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