##// END OF EJS Templates
initial version of monotone source for convert extension
Mikkel Fahnøe Jørgensen -
r6306:2f9de4aa default
parent child Browse files
Show More
@@ -0,0 +1,214
1 # monotone support for the convert extension
2
3 import os
4 import re
5 import time
6 from mercurial import util
7
8 from common import NoRepo, commit, converter_source, checktool
9
10 class monotone_source(converter_source):
11 def __init__(self, ui, path=None, rev=None):
12 converter_source.__init__(self, ui, path, rev)
13
14 self.ui = ui
15 self.path = path
16
17
18 # regular expressions for parsing monotone output
19
20 space = r'\s*'
21 name = r'\s+"((?:[^"]|\\")*)"\s*'
22 value = name
23 revision = r'\s+\[(\w+)\]\s*'
24 lines = r'(?:.|\n)+'
25
26 self.dir_re = re.compile(space + "dir" + name)
27 self.file_re = re.compile(space + "file" + name + "content" + revision)
28 self.add_file_re = re.compile(space + "add_file" + name + "content" + revision)
29 self.patch_re = re.compile(space + "patch" + name + "from" + revision + "to" + revision)
30 self.rename_re = re.compile(space + "rename" + name + "to" + name)
31 self.tag_re = re.compile(space + "tag" + name + "revision" + revision)
32 self.cert_re = re.compile(lines + space + "name" + name + "value" + value)
33
34 attr = space + "file" + lines + space + "attr" + space
35 self.attr_execute_re = re.compile(attr + '"mtn:execute"' + space + '"true"')
36
37 # cached data
38
39 self.manifest_rev = None
40 self.manifest = None
41 self.files = None
42 self.dirs = None
43
44 norepo = NoRepo("%s does not look like a monotone repo" % path)
45 if not os.path.exists(path):
46 raise norepo
47
48 checktool('mtn')
49
50 # test if there are are any revisions
51 self.rev = None
52 try :
53 self.getheads()
54 except :
55 raise norepo
56
57 self.rev = rev
58
59
60 def mtncmd(self, arg):
61 cmdline = "mtn -d %s automate %s" % (util.shellquote(self.path), arg)
62 self.ui.debug(cmdline, '\n')
63 p = util.popen(cmdline)
64 result = p.read()
65 if p.close():
66 raise IOError()
67 return result
68
69 def mtnloadmanifest(self, rev):
70 if self.manifest_rev == rev:
71 return
72 self.manifest_rev = rev
73 self.manifest = self.mtncmd("get_manifest_of %s" % rev).split("\n\n")
74
75 manifest = self.manifest
76 files = {}
77 dirs = {}
78
79 for e in manifest:
80 m = self.file_re.match(e)
81 if m:
82 attr = ""
83 name = m.group(1)
84 node = m.group(2)
85 if self.attr_execute_re.match(e):
86 attr += "x"
87 files[name] = (node, attr)
88 m = self.dir_re.match(e)
89 if m:
90 dirs[m.group(1)] = True
91
92 self.files = files
93 self.dirs = dirs
94
95 def mtnisfile(self, name, rev):
96 # a non-file could be a directory or a deleted or renamed file
97 self.mtnloadmanifest(rev)
98 try :
99 self.files[name]
100 return True
101 except KeyError:
102 return False
103
104 def mtnisdir(self, name, rev):
105 self.mtnloadmanifest(rev)
106 try :
107 self.dirs[name]
108 return True
109 except KeyError:
110 return False
111
112 def mtngetcerts(self, rev):
113 certs = {"author":"<missing>", "date":"<missing>",
114 "changelog":"<missing>", "branch":"<missing>"}
115 cert_list = self.mtncmd("certs %s" % rev).split("\n\n")
116 for e in cert_list:
117 m = self.cert_re.match(e)
118 if m:
119 certs[m.group(1)] = m.group(2)
120 return certs
121
122 def mtngetparents(self, rev):
123 parents = self.mtncmd("parents %s" % rev).strip("\n").split("\n")
124 p = []
125 for x in parents:
126 if len(x) >= 40: # blank revs have been seen otherwise
127 p.append(x)
128 return p
129
130 def mtnrenamefiles(self, files, fromdir, todir):
131 renamed = {}
132 for tofile in files:
133 suffix = tofile.lstrip(todir)
134 if todir + suffix == tofile:
135 renamed[tofile] = (fromdir + suffix).lstrip("/")
136 return renamed
137
138
139 # implement the converter_source interface:
140
141 def getheads(self):
142 if not self.rev or self.rev == "":
143 return self.mtncmd("leaves").splitlines()
144 else:
145 return [self.rev]
146
147 def getchanges(self, rev):
148 revision = self.mtncmd("get_revision %s" % rev).split("\n\n")
149 files = {}
150 copies = {}
151 for e in revision:
152 m = self.add_file_re.match(e)
153 if m:
154 files[m.group(1)] = rev
155 m = self.patch_re.match(e)
156 if m:
157 files[m.group(1)] = rev
158
159 # Delete/rename is handled later when the convert engine
160 # discovers an IOError exception from getfile,
161 # but only if we add the "from" file to the list of changes.
162 m = self.rename_re.match(e)
163 if m:
164 toname = m.group(2)
165 fromname = m.group(1)
166 if self.mtnisfile(toname, rev):
167 copies[toname] = fromname
168 files[toname] = rev
169 files[fromname] = rev
170 if self.mtnisdir(toname, rev):
171 renamed = self.mtnrenamefiles(self.files, fromname, toname)
172 for tofile, fromfile in renamed.items():
173 self.ui.debug (("copying file in renamed dir from '%s' to '%s'" % (fromfile, tofile)), "\n")
174 files[tofile] = rev
175 for fromfile in renamed.values():
176 files[fromfile] = rev
177
178 return (files.items(), copies)
179
180 def getmode(self, name, rev):
181 self.mtnloadmanifest(rev)
182 try :
183 node, attr = self.files[name]
184 return attr
185 except KeyError:
186 return ""
187
188 def getfile(self, name, rev):
189 if not self.mtnisfile(name, rev):
190 raise IOError() # file was deleted or renamed
191 return self.mtncmd("get_file_of %s -r %s" % (util.shellquote(name), rev))
192
193 def getcommit(self, rev):
194 certs = self.mtngetcerts(rev)
195 return commit(
196 author=certs["author"],
197 date=util.datestr(util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")),
198 desc=certs["changelog"],
199 rev=rev,
200 parents=self.mtngetparents(rev),
201 branch=certs["branch"])
202
203 def gettags(self):
204 tags = {}
205 for e in self.mtncmd("tags").split("\n\n"):
206 m = self.tag_re.match(e)
207 if m:
208 tags[m.group(1)] = m.group(2)
209 return tags
210
211 def getchangedfiles(self, rev, i):
212 # This function is only needed to support --filemap
213 # ... and we don't support that
214 raise NotImplementedError()
@@ -1,148 +1,149
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 - Monotone
22 - GNU Arch
23 - GNU Arch
23
24
24 Accepted destination formats:
25 Accepted destination formats:
25 - Mercurial
26 - Mercurial
26 - Subversion (history on branches is not preserved)
27 - Subversion (history on branches is not preserved)
27
28
28 If no revision is given, all revisions will be converted. Otherwise,
29 If no revision is given, all revisions will be converted. Otherwise,
29 convert will only import up to the named revision (given in a format
30 convert will only import up to the named revision (given in a format
30 understood by the source).
31 understood by the source).
31
32
32 If no destination directory name is specified, it defaults to the
33 If no destination directory name is specified, it defaults to the
33 basename of the source with '-hg' appended. If the destination
34 basename of the source with '-hg' appended. If the destination
34 repository doesn't exist, it will be created.
35 repository doesn't exist, it will be created.
35
36
36 If <REVMAP> isn't given, it will be put in a default location
37 If <REVMAP> isn't given, it will be put in a default location
37 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text
38 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text
38 file that maps each source commit ID to the destination ID for
39 file that maps each source commit ID to the destination ID for
39 that revision, like so:
40 that revision, like so:
40 <source ID> <destination ID>
41 <source ID> <destination ID>
41
42
42 If the file doesn't exist, it's automatically created. It's updated
43 If the file doesn't exist, it's automatically created. It's updated
43 on each commit copied, so convert-repo can be interrupted and can
44 on each commit copied, so convert-repo can be interrupted and can
44 be run repeatedly to copy new commits.
45 be run repeatedly to copy new commits.
45
46
46 The [username mapping] file is a simple text file that maps each source
47 The [username mapping] file is a simple text file that maps each source
47 commit author to a destination commit author. It is handy for source SCMs
48 commit author to a destination commit author. It is handy for source SCMs
48 that use unix logins to identify authors (eg: CVS). One line per author
49 that use unix logins to identify authors (eg: CVS). One line per author
49 mapping and the line format is:
50 mapping and the line format is:
50 srcauthor=whatever string you want
51 srcauthor=whatever string you want
51
52
52 The filemap is a file that allows filtering and remapping of files
53 The filemap is a file that allows filtering and remapping of files
53 and directories. Comment lines start with '#'. Each line can
54 and directories. Comment lines start with '#'. Each line can
54 contain one of the following directives:
55 contain one of the following directives:
55
56
56 include path/to/file
57 include path/to/file
57
58
58 exclude path/to/file
59 exclude path/to/file
59
60
60 rename from/file to/file
61 rename from/file to/file
61
62
62 The 'include' directive causes a file, or all files under a
63 The 'include' directive causes a file, or all files under a
63 directory, to be included in the destination repository, and the
64 directory, to be included in the destination repository, and the
64 exclusion of all other files and dirs not explicitely included.
65 exclusion of all other files and dirs not explicitely included.
65 The 'exclude' directive causes files or directories to be omitted.
66 The 'exclude' directive causes files or directories to be omitted.
66 The 'rename' directive renames a file or directory. To rename from a
67 The 'rename' directive renames a file or directory. To rename from a
67 subdirectory into the root of the repository, use '.' as the path to
68 subdirectory into the root of the repository, use '.' as the path to
68 rename to.
69 rename to.
69
70
70 The splicemap is a file that allows insertion of synthetic
71 The splicemap is a file that allows insertion of synthetic
71 history, letting you specify the parents of a revision. This is
72 history, letting you specify the parents of a revision. This is
72 useful if you want to e.g. give a Subversion merge two parents, or
73 useful if you want to e.g. give a Subversion merge two parents, or
73 graft two disconnected series of history together. Each entry
74 graft two disconnected series of history together. Each entry
74 contains a key, followed by a space, followed by one or two
75 contains a key, followed by a space, followed by one or two
75 values, separated by spaces. The key is the revision ID in the
76 values, separated by spaces. The key is the revision ID in the
76 source revision control system whose parents should be modified
77 source revision control system whose parents should be modified
77 (same format as a key in .hg/shamap). The values are the revision
78 (same format as a key in .hg/shamap). The values are the revision
78 IDs (in either the source or destination revision control system)
79 IDs (in either the source or destination revision control system)
79 that should be used as the new parents for that node.
80 that should be used as the new parents for that node.
80
81
81 Mercurial Source
82 Mercurial Source
82 -----------------
83 -----------------
83
84
84 --config convert.hg.saverev=True (boolean)
85 --config convert.hg.saverev=True (boolean)
85 allow target to preserve source revision ID
86 allow target to preserve source revision ID
86
87
87 Subversion Source
88 Subversion Source
88 -----------------
89 -----------------
89
90
90 Subversion source detects classical trunk/branches/tags layouts.
91 Subversion source detects classical trunk/branches/tags layouts.
91 By default, the supplied "svn://repo/path/" source URL is
92 By default, the supplied "svn://repo/path/" source URL is
92 converted as a single branch. If "svn://repo/path/trunk" exists
93 converted as a single branch. If "svn://repo/path/trunk" exists
93 it replaces the default branch. If "svn://repo/path/branches"
94 it replaces the default branch. If "svn://repo/path/branches"
94 exists, its subdirectories are listed as possible branches. If
95 exists, its subdirectories are listed as possible branches. If
95 "svn://repo/path/tags" exists, it is looked for tags referencing
96 "svn://repo/path/tags" exists, it is looked for tags referencing
96 converted branches. Default "trunk", "branches" and "tags" values
97 converted branches. Default "trunk", "branches" and "tags" values
97 can be overriden with following options. Set them to paths
98 can be overriden with following options. Set them to paths
98 relative to the source URL, or leave them blank to disable
99 relative to the source URL, or leave them blank to disable
99 autodetection.
100 autodetection.
100
101
101 --config convert.svn.branches=branches (directory name)
102 --config convert.svn.branches=branches (directory name)
102 specify the directory containing branches
103 specify the directory containing branches
103 --config convert.svn.tags=tags (directory name)
104 --config convert.svn.tags=tags (directory name)
104 specify the directory containing tags
105 specify the directory containing tags
105 --config convert.svn.trunk=trunk (directory name)
106 --config convert.svn.trunk=trunk (directory name)
106 specify the name of the trunk branch
107 specify the name of the trunk branch
107
108
108 Source history can be retrieved starting at a specific revision,
109 Source history can be retrieved starting at a specific revision,
109 instead of being integrally converted. Only single branch
110 instead of being integrally converted. Only single branch
110 conversions are supported.
111 conversions are supported.
111
112
112 --config convert.svn.startrev=0 (svn revision number)
113 --config convert.svn.startrev=0 (svn revision number)
113 specify start Subversion revision.
114 specify start Subversion revision.
114
115
115 Mercurial Destination
116 Mercurial Destination
116 ---------------------
117 ---------------------
117
118
118 --config convert.hg.clonebranches=False (boolean)
119 --config convert.hg.clonebranches=False (boolean)
119 dispatch source branches in separate clones.
120 dispatch source branches in separate clones.
120 --config convert.hg.tagsbranch=default (branch name)
121 --config convert.hg.tagsbranch=default (branch name)
121 tag revisions branch name
122 tag revisions branch name
122 --config convert.hg.usebranchnames=True (boolean)
123 --config convert.hg.usebranchnames=True (boolean)
123 preserve branch names
124 preserve branch names
124
125
125 """
126 """
126 return convcmd.convert(ui, src, dest, revmapfile, **opts)
127 return convcmd.convert(ui, src, dest, revmapfile, **opts)
127
128
128 def debugsvnlog(ui, **opts):
129 def debugsvnlog(ui, **opts):
129 return convcmd.debugsvnlog(ui, **opts)
130 return convcmd.debugsvnlog(ui, **opts)
130
131
131 commands.norepo += " convert debugsvnlog"
132 commands.norepo += " convert debugsvnlog"
132
133
133 cmdtable = {
134 cmdtable = {
134 "convert":
135 "convert":
135 (convert,
136 (convert,
136 [('A', 'authors', '', 'username mapping filename'),
137 [('A', 'authors', '', 'username mapping filename'),
137 ('d', 'dest-type', '', 'destination repository type'),
138 ('d', 'dest-type', '', 'destination repository type'),
138 ('', 'filemap', '', 'remap file names using contents of file'),
139 ('', 'filemap', '', 'remap file names using contents of file'),
139 ('r', 'rev', '', 'import up to target revision REV'),
140 ('r', 'rev', '', 'import up to target revision REV'),
140 ('s', 'source-type', '', 'source repository type'),
141 ('s', 'source-type', '', 'source repository type'),
141 ('', 'splicemap', '', 'splice synthesized history into place'),
142 ('', 'splicemap', '', 'splice synthesized history into place'),
142 ('', 'datesort', None, 'try to sort changesets by date')],
143 ('', 'datesort', None, 'try to sort changesets by date')],
143 'hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
144 'hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
144 "debugsvnlog":
145 "debugsvnlog":
145 (debugsvnlog,
146 (debugsvnlog,
146 [],
147 [],
147 'hg debugsvnlog'),
148 'hg debugsvnlog'),
148 }
149 }
@@ -1,352 +1,354
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, mapfile
8 from common import NoRepo, SKIPREV, 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 monotone import monotone_source
14 from gnuarch import gnuarch_source
15 from gnuarch import gnuarch_source
15 import filemap
16 import filemap
16
17
17 import os, shutil
18 import os, shutil
18 from mercurial import hg, util
19 from mercurial import hg, util
19 from mercurial.i18n import _
20 from mercurial.i18n import _
20
21
21 orig_encoding = 'ascii'
22 orig_encoding = 'ascii'
22
23
23 def recode(s):
24 def recode(s):
24 if isinstance(s, unicode):
25 if isinstance(s, unicode):
25 return s.encode(orig_encoding, 'replace')
26 return s.encode(orig_encoding, 'replace')
26 else:
27 else:
27 return s.decode('utf-8').encode(orig_encoding, 'replace')
28 return s.decode('utf-8').encode(orig_encoding, 'replace')
28
29
29 source_converters = [
30 source_converters = [
30 ('cvs', convert_cvs),
31 ('cvs', convert_cvs),
31 ('git', convert_git),
32 ('git', convert_git),
32 ('svn', svn_source),
33 ('svn', svn_source),
33 ('hg', mercurial_source),
34 ('hg', mercurial_source),
34 ('darcs', darcs_source),
35 ('darcs', darcs_source),
36 ('mtn', monotone_source),
35 ('gnuarch', gnuarch_source),
37 ('gnuarch', gnuarch_source),
36 ]
38 ]
37
39
38 sink_converters = [
40 sink_converters = [
39 ('hg', mercurial_sink),
41 ('hg', mercurial_sink),
40 ('svn', svn_sink),
42 ('svn', svn_sink),
41 ]
43 ]
42
44
43 def convertsource(ui, path, type, rev):
45 def convertsource(ui, path, type, rev):
44 exceptions = []
46 exceptions = []
45 for name, source in source_converters:
47 for name, source in source_converters:
46 try:
48 try:
47 if not type or name == type:
49 if not type or name == type:
48 return source(ui, path, rev)
50 return source(ui, path, rev)
49 except NoRepo, inst:
51 except NoRepo, inst:
50 exceptions.append(inst)
52 exceptions.append(inst)
51 if not ui.quiet:
53 if not ui.quiet:
52 for inst in exceptions:
54 for inst in exceptions:
53 ui.write(_("%s\n") % inst)
55 ui.write(_("%s\n") % inst)
54 raise util.Abort('%s: unknown repository type' % path)
56 raise util.Abort('%s: unknown repository type' % path)
55
57
56 def convertsink(ui, path, type):
58 def convertsink(ui, path, type):
57 for name, sink in sink_converters:
59 for name, sink in sink_converters:
58 try:
60 try:
59 if not type or name == type:
61 if not type or name == type:
60 return sink(ui, path)
62 return sink(ui, path)
61 except NoRepo, inst:
63 except NoRepo, inst:
62 ui.note(_("convert: %s\n") % inst)
64 ui.note(_("convert: %s\n") % inst)
63 raise util.Abort('%s: unknown repository type' % path)
65 raise util.Abort('%s: unknown repository type' % path)
64
66
65 class converter(object):
67 class converter(object):
66 def __init__(self, ui, source, dest, revmapfile, opts):
68 def __init__(self, ui, source, dest, revmapfile, opts):
67
69
68 self.source = source
70 self.source = source
69 self.dest = dest
71 self.dest = dest
70 self.ui = ui
72 self.ui = ui
71 self.opts = opts
73 self.opts = opts
72 self.commitcache = {}
74 self.commitcache = {}
73 self.authors = {}
75 self.authors = {}
74 self.authorfile = None
76 self.authorfile = None
75
77
76 self.map = mapfile(ui, revmapfile)
78 self.map = mapfile(ui, revmapfile)
77
79
78 # Read first the dst author map if any
80 # Read first the dst author map if any
79 authorfile = self.dest.authorfile()
81 authorfile = self.dest.authorfile()
80 if authorfile and os.path.exists(authorfile):
82 if authorfile and os.path.exists(authorfile):
81 self.readauthormap(authorfile)
83 self.readauthormap(authorfile)
82 # Extend/Override with new author map if necessary
84 # Extend/Override with new author map if necessary
83 if opts.get('authors'):
85 if opts.get('authors'):
84 self.readauthormap(opts.get('authors'))
86 self.readauthormap(opts.get('authors'))
85 self.authorfile = self.dest.authorfile()
87 self.authorfile = self.dest.authorfile()
86
88
87 self.splicemap = mapfile(ui, opts.get('splicemap'))
89 self.splicemap = mapfile(ui, opts.get('splicemap'))
88
90
89 def walktree(self, heads):
91 def walktree(self, heads):
90 '''Return a mapping that identifies the uncommitted parents of every
92 '''Return a mapping that identifies the uncommitted parents of every
91 uncommitted changeset.'''
93 uncommitted changeset.'''
92 visit = heads
94 visit = heads
93 known = {}
95 known = {}
94 parents = {}
96 parents = {}
95 while visit:
97 while visit:
96 n = visit.pop(0)
98 n = visit.pop(0)
97 if n in known or n in self.map: continue
99 if n in known or n in self.map: continue
98 known[n] = 1
100 known[n] = 1
99 commit = self.cachecommit(n)
101 commit = self.cachecommit(n)
100 parents[n] = []
102 parents[n] = []
101 for p in commit.parents:
103 for p in commit.parents:
102 parents[n].append(p)
104 parents[n].append(p)
103 visit.append(p)
105 visit.append(p)
104
106
105 return parents
107 return parents
106
108
107 def toposort(self, parents):
109 def toposort(self, parents):
108 '''Return an ordering such that every uncommitted changeset is
110 '''Return an ordering such that every uncommitted changeset is
109 preceeded by all its uncommitted ancestors.'''
111 preceeded by all its uncommitted ancestors.'''
110 visit = parents.keys()
112 visit = parents.keys()
111 seen = {}
113 seen = {}
112 children = {}
114 children = {}
113 actives = []
115 actives = []
114
116
115 while visit:
117 while visit:
116 n = visit.pop(0)
118 n = visit.pop(0)
117 if n in seen: continue
119 if n in seen: continue
118 seen[n] = 1
120 seen[n] = 1
119 # Ensure that nodes without parents are present in the 'children'
121 # Ensure that nodes without parents are present in the 'children'
120 # mapping.
122 # mapping.
121 children.setdefault(n, [])
123 children.setdefault(n, [])
122 hasparent = False
124 hasparent = False
123 for p in parents[n]:
125 for p in parents[n]:
124 if not p in self.map:
126 if not p in self.map:
125 visit.append(p)
127 visit.append(p)
126 hasparent = True
128 hasparent = True
127 children.setdefault(p, []).append(n)
129 children.setdefault(p, []).append(n)
128 if not hasparent:
130 if not hasparent:
129 actives.append(n)
131 actives.append(n)
130
132
131 del seen
133 del seen
132 del visit
134 del visit
133
135
134 if self.opts.get('datesort'):
136 if self.opts.get('datesort'):
135 dates = {}
137 dates = {}
136 def getdate(n):
138 def getdate(n):
137 if n not in dates:
139 if n not in dates:
138 dates[n] = util.parsedate(self.commitcache[n].date)
140 dates[n] = util.parsedate(self.commitcache[n].date)
139 return dates[n]
141 return dates[n]
140
142
141 def picknext(nodes):
143 def picknext(nodes):
142 return min([(getdate(n), n) for n in nodes])[1]
144 return min([(getdate(n), n) for n in nodes])[1]
143 else:
145 else:
144 prev = [None]
146 prev = [None]
145 def picknext(nodes):
147 def picknext(nodes):
146 # Return the first eligible child of the previously converted
148 # Return the first eligible child of the previously converted
147 # revision, or any of them.
149 # revision, or any of them.
148 next = nodes[0]
150 next = nodes[0]
149 for n in nodes:
151 for n in nodes:
150 if prev[0] in parents[n]:
152 if prev[0] in parents[n]:
151 next = n
153 next = n
152 break
154 break
153 prev[0] = next
155 prev[0] = next
154 return next
156 return next
155
157
156 s = []
158 s = []
157 pendings = {}
159 pendings = {}
158 while actives:
160 while actives:
159 n = picknext(actives)
161 n = picknext(actives)
160 actives.remove(n)
162 actives.remove(n)
161 s.append(n)
163 s.append(n)
162
164
163 # Update dependents list
165 # Update dependents list
164 for c in children.get(n, []):
166 for c in children.get(n, []):
165 if c not in pendings:
167 if c not in pendings:
166 pendings[c] = [p for p in parents[c] if p not in self.map]
168 pendings[c] = [p for p in parents[c] if p not in self.map]
167 try:
169 try:
168 pendings[c].remove(n)
170 pendings[c].remove(n)
169 except ValueError:
171 except ValueError:
170 raise util.Abort(_('cycle detected between %s and %s')
172 raise util.Abort(_('cycle detected between %s and %s')
171 % (recode(c), recode(n)))
173 % (recode(c), recode(n)))
172 if not pendings[c]:
174 if not pendings[c]:
173 # Parents are converted, node is eligible
175 # Parents are converted, node is eligible
174 actives.insert(0, c)
176 actives.insert(0, c)
175 pendings[c] = None
177 pendings[c] = None
176
178
177 if len(s) != len(parents):
179 if len(s) != len(parents):
178 raise util.Abort(_("not all revisions were sorted"))
180 raise util.Abort(_("not all revisions were sorted"))
179
181
180 return s
182 return s
181
183
182 def writeauthormap(self):
184 def writeauthormap(self):
183 authorfile = self.authorfile
185 authorfile = self.authorfile
184 if authorfile:
186 if authorfile:
185 self.ui.status('Writing author map file %s\n' % authorfile)
187 self.ui.status('Writing author map file %s\n' % authorfile)
186 ofile = open(authorfile, 'w+')
188 ofile = open(authorfile, 'w+')
187 for author in self.authors:
189 for author in self.authors:
188 ofile.write("%s=%s\n" % (author, self.authors[author]))
190 ofile.write("%s=%s\n" % (author, self.authors[author]))
189 ofile.close()
191 ofile.close()
190
192
191 def readauthormap(self, authorfile):
193 def readauthormap(self, authorfile):
192 afile = open(authorfile, 'r')
194 afile = open(authorfile, 'r')
193 for line in afile:
195 for line in afile:
194 if line.strip() == '':
196 if line.strip() == '':
195 continue
197 continue
196 try:
198 try:
197 srcauthor, dstauthor = line.split('=', 1)
199 srcauthor, dstauthor = line.split('=', 1)
198 srcauthor = srcauthor.strip()
200 srcauthor = srcauthor.strip()
199 dstauthor = dstauthor.strip()
201 dstauthor = dstauthor.strip()
200 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
202 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
201 self.ui.status(
203 self.ui.status(
202 'Overriding mapping for author %s, was %s, will be %s\n'
204 'Overriding mapping for author %s, was %s, will be %s\n'
203 % (srcauthor, self.authors[srcauthor], dstauthor))
205 % (srcauthor, self.authors[srcauthor], dstauthor))
204 else:
206 else:
205 self.ui.debug('Mapping author %s to %s\n'
207 self.ui.debug('Mapping author %s to %s\n'
206 % (srcauthor, dstauthor))
208 % (srcauthor, dstauthor))
207 self.authors[srcauthor] = dstauthor
209 self.authors[srcauthor] = dstauthor
208 except IndexError:
210 except IndexError:
209 self.ui.warn(
211 self.ui.warn(
210 'Ignoring bad line in author map file %s: %s\n'
212 'Ignoring bad line in author map file %s: %s\n'
211 % (authorfile, line.rstrip()))
213 % (authorfile, line.rstrip()))
212 afile.close()
214 afile.close()
213
215
214 def cachecommit(self, rev):
216 def cachecommit(self, rev):
215 commit = self.source.getcommit(rev)
217 commit = self.source.getcommit(rev)
216 commit.author = self.authors.get(commit.author, commit.author)
218 commit.author = self.authors.get(commit.author, commit.author)
217 self.commitcache[rev] = commit
219 self.commitcache[rev] = commit
218 return commit
220 return commit
219
221
220 def copy(self, rev):
222 def copy(self, rev):
221 commit = self.commitcache[rev]
223 commit = self.commitcache[rev]
222 do_copies = hasattr(self.dest, 'copyfile')
224 do_copies = hasattr(self.dest, 'copyfile')
223 filenames = []
225 filenames = []
224
226
225 changes = self.source.getchanges(rev)
227 changes = self.source.getchanges(rev)
226 if isinstance(changes, basestring):
228 if isinstance(changes, basestring):
227 if changes == SKIPREV:
229 if changes == SKIPREV:
228 dest = SKIPREV
230 dest = SKIPREV
229 else:
231 else:
230 dest = self.map[changes]
232 dest = self.map[changes]
231 self.map[rev] = dest
233 self.map[rev] = dest
232 return
234 return
233 files, copies = changes
235 files, copies = changes
234 pbranches = []
236 pbranches = []
235 if commit.parents:
237 if commit.parents:
236 for prev in commit.parents:
238 for prev in commit.parents:
237 if prev not in self.commitcache:
239 if prev not in self.commitcache:
238 self.cachecommit(prev)
240 self.cachecommit(prev)
239 pbranches.append((self.map[prev],
241 pbranches.append((self.map[prev],
240 self.commitcache[prev].branch))
242 self.commitcache[prev].branch))
241 self.dest.setbranch(commit.branch, pbranches)
243 self.dest.setbranch(commit.branch, pbranches)
242 for f, v in files:
244 for f, v in files:
243 filenames.append(f)
245 filenames.append(f)
244 try:
246 try:
245 data = self.source.getfile(f, v)
247 data = self.source.getfile(f, v)
246 except IOError, inst:
248 except IOError, inst:
247 self.dest.delfile(f)
249 self.dest.delfile(f)
248 else:
250 else:
249 e = self.source.getmode(f, v)
251 e = self.source.getmode(f, v)
250 self.dest.putfile(f, e, data)
252 self.dest.putfile(f, e, data)
251 if do_copies:
253 if do_copies:
252 if f in copies:
254 if f in copies:
253 copyf = copies[f]
255 copyf = copies[f]
254 # Merely marks that a copy happened.
256 # Merely marks that a copy happened.
255 self.dest.copyfile(copyf, f)
257 self.dest.copyfile(copyf, f)
256
258
257 try:
259 try:
258 parents = self.splicemap[rev].replace(',', ' ').split()
260 parents = self.splicemap[rev].replace(',', ' ').split()
259 self.ui.status('spliced in %s as parents of %s\n' %
261 self.ui.status('spliced in %s as parents of %s\n' %
260 (parents, rev))
262 (parents, rev))
261 parents = [self.map.get(p, p) for p in parents]
263 parents = [self.map.get(p, p) for p in parents]
262 except KeyError:
264 except KeyError:
263 parents = [b[0] for b in pbranches]
265 parents = [b[0] for b in pbranches]
264 newnode = self.dest.putcommit(filenames, parents, commit)
266 newnode = self.dest.putcommit(filenames, parents, commit)
265 self.source.converted(rev, newnode)
267 self.source.converted(rev, newnode)
266 self.map[rev] = newnode
268 self.map[rev] = newnode
267
269
268 def convert(self):
270 def convert(self):
269
271
270 try:
272 try:
271 self.source.before()
273 self.source.before()
272 self.dest.before()
274 self.dest.before()
273 self.source.setrevmap(self.map)
275 self.source.setrevmap(self.map)
274 self.ui.status("scanning source...\n")
276 self.ui.status("scanning source...\n")
275 heads = self.source.getheads()
277 heads = self.source.getheads()
276 parents = self.walktree(heads)
278 parents = self.walktree(heads)
277 self.ui.status("sorting...\n")
279 self.ui.status("sorting...\n")
278 t = self.toposort(parents)
280 t = self.toposort(parents)
279 num = len(t)
281 num = len(t)
280 c = None
282 c = None
281
283
282 self.ui.status("converting...\n")
284 self.ui.status("converting...\n")
283 for c in t:
285 for c in t:
284 num -= 1
286 num -= 1
285 desc = self.commitcache[c].desc
287 desc = self.commitcache[c].desc
286 if "\n" in desc:
288 if "\n" in desc:
287 desc = desc.splitlines()[0]
289 desc = desc.splitlines()[0]
288 # convert log message to local encoding without using
290 # convert log message to local encoding without using
289 # tolocal() because util._encoding conver() use it as
291 # tolocal() because util._encoding conver() use it as
290 # 'utf-8'
292 # 'utf-8'
291 self.ui.status("%d %s\n" % (num, recode(desc)))
293 self.ui.status("%d %s\n" % (num, recode(desc)))
292 self.ui.note(_("source: %s\n" % recode(c)))
294 self.ui.note(_("source: %s\n" % recode(c)))
293 self.copy(c)
295 self.copy(c)
294
296
295 tags = self.source.gettags()
297 tags = self.source.gettags()
296 ctags = {}
298 ctags = {}
297 for k in tags:
299 for k in tags:
298 v = tags[k]
300 v = tags[k]
299 if self.map.get(v, SKIPREV) != SKIPREV:
301 if self.map.get(v, SKIPREV) != SKIPREV:
300 ctags[k] = self.map[v]
302 ctags[k] = self.map[v]
301
303
302 if c and ctags:
304 if c and ctags:
303 nrev = self.dest.puttags(ctags)
305 nrev = self.dest.puttags(ctags)
304 # write another hash correspondence to override the previous
306 # write another hash correspondence to override the previous
305 # one so we don't end up with extra tag heads
307 # one so we don't end up with extra tag heads
306 if nrev:
308 if nrev:
307 self.map[c] = nrev
309 self.map[c] = nrev
308
310
309 self.writeauthormap()
311 self.writeauthormap()
310 finally:
312 finally:
311 self.cleanup()
313 self.cleanup()
312
314
313 def cleanup(self):
315 def cleanup(self):
314 try:
316 try:
315 self.dest.after()
317 self.dest.after()
316 finally:
318 finally:
317 self.source.after()
319 self.source.after()
318 self.map.close()
320 self.map.close()
319
321
320 def convert(ui, src, dest=None, revmapfile=None, **opts):
322 def convert(ui, src, dest=None, revmapfile=None, **opts):
321 global orig_encoding
323 global orig_encoding
322 orig_encoding = util._encoding
324 orig_encoding = util._encoding
323 util._encoding = 'UTF-8'
325 util._encoding = 'UTF-8'
324
326
325 if not dest:
327 if not dest:
326 dest = hg.defaultdest(src) + "-hg"
328 dest = hg.defaultdest(src) + "-hg"
327 ui.status("assuming destination %s\n" % dest)
329 ui.status("assuming destination %s\n" % dest)
328
330
329 destc = convertsink(ui, dest, opts.get('dest_type'))
331 destc = convertsink(ui, dest, opts.get('dest_type'))
330
332
331 try:
333 try:
332 srcc = convertsource(ui, src, opts.get('source_type'),
334 srcc = convertsource(ui, src, opts.get('source_type'),
333 opts.get('rev'))
335 opts.get('rev'))
334 except Exception:
336 except Exception:
335 for path in destc.created:
337 for path in destc.created:
336 shutil.rmtree(path, True)
338 shutil.rmtree(path, True)
337 raise
339 raise
338
340
339 fmap = opts.get('filemap')
341 fmap = opts.get('filemap')
340 if fmap:
342 if fmap:
341 srcc = filemap.filemap_source(ui, srcc, fmap)
343 srcc = filemap.filemap_source(ui, srcc, fmap)
342 destc.setfilemapmode(True)
344 destc.setfilemapmode(True)
343
345
344 if not revmapfile:
346 if not revmapfile:
345 try:
347 try:
346 revmapfile = destc.revmapfile()
348 revmapfile = destc.revmapfile()
347 except:
349 except:
348 revmapfile = os.path.join(destc, "map")
350 revmapfile = os.path.join(destc, "map")
349
351
350 c = converter(ui, srcc, destc, revmapfile, opts)
352 c = converter(ui, srcc, destc, revmapfile, opts)
351 c.convert()
353 c.convert()
352
354
General Comments 0
You need to be logged in to leave comments. Login now