svn: remove hack in metadata blob
[yap.git] / plugins / svn.py
blob0807c220ebaf7bd6bcf31317a3efc311d68e1779
2 from yap.yap import YapCore, YapError
3 from yap.util import get_output, takes_options, run_command, run_safely, short_help
5 import os
6 import tempfile
7 import glob
8 import pickle
10 class RepoBlob(object):
11 def __init__(self, url, fetch):
12 self.url = url
13 self.fetch = fetch
15 self.uuid = None
16 self.branches = None
17 self.tags = None
18 self.metadata = {}
20 def add_metadata(self, branch):
21 assert branch not in self.metadata
22 gitdir = get_output("git rev-parse --git-dir")
23 assert gitdir
24 revmap = os.path.join(gitdir[0], "svn", "svn", branch, ".rev_map*")
25 revmap = glob.glob(revmap)
26 if not revmap:
27 return
28 uuid = revmap[0].split('.')[-1]
29 if self.uuid is None:
30 self.uuid = uuid
31 assert self.uuid == uuid
32 data = file(revmap[0]).read()
33 self.metadata[branch] = data
35 class SvnPlugin(YapCore):
36 "Allow yap to interoperate with Subversion repositories"
37 def _get_root(self, url):
38 root = get_output("svn info %s 2>/dev/null | gawk '/Repository Root:/{print $3}'" % url)
39 if not root:
40 raise YapError("Not an SVN repo: %s" % url)
41 return root[0]
43 def _configure_repo(self, url, fetch=None):
44 root = self._get_root(url)
45 os.system("git config svn-remote.svn.url %s" % root)
46 if fetch is None:
47 trunk = url.replace(root, '').strip('/')
48 else:
49 trunk = fetch.split(':')[0]
50 os.system("git config svn-remote.svn.fetch %s:refs/remotes/svn/trunk"
51 % trunk)
53 branches = trunk.replace('trunk', 'branches')
54 if branches != trunk:
55 os.system("git config svn-remote.svn.branches %s/*:refs/remotes/svn/*" % branches)
56 tags = trunk.replace('trunk', 'tags')
57 if tags != trunk:
58 os.system("git config svn-remote.svn.tags %s/*:refs/tags/*" % tags)
59 self.cmd_repo("svn", url)
60 os.system("git config yap.svn.enabled 1")
62 def _create_tagged_blob(self):
63 url = get_output("git config svn-remote.svn.url")[0]
64 fetch = get_output("git config --get-all svn-remote.svn.fetch")
65 blob = RepoBlob(url, fetch)
66 for b in get_output("git for-each-ref --format='%(refname)' 'refs/remotes/svn/*'"):
67 b = b.replace('refs/remotes/svn/', '')
68 blob.add_metadata(b)
70 fd_w, fd_r = os.popen2("git hash-object -w --stdin")
71 pickle.dump(blob, fd_w)
72 fd_w.close()
73 hash = fd_r.readline().strip()
74 run_safely("git tag -f yap-svn %s" % hash)
76 def _cleanup_branches(self):
77 for b in get_output("git for-each-ref --format='%(refname)' 'refs/remotes/svn/*@*'"):
78 head = b.replace('refs/remotes/svn/', '')
79 path = os.path.join(".git", "svn", "svn", head)
80 files = os.listdir(path)
81 for f in files:
82 os.unlink(os.path.join(path, f))
83 os.rmdir(path)
85 ref = get_output("git rev-parse %s" % b)
86 if ref:
87 run_safely("git update-ref -d %s %s" % (b, ref[0]))
89 def _clone_svn(self, url, directory=None, **flags):
90 url = url.rstrip('/')
91 if directory is None:
92 directory = url.rsplit('/')[-1]
93 directory = directory.replace('.git', '')
95 try:
96 os.mkdir(directory)
97 except OSError:
98 raise YapError("Directory exists: %s" % directory)
99 os.chdir(directory)
101 self.cmd_init()
102 run_command("git config svn-remote.svn.noMetadata 1")
103 self._configure_repo(url)
104 os.system("git svn fetch -r %s:HEAD" % flags.get('-r', '1'))
106 self._cleanup_branches()
107 self._create_tagged_blob()
109 def _push_svn(self, branch, **flags):
110 if '-d' in flags:
111 raise YapError("Deleting svn branches not supported")
112 print "Verifying branch is up-to-date"
113 run_safely("git svn fetch svn")
115 branch = branch.replace('refs/heads/', '')
116 rev = get_output("git rev-parse --verify refs/remotes/svn/%s" % branch)
118 # Create the branch if requested
119 if not rev:
120 if '-c' not in flags:
121 raise YapError("No matching branch on the repo. Use -c to create a new branch there.")
122 src = get_output("git svn info | gawk '/URL:/{print $2}'")[0]
123 brev = get_output("git svn info | gawk '/Revision:/{print $2}'")[0]
124 root = get_output("git config svn-remote.svn.url")[0]
125 branch_path = get_output("git config svn-remote.svn.branches")[0].split(':')[0]
126 branch_path = branch_path.rstrip('/*')
127 dst = '/'.join((root, branch_path, branch))
129 # Create the branch in svn
130 run_safely("svn cp -r%s %s %s -m 'create branch %s'"
131 % (brev, src, dst, branch))
132 run_safely("git svn fetch svn")
133 rev = get_output("git rev-parse refs/remotes/svn/%s 2>/dev/null" % branch)
134 base = get_output("git svn find-rev r%s" % brev)
136 # Apply our commits to the new branch
137 try:
138 fd, tmpfile = tempfile.mkstemp("yap")
139 os.close(fd)
140 print base[0]
141 os.system("git format-patch -k --stdout '%s' > %s"
142 % (base[0], tmpfile))
143 start = get_output("git rev-parse HEAD")
144 self.cmd_point("refs/remotes/svn/%s"
145 % branch, **{'-f': True})
147 stat = os.stat(tmpfile)
148 size = stat[6]
149 if size > 0:
150 rc = run_command("git am -3 %s" % tmpfile)
151 if (rc):
152 self.cmd_point(start[0], **{'-f': True})
153 raise YapError("Failed to port changes to new svn branch")
154 finally:
155 os.unlink(tmpfile)
157 base = get_output("git merge-base HEAD %s" % rev[0])
158 if base[0] != rev[0]:
159 raise YapError("Branch not up-to-date. Update first.")
160 current = get_output("git symbolic-ref HEAD")
161 if not current:
162 raise YapError("Not on a branch!")
163 current = current[0].replace('refs/heads/', '')
164 self._confirm_push(current, branch, "svn")
165 if run_command("git update-index --refresh"):
166 raise YapError("Can't push with uncommitted changes")
168 master = get_output("git rev-parse --verify refs/heads/master 2>/dev/null")
169 os.system("git svn dcommit")
170 run_safely("git svn rebase")
171 if not master:
172 master = get_output("git rev-parse --verify refs/heads/master 2>/dev/null")
173 if master:
174 run_safely("git update-ref -d refs/heads/master %s" % master[0])
176 def _fetch_svn(self):
177 os.system("git svn fetch svn")
178 self._create_tagged_blob()
179 self._cleanup_branches()
181 def _enabled(self):
182 enabled = get_output("git config yap.svn.enabled")
183 return bool(enabled)
185 def _applicable(self, args):
186 if not self._enabled():
187 return False
189 if args and args[0] == 'svn':
190 return True
192 if not args:
193 current = get_output("git symbolic-ref HEAD")
194 if not current:
195 raise YapError("Not on a branch!")
197 current = current[0].replace('refs/heads/', '')
198 remote, merge = self._get_tracking(current)
199 if remote == "svn":
200 return True
202 return False
204 # Ensure users don't accidentally kill our "svn" repo
205 def cmd_repo(self, *args, **flags):
206 if self._enabled():
207 if '-d' in flags and args and args[0] == "svn":
208 raise YapError("Refusing to delete special svn repository")
209 super(SvnPlugin, self).cmd_repo(*args, **flags)
211 @takes_options("r:")
212 def cmd_clone(self, *args, **flags):
213 handled = True
214 if not args:
215 handled = False
216 if (handled and not args[0].startswith("http")
217 and not args[0].startswith("svn")):
218 handled = False
219 if handled and run_command("svn info %s" % args[0]):
220 handled = False
222 if handled:
223 self._clone_svn(*args, **flags)
224 else:
225 super(SvnPlugin, self).cmd_clone(*args, **flags)
227 if self._enabled():
228 # nothing to do
229 return
231 run_safely("git fetch origin --tags")
232 hash = get_output("git rev-parse --verify refs/tags/yap-svn 2>/dev/null")
233 if not hash:
234 return
236 fd = os.popen("git cat-file blob %s" % hash[0])
237 blob = pickle.load(fd)
238 self._configure_repo(blob.url, blob.fetch[0])
239 for extra in blob.fetch[1:]:
240 run_safely("git config svn-remote.svn.fetch %s" % extra)
242 for b in blob.metadata.keys():
243 branch = os.path.join(".git", "svn", "svn", b)
244 os.makedirs(branch)
245 fd = file(os.path.join(branch, ".rev_map.%s" % blob.uuid), "w")
246 fd.write(blob.metadata[b])
247 run_safely("git fetch origin 'refs/remotes/svn/*:refs/remotes/svn/*'")
250 def cmd_fetch(self, *args, **flags):
251 if self._applicable(args):
252 self._fetch_svn()
253 return
255 super(SvnPlugin, self).cmd_fetch(*args, **flags)
257 def cmd_push(self, *args, **flags):
258 if self._applicable(args):
259 if len (args) >= 2:
260 merge = args[1]
261 else:
262 current = get_output("git symbolic-ref HEAD")
263 if not current:
264 raise YapError("Not on a branch!")
266 current = current[0].replace('refs/heads/', '')
267 remote, merge = self._get_tracking(current)
268 if remote != "svn":
269 raise YapError("Need a branch name")
270 self._push_svn(merge, **flags)
271 return
272 super(SvnPlugin, self).cmd_push(*args, **flags)
274 @short_help("change options for the svn plugin")
275 def cmd_svn(self, subcmd):
276 "enable"
278 if subcmd not in ["enable"]:
279 raise TypeError
281 if "svn" in [x[0] for x in self._list_remotes()]:
282 raise YapError("A remote named 'svn' already exists")
285 if not run_command("git config svn-remote.svn.branches"):
286 raise YapError("Cannot currently enable in a repository with svn branches")
288 url = get_output("git config svn-remote.svn.url")
289 if not url:
290 raise YapError("Not a git-svn repository?")
291 fetch = get_output("git config svn-remote.svn.fetch")
292 assert fetch
293 lhs, rhs = fetch[0].split(':')
296 rev = get_output("git rev-parse %s" % rhs)
297 assert rev
298 run_safely("git update-ref refs/remotes/svn/trunk %s" % rev[0])
300 url = '/'.join((url[0], lhs))
301 self._configure_repo(url)
302 run_safely("git update-ref -d %s %s" % (rhs, rev[0]))