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