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