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