##// END OF EJS Templates
convert: support darcs as a source repo
Bryan O'Sullivan -
r5359:6b610443 default
parent child Browse files
Show More
@@ -0,0 +1,137 b''
1 # darcs support for the convert extension
2
3 from common import NoRepo, commit, converter_source
4 from mercurial.i18n import _
5 from mercurial import util
6 import os, shutil, tempfile
7
8 # The naming drift of ElementTree is fun!
9
10 try: from xml.etree.cElementTree import ElementTree
11 except ImportError:
12 try: from xml.etree.ElementTree import ElementTree
13 except ImportError:
14 try: from elementtree.cElementTree import ElementTree
15 except ImportError:
16 try: from elementtree.ElementTree import ElementTree
17 except ImportError: ElementTree = None
18
19
20 class darcs_source(converter_source):
21 def __init__(self, ui, path, rev=None):
22 super(darcs_source, self).__init__(ui, path, rev=rev)
23
24 if not os.path.exists(os.path.join(path, '_darcs', 'inventory')):
25 raise NoRepo("couldn't open darcs repo %s" % path)
26
27 if ElementTree is None:
28 raise util.Abort(_("Python ElementTree module is not available"))
29
30 self.path = os.path.realpath(path)
31
32 self.lastrev = None
33 self.changes = {}
34 self.parents = {}
35 self.tags = {}
36
37 def before(self):
38 self.tmppath = tempfile.mkdtemp(
39 prefix='convert-' + os.path.basename(self.path) + '-')
40 output, status = self.run('init', repodir=self.tmppath)
41 self.checkexit(status)
42
43 tree = self.xml('changes', '--xml-output', '--summary')
44 tagname = None
45 child = None
46 for elt in tree.findall('patch'):
47 node = elt.get('hash')
48 name = elt.findtext('name', '')
49 if name.startswith('TAG '):
50 tagname = name[4:].strip()
51 elif tagname is not None:
52 self.tags[tagname] = node
53 tagname = None
54 self.changes[node] = elt
55 self.parents[child] = [node]
56 child = node
57 self.parents[child] = []
58
59 def after(self):
60 self.ui.debug('cleaning up %s\n' % self.tmppath)
61 #shutil.rmtree(self.tmppath, ignore_errors=True)
62
63 def _run(self, cmd, *args, **kwargs):
64 cmdline = 'darcs %s --repodir=%r %s </dev/null' % (
65 cmd, kwargs.get('repodir', self.path), ' '.join(args))
66 self.ui.debug(cmdline, '\n')
67 return os.popen(cmdline, 'r')
68
69 def run(self, cmd, *args, **kwargs):
70 fp = self._run(cmd, *args, **kwargs)
71 output = fp.read()
72 return output, fp.close()
73
74 def checkexit(self, status, output=''):
75 if status:
76 if output:
77 ui.warn(_('darcs error:\n'))
78 ui.warn(output)
79 msg = util.explain_exit(status)[0]
80 raise util.Abort(_('darcs %s') % msg)
81
82 def xml(self, cmd, *opts):
83 etree = ElementTree()
84 fp = self._run(cmd, *opts)
85 etree.parse(fp)
86 self.checkexit(fp.close())
87 return etree.getroot()
88
89 def getheads(self):
90 return self.parents[None]
91
92 def getcommit(self, rev):
93 elt = self.changes[rev]
94 date = util.strdate(elt.get('local_date'), '%a %b %d %H:%M:%S %Z %Y')
95 desc = elt.findtext('name') + '\n' + elt.findtext('comment', '')
96 return commit(author=elt.get('author'), date=util.datestr(date),
97 desc=desc.strip(), parents=self.parents[rev])
98
99 def pull(self, rev):
100 output, status = self.run('pull %r --all --match="hash %s"' %
101 (self.path, rev),
102 '--no-test', '--no-posthook',
103 '--external-merge=/bin/false',
104 repodir=self.tmppath)
105 if status:
106 if output.find('We have conflicts in') == -1:
107 self.checkexit(status, output)
108 output, status = self.run('revert --all', repodir=self.tmppath)
109 self.checkexit(status, output)
110
111 def getchanges(self, rev):
112 self.pull(rev)
113 copies = {}
114 changes = []
115 for elt in self.changes[rev].find('summary').getchildren():
116 if elt.tag in ('add_directory', 'remove_directory'):
117 continue
118 if elt.tag == 'move':
119 changes.append((elt.get('from'), rev))
120 copies[elt.get('from')] = elt.get('to')
121 else:
122 changes.append((elt.text.strip(), rev))
123 changes.sort()
124 self.lastrev = rev
125 return changes, copies
126
127 def getfile(self, name, rev):
128 if rev != self.lastrev:
129 raise util.Abort(_('internal calling inconsistency'))
130 return open(os.path.join(self.tmppath, name), 'rb').read()
131
132 def getmode(self, name, rev):
133 mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
134 return (mode & 0111) and 'x' or ''
135
136 def gettags(self):
137 return self.tags
@@ -1,251 +1,255 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # Encoding: iso-8859-1
2 # Encoding: iso-8859-1
3 # vim: tw=80 ts=4 sw=4 noet
3 # vim: tw=80 ts=4 sw=4 noet
4 # -----------------------------------------------------------------------------
4 # -----------------------------------------------------------------------------
5 # Project : Basic Darcs to Mercurial conversion script
5 # Project : Basic Darcs to Mercurial conversion script
6 #
7 # *** DEPRECATED. Use the convert extension instead. This script will
8 # *** be removed soon.
9 #
6 # -----------------------------------------------------------------------------
10 # -----------------------------------------------------------------------------
7 # Authors : Sebastien Pierre <sebastien@xprima.com>
11 # Authors : Sebastien Pierre <sebastien@xprima.com>
8 # TK Soh <teekaysoh@gmail.com>
12 # TK Soh <teekaysoh@gmail.com>
9 # -----------------------------------------------------------------------------
13 # -----------------------------------------------------------------------------
10 # Creation : 24-May-2006
14 # Creation : 24-May-2006
11 # Last mod : 05-Jun-2006
12 # -----------------------------------------------------------------------------
15 # -----------------------------------------------------------------------------
13
16
14 import os, sys
17 import os, sys
15 import tempfile
18 import tempfile
16 import xml.dom.minidom as xml_dom
19 import xml.dom.minidom as xml_dom
17 from time import strptime, mktime
20 from time import strptime, mktime
18 import re
21 import re
19
22
20 DARCS_REPO = None
23 DARCS_REPO = None
21 HG_REPO = None
24 HG_REPO = None
22
25
23 USAGE = """\
26 USAGE = """\
24 %s DARCSREPO HGREPO [SKIP]
27 %s DARCSREPO HGREPO [SKIP]
25
28
26 Converts the given Darcs repository to a new Mercurial repository. The given
29 Converts the given Darcs repository to a new Mercurial repository. The given
27 HGREPO must not exist, as it will be created and filled up (this will avoid
30 HGREPO must not exist, as it will be created and filled up (this will avoid
28 overwriting valuable data.
31 overwriting valuable data.
29
32
30 In case an error occurs within the process, you can resume the process by
33 In case an error occurs within the process, you can resume the process by
31 giving the last successfuly applied change number.
34 giving the last successfuly applied change number.
32 """ % (os.path.basename(sys.argv[0]))
35 """ % (os.path.basename(sys.argv[0]))
33
36
34 # ------------------------------------------------------------------------------
37 # ------------------------------------------------------------------------------
35 #
38 #
36 # Utilities
39 # Utilities
37 #
40 #
38 # ------------------------------------------------------------------------------
41 # ------------------------------------------------------------------------------
39
42
40 def cmd(text, path=None, silent=False):
43 def cmd(text, path=None, silent=False):
41 """Executes a command, in the given directory (if any), and returns the
44 """Executes a command, in the given directory (if any), and returns the
42 command result as a string."""
45 command result as a string."""
43 cwd = None
46 cwd = None
44 if path:
47 if path:
45 path = os.path.abspath(path)
48 path = os.path.abspath(path)
46 cwd = os.getcwd()
49 cwd = os.getcwd()
47 os.chdir(path)
50 os.chdir(path)
48 if not silent: print "> ", text
51 if not silent: print "> ", text
49 res = os.popen(text).read()
52 res = os.popen(text).read()
50 if path:
53 if path:
51 os.chdir(cwd)
54 os.chdir(cwd)
52 return res
55 return res
53
56
54 def writefile(path, data):
57 def writefile(path, data):
55 """Writes the given data into the given file."""
58 """Writes the given data into the given file."""
56 f = file(path, "w") ; f.write(data) ; f.close()
59 f = file(path, "w") ; f.write(data) ; f.close()
57
60
58 def error( *args ):
61 def error( *args ):
59 sys.stderr.write("ERROR: ")
62 sys.stderr.write("ERROR: ")
60 for a in args: sys.stderr.write(str(a))
63 for a in args: sys.stderr.write(str(a))
61 sys.stderr.write("\n")
64 sys.stderr.write("\n")
62 sys.stderr.write("You can make manual fixes if necessary and then resume by"
65 sys.stderr.write("You can make manual fixes if necessary and then resume by"
63 " giving the last changeset number")
66 " giving the last changeset number")
64 sys.exit(-1)
67 sys.exit(-1)
65
68
66 # ------------------------------------------------------------------------------
69 # ------------------------------------------------------------------------------
67 #
70 #
68 # Darcs interface
71 # Darcs interface
69 #
72 #
70 # ------------------------------------------------------------------------------
73 # ------------------------------------------------------------------------------
71
74
72 def darcs_changes(darcsRepo):
75 def darcs_changes(darcsRepo):
73 """Gets the changes list from the given darcs repository. This returns the
76 """Gets the changes list from the given darcs repository. This returns the
74 chronological list of changes as (change name, change summary)."""
77 chronological list of changes as (change name, change summary)."""
75 changes = cmd("darcs changes --reverse --xml-output", darcsRepo)
78 changes = cmd("darcs changes --reverse --xml-output", darcsRepo)
76 doc = xml_dom.parseString(changes)
79 doc = xml_dom.parseString(changes)
77 for patch_node in doc.childNodes[0].childNodes:
80 for patch_node in doc.childNodes[0].childNodes:
78 name = filter(lambda n: n.nodeName == "name", patch_node.childNodes)
81 name = filter(lambda n: n.nodeName == "name", patch_node.childNodes)
79 comm = filter(lambda n: n.nodeName == "comment", patch_node.childNodes)
82 comm = filter(lambda n: n.nodeName == "comment", patch_node.childNodes)
80 if not name:continue
83 if not name:continue
81 else: name = name[0].childNodes[0].data
84 else: name = name[0].childNodes[0].data
82 if not comm: comm = ""
85 if not comm: comm = ""
83 else: comm = comm[0].childNodes[0].data
86 else: comm = comm[0].childNodes[0].data
84 author = patch_node.getAttribute("author")
87 author = patch_node.getAttribute("author")
85 date = patch_node.getAttribute("date")
88 date = patch_node.getAttribute("date")
86 chash = os.path.splitext(patch_node.getAttribute("hash"))[0]
89 chash = os.path.splitext(patch_node.getAttribute("hash"))[0]
87 yield author, date, name, chash, comm
90 yield author, date, name, chash, comm
88
91
89 def darcs_tip(darcs_repo):
92 def darcs_tip(darcs_repo):
90 changes = cmd("darcs changes",darcs_repo,silent=True)
93 changes = cmd("darcs changes",darcs_repo,silent=True)
91 changes = filter(lambda l: l.strip().startswith("* "), changes.split("\n"))
94 changes = filter(lambda l: l.strip().startswith("* "), changes.split("\n"))
92 return len(changes)
95 return len(changes)
93
96
94 def darcs_pull(hg_repo, darcs_repo, chash):
97 def darcs_pull(hg_repo, darcs_repo, chash):
95 old_tip = darcs_tip(darcs_repo)
98 old_tip = darcs_tip(darcs_repo)
96 res = cmd("darcs pull \"%s\" --all --match=\"hash %s\"" % (darcs_repo, chash), hg_repo)
99 res = cmd("darcs pull \"%s\" --all --match=\"hash %s\"" % (darcs_repo, chash), hg_repo)
97 if re.search('^We have conflicts in the following files:$', res, re.MULTILINE):
100 if re.search('^We have conflicts in the following files:$', res, re.MULTILINE):
98 print "Trying to revert files to work around conflict..."
101 print "Trying to revert files to work around conflict..."
99 rev_res = cmd ("darcs revert --all", hg_repo)
102 rev_res = cmd ("darcs revert --all", hg_repo)
100 print rev_res
103 print rev_res
101 print res
104 print res
102 new_tip = darcs_tip(darcs_repo)
105 new_tip = darcs_tip(darcs_repo)
103 if not new_tip != old_tip + 1:
106 if not new_tip != old_tip + 1:
104 error("Darcs pull did not work as expected: " + res)
107 error("Darcs pull did not work as expected: " + res)
105
108
106 def darcs_changes_summary(darcs_repo, chash):
109 def darcs_changes_summary(darcs_repo, chash):
107 """Gets the changes from the darcs summary. This returns the chronological
110 """Gets the changes from the darcs summary. This returns the chronological
108 list of changes as (change_type, args). Eg. ('add_file', 'foo.txt') or
111 list of changes as (change_type, args). Eg. ('add_file', 'foo.txt') or
109 ('move', ['foo.txt','bar.txt'])."""
112 ('move', ['foo.txt','bar.txt'])."""
110 change = cmd("darcs changes --summary --xml-output --match=\"hash %s\"" % (chash), darcs_repo)
113 change = cmd("darcs changes --summary --xml-output --match=\"hash %s\"" % (chash), darcs_repo)
111 doc = xml_dom.parseString(change)
114 doc = xml_dom.parseString(change)
112 for patch_node in doc.childNodes[0].childNodes:
115 for patch_node in doc.childNodes[0].childNodes:
113 summary_nodes = filter(lambda n: n.nodeName == "summary" and n.nodeType == n.ELEMENT_NODE, patch_node.childNodes)
116 summary_nodes = filter(lambda n: n.nodeName == "summary" and n.nodeType == n.ELEMENT_NODE, patch_node.childNodes)
114 for summary_node in summary_nodes:
117 for summary_node in summary_nodes:
115 change_nodes = filter(lambda n: n.nodeType == n.ELEMENT_NODE, summary_node.childNodes)
118 change_nodes = filter(lambda n: n.nodeType == n.ELEMENT_NODE, summary_node.childNodes)
116 if len(change_nodes) == 0:
119 if len(change_nodes) == 0:
117 name = filter(lambda n: n.nodeName == "name", patch_node.childNodes)
120 name = filter(lambda n: n.nodeName == "name", patch_node.childNodes)
118 if not name:
121 if not name:
119 error("Darcs patch has an empty summary node and no name: " + patch_node.toxml())
122 error("Darcs patch has an empty summary node and no name: " + patch_node.toxml())
120 name = name[0].childNodes[0].data.strip()
123 name = name[0].childNodes[0].data.strip()
121 (tag, sub_count) = re.subn('^TAG ', '', name, 1)
124 (tag, sub_count) = re.subn('^TAG ', '', name, 1)
122 if sub_count != 1:
125 if sub_count != 1:
123 error("Darcs patch has an empty summary node but doesn't look like a tag: " + patch_node.toxml());
126 error("Darcs patch has an empty summary node but doesn't look like a tag: " + patch_node.toxml());
124 for change_node in change_nodes:
127 for change_node in change_nodes:
125 change = change_node.nodeName
128 change = change_node.nodeName
126 if change == 'modify_file':
129 if change == 'modify_file':
127 yield change, change_node.childNodes[0].data.strip()
130 yield change, change_node.childNodes[0].data.strip()
128 elif change == 'add_file':
131 elif change == 'add_file':
129 yield change, change_node.childNodes[0].data.strip()
132 yield change, change_node.childNodes[0].data.strip()
130 elif change == 'remove_file':
133 elif change == 'remove_file':
131 yield change, change_node.childNodes[0].data.strip()
134 yield change, change_node.childNodes[0].data.strip()
132 elif change == 'add_directory':
135 elif change == 'add_directory':
133 yield change, change_node.childNodes[0].data.strip()
136 yield change, change_node.childNodes[0].data.strip()
134 elif change == 'remove_directory':
137 elif change == 'remove_directory':
135 yield change, change_node.childNodes[0].data.strip()
138 yield change, change_node.childNodes[0].data.strip()
136 elif change == 'move':
139 elif change == 'move':
137 yield change, (change_node.getAttribute('from'), change_node.getAttribute('to'))
140 yield change, (change_node.getAttribute('from'), change_node.getAttribute('to'))
138 else:
141 else:
139 error('Problem parsing summary xml: Unexpected element: ' + change_node.toxml())
142 error('Problem parsing summary xml: Unexpected element: ' + change_node.toxml())
140
143
141 # ------------------------------------------------------------------------------
144 # ------------------------------------------------------------------------------
142 #
145 #
143 # Mercurial interface
146 # Mercurial interface
144 #
147 #
145 # ------------------------------------------------------------------------------
148 # ------------------------------------------------------------------------------
146
149
147 def hg_commit( hg_repo, text, author, date ):
150 def hg_commit( hg_repo, text, author, date ):
148 fd, tmpfile = tempfile.mkstemp(prefix="darcs2hg_")
151 fd, tmpfile = tempfile.mkstemp(prefix="darcs2hg_")
149 writefile(tmpfile, text)
152 writefile(tmpfile, text)
150 old_tip = hg_tip(hg_repo)
153 old_tip = hg_tip(hg_repo)
151 cmd("hg add -X _darcs", hg_repo)
154 cmd("hg add -X _darcs", hg_repo)
152 cmd("hg remove -X _darcs --after", hg_repo)
155 cmd("hg remove -X _darcs --after", hg_repo)
153 res = cmd("hg commit -l %s -u \"%s\" -d \"%s 0\"" % (tmpfile, author, date), hg_repo)
156 res = cmd("hg commit -l %s -u \"%s\" -d \"%s 0\"" % (tmpfile, author, date), hg_repo)
154 os.close(fd)
157 os.close(fd)
155 os.unlink(tmpfile)
158 os.unlink(tmpfile)
156 new_tip = hg_tip(hg_repo)
159 new_tip = hg_tip(hg_repo)
157 if not new_tip == old_tip + 1:
160 if not new_tip == old_tip + 1:
158 # Sometimes we may have empty commits, we simply skip them
161 # Sometimes we may have empty commits, we simply skip them
159 if res.strip().lower().find("nothing changed") != -1:
162 if res.strip().lower().find("nothing changed") != -1:
160 pass
163 pass
161 else:
164 else:
162 error("Mercurial commit did not work as expected: " + res)
165 error("Mercurial commit did not work as expected: " + res)
163
166
164 def hg_tip( hg_repo ):
167 def hg_tip( hg_repo ):
165 """Returns the latest local revision number in the given repository."""
168 """Returns the latest local revision number in the given repository."""
166 tip = cmd("hg tip", hg_repo, silent=True)
169 tip = cmd("hg tip", hg_repo, silent=True)
167 tip = tip.split("\n")[0].split(":")[1].strip()
170 tip = tip.split("\n")[0].split(":")[1].strip()
168 return int(tip)
171 return int(tip)
169
172
170 def hg_rename( hg_repo, from_file, to_file ):
173 def hg_rename( hg_repo, from_file, to_file ):
171 cmd("hg rename --after \"%s\" \"%s\"" % (from_file, to_file), hg_repo);
174 cmd("hg rename --after \"%s\" \"%s\"" % (from_file, to_file), hg_repo);
172
175
173 def hg_tag ( hg_repo, text, author, date ):
176 def hg_tag ( hg_repo, text, author, date ):
174 old_tip = hg_tip(hg_repo)
177 old_tip = hg_tip(hg_repo)
175 res = cmd("hg tag -u \"%s\" -d \"%s 0\" \"%s\"" % (author, date, text), hg_repo)
178 res = cmd("hg tag -u \"%s\" -d \"%s 0\" \"%s\"" % (author, date, text), hg_repo)
176 new_tip = hg_tip(hg_repo)
179 new_tip = hg_tip(hg_repo)
177 if not new_tip == old_tip + 1:
180 if not new_tip == old_tip + 1:
178 error("Mercurial tag did not work as expected: " + res)
181 error("Mercurial tag did not work as expected: " + res)
179
182
180 def hg_handle_change( hg_repo, author, date, change, arg ):
183 def hg_handle_change( hg_repo, author, date, change, arg ):
181 """Processes a change event as output by darcs_changes_summary. These
184 """Processes a change event as output by darcs_changes_summary. These
182 consist of file move/rename/add/delete commands."""
185 consist of file move/rename/add/delete commands."""
183 if change == 'modify_file':
186 if change == 'modify_file':
184 pass
187 pass
185 elif change == 'add_file':
188 elif change == 'add_file':
186 pass
189 pass
187 elif change =='remove_file':
190 elif change =='remove_file':
188 pass
191 pass
189 elif change == 'add_directory':
192 elif change == 'add_directory':
190 pass
193 pass
191 elif change == 'remove_directory':
194 elif change == 'remove_directory':
192 pass
195 pass
193 elif change == 'move':
196 elif change == 'move':
194 hg_rename(hg_repo, arg[0], arg[1])
197 hg_rename(hg_repo, arg[0], arg[1])
195 elif change == 'tag':
198 elif change == 'tag':
196 hg_tag(hg_repo, arg, author, date)
199 hg_tag(hg_repo, arg, author, date)
197 else:
200 else:
198 error('Unknown change type ' + change + ': ' + arg)
201 error('Unknown change type ' + change + ': ' + arg)
199
202
200 # ------------------------------------------------------------------------------
203 # ------------------------------------------------------------------------------
201 #
204 #
202 # Main
205 # Main
203 #
206 #
204 # ------------------------------------------------------------------------------
207 # ------------------------------------------------------------------------------
205
208
206 if __name__ == "__main__":
209 if __name__ == "__main__":
207 args = sys.argv[1:]
210 args = sys.argv[1:]
208 # We parse the arguments
211 # We parse the arguments
209 if len(args) == 2:
212 if len(args) == 2:
210 darcs_repo = os.path.abspath(args[0])
213 darcs_repo = os.path.abspath(args[0])
211 hg_repo = os.path.abspath(args[1])
214 hg_repo = os.path.abspath(args[1])
212 skip = None
215 skip = None
213 elif len(args) == 3:
216 elif len(args) == 3:
214 darcs_repo = os.path.abspath(args[0])
217 darcs_repo = os.path.abspath(args[0])
215 hg_repo = os.path.abspath(args[1])
218 hg_repo = os.path.abspath(args[1])
216 skip = int(args[2])
219 skip = int(args[2])
217 else:
220 else:
218 print USAGE
221 print USAGE
219 sys.exit(-1)
222 sys.exit(-1)
223 print 'This command is deprecated. Use the convert extension instead.'
220 # Initializes the target repo
224 # Initializes the target repo
221 if not os.path.isdir(darcs_repo + "/_darcs"):
225 if not os.path.isdir(darcs_repo + "/_darcs"):
222 print "No darcs directory found at: " + darcs_repo
226 print "No darcs directory found at: " + darcs_repo
223 sys.exit(-1)
227 sys.exit(-1)
224 if not os.path.isdir(hg_repo):
228 if not os.path.isdir(hg_repo):
225 os.mkdir(hg_repo)
229 os.mkdir(hg_repo)
226 elif skip == None:
230 elif skip == None:
227 print "Given HG repository must not exist when no SKIP is specified."
231 print "Given HG repository must not exist when no SKIP is specified."
228 sys.exit(-1)
232 sys.exit(-1)
229 if skip == None:
233 if skip == None:
230 cmd("hg init \"%s\"" % (hg_repo))
234 cmd("hg init \"%s\"" % (hg_repo))
231 cmd("darcs initialize", hg_repo)
235 cmd("darcs initialize", hg_repo)
232 # Get the changes from the Darcs repository
236 # Get the changes from the Darcs repository
233 change_number = 0
237 change_number = 0
234 for author, date, summary, chash, description in darcs_changes(darcs_repo):
238 for author, date, summary, chash, description in darcs_changes(darcs_repo):
235 print "== changeset", change_number,
239 print "== changeset", change_number,
236 if skip != None and change_number <= skip:
240 if skip != None and change_number <= skip:
237 print "(skipping)"
241 print "(skipping)"
238 else:
242 else:
239 text = summary + "\n" + description
243 text = summary + "\n" + description
240 # The commit hash has a date like 20021020201112
244 # The commit hash has a date like 20021020201112
241 # --------------------------------YYYYMMDDHHMMSS
245 # --------------------------------YYYYMMDDHHMMSS
242 date = chash.split("-")[0]
246 date = chash.split("-")[0]
243 epoch = int(mktime(strptime(date, '%Y%m%d%H%M%S')))
247 epoch = int(mktime(strptime(date, '%Y%m%d%H%M%S')))
244 darcs_pull(hg_repo, darcs_repo, chash)
248 darcs_pull(hg_repo, darcs_repo, chash)
245 for change, arg in darcs_changes_summary(darcs_repo, chash):
249 for change, arg in darcs_changes_summary(darcs_repo, chash):
246 hg_handle_change(hg_repo, author, epoch, change, arg)
250 hg_handle_change(hg_repo, author, epoch, change, arg)
247 hg_commit(hg_repo, text, author, epoch)
251 hg_commit(hg_repo, text, author, epoch)
248 change_number += 1
252 change_number += 1
249 print "Darcs repository (_darcs) was not deleted. You can keep or remove it."
253 print "Darcs repository (_darcs) was not deleted. You can keep or remove it."
250
254
251 # EOF
255 # EOF
@@ -1,487 +1,489 b''
1 # convert.py Foreign SCM converter
1 # convert.py Foreign SCM converter
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 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 from common import NoRepo, converter_source, converter_sink
8 from common import NoRepo, converter_source, converter_sink
9 from cvs import convert_cvs
9 from cvs import convert_cvs
10 from darcs import darcs_source
10 from git import convert_git
11 from git import convert_git
11 from hg import mercurial_source, mercurial_sink
12 from hg import mercurial_source, mercurial_sink
12 from subversion import convert_svn, debugsvnlog
13 from subversion import convert_svn, debugsvnlog
13
14
14 import os, shlex, shutil
15 import os, shlex, shutil
15 from mercurial import hg, ui, util, commands
16 from mercurial import hg, ui, util, commands
16 from mercurial.i18n import _
17 from mercurial.i18n import _
17
18
18 commands.norepo += " convert debugsvnlog"
19 commands.norepo += " convert debugsvnlog"
19
20
20 converters = [convert_cvs, convert_git, convert_svn, mercurial_source,
21 converters = [convert_cvs, convert_git, convert_svn, mercurial_source,
21 mercurial_sink]
22 mercurial_sink, darcs_source]
22
23
23 def convertsource(ui, path, **opts):
24 def convertsource(ui, path, **opts):
24 for c in converters:
25 for c in converters:
25 try:
26 try:
26 return c.getcommit and c(ui, path, **opts)
27 return c.getcommit and c(ui, path, **opts)
27 except (AttributeError, NoRepo):
28 except (AttributeError, NoRepo):
28 pass
29 pass
29 raise util.Abort('%s: unknown repository type' % path)
30 raise util.Abort('%s: unknown repository type' % path)
30
31
31 def convertsink(ui, path):
32 def convertsink(ui, path):
32 if not os.path.isdir(path):
33 if not os.path.isdir(path):
33 raise util.Abort("%s: not a directory" % path)
34 raise util.Abort("%s: not a directory" % path)
34 for c in converters:
35 for c in converters:
35 try:
36 try:
36 return c.putcommit and c(ui, path)
37 return c.putcommit and c(ui, path)
37 except (AttributeError, NoRepo):
38 except (AttributeError, NoRepo):
38 pass
39 pass
39 raise util.Abort('%s: unknown repository type' % path)
40 raise util.Abort('%s: unknown repository type' % path)
40
41
41 class converter(object):
42 class converter(object):
42 def __init__(self, ui, source, dest, revmapfile, filemapper, opts):
43 def __init__(self, ui, source, dest, revmapfile, filemapper, opts):
43
44
44 self.source = source
45 self.source = source
45 self.dest = dest
46 self.dest = dest
46 self.ui = ui
47 self.ui = ui
47 self.opts = opts
48 self.opts = opts
48 self.commitcache = {}
49 self.commitcache = {}
49 self.revmapfile = revmapfile
50 self.revmapfile = revmapfile
50 self.revmapfilefd = None
51 self.revmapfilefd = None
51 self.authors = {}
52 self.authors = {}
52 self.authorfile = None
53 self.authorfile = None
53 self.mapfile = filemapper
54 self.mapfile = filemapper
54
55
55 self.map = {}
56 self.map = {}
56 try:
57 try:
57 origrevmapfile = open(self.revmapfile, 'r')
58 origrevmapfile = open(self.revmapfile, 'r')
58 for l in origrevmapfile:
59 for l in origrevmapfile:
59 sv, dv = l[:-1].split()
60 sv, dv = l[:-1].split()
60 self.map[sv] = dv
61 self.map[sv] = dv
61 origrevmapfile.close()
62 origrevmapfile.close()
62 except IOError:
63 except IOError:
63 pass
64 pass
64
65
65 # Read first the dst author map if any
66 # Read first the dst author map if any
66 authorfile = self.dest.authorfile()
67 authorfile = self.dest.authorfile()
67 if authorfile and os.path.exists(authorfile):
68 if authorfile and os.path.exists(authorfile):
68 self.readauthormap(authorfile)
69 self.readauthormap(authorfile)
69 # Extend/Override with new author map if necessary
70 # Extend/Override with new author map if necessary
70 if opts.get('authors'):
71 if opts.get('authors'):
71 self.readauthormap(opts.get('authors'))
72 self.readauthormap(opts.get('authors'))
72 self.authorfile = self.dest.authorfile()
73 self.authorfile = self.dest.authorfile()
73
74
74 def walktree(self, heads):
75 def walktree(self, heads):
75 '''Return a mapping that identifies the uncommitted parents of every
76 '''Return a mapping that identifies the uncommitted parents of every
76 uncommitted changeset.'''
77 uncommitted changeset.'''
77 visit = heads
78 visit = heads
78 known = {}
79 known = {}
79 parents = {}
80 parents = {}
80 while visit:
81 while visit:
81 n = visit.pop(0)
82 n = visit.pop(0)
82 if n in known or n in self.map: continue
83 if n in known or n in self.map: continue
83 known[n] = 1
84 known[n] = 1
84 commit = self.cachecommit(n)
85 commit = self.cachecommit(n)
85 parents[n] = []
86 parents[n] = []
86 for p in commit.parents:
87 for p in commit.parents:
87 parents[n].append(p)
88 parents[n].append(p)
88 visit.append(p)
89 visit.append(p)
89
90
90 return parents
91 return parents
91
92
92 def toposort(self, parents):
93 def toposort(self, parents):
93 '''Return an ordering such that every uncommitted changeset is
94 '''Return an ordering such that every uncommitted changeset is
94 preceeded by all its uncommitted ancestors.'''
95 preceeded by all its uncommitted ancestors.'''
95 visit = parents.keys()
96 visit = parents.keys()
96 seen = {}
97 seen = {}
97 children = {}
98 children = {}
98
99
99 while visit:
100 while visit:
100 n = visit.pop(0)
101 n = visit.pop(0)
101 if n in seen: continue
102 if n in seen: continue
102 seen[n] = 1
103 seen[n] = 1
103 # Ensure that nodes without parents are present in the 'children'
104 # Ensure that nodes without parents are present in the 'children'
104 # mapping.
105 # mapping.
105 children.setdefault(n, [])
106 children.setdefault(n, [])
106 for p in parents[n]:
107 for p in parents[n]:
107 if not p in self.map:
108 if not p in self.map:
108 visit.append(p)
109 visit.append(p)
109 children.setdefault(p, []).append(n)
110 children.setdefault(p, []).append(n)
110
111
111 s = []
112 s = []
112 removed = {}
113 removed = {}
113 visit = children.keys()
114 visit = children.keys()
114 while visit:
115 while visit:
115 n = visit.pop(0)
116 n = visit.pop(0)
116 if n in removed: continue
117 if n in removed: continue
117 dep = 0
118 dep = 0
118 if n in parents:
119 if n in parents:
119 for p in parents[n]:
120 for p in parents[n]:
120 if p in self.map: continue
121 if p in self.map: continue
121 if p not in removed:
122 if p not in removed:
122 # we're still dependent
123 # we're still dependent
123 visit.append(n)
124 visit.append(n)
124 dep = 1
125 dep = 1
125 break
126 break
126
127
127 if not dep:
128 if not dep:
128 # all n's parents are in the list
129 # all n's parents are in the list
129 removed[n] = 1
130 removed[n] = 1
130 if n not in self.map:
131 if n not in self.map:
131 s.append(n)
132 s.append(n)
132 if n in children:
133 if n in children:
133 for c in children[n]:
134 for c in children[n]:
134 visit.insert(0, c)
135 visit.insert(0, c)
135
136
136 if self.opts.get('datesort'):
137 if self.opts.get('datesort'):
137 depth = {}
138 depth = {}
138 for n in s:
139 for n in s:
139 depth[n] = 0
140 depth[n] = 0
140 pl = [p for p in self.commitcache[n].parents
141 pl = [p for p in self.commitcache[n].parents
141 if p not in self.map]
142 if p not in self.map]
142 if pl:
143 if pl:
143 depth[n] = max([depth[p] for p in pl]) + 1
144 depth[n] = max([depth[p] for p in pl]) + 1
144
145
145 s = [(depth[n], self.commitcache[n].date, n) for n in s]
146 s = [(depth[n], self.commitcache[n].date, n) for n in s]
146 s.sort()
147 s.sort()
147 s = [e[2] for e in s]
148 s = [e[2] for e in s]
148
149
149 return s
150 return s
150
151
151 def mapentry(self, src, dst):
152 def mapentry(self, src, dst):
152 if self.revmapfilefd is None:
153 if self.revmapfilefd is None:
153 try:
154 try:
154 self.revmapfilefd = open(self.revmapfile, "a")
155 self.revmapfilefd = open(self.revmapfile, "a")
155 except IOError, (errno, strerror):
156 except IOError, (errno, strerror):
156 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.revmapfile, errno, strerror))
157 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.revmapfile, errno, strerror))
157 self.map[src] = dst
158 self.map[src] = dst
158 self.revmapfilefd.write("%s %s\n" % (src, dst))
159 self.revmapfilefd.write("%s %s\n" % (src, dst))
159 self.revmapfilefd.flush()
160 self.revmapfilefd.flush()
160
161
161 def writeauthormap(self):
162 def writeauthormap(self):
162 authorfile = self.authorfile
163 authorfile = self.authorfile
163 if authorfile:
164 if authorfile:
164 self.ui.status('Writing author map file %s\n' % authorfile)
165 self.ui.status('Writing author map file %s\n' % authorfile)
165 ofile = open(authorfile, 'w+')
166 ofile = open(authorfile, 'w+')
166 for author in self.authors:
167 for author in self.authors:
167 ofile.write("%s=%s\n" % (author, self.authors[author]))
168 ofile.write("%s=%s\n" % (author, self.authors[author]))
168 ofile.close()
169 ofile.close()
169
170
170 def readauthormap(self, authorfile):
171 def readauthormap(self, authorfile):
171 afile = open(authorfile, 'r')
172 afile = open(authorfile, 'r')
172 for line in afile:
173 for line in afile:
173 try:
174 try:
174 srcauthor = line.split('=')[0].strip()
175 srcauthor = line.split('=')[0].strip()
175 dstauthor = line.split('=')[1].strip()
176 dstauthor = line.split('=')[1].strip()
176 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
177 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
177 self.ui.status(
178 self.ui.status(
178 'Overriding mapping for author %s, was %s, will be %s\n'
179 'Overriding mapping for author %s, was %s, will be %s\n'
179 % (srcauthor, self.authors[srcauthor], dstauthor))
180 % (srcauthor, self.authors[srcauthor], dstauthor))
180 else:
181 else:
181 self.ui.debug('Mapping author %s to %s\n'
182 self.ui.debug('Mapping author %s to %s\n'
182 % (srcauthor, dstauthor))
183 % (srcauthor, dstauthor))
183 self.authors[srcauthor] = dstauthor
184 self.authors[srcauthor] = dstauthor
184 except IndexError:
185 except IndexError:
185 self.ui.warn(
186 self.ui.warn(
186 'Ignoring bad line in author file map %s: %s\n'
187 'Ignoring bad line in author file map %s: %s\n'
187 % (authorfile, line))
188 % (authorfile, line))
188 afile.close()
189 afile.close()
189
190
190 def cachecommit(self, rev):
191 def cachecommit(self, rev):
191 commit = self.source.getcommit(rev)
192 commit = self.source.getcommit(rev)
192 commit.author = self.authors.get(commit.author, commit.author)
193 commit.author = self.authors.get(commit.author, commit.author)
193 self.commitcache[rev] = commit
194 self.commitcache[rev] = commit
194 return commit
195 return commit
195
196
196 def copy(self, rev):
197 def copy(self, rev):
197 commit = self.commitcache[rev]
198 commit = self.commitcache[rev]
198 do_copies = hasattr(self.dest, 'copyfile')
199 do_copies = hasattr(self.dest, 'copyfile')
199 filenames = []
200 filenames = []
200
201
201 files, copies = self.source.getchanges(rev)
202 files, copies = self.source.getchanges(rev)
202 parents = [self.map[r] for r in commit.parents]
203 parents = [self.map[r] for r in commit.parents]
203 if commit.parents:
204 if commit.parents:
204 prev = commit.parents[0]
205 prev = commit.parents[0]
205 if prev not in self.commitcache:
206 if prev not in self.commitcache:
206 self.cachecommit(prev)
207 self.cachecommit(prev)
207 pbranch = self.commitcache[prev].branch
208 pbranch = self.commitcache[prev].branch
208 else:
209 else:
209 pbranch = None
210 pbranch = None
210 self.dest.setbranch(commit.branch, pbranch, parents)
211 self.dest.setbranch(commit.branch, pbranch, parents)
211 for f, v in files:
212 for f, v in files:
212 newf = self.mapfile(f)
213 newf = self.mapfile(f)
213 if not newf:
214 if not newf:
214 continue
215 continue
215 filenames.append(newf)
216 filenames.append(newf)
216 try:
217 try:
217 data = self.source.getfile(f, v)
218 data = self.source.getfile(f, v)
218 except IOError, inst:
219 except IOError, inst:
219 self.dest.delfile(newf)
220 self.dest.delfile(newf)
220 else:
221 else:
221 e = self.source.getmode(f, v)
222 e = self.source.getmode(f, v)
222 self.dest.putfile(newf, e, data)
223 self.dest.putfile(newf, e, data)
223 if do_copies:
224 if do_copies:
224 if f in copies:
225 if f in copies:
225 copyf = self.mapfile(copies[f])
226 copyf = self.mapfile(copies[f])
226 if copyf:
227 if copyf:
227 # Merely marks that a copy happened.
228 # Merely marks that a copy happened.
228 self.dest.copyfile(copyf, newf)
229 self.dest.copyfile(copyf, newf)
229
230
230 if not filenames and self.mapfile.active():
231 if not filenames and self.mapfile.active():
231 newnode = parents[0]
232 newnode = parents[0]
232 else:
233 else:
233 newnode = self.dest.putcommit(filenames, parents, commit)
234 newnode = self.dest.putcommit(filenames, parents, commit)
234 self.mapentry(rev, newnode)
235 self.mapentry(rev, newnode)
235
236
236 def convert(self):
237 def convert(self):
237 try:
238 try:
238 self.source.before()
239 self.source.before()
239 self.dest.before()
240 self.dest.before()
240 self.source.setrevmap(self.map)
241 self.source.setrevmap(self.map)
241 self.ui.status("scanning source...\n")
242 self.ui.status("scanning source...\n")
242 heads = self.source.getheads()
243 heads = self.source.getheads()
243 parents = self.walktree(heads)
244 parents = self.walktree(heads)
244 self.ui.status("sorting...\n")
245 self.ui.status("sorting...\n")
245 t = self.toposort(parents)
246 t = self.toposort(parents)
246 num = len(t)
247 num = len(t)
247 c = None
248 c = None
248
249
249 self.ui.status("converting...\n")
250 self.ui.status("converting...\n")
250 for c in t:
251 for c in t:
251 num -= 1
252 num -= 1
252 desc = self.commitcache[c].desc
253 desc = self.commitcache[c].desc
253 if "\n" in desc:
254 if "\n" in desc:
254 desc = desc.splitlines()[0]
255 desc = desc.splitlines()[0]
255 self.ui.status("%d %s\n" % (num, desc))
256 self.ui.status("%d %s\n" % (num, desc))
256 self.copy(c)
257 self.copy(c)
257
258
258 tags = self.source.gettags()
259 tags = self.source.gettags()
259 ctags = {}
260 ctags = {}
260 for k in tags:
261 for k in tags:
261 v = tags[k]
262 v = tags[k]
262 if v in self.map:
263 if v in self.map:
263 ctags[k] = self.map[v]
264 ctags[k] = self.map[v]
264
265
265 if c and ctags:
266 if c and ctags:
266 nrev = self.dest.puttags(ctags)
267 nrev = self.dest.puttags(ctags)
267 # write another hash correspondence to override the previous
268 # write another hash correspondence to override the previous
268 # one so we don't end up with extra tag heads
269 # one so we don't end up with extra tag heads
269 if nrev:
270 if nrev:
270 self.mapentry(c, nrev)
271 self.mapentry(c, nrev)
271
272
272 self.writeauthormap()
273 self.writeauthormap()
273 finally:
274 finally:
274 self.cleanup()
275 self.cleanup()
275
276
276 def cleanup(self):
277 def cleanup(self):
277 try:
278 try:
278 self.dest.after()
279 self.dest.after()
279 finally:
280 finally:
280 self.source.after()
281 self.source.after()
281 if self.revmapfilefd:
282 if self.revmapfilefd:
282 self.revmapfilefd.close()
283 self.revmapfilefd.close()
283
284
284 def rpairs(name):
285 def rpairs(name):
285 e = len(name)
286 e = len(name)
286 while e != -1:
287 while e != -1:
287 yield name[:e], name[e+1:]
288 yield name[:e], name[e+1:]
288 e = name.rfind('/', 0, e)
289 e = name.rfind('/', 0, e)
289
290
290 class filemapper(object):
291 class filemapper(object):
291 '''Map and filter filenames when importing.
292 '''Map and filter filenames when importing.
292 A name can be mapped to itself, a new name, or None (omit from new
293 A name can be mapped to itself, a new name, or None (omit from new
293 repository).'''
294 repository).'''
294
295
295 def __init__(self, ui, path=None):
296 def __init__(self, ui, path=None):
296 self.ui = ui
297 self.ui = ui
297 self.include = {}
298 self.include = {}
298 self.exclude = {}
299 self.exclude = {}
299 self.rename = {}
300 self.rename = {}
300 if path:
301 if path:
301 if self.parse(path):
302 if self.parse(path):
302 raise util.Abort(_('errors in filemap'))
303 raise util.Abort(_('errors in filemap'))
303
304
304 def parse(self, path):
305 def parse(self, path):
305 errs = 0
306 errs = 0
306 def check(name, mapping, listname):
307 def check(name, mapping, listname):
307 if name in mapping:
308 if name in mapping:
308 self.ui.warn(_('%s:%d: %r already in %s list\n') %
309 self.ui.warn(_('%s:%d: %r already in %s list\n') %
309 (lex.infile, lex.lineno, name, listname))
310 (lex.infile, lex.lineno, name, listname))
310 return 1
311 return 1
311 return 0
312 return 0
312 lex = shlex.shlex(open(path), path, True)
313 lex = shlex.shlex(open(path), path, True)
313 lex.wordchars += '!@#$%^&*()-=+[]{}|;:,./<>?'
314 lex.wordchars += '!@#$%^&*()-=+[]{}|;:,./<>?'
314 cmd = lex.get_token()
315 cmd = lex.get_token()
315 while cmd:
316 while cmd:
316 if cmd == 'include':
317 if cmd == 'include':
317 name = lex.get_token()
318 name = lex.get_token()
318 errs += check(name, self.exclude, 'exclude')
319 errs += check(name, self.exclude, 'exclude')
319 self.include[name] = name
320 self.include[name] = name
320 elif cmd == 'exclude':
321 elif cmd == 'exclude':
321 name = lex.get_token()
322 name = lex.get_token()
322 errs += check(name, self.include, 'include')
323 errs += check(name, self.include, 'include')
323 errs += check(name, self.rename, 'rename')
324 errs += check(name, self.rename, 'rename')
324 self.exclude[name] = name
325 self.exclude[name] = name
325 elif cmd == 'rename':
326 elif cmd == 'rename':
326 src = lex.get_token()
327 src = lex.get_token()
327 dest = lex.get_token()
328 dest = lex.get_token()
328 errs += check(src, self.exclude, 'exclude')
329 errs += check(src, self.exclude, 'exclude')
329 self.rename[src] = dest
330 self.rename[src] = dest
330 elif cmd == 'source':
331 elif cmd == 'source':
331 errs += self.parse(lex.get_token())
332 errs += self.parse(lex.get_token())
332 else:
333 else:
333 self.ui.warn(_('%s:%d: unknown directive %r\n') %
334 self.ui.warn(_('%s:%d: unknown directive %r\n') %
334 (lex.infile, lex.lineno, cmd))
335 (lex.infile, lex.lineno, cmd))
335 errs += 1
336 errs += 1
336 cmd = lex.get_token()
337 cmd = lex.get_token()
337 return errs
338 return errs
338
339
339 def lookup(self, name, mapping):
340 def lookup(self, name, mapping):
340 for pre, suf in rpairs(name):
341 for pre, suf in rpairs(name):
341 try:
342 try:
342 return mapping[pre], pre, suf
343 return mapping[pre], pre, suf
343 except KeyError, err:
344 except KeyError, err:
344 pass
345 pass
345 return '', name, ''
346 return '', name, ''
346
347
347 def __call__(self, name):
348 def __call__(self, name):
348 if self.include:
349 if self.include:
349 inc = self.lookup(name, self.include)[0]
350 inc = self.lookup(name, self.include)[0]
350 else:
351 else:
351 inc = name
352 inc = name
352 if self.exclude:
353 if self.exclude:
353 exc = self.lookup(name, self.exclude)[0]
354 exc = self.lookup(name, self.exclude)[0]
354 else:
355 else:
355 exc = ''
356 exc = ''
356 if not inc or exc:
357 if not inc or exc:
357 return None
358 return None
358 newpre, pre, suf = self.lookup(name, self.rename)
359 newpre, pre, suf = self.lookup(name, self.rename)
359 if newpre:
360 if newpre:
360 if newpre == '.':
361 if newpre == '.':
361 return suf
362 return suf
362 if suf:
363 if suf:
363 return newpre + '/' + suf
364 return newpre + '/' + suf
364 return newpre
365 return newpre
365 return name
366 return name
366
367
367 def active(self):
368 def active(self):
368 return bool(self.include or self.exclude or self.rename)
369 return bool(self.include or self.exclude or self.rename)
369
370
370 def convert(ui, src, dest=None, revmapfile=None, **opts):
371 def convert(ui, src, dest=None, revmapfile=None, **opts):
371 """Convert a foreign SCM repository to a Mercurial one.
372 """Convert a foreign SCM repository to a Mercurial one.
372
373
373 Accepted source formats:
374 Accepted source formats:
374 - GIT
375 - CVS
375 - CVS
376 - SVN
376 - Darcs
377 - git
378 - Subversion
377
379
378 Accepted destination formats:
380 Accepted destination formats:
379 - Mercurial
381 - Mercurial
380
382
381 If no revision is given, all revisions will be converted. Otherwise,
383 If no revision is given, all revisions will be converted. Otherwise,
382 convert will only import up to the named revision (given in a format
384 convert will only import up to the named revision (given in a format
383 understood by the source).
385 understood by the source).
384
386
385 If no destination directory name is specified, it defaults to the
387 If no destination directory name is specified, it defaults to the
386 basename of the source with '-hg' appended. If the destination
388 basename of the source with '-hg' appended. If the destination
387 repository doesn't exist, it will be created.
389 repository doesn't exist, it will be created.
388
390
389 If <revmapfile> isn't given, it will be put in a default location
391 If <revmapfile> isn't given, it will be put in a default location
390 (<dest>/.hg/shamap by default). The <revmapfile> is a simple text
392 (<dest>/.hg/shamap by default). The <revmapfile> is a simple text
391 file that maps each source commit ID to the destination ID for
393 file that maps each source commit ID to the destination ID for
392 that revision, like so:
394 that revision, like so:
393 <source ID> <destination ID>
395 <source ID> <destination ID>
394
396
395 If the file doesn't exist, it's automatically created. It's updated
397 If the file doesn't exist, it's automatically created. It's updated
396 on each commit copied, so convert-repo can be interrupted and can
398 on each commit copied, so convert-repo can be interrupted and can
397 be run repeatedly to copy new commits.
399 be run repeatedly to copy new commits.
398
400
399 The [username mapping] file is a simple text file that maps each source
401 The [username mapping] file is a simple text file that maps each source
400 commit author to a destination commit author. It is handy for source SCMs
402 commit author to a destination commit author. It is handy for source SCMs
401 that use unix logins to identify authors (eg: CVS). One line per author
403 that use unix logins to identify authors (eg: CVS). One line per author
402 mapping and the line format is:
404 mapping and the line format is:
403 srcauthor=whatever string you want
405 srcauthor=whatever string you want
404
406
405 The filemap is a file that allows filtering and remapping of files
407 The filemap is a file that allows filtering and remapping of files
406 and directories. Comment lines start with '#'. Each line can
408 and directories. Comment lines start with '#'. Each line can
407 contain one of the following directives:
409 contain one of the following directives:
408
410
409 include path/to/file
411 include path/to/file
410
412
411 exclude path/to/file
413 exclude path/to/file
412
414
413 rename from/file to/file
415 rename from/file to/file
414
416
415 The 'include' directive causes a file, or all files under a
417 The 'include' directive causes a file, or all files under a
416 directory, to be included in the destination repository. The
418 directory, to be included in the destination repository. The
417 'exclude' directive causes files or directories to be omitted.
419 'exclude' directive causes files or directories to be omitted.
418 The 'rename' directive renames a file or directory. To rename
420 The 'rename' directive renames a file or directory. To rename
419 from a subdirectory into the root of the repository, use '.' as
421 from a subdirectory into the root of the repository, use '.' as
420 the path to rename to.
422 the path to rename to.
421 """
423 """
422
424
423 util._encoding = 'UTF-8'
425 util._encoding = 'UTF-8'
424
426
425 if not dest:
427 if not dest:
426 dest = hg.defaultdest(src) + "-hg"
428 dest = hg.defaultdest(src) + "-hg"
427 ui.status("assuming destination %s\n" % dest)
429 ui.status("assuming destination %s\n" % dest)
428
430
429 # Try to be smart and initalize things when required
431 # Try to be smart and initalize things when required
430 created = False
432 created = False
431 if os.path.isdir(dest):
433 if os.path.isdir(dest):
432 if len(os.listdir(dest)) > 0:
434 if len(os.listdir(dest)) > 0:
433 try:
435 try:
434 hg.repository(ui, dest)
436 hg.repository(ui, dest)
435 ui.status("destination %s is a Mercurial repository\n" % dest)
437 ui.status("destination %s is a Mercurial repository\n" % dest)
436 except hg.RepoError:
438 except hg.RepoError:
437 raise util.Abort(
439 raise util.Abort(
438 "destination directory %s is not empty.\n"
440 "destination directory %s is not empty.\n"
439 "Please specify an empty directory to be initialized\n"
441 "Please specify an empty directory to be initialized\n"
440 "or an already initialized mercurial repository"
442 "or an already initialized mercurial repository"
441 % dest)
443 % dest)
442 else:
444 else:
443 ui.status("initializing destination %s repository\n" % dest)
445 ui.status("initializing destination %s repository\n" % dest)
444 hg.repository(ui, dest, create=True)
446 hg.repository(ui, dest, create=True)
445 created = True
447 created = True
446 elif os.path.exists(dest):
448 elif os.path.exists(dest):
447 raise util.Abort("destination %s exists and is not a directory" % dest)
449 raise util.Abort("destination %s exists and is not a directory" % dest)
448 else:
450 else:
449 ui.status("initializing destination %s repository\n" % dest)
451 ui.status("initializing destination %s repository\n" % dest)
450 hg.repository(ui, dest, create=True)
452 hg.repository(ui, dest, create=True)
451 created = True
453 created = True
452
454
453 destc = convertsink(ui, dest)
455 destc = convertsink(ui, dest)
454
456
455 try:
457 try:
456 srcc = convertsource(ui, src, rev=opts.get('rev'))
458 srcc = convertsource(ui, src, rev=opts.get('rev'))
457 except Exception:
459 except Exception:
458 if created:
460 if created:
459 shutil.rmtree(dest, True)
461 shutil.rmtree(dest, True)
460 raise
462 raise
461
463
462 if not revmapfile:
464 if not revmapfile:
463 try:
465 try:
464 revmapfile = destc.revmapfile()
466 revmapfile = destc.revmapfile()
465 except:
467 except:
466 revmapfile = os.path.join(destc, "map")
468 revmapfile = os.path.join(destc, "map")
467
469
468
470
469 c = converter(ui, srcc, destc, revmapfile, filemapper(ui, opts['filemap']),
471 c = converter(ui, srcc, destc, revmapfile, filemapper(ui, opts['filemap']),
470 opts)
472 opts)
471 c.convert()
473 c.convert()
472
474
473
475
474 cmdtable = {
476 cmdtable = {
475 "convert":
477 "convert":
476 (convert,
478 (convert,
477 [('A', 'authors', '', 'username mapping filename'),
479 [('A', 'authors', '', 'username mapping filename'),
478 ('', 'filemap', '', 'remap file names using contents of file'),
480 ('', 'filemap', '', 'remap file names using contents of file'),
479 ('r', 'rev', '', 'import up to target revision REV'),
481 ('r', 'rev', '', 'import up to target revision REV'),
480 ('', 'datesort', None, 'try to sort changesets by date')],
482 ('', 'datesort', None, 'try to sort changesets by date')],
481 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
483 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
482 "debugsvnlog":
484 "debugsvnlog":
483 (debugsvnlog,
485 (debugsvnlog,
484 [],
486 [],
485 'hg debugsvnlog'),
487 'hg debugsvnlog'),
486 }
488 }
487
489
General Comments 0
You need to be logged in to leave comments. Login now