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