##// END OF EJS Templates
Add some more smart when initializing destination repository
Edouard Gomez -
r4521:d634b61e default
parent child Browse files
Show More
@@ -1,724 +1,743 b''
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
9 from mercurial import hg, ui, util, commands, repo
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\n" % 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*))?(.*)', root)
208 if m:
208 if m:
209 conntype = "pserver"
209 conntype = "pserver"
210 user, passw, serv, port, root = m.groups()
210 user, passw, serv, port, root = m.groups()
211 if not user:
211 if not user:
212 user = "anonymous"
212 user = "anonymous"
213 rr = ":pserver:" + user + "@" + serv + ":" + root
213 rr = ":pserver:" + user + "@" + serv + ":" + root
214 if port:
214 if port:
215 rr2, port = "-", int(port)
215 rr2, port = "-", int(port)
216 else:
216 else:
217 rr2, port = rr, 2401
217 rr2, port = rr, 2401
218 rr += str(port)
218 rr += str(port)
219
219
220 if not passw:
220 if not passw:
221 passw = "A"
221 passw = "A"
222 pf = open(os.path.join(os.environ["HOME"], ".cvspass"))
222 pf = open(os.path.join(os.environ["HOME"], ".cvspass"))
223 for l in pf:
223 for l in pf:
224 # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
224 # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
225 m = re.match(r'(/\d+\s+/)?(.*)', l)
225 m = re.match(r'(/\d+\s+/)?(.*)', l)
226 l = m.group(2)
226 l = m.group(2)
227 w, p = l.split(' ', 1)
227 w, p = l.split(' ', 1)
228 if w in [rr, rr2]:
228 if w in [rr, rr2]:
229 passw = p
229 passw = p
230 break
230 break
231 pf.close()
231 pf.close()
232
232
233 sck = socket.socket()
233 sck = socket.socket()
234 sck.connect((serv, port))
234 sck.connect((serv, port))
235 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw, "END AUTH REQUEST", ""]))
235 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw, "END AUTH REQUEST", ""]))
236 if sck.recv(128) != "I LOVE YOU\n":
236 if sck.recv(128) != "I LOVE YOU\n":
237 raise NoRepo("CVS pserver authentication failed")
237 raise NoRepo("CVS pserver authentication failed")
238
238
239 self.writep = self.readp = sck.makefile('r+')
239 self.writep = self.readp = sck.makefile('r+')
240
240
241 if not conntype and root.startswith(":local:"):
241 if not conntype and root.startswith(":local:"):
242 conntype = "local"
242 conntype = "local"
243 root = root[7:]
243 root = root[7:]
244
244
245 if not conntype:
245 if not conntype:
246 # :ext:user@host/home/user/path/to/cvsroot
246 # :ext:user@host/home/user/path/to/cvsroot
247 if root.startswith(":ext:"):
247 if root.startswith(":ext:"):
248 root = root[5:]
248 root = root[5:]
249 m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
249 m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
250 if not m:
250 if not m:
251 conntype = "local"
251 conntype = "local"
252 else:
252 else:
253 conntype = "rsh"
253 conntype = "rsh"
254 user, host, root = m.group(1), m.group(2), m.group(3)
254 user, host, root = m.group(1), m.group(2), m.group(3)
255
255
256 if conntype != "pserver":
256 if conntype != "pserver":
257 if conntype == "rsh":
257 if conntype == "rsh":
258 rsh = os.environ.get("CVS_RSH" or "rsh")
258 rsh = os.environ.get("CVS_RSH" or "rsh")
259 if user:
259 if user:
260 cmd = [rsh, '-l', user, host] + cmd
260 cmd = [rsh, '-l', user, host] + cmd
261 else:
261 else:
262 cmd = [rsh, host] + cmd
262 cmd = [rsh, host] + cmd
263
263
264 self.writep, self.readp = os.popen2(cmd)
264 self.writep, self.readp = os.popen2(cmd)
265
265
266 self.realroot = root
266 self.realroot = root
267
267
268 self.writep.write("Root %s\n" % root)
268 self.writep.write("Root %s\n" % root)
269 self.writep.write("Valid-responses ok error Valid-requests Mode"
269 self.writep.write("Valid-responses ok error Valid-requests Mode"
270 " M Mbinary E Checked-in Created Updated"
270 " M Mbinary E Checked-in Created Updated"
271 " Merged Removed\n")
271 " Merged Removed\n")
272 self.writep.write("valid-requests\n")
272 self.writep.write("valid-requests\n")
273 self.writep.flush()
273 self.writep.flush()
274 r = self.readp.readline()
274 r = self.readp.readline()
275 if not r.startswith("Valid-requests"):
275 if not r.startswith("Valid-requests"):
276 raise util.Abort("server sucks\n")
276 raise util.Abort("server sucks\n")
277 if "UseUnchanged" in r:
277 if "UseUnchanged" in r:
278 self.writep.write("UseUnchanged\n")
278 self.writep.write("UseUnchanged\n")
279 self.writep.flush()
279 self.writep.flush()
280 r = self.readp.readline()
280 r = self.readp.readline()
281
281
282 def getheads(self):
282 def getheads(self):
283 return self.heads
283 return self.heads
284
284
285 def _getfile(self, name, rev):
285 def _getfile(self, name, rev):
286 if rev.endswith("(DEAD)"):
286 if rev.endswith("(DEAD)"):
287 raise IOError
287 raise IOError
288
288
289 args = ("-N -P -kk -r %s --" % rev).split()
289 args = ("-N -P -kk -r %s --" % rev).split()
290 args.append(os.path.join(self.cvsrepo, name))
290 args.append(os.path.join(self.cvsrepo, name))
291 for x in args:
291 for x in args:
292 self.writep.write("Argument %s\n" % x)
292 self.writep.write("Argument %s\n" % x)
293 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
293 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
294 self.writep.flush()
294 self.writep.flush()
295
295
296 data = ""
296 data = ""
297 while 1:
297 while 1:
298 line = self.readp.readline()
298 line = self.readp.readline()
299 if line.startswith("Created ") or line.startswith("Updated "):
299 if line.startswith("Created ") or line.startswith("Updated "):
300 self.readp.readline() # path
300 self.readp.readline() # path
301 self.readp.readline() # entries
301 self.readp.readline() # entries
302 mode = self.readp.readline()[:-1]
302 mode = self.readp.readline()[:-1]
303 count = int(self.readp.readline()[:-1])
303 count = int(self.readp.readline()[:-1])
304 data = self.readp.read(count)
304 data = self.readp.read(count)
305 elif line.startswith(" "):
305 elif line.startswith(" "):
306 data += line[1:]
306 data += line[1:]
307 elif line.startswith("M "):
307 elif line.startswith("M "):
308 pass
308 pass
309 elif line.startswith("Mbinary "):
309 elif line.startswith("Mbinary "):
310 count = int(self.readp.readline()[:-1])
310 count = int(self.readp.readline()[:-1])
311 data = self.readp.read(count)
311 data = self.readp.read(count)
312 else:
312 else:
313 if line == "ok\n":
313 if line == "ok\n":
314 return (data, "x" in mode and "x" or "")
314 return (data, "x" in mode and "x" or "")
315 elif line.startswith("E "):
315 elif line.startswith("E "):
316 self.ui.warn("cvs server: %s\n" % line[2:])
316 self.ui.warn("cvs server: %s\n" % line[2:])
317 elif line.startswith("Remove"):
317 elif line.startswith("Remove"):
318 l = self.readp.readline()
318 l = self.readp.readline()
319 l = self.readp.readline()
319 l = self.readp.readline()
320 if l != "ok\n":
320 if l != "ok\n":
321 raise util.Abort("unknown CVS response: %s\n" % l)
321 raise util.Abort("unknown CVS response: %s\n" % l)
322 else:
322 else:
323 raise util.Abort("unknown CVS response: %s\n" % line)
323 raise util.Abort("unknown CVS response: %s\n" % line)
324
324
325 def getfile(self, file, rev):
325 def getfile(self, file, rev):
326 data, mode = self._getfile(file, rev)
326 data, mode = self._getfile(file, rev)
327 self.modecache[(file, rev)] = mode
327 self.modecache[(file, rev)] = mode
328 return data
328 return data
329
329
330 def getmode(self, file, rev):
330 def getmode(self, file, rev):
331 return self.modecache[(file, rev)]
331 return self.modecache[(file, rev)]
332
332
333 def getchanges(self, rev):
333 def getchanges(self, rev):
334 self.modecache = {}
334 self.modecache = {}
335 files = self.files[rev]
335 files = self.files[rev]
336 cl = files.items()
336 cl = files.items()
337 cl.sort()
337 cl.sort()
338 return cl
338 return cl
339
339
340 def recode(self, text):
340 def recode(self, text):
341 return text.decode(self.encoding, "replace").encode("utf-8")
341 return text.decode(self.encoding, "replace").encode("utf-8")
342
342
343 def getcommit(self, rev):
343 def getcommit(self, rev):
344 return self.changeset[rev]
344 return self.changeset[rev]
345
345
346 def gettags(self):
346 def gettags(self):
347 return self.tags
347 return self.tags
348
348
349 class convert_git(converter_source):
349 class convert_git(converter_source):
350 def __init__(self, ui, path):
350 def __init__(self, ui, path):
351 if os.path.isdir(path + "/.git"):
351 if os.path.isdir(path + "/.git"):
352 path += "/.git"
352 path += "/.git"
353 self.path = path
353 self.path = path
354 self.ui = ui
354 self.ui = ui
355 if not os.path.exists(path + "/objects"):
355 if not os.path.exists(path + "/objects"):
356 raise NoRepo("couldn't open GIT repo %s" % path)
356 raise NoRepo("couldn't open GIT repo %s" % path)
357
357
358 def getheads(self):
358 def getheads(self):
359 fh = os.popen("GIT_DIR=%s git-rev-parse --verify HEAD" % self.path)
359 fh = os.popen("GIT_DIR=%s git-rev-parse --verify HEAD" % self.path)
360 return [fh.read()[:-1]]
360 return [fh.read()[:-1]]
361
361
362 def catfile(self, rev, type):
362 def catfile(self, rev, type):
363 if rev == "0" * 40: raise IOError()
363 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))
364 fh = os.popen("GIT_DIR=%s git-cat-file %s %s 2>/dev/null" % (self.path, type, rev))
365 return fh.read()
365 return fh.read()
366
366
367 def getfile(self, name, rev):
367 def getfile(self, name, rev):
368 return self.catfile(rev, "blob")
368 return self.catfile(rev, "blob")
369
369
370 def getmode(self, name, rev):
370 def getmode(self, name, rev):
371 return self.modecache[(name, rev)]
371 return self.modecache[(name, rev)]
372
372
373 def getchanges(self, version):
373 def getchanges(self, version):
374 self.modecache = {}
374 self.modecache = {}
375 fh = os.popen("GIT_DIR=%s git-diff-tree --root -m -r %s" % (self.path, version))
375 fh = os.popen("GIT_DIR=%s git-diff-tree --root -m -r %s" % (self.path, version))
376 changes = []
376 changes = []
377 for l in fh:
377 for l in fh:
378 if "\t" not in l: continue
378 if "\t" not in l: continue
379 m, f = l[:-1].split("\t")
379 m, f = l[:-1].split("\t")
380 m = m.split()
380 m = m.split()
381 h = m[3]
381 h = m[3]
382 p = (m[1] == "100755")
382 p = (m[1] == "100755")
383 s = (m[1] == "120000")
383 s = (m[1] == "120000")
384 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
384 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
385 changes.append((f, h))
385 changes.append((f, h))
386 return changes
386 return changes
387
387
388 def getcommit(self, version):
388 def getcommit(self, version):
389 c = self.catfile(version, "commit") # read the commit hash
389 c = self.catfile(version, "commit") # read the commit hash
390 end = c.find("\n\n")
390 end = c.find("\n\n")
391 message = c[end+2:]
391 message = c[end+2:]
392 message = recode(message)
392 message = recode(message)
393 l = c[:end].splitlines()
393 l = c[:end].splitlines()
394 manifest = l[0].split()[1]
394 manifest = l[0].split()[1]
395 parents = []
395 parents = []
396 for e in l[1:]:
396 for e in l[1:]:
397 n,v = e.split(" ", 1)
397 n,v = e.split(" ", 1)
398 if n == "author":
398 if n == "author":
399 p = v.split()
399 p = v.split()
400 tm, tz = p[-2:]
400 tm, tz = p[-2:]
401 author = " ".join(p[:-2])
401 author = " ".join(p[:-2])
402 if author[0] == "<": author = author[1:-1]
402 if author[0] == "<": author = author[1:-1]
403 author = recode(author)
403 author = recode(author)
404 if n == "committer":
404 if n == "committer":
405 p = v.split()
405 p = v.split()
406 tm, tz = p[-2:]
406 tm, tz = p[-2:]
407 committer = " ".join(p[:-2])
407 committer = " ".join(p[:-2])
408 if committer[0] == "<": committer = committer[1:-1]
408 if committer[0] == "<": committer = committer[1:-1]
409 committer = recode(committer)
409 committer = recode(committer)
410 message += "\ncommitter: %s\n" % committer
410 message += "\ncommitter: %s\n" % committer
411 if n == "parent": parents.append(v)
411 if n == "parent": parents.append(v)
412
412
413 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
413 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
414 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
414 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
415 date = tm + " " + str(tz)
415 date = tm + " " + str(tz)
416
416
417 c = commit(parents=parents, date=date, author=author, desc=message)
417 c = commit(parents=parents, date=date, author=author, desc=message)
418 return c
418 return c
419
419
420 def gettags(self):
420 def gettags(self):
421 tags = {}
421 tags = {}
422 fh = os.popen('git-ls-remote --tags "%s" 2>/dev/null' % self.path)
422 fh = os.popen('git-ls-remote --tags "%s" 2>/dev/null' % self.path)
423 prefix = 'refs/tags/'
423 prefix = 'refs/tags/'
424 for line in fh:
424 for line in fh:
425 line = line.strip()
425 line = line.strip()
426 if not line.endswith("^{}"):
426 if not line.endswith("^{}"):
427 continue
427 continue
428 node, tag = line.split(None, 1)
428 node, tag = line.split(None, 1)
429 if not tag.startswith(prefix):
429 if not tag.startswith(prefix):
430 continue
430 continue
431 tag = tag[len(prefix):-3]
431 tag = tag[len(prefix):-3]
432 tags[tag] = node
432 tags[tag] = node
433
433
434 return tags
434 return tags
435
435
436 class convert_mercurial(converter_sink):
436 class convert_mercurial(converter_sink):
437 def __init__(self, ui, path):
437 def __init__(self, ui, path):
438 self.path = path
438 self.path = path
439 self.ui = ui
439 self.ui = ui
440 try:
440 try:
441 self.repo = hg.repository(self.ui, path)
441 self.repo = hg.repository(self.ui, path)
442 except:
442 except:
443 raise NoRepo("could open hg repo %s" % path)
443 raise NoRepo("could open hg repo %s" % path)
444
444
445 def mapfile(self):
445 def mapfile(self):
446 return os.path.join(self.path, ".hg", "shamap")
446 return os.path.join(self.path, ".hg", "shamap")
447
447
448 def getheads(self):
448 def getheads(self):
449 h = self.repo.changelog.heads()
449 h = self.repo.changelog.heads()
450 return [ hg.hex(x) for x in h ]
450 return [ hg.hex(x) for x in h ]
451
451
452 def putfile(self, f, e, data):
452 def putfile(self, f, e, data):
453 self.repo.wwrite(f, data, e)
453 self.repo.wwrite(f, data, e)
454 if self.repo.dirstate.state(f) == '?':
454 if self.repo.dirstate.state(f) == '?':
455 self.repo.dirstate.update([f], "a")
455 self.repo.dirstate.update([f], "a")
456
456
457 def delfile(self, f):
457 def delfile(self, f):
458 try:
458 try:
459 os.unlink(self.repo.wjoin(f))
459 os.unlink(self.repo.wjoin(f))
460 #self.repo.remove([f])
460 #self.repo.remove([f])
461 except:
461 except:
462 pass
462 pass
463
463
464 def putcommit(self, files, parents, commit):
464 def putcommit(self, files, parents, commit):
465 seen = {}
465 seen = {}
466 pl = []
466 pl = []
467 for p in parents:
467 for p in parents:
468 if p not in seen:
468 if p not in seen:
469 pl.append(p)
469 pl.append(p)
470 seen[p] = 1
470 seen[p] = 1
471 parents = pl
471 parents = pl
472
472
473 if len(parents) < 2: parents.append("0" * 40)
473 if len(parents) < 2: parents.append("0" * 40)
474 if len(parents) < 2: parents.append("0" * 40)
474 if len(parents) < 2: parents.append("0" * 40)
475 p2 = parents.pop(0)
475 p2 = parents.pop(0)
476
476
477 text = commit.desc
477 text = commit.desc
478 extra = {}
478 extra = {}
479 try:
479 try:
480 extra["branch"] = commit.branch
480 extra["branch"] = commit.branch
481 except AttributeError:
481 except AttributeError:
482 pass
482 pass
483
483
484 while parents:
484 while parents:
485 p1 = p2
485 p1 = p2
486 p2 = parents.pop(0)
486 p2 = parents.pop(0)
487 a = self.repo.rawcommit(files, text, commit.author, commit.date,
487 a = self.repo.rawcommit(files, text, commit.author, commit.date,
488 hg.bin(p1), hg.bin(p2), extra=extra)
488 hg.bin(p1), hg.bin(p2), extra=extra)
489 text = "(octopus merge fixup)\n"
489 text = "(octopus merge fixup)\n"
490 p2 = hg.hex(self.repo.changelog.tip())
490 p2 = hg.hex(self.repo.changelog.tip())
491
491
492 return p2
492 return p2
493
493
494 def puttags(self, tags):
494 def puttags(self, tags):
495 try:
495 try:
496 old = self.repo.wfile(".hgtags").read()
496 old = self.repo.wfile(".hgtags").read()
497 oldlines = old.splitlines(1)
497 oldlines = old.splitlines(1)
498 oldlines.sort()
498 oldlines.sort()
499 except:
499 except:
500 oldlines = []
500 oldlines = []
501
501
502 k = tags.keys()
502 k = tags.keys()
503 k.sort()
503 k.sort()
504 newlines = []
504 newlines = []
505 for tag in k:
505 for tag in k:
506 newlines.append("%s %s\n" % (tags[tag], tag))
506 newlines.append("%s %s\n" % (tags[tag], tag))
507
507
508 newlines.sort()
508 newlines.sort()
509
509
510 if newlines != oldlines:
510 if newlines != oldlines:
511 self.ui.status("updating tags\n")
511 self.ui.status("updating tags\n")
512 f = self.repo.wfile(".hgtags", "w")
512 f = self.repo.wfile(".hgtags", "w")
513 f.write("".join(newlines))
513 f.write("".join(newlines))
514 f.close()
514 f.close()
515 if not oldlines: self.repo.add([".hgtags"])
515 if not oldlines: self.repo.add([".hgtags"])
516 date = "%s 0" % int(time.mktime(time.gmtime()))
516 date = "%s 0" % int(time.mktime(time.gmtime()))
517 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
517 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
518 date, self.repo.changelog.tip(), hg.nullid)
518 date, self.repo.changelog.tip(), hg.nullid)
519 return hg.hex(self.repo.changelog.tip())
519 return hg.hex(self.repo.changelog.tip())
520
520
521 converters = [convert_cvs, convert_git, convert_mercurial]
521 converters = [convert_cvs, convert_git, convert_mercurial]
522
522
523 def converter(ui, path):
523 def converter(ui, path):
524 if not os.path.isdir(path):
524 if not os.path.isdir(path):
525 raise util.Abort("%s: not a directory\n" % path)
525 raise util.Abort("%s: not a directory\n" % path)
526 for c in converters:
526 for c in converters:
527 try:
527 try:
528 return c(ui, path)
528 return c(ui, path)
529 except NoRepo:
529 except NoRepo:
530 pass
530 pass
531 raise util.Abort("%s: unknown repository type\n" % path)
531 raise util.Abort("%s: unknown repository type\n" % path)
532
532
533 class convert(object):
533 class convert(object):
534 def __init__(self, ui, source, dest, mapfile, opts):
534 def __init__(self, ui, source, dest, mapfile, opts):
535
535
536 self.source = source
536 self.source = source
537 self.dest = dest
537 self.dest = dest
538 self.ui = ui
538 self.ui = ui
539 self.mapfile = mapfile
539 self.mapfile = mapfile
540 self.opts = opts
540 self.opts = opts
541 self.commitcache = {}
541 self.commitcache = {}
542
542
543 self.map = {}
543 self.map = {}
544 try:
544 try:
545 for l in file(self.mapfile):
545 for l in file(self.mapfile):
546 sv, dv = l[:-1].split()
546 sv, dv = l[:-1].split()
547 self.map[sv] = dv
547 self.map[sv] = dv
548 except IOError:
548 except IOError:
549 pass
549 pass
550
550
551 def walktree(self, heads):
551 def walktree(self, heads):
552 visit = heads
552 visit = heads
553 known = {}
553 known = {}
554 parents = {}
554 parents = {}
555 while visit:
555 while visit:
556 n = visit.pop(0)
556 n = visit.pop(0)
557 if n in known or n in self.map: continue
557 if n in known or n in self.map: continue
558 known[n] = 1
558 known[n] = 1
559 self.commitcache[n] = self.source.getcommit(n)
559 self.commitcache[n] = self.source.getcommit(n)
560 cp = self.commitcache[n].parents
560 cp = self.commitcache[n].parents
561 for p in cp:
561 for p in cp:
562 parents.setdefault(n, []).append(p)
562 parents.setdefault(n, []).append(p)
563 visit.append(p)
563 visit.append(p)
564
564
565 return parents
565 return parents
566
566
567 def toposort(self, parents):
567 def toposort(self, parents):
568 visit = parents.keys()
568 visit = parents.keys()
569 seen = {}
569 seen = {}
570 children = {}
570 children = {}
571
571
572 while visit:
572 while visit:
573 n = visit.pop(0)
573 n = visit.pop(0)
574 if n in seen: continue
574 if n in seen: continue
575 seen[n] = 1
575 seen[n] = 1
576 pc = 0
576 pc = 0
577 if n in parents:
577 if n in parents:
578 for p in parents[n]:
578 for p in parents[n]:
579 if p not in self.map: pc += 1
579 if p not in self.map: pc += 1
580 visit.append(p)
580 visit.append(p)
581 children.setdefault(p, []).append(n)
581 children.setdefault(p, []).append(n)
582 if not pc: root = n
582 if not pc: root = n
583
583
584 s = []
584 s = []
585 removed = {}
585 removed = {}
586 visit = children.keys()
586 visit = children.keys()
587 while visit:
587 while visit:
588 n = visit.pop(0)
588 n = visit.pop(0)
589 if n in removed: continue
589 if n in removed: continue
590 dep = 0
590 dep = 0
591 if n in parents:
591 if n in parents:
592 for p in parents[n]:
592 for p in parents[n]:
593 if p in self.map: continue
593 if p in self.map: continue
594 if p not in removed:
594 if p not in removed:
595 # we're still dependent
595 # we're still dependent
596 visit.append(n)
596 visit.append(n)
597 dep = 1
597 dep = 1
598 break
598 break
599
599
600 if not dep:
600 if not dep:
601 # all n's parents are in the list
601 # all n's parents are in the list
602 removed[n] = 1
602 removed[n] = 1
603 if n not in self.map:
603 if n not in self.map:
604 s.append(n)
604 s.append(n)
605 if n in children:
605 if n in children:
606 for c in children[n]:
606 for c in children[n]:
607 visit.insert(0, c)
607 visit.insert(0, c)
608
608
609 if self.opts.get('datesort'):
609 if self.opts.get('datesort'):
610 depth = {}
610 depth = {}
611 for n in s:
611 for n in s:
612 depth[n] = 0
612 depth[n] = 0
613 pl = [p for p in self.commitcache[n].parents if p not in self.map]
613 pl = [p for p in self.commitcache[n].parents if p not in self.map]
614 if pl:
614 if pl:
615 depth[n] = max([depth[p] for p in pl]) + 1
615 depth[n] = max([depth[p] for p in pl]) + 1
616
616
617 s = [(depth[n], self.commitcache[n].date, n) for n in s]
617 s = [(depth[n], self.commitcache[n].date, n) for n in s]
618 s.sort()
618 s.sort()
619 s = [e[2] for e in s]
619 s = [e[2] for e in s]
620
620
621 return s
621 return s
622
622
623 def copy(self, rev):
623 def copy(self, rev):
624 c = self.commitcache[rev]
624 c = self.commitcache[rev]
625 files = self.source.getchanges(rev)
625 files = self.source.getchanges(rev)
626
626
627 for f,v in files:
627 for f,v in files:
628 try:
628 try:
629 data = self.source.getfile(f, v)
629 data = self.source.getfile(f, v)
630 except IOError, inst:
630 except IOError, inst:
631 self.dest.delfile(f)
631 self.dest.delfile(f)
632 else:
632 else:
633 e = self.source.getmode(f, v)
633 e = self.source.getmode(f, v)
634 self.dest.putfile(f, e, data)
634 self.dest.putfile(f, e, data)
635
635
636 r = [self.map[v] for v in c.parents]
636 r = [self.map[v] for v in c.parents]
637 f = [f for f,v in files]
637 f = [f for f,v in files]
638 self.map[rev] = self.dest.putcommit(f, r, c)
638 self.map[rev] = self.dest.putcommit(f, r, c)
639 file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev]))
639 file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev]))
640
640
641 def convert(self):
641 def convert(self):
642 self.ui.status("scanning source...\n")
642 self.ui.status("scanning source...\n")
643 heads = self.source.getheads()
643 heads = self.source.getheads()
644 parents = self.walktree(heads)
644 parents = self.walktree(heads)
645 self.ui.status("sorting...\n")
645 self.ui.status("sorting...\n")
646 t = self.toposort(parents)
646 t = self.toposort(parents)
647 num = len(t)
647 num = len(t)
648 c = None
648 c = None
649
649
650 self.ui.status("converting...\n")
650 self.ui.status("converting...\n")
651 for c in t:
651 for c in t:
652 num -= 1
652 num -= 1
653 desc = self.commitcache[c].desc
653 desc = self.commitcache[c].desc
654 if "\n" in desc:
654 if "\n" in desc:
655 desc = desc.splitlines()[0]
655 desc = desc.splitlines()[0]
656 self.ui.status("%d %s\n" % (num, desc))
656 self.ui.status("%d %s\n" % (num, desc))
657 self.copy(c)
657 self.copy(c)
658
658
659 tags = self.source.gettags()
659 tags = self.source.gettags()
660 ctags = {}
660 ctags = {}
661 for k in tags:
661 for k in tags:
662 v = tags[k]
662 v = tags[k]
663 if v in self.map:
663 if v in self.map:
664 ctags[k] = self.map[v]
664 ctags[k] = self.map[v]
665
665
666 if c and ctags:
666 if c and ctags:
667 nrev = self.dest.puttags(ctags)
667 nrev = self.dest.puttags(ctags)
668 # write another hash correspondence to override the previous
668 # write another hash correspondence to override the previous
669 # one so we don't end up with extra tag heads
669 # one so we don't end up with extra tag heads
670 if nrev:
670 if nrev:
671 file(self.mapfile, "a").write("%s %s\n" % (c, nrev))
671 file(self.mapfile, "a").write("%s %s\n" % (c, nrev))
672
672
673 def _convert(ui, src, dest=None, mapfile=None, **opts):
673 def _convert(ui, src, dest=None, mapfile=None, **opts):
674 '''Convert a foreign SCM repository to a Mercurial one.
674 '''Convert a foreign SCM repository to a Mercurial one.
675
675
676 Accepted source formats:
676 Accepted source formats:
677 - GIT
677 - GIT
678 - CVS
678 - CVS
679
679
680 Accepted destination formats:
680 Accepted destination formats:
681 - Mercurial
681 - Mercurial
682
682
683 If destination isn't given, a new Mercurial repo named <src>-hg will
683 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
684 be created. If <mapfile> isn't given, it will be put in a default
685 location (<dest>/.hg/shamap by default)
685 location (<dest>/.hg/shamap by default)
686
686
687 The <mapfile> is a simple text file that maps each source commit ID to
687 The <mapfile> is a simple text file that maps each source commit ID to
688 the destination ID for that revision, like so:
688 the destination ID for that revision, like so:
689
689
690 <source ID> <destination ID>
690 <source ID> <destination ID>
691
691
692 If the file doesn't exist, it's automatically created. It's updated
692 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
693 on each commit copied, so convert-repo can be interrupted and can
694 be run repeatedly to copy new commits.
694 be run repeatedly to copy new commits.
695 '''
695 '''
696
696
697 srcc = converter(ui, src)
697 srcc = converter(ui, src)
698 if not hasattr(srcc, "getcommit"):
698 if not hasattr(srcc, "getcommit"):
699 raise util.Abort("%s: can't read from this repo type\n" % src)
699 raise util.Abort("%s: can't read from this repo type\n" % src)
700
700
701 if not dest:
701 if not dest:
702 dest = src + "-hg"
702 dest = src + "-hg"
703 ui.status("assuming destination %s\n" % dest)
703 ui.status("assuming destination %s\n" % dest)
704 if not os.path.isdir(dest):
704
705 ui.status("creating repository %s\n" % dest)
705 # Try to be smart and initalize things when required
706 os.system("hg init " + dest)
706 if os.path.isdir(dest):
707 if len(os.listdir(dest)) > 0:
708 try:
709 hg.repository(ui, dest)
710 ui.status("destination %s is a Mercurial repository\n" % dest)
711 except repo.RepoError:
712 raise util.Abort(
713 """destination directory %s is not empty.
714 Please specify an empty directory to be initialized or an already initialized
715 mercurial repository
716 """ % dest)
717 else:
718 ui.status("initializing destination %s repository\n" % dest)
719 hg.repository(ui, dest, create=True)
720 elif os.path.exists(dest):
721 raise util.Abort("destination %s exists and is not a directory\n" % dest)
722 else:
723 ui.status("initializing destination %s repository\n" % dest)
724 hg.repository(ui, dest, create=True)
725
707 destc = converter(ui, dest)
726 destc = converter(ui, dest)
708 if not hasattr(destc, "putcommit"):
727 if not hasattr(destc, "putcommit"):
709 raise util.Abort("%s: can't write to this repo type\n" % src)
728 raise util.Abort("%s: can't write to this repo type\n" % src)
710
729
711 if not mapfile:
730 if not mapfile:
712 try:
731 try:
713 mapfile = destc.mapfile()
732 mapfile = destc.mapfile()
714 except:
733 except:
715 mapfile = os.path.join(destc, "map")
734 mapfile = os.path.join(destc, "map")
716
735
717 c = convert(ui, srcc, destc, mapfile, opts)
736 c = convert(ui, srcc, destc, mapfile, opts)
718 c.convert()
737 c.convert()
719
738
720 cmdtable = {
739 cmdtable = {
721 "convert": (_convert,
740 "convert": (_convert,
722 [('', 'datesort', None, 'try to sort changesets by date')],
741 [('', 'datesort', None, 'try to sort changesets by date')],
723 'hg convert [OPTIONS] <src> [dst [map]]'),
742 'hg convert [OPTIONS] <src> [dst [map]]'),
724 }
743 }
General Comments 0
You need to be logged in to leave comments. Login now