2 from yap
.yap
import YapCore
, YapError
3 from yap
.util
import get_output
, takes_options
, run_command
, run_safely
, short_help
13 class RepoBlob(object):
14 def __init__(self
, keys
):
22 def add_metadata(self
, branch
):
23 assert branch
not in self
.metadata
24 gitdir
= get_output("git rev-parse --git-dir")
26 revmap
= os
.path
.join(gitdir
[0], "svn", "svn", branch
, ".rev_map*")
27 revmap
= glob
.glob(revmap
)
30 uuid
= revmap
[0].split('.')[-1]
33 assert self
.uuid
== uuid
34 rev
= get_output("git rev-parse refs/remotes/svn/%s" % branch
)
35 data
= file(revmap
[0]).read()
36 self
.metadata
[branch
] = rev
[0], data
38 # Helper class for dealing with SVN metadata
39 class SVNRevMap(object):
41 def __init__(self
, filename
):
42 self
.fd
= file(filename
, "rb")
43 size
= os
.stat(filename
)[6]
44 self
.nrecords
= size
/ self
.RECORD_SZ
46 def __getitem__(self
, index
):
47 if index
>= self
.nrecords
:
49 return self
.get_record(index
)[0]
54 def get_record(self
, index
):
55 self
.fd
.seek(index
* self
.RECORD_SZ
)
56 record
= self
.fd
.read(self
.RECORD_SZ
)
57 return self
.parse_record(record
)
59 def parse_record(self
, record
):
60 record
= struct
.unpack("!I20B", record
)
62 hash = map(lambda x
: "%02x" % x
, record
[1:])
66 class SvnPlugin(YapCore
):
67 "Allow yap to interoperate with Subversion repositories"
69 revpat
= re
.compile('^r(\d+)$')
71 def __init__(self
, *args
, **flags
):
72 super(SvnPlugin
, self
).__init
__(*args
, **flags
)
73 self
._svn
_next
_rev
= None
75 def _get_root(self
, url
):
76 root
= get_output("svn info %s 2>/dev/null | gawk '/Repository Root:/{print $3}'" % url
)
78 raise YapError("Not an SVN repo: %s" % url
)
81 def _configure_repo(self
, url
, fetch
=None):
82 root
= self
._get
_root
(url
)
83 os
.system("git config svn-remote.svn.url %s" % root
)
85 trunk
= url
.replace(root
, '').strip('/')
87 trunk
= fetch
.split(':')[0]
88 os
.system("git config svn-remote.svn.fetch %s:refs/remotes/svn/trunk"
91 branches
= trunk
.replace('trunk', 'branches')
93 os
.system("git config svn-remote.svn.branches %s/*:refs/remotes/svn/*" % branches
)
94 tags
= trunk
.replace('trunk', 'tags')
96 os
.system("git config svn-remote.svn.tags %s/*:refs/remotes/svn/tags/*" % tags
)
97 self
.cmd_repo("svn", url
)
98 os
.system("git config yap.svn.enabled 1")
100 def _create_tagged_blob(self
):
102 for i
in get_output("git config --list | grep ^svn-remote."):
105 blob
= RepoBlob(keys
)
106 for b
in get_output("git for-each-ref --format='%(refname)' 'refs/remotes/svn/*'"):
107 b
= b
.replace('refs/remotes/svn/', '')
110 fd_w
, fd_r
= os
.popen2("git hash-object -w --stdin")
111 pickle
.dump(blob
, fd_w
)
113 hash = fd_r
.readline().strip()
114 run_safely("git tag -f yap-svn %s" % hash)
116 def _cleanup_branches(self
):
117 for b
in get_output("git for-each-ref --format='%(refname)' 'refs/remotes/svn/*@*'"):
118 head
= b
.replace('refs/remotes/svn/', '')
119 path
= os
.path
.join(".git", "svn", "svn", head
)
120 files
= os
.listdir(path
)
122 os
.unlink(os
.path
.join(path
, f
))
125 ref
= get_output("git rev-parse %s" % b
)
127 run_safely("git update-ref -d %s %s" % (b
, ref
[0]))
129 def _clone_svn(self
, url
, directory
=None, **flags
):
130 url
= url
.rstrip('/')
131 if directory
is None:
132 directory
= url
.rsplit('/')[-1]
133 directory
= directory
.replace('.git', '')
138 raise YapError("Directory exists: %s" % directory
)
142 self
._configure
_repo
(url
)
143 os
.system("git svn fetch -r %s:HEAD" % flags
.get('-r', '1'))
145 self
._cleanup
_branches
()
146 self
._create
_tagged
_blob
()
148 def _push_svn(self
, branch
, **flags
):
150 raise YapError("Deleting svn branches not supported")
151 print "Verifying branch is up-to-date"
152 run_safely("git svn fetch svn")
154 branch
= branch
.replace('refs/heads/', '')
155 rev
= get_output("git rev-parse --verify refs/remotes/svn/%s" % branch
)
157 # Create the branch if requested
159 if '-c' not in flags
:
160 raise YapError("No matching branch on the repo. Use -c to create a new branch there.")
161 src
= get_output("git svn info | gawk '/URL:/{print $2}'")[0]
162 brev
= get_output("git svn info | gawk '/Revision:/{print $2}'")[0]
163 root
= get_output("git config svn-remote.svn.url")[0]
164 branch_path
= get_output("git config svn-remote.svn.branches")[0].split(':')[0]
165 branch_path
= branch_path
.rstrip('/*')
166 dst
= '/'.join((root
, branch_path
, branch
))
168 # Create the branch in svn
169 run_safely("svn cp -r%s %s %s -m 'create branch %s'"
170 % (brev
, src
, dst
, branch
))
171 run_safely("git svn fetch svn")
172 rev
= get_output("git rev-parse refs/remotes/svn/%s 2>/dev/null" % branch
)
173 base
= get_output("git svn find-rev r%s" % brev
)
175 # Apply our commits to the new branch
177 fd
, tmpfile
= tempfile
.mkstemp("yap")
179 os
.system("git format-patch -k --stdout '%s' > %s"
180 % (base
[0], tmpfile
))
181 start
= get_output("git rev-parse HEAD")
182 self
.cmd_point("refs/remotes/svn/%s"
183 % branch
, **{'-f': True})
185 stat
= os
.stat(tmpfile
)
188 rc
= run_command("git am -3 %s" % tmpfile
)
190 self
.cmd_point(start
[0], **{'-f': True})
191 raise YapError("Failed to port changes to new svn branch")
195 base
= get_output("git merge-base HEAD %s" % rev
[0])
196 if base
[0] != rev
[0]:
197 raise YapError("Branch not up-to-date. Update first.")
198 current
= get_output("git symbolic-ref HEAD")
200 raise YapError("Not on a branch!")
201 current
= current
[0].replace('refs/heads/', '')
202 self
._confirm
_push
(current
, branch
, "svn")
203 if run_command("git update-index --refresh"):
204 raise YapError("Can't push with uncommitted changes")
206 master
= get_output("git rev-parse --verify refs/heads/master 2>/dev/null")
207 os
.system("git svn dcommit")
208 run_safely("git svn rebase")
210 master
= get_output("git rev-parse --verify refs/heads/master 2>/dev/null")
212 run_safely("git update-ref -d refs/heads/master %s" % master
[0])
215 repo
= get_output('git rev-parse --git-dir')[0]
216 dir = os
.path
.join(repo
, 'yap')
217 fd
, tmplock
= tempfile
.mkstemp("yap", dir=dir)
221 lockfile
= os
.path
.join(dir, 'svn-lock')
223 os
.link(tmplock
, lockfile
)
225 raise YapError("A subversion operation is already in progress")
229 def _unlock_svn(self
):
230 repo
= get_output('git rev-parse --git-dir')[0]
231 dir = os
.path
.join(repo
, 'yap')
232 lockfile
= os
.path
.join(dir, 'svn-lock')
239 def _fetch_svn(self
):
241 os
.system("git svn fetch svn")
243 self
._create
_tagged
_blob
()
244 self
._cleanup
_branches
()
247 enabled
= get_output("git config yap.svn.enabled")
250 def _applicable(self
, args
):
251 if not self
._enabled
():
254 if args
and args
[0] == 'svn':
258 current
= get_output("git symbolic-ref HEAD")
260 raise YapError("Not on a branch!")
262 current
= current
[0].replace('refs/heads/', '')
263 remote
, merge
= self
._get
_tracking
(current
)
269 # Ensure users don't accidentally kill our "svn" repo
270 def cmd_repo(self
, *args
, **flags
):
272 if '-d' in flags
and args
and args
[0] == "svn":
273 raise YapError("Refusing to delete special svn repository")
274 super(SvnPlugin
, self
).cmd_repo(*args
, **flags
)
277 def cmd_clone(self
, *args
, **flags
):
281 if (handled
and not args
[0].startswith("http")
282 and not args
[0].startswith("svn")
283 and not args
[0].startswith("file://")):
285 if handled
and run_command("svn info %s" % args
[0]):
289 self
._clone
_svn
(*args
, **flags
)
291 super(SvnPlugin
, self
).cmd_clone(*args
, **flags
)
297 run_safely("git fetch origin --tags")
298 hash = get_output("git rev-parse --verify refs/tags/yap-svn 2>/dev/null")
302 fd
= os
.popen("git cat-file blob %s" % hash[0])
303 blob
= pickle
.load(fd
)
304 for k
, v
in blob
.keys
.items():
305 run_safely("git config %s %s" % (k
, v
))
307 self
.cmd_repo("svn", blob
.keys
['svn-remote.svn.url'])
308 os
.system("git config yap.svn.enabled 1")
309 run_safely("git fetch origin 'refs/remotes/svn/*:refs/remotes/svn/*'")
311 for b
in blob
.metadata
.keys():
312 branch
= os
.path
.join(".git", "svn", "svn", b
)
314 fd
= file(os
.path
.join(branch
, ".rev_map.%s" % blob
.uuid
), "w")
316 rev
, metadata
= blob
.metadata
[b
]
318 run_command("git update-ref refs/remotes/svn/%s %s" % (b
, rev
))
320 def cmd_fetch(self
, *args
, **flags
):
321 if self
._applicable
(args
):
325 super(SvnPlugin
, self
).cmd_fetch(*args
, **flags
)
327 def cmd_push(self
, *args
, **flags
):
328 if self
._applicable
(args
):
332 current
= get_output("git symbolic-ref HEAD")
334 raise YapError("Not on a branch!")
336 current
= current
[0].replace('refs/heads/', '')
337 remote
, merge
= self
._get
_tracking
(current
)
339 raise YapError("Need a branch name")
340 self
._push
_svn
(merge
, **flags
)
342 super(SvnPlugin
, self
).cmd_push(*args
, **flags
)
344 @short_help("change options for the svn plugin")
345 def cmd_svn(self
, subcmd
):
348 if subcmd
not in ["enable"]:
351 if "svn" in [x
[0] for x
in self
._list
_remotes
()]:
352 raise YapError("A remote named 'svn' already exists")
355 if not run_command("git config svn-remote.svn.branches"):
356 raise YapError("Cannot currently enable in a repository with svn branches")
358 url
= get_output("git config svn-remote.svn.url")
360 raise YapError("Not a git-svn repository?")
361 fetch
= get_output("git config svn-remote.svn.fetch")
363 lhs
, rhs
= fetch
[0].split(':')
366 rev
= get_output("git rev-parse %s" % rhs
)
368 run_safely("git update-ref refs/remotes/svn/trunk %s" % rev
[0])
370 url
= '/'.join((url
[0], lhs
))
371 self
._configure
_repo
(url
)
372 run_safely("git update-ref -d %s %s" % (rhs
, rev
[0]))
374 # We are intentionally overriding yap utility functions
375 def _filter_log(self
, commit
):
376 commit
= super(SvnPlugin
, self
)._filter
_log
(commit
)
380 if line
.strip().startswith("git-svn-id:"):
381 while not new
[-1].strip():
384 urlrev
= line
.strip().split(' ')[1]
385 url
, rev
= urlrev
.split('@')
386 hash = commit
[0].split(' ')[1].strip()
387 if self
._svn
_next
_rev
!= hash:
388 h2
= self
._resolve
_svn
_rev
(int(rev
))
392 next_hash
= get_output("git rev-parse --verify %s^" % hash)
394 self
._svn
_next
_rev
= next_hash
[0]
396 self
._svn
_next
_rev
= None
397 root
= get_output("git config svn-remote.svn.url")
399 url
= url
.replace(root
[0], '')
400 new
.insert(1, "Subversion: r%s %s\n" % (rev
, url
))
406 def _resolve_svn_rev(self
, revnum
):
408 gitdir
= get_output("git rev-parse --git-dir")
411 # Work with whateven svn remote is configured
412 remotes
= get_output("git config --get-regexp 'svn-remote.*.fetch'")
416 for remote
in remotes
:
417 remote
= remote
.split(' ')
418 remote
= remote
[1].split(':')
419 remote
= remote
[1].split('/')
421 path
= os
.path
.join(gitdir
[0], "svn", remote
,
423 revmaps
+= glob
.glob(path
)
427 idx
= bisect
.bisect_left(rm
, revnum
)
428 if idx
>= len(rm
) or rm
[idx
] != revnum
:
431 revnum
, rev
= rm
.get_record(idx
)
437 rev
= get_output("git svn find-rev r%d 2>/dev/null" % revnum
)
442 def _resolve_rev(self
, *args
, **flags
):
445 m
= self
.revpat
.match(args
[0])
447 revnum
= int(m
.group(1))
448 rev
= self
._resolve
_svn
_rev
(revnum
)
451 rev
= super(SvnPlugin
, self
)._resolve
_rev
(*args
, **flags
)