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