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