Mercurial > hg > mercurial-crew-with-dirclash
annotate contrib/convert-repo @ 857:41b344235bb7
[PATCH] Propagate the template map though recursively
This patch allows propagates the template map though recursively
though all the templates.
This allows for some hgweb template cleanup patches as well as it makes
writing new skins/themes for hgweb much much easier. (I'm planing to
write several basic ones.)
author | Jeff Sipek <jeffpc@optonline.net> |
---|---|
date | Mon, 08 Aug 2005 19:49:34 -0800 |
parents | 574869103985 |
children | 227cfbe34109 |
rev | line source |
---|---|
316 | 1 #!/usr/bin/env python |
2 # | |
3 # This is a generalized framework for converting between SCM | |
4 # repository formats. | |
5 # | |
6 # In its current form, it's hardcoded to convert incrementally between | |
7 # git and Mercurial. | |
8 # | |
9 # To use, you must first import the first git version into Mercurial, | |
10 # and establish a mapping between the git commit hash and the hash in | |
11 # Mercurial for that version. This mapping is kept in a simple text | |
12 # file with lines like so: | |
13 # | |
14 # <git hash> <mercurial hash> | |
15 # | |
16 # To convert the rest of the repo, run: | |
17 # | |
18 # convert-repo <git-dir> <hg-dir> <mapfile> | |
19 # | |
20 # This updates the mapfile on each commit copied, so it can be | |
21 # interrupted and can be run repeatedly to copy new commits. | |
22 | |
694 | 23 import sys, os, zlib, sha, time |
450 | 24 from mercurial import hg, ui, util |
316 | 25 |
26 class convert_git: | |
27 def __init__(self, path): | |
28 self.path = path | |
29 | |
30 def getheads(self): | |
31 h = file(self.path + "/.git/HEAD").read()[:-1] | |
32 return [h] | |
33 | |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
34 def catfile(self, rev, type): |
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
35 if rev == "0" * 40: raise IOError() |
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
36 path = os.getcwd() |
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
37 os.chdir(self.path) |
694 | 38 fh = os.popen("git-cat-file %s %s 2>/dev/null" % (type, rev)) |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
39 os.chdir(path) |
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
40 return fh.read() |
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
41 |
316 | 42 def getfile(self, name, rev): |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
43 return self.catfile(rev, "blob") |
316 | 44 |
45 def getchanges(self, version): | |
46 path = os.getcwd() | |
47 os.chdir(self.path) | |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
48 fh = os.popen("git-diff-tree --root -m -r %s" % (version)) |
316 | 49 os.chdir(path) |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
50 |
316 | 51 changes = [] |
52 for l in fh: | |
53 if "\t" not in l: continue | |
54 m, f = l[:-1].split("\t") | |
55 m = m.split() | |
56 h = m[3] | |
57 p = (m[1] == "100755") | |
58 changes.append((f, h, p)) | |
59 return changes | |
60 | |
61 def getcommit(self, version): | |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
62 c = self.catfile(version, "commit") # read the commit hash |
316 | 63 end = c.find("\n\n") |
64 message = c[end+2:] | |
65 l = c[:end].splitlines() | |
66 manifest = l[0].split()[1] | |
67 parents = [] | |
68 for e in l[1:]: | |
69 n,v = e.split(" ", 1) | |
70 if n == "author": | |
71 p = v.split() | |
72 date = " ".join(p[-2:]) | |
73 author = " ".join(p[:-2]) | |
74 if author[0] == "<": author = author[1:-1] | |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
75 if n == "committer": |
431 | 76 p = v.split() |
77 date = " ".join(p[-2:]) | |
78 committer = " ".join(p[:-2]) | |
79 if committer[0] == "<": committer = committer[1:-1] | |
80 message += "\ncommitter: %s %s\n" % (committer, date) | |
316 | 81 if n == "parent": parents.append(v) |
82 return (parents, author, date, message) | |
83 | |
694 | 84 def gettags(self): |
85 tags = {} | |
86 for f in os.listdir(self.path + "/.git/refs/tags"): | |
87 try: | |
88 h = file(self.path + "/.git/refs/tags/" + f).read().strip() | |
89 p, a, d, m = self.getcommit(h) | |
90 if not p: p = [h] # git is ugly, don't blame me | |
91 tags[f] = p[0] | |
92 except: | |
93 pass | |
94 return tags | |
95 | |
316 | 96 class convert_mercurial: |
97 def __init__(self, path): | |
98 self.path = path | |
99 u = ui.ui() | |
100 self.repo = hg.repository(u, path) | |
101 | |
102 def getheads(self): | |
103 h = self.repo.changelog.heads() | |
104 h = [ hg.hex(x) for x in h ] | |
105 return h | |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
106 |
316 | 107 def putfile(self, f, e, data): |
108 self.repo.wfile(f, "w").write(data) | |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
109 if self.repo.dirstate.state(f) == '?': |
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
110 self.repo.dirstate.update([f], "a") |
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
111 |
450 | 112 util.set_exec(self.repo.wjoin(f), e) |
316 | 113 |
114 def delfile(self, f): | |
115 try: | |
116 os.unlink(self.repo.wjoin(f)) | |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
117 #self.repo.remove([f]) |
316 | 118 except: |
119 pass | |
120 | |
121 def putcommit(self, files, parents, author, dest, text): | |
431 | 122 seen = {} |
123 pl = [] | |
124 for p in parents: | |
125 if p not in seen: | |
126 pl.append(p) | |
127 seen[p] = 1 | |
128 parents = pl | |
316 | 129 |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
130 if len(parents) < 2: parents.append("0" * 40) |
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
131 if len(parents) < 2: parents.append("0" * 40) |
431 | 132 p2 = parents.pop(0) |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
133 |
431 | 134 while parents: |
135 p1 = p2 | |
136 p2 = parents.pop(0) | |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
137 self.repo.rawcommit(files, text, author, dest, |
431 | 138 hg.bin(p1), hg.bin(p2)) |
139 text = "(octopus merge fixup)\n" | |
140 | |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
141 return hg.hex(self.repo.changelog.tip()) |
316 | 142 |
694 | 143 def puttags(self, tags): |
144 try: | |
145 old = self.repo.wfile(".hgtags").read() | |
146 oldlines = old.splitlines(1) | |
147 oldlines.sort() | |
148 except: | |
149 oldlines = [] | |
150 | |
151 k = tags.keys() | |
152 k.sort() | |
153 newlines = [] | |
154 for tag in k: | |
155 newlines.append("%s %s\n" % (tags[tag], tag)) | |
156 | |
157 newlines.sort() | |
158 | |
159 if newlines != oldlines: | |
160 print "updating tags" | |
161 f = self.repo.wfile(".hgtags", "w") | |
162 f.write("".join(newlines)) | |
163 f.close() | |
164 if not oldlines: self.repo.add([".hgtags"]) | |
165 date = "%s 0" % time.mktime(time.gmtime()) | |
166 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo", | |
167 date, self.repo.changelog.tip(), hg.nullid) | |
168 | |
316 | 169 class convert: |
170 def __init__(self, source, dest, mapfile): | |
171 self.source = source | |
172 self.dest = dest | |
173 self.mapfile = mapfile | |
174 self.commitcache = {} | |
175 | |
176 self.map = {} | |
177 for l in file(self.mapfile): | |
178 sv, dv = l[:-1].split() | |
179 self.map[sv] = dv | |
180 | |
181 def walktree(self, heads): | |
182 visit = heads | |
183 known = {} | |
184 parents = {} | |
185 while visit: | |
186 n = visit.pop(0) | |
187 if n in known or n in self.map: continue | |
188 known[n] = 1 | |
189 self.commitcache[n] = self.source.getcommit(n) | |
190 cp = self.commitcache[n][0] | |
191 for p in cp: | |
192 parents.setdefault(n, []).append(p) | |
193 visit.append(p) | |
194 | |
195 return parents | |
196 | |
197 def toposort(self, parents): | |
198 visit = parents.keys() | |
199 seen = {} | |
200 children = {} | |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
201 |
316 | 202 while visit: |
203 n = visit.pop(0) | |
204 if n in seen: continue | |
205 seen[n] = 1 | |
206 pc = 0 | |
207 if n in parents: | |
208 for p in parents[n]: | |
209 if p not in self.map: pc += 1 | |
210 visit.append(p) | |
211 children.setdefault(p, []).append(n) | |
212 if not pc: root = n | |
213 | |
214 s = [] | |
215 removed = {} | |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
216 visit = children.keys() |
316 | 217 while visit: |
218 n = visit.pop(0) | |
219 if n in removed: continue | |
220 dep = 0 | |
221 if n in parents: | |
222 for p in parents[n]: | |
223 if p in self.map: continue | |
224 if p not in removed: | |
225 # we're still dependent | |
226 visit.append(n) | |
227 dep = 1 | |
228 break | |
229 | |
230 if not dep: | |
231 # all n's parents are in the list | |
232 removed[n] = 1 | |
233 s.append(n) | |
234 if n in children: | |
235 for c in children[n]: | |
236 visit.insert(0, c) | |
237 | |
238 return s | |
239 | |
240 def copy(self, rev): | |
241 p, a, d, t = self.commitcache[rev] | |
242 files = self.source.getchanges(rev) | |
243 | |
244 for f,v,e in files: | |
245 try: | |
246 data = self.source.getfile(f, v) | |
247 except IOError, inst: | |
248 self.dest.delfile(f) | |
249 else: | |
250 self.dest.putfile(f, e, data) | |
251 | |
252 r = [self.map[v] for v in p] | |
253 f = [f for f,v,e in files] | |
254 self.map[rev] = self.dest.putcommit(f, r, a, d, t) | |
255 file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev])) | |
256 | |
257 def convert(self): | |
258 heads = self.source.getheads() | |
259 parents = self.walktree(heads) | |
260 t = self.toposort(parents) | |
261 num = len(t) | |
262 | |
263 for c in t: | |
264 num -= 1 | |
265 if c in self.map: continue | |
266 desc = self.commitcache[c][3].splitlines()[0] | |
267 print num, desc | |
268 self.copy(c) | |
269 | |
694 | 270 tags = self.source.gettags() |
271 ctags = {} | |
272 for k in tags: | |
273 v = tags[k] | |
274 if v in self.map: | |
275 ctags[k] = self.map[v] | |
276 | |
277 self.dest.puttags(ctags) | |
278 | |
316 | 279 gitpath, hgpath, mapfile = sys.argv[1:] |
280 | |
281 c = convert(convert_git(gitpath), convert_mercurial(hgpath), mapfile) | |
282 c.convert() |