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