##// END OF EJS Templates
convert-repo: automatically create empty map file
Matt Mackall -
r1655:7bfd4724 default
parent child Browse files
Show More
@@ -1,285 +1,288 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 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 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 132 self.repo.rawcommit(files, text, author, dest,
133 133 hg.bin(p1), hg.bin(p2))
134 134 text = "(octopus merge fixup)\n"
135 135 p2 = hg.hex(self.repo.changelog.tip())
136 136
137 137 return p2
138 138
139 139 def puttags(self, tags):
140 140 try:
141 141 old = self.repo.wfile(".hgtags").read()
142 142 oldlines = old.splitlines(1)
143 143 oldlines.sort()
144 144 except:
145 145 oldlines = []
146 146
147 147 k = tags.keys()
148 148 k.sort()
149 149 newlines = []
150 150 for tag in k:
151 151 newlines.append("%s %s\n" % (tags[tag], tag))
152 152
153 153 newlines.sort()
154 154
155 155 if newlines != oldlines:
156 156 #print "updating tags"
157 157 f = self.repo.wfile(".hgtags", "w")
158 158 f.write("".join(newlines))
159 159 f.close()
160 160 if not oldlines: self.repo.add([".hgtags"])
161 161 date = "%s 0" % int(time.mktime(time.gmtime()))
162 162 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
163 163 date, self.repo.changelog.tip(), hg.nullid)
164 164 return hg.hex(self.repo.changelog.tip())
165 165
166 166 class convert:
167 167 def __init__(self, source, dest, mapfile):
168 168 self.source = source
169 169 self.dest = dest
170 170 self.mapfile = mapfile
171 171 self.commitcache = {}
172 172
173 173 self.map = {}
174 for l in file(self.mapfile):
175 sv, dv = l[:-1].split()
176 self.map[sv] = dv
174 try:
175 for l in file(self.mapfile):
176 sv, dv = l[:-1].split()
177 self.map[sv] = dv
178 except IOError:
179 pass
177 180
178 181 def walktree(self, heads):
179 182 visit = heads
180 183 known = {}
181 184 parents = {}
182 185 while visit:
183 186 n = visit.pop(0)
184 187 if n in known or n in self.map: continue
185 188 known[n] = 1
186 189 self.commitcache[n] = self.source.getcommit(n)
187 190 cp = self.commitcache[n][0]
188 191 for p in cp:
189 192 parents.setdefault(n, []).append(p)
190 193 visit.append(p)
191 194
192 195 return parents
193 196
194 197 def toposort(self, parents):
195 198 visit = parents.keys()
196 199 seen = {}
197 200 children = {}
198 201
199 202 while visit:
200 203 n = visit.pop(0)
201 204 if n in seen: continue
202 205 seen[n] = 1
203 206 pc = 0
204 207 if n in parents:
205 208 for p in parents[n]:
206 209 if p not in self.map: pc += 1
207 210 visit.append(p)
208 211 children.setdefault(p, []).append(n)
209 212 if not pc: root = n
210 213
211 214 s = []
212 215 removed = {}
213 216 visit = children.keys()
214 217 while visit:
215 218 n = visit.pop(0)
216 219 if n in removed: continue
217 220 dep = 0
218 221 if n in parents:
219 222 for p in parents[n]:
220 223 if p in self.map: continue
221 224 if p not in removed:
222 225 # we're still dependent
223 226 visit.append(n)
224 227 dep = 1
225 228 break
226 229
227 230 if not dep:
228 231 # all n's parents are in the list
229 232 removed[n] = 1
230 233 s.append(n)
231 234 if n in children:
232 235 for c in children[n]:
233 236 visit.insert(0, c)
234 237
235 238 return s
236 239
237 240 def copy(self, rev):
238 241 p, a, d, t = self.commitcache[rev]
239 242 files = self.source.getchanges(rev)
240 243
241 244 for f,v,e in files:
242 245 try:
243 246 data = self.source.getfile(f, v)
244 247 except IOError, inst:
245 248 self.dest.delfile(f)
246 249 else:
247 250 self.dest.putfile(f, e, data)
248 251
249 252 r = [self.map[v] for v in p]
250 253 f = [f for f,v,e in files]
251 254 self.map[rev] = self.dest.putcommit(f, r, a, d, t)
252 255 file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev]))
253 256
254 257 def convert(self):
255 258 heads = self.source.getheads()
256 259 parents = self.walktree(heads)
257 260 t = self.toposort(parents)
258 261 t = [n for n in t if n not in self.map]
259 262 num = len(t)
260 263
261 264 for c in t:
262 265 num -= 1
263 266 desc = self.commitcache[c][3].splitlines()[0]
264 267 #print num, desc
265 268 self.copy(c)
266 269
267 270 tags = self.source.gettags()
268 271 ctags = {}
269 272 for k in tags:
270 273 v = tags[k]
271 274 if v in self.map:
272 275 ctags[k] = self.map[v]
273 276
274 277 if ctags:
275 278 nrev = self.dest.puttags(ctags)
276 279 # write another hash correspondence to override the previous
277 280 # one so we don't end up with extra tag heads
278 281 file(self.mapfile, "a").write("%s %s\n" % (c, nrev))
279 282
280 283 gitpath, hgpath, mapfile = sys.argv[1:]
281 284 if os.path.isdir(gitpath + "/.git"):
282 285 gitpath += "/.git"
283 286
284 287 c = convert(convert_git(gitpath), convert_mercurial(hgpath), mapfile)
285 288 c.convert()
General Comments 0
You need to be logged in to leave comments. Login now