##// END OF EJS Templates
more whitespace cleanup and some other style nits
Dirkjan Ochtman -
r8222:d30a2159 default
parent child Browse files
Show More
@@ -1,261 +1,261 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 '''converting foreign VCS repositories to Mercurial'''
7 '''converting foreign VCS repositories to Mercurial'''
8
8
9 import convcmd
9 import convcmd
10 import cvsps
10 import cvsps
11 import subversion
11 import subversion
12 from mercurial import commands
12 from mercurial import commands
13 from mercurial.i18n import _
13 from mercurial.i18n import _
14
14
15 # Commands definition was moved elsewhere to ease demandload job.
15 # Commands definition was moved elsewhere to ease demandload job.
16
16
17 def convert(ui, src, dest=None, revmapfile=None, **opts):
17 def convert(ui, src, dest=None, revmapfile=None, **opts):
18 """convert a foreign SCM repository to a Mercurial one.
18 """convert a foreign SCM repository to a Mercurial one.
19
19
20 Accepted source formats [identifiers]:
20 Accepted source formats [identifiers]:
21 - Mercurial [hg]
21 - Mercurial [hg]
22 - CVS [cvs]
22 - CVS [cvs]
23 - Darcs [darcs]
23 - Darcs [darcs]
24 - git [git]
24 - git [git]
25 - Subversion [svn]
25 - Subversion [svn]
26 - Monotone [mtn]
26 - Monotone [mtn]
27 - GNU Arch [gnuarch]
27 - GNU Arch [gnuarch]
28 - Bazaar [bzr]
28 - Bazaar [bzr]
29 - Perforce [p4]
29 - Perforce [p4]
30
30
31 Accepted destination formats [identifiers]:
31 Accepted destination formats [identifiers]:
32 - Mercurial [hg]
32 - Mercurial [hg]
33 - Subversion [svn] (history on branches is not preserved)
33 - Subversion [svn] (history on branches is not preserved)
34
34
35 If no revision is given, all revisions will be converted.
35 If no revision is given, all revisions will be converted.
36 Otherwise, convert will only import up to the named revision
36 Otherwise, convert will only import up to the named revision
37 (given in a format understood by the source).
37 (given in a format understood by the source).
38
38
39 If no destination directory name is specified, it defaults to the
39 If no destination directory name is specified, it defaults to the
40 basename of the source with '-hg' appended. If the destination
40 basename of the source with '-hg' appended. If the destination
41 repository doesn't exist, it will be created.
41 repository doesn't exist, it will be created.
42
42
43 If <REVMAP> isn't given, it will be put in a default location
43 If <REVMAP> isn't given, it will be put in a default location
44 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file
44 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file
45 that maps each source commit ID to the destination ID for that
45 that maps each source commit ID to the destination ID for that
46 revision, like so:
46 revision, like so:
47 <source ID> <destination ID>
47 <source ID> <destination ID>
48
48
49 If the file doesn't exist, it's automatically created. It's
49 If the file doesn't exist, it's automatically created. It's
50 updated on each commit copied, so convert-repo can be interrupted
50 updated on each commit copied, so convert-repo can be interrupted
51 and can be run repeatedly to copy new commits.
51 and can be run repeatedly to copy new commits.
52
52
53 The [username mapping] file is a simple text file that maps each
53 The [username mapping] file is a simple text file that maps each
54 source commit author to a destination commit author. It is handy
54 source commit author to a destination commit author. It is handy
55 for source SCMs that use unix logins to identify authors (eg:
55 for source SCMs that use unix logins to identify authors (eg:
56 CVS). One line per author mapping and the line format is:
56 CVS). One line per author mapping and the line format is:
57 srcauthor=whatever string you want
57 srcauthor=whatever string you want
58
58
59 The filemap is a file that allows filtering and remapping of files
59 The filemap is a file that allows filtering and remapping of files
60 and directories. Comment lines start with '#'. Each line can
60 and directories. Comment lines start with '#'. Each line can
61 contain one of the following directives:
61 contain one of the following directives:
62
62
63 include path/to/file
63 include path/to/file
64
64
65 exclude path/to/file
65 exclude path/to/file
66
66
67 rename from/file to/file
67 rename from/file to/file
68
68
69 The 'include' directive causes a file, or all files under a
69 The 'include' directive causes a file, or all files under a
70 directory, to be included in the destination repository, and the
70 directory, to be included in the destination repository, and the
71 exclusion of all other files and directories not explicitely included.
71 exclusion of all other files and directories not explicitely included.
72 The 'exclude' directive causes files or directories to be omitted.
72 The 'exclude' directive causes files or directories to be omitted.
73 The 'rename' directive renames a file or directory. To rename from
73 The 'rename' directive renames a file or directory. To rename from
74 a subdirectory into the root of the repository, use '.' as the
74 a subdirectory into the root of the repository, use '.' as the
75 path to rename to.
75 path to rename to.
76
76
77 The splicemap is a file that allows insertion of synthetic
77 The splicemap is a file that allows insertion of synthetic
78 history, letting you specify the parents of a revision. This is
78 history, letting you specify the parents of a revision. This is
79 useful if you want to e.g. give a Subversion merge two parents, or
79 useful if you want to e.g. give a Subversion merge two parents, or
80 graft two disconnected series of history together. Each entry
80 graft two disconnected series of history together. Each entry
81 contains a key, followed by a space, followed by one or two
81 contains a key, followed by a space, followed by one or two
82 comma-separated values. The key is the revision ID in the source
82 comma-separated values. The key is the revision ID in the source
83 revision control system whose parents should be modified (same
83 revision control system whose parents should be modified (same
84 format as a key in .hg/shamap). The values are the revision IDs
84 format as a key in .hg/shamap). The values are the revision IDs
85 (in either the source or destination revision control system) that
85 (in either the source or destination revision control system) that
86 should be used as the new parents for that node.
86 should be used as the new parents for that node.
87
87
88 Mercurial Source
88 Mercurial Source
89 -----------------
89 -----------------
90
90
91 --config convert.hg.ignoreerrors=False (boolean)
91 --config convert.hg.ignoreerrors=False (boolean)
92 ignore integrity errors when reading. Use it to fix Mercurial
92 ignore integrity errors when reading. Use it to fix Mercurial
93 repositories with missing revlogs, by converting from and to
93 repositories with missing revlogs, by converting from and to
94 Mercurial.
94 Mercurial.
95 --config convert.hg.saverev=False (boolean)
95 --config convert.hg.saverev=False (boolean)
96 store original revision ID in changeset (forces target IDs to
96 store original revision ID in changeset (forces target IDs to
97 change)
97 change)
98 --config convert.hg.startrev=0 (hg revision identifier)
98 --config convert.hg.startrev=0 (hg revision identifier)
99 convert start revision and its descendants
99 convert start revision and its descendants
100
100
101 CVS Source
101 CVS Source
102 ----------
102 ----------
103
103
104 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
104 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
105 to indicate the starting point of what will be converted. Direct
105 to indicate the starting point of what will be converted. Direct
106 access to the repository files is not needed, unless of course the
106 access to the repository files is not needed, unless of course the
107 repository is :local:. The conversion uses the top level directory
107 repository is :local:. The conversion uses the top level directory
108 in the sandbox to find the CVS repository, and then uses CVS rlog
108 in the sandbox to find the CVS repository, and then uses CVS rlog
109 commands to find files to convert. This means that unless a
109 commands to find files to convert. This means that unless a
110 filemap is given, all files under the starting directory will be
110 filemap is given, all files under the starting directory will be
111 converted, and that any directory reorganisation in the CVS
111 converted, and that any directory reorganisation in the CVS
112 sandbox is ignored.
112 sandbox is ignored.
113
113
114 Because CVS does not have changesets, it is necessary to collect
114 Because CVS does not have changesets, it is necessary to collect
115 individual commits to CVS and merge them into changesets. CVS
115 individual commits to CVS and merge them into changesets. CVS
116 source uses its internal changeset merging code by default but can
116 source uses its internal changeset merging code by default but can
117 be configured to call the external 'cvsps' program by setting:
117 be configured to call the external 'cvsps' program by setting:
118 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
118 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
119 This is a legacy option and may be removed in future.
119 This is a legacy option and may be removed in future.
120
120
121 The options shown are the defaults.
121 The options shown are the defaults.
122
122
123 Internal cvsps is selected by setting
123 Internal cvsps is selected by setting
124 --config convert.cvsps=builtin
124 --config convert.cvsps=builtin
125 and has a few more configurable options:
125 and has a few more configurable options:
126 --config convert.cvsps.cache=True (boolean)
126 --config convert.cvsps.cache=True (boolean)
127 Set to False to disable remote log caching, for testing and
127 Set to False to disable remote log caching, for testing and
128 debugging purposes.
128 debugging purposes.
129 --config convert.cvsps.fuzz=60 (integer)
129 --config convert.cvsps.fuzz=60 (integer)
130 Specify the maximum time (in seconds) that is allowed
130 Specify the maximum time (in seconds) that is allowed
131 between commits with identical user and log message in a
131 between commits with identical user and log message in a
132 single changeset. When very large files were checked in as
132 single changeset. When very large files were checked in as
133 part of a changeset then the default may not be long
133 part of a changeset then the default may not be long
134 enough.
134 enough.
135 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
135 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
136 Specify a regular expression to which commit log messages
136 Specify a regular expression to which commit log messages
137 are matched. If a match occurs, then the conversion
137 are matched. If a match occurs, then the conversion
138 process will insert a dummy revision merging the branch on
138 process will insert a dummy revision merging the branch on
139 which this log message occurs to the branch indicated in
139 which this log message occurs to the branch indicated in
140 the regex.
140 the regex.
141 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
141 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
142 Specify a regular expression to which commit log messages
142 Specify a regular expression to which commit log messages
143 are matched. If a match occurs, then the conversion
143 are matched. If a match occurs, then the conversion
144 process will add the most recent revision on the branch
144 process will add the most recent revision on the branch
145 indicated in the regex as the second parent of the
145 indicated in the regex as the second parent of the
146 changeset.
146 changeset.
147
147
148 The hgext/convert/cvsps wrapper script allows the builtin
148 The hgext/convert/cvsps wrapper script allows the builtin
149 changeset merging code to be run without doing a conversion. Its
149 changeset merging code to be run without doing a conversion. Its
150 parameters and output are similar to that of cvsps 2.1.
150 parameters and output are similar to that of cvsps 2.1.
151
151
152 Subversion Source
152 Subversion Source
153 -----------------
153 -----------------
154
154
155 Subversion source detects classical trunk/branches/tags layouts.
155 Subversion source detects classical trunk/branches/tags layouts.
156 By default, the supplied "svn://repo/path/" source URL is
156 By default, the supplied "svn://repo/path/" source URL is
157 converted as a single branch. If "svn://repo/path/trunk" exists it
157 converted as a single branch. If "svn://repo/path/trunk" exists it
158 replaces the default branch. If "svn://repo/path/branches" exists,
158 replaces the default branch. If "svn://repo/path/branches" exists,
159 its subdirectories are listed as possible branches. If
159 its subdirectories are listed as possible branches. If
160 "svn://repo/path/tags" exists, it is looked for tags referencing
160 "svn://repo/path/tags" exists, it is looked for tags referencing
161 converted branches. Default "trunk", "branches" and "tags" values
161 converted branches. Default "trunk", "branches" and "tags" values
162 can be overriden with following options. Set them to paths
162 can be overriden with following options. Set them to paths
163 relative to the source URL, or leave them blank to disable
163 relative to the source URL, or leave them blank to disable
164 autodetection.
164 autodetection.
165
165
166 --config convert.svn.branches=branches (directory name)
166 --config convert.svn.branches=branches (directory name)
167 specify the directory containing branches
167 specify the directory containing branches
168 --config convert.svn.tags=tags (directory name)
168 --config convert.svn.tags=tags (directory name)
169 specify the directory containing tags
169 specify the directory containing tags
170 --config convert.svn.trunk=trunk (directory name)
170 --config convert.svn.trunk=trunk (directory name)
171 specify the name of the trunk branch
171 specify the name of the trunk branch
172
172
173 Source history can be retrieved starting at a specific revision,
173 Source history can be retrieved starting at a specific revision,
174 instead of being integrally converted. Only single branch
174 instead of being integrally converted. Only single branch
175 conversions are supported.
175 conversions are supported.
176
176
177 --config convert.svn.startrev=0 (svn revision number)
177 --config convert.svn.startrev=0 (svn revision number)
178 specify start Subversion revision.
178 specify start Subversion revision.
179
179
180 Perforce Source
180 Perforce Source
181 ---------------
181 ---------------
182
182
183 The Perforce (P4) importer can be given a p4 depot path or a
183 The Perforce (P4) importer can be given a p4 depot path or a
184 client specification as source. It will convert all files in the
184 client specification as source. It will convert all files in the
185 source to a flat Mercurial repository, ignoring labels, branches
185 source to a flat Mercurial repository, ignoring labels, branches
186 and integrations. Note that when a depot path is given you then
186 and integrations. Note that when a depot path is given you then
187 usually should specify a target directory, because otherwise the
187 usually should specify a target directory, because otherwise the
188 target may be named ...-hg.
188 target may be named ...-hg.
189
189
190 It is possible to limit the amount of source history to be
190 It is possible to limit the amount of source history to be
191 converted by specifying an initial Perforce revision.
191 converted by specifying an initial Perforce revision.
192
192
193 --config convert.p4.startrev=0 (perforce changelist number)
193 --config convert.p4.startrev=0 (perforce changelist number)
194 specify initial Perforce revision.
194 specify initial Perforce revision.
195
195
196
196
197 Mercurial Destination
197 Mercurial Destination
198 ---------------------
198 ---------------------
199
199
200 --config convert.hg.clonebranches=False (boolean)
200 --config convert.hg.clonebranches=False (boolean)
201 dispatch source branches in separate clones.
201 dispatch source branches in separate clones.
202 --config convert.hg.tagsbranch=default (branch name)
202 --config convert.hg.tagsbranch=default (branch name)
203 tag revisions branch name
203 tag revisions branch name
204 --config convert.hg.usebranchnames=True (boolean)
204 --config convert.hg.usebranchnames=True (boolean)
205 preserve branch names
205 preserve branch names
206
206
207 """
207 """
208 return convcmd.convert(ui, src, dest, revmapfile, **opts)
208 return convcmd.convert(ui, src, dest, revmapfile, **opts)
209
209
210 def debugsvnlog(ui, **opts):
210 def debugsvnlog(ui, **opts):
211 return subversion.debugsvnlog(ui, **opts)
211 return subversion.debugsvnlog(ui, **opts)
212
212
213 def debugcvsps(ui, *args, **opts):
213 def debugcvsps(ui, *args, **opts):
214 '''create changeset information from CVS
214 '''create changeset information from CVS
215
215
216 This command is intended as a debugging tool for the CVS to
216 This command is intended as a debugging tool for the CVS to
217 Mercurial converter, and can be used as a direct replacement for
217 Mercurial converter, and can be used as a direct replacement for
218 cvsps.
218 cvsps.
219
219
220 Hg debugcvsps reads the CVS rlog for current directory (or any
220 Hg debugcvsps reads the CVS rlog for current directory (or any
221 named directory) in the CVS repository, and converts the log to a
221 named directory) in the CVS repository, and converts the log to a
222 series of changesets based on matching commit log entries and
222 series of changesets based on matching commit log entries and
223 dates.'''
223 dates.'''
224 return cvsps.debugcvsps(ui, *args, **opts)
224 return cvsps.debugcvsps(ui, *args, **opts)
225
225
226 commands.norepo += " convert debugsvnlog debugcvsps"
226 commands.norepo += " convert debugsvnlog debugcvsps"
227
227
228 cmdtable = {
228 cmdtable = {
229 "convert":
229 "convert":
230 (convert,
230 (convert,
231 [('A', 'authors', '', _('username mapping filename')),
231 [('A', 'authors', '', _('username mapping filename')),
232 ('d', 'dest-type', '', _('destination repository type')),
232 ('d', 'dest-type', '', _('destination repository type')),
233 ('', 'filemap', '', _('remap file names using contents of file')),
233 ('', 'filemap', '', _('remap file names using contents of file')),
234 ('r', 'rev', '', _('import up to target revision REV')),
234 ('r', 'rev', '', _('import up to target revision REV')),
235 ('s', 'source-type', '', _('source repository type')),
235 ('s', 'source-type', '', _('source repository type')),
236 ('', 'splicemap', '', _('splice synthesized history into place')),
236 ('', 'splicemap', '', _('splice synthesized history into place')),
237 ('', 'datesort', None, _('try to sort changesets by date'))],
237 ('', 'datesort', None, _('try to sort changesets by date'))],
238 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')),
238 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')),
239 "debugsvnlog":
239 "debugsvnlog":
240 (debugsvnlog,
240 (debugsvnlog,
241 [],
241 [],
242 'hg debugsvnlog'),
242 'hg debugsvnlog'),
243 "debugcvsps":
243 "debugcvsps":
244 (debugcvsps,
244 (debugcvsps,
245 [
245 [
246 # Main options shared with cvsps-2.1
246 # Main options shared with cvsps-2.1
247 ('b', 'branches', [], _('only return changes on specified branches')),
247 ('b', 'branches', [], _('only return changes on specified branches')),
248 ('p', 'prefix', '', _('prefix to remove from file names')),
248 ('p', 'prefix', '', _('prefix to remove from file names')),
249 ('r', 'revisions', [], _('only return changes after or between specified tags')),
249 ('r', 'revisions', [], _('only return changes after or between specified tags')),
250 ('u', 'update-cache', None, _("update cvs log cache")),
250 ('u', 'update-cache', None, _("update cvs log cache")),
251 ('x', 'new-cache', None, _("create new cvs log cache")),
251 ('x', 'new-cache', None, _("create new cvs log cache")),
252 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
252 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
253 ('', 'root', '', _('specify cvsroot')),
253 ('', 'root', '', _('specify cvsroot')),
254 # Options specific to builtin cvsps
254 # Options specific to builtin cvsps
255 ('', 'parents', '', _('show parent changesets')),
255 ('', 'parents', '', _('show parent changesets')),
256 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
256 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
257 # Options that are ignored for compatibility with cvsps-2.1
257 # Options that are ignored for compatibility with cvsps-2.1
258 ('A', 'cvs-direct', None, _('ignored for compatibility')),
258 ('A', 'cvs-direct', None, _('ignored for compatibility')),
259 ],
259 ],
260 _('hg debugcvsps [OPTION]... [PATH]...')),
260 _('hg debugcvsps [OPTION]... [PATH]...')),
261 }
261 }
@@ -1,469 +1,469 b''
1 # rebase.py - rebasing feature for mercurial
1 # rebase.py - rebasing feature for mercurial
2 #
2 #
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot 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 '''move sets of revisions to a different ancestor
8 '''move sets of revisions to a different ancestor
9
9
10 This extension lets you rebase changesets in an existing Mercurial
10 This extension lets you rebase changesets in an existing Mercurial
11 repository.
11 repository.
12
12
13 For more information:
13 For more information:
14 http://www.selenic.com/mercurial/wiki/index.cgi/RebaseProject
14 http://www.selenic.com/mercurial/wiki/index.cgi/RebaseProject
15 '''
15 '''
16
16
17 from mercurial import util, repair, merge, cmdutil, commands, error
17 from mercurial import util, repair, merge, cmdutil, commands, error
18 from mercurial import extensions, ancestor, copies, patch
18 from mercurial import extensions, ancestor, copies, patch
19 from mercurial.commands import templateopts
19 from mercurial.commands import templateopts
20 from mercurial.node import nullrev
20 from mercurial.node import nullrev
21 from mercurial.lock import release
21 from mercurial.lock import release
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 import os, errno
23 import os, errno
24
24
25 def rebasemerge(repo, rev, first=False):
25 def rebasemerge(repo, rev, first=False):
26 'return the correct ancestor'
26 'return the correct ancestor'
27 oldancestor = ancestor.ancestor
27 oldancestor = ancestor.ancestor
28
28
29 def newancestor(a, b, pfunc):
29 def newancestor(a, b, pfunc):
30 ancestor.ancestor = oldancestor
30 ancestor.ancestor = oldancestor
31 if b == rev:
31 if b == rev:
32 return repo[rev].parents()[0].rev()
32 return repo[rev].parents()[0].rev()
33 return ancestor.ancestor(a, b, pfunc)
33 return ancestor.ancestor(a, b, pfunc)
34
34
35 if not first:
35 if not first:
36 ancestor.ancestor = newancestor
36 ancestor.ancestor = newancestor
37 else:
37 else:
38 repo.ui.debug(_("first revision, do not change ancestor\n"))
38 repo.ui.debug(_("first revision, do not change ancestor\n"))
39 stats = merge.update(repo, rev, True, True, False)
39 stats = merge.update(repo, rev, True, True, False)
40 return stats
40 return stats
41
41
42 def rebase(ui, repo, **opts):
42 def rebase(ui, repo, **opts):
43 """move changeset (and descendants) to a different branch
43 """move changeset (and descendants) to a different branch
44
44
45 Rebase uses repeated merging to graft changesets from one part of
45 Rebase uses repeated merging to graft changesets from one part of
46 history onto another. This can be useful for linearizing local
46 history onto another. This can be useful for linearizing local
47 changes relative to a master development tree.
47 changes relative to a master development tree.
48
48
49 If a rebase is interrupted to manually resolve a merge, it can be
49 If a rebase is interrupted to manually resolve a merge, it can be
50 continued with --continue/-c or aborted with --abort/-a.
50 continued with --continue/-c or aborted with --abort/-a.
51 """
51 """
52 originalwd = target = None
52 originalwd = target = None
53 external = nullrev
53 external = nullrev
54 state = skipped = {}
54 state = skipped = {}
55
55
56 lock = wlock = None
56 lock = wlock = None
57 try:
57 try:
58 lock = repo.lock()
58 lock = repo.lock()
59 wlock = repo.wlock()
59 wlock = repo.wlock()
60
60
61 # Validate input and define rebasing points
61 # Validate input and define rebasing points
62 destf = opts.get('dest', None)
62 destf = opts.get('dest', None)
63 srcf = opts.get('source', None)
63 srcf = opts.get('source', None)
64 basef = opts.get('base', None)
64 basef = opts.get('base', None)
65 contf = opts.get('continue')
65 contf = opts.get('continue')
66 abortf = opts.get('abort')
66 abortf = opts.get('abort')
67 collapsef = opts.get('collapse', False)
67 collapsef = opts.get('collapse', False)
68 extrafn = opts.get('extrafn')
68 extrafn = opts.get('extrafn')
69 keepf = opts.get('keep', False)
69 keepf = opts.get('keep', False)
70 keepbranchesf = opts.get('keepbranches', False)
70 keepbranchesf = opts.get('keepbranches', False)
71
71
72 if contf or abortf:
72 if contf or abortf:
73 if contf and abortf:
73 if contf and abortf:
74 raise error.ParseError('rebase',
74 raise error.ParseError('rebase',
75 _('cannot use both abort and continue'))
75 _('cannot use both abort and continue'))
76 if collapsef:
76 if collapsef:
77 raise error.ParseError(
77 raise error.ParseError(
78 'rebase', _('cannot use collapse with continue or abort'))
78 'rebase', _('cannot use collapse with continue or abort'))
79
79
80 if srcf or basef or destf:
80 if srcf or basef or destf:
81 raise error.ParseError('rebase',
81 raise error.ParseError('rebase',
82 _('abort and continue do not allow specifying revisions'))
82 _('abort and continue do not allow specifying revisions'))
83
83
84 (originalwd, target, state, collapsef, keepf,
84 (originalwd, target, state, collapsef, keepf,
85 keepbranchesf, external) = restorestatus(repo)
85 keepbranchesf, external) = restorestatus(repo)
86 if abortf:
86 if abortf:
87 abort(repo, originalwd, target, state)
87 abort(repo, originalwd, target, state)
88 return
88 return
89 else:
89 else:
90 if srcf and basef:
90 if srcf and basef:
91 raise error.ParseError('rebase', _('cannot specify both a '
91 raise error.ParseError('rebase', _('cannot specify both a '
92 'revision and a base'))
92 'revision and a base'))
93 cmdutil.bail_if_changed(repo)
93 cmdutil.bail_if_changed(repo)
94 result = buildstate(repo, destf, srcf, basef, collapsef)
94 result = buildstate(repo, destf, srcf, basef, collapsef)
95 if result:
95 if result:
96 originalwd, target, state, external = result
96 originalwd, target, state, external = result
97 else: # Empty state built, nothing to rebase
97 else: # Empty state built, nothing to rebase
98 repo.ui.status(_('nothing to rebase\n'))
98 repo.ui.status(_('nothing to rebase\n'))
99 return
99 return
100
100
101 if keepbranchesf:
101 if keepbranchesf:
102 if extrafn:
102 if extrafn:
103 raise error.ParseError(
103 raise error.ParseError(
104 'rebase', _('cannot use both keepbranches and extrafn'))
104 'rebase', _('cannot use both keepbranches and extrafn'))
105 def extrafn(ctx, extra):
105 def extrafn(ctx, extra):
106 extra['branch'] = ctx.branch()
106 extra['branch'] = ctx.branch()
107
107
108 # Rebase
108 # Rebase
109 targetancestors = list(repo.changelog.ancestors(target))
109 targetancestors = list(repo.changelog.ancestors(target))
110 targetancestors.append(target)
110 targetancestors.append(target)
111
111
112 for rev in sorted(state):
112 for rev in sorted(state):
113 if state[rev] == -1:
113 if state[rev] == -1:
114 storestatus(repo, originalwd, target, state, collapsef, keepf,
114 storestatus(repo, originalwd, target, state, collapsef, keepf,
115 keepbranchesf, external)
115 keepbranchesf, external)
116 rebasenode(repo, rev, target, state, skipped, targetancestors,
116 rebasenode(repo, rev, target, state, skipped, targetancestors,
117 collapsef, extrafn)
117 collapsef, extrafn)
118 ui.note(_('rebase merging completed\n'))
118 ui.note(_('rebase merging completed\n'))
119
119
120 if collapsef:
120 if collapsef:
121 p1, p2 = defineparents(repo, min(state), target,
121 p1, p2 = defineparents(repo, min(state), target,
122 state, targetancestors)
122 state, targetancestors)
123 concludenode(repo, rev, p1, external, state, collapsef,
123 concludenode(repo, rev, p1, external, state, collapsef,
124 last=True, skipped=skipped, extrafn=extrafn)
124 last=True, skipped=skipped, extrafn=extrafn)
125
125
126 if 'qtip' in repo.tags():
126 if 'qtip' in repo.tags():
127 updatemq(repo, state, skipped, **opts)
127 updatemq(repo, state, skipped, **opts)
128
128
129 if not keepf:
129 if not keepf:
130 # Remove no more useful revisions
130 # Remove no more useful revisions
131 if set(repo.changelog.descendants(min(state))) - set(state):
131 if set(repo.changelog.descendants(min(state))) - set(state):
132 ui.warn(_("warning: new changesets detected on source branch, "
132 ui.warn(_("warning: new changesets detected on source branch, "
133 "not stripping\n"))
133 "not stripping\n"))
134 else:
134 else:
135 repair.strip(repo.ui, repo, repo[min(state)].node(), "strip")
135 repair.strip(repo.ui, repo, repo[min(state)].node(), "strip")
136
136
137 clearstatus(repo)
137 clearstatus(repo)
138 ui.status(_("rebase completed\n"))
138 ui.status(_("rebase completed\n"))
139 if os.path.exists(repo.sjoin('undo')):
139 if os.path.exists(repo.sjoin('undo')):
140 util.unlink(repo.sjoin('undo'))
140 util.unlink(repo.sjoin('undo'))
141 if skipped:
141 if skipped:
142 ui.note(_("%d revisions have been skipped\n") % len(skipped))
142 ui.note(_("%d revisions have been skipped\n") % len(skipped))
143 finally:
143 finally:
144 release(lock, wlock)
144 release(lock, wlock)
145
145
146 def concludenode(repo, rev, p1, p2, state, collapse, last=False, skipped={},
146 def concludenode(repo, rev, p1, p2, state, collapse, last=False, skipped={},
147 extrafn=None):
147 extrafn=None):
148 """Skip commit if collapsing has been required and rev is not the last
148 """Skip commit if collapsing has been required and rev is not the last
149 revision, commit otherwise
149 revision, commit otherwise
150 """
150 """
151 repo.ui.debug(_(" set parents\n"))
151 repo.ui.debug(_(" set parents\n"))
152 if collapse and not last:
152 if collapse and not last:
153 repo.dirstate.setparents(repo[p1].node())
153 repo.dirstate.setparents(repo[p1].node())
154 return None
154 return None
155
155
156 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
156 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
157
157
158 # Commit, record the old nodeid
158 # Commit, record the old nodeid
159 m, a, r = repo.status()[:3]
159 m, a, r = repo.status()[:3]
160 newrev = nullrev
160 newrev = nullrev
161 try:
161 try:
162 if last:
162 if last:
163 commitmsg = 'Collapsed revision'
163 commitmsg = 'Collapsed revision'
164 for rebased in state:
164 for rebased in state:
165 if rebased not in skipped:
165 if rebased not in skipped:
166 commitmsg += '\n* %s' % repo[rebased].description()
166 commitmsg += '\n* %s' % repo[rebased].description()
167 commitmsg = repo.ui.edit(commitmsg, repo.ui.username())
167 commitmsg = repo.ui.edit(commitmsg, repo.ui.username())
168 else:
168 else:
169 commitmsg = repo[rev].description()
169 commitmsg = repo[rev].description()
170 # Commit might fail if unresolved files exist
170 # Commit might fail if unresolved files exist
171 extra = {'rebase_source': repo[rev].hex()}
171 extra = {'rebase_source': repo[rev].hex()}
172 if extrafn:
172 if extrafn:
173 extrafn(repo[rev], extra)
173 extrafn(repo[rev], extra)
174 newrev = repo.commit(m+a+r,
174 newrev = repo.commit(m+a+r,
175 text=commitmsg,
175 text=commitmsg,
176 user=repo[rev].user(),
176 user=repo[rev].user(),
177 date=repo[rev].date(),
177 date=repo[rev].date(),
178 extra=extra)
178 extra=extra)
179 return newrev
179 return newrev
180 except util.Abort:
180 except util.Abort:
181 # Invalidate the previous setparents
181 # Invalidate the previous setparents
182 repo.dirstate.invalidate()
182 repo.dirstate.invalidate()
183 raise
183 raise
184
184
185 def rebasenode(repo, rev, target, state, skipped, targetancestors, collapse,
185 def rebasenode(repo, rev, target, state, skipped, targetancestors, collapse,
186 extrafn):
186 extrafn):
187 'Rebase a single revision'
187 'Rebase a single revision'
188 repo.ui.debug(_("rebasing %d:%s\n") % (rev, repo[rev]))
188 repo.ui.debug(_("rebasing %d:%s\n") % (rev, repo[rev]))
189
189
190 p1, p2 = defineparents(repo, rev, target, state, targetancestors)
190 p1, p2 = defineparents(repo, rev, target, state, targetancestors)
191
191
192 repo.ui.debug(_(" future parents are %d and %d\n") % (repo[p1].rev(),
192 repo.ui.debug(_(" future parents are %d and %d\n") % (repo[p1].rev(),
193 repo[p2].rev()))
193 repo[p2].rev()))
194
194
195 # Merge phase
195 # Merge phase
196 if len(repo.parents()) != 2:
196 if len(repo.parents()) != 2:
197 # Update to target and merge it with local
197 # Update to target and merge it with local
198 if repo['.'].rev() != repo[p1].rev():
198 if repo['.'].rev() != repo[p1].rev():
199 repo.ui.debug(_(" update to %d:%s\n") % (repo[p1].rev(), repo[p1]))
199 repo.ui.debug(_(" update to %d:%s\n") % (repo[p1].rev(), repo[p1]))
200 merge.update(repo, p1, False, True, False)
200 merge.update(repo, p1, False, True, False)
201 else:
201 else:
202 repo.ui.debug(_(" already in target\n"))
202 repo.ui.debug(_(" already in target\n"))
203 repo.dirstate.write()
203 repo.dirstate.write()
204 repo.ui.debug(_(" merge against %d:%s\n") % (repo[rev].rev(), repo[rev]))
204 repo.ui.debug(_(" merge against %d:%s\n") % (repo[rev].rev(), repo[rev]))
205 first = repo[rev].rev() == repo[min(state)].rev()
205 first = repo[rev].rev() == repo[min(state)].rev()
206 stats = rebasemerge(repo, rev, first)
206 stats = rebasemerge(repo, rev, first)
207
207
208 if stats[3] > 0:
208 if stats[3] > 0:
209 raise util.Abort(_('fix unresolved conflicts with hg resolve then '
209 raise util.Abort(_('fix unresolved conflicts with hg resolve then '
210 'run hg rebase --continue'))
210 'run hg rebase --continue'))
211 else: # we have an interrupted rebase
211 else: # we have an interrupted rebase
212 repo.ui.debug(_('resuming interrupted rebase\n'))
212 repo.ui.debug(_('resuming interrupted rebase\n'))
213
213
214 # Keep track of renamed files in the revision that is going to be rebased
214 # Keep track of renamed files in the revision that is going to be rebased
215 # Here we simulate the copies and renames in the source changeset
215 # Here we simulate the copies and renames in the source changeset
216 cop, diver = copies.copies(repo, repo[rev], repo[target], repo[p2], True)
216 cop, diver = copies.copies(repo, repo[rev], repo[target], repo[p2], True)
217 m1 = repo[rev].manifest()
217 m1 = repo[rev].manifest()
218 m2 = repo[target].manifest()
218 m2 = repo[target].manifest()
219 for k, v in cop.iteritems():
219 for k, v in cop.iteritems():
220 if k in m1:
220 if k in m1:
221 if v in m1 or v in m2:
221 if v in m1 or v in m2:
222 repo.dirstate.copy(v, k)
222 repo.dirstate.copy(v, k)
223 if v in m2 and v not in m1:
223 if v in m2 and v not in m1:
224 repo.dirstate.remove(v)
224 repo.dirstate.remove(v)
225
225
226 newrev = concludenode(repo, rev, p1, p2, state, collapse,
226 newrev = concludenode(repo, rev, p1, p2, state, collapse,
227 extrafn=extrafn)
227 extrafn=extrafn)
228
228
229 # Update the state
229 # Update the state
230 if newrev is not None:
230 if newrev is not None:
231 state[rev] = repo[newrev].rev()
231 state[rev] = repo[newrev].rev()
232 else:
232 else:
233 if not collapse:
233 if not collapse:
234 repo.ui.note(_('no changes, revision %d skipped\n') % rev)
234 repo.ui.note(_('no changes, revision %d skipped\n') % rev)
235 repo.ui.debug(_('next revision set to %s\n') % p1)
235 repo.ui.debug(_('next revision set to %s\n') % p1)
236 skipped[rev] = True
236 skipped[rev] = True
237 state[rev] = p1
237 state[rev] = p1
238
238
239 def defineparents(repo, rev, target, state, targetancestors):
239 def defineparents(repo, rev, target, state, targetancestors):
240 'Return the new parent relationship of the revision that will be rebased'
240 'Return the new parent relationship of the revision that will be rebased'
241 parents = repo[rev].parents()
241 parents = repo[rev].parents()
242 p1 = p2 = nullrev
242 p1 = p2 = nullrev
243
243
244 P1n = parents[0].rev()
244 P1n = parents[0].rev()
245 if P1n in targetancestors:
245 if P1n in targetancestors:
246 p1 = target
246 p1 = target
247 elif P1n in state:
247 elif P1n in state:
248 p1 = state[P1n]
248 p1 = state[P1n]
249 else: # P1n external
249 else: # P1n external
250 p1 = target
250 p1 = target
251 p2 = P1n
251 p2 = P1n
252
252
253 if len(parents) == 2 and parents[1].rev() not in targetancestors:
253 if len(parents) == 2 and parents[1].rev() not in targetancestors:
254 P2n = parents[1].rev()
254 P2n = parents[1].rev()
255 # interesting second parent
255 # interesting second parent
256 if P2n in state:
256 if P2n in state:
257 if p1 == target: # P1n in targetancestors or external
257 if p1 == target: # P1n in targetancestors or external
258 p1 = state[P2n]
258 p1 = state[P2n]
259 else:
259 else:
260 p2 = state[P2n]
260 p2 = state[P2n]
261 else: # P2n external
261 else: # P2n external
262 if p2 != nullrev: # P1n external too => rev is a merged revision
262 if p2 != nullrev: # P1n external too => rev is a merged revision
263 raise util.Abort(_('cannot use revision %d as base, result '
263 raise util.Abort(_('cannot use revision %d as base, result '
264 'would have 3 parents') % rev)
264 'would have 3 parents') % rev)
265 p2 = P2n
265 p2 = P2n
266 return p1, p2
266 return p1, p2
267
267
268 def isagitpatch(repo, patchname):
268 def isagitpatch(repo, patchname):
269 'Return true if the given patch is in git format'
269 'Return true if the given patch is in git format'
270 mqpatch = os.path.join(repo.mq.path, patchname)
270 mqpatch = os.path.join(repo.mq.path, patchname)
271 for line in patch.linereader(file(mqpatch, 'rb')):
271 for line in patch.linereader(file(mqpatch, 'rb')):
272 if line.startswith('diff --git'):
272 if line.startswith('diff --git'):
273 return True
273 return True
274 return False
274 return False
275
275
276 def updatemq(repo, state, skipped, **opts):
276 def updatemq(repo, state, skipped, **opts):
277 'Update rebased mq patches - finalize and then import them'
277 'Update rebased mq patches - finalize and then import them'
278 mqrebase = {}
278 mqrebase = {}
279 for p in repo.mq.applied:
279 for p in repo.mq.applied:
280 if repo[p.rev].rev() in state:
280 if repo[p.rev].rev() in state:
281 repo.ui.debug(_('revision %d is an mq patch (%s), finalize it.\n') %
281 repo.ui.debug(_('revision %d is an mq patch (%s), finalize it.\n') %
282 (repo[p.rev].rev(), p.name))
282 (repo[p.rev].rev(), p.name))
283 mqrebase[repo[p.rev].rev()] = (p.name, isagitpatch(repo, p.name))
283 mqrebase[repo[p.rev].rev()] = (p.name, isagitpatch(repo, p.name))
284
284
285 if mqrebase:
285 if mqrebase:
286 repo.mq.finish(repo, mqrebase.keys())
286 repo.mq.finish(repo, mqrebase.keys())
287
287
288 # We must start import from the newest revision
288 # We must start import from the newest revision
289 for rev in sorted(mqrebase, reverse=True):
289 for rev in sorted(mqrebase, reverse=True):
290 if rev not in skipped:
290 if rev not in skipped:
291 repo.ui.debug(_('import mq patch %d (%s)\n')
291 repo.ui.debug(_('import mq patch %d (%s)\n')
292 % (state[rev], mqrebase[rev][0]))
292 % (state[rev], mqrebase[rev][0]))
293 repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
293 repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
294 git=mqrebase[rev][1],rev=[str(state[rev])])
294 git=mqrebase[rev][1],rev=[str(state[rev])])
295 repo.mq.save_dirty()
295 repo.mq.save_dirty()
296
296
297 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
297 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
298 external):
298 external):
299 'Store the current status to allow recovery'
299 'Store the current status to allow recovery'
300 f = repo.opener("rebasestate", "w")
300 f = repo.opener("rebasestate", "w")
301 f.write(repo[originalwd].hex() + '\n')
301 f.write(repo[originalwd].hex() + '\n')
302 f.write(repo[target].hex() + '\n')
302 f.write(repo[target].hex() + '\n')
303 f.write(repo[external].hex() + '\n')
303 f.write(repo[external].hex() + '\n')
304 f.write('%d\n' % int(collapse))
304 f.write('%d\n' % int(collapse))
305 f.write('%d\n' % int(keep))
305 f.write('%d\n' % int(keep))
306 f.write('%d\n' % int(keepbranches))
306 f.write('%d\n' % int(keepbranches))
307 for d, v in state.iteritems():
307 for d, v in state.iteritems():
308 oldrev = repo[d].hex()
308 oldrev = repo[d].hex()
309 newrev = repo[v].hex()
309 newrev = repo[v].hex()
310 f.write("%s:%s\n" % (oldrev, newrev))
310 f.write("%s:%s\n" % (oldrev, newrev))
311 f.close()
311 f.close()
312 repo.ui.debug(_('rebase status stored\n'))
312 repo.ui.debug(_('rebase status stored\n'))
313
313
314 def clearstatus(repo):
314 def clearstatus(repo):
315 'Remove the status files'
315 'Remove the status files'
316 if os.path.exists(repo.join("rebasestate")):
316 if os.path.exists(repo.join("rebasestate")):
317 util.unlink(repo.join("rebasestate"))
317 util.unlink(repo.join("rebasestate"))
318
318
319 def restorestatus(repo):
319 def restorestatus(repo):
320 'Restore a previously stored status'
320 'Restore a previously stored status'
321 try:
321 try:
322 target = None
322 target = None
323 collapse = False
323 collapse = False
324 external = nullrev
324 external = nullrev
325 state = {}
325 state = {}
326 f = repo.opener("rebasestate")
326 f = repo.opener("rebasestate")
327 for i, l in enumerate(f.read().splitlines()):
327 for i, l in enumerate(f.read().splitlines()):
328 if i == 0:
328 if i == 0:
329 originalwd = repo[l].rev()
329 originalwd = repo[l].rev()
330 elif i == 1:
330 elif i == 1:
331 target = repo[l].rev()
331 target = repo[l].rev()
332 elif i == 2:
332 elif i == 2:
333 external = repo[l].rev()
333 external = repo[l].rev()
334 elif i == 3:
334 elif i == 3:
335 collapse = bool(int(l))
335 collapse = bool(int(l))
336 elif i == 4:
336 elif i == 4:
337 keep = bool(int(l))
337 keep = bool(int(l))
338 elif i == 5:
338 elif i == 5:
339 keepbranches = bool(int(l))
339 keepbranches = bool(int(l))
340 else:
340 else:
341 oldrev, newrev = l.split(':')
341 oldrev, newrev = l.split(':')
342 state[repo[oldrev].rev()] = repo[newrev].rev()
342 state[repo[oldrev].rev()] = repo[newrev].rev()
343 repo.ui.debug(_('rebase status resumed\n'))
343 repo.ui.debug(_('rebase status resumed\n'))
344 return originalwd, target, state, collapse, keep, keepbranches, external
344 return originalwd, target, state, collapse, keep, keepbranches, external
345 except IOError, err:
345 except IOError, err:
346 if err.errno != errno.ENOENT:
346 if err.errno != errno.ENOENT:
347 raise
347 raise
348 raise util.Abort(_('no rebase in progress'))
348 raise util.Abort(_('no rebase in progress'))
349
349
350 def abort(repo, originalwd, target, state):
350 def abort(repo, originalwd, target, state):
351 'Restore the repository to its original state'
351 'Restore the repository to its original state'
352 if set(repo.changelog.descendants(target)) - set(state.values()):
352 if set(repo.changelog.descendants(target)) - set(state.values()):
353 repo.ui.warn(_("warning: new changesets detected on target branch, "
353 repo.ui.warn(_("warning: new changesets detected on target branch, "
354 "not stripping\n"))
354 "not stripping\n"))
355 else:
355 else:
356 # Strip from the first rebased revision
356 # Strip from the first rebased revision
357 merge.update(repo, repo[originalwd].rev(), False, True, False)
357 merge.update(repo, repo[originalwd].rev(), False, True, False)
358 rebased = filter(lambda x: x > -1, state.values())
358 rebased = filter(lambda x: x > -1, state.values())
359 if rebased:
359 if rebased:
360 strippoint = min(rebased)
360 strippoint = min(rebased)
361 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
361 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
362 clearstatus(repo)
362 clearstatus(repo)
363 repo.ui.status(_('rebase aborted\n'))
363 repo.ui.status(_('rebase aborted\n'))
364
364
365 def buildstate(repo, dest, src, base, collapse):
365 def buildstate(repo, dest, src, base, collapse):
366 'Define which revisions are going to be rebased and where'
366 'Define which revisions are going to be rebased and where'
367 targetancestors = set()
367 targetancestors = set()
368
368
369 if not dest:
369 if not dest:
370 # Destination defaults to the latest revision in the current branch
370 # Destination defaults to the latest revision in the current branch
371 branch = repo[None].branch()
371 branch = repo[None].branch()
372 dest = repo[branch].rev()
372 dest = repo[branch].rev()
373 else:
373 else:
374 if 'qtip' in repo.tags() and (repo[dest].hex() in
374 if 'qtip' in repo.tags() and (repo[dest].hex() in
375 [s.rev for s in repo.mq.applied]):
375 [s.rev for s in repo.mq.applied]):
376 raise util.Abort(_('cannot rebase onto an applied mq patch'))
376 raise util.Abort(_('cannot rebase onto an applied mq patch'))
377 dest = repo[dest].rev()
377 dest = repo[dest].rev()
378
378
379 if src:
379 if src:
380 commonbase = repo[src].ancestor(repo[dest])
380 commonbase = repo[src].ancestor(repo[dest])
381 if commonbase == repo[src]:
381 if commonbase == repo[src]:
382 raise util.Abort(_('cannot rebase an ancestor'))
382 raise util.Abort(_('cannot rebase an ancestor'))
383 if commonbase == repo[dest]:
383 if commonbase == repo[dest]:
384 raise util.Abort(_('cannot rebase a descendant'))
384 raise util.Abort(_('cannot rebase a descendant'))
385 source = repo[src].rev()
385 source = repo[src].rev()
386 else:
386 else:
387 if base:
387 if base:
388 cwd = repo[base].rev()
388 cwd = repo[base].rev()
389 else:
389 else:
390 cwd = repo['.'].rev()
390 cwd = repo['.'].rev()
391
391
392 if cwd == dest:
392 if cwd == dest:
393 repo.ui.debug(_('already working on current\n'))
393 repo.ui.debug(_('already working on current\n'))
394 return None
394 return None
395
395
396 targetancestors = set(repo.changelog.ancestors(dest))
396 targetancestors = set(repo.changelog.ancestors(dest))
397 if cwd in targetancestors:
397 if cwd in targetancestors:
398 repo.ui.debug(_('already working on the current branch\n'))
398 repo.ui.debug(_('already working on the current branch\n'))
399 return None
399 return None
400
400
401 cwdancestors = set(repo.changelog.ancestors(cwd))
401 cwdancestors = set(repo.changelog.ancestors(cwd))
402 cwdancestors.add(cwd)
402 cwdancestors.add(cwd)
403 rebasingbranch = cwdancestors - targetancestors
403 rebasingbranch = cwdancestors - targetancestors
404 source = min(rebasingbranch)
404 source = min(rebasingbranch)
405
405
406 repo.ui.debug(_('rebase onto %d starting from %d\n') % (dest, source))
406 repo.ui.debug(_('rebase onto %d starting from %d\n') % (dest, source))
407 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
407 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
408 external = nullrev
408 external = nullrev
409 if collapse:
409 if collapse:
410 if not targetancestors:
410 if not targetancestors:
411 targetancestors = set(repo.changelog.ancestors(dest))
411 targetancestors = set(repo.changelog.ancestors(dest))
412 for rev in state:
412 for rev in state:
413 # Check externals and fail if there are more than one
413 # Check externals and fail if there are more than one
414 for p in repo[rev].parents():
414 for p in repo[rev].parents():
415 if (p.rev() not in state and p.rev() != source
415 if (p.rev() not in state and p.rev() != source
416 and p.rev() not in targetancestors):
416 and p.rev() not in targetancestors):
417 if external != nullrev:
417 if external != nullrev:
418 raise util.Abort(_('unable to collapse, there is more '
418 raise util.Abort(_('unable to collapse, there is more '
419 'than one external parent'))
419 'than one external parent'))
420 external = p.rev()
420 external = p.rev()
421
421
422 state[source] = nullrev
422 state[source] = nullrev
423 return repo['.'].rev(), repo[dest].rev(), state, external
423 return repo['.'].rev(), repo[dest].rev(), state, external
424
424
425 def pullrebase(orig, ui, repo, *args, **opts):
425 def pullrebase(orig, ui, repo, *args, **opts):
426 'Call rebase after pull if the latter has been invoked with --rebase'
426 'Call rebase after pull if the latter has been invoked with --rebase'
427 if opts.get('rebase'):
427 if opts.get('rebase'):
428 if opts.get('update'):
428 if opts.get('update'):
429 del opts.get['update']
429 del opts.get['update']
430 ui.debug(_('--update and --rebase are not compatible, ignoring '
430 ui.debug(_('--update and --rebase are not compatible, ignoring '
431 'the update flag\n'))
431 'the update flag\n'))
432
432
433 cmdutil.bail_if_changed(repo)
433 cmdutil.bail_if_changed(repo)
434 revsprepull = len(repo)
434 revsprepull = len(repo)
435 orig(ui, repo, *args, **opts)
435 orig(ui, repo, *args, **opts)
436 revspostpull = len(repo)
436 revspostpull = len(repo)
437 if revspostpull > revsprepull:
437 if revspostpull > revsprepull:
438 rebase(ui, repo, **opts)
438 rebase(ui, repo, **opts)
439 branch = repo[None].branch()
439 branch = repo[None].branch()
440 dest = repo[branch].rev()
440 dest = repo[branch].rev()
441 if dest != repo['.'].rev():
441 if dest != repo['.'].rev():
442 # there was nothing to rebase we force an update
442 # there was nothing to rebase we force an update
443 merge.update(repo, dest, False, False, False)
443 merge.update(repo, dest, False, False, False)
444 else:
444 else:
445 orig(ui, repo, *args, **opts)
445 orig(ui, repo, *args, **opts)
446
446
447 def uisetup(ui):
447 def uisetup(ui):
448 'Replace pull with a decorator to provide --rebase option'
448 'Replace pull with a decorator to provide --rebase option'
449 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
449 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
450 entry[1].append(('', 'rebase', None,
450 entry[1].append(('', 'rebase', None,
451 _("rebase working directory to branch head"))
451 _("rebase working directory to branch head"))
452 )
452 )
453
453
454 cmdtable = {
454 cmdtable = {
455 "rebase":
455 "rebase":
456 (rebase,
456 (rebase,
457 [
457 [
458 ('s', 'source', '', _('rebase from a given revision')),
458 ('s', 'source', '', _('rebase from a given revision')),
459 ('b', 'base', '', _('rebase from the base of a given revision')),
459 ('b', 'base', '', _('rebase from the base of a given revision')),
460 ('d', 'dest', '', _('rebase onto a given revision')),
460 ('d', 'dest', '', _('rebase onto a given revision')),
461 ('', 'collapse', False, _('collapse the rebased revisions')),
461 ('', 'collapse', False, _('collapse the rebased revisions')),
462 ('', 'keep', False, _('keep original revisions')),
462 ('', 'keep', False, _('keep original revisions')),
463 ('', 'keepbranches', False, _('keep original branches')),
463 ('', 'keepbranches', False, _('keep original branches')),
464 ('c', 'continue', False, _('continue an interrupted rebase')),
464 ('c', 'continue', False, _('continue an interrupted rebase')),
465 ('a', 'abort', False, _('abort an interrupted rebase')),] +
465 ('a', 'abort', False, _('abort an interrupted rebase')),] +
466 templateopts,
466 templateopts,
467 _('hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--keep] '
467 _('hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--keep] '
468 '[--keepbranches] | [-c] | [-a]')),
468 '[--keepbranches] | [-c] | [-a]')),
469 }
469 }
@@ -1,125 +1,125 b''
1 from i18n import _
1 from i18n import _
2 import re, error, os
2 import re, error, os
3
3
4 class sortdict(dict):
4 class sortdict(dict):
5 'a simple sorted dictionary'
5 'a simple sorted dictionary'
6 def __init__(self, data=None):
6 def __init__(self, data=None):
7 self._list = []
7 self._list = []
8 if data:
8 if data:
9 self.update(data)
9 self.update(data)
10 def copy(self):
10 def copy(self):
11 return sortdict(self)
11 return sortdict(self)
12 def __setitem__(self, key, val):
12 def __setitem__(self, key, val):
13 if key in self:
13 if key in self:
14 self._list.remove(key)
14 self._list.remove(key)
15 self._list.append(key)
15 self._list.append(key)
16 dict.__setitem__(self, key, val)
16 dict.__setitem__(self, key, val)
17 def __iter__(self):
17 def __iter__(self):
18 return self._list.__iter__()
18 return self._list.__iter__()
19 def update(self, src):
19 def update(self, src):
20 for k in src:
20 for k in src:
21 self[k] = src[k]
21 self[k] = src[k]
22 def items(self):
22 def items(self):
23 return [(k,self[k]) for k in self._list]
23 return [(k, self[k]) for k in self._list]
24 def __delitem__(self, key):
24 def __delitem__(self, key):
25 dict.__delitem__(self, key)
25 dict.__delitem__(self, key)
26 self._list.remove(key)
26 self._list.remove(key)
27
27
28 class config(object):
28 class config(object):
29 def __init__(self, data=None):
29 def __init__(self, data=None):
30 self._data = {}
30 self._data = {}
31 self._source = {}
31 self._source = {}
32 if data:
32 if data:
33 for k in data._data:
33 for k in data._data:
34 self._data[k] = data[k].copy()
34 self._data[k] = data[k].copy()
35 self._source = data._source.copy()
35 self._source = data._source.copy()
36 def copy(self):
36 def copy(self):
37 return config(self)
37 return config(self)
38 def __contains__(self, section):
38 def __contains__(self, section):
39 return section in self._data
39 return section in self._data
40 def __getitem__(self, section):
40 def __getitem__(self, section):
41 return self._data.get(section, {})
41 return self._data.get(section, {})
42 def __iter__(self):
42 def __iter__(self):
43 for d in self.sections():
43 for d in self.sections():
44 yield d
44 yield d
45 def update(self, src):
45 def update(self, src):
46 for s in src:
46 for s in src:
47 if s not in self:
47 if s not in self:
48 self._data[s] = sortdict()
48 self._data[s] = sortdict()
49 self._data[s].update(src._data[s])
49 self._data[s].update(src._data[s])
50 self._source.update(src._source)
50 self._source.update(src._source)
51 def get(self, section, item, default=None):
51 def get(self, section, item, default=None):
52 return self._data.get(section, {}).get(item, default)
52 return self._data.get(section, {}).get(item, default)
53 def source(self, section, item):
53 def source(self, section, item):
54 return self._source.get((section, item), "")
54 return self._source.get((section, item), "")
55 def sections(self):
55 def sections(self):
56 return sorted(self._data.keys())
56 return sorted(self._data.keys())
57 def items(self, section):
57 def items(self, section):
58 return self._data.get(section, {}).items()
58 return self._data.get(section, {}).items()
59 def set(self, section, item, value, source=""):
59 def set(self, section, item, value, source=""):
60 if section not in self:
60 if section not in self:
61 self._data[section] = sortdict()
61 self._data[section] = sortdict()
62 self._data[section][item] = value
62 self._data[section][item] = value
63 self._source[(section, item)] = source
63 self._source[(section, item)] = source
64
64
65 def read(self, path, fp=None, sections=None):
65 def read(self, path, fp=None, sections=None):
66 sectionre = re.compile(r'\[([^\[]+)\]')
66 sectionre = re.compile(r'\[([^\[]+)\]')
67 itemre = re.compile(r'([^=\s]+)\s*=\s*(.*\S|)')
67 itemre = re.compile(r'([^=\s]+)\s*=\s*(.*\S|)')
68 contre = re.compile(r'\s+(\S.*\S)')
68 contre = re.compile(r'\s+(\S.*\S)')
69 emptyre = re.compile(r'(;|#|\s*$)')
69 emptyre = re.compile(r'(;|#|\s*$)')
70 unsetre = re.compile(r'%unset\s+(\S+)')
70 unsetre = re.compile(r'%unset\s+(\S+)')
71 includere = re.compile(r'%include\s+(\S.*\S)')
71 includere = re.compile(r'%include\s+(\S.*\S)')
72 section = ""
72 section = ""
73 item = None
73 item = None
74 line = 0
74 line = 0
75 cont = 0
75 cont = 0
76
76
77 if not fp:
77 if not fp:
78 fp = open(path)
78 fp = open(path)
79
79
80 for l in fp:
80 for l in fp:
81 line += 1
81 line += 1
82 if cont:
82 if cont:
83 m = contre.match(l)
83 m = contre.match(l)
84 if m:
84 if m:
85 if sections and section not in sections:
85 if sections and section not in sections:
86 continue
86 continue
87 v = self.get(section, item) + "\n" + m.group(1)
87 v = self.get(section, item) + "\n" + m.group(1)
88 self.set(section, item, v, "%s:%d" % (path, line))
88 self.set(section, item, v, "%s:%d" % (path, line))
89 continue
89 continue
90 item = None
90 item = None
91 m = includere.match(l)
91 m = includere.match(l)
92 if m:
92 if m:
93 inc = m.group(1)
93 inc = m.group(1)
94 base = os.path.dirname(path)
94 base = os.path.dirname(path)
95 inc = os.path.normpath(os.path.join(base, inc))
95 inc = os.path.normpath(os.path.join(base, inc))
96 incfp = open(inc)
96 incfp = open(inc)
97 self.read(inc, incfp)
97 self.read(inc, incfp)
98 continue
98 continue
99 if emptyre.match(l):
99 if emptyre.match(l):
100 continue
100 continue
101 m = sectionre.match(l)
101 m = sectionre.match(l)
102 if m:
102 if m:
103 section = m.group(1)
103 section = m.group(1)
104 if section not in self:
104 if section not in self:
105 self._data[section] = sortdict()
105 self._data[section] = sortdict()
106 continue
106 continue
107 m = itemre.match(l)
107 m = itemre.match(l)
108 if m:
108 if m:
109 item = m.group(1)
109 item = m.group(1)
110 cont = 1
110 cont = 1
111 if sections and section not in sections:
111 if sections and section not in sections:
112 continue
112 continue
113 self.set(section, item, m.group(2), "%s:%d" % (path, line))
113 self.set(section, item, m.group(2), "%s:%d" % (path, line))
114 continue
114 continue
115 m = unsetre.match(l)
115 m = unsetre.match(l)
116 if m:
116 if m:
117 name = m.group(1)
117 name = m.group(1)
118 if sections and section not in sections:
118 if sections and section not in sections:
119 continue
119 continue
120 if self.get(section, name) != None:
120 if self.get(section, name) != None:
121 del self._data[section][name]
121 del self._data[section][name]
122 continue
122 continue
123
123
124 raise error.ConfigError(_('config error at %s:%d: \'%s\'')
124 raise error.ConfigError(_('config error at %s:%d: \'%s\'')
125 % (path, line, l.rstrip()))
125 % (path, line, l.rstrip()))
@@ -1,340 +1,341 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
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 i18n import _
8 from i18n import _
9 import errno, getpass, os, re, socket, sys, tempfile
9 import errno, getpass, os, re, socket, sys, tempfile
10 import config, traceback, util, error
10 import config, traceback, util, error
11
11
12 _booleans = {'1':True, 'yes':True, 'true':True, 'on':True,
12 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True,
13 '0':False, 'no':False, 'false':False, 'off':False}
13 '0': False, 'no': False, 'false': False, 'off': False}
14
14
15 class ui(object):
15 class ui(object):
16 def __init__(self, src=None):
16 def __init__(self, src=None):
17 self._buffers = []
17 self._buffers = []
18 self.quiet = self.verbose = self.debugflag = self._traceback = False
18 self.quiet = self.verbose = self.debugflag = self._traceback = False
19 self._reportuntrusted = True
19 self._reportuntrusted = True
20 self._ocfg = config.config() # overlay
20 self._ocfg = config.config() # overlay
21 self._tcfg = config.config() # trusted
21 self._tcfg = config.config() # trusted
22 self._ucfg = config.config() # untrusted
22 self._ucfg = config.config() # untrusted
23 self._trustusers = {}
23 self._trustusers = {}
24 self._trustgroups = {}
24 self._trustgroups = {}
25
25
26 if src:
26 if src:
27 self._tcfg = src._tcfg.copy()
27 self._tcfg = src._tcfg.copy()
28 self._ucfg = src._ucfg.copy()
28 self._ucfg = src._ucfg.copy()
29 self._ocfg = src._ocfg.copy()
29 self._ocfg = src._ocfg.copy()
30 self._trustusers = src._trustusers.copy()
30 self._trustusers = src._trustusers.copy()
31 self._trustgroups = src._trustgroups.copy()
31 self._trustgroups = src._trustgroups.copy()
32 self.fixconfig()
32 self.fixconfig()
33 else:
33 else:
34 # we always trust global config files
34 # we always trust global config files
35 for f in util.rcpath():
35 for f in util.rcpath():
36 self.readconfig(f, trust=True)
36 self.readconfig(f, trust=True)
37
37 def copy(self):
38 def copy(self):
38 return self.__class__(self)
39 return self.__class__(self)
39
40
40 def _is_trusted(self, fp, f):
41 def _is_trusted(self, fp, f):
41 st = util.fstat(fp)
42 st = util.fstat(fp)
42 if util.isowner(fp, st):
43 if util.isowner(fp, st):
43 return True
44 return True
44
45
45 tusers, tgroups = self._trustusers, self._trustgroups
46 tusers, tgroups = self._trustusers, self._trustgroups
46 if '*' in tusers or '*' in tgroups:
47 if '*' in tusers or '*' in tgroups:
47 return True
48 return True
48
49
49 user = util.username(st.st_uid)
50 user = util.username(st.st_uid)
50 group = util.groupname(st.st_gid)
51 group = util.groupname(st.st_gid)
51 if user in tusers or group in tgroups or user == util.username():
52 if user in tusers or group in tgroups or user == util.username():
52 return True
53 return True
53
54
54 if self._reportuntrusted:
55 if self._reportuntrusted:
55 self.warn(_('Not trusting file %s from untrusted '
56 self.warn(_('Not trusting file %s from untrusted '
56 'user %s, group %s\n') % (f, user, group))
57 'user %s, group %s\n') % (f, user, group))
57 return False
58 return False
58
59
59 def readconfig(self, filename, root=None, trust=False,
60 def readconfig(self, filename, root=None, trust=False,
60 sections = None):
61 sections=None):
61 try:
62 try:
62 fp = open(filename)
63 fp = open(filename)
63 except IOError:
64 except IOError:
64 if not sections: # ignore unless we were looking for something
65 if not sections: # ignore unless we were looking for something
65 return
66 return
66 raise
67 raise
67
68
68 cfg = config.config()
69 cfg = config.config()
69 trusted = sections or trust or self._is_trusted(fp, filename)
70 trusted = sections or trust or self._is_trusted(fp, filename)
70
71
71 try:
72 try:
72 cfg.read(filename, fp, sections=sections)
73 cfg.read(filename, fp, sections=sections)
73 except error.ConfigError, inst:
74 except error.ConfigError, inst:
74 if trusted:
75 if trusted:
75 raise
76 raise
76 self.warn(_("Ignored: %s\n") % str(inst))
77 self.warn(_("Ignored: %s\n") % str(inst))
77
78
78 if trusted:
79 if trusted:
79 self._tcfg.update(cfg)
80 self._tcfg.update(cfg)
80 self._tcfg.update(self._ocfg)
81 self._tcfg.update(self._ocfg)
81 self._ucfg.update(cfg)
82 self._ucfg.update(cfg)
82 self._ucfg.update(self._ocfg)
83 self._ucfg.update(self._ocfg)
83
84
84 if root is None:
85 if root is None:
85 root = os.path.expanduser('~')
86 root = os.path.expanduser('~')
86 self.fixconfig(root=root)
87 self.fixconfig(root=root)
87
88
88 def fixconfig(self, root=None):
89 def fixconfig(self, root=None):
89 # translate paths relative to root (or home) into absolute paths
90 # translate paths relative to root (or home) into absolute paths
90 root = root or os.getcwd()
91 root = root or os.getcwd()
91 for c in self._tcfg, self._ucfg, self._ocfg:
92 for c in self._tcfg, self._ucfg, self._ocfg:
92 for n, p in c.items('paths'):
93 for n, p in c.items('paths'):
93 if p and "://" not in p and not os.path.isabs(p):
94 if p and "://" not in p and not os.path.isabs(p):
94 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
95 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
95
96
96 # update ui options
97 # update ui options
97 self.debugflag = self.configbool('ui', 'debug')
98 self.debugflag = self.configbool('ui', 'debug')
98 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
99 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
99 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
100 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
100 if self.verbose and self.quiet:
101 if self.verbose and self.quiet:
101 self.quiet = self.verbose = False
102 self.quiet = self.verbose = False
102 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
103 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
103 self._traceback = self.configbool('ui', 'traceback', False)
104 self._traceback = self.configbool('ui', 'traceback', False)
104
105
105 # update trust information
106 # update trust information
106 for user in self.configlist('trusted', 'users'):
107 for user in self.configlist('trusted', 'users'):
107 self._trustusers[user] = 1
108 self._trustusers[user] = 1
108 for group in self.configlist('trusted', 'groups'):
109 for group in self.configlist('trusted', 'groups'):
109 self._trustgroups[group] = 1
110 self._trustgroups[group] = 1
110
111
111 def setconfig(self, section, name, value):
112 def setconfig(self, section, name, value):
112 for cfg in (self._ocfg, self._tcfg, self._ucfg):
113 for cfg in (self._ocfg, self._tcfg, self._ucfg):
113 cfg.set(section, name, value)
114 cfg.set(section, name, value)
114 self.fixconfig()
115 self.fixconfig()
115
116
116 def _data(self, untrusted):
117 def _data(self, untrusted):
117 return untrusted and self._ucfg or self._tcfg
118 return untrusted and self._ucfg or self._tcfg
118
119
119 def configsource(self, section, name, untrusted=False):
120 def configsource(self, section, name, untrusted=False):
120 return self._data(untrusted).source(section, name) or 'none'
121 return self._data(untrusted).source(section, name) or 'none'
121
122
122 def config(self, section, name, default=None, untrusted=False):
123 def config(self, section, name, default=None, untrusted=False):
123 value = self._data(untrusted).get(section, name, default)
124 value = self._data(untrusted).get(section, name, default)
124 if self.debugflag and not untrusted and self._reportuntrusted:
125 if self.debugflag and not untrusted and self._reportuntrusted:
125 uvalue = self._ucfg.get(section, name)
126 uvalue = self._ucfg.get(section, name)
126 if uvalue is not None and uvalue != value:
127 if uvalue is not None and uvalue != value:
127 self.debug(_("ignoring untrusted configuration option "
128 self.debug(_("ignoring untrusted configuration option "
128 "%s.%s = %s\n") % (section, name, uvalue))
129 "%s.%s = %s\n") % (section, name, uvalue))
129 return value
130 return value
130
131
131 def configbool(self, section, name, default=False, untrusted=False):
132 def configbool(self, section, name, default=False, untrusted=False):
132 v = self.config(section, name, None, untrusted)
133 v = self.config(section, name, None, untrusted)
133 if v == None:
134 if v == None:
134 return default
135 return default
135 if v.lower() not in _booleans:
136 if v.lower() not in _booleans:
136 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
137 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
137 % (section, name, v))
138 % (section, name, v))
138 return _booleans[v.lower()]
139 return _booleans[v.lower()]
139
140
140 def configlist(self, section, name, default=None, untrusted=False):
141 def configlist(self, section, name, default=None, untrusted=False):
141 """Return a list of comma/space separated strings"""
142 """Return a list of comma/space separated strings"""
142 result = self.config(section, name, untrusted=untrusted)
143 result = self.config(section, name, untrusted=untrusted)
143 if result is None:
144 if result is None:
144 result = default or []
145 result = default or []
145 if isinstance(result, basestring):
146 if isinstance(result, basestring):
146 result = result.replace(",", " ").split()
147 result = result.replace(",", " ").split()
147 return result
148 return result
148
149
149 def has_section(self, section, untrusted=False):
150 def has_section(self, section, untrusted=False):
150 '''tell whether section exists in config.'''
151 '''tell whether section exists in config.'''
151 return section in self._data(untrusted)
152 return section in self._data(untrusted)
152
153
153 def configitems(self, section, untrusted=False):
154 def configitems(self, section, untrusted=False):
154 items = self._data(untrusted).items(section)
155 items = self._data(untrusted).items(section)
155 if self.debugflag and not untrusted and self._reportuntrusted:
156 if self.debugflag and not untrusted and self._reportuntrusted:
156 for k,v in self._ucfg.items(section):
157 for k, v in self._ucfg.items(section):
157 if self._tcfg.get(section, k) != v:
158 if self._tcfg.get(section, k) != v:
158 self.debug(_("ignoring untrusted configuration option "
159 self.debug(_("ignoring untrusted configuration option "
159 "%s.%s = %s\n") % (section, k, v))
160 "%s.%s = %s\n") % (section, k, v))
160 return items
161 return items
161
162
162 def walkconfig(self, untrusted=False):
163 def walkconfig(self, untrusted=False):
163 cfg = self._data(untrusted)
164 cfg = self._data(untrusted)
164 for section in cfg.sections():
165 for section in cfg.sections():
165 for name, value in self.configitems(section, untrusted):
166 for name, value in self.configitems(section, untrusted):
166 yield section, name, str(value).replace('\n', '\\n')
167 yield section, name, str(value).replace('\n', '\\n')
167
168
168 def username(self):
169 def username(self):
169 """Return default username to be used in commits.
170 """Return default username to be used in commits.
170
171
171 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
172 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
172 and stop searching if one of these is set.
173 and stop searching if one of these is set.
173 If not found and ui.askusername is True, ask the user, else use
174 If not found and ui.askusername is True, ask the user, else use
174 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
175 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
175 """
176 """
176 user = os.environ.get("HGUSER")
177 user = os.environ.get("HGUSER")
177 if user is None:
178 if user is None:
178 user = self.config("ui", "username")
179 user = self.config("ui", "username")
179 if user is None:
180 if user is None:
180 user = os.environ.get("EMAIL")
181 user = os.environ.get("EMAIL")
181 if user is None and self.configbool("ui", "askusername"):
182 if user is None and self.configbool("ui", "askusername"):
182 user = self.prompt(_("enter a commit username:"), default=None)
183 user = self.prompt(_("enter a commit username:"), default=None)
183 if user is None:
184 if user is None:
184 try:
185 try:
185 user = '%s@%s' % (util.getuser(), socket.getfqdn())
186 user = '%s@%s' % (util.getuser(), socket.getfqdn())
186 self.warn(_("No username found, using '%s' instead\n") % user)
187 self.warn(_("No username found, using '%s' instead\n") % user)
187 except KeyError:
188 except KeyError:
188 pass
189 pass
189 if not user:
190 if not user:
190 raise util.Abort(_("Please specify a username."))
191 raise util.Abort(_("Please specify a username."))
191 if "\n" in user:
192 if "\n" in user:
192 raise util.Abort(_("username %s contains a newline\n") % repr(user))
193 raise util.Abort(_("username %s contains a newline\n") % repr(user))
193 return user
194 return user
194
195
195 def shortuser(self, user):
196 def shortuser(self, user):
196 """Return a short representation of a user name or email address."""
197 """Return a short representation of a user name or email address."""
197 if not self.verbose: user = util.shortuser(user)
198 if not self.verbose: user = util.shortuser(user)
198 return user
199 return user
199
200
200 def _path(self, loc):
201 def _path(self, loc):
201 p = self.config('paths', loc)
202 p = self.config('paths', loc)
202 if p and '%%' in p:
203 if p and '%%' in p:
203 ui.warn('(deprecated \'\%\%\' in path %s=%s from %s)\n' %
204 ui.warn('(deprecated \'\%\%\' in path %s=%s from %s)\n' %
204 (loc, p, self.configsource('paths', loc)))
205 (loc, p, self.configsource('paths', loc)))
205 p = p.replace('%%', '%')
206 p = p.replace('%%', '%')
206 return p
207 return p
207
208
208 def expandpath(self, loc, default=None):
209 def expandpath(self, loc, default=None):
209 """Return repository location relative to cwd or from [paths]"""
210 """Return repository location relative to cwd or from [paths]"""
210 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
211 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
211 return loc
212 return loc
212
213
213 path = self._path(loc)
214 path = self._path(loc)
214 if not path and default is not None:
215 if not path and default is not None:
215 path = self._path(default)
216 path = self._path(default)
216 return path or loc
217 return path or loc
217
218
218 def pushbuffer(self):
219 def pushbuffer(self):
219 self._buffers.append([])
220 self._buffers.append([])
220
221
221 def popbuffer(self):
222 def popbuffer(self):
222 return "".join(self._buffers.pop())
223 return "".join(self._buffers.pop())
223
224
224 def write(self, *args):
225 def write(self, *args):
225 if self._buffers:
226 if self._buffers:
226 self._buffers[-1].extend([str(a) for a in args])
227 self._buffers[-1].extend([str(a) for a in args])
227 else:
228 else:
228 for a in args:
229 for a in args:
229 sys.stdout.write(str(a))
230 sys.stdout.write(str(a))
230
231
231 def write_err(self, *args):
232 def write_err(self, *args):
232 try:
233 try:
233 if not sys.stdout.closed: sys.stdout.flush()
234 if not sys.stdout.closed: sys.stdout.flush()
234 for a in args:
235 for a in args:
235 sys.stderr.write(str(a))
236 sys.stderr.write(str(a))
236 # stderr may be buffered under win32 when redirected to files,
237 # stderr may be buffered under win32 when redirected to files,
237 # including stdout.
238 # including stdout.
238 if not sys.stderr.closed: sys.stderr.flush()
239 if not sys.stderr.closed: sys.stderr.flush()
239 except IOError, inst:
240 except IOError, inst:
240 if inst.errno != errno.EPIPE:
241 if inst.errno != errno.EPIPE:
241 raise
242 raise
242
243
243 def flush(self):
244 def flush(self):
244 try: sys.stdout.flush()
245 try: sys.stdout.flush()
245 except: pass
246 except: pass
246 try: sys.stderr.flush()
247 try: sys.stderr.flush()
247 except: pass
248 except: pass
248
249
249 def interactive(self):
250 def interactive(self):
250 return self.configbool("ui", "interactive") or sys.stdin.isatty()
251 return self.configbool("ui", "interactive") or sys.stdin.isatty()
251
252
252 def _readline(self, prompt=''):
253 def _readline(self, prompt=''):
253 if sys.stdin.isatty():
254 if sys.stdin.isatty():
254 try:
255 try:
255 # magically add command line editing support, where
256 # magically add command line editing support, where
256 # available
257 # available
257 import readline
258 import readline
258 # force demandimport to really load the module
259 # force demandimport to really load the module
259 readline.read_history_file
260 readline.read_history_file
260 # windows sometimes raises something other than ImportError
261 # windows sometimes raises something other than ImportError
261 except Exception:
262 except Exception:
262 pass
263 pass
263 line = raw_input(prompt)
264 line = raw_input(prompt)
264 # When stdin is in binary mode on Windows, it can cause
265 # When stdin is in binary mode on Windows, it can cause
265 # raw_input() to emit an extra trailing carriage return
266 # raw_input() to emit an extra trailing carriage return
266 if os.linesep == '\r\n' and line and line[-1] == '\r':
267 if os.linesep == '\r\n' and line and line[-1] == '\r':
267 line = line[:-1]
268 line = line[:-1]
268 return line
269 return line
269
270
270 def prompt(self, msg, pat=None, default="y"):
271 def prompt(self, msg, pat=None, default="y"):
271 """Prompt user with msg, read response, and ensure it matches pat
272 """Prompt user with msg, read response, and ensure it matches pat
272
273
273 If not interactive -- the default is returned
274 If not interactive -- the default is returned
274 """
275 """
275 if not self.interactive():
276 if not self.interactive():
276 self.note(msg, ' ', default, "\n")
277 self.note(msg, ' ', default, "\n")
277 return default
278 return default
278 while True:
279 while True:
279 try:
280 try:
280 r = self._readline(msg + ' ')
281 r = self._readline(msg + ' ')
281 if not r:
282 if not r:
282 return default
283 return default
283 if not pat or re.match(pat, r):
284 if not pat or re.match(pat, r):
284 return r
285 return r
285 else:
286 else:
286 self.write(_("unrecognized response\n"))
287 self.write(_("unrecognized response\n"))
287 except EOFError:
288 except EOFError:
288 raise util.Abort(_('response expected'))
289 raise util.Abort(_('response expected'))
289
290
290 def getpass(self, prompt=None, default=None):
291 def getpass(self, prompt=None, default=None):
291 if not self.interactive(): return default
292 if not self.interactive(): return default
292 try:
293 try:
293 return getpass.getpass(prompt or _('password: '))
294 return getpass.getpass(prompt or _('password: '))
294 except EOFError:
295 except EOFError:
295 raise util.Abort(_('response expected'))
296 raise util.Abort(_('response expected'))
296 def status(self, *msg):
297 def status(self, *msg):
297 if not self.quiet: self.write(*msg)
298 if not self.quiet: self.write(*msg)
298 def warn(self, *msg):
299 def warn(self, *msg):
299 self.write_err(*msg)
300 self.write_err(*msg)
300 def note(self, *msg):
301 def note(self, *msg):
301 if self.verbose: self.write(*msg)
302 if self.verbose: self.write(*msg)
302 def debug(self, *msg):
303 def debug(self, *msg):
303 if self.debugflag: self.write(*msg)
304 if self.debugflag: self.write(*msg)
304 def edit(self, text, user):
305 def edit(self, text, user):
305 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
306 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
306 text=True)
307 text=True)
307 try:
308 try:
308 f = os.fdopen(fd, "w")
309 f = os.fdopen(fd, "w")
309 f.write(text)
310 f.write(text)
310 f.close()
311 f.close()
311
312
312 editor = self.geteditor()
313 editor = self.geteditor()
313
314
314 util.system("%s \"%s\"" % (editor, name),
315 util.system("%s \"%s\"" % (editor, name),
315 environ={'HGUSER': user},
316 environ={'HGUSER': user},
316 onerr=util.Abort, errprefix=_("edit failed"))
317 onerr=util.Abort, errprefix=_("edit failed"))
317
318
318 f = open(name)
319 f = open(name)
319 t = f.read()
320 t = f.read()
320 f.close()
321 f.close()
321 t = re.sub("(?m)^HG:.*\n", "", t)
322 t = re.sub("(?m)^HG:.*\n", "", t)
322 finally:
323 finally:
323 os.unlink(name)
324 os.unlink(name)
324
325
325 return t
326 return t
326
327
327 def traceback(self):
328 def traceback(self):
328 '''print exception traceback if traceback printing enabled.
329 '''print exception traceback if traceback printing enabled.
329 only to call in exception handler. returns true if traceback
330 only to call in exception handler. returns true if traceback
330 printed.'''
331 printed.'''
331 if self._traceback:
332 if self._traceback:
332 traceback.print_exc()
333 traceback.print_exc()
333 return self._traceback
334 return self._traceback
334
335
335 def geteditor(self):
336 def geteditor(self):
336 '''return editor to use'''
337 '''return editor to use'''
337 return (os.environ.get("HGEDITOR") or
338 return (os.environ.get("HGEDITOR") or
338 self.config("ui", "editor") or
339 self.config("ui", "editor") or
339 os.environ.get("VISUAL") or
340 os.environ.get("VISUAL") or
340 os.environ.get("EDITOR", "vi"))
341 os.environ.get("EDITOR", "vi"))
@@ -1,249 +1,249 b''
1 hg convert [OPTION]... SOURCE [DEST [REVMAP]]
1 hg convert [OPTION]... SOURCE [DEST [REVMAP]]
2
2
3 convert a foreign SCM repository to a Mercurial one.
3 convert a foreign SCM repository to a Mercurial one.
4
4
5 Accepted source formats [identifiers]:
5 Accepted source formats [identifiers]:
6 - Mercurial [hg]
6 - Mercurial [hg]
7 - CVS [cvs]
7 - CVS [cvs]
8 - Darcs [darcs]
8 - Darcs [darcs]
9 - git [git]
9 - git [git]
10 - Subversion [svn]
10 - Subversion [svn]
11 - Monotone [mtn]
11 - Monotone [mtn]
12 - GNU Arch [gnuarch]
12 - GNU Arch [gnuarch]
13 - Bazaar [bzr]
13 - Bazaar [bzr]
14 - Perforce [p4]
14 - Perforce [p4]
15
15
16 Accepted destination formats [identifiers]:
16 Accepted destination formats [identifiers]:
17 - Mercurial [hg]
17 - Mercurial [hg]
18 - Subversion [svn] (history on branches is not preserved)
18 - Subversion [svn] (history on branches is not preserved)
19
19
20 If no revision is given, all revisions will be converted.
20 If no revision is given, all revisions will be converted.
21 Otherwise, convert will only import up to the named revision
21 Otherwise, convert will only import up to the named revision
22 (given in a format understood by the source).
22 (given in a format understood by the source).
23
23
24 If no destination directory name is specified, it defaults to the
24 If no destination directory name is specified, it defaults to the
25 basename of the source with '-hg' appended. If the destination
25 basename of the source with '-hg' appended. If the destination
26 repository doesn't exist, it will be created.
26 repository doesn't exist, it will be created.
27
27
28 If <REVMAP> isn't given, it will be put in a default location
28 If <REVMAP> isn't given, it will be put in a default location
29 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file
29 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file
30 that maps each source commit ID to the destination ID for that
30 that maps each source commit ID to the destination ID for that
31 revision, like so:
31 revision, like so:
32 <source ID> <destination ID>
32 <source ID> <destination ID>
33
33
34 If the file doesn't exist, it's automatically created. It's
34 If the file doesn't exist, it's automatically created. It's
35 updated on each commit copied, so convert-repo can be interrupted
35 updated on each commit copied, so convert-repo can be interrupted
36 and can be run repeatedly to copy new commits.
36 and can be run repeatedly to copy new commits.
37
37
38 The [username mapping] file is a simple text file that maps each
38 The [username mapping] file is a simple text file that maps each
39 source commit author to a destination commit author. It is handy
39 source commit author to a destination commit author. It is handy
40 for source SCMs that use unix logins to identify authors (eg:
40 for source SCMs that use unix logins to identify authors (eg:
41 CVS). One line per author mapping and the line format is:
41 CVS). One line per author mapping and the line format is:
42 srcauthor=whatever string you want
42 srcauthor=whatever string you want
43
43
44 The filemap is a file that allows filtering and remapping of files
44 The filemap is a file that allows filtering and remapping of files
45 and directories. Comment lines start with '#'. Each line can
45 and directories. Comment lines start with '#'. Each line can
46 contain one of the following directives:
46 contain one of the following directives:
47
47
48 include path/to/file
48 include path/to/file
49
49
50 exclude path/to/file
50 exclude path/to/file
51
51
52 rename from/file to/file
52 rename from/file to/file
53
53
54 The 'include' directive causes a file, or all files under a
54 The 'include' directive causes a file, or all files under a
55 directory, to be included in the destination repository, and the
55 directory, to be included in the destination repository, and the
56 exclusion of all other files and directories not explicitely included.
56 exclusion of all other files and directories not explicitely included.
57 The 'exclude' directive causes files or directories to be omitted.
57 The 'exclude' directive causes files or directories to be omitted.
58 The 'rename' directive renames a file or directory. To rename from
58 The 'rename' directive renames a file or directory. To rename from
59 a subdirectory into the root of the repository, use '.' as the
59 a subdirectory into the root of the repository, use '.' as the
60 path to rename to.
60 path to rename to.
61
61
62 The splicemap is a file that allows insertion of synthetic
62 The splicemap is a file that allows insertion of synthetic
63 history, letting you specify the parents of a revision. This is
63 history, letting you specify the parents of a revision. This is
64 useful if you want to e.g. give a Subversion merge two parents, or
64 useful if you want to e.g. give a Subversion merge two parents, or
65 graft two disconnected series of history together. Each entry
65 graft two disconnected series of history together. Each entry
66 contains a key, followed by a space, followed by one or two
66 contains a key, followed by a space, followed by one or two
67 comma-separated values. The key is the revision ID in the source
67 comma-separated values. The key is the revision ID in the source
68 revision control system whose parents should be modified (same
68 revision control system whose parents should be modified (same
69 format as a key in .hg/shamap). The values are the revision IDs
69 format as a key in .hg/shamap). The values are the revision IDs
70 (in either the source or destination revision control system) that
70 (in either the source or destination revision control system) that
71 should be used as the new parents for that node.
71 should be used as the new parents for that node.
72
72
73 Mercurial Source
73 Mercurial Source
74 -----------------
74 -----------------
75
75
76 --config convert.hg.ignoreerrors=False (boolean)
76 --config convert.hg.ignoreerrors=False (boolean)
77 ignore integrity errors when reading. Use it to fix Mercurial
77 ignore integrity errors when reading. Use it to fix Mercurial
78 repositories with missing revlogs, by converting from and to
78 repositories with missing revlogs, by converting from and to
79 Mercurial.
79 Mercurial.
80 --config convert.hg.saverev=False (boolean)
80 --config convert.hg.saverev=False (boolean)
81 store original revision ID in changeset (forces target IDs to
81 store original revision ID in changeset (forces target IDs to
82 change)
82 change)
83 --config convert.hg.startrev=0 (hg revision identifier)
83 --config convert.hg.startrev=0 (hg revision identifier)
84 convert start revision and its descendants
84 convert start revision and its descendants
85
85
86 CVS Source
86 CVS Source
87 ----------
87 ----------
88
88
89 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
89 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
90 to indicate the starting point of what will be converted. Direct
90 to indicate the starting point of what will be converted. Direct
91 access to the repository files is not needed, unless of course the
91 access to the repository files is not needed, unless of course the
92 repository is :local:. The conversion uses the top level directory
92 repository is :local:. The conversion uses the top level directory
93 in the sandbox to find the CVS repository, and then uses CVS rlog
93 in the sandbox to find the CVS repository, and then uses CVS rlog
94 commands to find files to convert. This means that unless a
94 commands to find files to convert. This means that unless a
95 filemap is given, all files under the starting directory will be
95 filemap is given, all files under the starting directory will be
96 converted, and that any directory reorganisation in the CVS
96 converted, and that any directory reorganisation in the CVS
97 sandbox is ignored.
97 sandbox is ignored.
98
98
99 Because CVS does not have changesets, it is necessary to collect
99 Because CVS does not have changesets, it is necessary to collect
100 individual commits to CVS and merge them into changesets. CVS
100 individual commits to CVS and merge them into changesets. CVS
101 source uses its internal changeset merging code by default but can
101 source uses its internal changeset merging code by default but can
102 be configured to call the external 'cvsps' program by setting:
102 be configured to call the external 'cvsps' program by setting:
103 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
103 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
104 This is a legacy option and may be removed in future.
104 This is a legacy option and may be removed in future.
105
105
106 The options shown are the defaults.
106 The options shown are the defaults.
107
107
108 Internal cvsps is selected by setting
108 Internal cvsps is selected by setting
109 --config convert.cvsps=builtin
109 --config convert.cvsps=builtin
110 and has a few more configurable options:
110 and has a few more configurable options:
111 --config convert.cvsps.cache=True (boolean)
111 --config convert.cvsps.cache=True (boolean)
112 Set to False to disable remote log caching, for testing and
112 Set to False to disable remote log caching, for testing and
113 debugging purposes.
113 debugging purposes.
114 --config convert.cvsps.fuzz=60 (integer)
114 --config convert.cvsps.fuzz=60 (integer)
115 Specify the maximum time (in seconds) that is allowed
115 Specify the maximum time (in seconds) that is allowed
116 between commits with identical user and log message in a
116 between commits with identical user and log message in a
117 single changeset. When very large files were checked in as
117 single changeset. When very large files were checked in as
118 part of a changeset then the default may not be long
118 part of a changeset then the default may not be long
119 enough.
119 enough.
120 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
120 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
121 Specify a regular expression to which commit log messages
121 Specify a regular expression to which commit log messages
122 are matched. If a match occurs, then the conversion
122 are matched. If a match occurs, then the conversion
123 process will insert a dummy revision merging the branch on
123 process will insert a dummy revision merging the branch on
124 which this log message occurs to the branch indicated in
124 which this log message occurs to the branch indicated in
125 the regex.
125 the regex.
126 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
126 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
127 Specify a regular expression to which commit log messages
127 Specify a regular expression to which commit log messages
128 are matched. If a match occurs, then the conversion
128 are matched. If a match occurs, then the conversion
129 process will add the most recent revision on the branch
129 process will add the most recent revision on the branch
130 indicated in the regex as the second parent of the
130 indicated in the regex as the second parent of the
131 changeset.
131 changeset.
132
132
133 The hgext/convert/cvsps wrapper script allows the builtin
133 The hgext/convert/cvsps wrapper script allows the builtin
134 changeset merging code to be run without doing a conversion. Its
134 changeset merging code to be run without doing a conversion. Its
135 parameters and output are similar to that of cvsps 2.1.
135 parameters and output are similar to that of cvsps 2.1.
136
136
137 Subversion Source
137 Subversion Source
138 -----------------
138 -----------------
139
139
140 Subversion source detects classical trunk/branches/tags layouts.
140 Subversion source detects classical trunk/branches/tags layouts.
141 By default, the supplied "svn://repo/path/" source URL is
141 By default, the supplied "svn://repo/path/" source URL is
142 converted as a single branch. If "svn://repo/path/trunk" exists it
142 converted as a single branch. If "svn://repo/path/trunk" exists it
143 replaces the default branch. If "svn://repo/path/branches" exists,
143 replaces the default branch. If "svn://repo/path/branches" exists,
144 its subdirectories are listed as possible branches. If
144 its subdirectories are listed as possible branches. If
145 "svn://repo/path/tags" exists, it is looked for tags referencing
145 "svn://repo/path/tags" exists, it is looked for tags referencing
146 converted branches. Default "trunk", "branches" and "tags" values
146 converted branches. Default "trunk", "branches" and "tags" values
147 can be overriden with following options. Set them to paths
147 can be overriden with following options. Set them to paths
148 relative to the source URL, or leave them blank to disable
148 relative to the source URL, or leave them blank to disable
149 autodetection.
149 autodetection.
150
150
151 --config convert.svn.branches=branches (directory name)
151 --config convert.svn.branches=branches (directory name)
152 specify the directory containing branches
152 specify the directory containing branches
153 --config convert.svn.tags=tags (directory name)
153 --config convert.svn.tags=tags (directory name)
154 specify the directory containing tags
154 specify the directory containing tags
155 --config convert.svn.trunk=trunk (directory name)
155 --config convert.svn.trunk=trunk (directory name)
156 specify the name of the trunk branch
156 specify the name of the trunk branch
157
157
158 Source history can be retrieved starting at a specific revision,
158 Source history can be retrieved starting at a specific revision,
159 instead of being integrally converted. Only single branch
159 instead of being integrally converted. Only single branch
160 conversions are supported.
160 conversions are supported.
161
161
162 --config convert.svn.startrev=0 (svn revision number)
162 --config convert.svn.startrev=0 (svn revision number)
163 specify start Subversion revision.
163 specify start Subversion revision.
164
164
165 Perforce Source
165 Perforce Source
166 ---------------
166 ---------------
167
167
168 The Perforce (P4) importer can be given a p4 depot path or a
168 The Perforce (P4) importer can be given a p4 depot path or a
169 client specification as source. It will convert all files in the
169 client specification as source. It will convert all files in the
170 source to a flat Mercurial repository, ignoring labels, branches
170 source to a flat Mercurial repository, ignoring labels, branches
171 and integrations. Note that when a depot path is given you then
171 and integrations. Note that when a depot path is given you then
172 usually should specify a target directory, because otherwise the
172 usually should specify a target directory, because otherwise the
173 target may be named ...-hg.
173 target may be named ...-hg.
174
174
175 It is possible to limit the amount of source history to be
175 It is possible to limit the amount of source history to be
176 converted by specifying an initial Perforce revision.
176 converted by specifying an initial Perforce revision.
177
177
178 --config convert.p4.startrev=0 (perforce changelist number)
178 --config convert.p4.startrev=0 (perforce changelist number)
179 specify initial Perforce revision.
179 specify initial Perforce revision.
180
180
181
181
182 Mercurial Destination
182 Mercurial Destination
183 ---------------------
183 ---------------------
184
184
185 --config convert.hg.clonebranches=False (boolean)
185 --config convert.hg.clonebranches=False (boolean)
186 dispatch source branches in separate clones.
186 dispatch source branches in separate clones.
187 --config convert.hg.tagsbranch=default (branch name)
187 --config convert.hg.tagsbranch=default (branch name)
188 tag revisions branch name
188 tag revisions branch name
189 --config convert.hg.usebranchnames=True (boolean)
189 --config convert.hg.usebranchnames=True (boolean)
190 preserve branch names
190 preserve branch names
191
191
192 options:
192 options:
193
193
194 -A --authors username mapping filename
194 -A --authors username mapping filename
195 -d --dest-type destination repository type
195 -d --dest-type destination repository type
196 --filemap remap file names using contents of file
196 --filemap remap file names using contents of file
197 -r --rev import up to target revision REV
197 -r --rev import up to target revision REV
198 -s --source-type source repository type
198 -s --source-type source repository type
199 --splicemap splice synthesized history into place
199 --splicemap splice synthesized history into place
200 --datesort try to sort changesets by date
200 --datesort try to sort changesets by date
201
201
202 use "hg -v help convert" to show global options
202 use "hg -v help convert" to show global options
203 adding a
203 adding a
204 assuming destination a-hg
204 assuming destination a-hg
205 initializing destination a-hg repository
205 initializing destination a-hg repository
206 scanning source...
206 scanning source...
207 sorting...
207 sorting...
208 converting...
208 converting...
209 4 a
209 4 a
210 3 b
210 3 b
211 2 c
211 2 c
212 1 d
212 1 d
213 0 e
213 0 e
214 pulling from ../a
214 pulling from ../a
215 searching for changes
215 searching for changes
216 no changes found
216 no changes found
217 % should fail
217 % should fail
218 initializing destination bogusfile repository
218 initializing destination bogusfile repository
219 abort: cannot create new bundle repository
219 abort: cannot create new bundle repository
220 % should fail
220 % should fail
221 abort: Permission denied: bogusdir
221 abort: Permission denied: bogusdir
222 % should succeed
222 % should succeed
223 initializing destination bogusdir repository
223 initializing destination bogusdir repository
224 scanning source...
224 scanning source...
225 sorting...
225 sorting...
226 converting...
226 converting...
227 4 a
227 4 a
228 3 b
228 3 b
229 2 c
229 2 c
230 1 d
230 1 d
231 0 e
231 0 e
232 % test pre and post conversion actions
232 % test pre and post conversion actions
233 run hg source pre-conversion action
233 run hg source pre-conversion action
234 run hg sink pre-conversion action
234 run hg sink pre-conversion action
235 run hg sink post-conversion action
235 run hg sink post-conversion action
236 run hg source post-conversion action
236 run hg source post-conversion action
237 % converting empty dir should fail nicely
237 % converting empty dir should fail nicely
238 assuming destination emptydir-hg
238 assuming destination emptydir-hg
239 initializing destination emptydir-hg repository
239 initializing destination emptydir-hg repository
240 emptydir does not look like a CVS checkout
240 emptydir does not look like a CVS checkout
241 emptydir does not look like a Git repo
241 emptydir does not look like a Git repo
242 emptydir does not look like a Subversion repo
242 emptydir does not look like a Subversion repo
243 emptydir is not a local Mercurial repo
243 emptydir is not a local Mercurial repo
244 emptydir does not look like a darcs repo
244 emptydir does not look like a darcs repo
245 emptydir does not look like a monotone repo
245 emptydir does not look like a monotone repo
246 emptydir does not look like a GNU Arch repo
246 emptydir does not look like a GNU Arch repo
247 emptydir does not look like a Bazaar repo
247 emptydir does not look like a Bazaar repo
248 emptydir does not look like a P4 repo
248 emptydir does not look like a P4 repo
249 abort: emptydir: missing or unsupported repository
249 abort: emptydir: missing or unsupported repository
General Comments 0
You need to be logged in to leave comments. Login now