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