##// END OF EJS Templates
Some small cleanups for convert extension:...
Thomas Arendsen Hein -
r4532:c3a78a49 default
parent child Browse files
Show More
@@ -1,743 +1,749
1 # convert.py Foreign SCM converter
1 # convert.py Foreign SCM converter
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import sys, os, zlib, sha, time, re, locale, socket
8 import sys, os, zlib, sha, time, re, locale, socket
9 from mercurial import hg, ui, util, commands, repo
9 from mercurial import hg, ui, util, commands
10
10
11 commands.norepo += " convert"
11 commands.norepo += " convert"
12
12
13 class NoRepo(Exception): pass
13 class NoRepo(Exception): pass
14
14
15 class commit(object):
15 class commit(object):
16 def __init__(self, **parts):
16 def __init__(self, **parts):
17 for x in "author date desc parents".split():
17 for x in "author date desc parents".split():
18 if not x in parts:
18 if not x in parts:
19 raise util.Abort("commit missing field %s\n" % x)
19 raise util.Abort("commit missing field %s" % x)
20 self.__dict__.update(parts)
20 self.__dict__.update(parts)
21
21
22 def recode(s):
22 def recode(s):
23 try:
23 try:
24 return s.decode("utf-8").encode("utf-8")
24 return s.decode("utf-8").encode("utf-8")
25 except:
25 except:
26 try:
26 try:
27 return s.decode("latin-1").encode("utf-8")
27 return s.decode("latin-1").encode("utf-8")
28 except:
28 except:
29 return s.decode("utf-8", "replace").encode("utf-8")
29 return s.decode("utf-8", "replace").encode("utf-8")
30
30
31 class converter_source(object):
31 class converter_source(object):
32 """Conversion source interface"""
32 """Conversion source interface"""
33
33
34 def __init__(self, ui, path):
34 def __init__(self, ui, path):
35 """Initialize conversion source (or raise NoRepo("message")
35 """Initialize conversion source (or raise NoRepo("message")
36 exception if path is not a valid repository)"""
36 exception if path is not a valid repository)"""
37 raise NotImplementedError()
37 raise NotImplementedError()
38
38
39 def getheads(self):
39 def getheads(self):
40 """Return a list of this repository's heads"""
40 """Return a list of this repository's heads"""
41 raise NotImplementedError()
41 raise NotImplementedError()
42
42
43 def getfile(self, name, rev):
43 def getfile(self, name, rev):
44 """Return file contents as a string"""
44 """Return file contents as a string"""
45 raise NotImplementedError()
45 raise NotImplementedError()
46
46
47 def getmode(self, name, rev):
47 def getmode(self, name, rev):
48 """Return file mode, eg. '', 'x', or 'l'"""
48 """Return file mode, eg. '', 'x', or 'l'"""
49 raise NotImplementedError()
49 raise NotImplementedError()
50
50
51 def getchanges(self, version):
51 def getchanges(self, version):
52 """Return sorted list of (filename, id) tuples for all files changed in rev.
52 """Return sorted list of (filename, id) tuples for all files changed in rev.
53
53
54 id just tells us which revision to return in getfile(), e.g. in
54 id just tells us which revision to return in getfile(), e.g. in
55 git it's an object hash."""
55 git it's an object hash."""
56 raise NotImplementedError()
56 raise NotImplementedError()
57
57
58 def getcommit(self, version):
58 def getcommit(self, version):
59 """Return the commit object for version"""
59 """Return the commit object for version"""
60 raise NotImplementedError()
60 raise NotImplementedError()
61
61
62 def gettags(self):
62 def gettags(self):
63 """Return the tags as a dictionary of name: revision"""
63 """Return the tags as a dictionary of name: revision"""
64 raise NotImplementedError()
64 raise NotImplementedError()
65
65
66 class converter_sink(object):
66 class converter_sink(object):
67 """Conversion sink (target) interface"""
67 """Conversion sink (target) interface"""
68
68
69 def __init__(self, ui, path):
69 def __init__(self, ui, path):
70 """Initialize conversion sink (or raise NoRepo("message")
70 """Initialize conversion sink (or raise NoRepo("message")
71 exception if path is not a valid repository)"""
71 exception if path is not a valid repository)"""
72 raise NotImplementedError()
72 raise NotImplementedError()
73
73
74 def getheads(self):
74 def getheads(self):
75 """Return a list of this repository's heads"""
75 """Return a list of this repository's heads"""
76 raise NotImplementedError()
76 raise NotImplementedError()
77
77
78 def mapfile(self):
78 def mapfile(self):
79 """Path to a file that will contain lines
79 """Path to a file that will contain lines
80 source_rev_id sink_rev_id
80 source_rev_id sink_rev_id
81 mapping equivalent revision identifiers for each system."""
81 mapping equivalent revision identifiers for each system."""
82 raise NotImplementedError()
82 raise NotImplementedError()
83
83
84 def putfile(self, f, e, data):
84 def putfile(self, f, e, data):
85 """Put file for next putcommit().
85 """Put file for next putcommit().
86 f: path to file
86 f: path to file
87 e: '', 'x', or 'l' (regular file, executable, or symlink)
87 e: '', 'x', or 'l' (regular file, executable, or symlink)
88 data: file contents"""
88 data: file contents"""
89 raise NotImplementedError()
89 raise NotImplementedError()
90
90
91 def delfile(self, f):
91 def delfile(self, f):
92 """Delete file for next putcommit().
92 """Delete file for next putcommit().
93 f: path to file"""
93 f: path to file"""
94 raise NotImplementedError()
94 raise NotImplementedError()
95
95
96 def putcommit(self, files, parents, commit):
96 def putcommit(self, files, parents, commit):
97 """Create a revision with all changed files listed in 'files'
97 """Create a revision with all changed files listed in 'files'
98 and having listed parents. 'commit' is a commit object containing
98 and having listed parents. 'commit' is a commit object containing
99 at a minimum the author, date, and message for this changeset.
99 at a minimum the author, date, and message for this changeset.
100 Called after putfile() and delfile() calls. Note that the sink
100 Called after putfile() and delfile() calls. Note that the sink
101 repository is not told to update itself to a particular revision
101 repository is not told to update itself to a particular revision
102 (or even what that revision would be) before it receives the
102 (or even what that revision would be) before it receives the
103 file data."""
103 file data."""
104 raise NotImplementedError()
104 raise NotImplementedError()
105
105
106 def puttags(self, tags):
106 def puttags(self, tags):
107 """Put tags into sink.
107 """Put tags into sink.
108 tags: {tagname: sink_rev_id, ...}"""
108 tags: {tagname: sink_rev_id, ...}"""
109 raise NotImplementedError()
109 raise NotImplementedError()
110
110
111
111
112 # CVS conversion code inspired by hg-cvs-import and git-cvsimport
112 # CVS conversion code inspired by hg-cvs-import and git-cvsimport
113 class convert_cvs(converter_source):
113 class convert_cvs(converter_source):
114 def __init__(self, ui, path):
114 def __init__(self, ui, path):
115 self.path = path
115 self.path = path
116 self.ui = ui
116 self.ui = ui
117 cvs = os.path.join(path, "CVS")
117 cvs = os.path.join(path, "CVS")
118 if not os.path.exists(cvs):
118 if not os.path.exists(cvs):
119 raise NoRepo("couldn't open CVS repo %s" % path)
119 raise NoRepo("couldn't open CVS repo %s" % path)
120
120
121 self.changeset = {}
121 self.changeset = {}
122 self.files = {}
122 self.files = {}
123 self.tags = {}
123 self.tags = {}
124 self.lastbranch = {}
124 self.lastbranch = {}
125 self.parent = {}
125 self.parent = {}
126 self.socket = None
126 self.socket = None
127 self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
127 self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
128 self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
128 self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
129 self.encoding = locale.getpreferredencoding()
129 self.encoding = locale.getpreferredencoding()
130 self._parse()
130 self._parse()
131 self._connect()
131 self._connect()
132
132
133 def _parse(self):
133 def _parse(self):
134 if self.changeset:
134 if self.changeset:
135 return
135 return
136
136
137 d = os.getcwd()
137 d = os.getcwd()
138 try:
138 try:
139 os.chdir(self.path)
139 os.chdir(self.path)
140 id = None
140 id = None
141 state = 0
141 state = 0
142 for l in os.popen("cvsps -A -u --cvs-direct -q"):
142 for l in os.popen("cvsps -A -u --cvs-direct -q"):
143 if state == 0: # header
143 if state == 0: # header
144 if l.startswith("PatchSet"):
144 if l.startswith("PatchSet"):
145 id = l[9:-2]
145 id = l[9:-2]
146 elif l.startswith("Date"):
146 elif l.startswith("Date"):
147 date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
147 date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
148 date = util.datestr(date)
148 date = util.datestr(date)
149 elif l.startswith("Branch"):
149 elif l.startswith("Branch"):
150 branch = l[8:-1]
150 branch = l[8:-1]
151 self.parent[id] = self.lastbranch.get(branch,'bad')
151 self.parent[id] = self.lastbranch.get(branch, 'bad')
152 self.lastbranch[branch] = id
152 self.lastbranch[branch] = id
153 elif l.startswith("Ancestor branch"):
153 elif l.startswith("Ancestor branch"):
154 ancestor = l[17:-1]
154 ancestor = l[17:-1]
155 self.parent[id] = self.lastbranch[ancestor]
155 self.parent[id] = self.lastbranch[ancestor]
156 elif l.startswith("Author"):
156 elif l.startswith("Author"):
157 author = self.recode(l[8:-1])
157 author = self.recode(l[8:-1])
158 elif l.startswith("Tag: "):
158 elif l.startswith("Tag: "):
159 t = l[5:-1].rstrip()
159 t = l[5:-1].rstrip()
160 if t != "(none)":
160 if t != "(none)":
161 self.tags[t] = id
161 self.tags[t] = id
162 elif l.startswith("Log:"):
162 elif l.startswith("Log:"):
163 state = 1
163 state = 1
164 log = ""
164 log = ""
165 elif state == 1: # log
165 elif state == 1: # log
166 if l == "Members: \n":
166 if l == "Members: \n":
167 files = {}
167 files = {}
168 log = self.recode(log[:-1])
168 log = self.recode(log[:-1])
169 if log.isspace():
169 if log.isspace():
170 log = "*** empty log message ***\n"
170 log = "*** empty log message ***\n"
171 state = 2
171 state = 2
172 else:
172 else:
173 log += l
173 log += l
174 elif state == 2:
174 elif state == 2:
175 if l == "\n": #
175 if l == "\n": #
176 state = 0
176 state = 0
177 p = [self.parent[id]]
177 p = [self.parent[id]]
178 if id == "1":
178 if id == "1":
179 p = []
179 p = []
180 if branch == "HEAD":
180 if branch == "HEAD":
181 branch = ""
181 branch = ""
182 c = commit(author=author, date=date, parents=p,
182 c = commit(author=author, date=date, parents=p,
183 desc=log, branch=branch)
183 desc=log, branch=branch)
184 self.changeset[id] = c
184 self.changeset[id] = c
185 self.files[id] = files
185 self.files[id] = files
186 else:
186 else:
187 colon = l.rfind(':')
187 colon = l.rfind(':')
188 file = l[1:colon]
188 file = l[1:colon]
189 rev = l[colon+1:-2]
189 rev = l[colon+1:-2]
190 rev = rev.split("->")[1]
190 rev = rev.split("->")[1]
191 files[file] = rev
191 files[file] = rev
192
192
193 self.heads = self.lastbranch.values()
193 self.heads = self.lastbranch.values()
194 finally:
194 finally:
195 os.chdir(d)
195 os.chdir(d)
196
196
197 def _connect(self):
197 def _connect(self):
198 root = self.cvsroot
198 root = self.cvsroot
199 conntype = None
199 conntype = None
200 user, host = None, None
200 user, host = None, None
201 cmd = ['cvs', 'server']
201 cmd = ['cvs', 'server']
202
202
203 self.ui.status("connecting to %s\n" % root)
203 self.ui.status("connecting to %s\n" % root)
204
204
205 if root.startswith(":pserver:"):
205 if root.startswith(":pserver:"):
206 root = root[9:]
206 root = root[9:]
207 m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)', root)
207 m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
208 root)
208 if m:
209 if m:
209 conntype = "pserver"
210 conntype = "pserver"
210 user, passw, serv, port, root = m.groups()
211 user, passw, serv, port, root = m.groups()
211 if not user:
212 if not user:
212 user = "anonymous"
213 user = "anonymous"
213 rr = ":pserver:" + user + "@" + serv + ":" + root
214 rr = ":pserver:" + user + "@" + serv + ":" + root
214 if port:
215 if port:
215 rr2, port = "-", int(port)
216 rr2, port = "-", int(port)
216 else:
217 else:
217 rr2, port = rr, 2401
218 rr2, port = rr, 2401
218 rr += str(port)
219 rr += str(port)
219
220
220 if not passw:
221 if not passw:
221 passw = "A"
222 passw = "A"
222 pf = open(os.path.join(os.environ["HOME"], ".cvspass"))
223 pf = open(os.path.join(os.environ["HOME"], ".cvspass"))
223 for l in pf:
224 for l in pf:
224 # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
225 # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
225 m = re.match(r'(/\d+\s+/)?(.*)', l)
226 m = re.match(r'(/\d+\s+/)?(.*)', l)
226 l = m.group(2)
227 l = m.group(2)
227 w, p = l.split(' ', 1)
228 w, p = l.split(' ', 1)
228 if w in [rr, rr2]:
229 if w in [rr, rr2]:
229 passw = p
230 passw = p
230 break
231 break
231 pf.close()
232 pf.close()
232
233
233 sck = socket.socket()
234 sck = socket.socket()
234 sck.connect((serv, port))
235 sck.connect((serv, port))
235 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw, "END AUTH REQUEST", ""]))
236 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
237 "END AUTH REQUEST", ""]))
236 if sck.recv(128) != "I LOVE YOU\n":
238 if sck.recv(128) != "I LOVE YOU\n":
237 raise NoRepo("CVS pserver authentication failed")
239 raise NoRepo("CVS pserver authentication failed")
238
240
239 self.writep = self.readp = sck.makefile('r+')
241 self.writep = self.readp = sck.makefile('r+')
240
242
241 if not conntype and root.startswith(":local:"):
243 if not conntype and root.startswith(":local:"):
242 conntype = "local"
244 conntype = "local"
243 root = root[7:]
245 root = root[7:]
244
246
245 if not conntype:
247 if not conntype:
246 # :ext:user@host/home/user/path/to/cvsroot
248 # :ext:user@host/home/user/path/to/cvsroot
247 if root.startswith(":ext:"):
249 if root.startswith(":ext:"):
248 root = root[5:]
250 root = root[5:]
249 m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
251 m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
250 if not m:
252 if not m:
251 conntype = "local"
253 conntype = "local"
252 else:
254 else:
253 conntype = "rsh"
255 conntype = "rsh"
254 user, host, root = m.group(1), m.group(2), m.group(3)
256 user, host, root = m.group(1), m.group(2), m.group(3)
255
257
256 if conntype != "pserver":
258 if conntype != "pserver":
257 if conntype == "rsh":
259 if conntype == "rsh":
258 rsh = os.environ.get("CVS_RSH" or "rsh")
260 rsh = os.environ.get("CVS_RSH" or "rsh")
259 if user:
261 if user:
260 cmd = [rsh, '-l', user, host] + cmd
262 cmd = [rsh, '-l', user, host] + cmd
261 else:
263 else:
262 cmd = [rsh, host] + cmd
264 cmd = [rsh, host] + cmd
263
265
264 self.writep, self.readp = os.popen2(cmd)
266 self.writep, self.readp = os.popen2(cmd)
265
267
266 self.realroot = root
268 self.realroot = root
267
269
268 self.writep.write("Root %s\n" % root)
270 self.writep.write("Root %s\n" % root)
269 self.writep.write("Valid-responses ok error Valid-requests Mode"
271 self.writep.write("Valid-responses ok error Valid-requests Mode"
270 " M Mbinary E Checked-in Created Updated"
272 " M Mbinary E Checked-in Created Updated"
271 " Merged Removed\n")
273 " Merged Removed\n")
272 self.writep.write("valid-requests\n")
274 self.writep.write("valid-requests\n")
273 self.writep.flush()
275 self.writep.flush()
274 r = self.readp.readline()
276 r = self.readp.readline()
275 if not r.startswith("Valid-requests"):
277 if not r.startswith("Valid-requests"):
276 raise util.Abort("server sucks\n")
278 raise util.Abort("server sucks")
277 if "UseUnchanged" in r:
279 if "UseUnchanged" in r:
278 self.writep.write("UseUnchanged\n")
280 self.writep.write("UseUnchanged\n")
279 self.writep.flush()
281 self.writep.flush()
280 r = self.readp.readline()
282 r = self.readp.readline()
281
283
282 def getheads(self):
284 def getheads(self):
283 return self.heads
285 return self.heads
284
286
285 def _getfile(self, name, rev):
287 def _getfile(self, name, rev):
286 if rev.endswith("(DEAD)"):
288 if rev.endswith("(DEAD)"):
287 raise IOError
289 raise IOError
288
290
289 args = ("-N -P -kk -r %s --" % rev).split()
291 args = ("-N -P -kk -r %s --" % rev).split()
290 args.append(os.path.join(self.cvsrepo, name))
292 args.append(os.path.join(self.cvsrepo, name))
291 for x in args:
293 for x in args:
292 self.writep.write("Argument %s\n" % x)
294 self.writep.write("Argument %s\n" % x)
293 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
295 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
294 self.writep.flush()
296 self.writep.flush()
295
297
296 data = ""
298 data = ""
297 while 1:
299 while 1:
298 line = self.readp.readline()
300 line = self.readp.readline()
299 if line.startswith("Created ") or line.startswith("Updated "):
301 if line.startswith("Created ") or line.startswith("Updated "):
300 self.readp.readline() # path
302 self.readp.readline() # path
301 self.readp.readline() # entries
303 self.readp.readline() # entries
302 mode = self.readp.readline()[:-1]
304 mode = self.readp.readline()[:-1]
303 count = int(self.readp.readline()[:-1])
305 count = int(self.readp.readline()[:-1])
304 data = self.readp.read(count)
306 data = self.readp.read(count)
305 elif line.startswith(" "):
307 elif line.startswith(" "):
306 data += line[1:]
308 data += line[1:]
307 elif line.startswith("M "):
309 elif line.startswith("M "):
308 pass
310 pass
309 elif line.startswith("Mbinary "):
311 elif line.startswith("Mbinary "):
310 count = int(self.readp.readline()[:-1])
312 count = int(self.readp.readline()[:-1])
311 data = self.readp.read(count)
313 data = self.readp.read(count)
312 else:
314 else:
313 if line == "ok\n":
315 if line == "ok\n":
314 return (data, "x" in mode and "x" or "")
316 return (data, "x" in mode and "x" or "")
315 elif line.startswith("E "):
317 elif line.startswith("E "):
316 self.ui.warn("cvs server: %s\n" % line[2:])
318 self.ui.warn("cvs server: %s\n" % line[2:])
317 elif line.startswith("Remove"):
319 elif line.startswith("Remove"):
318 l = self.readp.readline()
320 l = self.readp.readline()
319 l = self.readp.readline()
321 l = self.readp.readline()
320 if l != "ok\n":
322 if l != "ok\n":
321 raise util.Abort("unknown CVS response: %s\n" % l)
323 raise util.Abort("unknown CVS response: %s" % l)
322 else:
324 else:
323 raise util.Abort("unknown CVS response: %s\n" % line)
325 raise util.Abort("unknown CVS response: %s" % line)
324
326
325 def getfile(self, file, rev):
327 def getfile(self, file, rev):
326 data, mode = self._getfile(file, rev)
328 data, mode = self._getfile(file, rev)
327 self.modecache[(file, rev)] = mode
329 self.modecache[(file, rev)] = mode
328 return data
330 return data
329
331
330 def getmode(self, file, rev):
332 def getmode(self, file, rev):
331 return self.modecache[(file, rev)]
333 return self.modecache[(file, rev)]
332
334
333 def getchanges(self, rev):
335 def getchanges(self, rev):
334 self.modecache = {}
336 self.modecache = {}
335 files = self.files[rev]
337 files = self.files[rev]
336 cl = files.items()
338 cl = files.items()
337 cl.sort()
339 cl.sort()
338 return cl
340 return cl
339
341
340 def recode(self, text):
342 def recode(self, text):
341 return text.decode(self.encoding, "replace").encode("utf-8")
343 return text.decode(self.encoding, "replace").encode("utf-8")
342
344
343 def getcommit(self, rev):
345 def getcommit(self, rev):
344 return self.changeset[rev]
346 return self.changeset[rev]
345
347
346 def gettags(self):
348 def gettags(self):
347 return self.tags
349 return self.tags
348
350
349 class convert_git(converter_source):
351 class convert_git(converter_source):
350 def __init__(self, ui, path):
352 def __init__(self, ui, path):
351 if os.path.isdir(path + "/.git"):
353 if os.path.isdir(path + "/.git"):
352 path += "/.git"
354 path += "/.git"
353 self.path = path
355 self.path = path
354 self.ui = ui
356 self.ui = ui
355 if not os.path.exists(path + "/objects"):
357 if not os.path.exists(path + "/objects"):
356 raise NoRepo("couldn't open GIT repo %s" % path)
358 raise NoRepo("couldn't open GIT repo %s" % path)
357
359
358 def getheads(self):
360 def getheads(self):
359 fh = os.popen("GIT_DIR=%s git-rev-parse --verify HEAD" % self.path)
361 fh = os.popen("GIT_DIR=%s git-rev-parse --verify HEAD" % self.path)
360 return [fh.read()[:-1]]
362 return [fh.read()[:-1]]
361
363
362 def catfile(self, rev, type):
364 def catfile(self, rev, type):
363 if rev == "0" * 40: raise IOError()
365 if rev == "0" * 40: raise IOError()
364 fh = os.popen("GIT_DIR=%s git-cat-file %s %s 2>/dev/null" % (self.path, type, rev))
366 fh = os.popen("GIT_DIR=%s git-cat-file %s %s 2>/dev/null"
367 % (self.path, type, rev))
365 return fh.read()
368 return fh.read()
366
369
367 def getfile(self, name, rev):
370 def getfile(self, name, rev):
368 return self.catfile(rev, "blob")
371 return self.catfile(rev, "blob")
369
372
370 def getmode(self, name, rev):
373 def getmode(self, name, rev):
371 return self.modecache[(name, rev)]
374 return self.modecache[(name, rev)]
372
375
373 def getchanges(self, version):
376 def getchanges(self, version):
374 self.modecache = {}
377 self.modecache = {}
375 fh = os.popen("GIT_DIR=%s git-diff-tree --root -m -r %s" % (self.path, version))
378 fh = os.popen("GIT_DIR=%s git-diff-tree --root -m -r %s"
379 % (self.path, version))
376 changes = []
380 changes = []
377 for l in fh:
381 for l in fh:
378 if "\t" not in l: continue
382 if "\t" not in l: continue
379 m, f = l[:-1].split("\t")
383 m, f = l[:-1].split("\t")
380 m = m.split()
384 m = m.split()
381 h = m[3]
385 h = m[3]
382 p = (m[1] == "100755")
386 p = (m[1] == "100755")
383 s = (m[1] == "120000")
387 s = (m[1] == "120000")
384 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
388 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
385 changes.append((f, h))
389 changes.append((f, h))
386 return changes
390 return changes
387
391
388 def getcommit(self, version):
392 def getcommit(self, version):
389 c = self.catfile(version, "commit") # read the commit hash
393 c = self.catfile(version, "commit") # read the commit hash
390 end = c.find("\n\n")
394 end = c.find("\n\n")
391 message = c[end+2:]
395 message = c[end+2:]
392 message = recode(message)
396 message = recode(message)
393 l = c[:end].splitlines()
397 l = c[:end].splitlines()
394 manifest = l[0].split()[1]
398 manifest = l[0].split()[1]
395 parents = []
399 parents = []
396 for e in l[1:]:
400 for e in l[1:]:
397 n,v = e.split(" ", 1)
401 n, v = e.split(" ", 1)
398 if n == "author":
402 if n == "author":
399 p = v.split()
403 p = v.split()
400 tm, tz = p[-2:]
404 tm, tz = p[-2:]
401 author = " ".join(p[:-2])
405 author = " ".join(p[:-2])
402 if author[0] == "<": author = author[1:-1]
406 if author[0] == "<": author = author[1:-1]
403 author = recode(author)
407 author = recode(author)
404 if n == "committer":
408 if n == "committer":
405 p = v.split()
409 p = v.split()
406 tm, tz = p[-2:]
410 tm, tz = p[-2:]
407 committer = " ".join(p[:-2])
411 committer = " ".join(p[:-2])
408 if committer[0] == "<": committer = committer[1:-1]
412 if committer[0] == "<": committer = committer[1:-1]
409 committer = recode(committer)
413 committer = recode(committer)
410 message += "\ncommitter: %s\n" % committer
414 message += "\ncommitter: %s\n" % committer
411 if n == "parent": parents.append(v)
415 if n == "parent": parents.append(v)
412
416
413 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
417 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
414 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
418 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
415 date = tm + " " + str(tz)
419 date = tm + " " + str(tz)
416
420
417 c = commit(parents=parents, date=date, author=author, desc=message)
421 c = commit(parents=parents, date=date, author=author, desc=message)
418 return c
422 return c
419
423
420 def gettags(self):
424 def gettags(self):
421 tags = {}
425 tags = {}
422 fh = os.popen('git-ls-remote --tags "%s" 2>/dev/null' % self.path)
426 fh = os.popen('git-ls-remote --tags "%s" 2>/dev/null' % self.path)
423 prefix = 'refs/tags/'
427 prefix = 'refs/tags/'
424 for line in fh:
428 for line in fh:
425 line = line.strip()
429 line = line.strip()
426 if not line.endswith("^{}"):
430 if not line.endswith("^{}"):
427 continue
431 continue
428 node, tag = line.split(None, 1)
432 node, tag = line.split(None, 1)
429 if not tag.startswith(prefix):
433 if not tag.startswith(prefix):
430 continue
434 continue
431 tag = tag[len(prefix):-3]
435 tag = tag[len(prefix):-3]
432 tags[tag] = node
436 tags[tag] = node
433
437
434 return tags
438 return tags
435
439
436 class convert_mercurial(converter_sink):
440 class convert_mercurial(converter_sink):
437 def __init__(self, ui, path):
441 def __init__(self, ui, path):
438 self.path = path
442 self.path = path
439 self.ui = ui
443 self.ui = ui
440 try:
444 try:
441 self.repo = hg.repository(self.ui, path)
445 self.repo = hg.repository(self.ui, path)
442 except:
446 except:
443 raise NoRepo("could open hg repo %s" % path)
447 raise NoRepo("could open hg repo %s" % path)
444
448
445 def mapfile(self):
449 def mapfile(self):
446 return os.path.join(self.path, ".hg", "shamap")
450 return os.path.join(self.path, ".hg", "shamap")
447
451
448 def getheads(self):
452 def getheads(self):
449 h = self.repo.changelog.heads()
453 h = self.repo.changelog.heads()
450 return [ hg.hex(x) for x in h ]
454 return [ hg.hex(x) for x in h ]
451
455
452 def putfile(self, f, e, data):
456 def putfile(self, f, e, data):
453 self.repo.wwrite(f, data, e)
457 self.repo.wwrite(f, data, e)
454 if self.repo.dirstate.state(f) == '?':
458 if self.repo.dirstate.state(f) == '?':
455 self.repo.dirstate.update([f], "a")
459 self.repo.dirstate.update([f], "a")
456
460
457 def delfile(self, f):
461 def delfile(self, f):
458 try:
462 try:
459 os.unlink(self.repo.wjoin(f))
463 os.unlink(self.repo.wjoin(f))
460 #self.repo.remove([f])
464 #self.repo.remove([f])
461 except:
465 except:
462 pass
466 pass
463
467
464 def putcommit(self, files, parents, commit):
468 def putcommit(self, files, parents, commit):
465 seen = {}
469 seen = {}
466 pl = []
470 pl = []
467 for p in parents:
471 for p in parents:
468 if p not in seen:
472 if p not in seen:
469 pl.append(p)
473 pl.append(p)
470 seen[p] = 1
474 seen[p] = 1
471 parents = pl
475 parents = pl
472
476
473 if len(parents) < 2: parents.append("0" * 40)
477 if len(parents) < 2: parents.append("0" * 40)
474 if len(parents) < 2: parents.append("0" * 40)
478 if len(parents) < 2: parents.append("0" * 40)
475 p2 = parents.pop(0)
479 p2 = parents.pop(0)
476
480
477 text = commit.desc
481 text = commit.desc
478 extra = {}
482 extra = {}
479 try:
483 try:
480 extra["branch"] = commit.branch
484 extra["branch"] = commit.branch
481 except AttributeError:
485 except AttributeError:
482 pass
486 pass
483
487
484 while parents:
488 while parents:
485 p1 = p2
489 p1 = p2
486 p2 = parents.pop(0)
490 p2 = parents.pop(0)
487 a = self.repo.rawcommit(files, text, commit.author, commit.date,
491 a = self.repo.rawcommit(files, text, commit.author, commit.date,
488 hg.bin(p1), hg.bin(p2), extra=extra)
492 hg.bin(p1), hg.bin(p2), extra=extra)
489 text = "(octopus merge fixup)\n"
493 text = "(octopus merge fixup)\n"
490 p2 = hg.hex(self.repo.changelog.tip())
494 p2 = hg.hex(self.repo.changelog.tip())
491
495
492 return p2
496 return p2
493
497
494 def puttags(self, tags):
498 def puttags(self, tags):
495 try:
499 try:
496 old = self.repo.wfile(".hgtags").read()
500 old = self.repo.wfile(".hgtags").read()
497 oldlines = old.splitlines(1)
501 oldlines = old.splitlines(1)
498 oldlines.sort()
502 oldlines.sort()
499 except:
503 except:
500 oldlines = []
504 oldlines = []
501
505
502 k = tags.keys()
506 k = tags.keys()
503 k.sort()
507 k.sort()
504 newlines = []
508 newlines = []
505 for tag in k:
509 for tag in k:
506 newlines.append("%s %s\n" % (tags[tag], tag))
510 newlines.append("%s %s\n" % (tags[tag], tag))
507
511
508 newlines.sort()
512 newlines.sort()
509
513
510 if newlines != oldlines:
514 if newlines != oldlines:
511 self.ui.status("updating tags\n")
515 self.ui.status("updating tags\n")
512 f = self.repo.wfile(".hgtags", "w")
516 f = self.repo.wfile(".hgtags", "w")
513 f.write("".join(newlines))
517 f.write("".join(newlines))
514 f.close()
518 f.close()
515 if not oldlines: self.repo.add([".hgtags"])
519 if not oldlines: self.repo.add([".hgtags"])
516 date = "%s 0" % int(time.mktime(time.gmtime()))
520 date = "%s 0" % int(time.mktime(time.gmtime()))
517 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
521 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
518 date, self.repo.changelog.tip(), hg.nullid)
522 date, self.repo.changelog.tip(), hg.nullid)
519 return hg.hex(self.repo.changelog.tip())
523 return hg.hex(self.repo.changelog.tip())
520
524
521 converters = [convert_cvs, convert_git, convert_mercurial]
525 converters = [convert_cvs, convert_git, convert_mercurial]
522
526
523 def converter(ui, path):
527 def converter(ui, path):
524 if not os.path.isdir(path):
528 if not os.path.isdir(path):
525 raise util.Abort("%s: not a directory\n" % path)
529 raise util.Abort("%s: not a directory" % path)
526 for c in converters:
530 for c in converters:
527 try:
531 try:
528 return c(ui, path)
532 return c(ui, path)
529 except NoRepo:
533 except NoRepo:
530 pass
534 pass
531 raise util.Abort("%s: unknown repository type\n" % path)
535 raise util.Abort("%s: unknown repository type" % path)
532
536
533 class convert(object):
537 class convert(object):
534 def __init__(self, ui, source, dest, mapfile, opts):
538 def __init__(self, ui, source, dest, mapfile, opts):
535
539
536 self.source = source
540 self.source = source
537 self.dest = dest
541 self.dest = dest
538 self.ui = ui
542 self.ui = ui
539 self.mapfile = mapfile
543 self.mapfile = mapfile
540 self.opts = opts
544 self.opts = opts
541 self.commitcache = {}
545 self.commitcache = {}
542
546
543 self.map = {}
547 self.map = {}
544 try:
548 try:
545 for l in file(self.mapfile):
549 for l in file(self.mapfile):
546 sv, dv = l[:-1].split()
550 sv, dv = l[:-1].split()
547 self.map[sv] = dv
551 self.map[sv] = dv
548 except IOError:
552 except IOError:
549 pass
553 pass
550
554
551 def walktree(self, heads):
555 def walktree(self, heads):
552 visit = heads
556 visit = heads
553 known = {}
557 known = {}
554 parents = {}
558 parents = {}
555 while visit:
559 while visit:
556 n = visit.pop(0)
560 n = visit.pop(0)
557 if n in known or n in self.map: continue
561 if n in known or n in self.map: continue
558 known[n] = 1
562 known[n] = 1
559 self.commitcache[n] = self.source.getcommit(n)
563 self.commitcache[n] = self.source.getcommit(n)
560 cp = self.commitcache[n].parents
564 cp = self.commitcache[n].parents
561 for p in cp:
565 for p in cp:
562 parents.setdefault(n, []).append(p)
566 parents.setdefault(n, []).append(p)
563 visit.append(p)
567 visit.append(p)
564
568
565 return parents
569 return parents
566
570
567 def toposort(self, parents):
571 def toposort(self, parents):
568 visit = parents.keys()
572 visit = parents.keys()
569 seen = {}
573 seen = {}
570 children = {}
574 children = {}
571
575
572 while visit:
576 while visit:
573 n = visit.pop(0)
577 n = visit.pop(0)
574 if n in seen: continue
578 if n in seen: continue
575 seen[n] = 1
579 seen[n] = 1
576 pc = 0
580 pc = 0
577 if n in parents:
581 if n in parents:
578 for p in parents[n]:
582 for p in parents[n]:
579 if p not in self.map: pc += 1
583 if p not in self.map: pc += 1
580 visit.append(p)
584 visit.append(p)
581 children.setdefault(p, []).append(n)
585 children.setdefault(p, []).append(n)
582 if not pc: root = n
586 if not pc: root = n
583
587
584 s = []
588 s = []
585 removed = {}
589 removed = {}
586 visit = children.keys()
590 visit = children.keys()
587 while visit:
591 while visit:
588 n = visit.pop(0)
592 n = visit.pop(0)
589 if n in removed: continue
593 if n in removed: continue
590 dep = 0
594 dep = 0
591 if n in parents:
595 if n in parents:
592 for p in parents[n]:
596 for p in parents[n]:
593 if p in self.map: continue
597 if p in self.map: continue
594 if p not in removed:
598 if p not in removed:
595 # we're still dependent
599 # we're still dependent
596 visit.append(n)
600 visit.append(n)
597 dep = 1
601 dep = 1
598 break
602 break
599
603
600 if not dep:
604 if not dep:
601 # all n's parents are in the list
605 # all n's parents are in the list
602 removed[n] = 1
606 removed[n] = 1
603 if n not in self.map:
607 if n not in self.map:
604 s.append(n)
608 s.append(n)
605 if n in children:
609 if n in children:
606 for c in children[n]:
610 for c in children[n]:
607 visit.insert(0, c)
611 visit.insert(0, c)
608
612
609 if self.opts.get('datesort'):
613 if self.opts.get('datesort'):
610 depth = {}
614 depth = {}
611 for n in s:
615 for n in s:
612 depth[n] = 0
616 depth[n] = 0
613 pl = [p for p in self.commitcache[n].parents if p not in self.map]
617 pl = [p for p in self.commitcache[n].parents
618 if p not in self.map]
614 if pl:
619 if pl:
615 depth[n] = max([depth[p] for p in pl]) + 1
620 depth[n] = max([depth[p] for p in pl]) + 1
616
621
617 s = [(depth[n], self.commitcache[n].date, n) for n in s]
622 s = [(depth[n], self.commitcache[n].date, n) for n in s]
618 s.sort()
623 s.sort()
619 s = [e[2] for e in s]
624 s = [e[2] for e in s]
620
625
621 return s
626 return s
622
627
623 def copy(self, rev):
628 def copy(self, rev):
624 c = self.commitcache[rev]
629 c = self.commitcache[rev]
625 files = self.source.getchanges(rev)
630 files = self.source.getchanges(rev)
626
631
627 for f,v in files:
632 for f, v in files:
628 try:
633 try:
629 data = self.source.getfile(f, v)
634 data = self.source.getfile(f, v)
630 except IOError, inst:
635 except IOError, inst:
631 self.dest.delfile(f)
636 self.dest.delfile(f)
632 else:
637 else:
633 e = self.source.getmode(f, v)
638 e = self.source.getmode(f, v)
634 self.dest.putfile(f, e, data)
639 self.dest.putfile(f, e, data)
635
640
636 r = [self.map[v] for v in c.parents]
641 r = [self.map[v] for v in c.parents]
637 f = [f for f,v in files]
642 f = [f for f, v in files]
638 self.map[rev] = self.dest.putcommit(f, r, c)
643 self.map[rev] = self.dest.putcommit(f, r, c)
639 file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev]))
644 file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev]))
640
645
641 def convert(self):
646 def convert(self):
642 self.ui.status("scanning source...\n")
647 self.ui.status("scanning source...\n")
643 heads = self.source.getheads()
648 heads = self.source.getheads()
644 parents = self.walktree(heads)
649 parents = self.walktree(heads)
645 self.ui.status("sorting...\n")
650 self.ui.status("sorting...\n")
646 t = self.toposort(parents)
651 t = self.toposort(parents)
647 num = len(t)
652 num = len(t)
648 c = None
653 c = None
649
654
650 self.ui.status("converting...\n")
655 self.ui.status("converting...\n")
651 for c in t:
656 for c in t:
652 num -= 1
657 num -= 1
653 desc = self.commitcache[c].desc
658 desc = self.commitcache[c].desc
654 if "\n" in desc:
659 if "\n" in desc:
655 desc = desc.splitlines()[0]
660 desc = desc.splitlines()[0]
656 self.ui.status("%d %s\n" % (num, desc))
661 self.ui.status("%d %s\n" % (num, desc))
657 self.copy(c)
662 self.copy(c)
658
663
659 tags = self.source.gettags()
664 tags = self.source.gettags()
660 ctags = {}
665 ctags = {}
661 for k in tags:
666 for k in tags:
662 v = tags[k]
667 v = tags[k]
663 if v in self.map:
668 if v in self.map:
664 ctags[k] = self.map[v]
669 ctags[k] = self.map[v]
665
670
666 if c and ctags:
671 if c and ctags:
667 nrev = self.dest.puttags(ctags)
672 nrev = self.dest.puttags(ctags)
668 # write another hash correspondence to override the previous
673 # write another hash correspondence to override the previous
669 # one so we don't end up with extra tag heads
674 # one so we don't end up with extra tag heads
670 if nrev:
675 if nrev:
671 file(self.mapfile, "a").write("%s %s\n" % (c, nrev))
676 file(self.mapfile, "a").write("%s %s\n" % (c, nrev))
672
677
673 def _convert(ui, src, dest=None, mapfile=None, **opts):
678 def _convert(ui, src, dest=None, mapfile=None, **opts):
674 '''Convert a foreign SCM repository to a Mercurial one.
679 '''Convert a foreign SCM repository to a Mercurial one.
675
680
676 Accepted source formats:
681 Accepted source formats:
677 - GIT
682 - GIT
678 - CVS
683 - CVS
679
684
680 Accepted destination formats:
685 Accepted destination formats:
681 - Mercurial
686 - Mercurial
682
687
683 If destination isn't given, a new Mercurial repo named <src>-hg will
688 If destination isn't given, a new Mercurial repo named <src>-hg will
684 be created. If <mapfile> isn't given, it will be put in a default
689 be created. If <mapfile> isn't given, it will be put in a default
685 location (<dest>/.hg/shamap by default)
690 location (<dest>/.hg/shamap by default)
686
691
687 The <mapfile> is a simple text file that maps each source commit ID to
692 The <mapfile> is a simple text file that maps each source commit ID to
688 the destination ID for that revision, like so:
693 the destination ID for that revision, like so:
689
694
690 <source ID> <destination ID>
695 <source ID> <destination ID>
691
696
692 If the file doesn't exist, it's automatically created. It's updated
697 If the file doesn't exist, it's automatically created. It's updated
693 on each commit copied, so convert-repo can be interrupted and can
698 on each commit copied, so convert-repo can be interrupted and can
694 be run repeatedly to copy new commits.
699 be run repeatedly to copy new commits.
695 '''
700 '''
696
701
697 srcc = converter(ui, src)
702 srcc = converter(ui, src)
698 if not hasattr(srcc, "getcommit"):
703 if not hasattr(srcc, "getcommit"):
699 raise util.Abort("%s: can't read from this repo type\n" % src)
704 raise util.Abort("%s: can't read from this repo type" % src)
700
705
701 if not dest:
706 if not dest:
702 dest = src + "-hg"
707 dest = src + "-hg"
703 ui.status("assuming destination %s\n" % dest)
708 ui.status("assuming destination %s\n" % dest)
704
709
705 # Try to be smart and initalize things when required
710 # Try to be smart and initalize things when required
706 if os.path.isdir(dest):
711 if os.path.isdir(dest):
707 if len(os.listdir(dest)) > 0:
712 if len(os.listdir(dest)) > 0:
708 try:
713 try:
709 hg.repository(ui, dest)
714 hg.repository(ui, dest)
710 ui.status("destination %s is a Mercurial repository\n" % dest)
715 ui.status("destination %s is a Mercurial repository\n" % dest)
711 except repo.RepoError:
716 except hg.RepoError:
712 raise util.Abort(
717 raise util.Abort(
713 """destination directory %s is not empty.
718 "destination directory %s is not empty.\n"
714 Please specify an empty directory to be initialized or an already initialized
719 "Please specify an empty directory to be initialized\n"
715 mercurial repository
720 "or an already initialized mercurial repository"
716 """ % dest)
721 % dest)
717 else:
722 else:
718 ui.status("initializing destination %s repository\n" % dest)
723 ui.status("initializing destination %s repository\n" % dest)
719 hg.repository(ui, dest, create=True)
724 hg.repository(ui, dest, create=True)
720 elif os.path.exists(dest):
725 elif os.path.exists(dest):
721 raise util.Abort("destination %s exists and is not a directory\n" % dest)
726 raise util.Abort("destination %s exists and is not a directory" % dest)
722 else:
727 else:
723 ui.status("initializing destination %s repository\n" % dest)
728 ui.status("initializing destination %s repository\n" % dest)
724 hg.repository(ui, dest, create=True)
729 hg.repository(ui, dest, create=True)
725
730
726 destc = converter(ui, dest)
731 destc = converter(ui, dest)
727 if not hasattr(destc, "putcommit"):
732 if not hasattr(destc, "putcommit"):
728 raise util.Abort("%s: can't write to this repo type\n" % src)
733 raise util.Abort("%s: can't write to this repo type" % src)
729
734
730 if not mapfile:
735 if not mapfile:
731 try:
736 try:
732 mapfile = destc.mapfile()
737 mapfile = destc.mapfile()
733 except:
738 except:
734 mapfile = os.path.join(destc, "map")
739 mapfile = os.path.join(destc, "map")
735
740
736 c = convert(ui, srcc, destc, mapfile, opts)
741 c = convert(ui, srcc, destc, mapfile, opts)
737 c.convert()
742 c.convert()
738
743
739 cmdtable = {
744 cmdtable = {
740 "convert": (_convert,
745 "convert":
746 (_convert,
741 [('', 'datesort', None, 'try to sort changesets by date')],
747 [('', 'datesort', None, 'try to sort changesets by date')],
742 'hg convert [OPTIONS] <src> [dst [map]]'),
748 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
743 }
749 }
General Comments 0
You need to be logged in to leave comments. Login now