Mercurial > hg > mercurial-crew-with-dirclash
annotate contrib/convert-repo @ 1705:4b5725a4a0a6
add changenav at bottom of page
if one reads through the changelog and wants to go to the
previous or next page one needs the navigation links at
the bottom, not at the top of the page
author | Johannes Stezenbach <js@linuxtv.org> |
---|---|
date | Tue, 07 Feb 2006 23:01:11 -0600 |
parents | 14d73fff4c45 |
children | 40346aa66b0f |
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 |
1656
14d73fff4c45
convert-repo: use commit rather than rawcommit
Matt Mackall <mpm@selenic.com>
parents:
1655
diff
changeset
|
24 from mercurial import hg, ui, util, commands |
316 | 25 |
26 class convert_git: | |
27 def __init__(self, path): | |
28 self.path = path | |
29 | |
30 def getheads(self): | |
1335
bea6356b8bca
git -> hg conversion script
Florian La Roche <laroche@redhat.com>
parents:
1237
diff
changeset
|
31 return [file(self.path + "/HEAD").read()[:-1]] |
316 | 32 |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
33 def catfile(self, rev, type): |
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
34 if rev == "0" * 40: raise IOError() |
1335
bea6356b8bca
git -> hg conversion script
Florian La Roche <laroche@redhat.com>
parents:
1237
diff
changeset
|
35 fh = os.popen("GIT_DIR=%s git-cat-file %s %s 2>/dev/null" % (self.path, type, rev)) |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
36 return fh.read() |
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
37 |
316 | 38 def getfile(self, name, rev): |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
39 return self.catfile(rev, "blob") |
316 | 40 |
41 def getchanges(self, version): | |
1335
bea6356b8bca
git -> hg conversion script
Florian La Roche <laroche@redhat.com>
parents:
1237
diff
changeset
|
42 fh = os.popen("GIT_DIR=%s git-diff-tree --root -m -r %s" % (self.path, version)) |
316 | 43 changes = [] |
44 for l in fh: | |
45 if "\t" not in l: continue | |
46 m, f = l[:-1].split("\t") | |
47 m = m.split() | |
48 h = m[3] | |
49 p = (m[1] == "100755") | |
50 changes.append((f, h, p)) | |
51 return changes | |
52 | |
53 def getcommit(self, version): | |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
54 c = self.catfile(version, "commit") # read the commit hash |
316 | 55 end = c.find("\n\n") |
56 message = c[end+2:] | |
57 l = c[:end].splitlines() | |
58 manifest = l[0].split()[1] | |
59 parents = [] | |
60 for e in l[1:]: | |
61 n,v = e.split(" ", 1) | |
62 if n == "author": | |
63 p = v.split() | |
1385
adb3de56635b
convert-repo: Fix timezone handling
Matt Mackall <mpm@selenic.com>
parents:
1335
diff
changeset
|
64 tm, tz = p[-2:] |
316 | 65 author = " ".join(p[:-2]) |
66 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
|
67 if n == "committer": |
431 | 68 p = v.split() |
1385
adb3de56635b
convert-repo: Fix timezone handling
Matt Mackall <mpm@selenic.com>
parents:
1335
diff
changeset
|
69 tm, tz = p[-2:] |
431 | 70 committer = " ".join(p[:-2]) |
71 if committer[0] == "<": committer = committer[1:-1] | |
1385
adb3de56635b
convert-repo: Fix timezone handling
Matt Mackall <mpm@selenic.com>
parents:
1335
diff
changeset
|
72 message += "\ncommitter: %s\n" % v |
316 | 73 if n == "parent": parents.append(v) |
1385
adb3de56635b
convert-repo: Fix timezone handling
Matt Mackall <mpm@selenic.com>
parents:
1335
diff
changeset
|
74 |
adb3de56635b
convert-repo: Fix timezone handling
Matt Mackall <mpm@selenic.com>
parents:
1335
diff
changeset
|
75 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:] |
adb3de56635b
convert-repo: Fix timezone handling
Matt Mackall <mpm@selenic.com>
parents:
1335
diff
changeset
|
76 tz = int(tzs) * (int(tzh) * 3600 + int(tzm)) |
adb3de56635b
convert-repo: Fix timezone handling
Matt Mackall <mpm@selenic.com>
parents:
1335
diff
changeset
|
77 date = tm + " " + str(tz) |
316 | 78 return (parents, author, date, message) |
79 | |
694 | 80 def gettags(self): |
81 tags = {} | |
1335
bea6356b8bca
git -> hg conversion script
Florian La Roche <laroche@redhat.com>
parents:
1237
diff
changeset
|
82 for f in os.listdir(self.path + "/refs/tags"): |
694 | 83 try: |
1335
bea6356b8bca
git -> hg conversion script
Florian La Roche <laroche@redhat.com>
parents:
1237
diff
changeset
|
84 h = file(self.path + "/refs/tags/" + f).read().strip() |
1386
a1040345fdda
convert-repo: retrieve the commit hash from the tag object for tag import
Matt Mackall <mpm@selenic.com>
parents:
1385
diff
changeset
|
85 c = self.catfile(h, "tag") # read the commit hash |
a1040345fdda
convert-repo: retrieve the commit hash from the tag object for tag import
Matt Mackall <mpm@selenic.com>
parents:
1385
diff
changeset
|
86 h = c.splitlines()[0].split()[1] |
1237 | 87 tags[f] = h |
694 | 88 except: |
89 pass | |
90 return tags | |
91 | |
316 | 92 class convert_mercurial: |
93 def __init__(self, path): | |
94 self.path = path | |
95 u = ui.ui() | |
96 self.repo = hg.repository(u, path) | |
97 | |
98 def getheads(self): | |
99 h = self.repo.changelog.heads() | |
1335
bea6356b8bca
git -> hg conversion script
Florian La Roche <laroche@redhat.com>
parents:
1237
diff
changeset
|
100 return [ hg.hex(x) for x in h ] |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
101 |
316 | 102 def putfile(self, f, e, data): |
103 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
|
104 if self.repo.dirstate.state(f) == '?': |
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
105 self.repo.dirstate.update([f], "a") |
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
106 |
450 | 107 util.set_exec(self.repo.wjoin(f), e) |
316 | 108 |
109 def delfile(self, f): | |
110 try: | |
111 os.unlink(self.repo.wjoin(f)) | |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
112 #self.repo.remove([f]) |
316 | 113 except: |
114 pass | |
115 | |
1656
14d73fff4c45
convert-repo: use commit rather than rawcommit
Matt Mackall <mpm@selenic.com>
parents:
1655
diff
changeset
|
116 def putcommit(self, files, parents, author, date, text): |
431 | 117 seen = {} |
118 pl = [] | |
119 for p in parents: | |
120 if p not in seen: | |
121 pl.append(p) | |
122 seen[p] = 1 | |
123 parents = pl | |
316 | 124 |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
125 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
|
126 if len(parents) < 2: parents.append("0" * 40) |
431 | 127 p2 = parents.pop(0) |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
128 |
431 | 129 while parents: |
130 p1 = p2 | |
131 p2 = parents.pop(0) | |
1656
14d73fff4c45
convert-repo: use commit rather than rawcommit
Matt Mackall <mpm@selenic.com>
parents:
1655
diff
changeset
|
132 self.repo.dirstate.setparents(hg.bin(p1), hg.bin(p2)) |
14d73fff4c45
convert-repo: use commit rather than rawcommit
Matt Mackall <mpm@selenic.com>
parents:
1655
diff
changeset
|
133 if len(files) > 0: |
14d73fff4c45
convert-repo: use commit rather than rawcommit
Matt Mackall <mpm@selenic.com>
parents:
1655
diff
changeset
|
134 olddir = os.getcwd() |
14d73fff4c45
convert-repo: use commit rather than rawcommit
Matt Mackall <mpm@selenic.com>
parents:
1655
diff
changeset
|
135 os.chdir(self.path) |
14d73fff4c45
convert-repo: use commit rather than rawcommit
Matt Mackall <mpm@selenic.com>
parents:
1655
diff
changeset
|
136 commands.addremove(self.repo.ui, self.repo, *files) |
14d73fff4c45
convert-repo: use commit rather than rawcommit
Matt Mackall <mpm@selenic.com>
parents:
1655
diff
changeset
|
137 os.chdir(olddir) |
14d73fff4c45
convert-repo: use commit rather than rawcommit
Matt Mackall <mpm@selenic.com>
parents:
1655
diff
changeset
|
138 self.repo.commit(files, text, author, date) |
431 | 139 text = "(octopus merge fixup)\n" |
1389
9b3ef6f3cef5
convert-repo: fix up octopus merge conversion
Matt Mackall <mpm@selenic.com>
parents:
1388
diff
changeset
|
140 p2 = hg.hex(self.repo.changelog.tip()) |
431 | 141 |
1389
9b3ef6f3cef5
convert-repo: fix up octopus merge conversion
Matt Mackall <mpm@selenic.com>
parents:
1388
diff
changeset
|
142 return p2 |
316 | 143 |
694 | 144 def puttags(self, tags): |
145 try: | |
146 old = self.repo.wfile(".hgtags").read() | |
147 oldlines = old.splitlines(1) | |
148 oldlines.sort() | |
149 except: | |
150 oldlines = [] | |
151 | |
152 k = tags.keys() | |
153 k.sort() | |
154 newlines = [] | |
155 for tag in k: | |
156 newlines.append("%s %s\n" % (tags[tag], tag)) | |
157 | |
158 newlines.sort() | |
159 | |
160 if newlines != oldlines: | |
1335
bea6356b8bca
git -> hg conversion script
Florian La Roche <laroche@redhat.com>
parents:
1237
diff
changeset
|
161 #print "updating tags" |
694 | 162 f = self.repo.wfile(".hgtags", "w") |
163 f.write("".join(newlines)) | |
164 f.close() | |
165 if not oldlines: self.repo.add([".hgtags"]) | |
1335
bea6356b8bca
git -> hg conversion script
Florian La Roche <laroche@redhat.com>
parents:
1237
diff
changeset
|
166 date = "%s 0" % int(time.mktime(time.gmtime())) |
694 | 167 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo", |
168 date, self.repo.changelog.tip(), hg.nullid) | |
1387
0c7e8d345564
convert-repo: linearize the tag commit
Matt Mackall <mpm@selenic.com>
parents:
1386
diff
changeset
|
169 return hg.hex(self.repo.changelog.tip()) |
694 | 170 |
316 | 171 class convert: |
172 def __init__(self, source, dest, mapfile): | |
173 self.source = source | |
174 self.dest = dest | |
175 self.mapfile = mapfile | |
176 self.commitcache = {} | |
177 | |
178 self.map = {} | |
1655
7bfd4724932a
convert-repo: automatically create empty map file
Matt Mackall <mpm@selenic.com>
parents:
1389
diff
changeset
|
179 try: |
7bfd4724932a
convert-repo: automatically create empty map file
Matt Mackall <mpm@selenic.com>
parents:
1389
diff
changeset
|
180 for l in file(self.mapfile): |
7bfd4724932a
convert-repo: automatically create empty map file
Matt Mackall <mpm@selenic.com>
parents:
1389
diff
changeset
|
181 sv, dv = l[:-1].split() |
7bfd4724932a
convert-repo: automatically create empty map file
Matt Mackall <mpm@selenic.com>
parents:
1389
diff
changeset
|
182 self.map[sv] = dv |
7bfd4724932a
convert-repo: automatically create empty map file
Matt Mackall <mpm@selenic.com>
parents:
1389
diff
changeset
|
183 except IOError: |
7bfd4724932a
convert-repo: automatically create empty map file
Matt Mackall <mpm@selenic.com>
parents:
1389
diff
changeset
|
184 pass |
316 | 185 |
186 def walktree(self, heads): | |
187 visit = heads | |
188 known = {} | |
189 parents = {} | |
190 while visit: | |
191 n = visit.pop(0) | |
192 if n in known or n in self.map: continue | |
193 known[n] = 1 | |
194 self.commitcache[n] = self.source.getcommit(n) | |
195 cp = self.commitcache[n][0] | |
196 for p in cp: | |
197 parents.setdefault(n, []).append(p) | |
198 visit.append(p) | |
199 | |
200 return parents | |
201 | |
202 def toposort(self, parents): | |
203 visit = parents.keys() | |
204 seen = {} | |
205 children = {} | |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
206 |
316 | 207 while visit: |
208 n = visit.pop(0) | |
209 if n in seen: continue | |
210 seen[n] = 1 | |
211 pc = 0 | |
212 if n in parents: | |
213 for p in parents[n]: | |
214 if p not in self.map: pc += 1 | |
215 visit.append(p) | |
216 children.setdefault(p, []).append(n) | |
217 if not pc: root = n | |
218 | |
219 s = [] | |
220 removed = {} | |
692
695dd9a491da
convert-repo: deal with packed git and other fixes
mpm@selenic.com
parents:
450
diff
changeset
|
221 visit = children.keys() |
316 | 222 while visit: |
223 n = visit.pop(0) | |
224 if n in removed: continue | |
225 dep = 0 | |
226 if n in parents: | |
227 for p in parents[n]: | |
228 if p in self.map: continue | |
229 if p not in removed: | |
230 # we're still dependent | |
231 visit.append(n) | |
232 dep = 1 | |
233 break | |
234 | |
235 if not dep: | |
236 # all n's parents are in the list | |
237 removed[n] = 1 | |
238 s.append(n) | |
239 if n in children: | |
240 for c in children[n]: | |
241 visit.insert(0, c) | |
242 | |
243 return s | |
244 | |
245 def copy(self, rev): | |
246 p, a, d, t = self.commitcache[rev] | |
247 files = self.source.getchanges(rev) | |
248 | |
249 for f,v,e in files: | |
250 try: | |
251 data = self.source.getfile(f, v) | |
252 except IOError, inst: | |
253 self.dest.delfile(f) | |
254 else: | |
255 self.dest.putfile(f, e, data) | |
256 | |
257 r = [self.map[v] for v in p] | |
258 f = [f for f,v,e in files] | |
259 self.map[rev] = self.dest.putcommit(f, r, a, d, t) | |
260 file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev])) | |
261 | |
262 def convert(self): | |
263 heads = self.source.getheads() | |
264 parents = self.walktree(heads) | |
265 t = self.toposort(parents) | |
1388
5eb2d3c54165
convert-repo: change duplicate elimination
Matt Mackall <mpm@selenic.com>
parents:
1387
diff
changeset
|
266 t = [n for n in t if n not in self.map] |
316 | 267 num = len(t) |
268 | |
269 for c in t: | |
270 num -= 1 | |
271 desc = self.commitcache[c][3].splitlines()[0] | |
1335
bea6356b8bca
git -> hg conversion script
Florian La Roche <laroche@redhat.com>
parents:
1237
diff
changeset
|
272 #print num, desc |
316 | 273 self.copy(c) |
274 | |
694 | 275 tags = self.source.gettags() |
276 ctags = {} | |
277 for k in tags: | |
278 v = tags[k] | |
279 if v in self.map: | |
280 ctags[k] = self.map[v] | |
281 | |
1387
0c7e8d345564
convert-repo: linearize the tag commit
Matt Mackall <mpm@selenic.com>
parents:
1386
diff
changeset
|
282 if ctags: |
0c7e8d345564
convert-repo: linearize the tag commit
Matt Mackall <mpm@selenic.com>
parents:
1386
diff
changeset
|
283 nrev = self.dest.puttags(ctags) |
0c7e8d345564
convert-repo: linearize the tag commit
Matt Mackall <mpm@selenic.com>
parents:
1386
diff
changeset
|
284 # write another hash correspondence to override the previous |
0c7e8d345564
convert-repo: linearize the tag commit
Matt Mackall <mpm@selenic.com>
parents:
1386
diff
changeset
|
285 # one so we don't end up with extra tag heads |
0c7e8d345564
convert-repo: linearize the tag commit
Matt Mackall <mpm@selenic.com>
parents:
1386
diff
changeset
|
286 file(self.mapfile, "a").write("%s %s\n" % (c, nrev)) |
694 | 287 |
316 | 288 gitpath, hgpath, mapfile = sys.argv[1:] |
1335
bea6356b8bca
git -> hg conversion script
Florian La Roche <laroche@redhat.com>
parents:
1237
diff
changeset
|
289 if os.path.isdir(gitpath + "/.git"): |
bea6356b8bca
git -> hg conversion script
Florian La Roche <laroche@redhat.com>
parents:
1237
diff
changeset
|
290 gitpath += "/.git" |
316 | 291 |
292 c = convert(convert_git(gitpath), convert_mercurial(hgpath), mapfile) | |
293 c.convert() |