Create new README file
[yap.git] / plugins / svn.py
blob2a78e9e384cdf2a4b6ed10561315ef3d1c4339fb
2 from yap import YapPlugin, 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)[0]
26 uuid = revmap.split('.')[-1]
27 if self.uuid is None:
28 self.uuid = uuid
29 assert self.uuid == uuid
30 data = file(revmap).read()
31 self.metadata[branch] = data
33 class SvnPlugin(YapPlugin):
34 "Allow yap to interoperate with Subversion repositories"
35 def __init__(self, yap):
36 self.yap = yap
38 def _get_root(self, url):
39 root = get_output("svn info %s 2>/dev/null | gawk '/Repository Root:/{print $3}'" % url)
40 if not root:
41 raise YapError("Not an SVN repo: %s" % url)
42 return root[0]
44 def _configure_repo(self, url, fetch=None):
45 root = self._get_root(url)
46 os.system("git config svn-remote.svn.url %s" % root)
47 if fetch is None:
48 trunk = url.replace(root, '').strip('/')
49 else:
50 trunk = fetch.split(':')[0]
51 os.system("git config svn-remote.svn.fetch %s:refs/remotes/svn/trunk"
52 % trunk)
54 branches = trunk.replace('trunk', 'branches')
55 if branches != trunk:
56 os.system("git config svn-remote.svn.branches %s/*:refs/remotes/svn/*" % branches)
57 tags = trunk.replace('trunk', 'tags')
58 if tags != trunk:
59 os.system("git config svn-remote.svn.tags %s/*:refs/tags/*" % tags)
60 self.yap.cmd_repo("svn", url)
61 os.system("git config yap.svn.enabled 1")
63 def _create_tagged_blob(self):
64 url = get_output("git config svn-remote.svn.url")[0]
65 fetch = get_output("git config --get-all svn-remote.svn.fetch")
66 blob = RepoBlob(url, fetch)
67 for b in get_output("git for-each-ref --format='%(refname)' 'refs/remotes/svn/*'"):
68 b = b.replace('refs/remotes/svn/', '')
69 blob.add_metadata(b)
71 fd_w, fd_r = os.popen2("git hash-object -w --stdin")
72 import __builtin__
73 __builtin__.RepoBlob = RepoBlob
74 pickle.dump(blob, fd_w)
75 fd_w.close()
76 hash = fd_r.readline().strip()
77 run_safely("git tag -f yap-svn %s" % hash)
79 def _clone_svn(self, url, directory=None, **flags):
80 url = url.rstrip('/')
81 if directory is None:
82 directory = url.rsplit('/')[-1]
83 directory = directory.replace('.git', '')
85 try:
86 os.mkdir(directory)
87 except OSError:
88 raise YapError("Directory exists: %s" % directory)
89 os.chdir(directory)
91 self.yap.cmd_init()
92 run_command("git config svn-remote.svn.noMetadata 1")
93 self._configure_repo(url)
94 os.system("git svn fetch -r %s:HEAD" % flags.get('-r', '1'))
95 self._create_tagged_blob()
97 def _push_svn(self, branch, **flags):
98 if '-d' in flags:
99 raise YapError("Deleting svn branches not supported")
100 print "Verifying branch is up-to-date"
101 run_safely("git svn fetch svn")
103 branch = branch.replace('refs/heads/', '')
104 rev = get_output("git rev-parse --verify refs/remotes/svn/%s" % branch)
106 # Create the branch if requested
107 if not rev:
108 if '-c' not in flags:
109 raise YapError("No matching branch on the repo. Use -c to create a new branch there.")
110 src = get_output("git svn info | gawk '/URL:/{print $2}'")[0]
111 brev = get_output("git svn info | gawk '/Revision:/{print $2}'")[0]
112 root = get_output("git config svn-remote.svn.url")[0]
113 branch_path = get_output("git config svn-remote.svn.branches")[0].split(':')[0]
114 branch_path = branch_path.rstrip('/*')
115 dst = '/'.join((root, branch_path, branch))
117 # Create the branch in svn
118 run_safely("svn cp -r%s %s %s -m 'create branch %s'"
119 % (brev, src, dst, branch))
120 run_safely("git svn fetch svn")
121 rev = get_output("git rev-parse refs/remotes/svn/%s 2>/dev/null" % branch)
122 base = get_output("git svn find-rev r%s" % brev)
124 # Apply our commits to the new branch
125 try:
126 fd, tmpfile = tempfile.mkstemp("yap")
127 os.close(fd)
128 print base[0]
129 os.system("git format-patch -k --stdout '%s' > %s"
130 % (base[0], tmpfile))
131 start = get_output("git rev-parse HEAD")
132 self.yap.cmd_point("refs/remotes/svn/%s"
133 % branch, **{'-f': True})
135 stat = os.stat(tmpfile)
136 size = stat[6]
137 if size > 0:
138 rc = run_command("git am -3 %s" % tmpfile)
139 if (rc):
140 self.yap.cmd_point(start[0], **{'-f': True})
141 raise YapError("Failed to port changes to new svn branch")
142 finally:
143 os.unlink(tmpfile)
145 base = get_output("git merge-base HEAD %s" % rev[0])
146 if base[0] != rev[0]:
147 raise YapError("Branch not up-to-date. Update first.")
148 current = get_output("git symbolic-ref HEAD")
149 if not current:
150 raise YapError("Not on a branch!")
151 current = current[0].replace('refs/heads/', '')
152 self.yap._confirm_push(current, branch, "svn")
153 if run_command("git update-index --refresh"):
154 raise YapError("Can't push with uncommitted changes")
156 master = get_output("git rev-parse --verify refs/heads/master 2>/dev/null")
157 os.system("git svn dcommit")
158 run_safely("git svn rebase")
159 if not master:
160 master = get_output("git rev-parse --verify refs/heads/master 2>/dev/null")
161 if master:
162 run_safely("git update-ref -d refs/heads/master %s" % master[0])
164 def _enabled(self):
165 enabled = get_output("git config yap.svn.enabled")
166 return bool(enabled)
168 # Ensure users don't accidentally kill our "svn" repo
169 def pre_repo(self, args, flags):
170 if not self._enabled():
171 return
172 if '-d' in flags and args and args[0] == "svn":
173 raise YapError("Refusing to delete special svn repository")
175 # Configure git-svn if we just cloned a yap-svn repo
176 def post_clone(self):
177 if self._enabled():
178 # nothing to do
179 return
181 run_safely("git fetch origin --tags")
182 hash = get_output("git rev-parse --verify refs/tags/yap-svn 2>/dev/null")
183 if not hash:
184 return
186 fd = os.popen("git cat-file blob %s" % hash[0])
187 import __builtin__
188 __builtin__.RepoBlob = RepoBlob
189 blob = pickle.load(fd)
190 self._configure_repo(blob.url, blob.fetch[0])
191 for extra in blob.fetch[1:]:
192 run_safely("git config svn-remote.svn.fetch %s" % extra)
194 for b in blob.metadata.keys():
195 branch = os.path.join(".git", "svn", "svn", b)
196 os.makedirs(branch)
197 fd = file(os.path.join(branch, ".rev_map.%s" % blob.uuid), "w")
198 fd.write(blob.metadata[b])
199 run_safely("git fetch origin 'refs/remotes/svn/*:refs/remotes/svn/*'")
201 @takes_options("r:")
202 def cmd_clone(self, *args, **flags):
203 if args and not run_command("svn info %s" % args[0]):
204 self._clone_svn(*args, **flags)
205 else:
206 self.yap._call_base("cmd_clone", *args, **flags)
208 def cmd_fetch(self, *args, **flags):
209 if self._enabled():
210 if args and args[0] == 'svn':
211 os.system("git svn fetch svn")
212 self._create_tagged_blob()
213 return
214 elif not args:
215 current = get_output("git symbolic-ref HEAD")
216 if not current:
217 raise YapError("Not on a branch!")
219 current = current[0].replace('refs/heads/', '')
220 remote, merge = self.yap._get_tracking(current)
221 if remote == "svn":
222 os.system("git svn fetch svn")
223 self._create_tagged_blob()
224 return
225 self.yap._call_base("cmd_fetch", *args, **flags)
227 def cmd_push(self, *args, **flags):
228 if self._enabled():
229 if args and args[0] == 'svn':
230 if len (args) < 2:
231 raise YapError("Need a branch name")
232 self._push_svn(args[1], **flags)
233 return
234 elif not args:
235 current = get_output("git symbolic-ref HEAD")
236 if not current:
237 raise YapError("Not on a branch!")
239 current = current[0].replace('refs/heads/', '')
240 remote, merge = self.yap._get_tracking(current)
241 if remote == "svn":
242 self._push_svn(merge, **flags)
243 return
245 self.yap._call_base("cmd_push", *args, **flags)
247 @short_help("change options for the svn plugin")
248 def cmd_svn(self, subcmd):
249 "enable"
251 if subcmd not in ["enable"]:
252 raise TypeError
254 if "svn" in [x[0] for x in self.yap._list_remotes()]:
255 raise YapError("A remote named 'svn' already exists")
258 if not run_command("git config svn-remote.svn.branches"):
259 raise YapError("Cannot currently enable in a repository with svn branches")
261 url = get_output("git config svn-remote.svn.url")
262 if not url:
263 raise YapError("Not a git-svn repository?")
264 fetch = get_output("git config svn-remote.svn.fetch")
265 assert fetch
266 lhs, rhs = fetch[0].split(':')
269 rev = get_output("git rev-parse %s" % rhs)
270 assert rev
271 run_safely("git update-ref refs/remotes/svn/trunk %s" % rev[0])
273 url = '/'.join((url[0], lhs))
274 self._configure_repo(url)
275 run_safely("git update-ref -d %s %s" % (rhs, rev[0]))