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