##// END OF EJS Templates
convert: added GNU Arch source converter
Aleix Conchillo Flaque -
r6035:df659eb2 default
parent child Browse files
Show More
@@ -0,0 +1,263 b''
1 # GNU Arch support for the convert extension
2
3 from common import NoRepo, checktool, commandline, commit, converter_source
4 from mercurial.i18n import _
5 from mercurial import util
6 import os, shutil, tempfile, stat
7
8 class gnuarch_source(converter_source, commandline):
9
10 class gnuarch_rev:
11 def __init__(self, rev):
12 self.rev = rev
13 self.summary = ''
14 self.date = None
15 self.author = ''
16 self.add_files = []
17 self.mod_files = []
18 self.del_files = []
19 self.ren_files = {}
20 self.ren_dirs = {}
21
22 def __init__(self, ui, path, rev=None):
23 super(gnuarch_source, self).__init__(ui, path, rev=rev)
24
25 if not os.path.exists(os.path.join(path, '{arch}')):
26 raise NoRepo(_("couldn't open GNU Arch repo %s" % path))
27
28 # Could use checktool, but we want to check for baz or tla.
29 self.execmd = None
30 if util.find_exe('tla'):
31 self.execmd = 'tla'
32 else:
33 if util.find_exe('baz'):
34 self.execmd = 'baz'
35 else:
36 raise util.Abort(_('cannot find a GNU Arch tool'))
37
38 commandline.__init__(self, ui, self.execmd)
39
40 self.path = os.path.realpath(path)
41 self.tmppath = None
42
43 self.treeversion = None
44 self.lastrev = None
45 self.changes = {}
46 self.parents = {}
47 self.tags = {}
48 self.modecache = {}
49
50 def before(self):
51 if self.execmd == 'tla':
52 output = self.run0('tree-version', self.path)
53 else:
54 output = self.run0('tree-version', '-d', self.path)
55 self.treeversion = output.strip()
56
57 self.ui.status(_('analyzing tree version %s...\n' % self.treeversion))
58
59 # Get name of temporary directory
60 version = self.treeversion.split('/')
61 self.tmppath = os.path.join(tempfile.gettempdir(),
62 'hg-%s' % version[1])
63
64 # Generate parents dictionary
65 child = []
66 output, status = self.runlines('revisions', self.treeversion)
67 self.checkexit(status, 'archive registered?')
68 for l in output:
69 rev = l.strip()
70 self.changes[rev] = self.gnuarch_rev(rev)
71
72 # Read author, date and summary
73 catlog = self.runlines0('cat-log', '-d', self.path, rev)
74 self._parsecatlog(catlog, rev)
75
76 self.parents[rev] = child
77 child = [rev]
78 if rev == self.rev:
79 break
80 self.parents[None] = child
81
82 def after(self):
83 self.ui.debug(_('cleaning up %s\n' % self.tmppath))
84 shutil.rmtree(self.tmppath, ignore_errors=True)
85
86 def getheads(self):
87 return self.parents[None]
88
89 def getfile(self, name, rev):
90 if rev != self.lastrev:
91 raise util.Abort(_('internal calling inconsistency'))
92
93 # Raise IOError if necessary (i.e. deleted files).
94 if not os.path.exists(os.path.join(self.tmppath, name)):
95 raise IOError
96
97 data, mode = self._getfile(name, rev)
98 self.modecache[(name, rev)] = mode
99
100 return data
101
102 def getmode(self, name, rev):
103 return self.modecache[(name, rev)]
104
105 def getchanges(self, rev):
106 self.modecache = {}
107 self._update(rev)
108 changes = []
109 copies = {}
110
111 for f in self.changes[rev].add_files:
112 changes.append((f, rev))
113
114 for f in self.changes[rev].mod_files:
115 changes.append((f, rev))
116
117 for f in self.changes[rev].del_files:
118 changes.append((f, rev))
119
120 for src in self.changes[rev].ren_files:
121 to = self.changes[rev].ren_files[src]
122 changes.append((src, rev))
123 changes.append((to, rev))
124 copies[src] = to
125
126 for src in self.changes[rev].ren_dirs:
127 to = self.changes[rev].ren_dirs[src]
128 chgs, cps = self._rendirchanges(src, to);
129 changes += [(f, rev) for f in chgs]
130 for c in cps:
131 copies[c] = cps[c]
132
133 changes.sort()
134 self.lastrev = rev
135
136 return changes, copies
137
138 def getcommit(self, rev):
139 changes = self.changes[rev]
140 return commit(author = changes.author, date = changes.date,
141 desc = changes.summary, parents = self.parents[rev])
142
143 def gettags(self):
144 return self.tags
145
146 def _execute(self, cmd, *args, **kwargs):
147 cmdline = [self.execmd, cmd]
148 cmdline += args
149 cmdline = [util.shellquote(arg) for arg in cmdline]
150 cmdline += ['>', util.nulldev, '2>', util.nulldev]
151 cmdline = util.quotecommand(' '.join(cmdline))
152 self.ui.debug(cmdline, '\n')
153 return os.system(cmdline)
154
155 def _update(self, rev):
156 if rev == 'base-0':
157 # Initialise 'base-0' revision
158 self.ui.debug(_('obtaining revision %s...\n' % rev))
159 revision = '%s--%s' % (self.treeversion, rev)
160 output = self._execute('get', revision, self.tmppath)
161 self.ui.debug(_('analysing revision %s...\n' % rev))
162 files = self._readcontents(self.tmppath)
163 self.changes[rev].add_files += files
164 else:
165 self.ui.debug(_('applying revision %s...\n' % rev))
166 revision = '%s--%s' % (self.treeversion, rev)
167 output = self._execute('replay', '-d', self.tmppath, revision)
168
169 old_rev = self.parents[rev][0]
170 self.ui.debug(_('computing changeset between %s and %s...\n' \
171 % (old_rev, rev)))
172 rev_a = '%s--%s' % (self.treeversion, old_rev)
173 rev_b = '%s--%s' % (self.treeversion, rev)
174 delta = self.runlines0('delta', '-n', rev_a, rev_b)
175 self._parsedelta(delta, rev)
176
177 def _getfile(self, name, rev):
178 mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
179 if stat.S_ISLNK(mode):
180 data = os.readlink(os.path.join(self.tmppath, name))
181 mode = mode and 'l' or ''
182 else:
183 data = open(os.path.join(self.tmppath, name), 'rb').read()
184 mode = (mode & 0111) and 'x' or ''
185 return data, mode
186
187 def _exclude(self, name):
188 exclude = [ '{arch}', '.arch-ids', '.arch-inventory' ]
189 for exc in exclude:
190 if name.find(exc) != -1:
191 return True
192 return False
193
194 def _readcontents(self, path):
195 files = []
196 contents = os.listdir(path)
197 while len(contents) > 0:
198 c = contents.pop()
199 p = os.path.join(path, c)
200 if not self._exclude(p):
201 if os.path.isdir(p):
202 contents += [os.path.join(c, f) for f in os.listdir(p)]
203 else:
204 files.append(c)
205 return files
206
207 def _rendirchanges(self, src, dest):
208 changes = []
209 copies = {}
210 files = self._readcontents(os.path.join(self.tmppath, dest))
211 for f in files:
212 s = os.path.join(src, f)
213 d = os.path.join(dest, f)
214 changes.append(s)
215 changes.append(d)
216 copies[s] = d
217 return changes, copies
218
219 def _parsecatlog(self, data, rev):
220 for l in data:
221 l = l.strip()
222 if l.startswith('Summary:'):
223 self.changes[rev].summary = l[len('Summary: '):]
224
225 if l.startswith('Standard-date:'):
226 date = l[len('Standard-date: '):]
227 strdate = util.strdate(date, '%Y-%m-%d %H:%M:%S')
228 self.changes[rev].date = util.datestr(strdate)
229
230 if l.startswith('Creator:'):
231 self.changes[rev].author = l[len('Creator: '):]
232
233 def _parsedelta(self, data, rev):
234 for l in data:
235 l = l.strip()
236 if l.startswith('A') and not l.startswith('A/'):
237 file = l[1:].strip()
238 if not self._exclude(file):
239 self.changes[rev].add_files.append(file)
240 elif l.startswith('/>'):
241 dirs = l[2:].strip().split(' ')
242 if len(dirs) == 1:
243 dirs = l[2:].strip().split('\t')
244 if not self._exclude(dirs[0]) and not self._exclude(dirs[1]):
245 self.changes[rev].ren_dirs[dirs[0]] = dirs[1]
246 elif l.startswith('M'):
247 file = l[1:].strip()
248 if not self._exclude(file):
249 self.changes[rev].mod_files.append(file)
250 elif l.startswith('->'):
251 file = l[2:].strip()
252 if not self._exclude(file):
253 self.changes[rev].mod_files.append(file)
254 elif l.startswith('D') and not l.startswith('D/'):
255 file = l[1:].strip()
256 if not self._exclude(file):
257 self.changes[rev].del_files.append(file)
258 elif l.startswith('=>'):
259 files = l[2:].strip().split(' ')
260 if len(files) == 1:
261 files = l[2:].strip().split('\t')
262 if not self._exclude(files[0]) and not self._exclude(files[1]):
263 self.changes[rev].ren_files[files[0]] = files[1]
@@ -1,108 +1,109 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 import convcmd
8 import convcmd
9 from mercurial import commands
9 from mercurial import commands
10
10
11 # Commands definition was moved elsewhere to ease demandload job.
11 # Commands definition was moved elsewhere to ease demandload job.
12
12
13 def convert(ui, src, dest=None, revmapfile=None, **opts):
13 def convert(ui, src, dest=None, revmapfile=None, **opts):
14 """Convert a foreign SCM repository to a Mercurial one.
14 """Convert a foreign SCM repository to a Mercurial one.
15
15
16 Accepted source formats:
16 Accepted source formats:
17 - Mercurial
17 - Mercurial
18 - CVS
18 - CVS
19 - Darcs
19 - Darcs
20 - git
20 - git
21 - Subversion
21 - Subversion
22 - GNU Arch
22
23
23 Accepted destination formats:
24 Accepted destination formats:
24 - Mercurial
25 - Mercurial
25 - Subversion (history on branches is not preserved)
26 - Subversion (history on branches is not preserved)
26
27
27 If no revision is given, all revisions will be converted. Otherwise,
28 If no revision is given, all revisions will be converted. Otherwise,
28 convert will only import up to the named revision (given in a format
29 convert will only import up to the named revision (given in a format
29 understood by the source).
30 understood by the source).
30
31
31 If no destination directory name is specified, it defaults to the
32 If no destination directory name is specified, it defaults to the
32 basename of the source with '-hg' appended. If the destination
33 basename of the source with '-hg' appended. If the destination
33 repository doesn't exist, it will be created.
34 repository doesn't exist, it will be created.
34
35
35 If <MAPFILE> isn't given, it will be put in a default location
36 If <MAPFILE> isn't given, it will be put in a default location
36 (<dest>/.hg/shamap by default). The <MAPFILE> is a simple text
37 (<dest>/.hg/shamap by default). The <MAPFILE> is a simple text
37 file that maps each source commit ID to the destination ID for
38 file that maps each source commit ID to the destination ID for
38 that revision, like so:
39 that revision, like so:
39 <source ID> <destination ID>
40 <source ID> <destination ID>
40
41
41 If the file doesn't exist, it's automatically created. It's updated
42 If the file doesn't exist, it's automatically created. It's updated
42 on each commit copied, so convert-repo can be interrupted and can
43 on each commit copied, so convert-repo can be interrupted and can
43 be run repeatedly to copy new commits.
44 be run repeatedly to copy new commits.
44
45
45 The [username mapping] file is a simple text file that maps each source
46 The [username mapping] file is a simple text file that maps each source
46 commit author to a destination commit author. It is handy for source SCMs
47 commit author to a destination commit author. It is handy for source SCMs
47 that use unix logins to identify authors (eg: CVS). One line per author
48 that use unix logins to identify authors (eg: CVS). One line per author
48 mapping and the line format is:
49 mapping and the line format is:
49 srcauthor=whatever string you want
50 srcauthor=whatever string you want
50
51
51 The filemap is a file that allows filtering and remapping of files
52 The filemap is a file that allows filtering and remapping of files
52 and directories. Comment lines start with '#'. Each line can
53 and directories. Comment lines start with '#'. Each line can
53 contain one of the following directives:
54 contain one of the following directives:
54
55
55 include path/to/file
56 include path/to/file
56
57
57 exclude path/to/file
58 exclude path/to/file
58
59
59 rename from/file to/file
60 rename from/file to/file
60
61
61 The 'include' directive causes a file, or all files under a
62 The 'include' directive causes a file, or all files under a
62 directory, to be included in the destination repository, and the
63 directory, to be included in the destination repository, and the
63 exclusion of all other files and dirs not explicitely included.
64 exclusion of all other files and dirs not explicitely included.
64 The 'exclude' directive causes files or directories to be omitted.
65 The 'exclude' directive causes files or directories to be omitted.
65 The 'rename' directive renames a file or directory. To rename from a
66 The 'rename' directive renames a file or directory. To rename from a
66 subdirectory into the root of the repository, use '.' as the path to
67 subdirectory into the root of the repository, use '.' as the path to
67 rename to.
68 rename to.
68
69
69 Back end options:
70 Back end options:
70
71
71 --config convert.hg.clonebranches=False (boolean)
72 --config convert.hg.clonebranches=False (boolean)
72 hg target: XXX not documented
73 hg target: XXX not documented
73 --config convert.hg.saverev=True (boolean)
74 --config convert.hg.saverev=True (boolean)
74 hg source: allow target to preserve source revision ID
75 hg source: allow target to preserve source revision ID
75 --config convert.hg.tagsbranch=default (branch name)
76 --config convert.hg.tagsbranch=default (branch name)
76 hg target: XXX not documented
77 hg target: XXX not documented
77 --config convert.hg.usebranchnames=True (boolean)
78 --config convert.hg.usebranchnames=True (boolean)
78 hg target: preserve branch names
79 hg target: preserve branch names
79
80
80 --config convert.svn.branches=branches (directory name)
81 --config convert.svn.branches=branches (directory name)
81 svn source: specify the directory containing branches
82 svn source: specify the directory containing branches
82 --config convert.svn.tags=tags (directory name)
83 --config convert.svn.tags=tags (directory name)
83 svn source: specify the directory containing tags
84 svn source: specify the directory containing tags
84 --config convert.svn.trunk=trunk (directory name)
85 --config convert.svn.trunk=trunk (directory name)
85 svn source: specify the name of the trunk branch
86 svn source: specify the name of the trunk branch
86 """
87 """
87 return convcmd.convert(ui, src, dest, revmapfile, **opts)
88 return convcmd.convert(ui, src, dest, revmapfile, **opts)
88
89
89 def debugsvnlog(ui, **opts):
90 def debugsvnlog(ui, **opts):
90 return convcmd.debugsvnlog(ui, **opts)
91 return convcmd.debugsvnlog(ui, **opts)
91
92
92 commands.norepo += " convert debugsvnlog"
93 commands.norepo += " convert debugsvnlog"
93
94
94 cmdtable = {
95 cmdtable = {
95 "convert":
96 "convert":
96 (convert,
97 (convert,
97 [('A', 'authors', '', 'username mapping filename'),
98 [('A', 'authors', '', 'username mapping filename'),
98 ('d', 'dest-type', '', 'destination repository type'),
99 ('d', 'dest-type', '', 'destination repository type'),
99 ('', 'filemap', '', 'remap file names using contents of file'),
100 ('', 'filemap', '', 'remap file names using contents of file'),
100 ('r', 'rev', '', 'import up to target revision REV'),
101 ('r', 'rev', '', 'import up to target revision REV'),
101 ('s', 'source-type', '', 'source repository type'),
102 ('s', 'source-type', '', 'source repository type'),
102 ('', 'datesort', None, 'try to sort changesets by date')],
103 ('', 'datesort', None, 'try to sort changesets by date')],
103 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
104 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
104 "debugsvnlog":
105 "debugsvnlog":
105 (debugsvnlog,
106 (debugsvnlog,
106 [],
107 [],
107 'hg debugsvnlog'),
108 'hg debugsvnlog'),
108 }
109 }
@@ -1,343 +1,354 b''
1 # common code for the convert extension
1 # common code for the convert extension
2 import base64, errno
2 import base64, errno
3 import os
3 import os
4 import cPickle as pickle
4 import cPickle as pickle
5 from mercurial import util
5 from mercurial import util
6 from mercurial.i18n import _
6 from mercurial.i18n import _
7
7
8 def encodeargs(args):
8 def encodeargs(args):
9 def encodearg(s):
9 def encodearg(s):
10 lines = base64.encodestring(s)
10 lines = base64.encodestring(s)
11 lines = [l.splitlines()[0] for l in lines]
11 lines = [l.splitlines()[0] for l in lines]
12 return ''.join(lines)
12 return ''.join(lines)
13
13
14 s = pickle.dumps(args)
14 s = pickle.dumps(args)
15 return encodearg(s)
15 return encodearg(s)
16
16
17 def decodeargs(s):
17 def decodeargs(s):
18 s = base64.decodestring(s)
18 s = base64.decodestring(s)
19 return pickle.loads(s)
19 return pickle.loads(s)
20
20
21 def checktool(exe, name=None):
21 def checktool(exe, name=None):
22 name = name or exe
22 name = name or exe
23 if not util.find_exe(exe):
23 if not util.find_exe(exe):
24 raise util.Abort('cannot find required "%s" tool' % name)
24 raise util.Abort('cannot find required "%s" tool' % name)
25
25
26 class NoRepo(Exception): pass
26 class NoRepo(Exception): pass
27
27
28 SKIPREV = 'SKIP'
28 SKIPREV = 'SKIP'
29
29
30 class commit(object):
30 class commit(object):
31 def __init__(self, author, date, desc, parents, branch=None, rev=None,
31 def __init__(self, author, date, desc, parents, branch=None, rev=None,
32 extra={}):
32 extra={}):
33 self.author = author or 'unknown'
33 self.author = author or 'unknown'
34 self.date = date or '0 0'
34 self.date = date or '0 0'
35 self.desc = desc
35 self.desc = desc
36 self.parents = parents
36 self.parents = parents
37 self.branch = branch
37 self.branch = branch
38 self.rev = rev
38 self.rev = rev
39 self.extra = extra
39 self.extra = extra
40
40
41 class converter_source(object):
41 class converter_source(object):
42 """Conversion source interface"""
42 """Conversion source interface"""
43
43
44 def __init__(self, ui, path=None, rev=None):
44 def __init__(self, ui, path=None, rev=None):
45 """Initialize conversion source (or raise NoRepo("message")
45 """Initialize conversion source (or raise NoRepo("message")
46 exception if path is not a valid repository)"""
46 exception if path is not a valid repository)"""
47 self.ui = ui
47 self.ui = ui
48 self.path = path
48 self.path = path
49 self.rev = rev
49 self.rev = rev
50
50
51 self.encoding = 'utf-8'
51 self.encoding = 'utf-8'
52
52
53 def before(self):
53 def before(self):
54 pass
54 pass
55
55
56 def after(self):
56 def after(self):
57 pass
57 pass
58
58
59 def setrevmap(self, revmap):
59 def setrevmap(self, revmap):
60 """set the map of already-converted revisions"""
60 """set the map of already-converted revisions"""
61 pass
61 pass
62
62
63 def getheads(self):
63 def getheads(self):
64 """Return a list of this repository's heads"""
64 """Return a list of this repository's heads"""
65 raise NotImplementedError()
65 raise NotImplementedError()
66
66
67 def getfile(self, name, rev):
67 def getfile(self, name, rev):
68 """Return file contents as a string"""
68 """Return file contents as a string"""
69 raise NotImplementedError()
69 raise NotImplementedError()
70
70
71 def getmode(self, name, rev):
71 def getmode(self, name, rev):
72 """Return file mode, eg. '', 'x', or 'l'"""
72 """Return file mode, eg. '', 'x', or 'l'"""
73 raise NotImplementedError()
73 raise NotImplementedError()
74
74
75 def getchanges(self, version):
75 def getchanges(self, version):
76 """Returns a tuple of (files, copies)
76 """Returns a tuple of (files, copies)
77 Files is a sorted list of (filename, id) tuples for all files changed
77 Files is a sorted list of (filename, id) tuples for all files changed
78 in version, where id is the source revision id of the file.
78 in version, where id is the source revision id of the file.
79
79
80 copies is a dictionary of dest: source
80 copies is a dictionary of dest: source
81 """
81 """
82 raise NotImplementedError()
82 raise NotImplementedError()
83
83
84 def getcommit(self, version):
84 def getcommit(self, version):
85 """Return the commit object for version"""
85 """Return the commit object for version"""
86 raise NotImplementedError()
86 raise NotImplementedError()
87
87
88 def gettags(self):
88 def gettags(self):
89 """Return the tags as a dictionary of name: revision"""
89 """Return the tags as a dictionary of name: revision"""
90 raise NotImplementedError()
90 raise NotImplementedError()
91
91
92 def recode(self, s, encoding=None):
92 def recode(self, s, encoding=None):
93 if not encoding:
93 if not encoding:
94 encoding = self.encoding or 'utf-8'
94 encoding = self.encoding or 'utf-8'
95
95
96 if isinstance(s, unicode):
96 if isinstance(s, unicode):
97 return s.encode("utf-8")
97 return s.encode("utf-8")
98 try:
98 try:
99 return s.decode(encoding).encode("utf-8")
99 return s.decode(encoding).encode("utf-8")
100 except:
100 except:
101 try:
101 try:
102 return s.decode("latin-1").encode("utf-8")
102 return s.decode("latin-1").encode("utf-8")
103 except:
103 except:
104 return s.decode(encoding, "replace").encode("utf-8")
104 return s.decode(encoding, "replace").encode("utf-8")
105
105
106 def getchangedfiles(self, rev, i):
106 def getchangedfiles(self, rev, i):
107 """Return the files changed by rev compared to parent[i].
107 """Return the files changed by rev compared to parent[i].
108
108
109 i is an index selecting one of the parents of rev. The return
109 i is an index selecting one of the parents of rev. The return
110 value should be the list of files that are different in rev and
110 value should be the list of files that are different in rev and
111 this parent.
111 this parent.
112
112
113 If rev has no parents, i is None.
113 If rev has no parents, i is None.
114
114
115 This function is only needed to support --filemap
115 This function is only needed to support --filemap
116 """
116 """
117 raise NotImplementedError()
117 raise NotImplementedError()
118
118
119 def converted(self, rev, sinkrev):
119 def converted(self, rev, sinkrev):
120 '''Notify the source that a revision has been converted.'''
120 '''Notify the source that a revision has been converted.'''
121 pass
121 pass
122
122
123
123
124 class converter_sink(object):
124 class converter_sink(object):
125 """Conversion sink (target) interface"""
125 """Conversion sink (target) interface"""
126
126
127 def __init__(self, ui, path):
127 def __init__(self, ui, path):
128 """Initialize conversion sink (or raise NoRepo("message")
128 """Initialize conversion sink (or raise NoRepo("message")
129 exception if path is not a valid repository)
129 exception if path is not a valid repository)
130
130
131 created is a list of paths to remove if a fatal error occurs
131 created is a list of paths to remove if a fatal error occurs
132 later"""
132 later"""
133 self.ui = ui
133 self.ui = ui
134 self.path = path
134 self.path = path
135 self.created = []
135 self.created = []
136
136
137 def getheads(self):
137 def getheads(self):
138 """Return a list of this repository's heads"""
138 """Return a list of this repository's heads"""
139 raise NotImplementedError()
139 raise NotImplementedError()
140
140
141 def revmapfile(self):
141 def revmapfile(self):
142 """Path to a file that will contain lines
142 """Path to a file that will contain lines
143 source_rev_id sink_rev_id
143 source_rev_id sink_rev_id
144 mapping equivalent revision identifiers for each system."""
144 mapping equivalent revision identifiers for each system."""
145 raise NotImplementedError()
145 raise NotImplementedError()
146
146
147 def authorfile(self):
147 def authorfile(self):
148 """Path to a file that will contain lines
148 """Path to a file that will contain lines
149 srcauthor=dstauthor
149 srcauthor=dstauthor
150 mapping equivalent authors identifiers for each system."""
150 mapping equivalent authors identifiers for each system."""
151 return None
151 return None
152
152
153 def putfile(self, f, e, data):
153 def putfile(self, f, e, data):
154 """Put file for next putcommit().
154 """Put file for next putcommit().
155 f: path to file
155 f: path to file
156 e: '', 'x', or 'l' (regular file, executable, or symlink)
156 e: '', 'x', or 'l' (regular file, executable, or symlink)
157 data: file contents"""
157 data: file contents"""
158 raise NotImplementedError()
158 raise NotImplementedError()
159
159
160 def delfile(self, f):
160 def delfile(self, f):
161 """Delete file for next putcommit().
161 """Delete file for next putcommit().
162 f: path to file"""
162 f: path to file"""
163 raise NotImplementedError()
163 raise NotImplementedError()
164
164
165 def putcommit(self, files, parents, commit):
165 def putcommit(self, files, parents, commit):
166 """Create a revision with all changed files listed in 'files'
166 """Create a revision with all changed files listed in 'files'
167 and having listed parents. 'commit' is a commit object containing
167 and having listed parents. 'commit' is a commit object containing
168 at a minimum the author, date, and message for this changeset.
168 at a minimum the author, date, and message for this changeset.
169 Called after putfile() and delfile() calls. Note that the sink
169 Called after putfile() and delfile() calls. Note that the sink
170 repository is not told to update itself to a particular revision
170 repository is not told to update itself to a particular revision
171 (or even what that revision would be) before it receives the
171 (or even what that revision would be) before it receives the
172 file data."""
172 file data."""
173 raise NotImplementedError()
173 raise NotImplementedError()
174
174
175 def puttags(self, tags):
175 def puttags(self, tags):
176 """Put tags into sink.
176 """Put tags into sink.
177 tags: {tagname: sink_rev_id, ...}"""
177 tags: {tagname: sink_rev_id, ...}"""
178 raise NotImplementedError()
178 raise NotImplementedError()
179
179
180 def setbranch(self, branch, pbranches):
180 def setbranch(self, branch, pbranches):
181 """Set the current branch name. Called before the first putfile
181 """Set the current branch name. Called before the first putfile
182 on the branch.
182 on the branch.
183 branch: branch name for subsequent commits
183 branch: branch name for subsequent commits
184 pbranches: (converted parent revision, parent branch) tuples"""
184 pbranches: (converted parent revision, parent branch) tuples"""
185 pass
185 pass
186
186
187 def setfilemapmode(self, active):
187 def setfilemapmode(self, active):
188 """Tell the destination that we're using a filemap
188 """Tell the destination that we're using a filemap
189
189
190 Some converter_sources (svn in particular) can claim that a file
190 Some converter_sources (svn in particular) can claim that a file
191 was changed in a revision, even if there was no change. This method
191 was changed in a revision, even if there was no change. This method
192 tells the destination that we're using a filemap and that it should
192 tells the destination that we're using a filemap and that it should
193 filter empty revisions.
193 filter empty revisions.
194 """
194 """
195 pass
195 pass
196
196
197 def before(self):
197 def before(self):
198 pass
198 pass
199
199
200 def after(self):
200 def after(self):
201 pass
201 pass
202
202
203
203
204 class commandline(object):
204 class commandline(object):
205 def __init__(self, ui, command):
205 def __init__(self, ui, command):
206 self.ui = ui
206 self.ui = ui
207 self.command = command
207 self.command = command
208
208
209 def prerun(self):
209 def prerun(self):
210 pass
210 pass
211
211
212 def postrun(self):
212 def postrun(self):
213 pass
213 pass
214
214
215 def _cmdline(self, cmd, *args, **kwargs):
215 def _cmdline(self, cmd, *args, **kwargs):
216 cmdline = [self.command, cmd] + list(args)
216 cmdline = [self.command, cmd] + list(args)
217 for k, v in kwargs.iteritems():
217 for k, v in kwargs.iteritems():
218 if len(k) == 1:
218 if len(k) == 1:
219 cmdline.append('-' + k)
219 cmdline.append('-' + k)
220 else:
220 else:
221 cmdline.append('--' + k.replace('_', '-'))
221 cmdline.append('--' + k.replace('_', '-'))
222 try:
222 try:
223 if len(k) == 1:
223 if len(k) == 1:
224 cmdline.append('' + v)
224 cmdline.append('' + v)
225 else:
225 else:
226 cmdline[-1] += '=' + v
226 cmdline[-1] += '=' + v
227 except TypeError:
227 except TypeError:
228 pass
228 pass
229 cmdline = [util.shellquote(arg) for arg in cmdline]
229 cmdline = [util.shellquote(arg) for arg in cmdline]
230 cmdline += ['<', util.nulldev]
230 cmdline += ['2>', util.nulldev, '<', util.nulldev]
231 cmdline = ' '.join(cmdline)
231 cmdline = ' '.join(cmdline)
232 self.ui.debug(cmdline, '\n')
232 self.ui.debug(cmdline, '\n')
233 return cmdline
233 return cmdline
234
234
235 def _run(self, cmd, *args, **kwargs):
235 def _run(self, cmd, *args, **kwargs):
236 cmdline = self._cmdline(cmd, *args, **kwargs)
236 cmdline = self._cmdline(cmd, *args, **kwargs)
237 self.prerun()
237 self.prerun()
238 try:
238 try:
239 return util.popen(cmdline)
239 return util.popen(cmdline)
240 finally:
240 finally:
241 self.postrun()
241 self.postrun()
242
242
243 def run(self, cmd, *args, **kwargs):
243 def run(self, cmd, *args, **kwargs):
244 fp = self._run(cmd, *args, **kwargs)
244 fp = self._run(cmd, *args, **kwargs)
245 output = fp.read()
245 output = fp.read()
246 self.ui.debug(output)
246 self.ui.debug(output)
247 return output, fp.close()
247 return output, fp.close()
248
248
249 def runlines(self, cmd, *args, **kwargs):
250 fp = self._run(cmd, *args, **kwargs)
251 output = fp.readlines()
252 self.ui.debug(output)
253 return output, fp.close()
254
249 def checkexit(self, status, output=''):
255 def checkexit(self, status, output=''):
250 if status:
256 if status:
251 if output:
257 if output:
252 self.ui.warn(_('%s error:\n') % self.command)
258 self.ui.warn(_('%s error:\n') % self.command)
253 self.ui.warn(output)
259 self.ui.warn(output)
254 msg = util.explain_exit(status)[0]
260 msg = util.explain_exit(status)[0]
255 raise util.Abort(_('%s %s') % (self.command, msg))
261 raise util.Abort(_('%s %s') % (self.command, msg))
256
262
257 def run0(self, cmd, *args, **kwargs):
263 def run0(self, cmd, *args, **kwargs):
258 output, status = self.run(cmd, *args, **kwargs)
264 output, status = self.run(cmd, *args, **kwargs)
259 self.checkexit(status, output)
265 self.checkexit(status, output)
260 return output
266 return output
261
267
268 def runlines0(self, cmd, *args, **kwargs):
269 output, status = self.runlines(cmd, *args, **kwargs)
270 self.checkexit(status, output)
271 return output
272
262 def getargmax(self):
273 def getargmax(self):
263 if '_argmax' in self.__dict__:
274 if '_argmax' in self.__dict__:
264 return self._argmax
275 return self._argmax
265
276
266 # POSIX requires at least 4096 bytes for ARG_MAX
277 # POSIX requires at least 4096 bytes for ARG_MAX
267 self._argmax = 4096
278 self._argmax = 4096
268 try:
279 try:
269 self._argmax = os.sysconf("SC_ARG_MAX")
280 self._argmax = os.sysconf("SC_ARG_MAX")
270 except:
281 except:
271 pass
282 pass
272
283
273 # Windows shells impose their own limits on command line length,
284 # Windows shells impose their own limits on command line length,
274 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
285 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
275 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
286 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
276 # details about cmd.exe limitations.
287 # details about cmd.exe limitations.
277
288
278 # Since ARG_MAX is for command line _and_ environment, lower our limit
289 # Since ARG_MAX is for command line _and_ environment, lower our limit
279 # (and make happy Windows shells while doing this).
290 # (and make happy Windows shells while doing this).
280
291
281 self._argmax = self._argmax/2 - 1
292 self._argmax = self._argmax/2 - 1
282 return self._argmax
293 return self._argmax
283
294
284 def limit_arglist(self, arglist, cmd, *args, **kwargs):
295 def limit_arglist(self, arglist, cmd, *args, **kwargs):
285 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
296 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
286 bytes = 0
297 bytes = 0
287 fl = []
298 fl = []
288 for fn in arglist:
299 for fn in arglist:
289 b = len(fn) + 3
300 b = len(fn) + 3
290 if bytes + b < limit or len(fl) == 0:
301 if bytes + b < limit or len(fl) == 0:
291 fl.append(fn)
302 fl.append(fn)
292 bytes += b
303 bytes += b
293 else:
304 else:
294 yield fl
305 yield fl
295 fl = [fn]
306 fl = [fn]
296 bytes = b
307 bytes = b
297 if fl:
308 if fl:
298 yield fl
309 yield fl
299
310
300 def xargs(self, arglist, cmd, *args, **kwargs):
311 def xargs(self, arglist, cmd, *args, **kwargs):
301 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
312 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
302 self.run0(cmd, *(list(args) + l), **kwargs)
313 self.run0(cmd, *(list(args) + l), **kwargs)
303
314
304 class mapfile(dict):
315 class mapfile(dict):
305 def __init__(self, ui, path):
316 def __init__(self, ui, path):
306 super(mapfile, self).__init__()
317 super(mapfile, self).__init__()
307 self.ui = ui
318 self.ui = ui
308 self.path = path
319 self.path = path
309 self.fp = None
320 self.fp = None
310 self.order = []
321 self.order = []
311 self._read()
322 self._read()
312
323
313 def _read(self):
324 def _read(self):
314 if self.path is None:
325 if self.path is None:
315 return
326 return
316 try:
327 try:
317 fp = open(self.path, 'r')
328 fp = open(self.path, 'r')
318 except IOError, err:
329 except IOError, err:
319 if err.errno != errno.ENOENT:
330 if err.errno != errno.ENOENT:
320 raise
331 raise
321 return
332 return
322 for line in fp:
333 for line in fp:
323 key, value = line[:-1].split(' ', 1)
334 key, value = line[:-1].split(' ', 1)
324 if key not in self:
335 if key not in self:
325 self.order.append(key)
336 self.order.append(key)
326 super(mapfile, self).__setitem__(key, value)
337 super(mapfile, self).__setitem__(key, value)
327 fp.close()
338 fp.close()
328
339
329 def __setitem__(self, key, value):
340 def __setitem__(self, key, value):
330 if self.fp is None:
341 if self.fp is None:
331 try:
342 try:
332 self.fp = open(self.path, 'a')
343 self.fp = open(self.path, 'a')
333 except IOError, err:
344 except IOError, err:
334 raise util.Abort(_('could not open map file %r: %s') %
345 raise util.Abort(_('could not open map file %r: %s') %
335 (self.path, err.strerror))
346 (self.path, err.strerror))
336 self.fp.write('%s %s\n' % (key, value))
347 self.fp.write('%s %s\n' % (key, value))
337 self.fp.flush()
348 self.fp.flush()
338 super(mapfile, self).__setitem__(key, value)
349 super(mapfile, self).__setitem__(key, value)
339
350
340 def close(self):
351 def close(self):
341 if self.fp:
352 if self.fp:
342 self.fp.close()
353 self.fp.close()
343 self.fp = None
354 self.fp = None
@@ -1,328 +1,330 b''
1 # convcmd - convert extension commands definition
1 # convcmd - convert extension commands definition
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, SKIPREV, converter_source, converter_sink, mapfile
8 from common import NoRepo, SKIPREV, converter_source, converter_sink, mapfile
9 from cvs import convert_cvs
9 from cvs import convert_cvs
10 from darcs import darcs_source
10 from darcs import darcs_source
11 from git import convert_git
11 from git import convert_git
12 from hg import mercurial_source, mercurial_sink
12 from hg import mercurial_source, mercurial_sink
13 from subversion import debugsvnlog, svn_source, svn_sink
13 from subversion import debugsvnlog, svn_source, svn_sink
14 from gnuarch import gnuarch_source
14 import filemap
15 import filemap
15
16
16 import os, shutil
17 import os, shutil
17 from mercurial import hg, util
18 from mercurial import hg, util
18 from mercurial.i18n import _
19 from mercurial.i18n import _
19
20
20 source_converters = [
21 source_converters = [
21 ('cvs', convert_cvs),
22 ('cvs', convert_cvs),
22 ('git', convert_git),
23 ('git', convert_git),
23 ('svn', svn_source),
24 ('svn', svn_source),
24 ('hg', mercurial_source),
25 ('hg', mercurial_source),
25 ('darcs', darcs_source),
26 ('darcs', darcs_source),
27 ('gnuarch', gnuarch_source),
26 ]
28 ]
27
29
28 sink_converters = [
30 sink_converters = [
29 ('hg', mercurial_sink),
31 ('hg', mercurial_sink),
30 ('svn', svn_sink),
32 ('svn', svn_sink),
31 ]
33 ]
32
34
33 def convertsource(ui, path, type, rev):
35 def convertsource(ui, path, type, rev):
34 exceptions = []
36 exceptions = []
35 for name, source in source_converters:
37 for name, source in source_converters:
36 try:
38 try:
37 if not type or name == type:
39 if not type or name == type:
38 return source(ui, path, rev)
40 return source(ui, path, rev)
39 except NoRepo, inst:
41 except NoRepo, inst:
40 exceptions.append(inst)
42 exceptions.append(inst)
41 if not ui.quiet:
43 if not ui.quiet:
42 for inst in exceptions:
44 for inst in exceptions:
43 ui.write(_("%s\n") % inst)
45 ui.write(_("%s\n") % inst)
44 raise util.Abort('%s: unknown repository type' % path)
46 raise util.Abort('%s: unknown repository type' % path)
45
47
46 def convertsink(ui, path, type):
48 def convertsink(ui, path, type):
47 for name, sink in sink_converters:
49 for name, sink in sink_converters:
48 try:
50 try:
49 if not type or name == type:
51 if not type or name == type:
50 return sink(ui, path)
52 return sink(ui, path)
51 except NoRepo, inst:
53 except NoRepo, inst:
52 ui.note(_("convert: %s\n") % inst)
54 ui.note(_("convert: %s\n") % inst)
53 raise util.Abort('%s: unknown repository type' % path)
55 raise util.Abort('%s: unknown repository type' % path)
54
56
55 class converter(object):
57 class converter(object):
56 def __init__(self, ui, source, dest, revmapfile, opts):
58 def __init__(self, ui, source, dest, revmapfile, opts):
57
59
58 self.source = source
60 self.source = source
59 self.dest = dest
61 self.dest = dest
60 self.ui = ui
62 self.ui = ui
61 self.opts = opts
63 self.opts = opts
62 self.commitcache = {}
64 self.commitcache = {}
63 self.authors = {}
65 self.authors = {}
64 self.authorfile = None
66 self.authorfile = None
65
67
66 self.map = mapfile(ui, revmapfile)
68 self.map = mapfile(ui, revmapfile)
67
69
68 # Read first the dst author map if any
70 # Read first the dst author map if any
69 authorfile = self.dest.authorfile()
71 authorfile = self.dest.authorfile()
70 if authorfile and os.path.exists(authorfile):
72 if authorfile and os.path.exists(authorfile):
71 self.readauthormap(authorfile)
73 self.readauthormap(authorfile)
72 # Extend/Override with new author map if necessary
74 # Extend/Override with new author map if necessary
73 if opts.get('authors'):
75 if opts.get('authors'):
74 self.readauthormap(opts.get('authors'))
76 self.readauthormap(opts.get('authors'))
75 self.authorfile = self.dest.authorfile()
77 self.authorfile = self.dest.authorfile()
76
78
77 self.splicemap = mapfile(ui, ui.config('convert', 'splicemap'))
79 self.splicemap = mapfile(ui, ui.config('convert', 'splicemap'))
78
80
79 def walktree(self, heads):
81 def walktree(self, heads):
80 '''Return a mapping that identifies the uncommitted parents of every
82 '''Return a mapping that identifies the uncommitted parents of every
81 uncommitted changeset.'''
83 uncommitted changeset.'''
82 visit = heads
84 visit = heads
83 known = {}
85 known = {}
84 parents = {}
86 parents = {}
85 while visit:
87 while visit:
86 n = visit.pop(0)
88 n = visit.pop(0)
87 if n in known or n in self.map: continue
89 if n in known or n in self.map: continue
88 known[n] = 1
90 known[n] = 1
89 commit = self.cachecommit(n)
91 commit = self.cachecommit(n)
90 parents[n] = []
92 parents[n] = []
91 for p in commit.parents:
93 for p in commit.parents:
92 parents[n].append(p)
94 parents[n].append(p)
93 visit.append(p)
95 visit.append(p)
94
96
95 return parents
97 return parents
96
98
97 def toposort(self, parents):
99 def toposort(self, parents):
98 '''Return an ordering such that every uncommitted changeset is
100 '''Return an ordering such that every uncommitted changeset is
99 preceeded by all its uncommitted ancestors.'''
101 preceeded by all its uncommitted ancestors.'''
100 visit = parents.keys()
102 visit = parents.keys()
101 seen = {}
103 seen = {}
102 children = {}
104 children = {}
103
105
104 while visit:
106 while visit:
105 n = visit.pop(0)
107 n = visit.pop(0)
106 if n in seen: continue
108 if n in seen: continue
107 seen[n] = 1
109 seen[n] = 1
108 # Ensure that nodes without parents are present in the 'children'
110 # Ensure that nodes without parents are present in the 'children'
109 # mapping.
111 # mapping.
110 children.setdefault(n, [])
112 children.setdefault(n, [])
111 for p in parents[n]:
113 for p in parents[n]:
112 if not p in self.map:
114 if not p in self.map:
113 visit.append(p)
115 visit.append(p)
114 children.setdefault(p, []).append(n)
116 children.setdefault(p, []).append(n)
115
117
116 s = []
118 s = []
117 removed = {}
119 removed = {}
118 visit = children.keys()
120 visit = children.keys()
119 while visit:
121 while visit:
120 n = visit.pop(0)
122 n = visit.pop(0)
121 if n in removed: continue
123 if n in removed: continue
122 dep = 0
124 dep = 0
123 if n in parents:
125 if n in parents:
124 for p in parents[n]:
126 for p in parents[n]:
125 if p in self.map: continue
127 if p in self.map: continue
126 if p not in removed:
128 if p not in removed:
127 # we're still dependent
129 # we're still dependent
128 visit.append(n)
130 visit.append(n)
129 dep = 1
131 dep = 1
130 break
132 break
131
133
132 if not dep:
134 if not dep:
133 # all n's parents are in the list
135 # all n's parents are in the list
134 removed[n] = 1
136 removed[n] = 1
135 if n not in self.map:
137 if n not in self.map:
136 s.append(n)
138 s.append(n)
137 if n in children:
139 if n in children:
138 for c in children[n]:
140 for c in children[n]:
139 visit.insert(0, c)
141 visit.insert(0, c)
140
142
141 if self.opts.get('datesort'):
143 if self.opts.get('datesort'):
142 depth = {}
144 depth = {}
143 for n in s:
145 for n in s:
144 depth[n] = 0
146 depth[n] = 0
145 pl = [p for p in self.commitcache[n].parents
147 pl = [p for p in self.commitcache[n].parents
146 if p not in self.map]
148 if p not in self.map]
147 if pl:
149 if pl:
148 depth[n] = max([depth[p] for p in pl]) + 1
150 depth[n] = max([depth[p] for p in pl]) + 1
149
151
150 s = [(depth[n], util.parsedate(self.commitcache[n].date), n)
152 s = [(depth[n], util.parsedate(self.commitcache[n].date), n)
151 for n in s]
153 for n in s]
152 s.sort()
154 s.sort()
153 s = [e[2] for e in s]
155 s = [e[2] for e in s]
154
156
155 return s
157 return s
156
158
157 def writeauthormap(self):
159 def writeauthormap(self):
158 authorfile = self.authorfile
160 authorfile = self.authorfile
159 if authorfile:
161 if authorfile:
160 self.ui.status('Writing author map file %s\n' % authorfile)
162 self.ui.status('Writing author map file %s\n' % authorfile)
161 ofile = open(authorfile, 'w+')
163 ofile = open(authorfile, 'w+')
162 for author in self.authors:
164 for author in self.authors:
163 ofile.write("%s=%s\n" % (author, self.authors[author]))
165 ofile.write("%s=%s\n" % (author, self.authors[author]))
164 ofile.close()
166 ofile.close()
165
167
166 def readauthormap(self, authorfile):
168 def readauthormap(self, authorfile):
167 afile = open(authorfile, 'r')
169 afile = open(authorfile, 'r')
168 for line in afile:
170 for line in afile:
169 try:
171 try:
170 srcauthor = line.split('=')[0].strip()
172 srcauthor = line.split('=')[0].strip()
171 dstauthor = line.split('=')[1].strip()
173 dstauthor = line.split('=')[1].strip()
172 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
174 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
173 self.ui.status(
175 self.ui.status(
174 'Overriding mapping for author %s, was %s, will be %s\n'
176 'Overriding mapping for author %s, was %s, will be %s\n'
175 % (srcauthor, self.authors[srcauthor], dstauthor))
177 % (srcauthor, self.authors[srcauthor], dstauthor))
176 else:
178 else:
177 self.ui.debug('Mapping author %s to %s\n'
179 self.ui.debug('Mapping author %s to %s\n'
178 % (srcauthor, dstauthor))
180 % (srcauthor, dstauthor))
179 self.authors[srcauthor] = dstauthor
181 self.authors[srcauthor] = dstauthor
180 except IndexError:
182 except IndexError:
181 self.ui.warn(
183 self.ui.warn(
182 'Ignoring bad line in author file map %s: %s\n'
184 'Ignoring bad line in author file map %s: %s\n'
183 % (authorfile, line))
185 % (authorfile, line))
184 afile.close()
186 afile.close()
185
187
186 def cachecommit(self, rev):
188 def cachecommit(self, rev):
187 commit = self.source.getcommit(rev)
189 commit = self.source.getcommit(rev)
188 commit.author = self.authors.get(commit.author, commit.author)
190 commit.author = self.authors.get(commit.author, commit.author)
189 self.commitcache[rev] = commit
191 self.commitcache[rev] = commit
190 return commit
192 return commit
191
193
192 def copy(self, rev):
194 def copy(self, rev):
193 commit = self.commitcache[rev]
195 commit = self.commitcache[rev]
194 do_copies = hasattr(self.dest, 'copyfile')
196 do_copies = hasattr(self.dest, 'copyfile')
195 filenames = []
197 filenames = []
196
198
197 changes = self.source.getchanges(rev)
199 changes = self.source.getchanges(rev)
198 if isinstance(changes, basestring):
200 if isinstance(changes, basestring):
199 if changes == SKIPREV:
201 if changes == SKIPREV:
200 dest = SKIPREV
202 dest = SKIPREV
201 else:
203 else:
202 dest = self.map[changes]
204 dest = self.map[changes]
203 self.map[rev] = dest
205 self.map[rev] = dest
204 return
206 return
205 files, copies = changes
207 files, copies = changes
206 pbranches = []
208 pbranches = []
207 if commit.parents:
209 if commit.parents:
208 for prev in commit.parents:
210 for prev in commit.parents:
209 if prev not in self.commitcache:
211 if prev not in self.commitcache:
210 self.cachecommit(prev)
212 self.cachecommit(prev)
211 pbranches.append((self.map[prev],
213 pbranches.append((self.map[prev],
212 self.commitcache[prev].branch))
214 self.commitcache[prev].branch))
213 self.dest.setbranch(commit.branch, pbranches)
215 self.dest.setbranch(commit.branch, pbranches)
214 for f, v in files:
216 for f, v in files:
215 filenames.append(f)
217 filenames.append(f)
216 try:
218 try:
217 data = self.source.getfile(f, v)
219 data = self.source.getfile(f, v)
218 except IOError, inst:
220 except IOError, inst:
219 self.dest.delfile(f)
221 self.dest.delfile(f)
220 else:
222 else:
221 e = self.source.getmode(f, v)
223 e = self.source.getmode(f, v)
222 self.dest.putfile(f, e, data)
224 self.dest.putfile(f, e, data)
223 if do_copies:
225 if do_copies:
224 if f in copies:
226 if f in copies:
225 copyf = copies[f]
227 copyf = copies[f]
226 # Merely marks that a copy happened.
228 # Merely marks that a copy happened.
227 self.dest.copyfile(copyf, f)
229 self.dest.copyfile(copyf, f)
228
230
229 try:
231 try:
230 parents = [self.splicemap[rev]]
232 parents = [self.splicemap[rev]]
231 self.ui.debug('spliced in %s as parents of %s\n' %
233 self.ui.debug('spliced in %s as parents of %s\n' %
232 (parents, rev))
234 (parents, rev))
233 except KeyError:
235 except KeyError:
234 parents = [b[0] for b in pbranches]
236 parents = [b[0] for b in pbranches]
235 newnode = self.dest.putcommit(filenames, parents, commit)
237 newnode = self.dest.putcommit(filenames, parents, commit)
236 self.source.converted(rev, newnode)
238 self.source.converted(rev, newnode)
237 self.map[rev] = newnode
239 self.map[rev] = newnode
238
240
239 def convert(self):
241 def convert(self):
240
242
241 def recode(s):
243 def recode(s):
242 return s.decode('utf-8').encode(orig_encoding, 'replace')
244 return s.decode('utf-8').encode(orig_encoding, 'replace')
243
245
244 try:
246 try:
245 self.source.before()
247 self.source.before()
246 self.dest.before()
248 self.dest.before()
247 self.source.setrevmap(self.map)
249 self.source.setrevmap(self.map)
248 self.ui.status("scanning source...\n")
250 self.ui.status("scanning source...\n")
249 heads = self.source.getheads()
251 heads = self.source.getheads()
250 parents = self.walktree(heads)
252 parents = self.walktree(heads)
251 self.ui.status("sorting...\n")
253 self.ui.status("sorting...\n")
252 t = self.toposort(parents)
254 t = self.toposort(parents)
253 num = len(t)
255 num = len(t)
254 c = None
256 c = None
255
257
256 self.ui.status("converting...\n")
258 self.ui.status("converting...\n")
257 for c in t:
259 for c in t:
258 num -= 1
260 num -= 1
259 desc = self.commitcache[c].desc
261 desc = self.commitcache[c].desc
260 if "\n" in desc:
262 if "\n" in desc:
261 desc = desc.splitlines()[0]
263 desc = desc.splitlines()[0]
262 # convert log message to local encoding without using
264 # convert log message to local encoding without using
263 # tolocal() because util._encoding conver() use it as
265 # tolocal() because util._encoding conver() use it as
264 # 'utf-8'
266 # 'utf-8'
265 self.ui.status("%d %s\n" % (num, recode(desc)))
267 self.ui.status("%d %s\n" % (num, recode(desc)))
266 self.ui.note(_("source: %s\n" % recode(c)))
268 self.ui.note(_("source: %s\n" % recode(c)))
267 self.copy(c)
269 self.copy(c)
268
270
269 tags = self.source.gettags()
271 tags = self.source.gettags()
270 ctags = {}
272 ctags = {}
271 for k in tags:
273 for k in tags:
272 v = tags[k]
274 v = tags[k]
273 if self.map.get(v, SKIPREV) != SKIPREV:
275 if self.map.get(v, SKIPREV) != SKIPREV:
274 ctags[k] = self.map[v]
276 ctags[k] = self.map[v]
275
277
276 if c and ctags:
278 if c and ctags:
277 nrev = self.dest.puttags(ctags)
279 nrev = self.dest.puttags(ctags)
278 # write another hash correspondence to override the previous
280 # write another hash correspondence to override the previous
279 # one so we don't end up with extra tag heads
281 # one so we don't end up with extra tag heads
280 if nrev:
282 if nrev:
281 self.map[c] = nrev
283 self.map[c] = nrev
282
284
283 self.writeauthormap()
285 self.writeauthormap()
284 finally:
286 finally:
285 self.cleanup()
287 self.cleanup()
286
288
287 def cleanup(self):
289 def cleanup(self):
288 try:
290 try:
289 self.dest.after()
291 self.dest.after()
290 finally:
292 finally:
291 self.source.after()
293 self.source.after()
292 self.map.close()
294 self.map.close()
293
295
294 orig_encoding = 'ascii'
296 orig_encoding = 'ascii'
295
297
296 def convert(ui, src, dest=None, revmapfile=None, **opts):
298 def convert(ui, src, dest=None, revmapfile=None, **opts):
297 global orig_encoding
299 global orig_encoding
298 orig_encoding = util._encoding
300 orig_encoding = util._encoding
299 util._encoding = 'UTF-8'
301 util._encoding = 'UTF-8'
300
302
301 if not dest:
303 if not dest:
302 dest = hg.defaultdest(src) + "-hg"
304 dest = hg.defaultdest(src) + "-hg"
303 ui.status("assuming destination %s\n" % dest)
305 ui.status("assuming destination %s\n" % dest)
304
306
305 destc = convertsink(ui, dest, opts.get('dest_type'))
307 destc = convertsink(ui, dest, opts.get('dest_type'))
306
308
307 try:
309 try:
308 srcc = convertsource(ui, src, opts.get('source_type'),
310 srcc = convertsource(ui, src, opts.get('source_type'),
309 opts.get('rev'))
311 opts.get('rev'))
310 except Exception:
312 except Exception:
311 for path in destc.created:
313 for path in destc.created:
312 shutil.rmtree(path, True)
314 shutil.rmtree(path, True)
313 raise
315 raise
314
316
315 fmap = opts.get('filemap')
317 fmap = opts.get('filemap')
316 if fmap:
318 if fmap:
317 srcc = filemap.filemap_source(ui, srcc, fmap)
319 srcc = filemap.filemap_source(ui, srcc, fmap)
318 destc.setfilemapmode(True)
320 destc.setfilemapmode(True)
319
321
320 if not revmapfile:
322 if not revmapfile:
321 try:
323 try:
322 revmapfile = destc.revmapfile()
324 revmapfile = destc.revmapfile()
323 except:
325 except:
324 revmapfile = os.path.join(destc, "map")
326 revmapfile = os.path.join(destc, "map")
325
327
326 c = converter(ui, srcc, destc, revmapfile, opts)
328 c = converter(ui, srcc, destc, revmapfile, opts)
327 c.convert()
329 c.convert()
328
330
General Comments 0
You need to be logged in to leave comments. Login now