##// END OF EJS Templates
commands: use minirst parser when displaying help
Martin Geisler -
r9157:9261667e default
parent child Browse files
Show More
@@ -1,282 +1,286 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 of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 '''import revisions from foreign VCS repositories into Mercurial'''
8 '''import revisions from foreign VCS repositories into Mercurial'''
9
9
10 import convcmd
10 import convcmd
11 import cvsps
11 import cvsps
12 import subversion
12 import subversion
13 from mercurial import commands
13 from mercurial import commands
14 from mercurial.i18n import _
14 from mercurial.i18n import _
15
15
16 # Commands definition was moved elsewhere to ease demandload job.
16 # Commands definition was moved elsewhere to ease demandload job.
17
17
18 def convert(ui, src, dest=None, revmapfile=None, **opts):
18 def convert(ui, src, dest=None, revmapfile=None, **opts):
19 """convert a foreign SCM repository to a Mercurial one.
19 """convert a foreign SCM repository to a Mercurial one.
20
20
21 Accepted source formats [identifiers]:
21 Accepted source formats [identifiers]:
22
22 - Mercurial [hg]
23 - Mercurial [hg]
23 - CVS [cvs]
24 - CVS [cvs]
24 - Darcs [darcs]
25 - Darcs [darcs]
25 - git [git]
26 - git [git]
26 - Subversion [svn]
27 - Subversion [svn]
27 - Monotone [mtn]
28 - Monotone [mtn]
28 - GNU Arch [gnuarch]
29 - GNU Arch [gnuarch]
29 - Bazaar [bzr]
30 - Bazaar [bzr]
30 - Perforce [p4]
31 - Perforce [p4]
31
32
32 Accepted destination formats [identifiers]:
33 Accepted destination formats [identifiers]:
34
33 - Mercurial [hg]
35 - Mercurial [hg]
34 - Subversion [svn] (history on branches is not preserved)
36 - Subversion [svn] (history on branches is not preserved)
35
37
36 If no revision is given, all revisions will be converted. Otherwise,
38 If no revision is given, all revisions will be converted. Otherwise,
37 convert will only import up to the named revision (given in a format
39 convert will only import up to the named revision (given in a format
38 understood by the source).
40 understood by the source).
39
41
40 If no destination directory name is specified, it defaults to the basename
42 If no destination directory name is specified, it defaults to the basename
41 of the source with '-hg' appended. If the destination repository doesn't
43 of the source with '-hg' appended. If the destination repository doesn't
42 exist, it will be created.
44 exist, it will be created.
43
45
44 By default, all sources except Mercurial will use --branchsort. Mercurial
46 By default, all sources except Mercurial will use --branchsort. Mercurial
45 uses --sourcesort to preserve original revision numbers order. Sort modes
47 uses --sourcesort to preserve original revision numbers order. Sort modes
46 have the following effects:
48 have the following effects:
47
49
48 --branchsort: convert from parent to child revision when possible, which
50 --branchsort convert from parent to child revision when possible, which
49 means branches are usually converted one after the other. It generates
51 means branches are usually converted one after the other. It
50 more compact repositories.
52 generates more compact repositories.
51 --datesort: sort revisions by date. Converted repositories have
53
52 good-looking changelogs but are often an order of magnitude larger than
54 --datesort sort revisions by date. Converted repositories have
53 the same ones generated by --branchsort.
55 good-looking changelogs but are often an order of magnitude
54 --sourcesort: try to preserve source revisions order, only supported by
56 larger than the same ones generated by --branchsort.
57
58 --sourcesort try to preserve source revisions order, only supported by
55 Mercurial sources.
59 Mercurial sources.
56
60
57 If <REVMAP> isn't given, it will be put in a default location
61 If <REVMAP> isn't given, it will be put in a default location
58 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file that
62 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file that
59 maps each source commit ID to the destination ID for that revision, like
63 maps each source commit ID to the destination ID for that revision, like
60 so:
64 so::
61
65
62 <source ID> <destination ID>
66 <source ID> <destination ID>
63
67
64 If the file doesn't exist, it's automatically created. It's updated on
68 If the file doesn't exist, it's automatically created. It's updated on
65 each commit copied, so convert-repo can be interrupted and can be run
69 each commit copied, so convert-repo can be interrupted and can be run
66 repeatedly to copy new commits.
70 repeatedly to copy new commits.
67
71
68 The [username mapping] file is a simple text file that maps each source
72 The [username mapping] file is a simple text file that maps each source
69 commit author to a destination commit author. It is handy for source SCMs
73 commit author to a destination commit author. It is handy for source SCMs
70 that use unix logins to identify authors (eg: CVS). One line per author
74 that use unix logins to identify authors (eg: CVS). One line per author
71 mapping and the line format is: srcauthor=whatever string you want
75 mapping and the line format is: srcauthor=whatever string you want
72
76
73 The filemap is a file that allows filtering and remapping of files and
77 The filemap is a file that allows filtering and remapping of files and
74 directories. Comment lines start with '#'. Each line can contain one of
78 directories. Comment lines start with '#'. Each line can contain one of
75 the following directives:
79 the following directives::
76
80
77 include path/to/file
81 include path/to/file
78
82
79 exclude path/to/file
83 exclude path/to/file
80
84
81 rename from/file to/file
85 rename from/file to/file
82
86
83 The 'include' directive causes a file, or all files under a directory, to
87 The 'include' directive causes a file, or all files under a directory, to
84 be included in the destination repository, and the exclusion of all other
88 be included in the destination repository, and the exclusion of all other
85 files and directories not explicitly included. The 'exclude' directive
89 files and directories not explicitly included. The 'exclude' directive
86 causes files or directories to be omitted. The 'rename' directive renames
90 causes files or directories to be omitted. The 'rename' directive renames
87 a file or directory. To rename from a subdirectory into the root of the
91 a file or directory. To rename from a subdirectory into the root of the
88 repository, use '.' as the path to rename to.
92 repository, use '.' as the path to rename to.
89
93
90 The splicemap is a file that allows insertion of synthetic history,
94 The splicemap is a file that allows insertion of synthetic history,
91 letting you specify the parents of a revision. This is useful if you want
95 letting you specify the parents of a revision. This is useful if you want
92 to e.g. give a Subversion merge two parents, or graft two disconnected
96 to e.g. give a Subversion merge two parents, or graft two disconnected
93 series of history together. Each entry contains a key, followed by a
97 series of history together. Each entry contains a key, followed by a
94 space, followed by one or two comma-separated values. The key is the
98 space, followed by one or two comma-separated values. The key is the
95 revision ID in the source revision control system whose parents should be
99 revision ID in the source revision control system whose parents should be
96 modified (same format as a key in .hg/shamap). The values are the revision
100 modified (same format as a key in .hg/shamap). The values are the revision
97 IDs (in either the source or destination revision control system) that
101 IDs (in either the source or destination revision control system) that
98 should be used as the new parents for that node.
102 should be used as the new parents for that node.
99
103
100 The branchmap is a file that allows you to rename a branch when it is
104 The branchmap is a file that allows you to rename a branch when it is
101 being brought in from whatever external repository. When used in
105 being brought in from whatever external repository. When used in
102 conjunction with a splicemap, it allows for a powerful combination to help
106 conjunction with a splicemap, it allows for a powerful combination to help
103 fix even the most badly mismanaged repositories and turn them into nicely
107 fix even the most badly mismanaged repositories and turn them into nicely
104 structured Mercurial repositories. The branchmap contains lines of the
108 structured Mercurial repositories. The branchmap contains lines of the
105 form "original_branch_name new_branch_name". "original_branch_name" is the
109 form "original_branch_name new_branch_name". "original_branch_name" is the
106 name of the branch in the source repository, and "new_branch_name" is the
110 name of the branch in the source repository, and "new_branch_name" is the
107 name of the branch is the destination repository. This can be used to (for
111 name of the branch is the destination repository. This can be used to (for
108 instance) move code in one repository from "default" to a named branch.
112 instance) move code in one repository from "default" to a named branch.
109
113
110 Mercurial Source
114 Mercurial Source
111 ----------------
115 ----------------
112
116
113 --config convert.hg.ignoreerrors=False (boolean)
117 --config convert.hg.ignoreerrors=False (boolean)
114 ignore integrity errors when reading. Use it to fix Mercurial
118 ignore integrity errors when reading. Use it to fix Mercurial
115 repositories with missing revlogs, by converting from and to
119 repositories with missing revlogs, by converting from and to
116 Mercurial.
120 Mercurial.
117 --config convert.hg.saverev=False (boolean)
121 --config convert.hg.saverev=False (boolean)
118 store original revision ID in changeset (forces target IDs to change)
122 store original revision ID in changeset (forces target IDs to change)
119 --config convert.hg.startrev=0 (hg revision identifier)
123 --config convert.hg.startrev=0 (hg revision identifier)
120 convert start revision and its descendants
124 convert start revision and its descendants
121
125
122 CVS Source
126 CVS Source
123 ----------
127 ----------
124
128
125 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
129 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
126 indicate the starting point of what will be converted. Direct access to
130 indicate the starting point of what will be converted. Direct access to
127 the repository files is not needed, unless of course the repository is
131 the repository files is not needed, unless of course the repository is
128 :local:. The conversion uses the top level directory in the sandbox to
132 :local:. The conversion uses the top level directory in the sandbox to
129 find the CVS repository, and then uses CVS rlog commands to find files to
133 find the CVS repository, and then uses CVS rlog commands to find files to
130 convert. This means that unless a filemap is given, all files under the
134 convert. This means that unless a filemap is given, all files under the
131 starting directory will be converted, and that any directory
135 starting directory will be converted, and that any directory
132 reorganization in the CVS sandbox is ignored.
136 reorganization in the CVS sandbox is ignored.
133
137
134 Because CVS does not have changesets, it is necessary to collect
138 Because CVS does not have changesets, it is necessary to collect
135 individual commits to CVS and merge them into changesets. CVS source uses
139 individual commits to CVS and merge them into changesets. CVS source uses
136 its internal changeset merging code by default but can be configured to
140 its internal changeset merging code by default but can be configured to
137 call the external 'cvsps' program by setting:
141 call the external 'cvsps' program by setting::
138
142
139 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
143 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
140
144
141 This option is deprecated and will be removed in Mercurial 1.4.
145 This option is deprecated and will be removed in Mercurial 1.4.
142
146
143 The options shown are the defaults.
147 The options shown are the defaults.
144
148
145 Internal cvsps is selected by setting
149 Internal cvsps is selected by setting ::
146
150
147 --config convert.cvsps=builtin
151 --config convert.cvsps=builtin
148
152
149 and has a few more configurable options:
153 and has a few more configurable options:
150
154
151 --config convert.cvsps.cache=True (boolean)
155 --config convert.cvsps.cache=True (boolean)
152 Set to False to disable remote log caching, for testing and debugging
156 Set to False to disable remote log caching, for testing and debugging
153 purposes.
157 purposes.
154 --config convert.cvsps.fuzz=60 (integer)
158 --config convert.cvsps.fuzz=60 (integer)
155 Specify the maximum time (in seconds) that is allowed between commits
159 Specify the maximum time (in seconds) that is allowed between commits
156 with identical user and log message in a single changeset. When very
160 with identical user and log message in a single changeset. When very
157 large files were checked in as part of a changeset then the default
161 large files were checked in as part of a changeset then the default
158 may not be long enough.
162 may not be long enough.
159 --config convert.cvsps.mergeto='{{mergetobranch ([-\\w]+)}}'
163 --config convert.cvsps.mergeto='{{mergetobranch ([-\\w]+)}}'
160 Specify a regular expression to which commit log messages are matched.
164 Specify a regular expression to which commit log messages are matched.
161 If a match occurs, then the conversion process will insert a dummy
165 If a match occurs, then the conversion process will insert a dummy
162 revision merging the branch on which this log message occurs to the
166 revision merging the branch on which this log message occurs to the
163 branch indicated in the regex.
167 branch indicated in the regex.
164 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\\w]+)}}'
168 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\\w]+)}}'
165 Specify a regular expression to which commit log messages are matched.
169 Specify a regular expression to which commit log messages are matched.
166 If a match occurs, then the conversion process will add the most
170 If a match occurs, then the conversion process will add the most
167 recent revision on the branch indicated in the regex as the second
171 recent revision on the branch indicated in the regex as the second
168 parent of the changeset.
172 parent of the changeset.
169
173
170 The hgext/convert/cvsps wrapper script allows the builtin changeset
174 The hgext/convert/cvsps wrapper script allows the builtin changeset
171 merging code to be run without doing a conversion. Its parameters and
175 merging code to be run without doing a conversion. Its parameters and
172 output are similar to that of cvsps 2.1.
176 output are similar to that of cvsps 2.1.
173
177
174 Subversion Source
178 Subversion Source
175 -----------------
179 -----------------
176
180
177 Subversion source detects classical trunk/branches/tags layouts. By
181 Subversion source detects classical trunk/branches/tags layouts. By
178 default, the supplied "svn://repo/path/" source URL is converted as a
182 default, the supplied "svn://repo/path/" source URL is converted as a
179 single branch. If "svn://repo/path/trunk" exists it replaces the default
183 single branch. If "svn://repo/path/trunk" exists it replaces the default
180 branch. If "svn://repo/path/branches" exists, its subdirectories are
184 branch. If "svn://repo/path/branches" exists, its subdirectories are
181 listed as possible branches. If "svn://repo/path/tags" exists, it is
185 listed as possible branches. If "svn://repo/path/tags" exists, it is
182 looked for tags referencing converted branches. Default "trunk",
186 looked for tags referencing converted branches. Default "trunk",
183 "branches" and "tags" values can be overridden with following options. Set
187 "branches" and "tags" values can be overridden with following options. Set
184 them to paths relative to the source URL, or leave them blank to disable
188 them to paths relative to the source URL, or leave them blank to disable
185 auto detection.
189 auto detection.
186
190
187 --config convert.svn.branches=branches (directory name)
191 --config convert.svn.branches=branches (directory name)
188 specify the directory containing branches
192 specify the directory containing branches
189 --config convert.svn.tags=tags (directory name)
193 --config convert.svn.tags=tags (directory name)
190 specify the directory containing tags
194 specify the directory containing tags
191 --config convert.svn.trunk=trunk (directory name)
195 --config convert.svn.trunk=trunk (directory name)
192 specify the name of the trunk branch
196 specify the name of the trunk branch
193
197
194 Source history can be retrieved starting at a specific revision, instead
198 Source history can be retrieved starting at a specific revision, instead
195 of being integrally converted. Only single branch conversions are
199 of being integrally converted. Only single branch conversions are
196 supported.
200 supported.
197
201
198 --config convert.svn.startrev=0 (svn revision number)
202 --config convert.svn.startrev=0 (svn revision number)
199 specify start Subversion revision.
203 specify start Subversion revision.
200
204
201 Perforce Source
205 Perforce Source
202 ---------------
206 ---------------
203
207
204 The Perforce (P4) importer can be given a p4 depot path or a client
208 The Perforce (P4) importer can be given a p4 depot path or a client
205 specification as source. It will convert all files in the source to a flat
209 specification as source. It will convert all files in the source to a flat
206 Mercurial repository, ignoring labels, branches and integrations. Note
210 Mercurial repository, ignoring labels, branches and integrations. Note
207 that when a depot path is given you then usually should specify a target
211 that when a depot path is given you then usually should specify a target
208 directory, because otherwise the target may be named ...-hg.
212 directory, because otherwise the target may be named ...-hg.
209
213
210 It is possible to limit the amount of source history to be converted by
214 It is possible to limit the amount of source history to be converted by
211 specifying an initial Perforce revision.
215 specifying an initial Perforce revision.
212
216
213 --config convert.p4.startrev=0 (perforce changelist number)
217 --config convert.p4.startrev=0 (perforce changelist number)
214 specify initial Perforce revision.
218 specify initial Perforce revision.
215
219
216 Mercurial Destination
220 Mercurial Destination
217 ---------------------
221 ---------------------
218
222
219 --config convert.hg.clonebranches=False (boolean)
223 --config convert.hg.clonebranches=False (boolean)
220 dispatch source branches in separate clones.
224 dispatch source branches in separate clones.
221 --config convert.hg.tagsbranch=default (branch name)
225 --config convert.hg.tagsbranch=default (branch name)
222 tag revisions branch name
226 tag revisions branch name
223 --config convert.hg.usebranchnames=True (boolean)
227 --config convert.hg.usebranchnames=True (boolean)
224 preserve branch names
228 preserve branch names
225
229
226 """
230 """
227 return convcmd.convert(ui, src, dest, revmapfile, **opts)
231 return convcmd.convert(ui, src, dest, revmapfile, **opts)
228
232
229 def debugsvnlog(ui, **opts):
233 def debugsvnlog(ui, **opts):
230 return subversion.debugsvnlog(ui, **opts)
234 return subversion.debugsvnlog(ui, **opts)
231
235
232 def debugcvsps(ui, *args, **opts):
236 def debugcvsps(ui, *args, **opts):
233 '''create changeset information from CVS
237 '''create changeset information from CVS
234
238
235 This command is intended as a debugging tool for the CVS to Mercurial
239 This command is intended as a debugging tool for the CVS to Mercurial
236 converter, and can be used as a direct replacement for cvsps.
240 converter, and can be used as a direct replacement for cvsps.
237
241
238 Hg debugcvsps reads the CVS rlog for current directory (or any named
242 Hg debugcvsps reads the CVS rlog for current directory (or any named
239 directory) in the CVS repository, and converts the log to a series of
243 directory) in the CVS repository, and converts the log to a series of
240 changesets based on matching commit log entries and dates.
244 changesets based on matching commit log entries and dates.
241 '''
245 '''
242 return cvsps.debugcvsps(ui, *args, **opts)
246 return cvsps.debugcvsps(ui, *args, **opts)
243
247
244 commands.norepo += " convert debugsvnlog debugcvsps"
248 commands.norepo += " convert debugsvnlog debugcvsps"
245
249
246 cmdtable = {
250 cmdtable = {
247 "convert":
251 "convert":
248 (convert,
252 (convert,
249 [('A', 'authors', '', _('username mapping filename')),
253 [('A', 'authors', '', _('username mapping filename')),
250 ('d', 'dest-type', '', _('destination repository type')),
254 ('d', 'dest-type', '', _('destination repository type')),
251 ('', 'filemap', '', _('remap file names using contents of file')),
255 ('', 'filemap', '', _('remap file names using contents of file')),
252 ('r', 'rev', '', _('import up to target revision REV')),
256 ('r', 'rev', '', _('import up to target revision REV')),
253 ('s', 'source-type', '', _('source repository type')),
257 ('s', 'source-type', '', _('source repository type')),
254 ('', 'splicemap', '', _('splice synthesized history into place')),
258 ('', 'splicemap', '', _('splice synthesized history into place')),
255 ('', 'branchmap', '', _('change branch names while converting')),
259 ('', 'branchmap', '', _('change branch names while converting')),
256 ('', 'branchsort', None, _('try to sort changesets by branches')),
260 ('', 'branchsort', None, _('try to sort changesets by branches')),
257 ('', 'datesort', None, _('try to sort changesets by date')),
261 ('', 'datesort', None, _('try to sort changesets by date')),
258 ('', 'sourcesort', None, _('preserve source changesets order'))],
262 ('', 'sourcesort', None, _('preserve source changesets order'))],
259 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')),
263 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')),
260 "debugsvnlog":
264 "debugsvnlog":
261 (debugsvnlog,
265 (debugsvnlog,
262 [],
266 [],
263 'hg debugsvnlog'),
267 'hg debugsvnlog'),
264 "debugcvsps":
268 "debugcvsps":
265 (debugcvsps,
269 (debugcvsps,
266 [
270 [
267 # Main options shared with cvsps-2.1
271 # Main options shared with cvsps-2.1
268 ('b', 'branches', [], _('only return changes on specified branches')),
272 ('b', 'branches', [], _('only return changes on specified branches')),
269 ('p', 'prefix', '', _('prefix to remove from file names')),
273 ('p', 'prefix', '', _('prefix to remove from file names')),
270 ('r', 'revisions', [], _('only return changes after or between specified tags')),
274 ('r', 'revisions', [], _('only return changes after or between specified tags')),
271 ('u', 'update-cache', None, _("update cvs log cache")),
275 ('u', 'update-cache', None, _("update cvs log cache")),
272 ('x', 'new-cache', None, _("create new cvs log cache")),
276 ('x', 'new-cache', None, _("create new cvs log cache")),
273 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
277 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
274 ('', 'root', '', _('specify cvsroot')),
278 ('', 'root', '', _('specify cvsroot')),
275 # Options specific to builtin cvsps
279 # Options specific to builtin cvsps
276 ('', 'parents', '', _('show parent changesets')),
280 ('', 'parents', '', _('show parent changesets')),
277 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
281 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
278 # Options that are ignored for compatibility with cvsps-2.1
282 # Options that are ignored for compatibility with cvsps-2.1
279 ('A', 'cvs-direct', None, _('ignored for compatibility')),
283 ('A', 'cvs-direct', None, _('ignored for compatibility')),
280 ],
284 ],
281 _('hg debugcvsps [OPTION]... [PATH]...')),
285 _('hg debugcvsps [OPTION]... [PATH]...')),
282 }
286 }
@@ -1,543 +1,543 b''
1 # keyword.py - $Keyword$ expansion for Mercurial
1 # keyword.py - $Keyword$ expansion for Mercurial
2 #
2 #
3 # Copyright 2007, 2008 Christian Ebert <blacktrash@gmx.net>
3 # Copyright 2007, 2008 Christian Ebert <blacktrash@gmx.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7 #
7 #
8 # $Id$
8 # $Id$
9 #
9 #
10 # Keyword expansion hack against the grain of a DSCM
10 # Keyword expansion hack against the grain of a DSCM
11 #
11 #
12 # There are many good reasons why this is not needed in a distributed
12 # There are many good reasons why this is not needed in a distributed
13 # SCM, still it may be useful in very small projects based on single
13 # SCM, still it may be useful in very small projects based on single
14 # files (like LaTeX packages), that are mostly addressed to an
14 # files (like LaTeX packages), that are mostly addressed to an
15 # audience not running a version control system.
15 # audience not running a version control system.
16 #
16 #
17 # For in-depth discussion refer to
17 # For in-depth discussion refer to
18 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
18 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
19 #
19 #
20 # Keyword expansion is based on Mercurial's changeset template mappings.
20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 #
21 #
22 # Binary files are not touched.
22 # Binary files are not touched.
23 #
23 #
24 # Files to act upon/ignore are specified in the [keyword] section.
24 # Files to act upon/ignore are specified in the [keyword] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
26 #
26 #
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
28
28
29 '''expand keywords in tracked files
29 '''expand keywords in tracked files
30
30
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in tracked
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in tracked
32 text files selected by your configuration.
32 text files selected by your configuration.
33
33
34 Keywords are only expanded in local repositories and not stored in the change
34 Keywords are only expanded in local repositories and not stored in the change
35 history. The mechanism can be regarded as a convenience for the current user
35 history. The mechanism can be regarded as a convenience for the current user
36 or for archive distribution.
36 or for archive distribution.
37
37
38 Configuration is done in the [keyword] and [keywordmaps] sections of hgrc
38 Configuration is done in the [keyword] and [keywordmaps] sections of hgrc
39 files.
39 files.
40
40
41 Example:
41 Example::
42
42
43 [keyword]
43 [keyword]
44 # expand keywords in every python file except those matching "x*"
44 # expand keywords in every python file except those matching "x*"
45 **.py =
45 **.py =
46 x* = ignore
46 x* = ignore
47
47
48 NOTE: the more specific you are in your filename patterns the less you lose
48 NOTE: the more specific you are in your filename patterns the less you lose
49 speed in huge repositories.
49 speed in huge repositories.
50
50
51 For [keywordmaps] template mapping and expansion demonstration and control run
51 For [keywordmaps] template mapping and expansion demonstration and control run
52 "hg kwdemo".
52 "hg kwdemo".
53
53
54 An additional date template filter {date|utcdate} is provided.
54 An additional date template filter {date|utcdate} is provided.
55
55
56 The default template mappings (view with "hg kwdemo -d") can be replaced with
56 The default template mappings (view with "hg kwdemo -d") can be replaced with
57 customized keywords and templates. Again, run "hg kwdemo" to control the
57 customized keywords and templates. Again, run "hg kwdemo" to control the
58 results of your config changes.
58 results of your config changes.
59
59
60 Before changing/disabling active keywords, run "hg kwshrink" to avoid the risk
60 Before changing/disabling active keywords, run "hg kwshrink" to avoid the risk
61 of inadvertently storing expanded keywords in the change history.
61 of inadvertently storing expanded keywords in the change history.
62
62
63 To force expansion after enabling it, or a configuration change, run "hg
63 To force expansion after enabling it, or a configuration change, run "hg
64 kwexpand".
64 kwexpand".
65
65
66 Also, when committing with the record extension or using mq's qrecord, be
66 Also, when committing with the record extension or using mq's qrecord, be
67 aware that keywords cannot be updated. Again, run "hg kwexpand" on the files
67 aware that keywords cannot be updated. Again, run "hg kwexpand" on the files
68 in question to update keyword expansions after all changes have been checked
68 in question to update keyword expansions after all changes have been checked
69 in.
69 in.
70
70
71 Expansions spanning more than one line and incremental expansions, like CVS'
71 Expansions spanning more than one line and incremental expansions, like CVS'
72 $Log$, are not supported. A keyword template map "Log = {desc}" expands to the
72 $Log$, are not supported. A keyword template map "Log = {desc}" expands to the
73 first line of the changeset description.
73 first line of the changeset description.
74 '''
74 '''
75
75
76 from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions
76 from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions
77 from mercurial import patch, localrepo, templater, templatefilters, util, match
77 from mercurial import patch, localrepo, templater, templatefilters, util, match
78 from mercurial.hgweb import webcommands
78 from mercurial.hgweb import webcommands
79 from mercurial.lock import release
79 from mercurial.lock import release
80 from mercurial.node import nullid
80 from mercurial.node import nullid
81 from mercurial.i18n import _
81 from mercurial.i18n import _
82 import re, shutil, tempfile, time
82 import re, shutil, tempfile, time
83
83
84 commands.optionalrepo += ' kwdemo'
84 commands.optionalrepo += ' kwdemo'
85
85
86 # hg commands that do not act on keywords
86 # hg commands that do not act on keywords
87 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
87 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
88 ' log outgoing push rename rollback tip verify'
88 ' log outgoing push rename rollback tip verify'
89 ' convert email glog')
89 ' convert email glog')
90
90
91 # hg commands that trigger expansion only when writing to working dir,
91 # hg commands that trigger expansion only when writing to working dir,
92 # not when reading filelog, and unexpand when reading from working dir
92 # not when reading filelog, and unexpand when reading from working dir
93 restricted = 'merge record resolve qfold qimport qnew qpush qrefresh qrecord'
93 restricted = 'merge record resolve qfold qimport qnew qpush qrefresh qrecord'
94
94
95 def utcdate(date):
95 def utcdate(date):
96 '''Returns hgdate in cvs-like UTC format.'''
96 '''Returns hgdate in cvs-like UTC format.'''
97 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
97 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
98
98
99 # make keyword tools accessible
99 # make keyword tools accessible
100 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
100 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
101
101
102
102
103 class kwtemplater(object):
103 class kwtemplater(object):
104 '''
104 '''
105 Sets up keyword templates, corresponding keyword regex, and
105 Sets up keyword templates, corresponding keyword regex, and
106 provides keyword substitution functions.
106 provides keyword substitution functions.
107 '''
107 '''
108 templates = {
108 templates = {
109 'Revision': '{node|short}',
109 'Revision': '{node|short}',
110 'Author': '{author|user}',
110 'Author': '{author|user}',
111 'Date': '{date|utcdate}',
111 'Date': '{date|utcdate}',
112 'RCSFile': '{file|basename},v',
112 'RCSFile': '{file|basename},v',
113 'Source': '{root}/{file},v',
113 'Source': '{root}/{file},v',
114 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
114 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
115 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
115 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
116 }
116 }
117
117
118 def __init__(self, ui, repo):
118 def __init__(self, ui, repo):
119 self.ui = ui
119 self.ui = ui
120 self.repo = repo
120 self.repo = repo
121 self.match = match.match(repo.root, '', [],
121 self.match = match.match(repo.root, '', [],
122 kwtools['inc'], kwtools['exc'])
122 kwtools['inc'], kwtools['exc'])
123 self.restrict = kwtools['hgcmd'] in restricted.split()
123 self.restrict = kwtools['hgcmd'] in restricted.split()
124
124
125 kwmaps = self.ui.configitems('keywordmaps')
125 kwmaps = self.ui.configitems('keywordmaps')
126 if kwmaps: # override default templates
126 if kwmaps: # override default templates
127 self.templates = dict((k, templater.parsestring(v, False))
127 self.templates = dict((k, templater.parsestring(v, False))
128 for k, v in kwmaps)
128 for k, v in kwmaps)
129 escaped = map(re.escape, self.templates.keys())
129 escaped = map(re.escape, self.templates.keys())
130 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
130 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
131 self.re_kw = re.compile(kwpat)
131 self.re_kw = re.compile(kwpat)
132
132
133 templatefilters.filters['utcdate'] = utcdate
133 templatefilters.filters['utcdate'] = utcdate
134 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
134 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
135 False, None, '', False)
135 False, None, '', False)
136
136
137 def substitute(self, data, path, ctx, subfunc):
137 def substitute(self, data, path, ctx, subfunc):
138 '''Replaces keywords in data with expanded template.'''
138 '''Replaces keywords in data with expanded template.'''
139 def kwsub(mobj):
139 def kwsub(mobj):
140 kw = mobj.group(1)
140 kw = mobj.group(1)
141 self.ct.use_template(self.templates[kw])
141 self.ct.use_template(self.templates[kw])
142 self.ui.pushbuffer()
142 self.ui.pushbuffer()
143 self.ct.show(ctx, root=self.repo.root, file=path)
143 self.ct.show(ctx, root=self.repo.root, file=path)
144 ekw = templatefilters.firstline(self.ui.popbuffer())
144 ekw = templatefilters.firstline(self.ui.popbuffer())
145 return '$%s: %s $' % (kw, ekw)
145 return '$%s: %s $' % (kw, ekw)
146 return subfunc(kwsub, data)
146 return subfunc(kwsub, data)
147
147
148 def expand(self, path, node, data):
148 def expand(self, path, node, data):
149 '''Returns data with keywords expanded.'''
149 '''Returns data with keywords expanded.'''
150 if not self.restrict and self.match(path) and not util.binary(data):
150 if not self.restrict and self.match(path) and not util.binary(data):
151 ctx = self.repo.filectx(path, fileid=node).changectx()
151 ctx = self.repo.filectx(path, fileid=node).changectx()
152 return self.substitute(data, path, ctx, self.re_kw.sub)
152 return self.substitute(data, path, ctx, self.re_kw.sub)
153 return data
153 return data
154
154
155 def iskwfile(self, path, flagfunc):
155 def iskwfile(self, path, flagfunc):
156 '''Returns true if path matches [keyword] pattern
156 '''Returns true if path matches [keyword] pattern
157 and is not a symbolic link.
157 and is not a symbolic link.
158 Caveat: localrepository._link fails on Windows.'''
158 Caveat: localrepository._link fails on Windows.'''
159 return self.match(path) and not 'l' in flagfunc(path)
159 return self.match(path) and not 'l' in flagfunc(path)
160
160
161 def overwrite(self, node, expand, files):
161 def overwrite(self, node, expand, files):
162 '''Overwrites selected files expanding/shrinking keywords.'''
162 '''Overwrites selected files expanding/shrinking keywords.'''
163 ctx = self.repo[node]
163 ctx = self.repo[node]
164 mf = ctx.manifest()
164 mf = ctx.manifest()
165 if node is not None: # commit
165 if node is not None: # commit
166 files = [f for f in ctx.files() if f in mf]
166 files = [f for f in ctx.files() if f in mf]
167 notify = self.ui.debug
167 notify = self.ui.debug
168 else: # kwexpand/kwshrink
168 else: # kwexpand/kwshrink
169 notify = self.ui.note
169 notify = self.ui.note
170 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
170 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
171 if candidates:
171 if candidates:
172 self.restrict = True # do not expand when reading
172 self.restrict = True # do not expand when reading
173 msg = (expand and _('overwriting %s expanding keywords\n')
173 msg = (expand and _('overwriting %s expanding keywords\n')
174 or _('overwriting %s shrinking keywords\n'))
174 or _('overwriting %s shrinking keywords\n'))
175 for f in candidates:
175 for f in candidates:
176 fp = self.repo.file(f)
176 fp = self.repo.file(f)
177 data = fp.read(mf[f])
177 data = fp.read(mf[f])
178 if util.binary(data):
178 if util.binary(data):
179 continue
179 continue
180 if expand:
180 if expand:
181 if node is None:
181 if node is None:
182 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
182 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
183 data, found = self.substitute(data, f, ctx,
183 data, found = self.substitute(data, f, ctx,
184 self.re_kw.subn)
184 self.re_kw.subn)
185 else:
185 else:
186 found = self.re_kw.search(data)
186 found = self.re_kw.search(data)
187 if found:
187 if found:
188 notify(msg % f)
188 notify(msg % f)
189 self.repo.wwrite(f, data, mf.flags(f))
189 self.repo.wwrite(f, data, mf.flags(f))
190 if node is None:
190 if node is None:
191 self.repo.dirstate.normal(f)
191 self.repo.dirstate.normal(f)
192 self.restrict = False
192 self.restrict = False
193
193
194 def shrinktext(self, text):
194 def shrinktext(self, text):
195 '''Unconditionally removes all keyword substitutions from text.'''
195 '''Unconditionally removes all keyword substitutions from text.'''
196 return self.re_kw.sub(r'$\1$', text)
196 return self.re_kw.sub(r'$\1$', text)
197
197
198 def shrink(self, fname, text):
198 def shrink(self, fname, text):
199 '''Returns text with all keyword substitutions removed.'''
199 '''Returns text with all keyword substitutions removed.'''
200 if self.match(fname) and not util.binary(text):
200 if self.match(fname) and not util.binary(text):
201 return self.shrinktext(text)
201 return self.shrinktext(text)
202 return text
202 return text
203
203
204 def shrinklines(self, fname, lines):
204 def shrinklines(self, fname, lines):
205 '''Returns lines with keyword substitutions removed.'''
205 '''Returns lines with keyword substitutions removed.'''
206 if self.match(fname):
206 if self.match(fname):
207 text = ''.join(lines)
207 text = ''.join(lines)
208 if not util.binary(text):
208 if not util.binary(text):
209 return self.shrinktext(text).splitlines(True)
209 return self.shrinktext(text).splitlines(True)
210 return lines
210 return lines
211
211
212 def wread(self, fname, data):
212 def wread(self, fname, data):
213 '''If in restricted mode returns data read from wdir with
213 '''If in restricted mode returns data read from wdir with
214 keyword substitutions removed.'''
214 keyword substitutions removed.'''
215 return self.restrict and self.shrink(fname, data) or data
215 return self.restrict and self.shrink(fname, data) or data
216
216
217 class kwfilelog(filelog.filelog):
217 class kwfilelog(filelog.filelog):
218 '''
218 '''
219 Subclass of filelog to hook into its read, add, cmp methods.
219 Subclass of filelog to hook into its read, add, cmp methods.
220 Keywords are "stored" unexpanded, and processed on reading.
220 Keywords are "stored" unexpanded, and processed on reading.
221 '''
221 '''
222 def __init__(self, opener, kwt, path):
222 def __init__(self, opener, kwt, path):
223 super(kwfilelog, self).__init__(opener, path)
223 super(kwfilelog, self).__init__(opener, path)
224 self.kwt = kwt
224 self.kwt = kwt
225 self.path = path
225 self.path = path
226
226
227 def read(self, node):
227 def read(self, node):
228 '''Expands keywords when reading filelog.'''
228 '''Expands keywords when reading filelog.'''
229 data = super(kwfilelog, self).read(node)
229 data = super(kwfilelog, self).read(node)
230 return self.kwt.expand(self.path, node, data)
230 return self.kwt.expand(self.path, node, data)
231
231
232 def add(self, text, meta, tr, link, p1=None, p2=None):
232 def add(self, text, meta, tr, link, p1=None, p2=None):
233 '''Removes keyword substitutions when adding to filelog.'''
233 '''Removes keyword substitutions when adding to filelog.'''
234 text = self.kwt.shrink(self.path, text)
234 text = self.kwt.shrink(self.path, text)
235 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
235 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
236
236
237 def cmp(self, node, text):
237 def cmp(self, node, text):
238 '''Removes keyword substitutions for comparison.'''
238 '''Removes keyword substitutions for comparison.'''
239 text = self.kwt.shrink(self.path, text)
239 text = self.kwt.shrink(self.path, text)
240 if self.renamed(node):
240 if self.renamed(node):
241 t2 = super(kwfilelog, self).read(node)
241 t2 = super(kwfilelog, self).read(node)
242 return t2 != text
242 return t2 != text
243 return revlog.revlog.cmp(self, node, text)
243 return revlog.revlog.cmp(self, node, text)
244
244
245 def _status(ui, repo, kwt, unknown, *pats, **opts):
245 def _status(ui, repo, kwt, unknown, *pats, **opts):
246 '''Bails out if [keyword] configuration is not active.
246 '''Bails out if [keyword] configuration is not active.
247 Returns status of working directory.'''
247 Returns status of working directory.'''
248 if kwt:
248 if kwt:
249 match = cmdutil.match(repo, pats, opts)
249 match = cmdutil.match(repo, pats, opts)
250 return repo.status(match=match, unknown=unknown, clean=True)
250 return repo.status(match=match, unknown=unknown, clean=True)
251 if ui.configitems('keyword'):
251 if ui.configitems('keyword'):
252 raise util.Abort(_('[keyword] patterns cannot match'))
252 raise util.Abort(_('[keyword] patterns cannot match'))
253 raise util.Abort(_('no [keyword] patterns configured'))
253 raise util.Abort(_('no [keyword] patterns configured'))
254
254
255 def _kwfwrite(ui, repo, expand, *pats, **opts):
255 def _kwfwrite(ui, repo, expand, *pats, **opts):
256 '''Selects files and passes them to kwtemplater.overwrite.'''
256 '''Selects files and passes them to kwtemplater.overwrite.'''
257 if repo.dirstate.parents()[1] != nullid:
257 if repo.dirstate.parents()[1] != nullid:
258 raise util.Abort(_('outstanding uncommitted merge'))
258 raise util.Abort(_('outstanding uncommitted merge'))
259 kwt = kwtools['templater']
259 kwt = kwtools['templater']
260 status = _status(ui, repo, kwt, False, *pats, **opts)
260 status = _status(ui, repo, kwt, False, *pats, **opts)
261 modified, added, removed, deleted = status[:4]
261 modified, added, removed, deleted = status[:4]
262 if modified or added or removed or deleted:
262 if modified or added or removed or deleted:
263 raise util.Abort(_('outstanding uncommitted changes'))
263 raise util.Abort(_('outstanding uncommitted changes'))
264 wlock = lock = None
264 wlock = lock = None
265 try:
265 try:
266 wlock = repo.wlock()
266 wlock = repo.wlock()
267 lock = repo.lock()
267 lock = repo.lock()
268 kwt.overwrite(None, expand, status[6])
268 kwt.overwrite(None, expand, status[6])
269 finally:
269 finally:
270 release(lock, wlock)
270 release(lock, wlock)
271
271
272 def demo(ui, repo, *args, **opts):
272 def demo(ui, repo, *args, **opts):
273 '''print [keywordmaps] configuration and an expansion example
273 '''print [keywordmaps] configuration and an expansion example
274
274
275 Show current, custom, or default keyword template maps and their
275 Show current, custom, or default keyword template maps and their
276 expansions.
276 expansions.
277
277
278 Extend current configuration by specifying maps as arguments and
278 Extend current configuration by specifying maps as arguments and
279 optionally by reading from an additional hgrc file.
279 optionally by reading from an additional hgrc file.
280
280
281 Override current keyword template maps with "default" option.
281 Override current keyword template maps with "default" option.
282 '''
282 '''
283 def demoitems(section, items):
283 def demoitems(section, items):
284 ui.write('[%s]\n' % section)
284 ui.write('[%s]\n' % section)
285 for k, v in items:
285 for k, v in items:
286 ui.write('%s = %s\n' % (k, v))
286 ui.write('%s = %s\n' % (k, v))
287
287
288 msg = 'hg keyword config and expansion example'
288 msg = 'hg keyword config and expansion example'
289 kwstatus = 'current'
289 kwstatus = 'current'
290 fn = 'demo.txt'
290 fn = 'demo.txt'
291 branchname = 'demobranch'
291 branchname = 'demobranch'
292 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
292 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
293 ui.note(_('creating temporary repository at %s\n') % tmpdir)
293 ui.note(_('creating temporary repository at %s\n') % tmpdir)
294 repo = localrepo.localrepository(ui, tmpdir, True)
294 repo = localrepo.localrepository(ui, tmpdir, True)
295 ui.setconfig('keyword', fn, '')
295 ui.setconfig('keyword', fn, '')
296 if args or opts.get('rcfile'):
296 if args or opts.get('rcfile'):
297 kwstatus = 'custom'
297 kwstatus = 'custom'
298 if opts.get('rcfile'):
298 if opts.get('rcfile'):
299 ui.readconfig(opts.get('rcfile'))
299 ui.readconfig(opts.get('rcfile'))
300 if opts.get('default'):
300 if opts.get('default'):
301 kwstatus = 'default'
301 kwstatus = 'default'
302 kwmaps = kwtemplater.templates
302 kwmaps = kwtemplater.templates
303 if ui.configitems('keywordmaps'):
303 if ui.configitems('keywordmaps'):
304 # override maps from optional rcfile
304 # override maps from optional rcfile
305 for k, v in kwmaps.iteritems():
305 for k, v in kwmaps.iteritems():
306 ui.setconfig('keywordmaps', k, v)
306 ui.setconfig('keywordmaps', k, v)
307 elif args:
307 elif args:
308 # simulate hgrc parsing
308 # simulate hgrc parsing
309 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
309 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
310 fp = repo.opener('hgrc', 'w')
310 fp = repo.opener('hgrc', 'w')
311 fp.writelines(rcmaps)
311 fp.writelines(rcmaps)
312 fp.close()
312 fp.close()
313 ui.readconfig(repo.join('hgrc'))
313 ui.readconfig(repo.join('hgrc'))
314 if not opts.get('default'):
314 if not opts.get('default'):
315 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
315 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
316 uisetup(ui)
316 uisetup(ui)
317 reposetup(ui, repo)
317 reposetup(ui, repo)
318 for k, v in ui.configitems('extensions'):
318 for k, v in ui.configitems('extensions'):
319 if k.endswith('keyword'):
319 if k.endswith('keyword'):
320 extension = '%s = %s' % (k, v)
320 extension = '%s = %s' % (k, v)
321 break
321 break
322 ui.status(_('\n\tconfig using %s keyword template maps\n') % kwstatus)
322 ui.status(_('\n\tconfig using %s keyword template maps\n') % kwstatus)
323 ui.write('[extensions]\n%s\n' % extension)
323 ui.write('[extensions]\n%s\n' % extension)
324 demoitems('keyword', ui.configitems('keyword'))
324 demoitems('keyword', ui.configitems('keyword'))
325 demoitems('keywordmaps', kwmaps.iteritems())
325 demoitems('keywordmaps', kwmaps.iteritems())
326 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
326 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
327 repo.wopener(fn, 'w').write(keywords)
327 repo.wopener(fn, 'w').write(keywords)
328 repo.add([fn])
328 repo.add([fn])
329 path = repo.wjoin(fn)
329 path = repo.wjoin(fn)
330 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
330 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
331 ui.note(keywords)
331 ui.note(keywords)
332 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
332 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
333 # silence branch command if not verbose
333 # silence branch command if not verbose
334 quiet = ui.quiet
334 quiet = ui.quiet
335 ui.quiet = not ui.verbose
335 ui.quiet = not ui.verbose
336 commands.branch(ui, repo, branchname)
336 commands.branch(ui, repo, branchname)
337 ui.quiet = quiet
337 ui.quiet = quiet
338 for name, cmd in ui.configitems('hooks'):
338 for name, cmd in ui.configitems('hooks'):
339 if name.split('.', 1)[0].find('commit') > -1:
339 if name.split('.', 1)[0].find('commit') > -1:
340 repo.ui.setconfig('hooks', name, '')
340 repo.ui.setconfig('hooks', name, '')
341 ui.note(_('unhooked all commit hooks\n'))
341 ui.note(_('unhooked all commit hooks\n'))
342 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
342 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
343 repo.commit(text=msg)
343 repo.commit(text=msg)
344 fmt = ui.verbose and ' in %s' % path or ''
344 fmt = ui.verbose and ' in %s' % path or ''
345 ui.status(_('\n\t%s keywords expanded%s\n') % (kwstatus, fmt))
345 ui.status(_('\n\t%s keywords expanded%s\n') % (kwstatus, fmt))
346 ui.write(repo.wread(fn))
346 ui.write(repo.wread(fn))
347 ui.debug(_('\nremoving temporary repository %s\n') % tmpdir)
347 ui.debug(_('\nremoving temporary repository %s\n') % tmpdir)
348 shutil.rmtree(tmpdir, ignore_errors=True)
348 shutil.rmtree(tmpdir, ignore_errors=True)
349
349
350 def expand(ui, repo, *pats, **opts):
350 def expand(ui, repo, *pats, **opts):
351 '''expand keywords in the working directory
351 '''expand keywords in the working directory
352
352
353 Run after (re)enabling keyword expansion.
353 Run after (re)enabling keyword expansion.
354
354
355 kwexpand refuses to run if given files contain local changes.
355 kwexpand refuses to run if given files contain local changes.
356 '''
356 '''
357 # 3rd argument sets expansion to True
357 # 3rd argument sets expansion to True
358 _kwfwrite(ui, repo, True, *pats, **opts)
358 _kwfwrite(ui, repo, True, *pats, **opts)
359
359
360 def files(ui, repo, *pats, **opts):
360 def files(ui, repo, *pats, **opts):
361 '''show files configured for keyword expansion
361 '''show files configured for keyword expansion
362
362
363 List which files in the working directory are matched by the [keyword]
363 List which files in the working directory are matched by the [keyword]
364 configuration patterns.
364 configuration patterns.
365
365
366 Useful to prevent inadvertent keyword expansion and to speed up execution
366 Useful to prevent inadvertent keyword expansion and to speed up execution
367 by including only files that are actual candidates for expansion.
367 by including only files that are actual candidates for expansion.
368
368
369 See "hg help keyword" on how to construct patterns both for inclusion and
369 See "hg help keyword" on how to construct patterns both for inclusion and
370 exclusion of files.
370 exclusion of files.
371
371
372 Use -u/--untracked to list untracked files as well.
372 Use -u/--untracked to list untracked files as well.
373
373
374 With -a/--all and -v/--verbose the codes used to show the status of files
374 With -a/--all and -v/--verbose the codes used to show the status of files
375 are:
375 are:
376 K = keyword expansion candidate
376 K = keyword expansion candidate
377 k = keyword expansion candidate (untracked)
377 k = keyword expansion candidate (untracked)
378 I = ignored
378 I = ignored
379 i = ignored (untracked)
379 i = ignored (untracked)
380 '''
380 '''
381 kwt = kwtools['templater']
381 kwt = kwtools['templater']
382 status = _status(ui, repo, kwt, opts.get('untracked'), *pats, **opts)
382 status = _status(ui, repo, kwt, opts.get('untracked'), *pats, **opts)
383 modified, added, removed, deleted, unknown, ignored, clean = status
383 modified, added, removed, deleted, unknown, ignored, clean = status
384 files = sorted(modified + added + clean)
384 files = sorted(modified + added + clean)
385 wctx = repo[None]
385 wctx = repo[None]
386 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
386 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
387 kwuntracked = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
387 kwuntracked = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
388 cwd = pats and repo.getcwd() or ''
388 cwd = pats and repo.getcwd() or ''
389 kwfstats = (not opts.get('ignore') and
389 kwfstats = (not opts.get('ignore') and
390 (('K', kwfiles), ('k', kwuntracked),) or ())
390 (('K', kwfiles), ('k', kwuntracked),) or ())
391 if opts.get('all') or opts.get('ignore'):
391 if opts.get('all') or opts.get('ignore'):
392 kwfstats += (('I', [f for f in files if f not in kwfiles]),
392 kwfstats += (('I', [f for f in files if f not in kwfiles]),
393 ('i', [f for f in unknown if f not in kwuntracked]),)
393 ('i', [f for f in unknown if f not in kwuntracked]),)
394 for char, filenames in kwfstats:
394 for char, filenames in kwfstats:
395 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
395 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
396 for f in filenames:
396 for f in filenames:
397 ui.write(fmt % repo.pathto(f, cwd))
397 ui.write(fmt % repo.pathto(f, cwd))
398
398
399 def shrink(ui, repo, *pats, **opts):
399 def shrink(ui, repo, *pats, **opts):
400 '''revert expanded keywords in the working directory
400 '''revert expanded keywords in the working directory
401
401
402 Run before changing/disabling active keywords or if you experience
402 Run before changing/disabling active keywords or if you experience
403 problems with "hg import" or "hg merge".
403 problems with "hg import" or "hg merge".
404
404
405 kwshrink refuses to run if given files contain local changes.
405 kwshrink refuses to run if given files contain local changes.
406 '''
406 '''
407 # 3rd argument sets expansion to False
407 # 3rd argument sets expansion to False
408 _kwfwrite(ui, repo, False, *pats, **opts)
408 _kwfwrite(ui, repo, False, *pats, **opts)
409
409
410
410
411 def uisetup(ui):
411 def uisetup(ui):
412 '''Collects [keyword] config in kwtools.
412 '''Collects [keyword] config in kwtools.
413 Monkeypatches dispatch._parse if needed.'''
413 Monkeypatches dispatch._parse if needed.'''
414
414
415 for pat, opt in ui.configitems('keyword'):
415 for pat, opt in ui.configitems('keyword'):
416 if opt != 'ignore':
416 if opt != 'ignore':
417 kwtools['inc'].append(pat)
417 kwtools['inc'].append(pat)
418 else:
418 else:
419 kwtools['exc'].append(pat)
419 kwtools['exc'].append(pat)
420
420
421 if kwtools['inc']:
421 if kwtools['inc']:
422 def kwdispatch_parse(orig, ui, args):
422 def kwdispatch_parse(orig, ui, args):
423 '''Monkeypatch dispatch._parse to obtain running hg command.'''
423 '''Monkeypatch dispatch._parse to obtain running hg command.'''
424 cmd, func, args, options, cmdoptions = orig(ui, args)
424 cmd, func, args, options, cmdoptions = orig(ui, args)
425 kwtools['hgcmd'] = cmd
425 kwtools['hgcmd'] = cmd
426 return cmd, func, args, options, cmdoptions
426 return cmd, func, args, options, cmdoptions
427
427
428 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
428 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
429
429
430 def reposetup(ui, repo):
430 def reposetup(ui, repo):
431 '''Sets up repo as kwrepo for keyword substitution.
431 '''Sets up repo as kwrepo for keyword substitution.
432 Overrides file method to return kwfilelog instead of filelog
432 Overrides file method to return kwfilelog instead of filelog
433 if file matches user configuration.
433 if file matches user configuration.
434 Wraps commit to overwrite configured files with updated
434 Wraps commit to overwrite configured files with updated
435 keyword substitutions.
435 keyword substitutions.
436 Monkeypatches patch and webcommands.'''
436 Monkeypatches patch and webcommands.'''
437
437
438 try:
438 try:
439 if (not repo.local() or not kwtools['inc']
439 if (not repo.local() or not kwtools['inc']
440 or kwtools['hgcmd'] in nokwcommands.split()
440 or kwtools['hgcmd'] in nokwcommands.split()
441 or '.hg' in util.splitpath(repo.root)
441 or '.hg' in util.splitpath(repo.root)
442 or repo._url.startswith('bundle:')):
442 or repo._url.startswith('bundle:')):
443 return
443 return
444 except AttributeError:
444 except AttributeError:
445 pass
445 pass
446
446
447 kwtools['templater'] = kwt = kwtemplater(ui, repo)
447 kwtools['templater'] = kwt = kwtemplater(ui, repo)
448
448
449 class kwrepo(repo.__class__):
449 class kwrepo(repo.__class__):
450 def file(self, f):
450 def file(self, f):
451 if f[0] == '/':
451 if f[0] == '/':
452 f = f[1:]
452 f = f[1:]
453 return kwfilelog(self.sopener, kwt, f)
453 return kwfilelog(self.sopener, kwt, f)
454
454
455 def wread(self, filename):
455 def wread(self, filename):
456 data = super(kwrepo, self).wread(filename)
456 data = super(kwrepo, self).wread(filename)
457 return kwt.wread(filename, data)
457 return kwt.wread(filename, data)
458
458
459 def commit(self, *args, **opts):
459 def commit(self, *args, **opts):
460 # use custom commitctx for user commands
460 # use custom commitctx for user commands
461 # other extensions can still wrap repo.commitctx directly
461 # other extensions can still wrap repo.commitctx directly
462 self.commitctx = self.kwcommitctx
462 self.commitctx = self.kwcommitctx
463 try:
463 try:
464 return super(kwrepo, self).commit(*args, **opts)
464 return super(kwrepo, self).commit(*args, **opts)
465 finally:
465 finally:
466 del self.commitctx
466 del self.commitctx
467
467
468 def kwcommitctx(self, ctx, error=False):
468 def kwcommitctx(self, ctx, error=False):
469 wlock = lock = None
469 wlock = lock = None
470 try:
470 try:
471 wlock = self.wlock()
471 wlock = self.wlock()
472 lock = self.lock()
472 lock = self.lock()
473 # store and postpone commit hooks
473 # store and postpone commit hooks
474 commithooks = {}
474 commithooks = {}
475 for name, cmd in ui.configitems('hooks'):
475 for name, cmd in ui.configitems('hooks'):
476 if name.split('.', 1)[0] == 'commit':
476 if name.split('.', 1)[0] == 'commit':
477 commithooks[name] = cmd
477 commithooks[name] = cmd
478 ui.setconfig('hooks', name, None)
478 ui.setconfig('hooks', name, None)
479 if commithooks:
479 if commithooks:
480 # store parents for commit hooks
480 # store parents for commit hooks
481 p1, p2 = ctx.p1(), ctx.p2()
481 p1, p2 = ctx.p1(), ctx.p2()
482 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
482 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
483
483
484 n = super(kwrepo, self).commitctx(ctx, error)
484 n = super(kwrepo, self).commitctx(ctx, error)
485
485
486 kwt.overwrite(n, True, None)
486 kwt.overwrite(n, True, None)
487 if commithooks:
487 if commithooks:
488 for name, cmd in commithooks.iteritems():
488 for name, cmd in commithooks.iteritems():
489 ui.setconfig('hooks', name, cmd)
489 ui.setconfig('hooks', name, cmd)
490 self.hook('commit', node=n, parent1=xp1, parent2=xp2)
490 self.hook('commit', node=n, parent1=xp1, parent2=xp2)
491 return n
491 return n
492 finally:
492 finally:
493 release(lock, wlock)
493 release(lock, wlock)
494
494
495 # monkeypatches
495 # monkeypatches
496 def kwpatchfile_init(orig, self, ui, fname, opener,
496 def kwpatchfile_init(orig, self, ui, fname, opener,
497 missing=False, eol=None):
497 missing=False, eol=None):
498 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
498 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
499 rejects or conflicts due to expanded keywords in working dir.'''
499 rejects or conflicts due to expanded keywords in working dir.'''
500 orig(self, ui, fname, opener, missing, eol)
500 orig(self, ui, fname, opener, missing, eol)
501 # shrink keywords read from working dir
501 # shrink keywords read from working dir
502 self.lines = kwt.shrinklines(self.fname, self.lines)
502 self.lines = kwt.shrinklines(self.fname, self.lines)
503
503
504 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
504 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
505 opts=None):
505 opts=None):
506 '''Monkeypatch patch.diff to avoid expansion except when
506 '''Monkeypatch patch.diff to avoid expansion except when
507 comparing against working dir.'''
507 comparing against working dir.'''
508 if node2 is not None:
508 if node2 is not None:
509 kwt.match = util.never
509 kwt.match = util.never
510 elif node1 is not None and node1 != repo['.'].node():
510 elif node1 is not None and node1 != repo['.'].node():
511 kwt.restrict = True
511 kwt.restrict = True
512 return orig(repo, node1, node2, match, changes, opts)
512 return orig(repo, node1, node2, match, changes, opts)
513
513
514 def kwweb_skip(orig, web, req, tmpl):
514 def kwweb_skip(orig, web, req, tmpl):
515 '''Wraps webcommands.x turning off keyword expansion.'''
515 '''Wraps webcommands.x turning off keyword expansion.'''
516 kwt.match = util.never
516 kwt.match = util.never
517 return orig(web, req, tmpl)
517 return orig(web, req, tmpl)
518
518
519 repo.__class__ = kwrepo
519 repo.__class__ = kwrepo
520
520
521 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
521 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
522 extensions.wrapfunction(patch, 'diff', kw_diff)
522 extensions.wrapfunction(patch, 'diff', kw_diff)
523 for c in 'annotate changeset rev filediff diff'.split():
523 for c in 'annotate changeset rev filediff diff'.split():
524 extensions.wrapfunction(webcommands, c, kwweb_skip)
524 extensions.wrapfunction(webcommands, c, kwweb_skip)
525
525
526 cmdtable = {
526 cmdtable = {
527 'kwdemo':
527 'kwdemo':
528 (demo,
528 (demo,
529 [('d', 'default', None, _('show default keyword template maps')),
529 [('d', 'default', None, _('show default keyword template maps')),
530 ('f', 'rcfile', [], _('read maps from rcfile'))],
530 ('f', 'rcfile', [], _('read maps from rcfile'))],
531 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
531 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
532 'kwexpand': (expand, commands.walkopts,
532 'kwexpand': (expand, commands.walkopts,
533 _('hg kwexpand [OPTION]... [FILE]...')),
533 _('hg kwexpand [OPTION]... [FILE]...')),
534 'kwfiles':
534 'kwfiles':
535 (files,
535 (files,
536 [('a', 'all', None, _('show keyword status flags of all files')),
536 [('a', 'all', None, _('show keyword status flags of all files')),
537 ('i', 'ignore', None, _('show files excluded from expansion')),
537 ('i', 'ignore', None, _('show files excluded from expansion')),
538 ('u', 'untracked', None, _('additionally show untracked files')),
538 ('u', 'untracked', None, _('additionally show untracked files')),
539 ] + commands.walkopts,
539 ] + commands.walkopts,
540 _('hg kwfiles [OPTION]... [FILE]...')),
540 _('hg kwfiles [OPTION]... [FILE]...')),
541 'kwshrink': (shrink, commands.walkopts,
541 'kwshrink': (shrink, commands.walkopts,
542 _('hg kwshrink [OPTION]... [FILE]...')),
542 _('hg kwshrink [OPTION]... [FILE]...')),
543 }
543 }
@@ -1,2618 +1,2618 b''
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 '''manage a stack of patches
8 '''manage a stack of patches
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and applied
11 repository. It manages two stacks of patches - all known patches, and applied
12 patches (subset of known patches).
12 patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches directory.
14 Known patches are represented as patch files in the .hg/patches directory.
15 Applied patches are both patch files and changesets.
15 Applied patches are both patch files and changesets.
16
16
17 Common tasks (use "hg help command" for more details):
17 Common tasks (use "hg help command" for more details)::
18
18
19 prepare repository to work with patches qinit
19 prepare repository to work with patches qinit
20 create new patch qnew
20 create new patch qnew
21 import existing patch qimport
21 import existing patch qimport
22
22
23 print patch series qseries
23 print patch series qseries
24 print applied patches qapplied
24 print applied patches qapplied
25 print name of top applied patch qtop
25 print name of top applied patch qtop
26
26
27 add known patch to applied stack qpush
27 add known patch to applied stack qpush
28 remove patch from applied stack qpop
28 remove patch from applied stack qpop
29 refresh contents of top applied patch qrefresh
29 refresh contents of top applied patch qrefresh
30 '''
30 '''
31
31
32 from mercurial.i18n import _
32 from mercurial.i18n import _
33 from mercurial.node import bin, hex, short, nullid, nullrev
33 from mercurial.node import bin, hex, short, nullid, nullrev
34 from mercurial.lock import release
34 from mercurial.lock import release
35 from mercurial import commands, cmdutil, hg, patch, util
35 from mercurial import commands, cmdutil, hg, patch, util
36 from mercurial import repair, extensions, url, error
36 from mercurial import repair, extensions, url, error
37 import os, sys, re, errno
37 import os, sys, re, errno
38
38
39 commands.norepo += " qclone"
39 commands.norepo += " qclone"
40
40
41 # Patch names looks like unix-file names.
41 # Patch names looks like unix-file names.
42 # They must be joinable with queue directory and result in the patch path.
42 # They must be joinable with queue directory and result in the patch path.
43 normname = util.normpath
43 normname = util.normpath
44
44
45 class statusentry(object):
45 class statusentry(object):
46 def __init__(self, rev, name=None):
46 def __init__(self, rev, name=None):
47 if not name:
47 if not name:
48 fields = rev.split(':', 1)
48 fields = rev.split(':', 1)
49 if len(fields) == 2:
49 if len(fields) == 2:
50 self.rev, self.name = fields
50 self.rev, self.name = fields
51 else:
51 else:
52 self.rev, self.name = None, None
52 self.rev, self.name = None, None
53 else:
53 else:
54 self.rev, self.name = rev, name
54 self.rev, self.name = rev, name
55
55
56 def __str__(self):
56 def __str__(self):
57 return self.rev + ':' + self.name
57 return self.rev + ':' + self.name
58
58
59 class patchheader(object):
59 class patchheader(object):
60 def __init__(self, pf):
60 def __init__(self, pf):
61 def eatdiff(lines):
61 def eatdiff(lines):
62 while lines:
62 while lines:
63 l = lines[-1]
63 l = lines[-1]
64 if (l.startswith("diff -") or
64 if (l.startswith("diff -") or
65 l.startswith("Index:") or
65 l.startswith("Index:") or
66 l.startswith("===========")):
66 l.startswith("===========")):
67 del lines[-1]
67 del lines[-1]
68 else:
68 else:
69 break
69 break
70 def eatempty(lines):
70 def eatempty(lines):
71 while lines:
71 while lines:
72 l = lines[-1]
72 l = lines[-1]
73 if re.match('\s*$', l):
73 if re.match('\s*$', l):
74 del lines[-1]
74 del lines[-1]
75 else:
75 else:
76 break
76 break
77
77
78 message = []
78 message = []
79 comments = []
79 comments = []
80 user = None
80 user = None
81 date = None
81 date = None
82 format = None
82 format = None
83 subject = None
83 subject = None
84 diffstart = 0
84 diffstart = 0
85
85
86 for line in file(pf):
86 for line in file(pf):
87 line = line.rstrip()
87 line = line.rstrip()
88 if line.startswith('diff --git'):
88 if line.startswith('diff --git'):
89 diffstart = 2
89 diffstart = 2
90 break
90 break
91 if diffstart:
91 if diffstart:
92 if line.startswith('+++ '):
92 if line.startswith('+++ '):
93 diffstart = 2
93 diffstart = 2
94 break
94 break
95 if line.startswith("--- "):
95 if line.startswith("--- "):
96 diffstart = 1
96 diffstart = 1
97 continue
97 continue
98 elif format == "hgpatch":
98 elif format == "hgpatch":
99 # parse values when importing the result of an hg export
99 # parse values when importing the result of an hg export
100 if line.startswith("# User "):
100 if line.startswith("# User "):
101 user = line[7:]
101 user = line[7:]
102 elif line.startswith("# Date "):
102 elif line.startswith("# Date "):
103 date = line[7:]
103 date = line[7:]
104 elif not line.startswith("# ") and line:
104 elif not line.startswith("# ") and line:
105 message.append(line)
105 message.append(line)
106 format = None
106 format = None
107 elif line == '# HG changeset patch':
107 elif line == '# HG changeset patch':
108 format = "hgpatch"
108 format = "hgpatch"
109 elif (format != "tagdone" and (line.startswith("Subject: ") or
109 elif (format != "tagdone" and (line.startswith("Subject: ") or
110 line.startswith("subject: "))):
110 line.startswith("subject: "))):
111 subject = line[9:]
111 subject = line[9:]
112 format = "tag"
112 format = "tag"
113 elif (format != "tagdone" and (line.startswith("From: ") or
113 elif (format != "tagdone" and (line.startswith("From: ") or
114 line.startswith("from: "))):
114 line.startswith("from: "))):
115 user = line[6:]
115 user = line[6:]
116 format = "tag"
116 format = "tag"
117 elif format == "tag" and line == "":
117 elif format == "tag" and line == "":
118 # when looking for tags (subject: from: etc) they
118 # when looking for tags (subject: from: etc) they
119 # end once you find a blank line in the source
119 # end once you find a blank line in the source
120 format = "tagdone"
120 format = "tagdone"
121 elif message or line:
121 elif message or line:
122 message.append(line)
122 message.append(line)
123 comments.append(line)
123 comments.append(line)
124
124
125 eatdiff(message)
125 eatdiff(message)
126 eatdiff(comments)
126 eatdiff(comments)
127 eatempty(message)
127 eatempty(message)
128 eatempty(comments)
128 eatempty(comments)
129
129
130 # make sure message isn't empty
130 # make sure message isn't empty
131 if format and format.startswith("tag") and subject:
131 if format and format.startswith("tag") and subject:
132 message.insert(0, "")
132 message.insert(0, "")
133 message.insert(0, subject)
133 message.insert(0, subject)
134
134
135 self.message = message
135 self.message = message
136 self.comments = comments
136 self.comments = comments
137 self.user = user
137 self.user = user
138 self.date = date
138 self.date = date
139 self.haspatch = diffstart > 1
139 self.haspatch = diffstart > 1
140
140
141 def setuser(self, user):
141 def setuser(self, user):
142 if not self.updateheader(['From: ', '# User '], user):
142 if not self.updateheader(['From: ', '# User '], user):
143 try:
143 try:
144 patchheaderat = self.comments.index('# HG changeset patch')
144 patchheaderat = self.comments.index('# HG changeset patch')
145 self.comments.insert(patchheaderat + 1,'# User ' + user)
145 self.comments.insert(patchheaderat + 1,'# User ' + user)
146 except ValueError:
146 except ValueError:
147 self.comments = ['From: ' + user, ''] + self.comments
147 self.comments = ['From: ' + user, ''] + self.comments
148 self.user = user
148 self.user = user
149
149
150 def setdate(self, date):
150 def setdate(self, date):
151 if self.updateheader(['# Date '], date):
151 if self.updateheader(['# Date '], date):
152 self.date = date
152 self.date = date
153
153
154 def setmessage(self, message):
154 def setmessage(self, message):
155 if self.comments:
155 if self.comments:
156 self._delmsg()
156 self._delmsg()
157 self.message = [message]
157 self.message = [message]
158 self.comments += self.message
158 self.comments += self.message
159
159
160 def updateheader(self, prefixes, new):
160 def updateheader(self, prefixes, new):
161 '''Update all references to a field in the patch header.
161 '''Update all references to a field in the patch header.
162 Return whether the field is present.'''
162 Return whether the field is present.'''
163 res = False
163 res = False
164 for prefix in prefixes:
164 for prefix in prefixes:
165 for i in xrange(len(self.comments)):
165 for i in xrange(len(self.comments)):
166 if self.comments[i].startswith(prefix):
166 if self.comments[i].startswith(prefix):
167 self.comments[i] = prefix + new
167 self.comments[i] = prefix + new
168 res = True
168 res = True
169 break
169 break
170 return res
170 return res
171
171
172 def __str__(self):
172 def __str__(self):
173 if not self.comments:
173 if not self.comments:
174 return ''
174 return ''
175 return '\n'.join(self.comments) + '\n\n'
175 return '\n'.join(self.comments) + '\n\n'
176
176
177 def _delmsg(self):
177 def _delmsg(self):
178 '''Remove existing message, keeping the rest of the comments fields.
178 '''Remove existing message, keeping the rest of the comments fields.
179 If comments contains 'subject: ', message will prepend
179 If comments contains 'subject: ', message will prepend
180 the field and a blank line.'''
180 the field and a blank line.'''
181 if self.message:
181 if self.message:
182 subj = 'subject: ' + self.message[0].lower()
182 subj = 'subject: ' + self.message[0].lower()
183 for i in xrange(len(self.comments)):
183 for i in xrange(len(self.comments)):
184 if subj == self.comments[i].lower():
184 if subj == self.comments[i].lower():
185 del self.comments[i]
185 del self.comments[i]
186 self.message = self.message[2:]
186 self.message = self.message[2:]
187 break
187 break
188 ci = 0
188 ci = 0
189 for mi in self.message:
189 for mi in self.message:
190 while mi != self.comments[ci]:
190 while mi != self.comments[ci]:
191 ci += 1
191 ci += 1
192 del self.comments[ci]
192 del self.comments[ci]
193
193
194 class queue(object):
194 class queue(object):
195 def __init__(self, ui, path, patchdir=None):
195 def __init__(self, ui, path, patchdir=None):
196 self.basepath = path
196 self.basepath = path
197 self.path = patchdir or os.path.join(path, "patches")
197 self.path = patchdir or os.path.join(path, "patches")
198 self.opener = util.opener(self.path)
198 self.opener = util.opener(self.path)
199 self.ui = ui
199 self.ui = ui
200 self.applied_dirty = 0
200 self.applied_dirty = 0
201 self.series_dirty = 0
201 self.series_dirty = 0
202 self.series_path = "series"
202 self.series_path = "series"
203 self.status_path = "status"
203 self.status_path = "status"
204 self.guards_path = "guards"
204 self.guards_path = "guards"
205 self.active_guards = None
205 self.active_guards = None
206 self.guards_dirty = False
206 self.guards_dirty = False
207 self._diffopts = None
207 self._diffopts = None
208
208
209 @util.propertycache
209 @util.propertycache
210 def applied(self):
210 def applied(self):
211 if os.path.exists(self.join(self.status_path)):
211 if os.path.exists(self.join(self.status_path)):
212 lines = self.opener(self.status_path).read().splitlines()
212 lines = self.opener(self.status_path).read().splitlines()
213 return [statusentry(l) for l in lines]
213 return [statusentry(l) for l in lines]
214 return []
214 return []
215
215
216 @util.propertycache
216 @util.propertycache
217 def full_series(self):
217 def full_series(self):
218 if os.path.exists(self.join(self.series_path)):
218 if os.path.exists(self.join(self.series_path)):
219 return self.opener(self.series_path).read().splitlines()
219 return self.opener(self.series_path).read().splitlines()
220 return []
220 return []
221
221
222 @util.propertycache
222 @util.propertycache
223 def series(self):
223 def series(self):
224 self.parse_series()
224 self.parse_series()
225 return self.series
225 return self.series
226
226
227 @util.propertycache
227 @util.propertycache
228 def series_guards(self):
228 def series_guards(self):
229 self.parse_series()
229 self.parse_series()
230 return self.series_guards
230 return self.series_guards
231
231
232 def invalidate(self):
232 def invalidate(self):
233 for a in 'applied full_series series series_guards'.split():
233 for a in 'applied full_series series series_guards'.split():
234 if a in self.__dict__:
234 if a in self.__dict__:
235 delattr(self, a)
235 delattr(self, a)
236 self.applied_dirty = 0
236 self.applied_dirty = 0
237 self.series_dirty = 0
237 self.series_dirty = 0
238 self.guards_dirty = False
238 self.guards_dirty = False
239 self.active_guards = None
239 self.active_guards = None
240
240
241 def diffopts(self):
241 def diffopts(self):
242 if self._diffopts is None:
242 if self._diffopts is None:
243 self._diffopts = patch.diffopts(self.ui)
243 self._diffopts = patch.diffopts(self.ui)
244 return self._diffopts
244 return self._diffopts
245
245
246 def join(self, *p):
246 def join(self, *p):
247 return os.path.join(self.path, *p)
247 return os.path.join(self.path, *p)
248
248
249 def find_series(self, patch):
249 def find_series(self, patch):
250 pre = re.compile("(\s*)([^#]+)")
250 pre = re.compile("(\s*)([^#]+)")
251 index = 0
251 index = 0
252 for l in self.full_series:
252 for l in self.full_series:
253 m = pre.match(l)
253 m = pre.match(l)
254 if m:
254 if m:
255 s = m.group(2)
255 s = m.group(2)
256 s = s.rstrip()
256 s = s.rstrip()
257 if s == patch:
257 if s == patch:
258 return index
258 return index
259 index += 1
259 index += 1
260 return None
260 return None
261
261
262 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
262 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
263
263
264 def parse_series(self):
264 def parse_series(self):
265 self.series = []
265 self.series = []
266 self.series_guards = []
266 self.series_guards = []
267 for l in self.full_series:
267 for l in self.full_series:
268 h = l.find('#')
268 h = l.find('#')
269 if h == -1:
269 if h == -1:
270 patch = l
270 patch = l
271 comment = ''
271 comment = ''
272 elif h == 0:
272 elif h == 0:
273 continue
273 continue
274 else:
274 else:
275 patch = l[:h]
275 patch = l[:h]
276 comment = l[h:]
276 comment = l[h:]
277 patch = patch.strip()
277 patch = patch.strip()
278 if patch:
278 if patch:
279 if patch in self.series:
279 if patch in self.series:
280 raise util.Abort(_('%s appears more than once in %s') %
280 raise util.Abort(_('%s appears more than once in %s') %
281 (patch, self.join(self.series_path)))
281 (patch, self.join(self.series_path)))
282 self.series.append(patch)
282 self.series.append(patch)
283 self.series_guards.append(self.guard_re.findall(comment))
283 self.series_guards.append(self.guard_re.findall(comment))
284
284
285 def check_guard(self, guard):
285 def check_guard(self, guard):
286 if not guard:
286 if not guard:
287 return _('guard cannot be an empty string')
287 return _('guard cannot be an empty string')
288 bad_chars = '# \t\r\n\f'
288 bad_chars = '# \t\r\n\f'
289 first = guard[0]
289 first = guard[0]
290 if first in '-+':
290 if first in '-+':
291 return (_('guard %r starts with invalid character: %r') %
291 return (_('guard %r starts with invalid character: %r') %
292 (guard, first))
292 (guard, first))
293 for c in bad_chars:
293 for c in bad_chars:
294 if c in guard:
294 if c in guard:
295 return _('invalid character in guard %r: %r') % (guard, c)
295 return _('invalid character in guard %r: %r') % (guard, c)
296
296
297 def set_active(self, guards):
297 def set_active(self, guards):
298 for guard in guards:
298 for guard in guards:
299 bad = self.check_guard(guard)
299 bad = self.check_guard(guard)
300 if bad:
300 if bad:
301 raise util.Abort(bad)
301 raise util.Abort(bad)
302 guards = sorted(set(guards))
302 guards = sorted(set(guards))
303 self.ui.debug(_('active guards: %s\n') % ' '.join(guards))
303 self.ui.debug(_('active guards: %s\n') % ' '.join(guards))
304 self.active_guards = guards
304 self.active_guards = guards
305 self.guards_dirty = True
305 self.guards_dirty = True
306
306
307 def active(self):
307 def active(self):
308 if self.active_guards is None:
308 if self.active_guards is None:
309 self.active_guards = []
309 self.active_guards = []
310 try:
310 try:
311 guards = self.opener(self.guards_path).read().split()
311 guards = self.opener(self.guards_path).read().split()
312 except IOError, err:
312 except IOError, err:
313 if err.errno != errno.ENOENT: raise
313 if err.errno != errno.ENOENT: raise
314 guards = []
314 guards = []
315 for i, guard in enumerate(guards):
315 for i, guard in enumerate(guards):
316 bad = self.check_guard(guard)
316 bad = self.check_guard(guard)
317 if bad:
317 if bad:
318 self.ui.warn('%s:%d: %s\n' %
318 self.ui.warn('%s:%d: %s\n' %
319 (self.join(self.guards_path), i + 1, bad))
319 (self.join(self.guards_path), i + 1, bad))
320 else:
320 else:
321 self.active_guards.append(guard)
321 self.active_guards.append(guard)
322 return self.active_guards
322 return self.active_guards
323
323
324 def set_guards(self, idx, guards):
324 def set_guards(self, idx, guards):
325 for g in guards:
325 for g in guards:
326 if len(g) < 2:
326 if len(g) < 2:
327 raise util.Abort(_('guard %r too short') % g)
327 raise util.Abort(_('guard %r too short') % g)
328 if g[0] not in '-+':
328 if g[0] not in '-+':
329 raise util.Abort(_('guard %r starts with invalid char') % g)
329 raise util.Abort(_('guard %r starts with invalid char') % g)
330 bad = self.check_guard(g[1:])
330 bad = self.check_guard(g[1:])
331 if bad:
331 if bad:
332 raise util.Abort(bad)
332 raise util.Abort(bad)
333 drop = self.guard_re.sub('', self.full_series[idx])
333 drop = self.guard_re.sub('', self.full_series[idx])
334 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
334 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
335 self.parse_series()
335 self.parse_series()
336 self.series_dirty = True
336 self.series_dirty = True
337
337
338 def pushable(self, idx):
338 def pushable(self, idx):
339 if isinstance(idx, str):
339 if isinstance(idx, str):
340 idx = self.series.index(idx)
340 idx = self.series.index(idx)
341 patchguards = self.series_guards[idx]
341 patchguards = self.series_guards[idx]
342 if not patchguards:
342 if not patchguards:
343 return True, None
343 return True, None
344 guards = self.active()
344 guards = self.active()
345 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
345 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
346 if exactneg:
346 if exactneg:
347 return False, exactneg[0]
347 return False, exactneg[0]
348 pos = [g for g in patchguards if g[0] == '+']
348 pos = [g for g in patchguards if g[0] == '+']
349 exactpos = [g for g in pos if g[1:] in guards]
349 exactpos = [g for g in pos if g[1:] in guards]
350 if pos:
350 if pos:
351 if exactpos:
351 if exactpos:
352 return True, exactpos[0]
352 return True, exactpos[0]
353 return False, pos
353 return False, pos
354 return True, ''
354 return True, ''
355
355
356 def explain_pushable(self, idx, all_patches=False):
356 def explain_pushable(self, idx, all_patches=False):
357 write = all_patches and self.ui.write or self.ui.warn
357 write = all_patches and self.ui.write or self.ui.warn
358 if all_patches or self.ui.verbose:
358 if all_patches or self.ui.verbose:
359 if isinstance(idx, str):
359 if isinstance(idx, str):
360 idx = self.series.index(idx)
360 idx = self.series.index(idx)
361 pushable, why = self.pushable(idx)
361 pushable, why = self.pushable(idx)
362 if all_patches and pushable:
362 if all_patches and pushable:
363 if why is None:
363 if why is None:
364 write(_('allowing %s - no guards in effect\n') %
364 write(_('allowing %s - no guards in effect\n') %
365 self.series[idx])
365 self.series[idx])
366 else:
366 else:
367 if not why:
367 if not why:
368 write(_('allowing %s - no matching negative guards\n') %
368 write(_('allowing %s - no matching negative guards\n') %
369 self.series[idx])
369 self.series[idx])
370 else:
370 else:
371 write(_('allowing %s - guarded by %r\n') %
371 write(_('allowing %s - guarded by %r\n') %
372 (self.series[idx], why))
372 (self.series[idx], why))
373 if not pushable:
373 if not pushable:
374 if why:
374 if why:
375 write(_('skipping %s - guarded by %r\n') %
375 write(_('skipping %s - guarded by %r\n') %
376 (self.series[idx], why))
376 (self.series[idx], why))
377 else:
377 else:
378 write(_('skipping %s - no matching guards\n') %
378 write(_('skipping %s - no matching guards\n') %
379 self.series[idx])
379 self.series[idx])
380
380
381 def save_dirty(self):
381 def save_dirty(self):
382 def write_list(items, path):
382 def write_list(items, path):
383 fp = self.opener(path, 'w')
383 fp = self.opener(path, 'w')
384 for i in items:
384 for i in items:
385 fp.write("%s\n" % i)
385 fp.write("%s\n" % i)
386 fp.close()
386 fp.close()
387 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
387 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
388 if self.series_dirty: write_list(self.full_series, self.series_path)
388 if self.series_dirty: write_list(self.full_series, self.series_path)
389 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
389 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
390
390
391 def removeundo(self, repo):
391 def removeundo(self, repo):
392 undo = repo.sjoin('undo')
392 undo = repo.sjoin('undo')
393 if not os.path.exists(undo):
393 if not os.path.exists(undo):
394 return
394 return
395 try:
395 try:
396 os.unlink(undo)
396 os.unlink(undo)
397 except OSError, inst:
397 except OSError, inst:
398 self.ui.warn(_('error removing undo: %s\n') % str(inst))
398 self.ui.warn(_('error removing undo: %s\n') % str(inst))
399
399
400 def printdiff(self, repo, node1, node2=None, files=None,
400 def printdiff(self, repo, node1, node2=None, files=None,
401 fp=None, changes=None, opts={}):
401 fp=None, changes=None, opts={}):
402 m = cmdutil.match(repo, files, opts)
402 m = cmdutil.match(repo, files, opts)
403 chunks = patch.diff(repo, node1, node2, m, changes, self.diffopts())
403 chunks = patch.diff(repo, node1, node2, m, changes, self.diffopts())
404 write = fp is None and repo.ui.write or fp.write
404 write = fp is None and repo.ui.write or fp.write
405 for chunk in chunks:
405 for chunk in chunks:
406 write(chunk)
406 write(chunk)
407
407
408 def mergeone(self, repo, mergeq, head, patch, rev):
408 def mergeone(self, repo, mergeq, head, patch, rev):
409 # first try just applying the patch
409 # first try just applying the patch
410 (err, n) = self.apply(repo, [ patch ], update_status=False,
410 (err, n) = self.apply(repo, [ patch ], update_status=False,
411 strict=True, merge=rev)
411 strict=True, merge=rev)
412
412
413 if err == 0:
413 if err == 0:
414 return (err, n)
414 return (err, n)
415
415
416 if n is None:
416 if n is None:
417 raise util.Abort(_("apply failed for patch %s") % patch)
417 raise util.Abort(_("apply failed for patch %s") % patch)
418
418
419 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
419 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
420
420
421 # apply failed, strip away that rev and merge.
421 # apply failed, strip away that rev and merge.
422 hg.clean(repo, head)
422 hg.clean(repo, head)
423 self.strip(repo, n, update=False, backup='strip')
423 self.strip(repo, n, update=False, backup='strip')
424
424
425 ctx = repo[rev]
425 ctx = repo[rev]
426 ret = hg.merge(repo, rev)
426 ret = hg.merge(repo, rev)
427 if ret:
427 if ret:
428 raise util.Abort(_("update returned %d") % ret)
428 raise util.Abort(_("update returned %d") % ret)
429 n = repo.commit(ctx.description(), ctx.user(), force=True)
429 n = repo.commit(ctx.description(), ctx.user(), force=True)
430 if n is None:
430 if n is None:
431 raise util.Abort(_("repo commit failed"))
431 raise util.Abort(_("repo commit failed"))
432 try:
432 try:
433 ph = patchheader(mergeq.join(patch))
433 ph = patchheader(mergeq.join(patch))
434 except:
434 except:
435 raise util.Abort(_("unable to read %s") % patch)
435 raise util.Abort(_("unable to read %s") % patch)
436
436
437 patchf = self.opener(patch, "w")
437 patchf = self.opener(patch, "w")
438 comments = str(ph)
438 comments = str(ph)
439 if comments:
439 if comments:
440 patchf.write(comments)
440 patchf.write(comments)
441 self.printdiff(repo, head, n, fp=patchf)
441 self.printdiff(repo, head, n, fp=patchf)
442 patchf.close()
442 patchf.close()
443 self.removeundo(repo)
443 self.removeundo(repo)
444 return (0, n)
444 return (0, n)
445
445
446 def qparents(self, repo, rev=None):
446 def qparents(self, repo, rev=None):
447 if rev is None:
447 if rev is None:
448 (p1, p2) = repo.dirstate.parents()
448 (p1, p2) = repo.dirstate.parents()
449 if p2 == nullid:
449 if p2 == nullid:
450 return p1
450 return p1
451 if len(self.applied) == 0:
451 if len(self.applied) == 0:
452 return None
452 return None
453 return bin(self.applied[-1].rev)
453 return bin(self.applied[-1].rev)
454 pp = repo.changelog.parents(rev)
454 pp = repo.changelog.parents(rev)
455 if pp[1] != nullid:
455 if pp[1] != nullid:
456 arevs = [ x.rev for x in self.applied ]
456 arevs = [ x.rev for x in self.applied ]
457 p0 = hex(pp[0])
457 p0 = hex(pp[0])
458 p1 = hex(pp[1])
458 p1 = hex(pp[1])
459 if p0 in arevs:
459 if p0 in arevs:
460 return pp[0]
460 return pp[0]
461 if p1 in arevs:
461 if p1 in arevs:
462 return pp[1]
462 return pp[1]
463 return pp[0]
463 return pp[0]
464
464
465 def mergepatch(self, repo, mergeq, series):
465 def mergepatch(self, repo, mergeq, series):
466 if len(self.applied) == 0:
466 if len(self.applied) == 0:
467 # each of the patches merged in will have two parents. This
467 # each of the patches merged in will have two parents. This
468 # can confuse the qrefresh, qdiff, and strip code because it
468 # can confuse the qrefresh, qdiff, and strip code because it
469 # needs to know which parent is actually in the patch queue.
469 # needs to know which parent is actually in the patch queue.
470 # so, we insert a merge marker with only one parent. This way
470 # so, we insert a merge marker with only one parent. This way
471 # the first patch in the queue is never a merge patch
471 # the first patch in the queue is never a merge patch
472 #
472 #
473 pname = ".hg.patches.merge.marker"
473 pname = ".hg.patches.merge.marker"
474 n = repo.commit('[mq]: merge marker', force=True)
474 n = repo.commit('[mq]: merge marker', force=True)
475 self.removeundo(repo)
475 self.removeundo(repo)
476 self.applied.append(statusentry(hex(n), pname))
476 self.applied.append(statusentry(hex(n), pname))
477 self.applied_dirty = 1
477 self.applied_dirty = 1
478
478
479 head = self.qparents(repo)
479 head = self.qparents(repo)
480
480
481 for patch in series:
481 for patch in series:
482 patch = mergeq.lookup(patch, strict=True)
482 patch = mergeq.lookup(patch, strict=True)
483 if not patch:
483 if not patch:
484 self.ui.warn(_("patch %s does not exist\n") % patch)
484 self.ui.warn(_("patch %s does not exist\n") % patch)
485 return (1, None)
485 return (1, None)
486 pushable, reason = self.pushable(patch)
486 pushable, reason = self.pushable(patch)
487 if not pushable:
487 if not pushable:
488 self.explain_pushable(patch, all_patches=True)
488 self.explain_pushable(patch, all_patches=True)
489 continue
489 continue
490 info = mergeq.isapplied(patch)
490 info = mergeq.isapplied(patch)
491 if not info:
491 if not info:
492 self.ui.warn(_("patch %s is not applied\n") % patch)
492 self.ui.warn(_("patch %s is not applied\n") % patch)
493 return (1, None)
493 return (1, None)
494 rev = bin(info[1])
494 rev = bin(info[1])
495 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
495 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
496 if head:
496 if head:
497 self.applied.append(statusentry(hex(head), patch))
497 self.applied.append(statusentry(hex(head), patch))
498 self.applied_dirty = 1
498 self.applied_dirty = 1
499 if err:
499 if err:
500 return (err, head)
500 return (err, head)
501 self.save_dirty()
501 self.save_dirty()
502 return (0, head)
502 return (0, head)
503
503
504 def patch(self, repo, patchfile):
504 def patch(self, repo, patchfile):
505 '''Apply patchfile to the working directory.
505 '''Apply patchfile to the working directory.
506 patchfile: name of patch file'''
506 patchfile: name of patch file'''
507 files = {}
507 files = {}
508 try:
508 try:
509 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
509 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
510 files=files, eolmode=None)
510 files=files, eolmode=None)
511 except Exception, inst:
511 except Exception, inst:
512 self.ui.note(str(inst) + '\n')
512 self.ui.note(str(inst) + '\n')
513 if not self.ui.verbose:
513 if not self.ui.verbose:
514 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
514 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
515 return (False, files, False)
515 return (False, files, False)
516
516
517 return (True, files, fuzz)
517 return (True, files, fuzz)
518
518
519 def apply(self, repo, series, list=False, update_status=True,
519 def apply(self, repo, series, list=False, update_status=True,
520 strict=False, patchdir=None, merge=None, all_files={}):
520 strict=False, patchdir=None, merge=None, all_files={}):
521 wlock = lock = tr = None
521 wlock = lock = tr = None
522 try:
522 try:
523 wlock = repo.wlock()
523 wlock = repo.wlock()
524 lock = repo.lock()
524 lock = repo.lock()
525 tr = repo.transaction()
525 tr = repo.transaction()
526 try:
526 try:
527 ret = self._apply(repo, series, list, update_status,
527 ret = self._apply(repo, series, list, update_status,
528 strict, patchdir, merge, all_files=all_files)
528 strict, patchdir, merge, all_files=all_files)
529 tr.close()
529 tr.close()
530 self.save_dirty()
530 self.save_dirty()
531 return ret
531 return ret
532 except:
532 except:
533 try:
533 try:
534 tr.abort()
534 tr.abort()
535 finally:
535 finally:
536 repo.invalidate()
536 repo.invalidate()
537 repo.dirstate.invalidate()
537 repo.dirstate.invalidate()
538 raise
538 raise
539 finally:
539 finally:
540 del tr
540 del tr
541 release(lock, wlock)
541 release(lock, wlock)
542 self.removeundo(repo)
542 self.removeundo(repo)
543
543
544 def _apply(self, repo, series, list=False, update_status=True,
544 def _apply(self, repo, series, list=False, update_status=True,
545 strict=False, patchdir=None, merge=None, all_files={}):
545 strict=False, patchdir=None, merge=None, all_files={}):
546 '''returns (error, hash)
546 '''returns (error, hash)
547 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
547 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
548 # TODO unify with commands.py
548 # TODO unify with commands.py
549 if not patchdir:
549 if not patchdir:
550 patchdir = self.path
550 patchdir = self.path
551 err = 0
551 err = 0
552 n = None
552 n = None
553 for patchname in series:
553 for patchname in series:
554 pushable, reason = self.pushable(patchname)
554 pushable, reason = self.pushable(patchname)
555 if not pushable:
555 if not pushable:
556 self.explain_pushable(patchname, all_patches=True)
556 self.explain_pushable(patchname, all_patches=True)
557 continue
557 continue
558 self.ui.status(_("applying %s\n") % patchname)
558 self.ui.status(_("applying %s\n") % patchname)
559 pf = os.path.join(patchdir, patchname)
559 pf = os.path.join(patchdir, patchname)
560
560
561 try:
561 try:
562 ph = patchheader(self.join(patchname))
562 ph = patchheader(self.join(patchname))
563 except:
563 except:
564 self.ui.warn(_("unable to read %s\n") % patchname)
564 self.ui.warn(_("unable to read %s\n") % patchname)
565 err = 1
565 err = 1
566 break
566 break
567
567
568 message = ph.message
568 message = ph.message
569 if not message:
569 if not message:
570 message = _("imported patch %s\n") % patchname
570 message = _("imported patch %s\n") % patchname
571 else:
571 else:
572 if list:
572 if list:
573 message.append(_("\nimported patch %s") % patchname)
573 message.append(_("\nimported patch %s") % patchname)
574 message = '\n'.join(message)
574 message = '\n'.join(message)
575
575
576 if ph.haspatch:
576 if ph.haspatch:
577 (patcherr, files, fuzz) = self.patch(repo, pf)
577 (patcherr, files, fuzz) = self.patch(repo, pf)
578 all_files.update(files)
578 all_files.update(files)
579 patcherr = not patcherr
579 patcherr = not patcherr
580 else:
580 else:
581 self.ui.warn(_("patch %s is empty\n") % patchname)
581 self.ui.warn(_("patch %s is empty\n") % patchname)
582 patcherr, files, fuzz = 0, [], 0
582 patcherr, files, fuzz = 0, [], 0
583
583
584 if merge and files:
584 if merge and files:
585 # Mark as removed/merged and update dirstate parent info
585 # Mark as removed/merged and update dirstate parent info
586 removed = []
586 removed = []
587 merged = []
587 merged = []
588 for f in files:
588 for f in files:
589 if os.path.exists(repo.wjoin(f)):
589 if os.path.exists(repo.wjoin(f)):
590 merged.append(f)
590 merged.append(f)
591 else:
591 else:
592 removed.append(f)
592 removed.append(f)
593 for f in removed:
593 for f in removed:
594 repo.dirstate.remove(f)
594 repo.dirstate.remove(f)
595 for f in merged:
595 for f in merged:
596 repo.dirstate.merge(f)
596 repo.dirstate.merge(f)
597 p1, p2 = repo.dirstate.parents()
597 p1, p2 = repo.dirstate.parents()
598 repo.dirstate.setparents(p1, merge)
598 repo.dirstate.setparents(p1, merge)
599
599
600 files = patch.updatedir(self.ui, repo, files)
600 files = patch.updatedir(self.ui, repo, files)
601 match = cmdutil.matchfiles(repo, files or [])
601 match = cmdutil.matchfiles(repo, files or [])
602 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
602 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
603
603
604 if n is None:
604 if n is None:
605 raise util.Abort(_("repo commit failed"))
605 raise util.Abort(_("repo commit failed"))
606
606
607 if update_status:
607 if update_status:
608 self.applied.append(statusentry(hex(n), patchname))
608 self.applied.append(statusentry(hex(n), patchname))
609
609
610 if patcherr:
610 if patcherr:
611 self.ui.warn(_("patch failed, rejects left in working dir\n"))
611 self.ui.warn(_("patch failed, rejects left in working dir\n"))
612 err = 2
612 err = 2
613 break
613 break
614
614
615 if fuzz and strict:
615 if fuzz and strict:
616 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
616 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
617 err = 3
617 err = 3
618 break
618 break
619 return (err, n)
619 return (err, n)
620
620
621 def _cleanup(self, patches, numrevs, keep=False):
621 def _cleanup(self, patches, numrevs, keep=False):
622 if not keep:
622 if not keep:
623 r = self.qrepo()
623 r = self.qrepo()
624 if r:
624 if r:
625 r.remove(patches, True)
625 r.remove(patches, True)
626 else:
626 else:
627 for p in patches:
627 for p in patches:
628 os.unlink(self.join(p))
628 os.unlink(self.join(p))
629
629
630 if numrevs:
630 if numrevs:
631 del self.applied[:numrevs]
631 del self.applied[:numrevs]
632 self.applied_dirty = 1
632 self.applied_dirty = 1
633
633
634 for i in sorted([self.find_series(p) for p in patches], reverse=True):
634 for i in sorted([self.find_series(p) for p in patches], reverse=True):
635 del self.full_series[i]
635 del self.full_series[i]
636 self.parse_series()
636 self.parse_series()
637 self.series_dirty = 1
637 self.series_dirty = 1
638
638
639 def _revpatches(self, repo, revs):
639 def _revpatches(self, repo, revs):
640 firstrev = repo[self.applied[0].rev].rev()
640 firstrev = repo[self.applied[0].rev].rev()
641 patches = []
641 patches = []
642 for i, rev in enumerate(revs):
642 for i, rev in enumerate(revs):
643
643
644 if rev < firstrev:
644 if rev < firstrev:
645 raise util.Abort(_('revision %d is not managed') % rev)
645 raise util.Abort(_('revision %d is not managed') % rev)
646
646
647 ctx = repo[rev]
647 ctx = repo[rev]
648 base = bin(self.applied[i].rev)
648 base = bin(self.applied[i].rev)
649 if ctx.node() != base:
649 if ctx.node() != base:
650 msg = _('cannot delete revision %d above applied patches')
650 msg = _('cannot delete revision %d above applied patches')
651 raise util.Abort(msg % rev)
651 raise util.Abort(msg % rev)
652
652
653 patch = self.applied[i].name
653 patch = self.applied[i].name
654 for fmt in ('[mq]: %s', 'imported patch %s'):
654 for fmt in ('[mq]: %s', 'imported patch %s'):
655 if ctx.description() == fmt % patch:
655 if ctx.description() == fmt % patch:
656 msg = _('patch %s finalized without changeset message\n')
656 msg = _('patch %s finalized without changeset message\n')
657 repo.ui.status(msg % patch)
657 repo.ui.status(msg % patch)
658 break
658 break
659
659
660 patches.append(patch)
660 patches.append(patch)
661 return patches
661 return patches
662
662
663 def finish(self, repo, revs):
663 def finish(self, repo, revs):
664 patches = self._revpatches(repo, sorted(revs))
664 patches = self._revpatches(repo, sorted(revs))
665 self._cleanup(patches, len(patches))
665 self._cleanup(patches, len(patches))
666
666
667 def delete(self, repo, patches, opts):
667 def delete(self, repo, patches, opts):
668 if not patches and not opts.get('rev'):
668 if not patches and not opts.get('rev'):
669 raise util.Abort(_('qdelete requires at least one revision or '
669 raise util.Abort(_('qdelete requires at least one revision or '
670 'patch name'))
670 'patch name'))
671
671
672 realpatches = []
672 realpatches = []
673 for patch in patches:
673 for patch in patches:
674 patch = self.lookup(patch, strict=True)
674 patch = self.lookup(patch, strict=True)
675 info = self.isapplied(patch)
675 info = self.isapplied(patch)
676 if info:
676 if info:
677 raise util.Abort(_("cannot delete applied patch %s") % patch)
677 raise util.Abort(_("cannot delete applied patch %s") % patch)
678 if patch not in self.series:
678 if patch not in self.series:
679 raise util.Abort(_("patch %s not in series file") % patch)
679 raise util.Abort(_("patch %s not in series file") % patch)
680 realpatches.append(patch)
680 realpatches.append(patch)
681
681
682 numrevs = 0
682 numrevs = 0
683 if opts.get('rev'):
683 if opts.get('rev'):
684 if not self.applied:
684 if not self.applied:
685 raise util.Abort(_('no patches applied'))
685 raise util.Abort(_('no patches applied'))
686 revs = cmdutil.revrange(repo, opts['rev'])
686 revs = cmdutil.revrange(repo, opts['rev'])
687 if len(revs) > 1 and revs[0] > revs[1]:
687 if len(revs) > 1 and revs[0] > revs[1]:
688 revs.reverse()
688 revs.reverse()
689 revpatches = self._revpatches(repo, revs)
689 revpatches = self._revpatches(repo, revs)
690 realpatches += revpatches
690 realpatches += revpatches
691 numrevs = len(revpatches)
691 numrevs = len(revpatches)
692
692
693 self._cleanup(realpatches, numrevs, opts.get('keep'))
693 self._cleanup(realpatches, numrevs, opts.get('keep'))
694
694
695 def check_toppatch(self, repo):
695 def check_toppatch(self, repo):
696 if len(self.applied) > 0:
696 if len(self.applied) > 0:
697 top = bin(self.applied[-1].rev)
697 top = bin(self.applied[-1].rev)
698 pp = repo.dirstate.parents()
698 pp = repo.dirstate.parents()
699 if top not in pp:
699 if top not in pp:
700 raise util.Abort(_("working directory revision is not qtip"))
700 raise util.Abort(_("working directory revision is not qtip"))
701 return top
701 return top
702 return None
702 return None
703 def check_localchanges(self, repo, force=False, refresh=True):
703 def check_localchanges(self, repo, force=False, refresh=True):
704 m, a, r, d = repo.status()[:4]
704 m, a, r, d = repo.status()[:4]
705 if m or a or r or d:
705 if m or a or r or d:
706 if not force:
706 if not force:
707 if refresh:
707 if refresh:
708 raise util.Abort(_("local changes found, refresh first"))
708 raise util.Abort(_("local changes found, refresh first"))
709 else:
709 else:
710 raise util.Abort(_("local changes found"))
710 raise util.Abort(_("local changes found"))
711 return m, a, r, d
711 return m, a, r, d
712
712
713 _reserved = ('series', 'status', 'guards')
713 _reserved = ('series', 'status', 'guards')
714 def check_reserved_name(self, name):
714 def check_reserved_name(self, name):
715 if (name in self._reserved or name.startswith('.hg')
715 if (name in self._reserved or name.startswith('.hg')
716 or name.startswith('.mq')):
716 or name.startswith('.mq')):
717 raise util.Abort(_('"%s" cannot be used as the name of a patch')
717 raise util.Abort(_('"%s" cannot be used as the name of a patch')
718 % name)
718 % name)
719
719
720 def new(self, repo, patchfn, *pats, **opts):
720 def new(self, repo, patchfn, *pats, **opts):
721 """options:
721 """options:
722 msg: a string or a no-argument function returning a string
722 msg: a string or a no-argument function returning a string
723 """
723 """
724 msg = opts.get('msg')
724 msg = opts.get('msg')
725 force = opts.get('force')
725 force = opts.get('force')
726 user = opts.get('user')
726 user = opts.get('user')
727 date = opts.get('date')
727 date = opts.get('date')
728 if date:
728 if date:
729 date = util.parsedate(date)
729 date = util.parsedate(date)
730 self.check_reserved_name(patchfn)
730 self.check_reserved_name(patchfn)
731 if os.path.exists(self.join(patchfn)):
731 if os.path.exists(self.join(patchfn)):
732 raise util.Abort(_('patch "%s" already exists') % patchfn)
732 raise util.Abort(_('patch "%s" already exists') % patchfn)
733 if opts.get('include') or opts.get('exclude') or pats:
733 if opts.get('include') or opts.get('exclude') or pats:
734 match = cmdutil.match(repo, pats, opts)
734 match = cmdutil.match(repo, pats, opts)
735 # detect missing files in pats
735 # detect missing files in pats
736 def badfn(f, msg):
736 def badfn(f, msg):
737 raise util.Abort('%s: %s' % (f, msg))
737 raise util.Abort('%s: %s' % (f, msg))
738 match.bad = badfn
738 match.bad = badfn
739 m, a, r, d = repo.status(match=match)[:4]
739 m, a, r, d = repo.status(match=match)[:4]
740 else:
740 else:
741 m, a, r, d = self.check_localchanges(repo, force)
741 m, a, r, d = self.check_localchanges(repo, force)
742 match = cmdutil.matchfiles(repo, m + a + r)
742 match = cmdutil.matchfiles(repo, m + a + r)
743 commitfiles = m + a + r
743 commitfiles = m + a + r
744 self.check_toppatch(repo)
744 self.check_toppatch(repo)
745 insert = self.full_series_end()
745 insert = self.full_series_end()
746 wlock = repo.wlock()
746 wlock = repo.wlock()
747 try:
747 try:
748 # if patch file write fails, abort early
748 # if patch file write fails, abort early
749 p = self.opener(patchfn, "w")
749 p = self.opener(patchfn, "w")
750 try:
750 try:
751 if date:
751 if date:
752 p.write("# HG changeset patch\n")
752 p.write("# HG changeset patch\n")
753 if user:
753 if user:
754 p.write("# User " + user + "\n")
754 p.write("# User " + user + "\n")
755 p.write("# Date %d %d\n\n" % date)
755 p.write("# Date %d %d\n\n" % date)
756 elif user:
756 elif user:
757 p.write("From: " + user + "\n\n")
757 p.write("From: " + user + "\n\n")
758
758
759 if hasattr(msg, '__call__'):
759 if hasattr(msg, '__call__'):
760 msg = msg()
760 msg = msg()
761 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
761 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
762 n = repo.commit(commitmsg, user, date, match=match, force=True)
762 n = repo.commit(commitmsg, user, date, match=match, force=True)
763 if n is None:
763 if n is None:
764 raise util.Abort(_("repo commit failed"))
764 raise util.Abort(_("repo commit failed"))
765 try:
765 try:
766 self.full_series[insert:insert] = [patchfn]
766 self.full_series[insert:insert] = [patchfn]
767 self.applied.append(statusentry(hex(n), patchfn))
767 self.applied.append(statusentry(hex(n), patchfn))
768 self.parse_series()
768 self.parse_series()
769 self.series_dirty = 1
769 self.series_dirty = 1
770 self.applied_dirty = 1
770 self.applied_dirty = 1
771 if msg:
771 if msg:
772 msg = msg + "\n\n"
772 msg = msg + "\n\n"
773 p.write(msg)
773 p.write(msg)
774 if commitfiles:
774 if commitfiles:
775 diffopts = self.diffopts()
775 diffopts = self.diffopts()
776 if opts.get('git'): diffopts.git = True
776 if opts.get('git'): diffopts.git = True
777 parent = self.qparents(repo, n)
777 parent = self.qparents(repo, n)
778 chunks = patch.diff(repo, node1=parent, node2=n,
778 chunks = patch.diff(repo, node1=parent, node2=n,
779 match=match, opts=diffopts)
779 match=match, opts=diffopts)
780 for chunk in chunks:
780 for chunk in chunks:
781 p.write(chunk)
781 p.write(chunk)
782 p.close()
782 p.close()
783 wlock.release()
783 wlock.release()
784 wlock = None
784 wlock = None
785 r = self.qrepo()
785 r = self.qrepo()
786 if r: r.add([patchfn])
786 if r: r.add([patchfn])
787 except:
787 except:
788 repo.rollback()
788 repo.rollback()
789 raise
789 raise
790 except Exception:
790 except Exception:
791 patchpath = self.join(patchfn)
791 patchpath = self.join(patchfn)
792 try:
792 try:
793 os.unlink(patchpath)
793 os.unlink(patchpath)
794 except:
794 except:
795 self.ui.warn(_('error unlinking %s\n') % patchpath)
795 self.ui.warn(_('error unlinking %s\n') % patchpath)
796 raise
796 raise
797 self.removeundo(repo)
797 self.removeundo(repo)
798 finally:
798 finally:
799 release(wlock)
799 release(wlock)
800
800
801 def strip(self, repo, rev, update=True, backup="all", force=None):
801 def strip(self, repo, rev, update=True, backup="all", force=None):
802 wlock = lock = None
802 wlock = lock = None
803 try:
803 try:
804 wlock = repo.wlock()
804 wlock = repo.wlock()
805 lock = repo.lock()
805 lock = repo.lock()
806
806
807 if update:
807 if update:
808 self.check_localchanges(repo, force=force, refresh=False)
808 self.check_localchanges(repo, force=force, refresh=False)
809 urev = self.qparents(repo, rev)
809 urev = self.qparents(repo, rev)
810 hg.clean(repo, urev)
810 hg.clean(repo, urev)
811 repo.dirstate.write()
811 repo.dirstate.write()
812
812
813 self.removeundo(repo)
813 self.removeundo(repo)
814 repair.strip(self.ui, repo, rev, backup)
814 repair.strip(self.ui, repo, rev, backup)
815 # strip may have unbundled a set of backed up revisions after
815 # strip may have unbundled a set of backed up revisions after
816 # the actual strip
816 # the actual strip
817 self.removeundo(repo)
817 self.removeundo(repo)
818 finally:
818 finally:
819 release(lock, wlock)
819 release(lock, wlock)
820
820
821 def isapplied(self, patch):
821 def isapplied(self, patch):
822 """returns (index, rev, patch)"""
822 """returns (index, rev, patch)"""
823 for i, a in enumerate(self.applied):
823 for i, a in enumerate(self.applied):
824 if a.name == patch:
824 if a.name == patch:
825 return (i, a.rev, a.name)
825 return (i, a.rev, a.name)
826 return None
826 return None
827
827
828 # if the exact patch name does not exist, we try a few
828 # if the exact patch name does not exist, we try a few
829 # variations. If strict is passed, we try only #1
829 # variations. If strict is passed, we try only #1
830 #
830 #
831 # 1) a number to indicate an offset in the series file
831 # 1) a number to indicate an offset in the series file
832 # 2) a unique substring of the patch name was given
832 # 2) a unique substring of the patch name was given
833 # 3) patchname[-+]num to indicate an offset in the series file
833 # 3) patchname[-+]num to indicate an offset in the series file
834 def lookup(self, patch, strict=False):
834 def lookup(self, patch, strict=False):
835 patch = patch and str(patch)
835 patch = patch and str(patch)
836
836
837 def partial_name(s):
837 def partial_name(s):
838 if s in self.series:
838 if s in self.series:
839 return s
839 return s
840 matches = [x for x in self.series if s in x]
840 matches = [x for x in self.series if s in x]
841 if len(matches) > 1:
841 if len(matches) > 1:
842 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
842 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
843 for m in matches:
843 for m in matches:
844 self.ui.warn(' %s\n' % m)
844 self.ui.warn(' %s\n' % m)
845 return None
845 return None
846 if matches:
846 if matches:
847 return matches[0]
847 return matches[0]
848 if len(self.series) > 0 and len(self.applied) > 0:
848 if len(self.series) > 0 and len(self.applied) > 0:
849 if s == 'qtip':
849 if s == 'qtip':
850 return self.series[self.series_end(True)-1]
850 return self.series[self.series_end(True)-1]
851 if s == 'qbase':
851 if s == 'qbase':
852 return self.series[0]
852 return self.series[0]
853 return None
853 return None
854
854
855 if patch is None:
855 if patch is None:
856 return None
856 return None
857 if patch in self.series:
857 if patch in self.series:
858 return patch
858 return patch
859
859
860 if not os.path.isfile(self.join(patch)):
860 if not os.path.isfile(self.join(patch)):
861 try:
861 try:
862 sno = int(patch)
862 sno = int(patch)
863 except(ValueError, OverflowError):
863 except(ValueError, OverflowError):
864 pass
864 pass
865 else:
865 else:
866 if -len(self.series) <= sno < len(self.series):
866 if -len(self.series) <= sno < len(self.series):
867 return self.series[sno]
867 return self.series[sno]
868
868
869 if not strict:
869 if not strict:
870 res = partial_name(patch)
870 res = partial_name(patch)
871 if res:
871 if res:
872 return res
872 return res
873 minus = patch.rfind('-')
873 minus = patch.rfind('-')
874 if minus >= 0:
874 if minus >= 0:
875 res = partial_name(patch[:minus])
875 res = partial_name(patch[:minus])
876 if res:
876 if res:
877 i = self.series.index(res)
877 i = self.series.index(res)
878 try:
878 try:
879 off = int(patch[minus+1:] or 1)
879 off = int(patch[minus+1:] or 1)
880 except(ValueError, OverflowError):
880 except(ValueError, OverflowError):
881 pass
881 pass
882 else:
882 else:
883 if i - off >= 0:
883 if i - off >= 0:
884 return self.series[i - off]
884 return self.series[i - off]
885 plus = patch.rfind('+')
885 plus = patch.rfind('+')
886 if plus >= 0:
886 if plus >= 0:
887 res = partial_name(patch[:plus])
887 res = partial_name(patch[:plus])
888 if res:
888 if res:
889 i = self.series.index(res)
889 i = self.series.index(res)
890 try:
890 try:
891 off = int(patch[plus+1:] or 1)
891 off = int(patch[plus+1:] or 1)
892 except(ValueError, OverflowError):
892 except(ValueError, OverflowError):
893 pass
893 pass
894 else:
894 else:
895 if i + off < len(self.series):
895 if i + off < len(self.series):
896 return self.series[i + off]
896 return self.series[i + off]
897 raise util.Abort(_("patch %s not in series") % patch)
897 raise util.Abort(_("patch %s not in series") % patch)
898
898
899 def push(self, repo, patch=None, force=False, list=False,
899 def push(self, repo, patch=None, force=False, list=False,
900 mergeq=None, all=False):
900 mergeq=None, all=False):
901 wlock = repo.wlock()
901 wlock = repo.wlock()
902 try:
902 try:
903 if repo.dirstate.parents()[0] not in repo.heads():
903 if repo.dirstate.parents()[0] not in repo.heads():
904 self.ui.status(_("(working directory not at a head)\n"))
904 self.ui.status(_("(working directory not at a head)\n"))
905
905
906 if not self.series:
906 if not self.series:
907 self.ui.warn(_('no patches in series\n'))
907 self.ui.warn(_('no patches in series\n'))
908 return 0
908 return 0
909
909
910 patch = self.lookup(patch)
910 patch = self.lookup(patch)
911 # Suppose our series file is: A B C and the current 'top'
911 # Suppose our series file is: A B C and the current 'top'
912 # patch is B. qpush C should be performed (moving forward)
912 # patch is B. qpush C should be performed (moving forward)
913 # qpush B is a NOP (no change) qpush A is an error (can't
913 # qpush B is a NOP (no change) qpush A is an error (can't
914 # go backwards with qpush)
914 # go backwards with qpush)
915 if patch:
915 if patch:
916 info = self.isapplied(patch)
916 info = self.isapplied(patch)
917 if info:
917 if info:
918 if info[0] < len(self.applied) - 1:
918 if info[0] < len(self.applied) - 1:
919 raise util.Abort(
919 raise util.Abort(
920 _("cannot push to a previous patch: %s") % patch)
920 _("cannot push to a previous patch: %s") % patch)
921 self.ui.warn(
921 self.ui.warn(
922 _('qpush: %s is already at the top\n') % patch)
922 _('qpush: %s is already at the top\n') % patch)
923 return
923 return
924 pushable, reason = self.pushable(patch)
924 pushable, reason = self.pushable(patch)
925 if not pushable:
925 if not pushable:
926 if reason:
926 if reason:
927 reason = _('guarded by %r') % reason
927 reason = _('guarded by %r') % reason
928 else:
928 else:
929 reason = _('no matching guards')
929 reason = _('no matching guards')
930 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
930 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
931 return 1
931 return 1
932 elif all:
932 elif all:
933 patch = self.series[-1]
933 patch = self.series[-1]
934 if self.isapplied(patch):
934 if self.isapplied(patch):
935 self.ui.warn(_('all patches are currently applied\n'))
935 self.ui.warn(_('all patches are currently applied\n'))
936 return 0
936 return 0
937
937
938 # Following the above example, starting at 'top' of B:
938 # Following the above example, starting at 'top' of B:
939 # qpush should be performed (pushes C), but a subsequent
939 # qpush should be performed (pushes C), but a subsequent
940 # qpush without an argument is an error (nothing to
940 # qpush without an argument is an error (nothing to
941 # apply). This allows a loop of "...while hg qpush..." to
941 # apply). This allows a loop of "...while hg qpush..." to
942 # work as it detects an error when done
942 # work as it detects an error when done
943 start = self.series_end()
943 start = self.series_end()
944 if start == len(self.series):
944 if start == len(self.series):
945 self.ui.warn(_('patch series already fully applied\n'))
945 self.ui.warn(_('patch series already fully applied\n'))
946 return 1
946 return 1
947 if not force:
947 if not force:
948 self.check_localchanges(repo)
948 self.check_localchanges(repo)
949
949
950 self.applied_dirty = 1
950 self.applied_dirty = 1
951 if start > 0:
951 if start > 0:
952 self.check_toppatch(repo)
952 self.check_toppatch(repo)
953 if not patch:
953 if not patch:
954 patch = self.series[start]
954 patch = self.series[start]
955 end = start + 1
955 end = start + 1
956 else:
956 else:
957 end = self.series.index(patch, start) + 1
957 end = self.series.index(patch, start) + 1
958
958
959 s = self.series[start:end]
959 s = self.series[start:end]
960 all_files = {}
960 all_files = {}
961 try:
961 try:
962 if mergeq:
962 if mergeq:
963 ret = self.mergepatch(repo, mergeq, s)
963 ret = self.mergepatch(repo, mergeq, s)
964 else:
964 else:
965 ret = self.apply(repo, s, list, all_files=all_files)
965 ret = self.apply(repo, s, list, all_files=all_files)
966 except:
966 except:
967 self.ui.warn(_('cleaning up working directory...'))
967 self.ui.warn(_('cleaning up working directory...'))
968 node = repo.dirstate.parents()[0]
968 node = repo.dirstate.parents()[0]
969 hg.revert(repo, node, None)
969 hg.revert(repo, node, None)
970 unknown = repo.status(unknown=True)[4]
970 unknown = repo.status(unknown=True)[4]
971 # only remove unknown files that we know we touched or
971 # only remove unknown files that we know we touched or
972 # created while patching
972 # created while patching
973 for f in unknown:
973 for f in unknown:
974 if f in all_files:
974 if f in all_files:
975 util.unlink(repo.wjoin(f))
975 util.unlink(repo.wjoin(f))
976 self.ui.warn(_('done\n'))
976 self.ui.warn(_('done\n'))
977 raise
977 raise
978
978
979 top = self.applied[-1].name
979 top = self.applied[-1].name
980 if ret[0] and ret[0] > 1:
980 if ret[0] and ret[0] > 1:
981 msg = _("errors during apply, please fix and refresh %s\n")
981 msg = _("errors during apply, please fix and refresh %s\n")
982 self.ui.write(msg % top)
982 self.ui.write(msg % top)
983 else:
983 else:
984 self.ui.write(_("now at: %s\n") % top)
984 self.ui.write(_("now at: %s\n") % top)
985 return ret[0]
985 return ret[0]
986
986
987 finally:
987 finally:
988 wlock.release()
988 wlock.release()
989
989
990 def pop(self, repo, patch=None, force=False, update=True, all=False):
990 def pop(self, repo, patch=None, force=False, update=True, all=False):
991 def getfile(f, rev, flags):
991 def getfile(f, rev, flags):
992 t = repo.file(f).read(rev)
992 t = repo.file(f).read(rev)
993 repo.wwrite(f, t, flags)
993 repo.wwrite(f, t, flags)
994
994
995 wlock = repo.wlock()
995 wlock = repo.wlock()
996 try:
996 try:
997 if patch:
997 if patch:
998 # index, rev, patch
998 # index, rev, patch
999 info = self.isapplied(patch)
999 info = self.isapplied(patch)
1000 if not info:
1000 if not info:
1001 patch = self.lookup(patch)
1001 patch = self.lookup(patch)
1002 info = self.isapplied(patch)
1002 info = self.isapplied(patch)
1003 if not info:
1003 if not info:
1004 raise util.Abort(_("patch %s is not applied") % patch)
1004 raise util.Abort(_("patch %s is not applied") % patch)
1005
1005
1006 if len(self.applied) == 0:
1006 if len(self.applied) == 0:
1007 # Allow qpop -a to work repeatedly,
1007 # Allow qpop -a to work repeatedly,
1008 # but not qpop without an argument
1008 # but not qpop without an argument
1009 self.ui.warn(_("no patches applied\n"))
1009 self.ui.warn(_("no patches applied\n"))
1010 return not all
1010 return not all
1011
1011
1012 if all:
1012 if all:
1013 start = 0
1013 start = 0
1014 elif patch:
1014 elif patch:
1015 start = info[0] + 1
1015 start = info[0] + 1
1016 else:
1016 else:
1017 start = len(self.applied) - 1
1017 start = len(self.applied) - 1
1018
1018
1019 if start >= len(self.applied):
1019 if start >= len(self.applied):
1020 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1020 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1021 return
1021 return
1022
1022
1023 if not update:
1023 if not update:
1024 parents = repo.dirstate.parents()
1024 parents = repo.dirstate.parents()
1025 rr = [ bin(x.rev) for x in self.applied ]
1025 rr = [ bin(x.rev) for x in self.applied ]
1026 for p in parents:
1026 for p in parents:
1027 if p in rr:
1027 if p in rr:
1028 self.ui.warn(_("qpop: forcing dirstate update\n"))
1028 self.ui.warn(_("qpop: forcing dirstate update\n"))
1029 update = True
1029 update = True
1030 else:
1030 else:
1031 parents = [p.hex() for p in repo[None].parents()]
1031 parents = [p.hex() for p in repo[None].parents()]
1032 needupdate = False
1032 needupdate = False
1033 for entry in self.applied[start:]:
1033 for entry in self.applied[start:]:
1034 if entry.rev in parents:
1034 if entry.rev in parents:
1035 needupdate = True
1035 needupdate = True
1036 break
1036 break
1037 update = needupdate
1037 update = needupdate
1038
1038
1039 if not force and update:
1039 if not force and update:
1040 self.check_localchanges(repo)
1040 self.check_localchanges(repo)
1041
1041
1042 self.applied_dirty = 1
1042 self.applied_dirty = 1
1043 end = len(self.applied)
1043 end = len(self.applied)
1044 rev = bin(self.applied[start].rev)
1044 rev = bin(self.applied[start].rev)
1045 if update:
1045 if update:
1046 top = self.check_toppatch(repo)
1046 top = self.check_toppatch(repo)
1047
1047
1048 try:
1048 try:
1049 heads = repo.changelog.heads(rev)
1049 heads = repo.changelog.heads(rev)
1050 except error.LookupError:
1050 except error.LookupError:
1051 node = short(rev)
1051 node = short(rev)
1052 raise util.Abort(_('trying to pop unknown node %s') % node)
1052 raise util.Abort(_('trying to pop unknown node %s') % node)
1053
1053
1054 if heads != [bin(self.applied[-1].rev)]:
1054 if heads != [bin(self.applied[-1].rev)]:
1055 raise util.Abort(_("popping would remove a revision not "
1055 raise util.Abort(_("popping would remove a revision not "
1056 "managed by this patch queue"))
1056 "managed by this patch queue"))
1057
1057
1058 # we know there are no local changes, so we can make a simplified
1058 # we know there are no local changes, so we can make a simplified
1059 # form of hg.update.
1059 # form of hg.update.
1060 if update:
1060 if update:
1061 qp = self.qparents(repo, rev)
1061 qp = self.qparents(repo, rev)
1062 changes = repo.changelog.read(qp)
1062 changes = repo.changelog.read(qp)
1063 mmap = repo.manifest.read(changes[0])
1063 mmap = repo.manifest.read(changes[0])
1064 m, a, r, d = repo.status(qp, top)[:4]
1064 m, a, r, d = repo.status(qp, top)[:4]
1065 if d:
1065 if d:
1066 raise util.Abort(_("deletions found between repo revs"))
1066 raise util.Abort(_("deletions found between repo revs"))
1067 for f in m:
1067 for f in m:
1068 getfile(f, mmap[f], mmap.flags(f))
1068 getfile(f, mmap[f], mmap.flags(f))
1069 for f in r:
1069 for f in r:
1070 getfile(f, mmap[f], mmap.flags(f))
1070 getfile(f, mmap[f], mmap.flags(f))
1071 for f in m + r:
1071 for f in m + r:
1072 repo.dirstate.normal(f)
1072 repo.dirstate.normal(f)
1073 for f in a:
1073 for f in a:
1074 try:
1074 try:
1075 os.unlink(repo.wjoin(f))
1075 os.unlink(repo.wjoin(f))
1076 except OSError, e:
1076 except OSError, e:
1077 if e.errno != errno.ENOENT:
1077 if e.errno != errno.ENOENT:
1078 raise
1078 raise
1079 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
1079 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
1080 except: pass
1080 except: pass
1081 repo.dirstate.forget(f)
1081 repo.dirstate.forget(f)
1082 repo.dirstate.setparents(qp, nullid)
1082 repo.dirstate.setparents(qp, nullid)
1083 for patch in reversed(self.applied[start:end]):
1083 for patch in reversed(self.applied[start:end]):
1084 self.ui.status(_("popping %s\n") % patch.name)
1084 self.ui.status(_("popping %s\n") % patch.name)
1085 del self.applied[start:end]
1085 del self.applied[start:end]
1086 self.strip(repo, rev, update=False, backup='strip')
1086 self.strip(repo, rev, update=False, backup='strip')
1087 if len(self.applied):
1087 if len(self.applied):
1088 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1088 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1089 else:
1089 else:
1090 self.ui.write(_("patch queue now empty\n"))
1090 self.ui.write(_("patch queue now empty\n"))
1091 finally:
1091 finally:
1092 wlock.release()
1092 wlock.release()
1093
1093
1094 def diff(self, repo, pats, opts):
1094 def diff(self, repo, pats, opts):
1095 top = self.check_toppatch(repo)
1095 top = self.check_toppatch(repo)
1096 if not top:
1096 if not top:
1097 self.ui.write(_("no patches applied\n"))
1097 self.ui.write(_("no patches applied\n"))
1098 return
1098 return
1099 qp = self.qparents(repo, top)
1099 qp = self.qparents(repo, top)
1100 self._diffopts = patch.diffopts(self.ui, opts)
1100 self._diffopts = patch.diffopts(self.ui, opts)
1101 self.printdiff(repo, qp, files=pats, opts=opts)
1101 self.printdiff(repo, qp, files=pats, opts=opts)
1102
1102
1103 def refresh(self, repo, pats=None, **opts):
1103 def refresh(self, repo, pats=None, **opts):
1104 if len(self.applied) == 0:
1104 if len(self.applied) == 0:
1105 self.ui.write(_("no patches applied\n"))
1105 self.ui.write(_("no patches applied\n"))
1106 return 1
1106 return 1
1107 msg = opts.get('msg', '').rstrip()
1107 msg = opts.get('msg', '').rstrip()
1108 newuser = opts.get('user')
1108 newuser = opts.get('user')
1109 newdate = opts.get('date')
1109 newdate = opts.get('date')
1110 if newdate:
1110 if newdate:
1111 newdate = '%d %d' % util.parsedate(newdate)
1111 newdate = '%d %d' % util.parsedate(newdate)
1112 wlock = repo.wlock()
1112 wlock = repo.wlock()
1113 try:
1113 try:
1114 self.check_toppatch(repo)
1114 self.check_toppatch(repo)
1115 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1115 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1116 top = bin(top)
1116 top = bin(top)
1117 if repo.changelog.heads(top) != [top]:
1117 if repo.changelog.heads(top) != [top]:
1118 raise util.Abort(_("cannot refresh a revision with children"))
1118 raise util.Abort(_("cannot refresh a revision with children"))
1119 cparents = repo.changelog.parents(top)
1119 cparents = repo.changelog.parents(top)
1120 patchparent = self.qparents(repo, top)
1120 patchparent = self.qparents(repo, top)
1121 ph = patchheader(self.join(patchfn))
1121 ph = patchheader(self.join(patchfn))
1122
1122
1123 patchf = self.opener(patchfn, 'r')
1123 patchf = self.opener(patchfn, 'r')
1124
1124
1125 # if the patch was a git patch, refresh it as a git patch
1125 # if the patch was a git patch, refresh it as a git patch
1126 for line in patchf:
1126 for line in patchf:
1127 if line.startswith('diff --git'):
1127 if line.startswith('diff --git'):
1128 self.diffopts().git = True
1128 self.diffopts().git = True
1129 break
1129 break
1130
1130
1131 if msg:
1131 if msg:
1132 ph.setmessage(msg)
1132 ph.setmessage(msg)
1133 if newuser:
1133 if newuser:
1134 ph.setuser(newuser)
1134 ph.setuser(newuser)
1135 if newdate:
1135 if newdate:
1136 ph.setdate(newdate)
1136 ph.setdate(newdate)
1137
1137
1138 # only commit new patch when write is complete
1138 # only commit new patch when write is complete
1139 patchf = self.opener(patchfn, 'w', atomictemp=True)
1139 patchf = self.opener(patchfn, 'w', atomictemp=True)
1140
1140
1141 patchf.seek(0)
1141 patchf.seek(0)
1142 patchf.truncate()
1142 patchf.truncate()
1143
1143
1144 comments = str(ph)
1144 comments = str(ph)
1145 if comments:
1145 if comments:
1146 patchf.write(comments)
1146 patchf.write(comments)
1147
1147
1148 if opts.get('git'):
1148 if opts.get('git'):
1149 self.diffopts().git = True
1149 self.diffopts().git = True
1150 tip = repo.changelog.tip()
1150 tip = repo.changelog.tip()
1151 if top == tip:
1151 if top == tip:
1152 # if the top of our patch queue is also the tip, there is an
1152 # if the top of our patch queue is also the tip, there is an
1153 # optimization here. We update the dirstate in place and strip
1153 # optimization here. We update the dirstate in place and strip
1154 # off the tip commit. Then just commit the current directory
1154 # off the tip commit. Then just commit the current directory
1155 # tree. We can also send repo.commit the list of files
1155 # tree. We can also send repo.commit the list of files
1156 # changed to speed up the diff
1156 # changed to speed up the diff
1157 #
1157 #
1158 # in short mode, we only diff the files included in the
1158 # in short mode, we only diff the files included in the
1159 # patch already plus specified files
1159 # patch already plus specified files
1160 #
1160 #
1161 # this should really read:
1161 # this should really read:
1162 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1162 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1163 # but we do it backwards to take advantage of manifest/chlog
1163 # but we do it backwards to take advantage of manifest/chlog
1164 # caching against the next repo.status call
1164 # caching against the next repo.status call
1165 #
1165 #
1166 mm, aa, dd, aa2 = repo.status(patchparent, tip)[:4]
1166 mm, aa, dd, aa2 = repo.status(patchparent, tip)[:4]
1167 changes = repo.changelog.read(tip)
1167 changes = repo.changelog.read(tip)
1168 man = repo.manifest.read(changes[0])
1168 man = repo.manifest.read(changes[0])
1169 aaa = aa[:]
1169 aaa = aa[:]
1170 matchfn = cmdutil.match(repo, pats, opts)
1170 matchfn = cmdutil.match(repo, pats, opts)
1171 if opts.get('short'):
1171 if opts.get('short'):
1172 # if amending a patch, we start with existing
1172 # if amending a patch, we start with existing
1173 # files plus specified files - unfiltered
1173 # files plus specified files - unfiltered
1174 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1174 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1175 # filter with inc/exl options
1175 # filter with inc/exl options
1176 matchfn = cmdutil.match(repo, opts=opts)
1176 matchfn = cmdutil.match(repo, opts=opts)
1177 else:
1177 else:
1178 match = cmdutil.matchall(repo)
1178 match = cmdutil.matchall(repo)
1179 m, a, r, d = repo.status(match=match)[:4]
1179 m, a, r, d = repo.status(match=match)[:4]
1180
1180
1181 # we might end up with files that were added between
1181 # we might end up with files that were added between
1182 # tip and the dirstate parent, but then changed in the
1182 # tip and the dirstate parent, but then changed in the
1183 # local dirstate. in this case, we want them to only
1183 # local dirstate. in this case, we want them to only
1184 # show up in the added section
1184 # show up in the added section
1185 for x in m:
1185 for x in m:
1186 if x not in aa:
1186 if x not in aa:
1187 mm.append(x)
1187 mm.append(x)
1188 # we might end up with files added by the local dirstate that
1188 # we might end up with files added by the local dirstate that
1189 # were deleted by the patch. In this case, they should only
1189 # were deleted by the patch. In this case, they should only
1190 # show up in the changed section.
1190 # show up in the changed section.
1191 for x in a:
1191 for x in a:
1192 if x in dd:
1192 if x in dd:
1193 del dd[dd.index(x)]
1193 del dd[dd.index(x)]
1194 mm.append(x)
1194 mm.append(x)
1195 else:
1195 else:
1196 aa.append(x)
1196 aa.append(x)
1197 # make sure any files deleted in the local dirstate
1197 # make sure any files deleted in the local dirstate
1198 # are not in the add or change column of the patch
1198 # are not in the add or change column of the patch
1199 forget = []
1199 forget = []
1200 for x in d + r:
1200 for x in d + r:
1201 if x in aa:
1201 if x in aa:
1202 del aa[aa.index(x)]
1202 del aa[aa.index(x)]
1203 forget.append(x)
1203 forget.append(x)
1204 continue
1204 continue
1205 elif x in mm:
1205 elif x in mm:
1206 del mm[mm.index(x)]
1206 del mm[mm.index(x)]
1207 dd.append(x)
1207 dd.append(x)
1208
1208
1209 m = list(set(mm))
1209 m = list(set(mm))
1210 r = list(set(dd))
1210 r = list(set(dd))
1211 a = list(set(aa))
1211 a = list(set(aa))
1212 c = [filter(matchfn, l) for l in (m, a, r)]
1212 c = [filter(matchfn, l) for l in (m, a, r)]
1213 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1213 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1214 chunks = patch.diff(repo, patchparent, match=match,
1214 chunks = patch.diff(repo, patchparent, match=match,
1215 changes=c, opts=self.diffopts())
1215 changes=c, opts=self.diffopts())
1216 for chunk in chunks:
1216 for chunk in chunks:
1217 patchf.write(chunk)
1217 patchf.write(chunk)
1218
1218
1219 try:
1219 try:
1220 if self.diffopts().git:
1220 if self.diffopts().git:
1221 copies = {}
1221 copies = {}
1222 for dst in a:
1222 for dst in a:
1223 src = repo.dirstate.copied(dst)
1223 src = repo.dirstate.copied(dst)
1224 # during qfold, the source file for copies may
1224 # during qfold, the source file for copies may
1225 # be removed. Treat this as a simple add.
1225 # be removed. Treat this as a simple add.
1226 if src is not None and src in repo.dirstate:
1226 if src is not None and src in repo.dirstate:
1227 copies.setdefault(src, []).append(dst)
1227 copies.setdefault(src, []).append(dst)
1228 repo.dirstate.add(dst)
1228 repo.dirstate.add(dst)
1229 # remember the copies between patchparent and tip
1229 # remember the copies between patchparent and tip
1230 for dst in aaa:
1230 for dst in aaa:
1231 f = repo.file(dst)
1231 f = repo.file(dst)
1232 src = f.renamed(man[dst])
1232 src = f.renamed(man[dst])
1233 if src:
1233 if src:
1234 copies.setdefault(src[0], []).extend(copies.get(dst, []))
1234 copies.setdefault(src[0], []).extend(copies.get(dst, []))
1235 if dst in a:
1235 if dst in a:
1236 copies[src[0]].append(dst)
1236 copies[src[0]].append(dst)
1237 # we can't copy a file created by the patch itself
1237 # we can't copy a file created by the patch itself
1238 if dst in copies:
1238 if dst in copies:
1239 del copies[dst]
1239 del copies[dst]
1240 for src, dsts in copies.iteritems():
1240 for src, dsts in copies.iteritems():
1241 for dst in dsts:
1241 for dst in dsts:
1242 repo.dirstate.copy(src, dst)
1242 repo.dirstate.copy(src, dst)
1243 else:
1243 else:
1244 for dst in a:
1244 for dst in a:
1245 repo.dirstate.add(dst)
1245 repo.dirstate.add(dst)
1246 # Drop useless copy information
1246 # Drop useless copy information
1247 for f in list(repo.dirstate.copies()):
1247 for f in list(repo.dirstate.copies()):
1248 repo.dirstate.copy(None, f)
1248 repo.dirstate.copy(None, f)
1249 for f in r:
1249 for f in r:
1250 repo.dirstate.remove(f)
1250 repo.dirstate.remove(f)
1251 # if the patch excludes a modified file, mark that
1251 # if the patch excludes a modified file, mark that
1252 # file with mtime=0 so status can see it.
1252 # file with mtime=0 so status can see it.
1253 mm = []
1253 mm = []
1254 for i in xrange(len(m)-1, -1, -1):
1254 for i in xrange(len(m)-1, -1, -1):
1255 if not matchfn(m[i]):
1255 if not matchfn(m[i]):
1256 mm.append(m[i])
1256 mm.append(m[i])
1257 del m[i]
1257 del m[i]
1258 for f in m:
1258 for f in m:
1259 repo.dirstate.normal(f)
1259 repo.dirstate.normal(f)
1260 for f in mm:
1260 for f in mm:
1261 repo.dirstate.normallookup(f)
1261 repo.dirstate.normallookup(f)
1262 for f in forget:
1262 for f in forget:
1263 repo.dirstate.forget(f)
1263 repo.dirstate.forget(f)
1264
1264
1265 if not msg:
1265 if not msg:
1266 if not ph.message:
1266 if not ph.message:
1267 message = "[mq]: %s\n" % patchfn
1267 message = "[mq]: %s\n" % patchfn
1268 else:
1268 else:
1269 message = "\n".join(ph.message)
1269 message = "\n".join(ph.message)
1270 else:
1270 else:
1271 message = msg
1271 message = msg
1272
1272
1273 user = ph.user or changes[1]
1273 user = ph.user or changes[1]
1274
1274
1275 # assumes strip can roll itself back if interrupted
1275 # assumes strip can roll itself back if interrupted
1276 repo.dirstate.setparents(*cparents)
1276 repo.dirstate.setparents(*cparents)
1277 self.applied.pop()
1277 self.applied.pop()
1278 self.applied_dirty = 1
1278 self.applied_dirty = 1
1279 self.strip(repo, top, update=False,
1279 self.strip(repo, top, update=False,
1280 backup='strip')
1280 backup='strip')
1281 except:
1281 except:
1282 repo.dirstate.invalidate()
1282 repo.dirstate.invalidate()
1283 raise
1283 raise
1284
1284
1285 try:
1285 try:
1286 # might be nice to attempt to roll back strip after this
1286 # might be nice to attempt to roll back strip after this
1287 patchf.rename()
1287 patchf.rename()
1288 n = repo.commit(message, user, ph.date, match=match,
1288 n = repo.commit(message, user, ph.date, match=match,
1289 force=True)
1289 force=True)
1290 self.applied.append(statusentry(hex(n), patchfn))
1290 self.applied.append(statusentry(hex(n), patchfn))
1291 except:
1291 except:
1292 ctx = repo[cparents[0]]
1292 ctx = repo[cparents[0]]
1293 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1293 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1294 self.save_dirty()
1294 self.save_dirty()
1295 self.ui.warn(_('refresh interrupted while patch was popped! '
1295 self.ui.warn(_('refresh interrupted while patch was popped! '
1296 '(revert --all, qpush to recover)\n'))
1296 '(revert --all, qpush to recover)\n'))
1297 raise
1297 raise
1298 else:
1298 else:
1299 self.printdiff(repo, patchparent, fp=patchf)
1299 self.printdiff(repo, patchparent, fp=patchf)
1300 patchf.rename()
1300 patchf.rename()
1301 added = repo.status()[1]
1301 added = repo.status()[1]
1302 for a in added:
1302 for a in added:
1303 f = repo.wjoin(a)
1303 f = repo.wjoin(a)
1304 try:
1304 try:
1305 os.unlink(f)
1305 os.unlink(f)
1306 except OSError, e:
1306 except OSError, e:
1307 if e.errno != errno.ENOENT:
1307 if e.errno != errno.ENOENT:
1308 raise
1308 raise
1309 try: os.removedirs(os.path.dirname(f))
1309 try: os.removedirs(os.path.dirname(f))
1310 except: pass
1310 except: pass
1311 # forget the file copies in the dirstate
1311 # forget the file copies in the dirstate
1312 # push should readd the files later on
1312 # push should readd the files later on
1313 repo.dirstate.forget(a)
1313 repo.dirstate.forget(a)
1314 self.pop(repo, force=True)
1314 self.pop(repo, force=True)
1315 self.push(repo, force=True)
1315 self.push(repo, force=True)
1316 finally:
1316 finally:
1317 wlock.release()
1317 wlock.release()
1318 self.removeundo(repo)
1318 self.removeundo(repo)
1319
1319
1320 def init(self, repo, create=False):
1320 def init(self, repo, create=False):
1321 if not create and os.path.isdir(self.path):
1321 if not create and os.path.isdir(self.path):
1322 raise util.Abort(_("patch queue directory already exists"))
1322 raise util.Abort(_("patch queue directory already exists"))
1323 try:
1323 try:
1324 os.mkdir(self.path)
1324 os.mkdir(self.path)
1325 except OSError, inst:
1325 except OSError, inst:
1326 if inst.errno != errno.EEXIST or not create:
1326 if inst.errno != errno.EEXIST or not create:
1327 raise
1327 raise
1328 if create:
1328 if create:
1329 return self.qrepo(create=True)
1329 return self.qrepo(create=True)
1330
1330
1331 def unapplied(self, repo, patch=None):
1331 def unapplied(self, repo, patch=None):
1332 if patch and patch not in self.series:
1332 if patch and patch not in self.series:
1333 raise util.Abort(_("patch %s is not in series file") % patch)
1333 raise util.Abort(_("patch %s is not in series file") % patch)
1334 if not patch:
1334 if not patch:
1335 start = self.series_end()
1335 start = self.series_end()
1336 else:
1336 else:
1337 start = self.series.index(patch) + 1
1337 start = self.series.index(patch) + 1
1338 unapplied = []
1338 unapplied = []
1339 for i in xrange(start, len(self.series)):
1339 for i in xrange(start, len(self.series)):
1340 pushable, reason = self.pushable(i)
1340 pushable, reason = self.pushable(i)
1341 if pushable:
1341 if pushable:
1342 unapplied.append((i, self.series[i]))
1342 unapplied.append((i, self.series[i]))
1343 self.explain_pushable(i)
1343 self.explain_pushable(i)
1344 return unapplied
1344 return unapplied
1345
1345
1346 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1346 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1347 summary=False):
1347 summary=False):
1348 def displayname(pfx, patchname):
1348 def displayname(pfx, patchname):
1349 if summary:
1349 if summary:
1350 ph = patchheader(self.join(patchname))
1350 ph = patchheader(self.join(patchname))
1351 msg = ph.message
1351 msg = ph.message
1352 msg = msg and ': ' + msg[0] or ': '
1352 msg = msg and ': ' + msg[0] or ': '
1353 else:
1353 else:
1354 msg = ''
1354 msg = ''
1355 msg = "%s%s%s" % (pfx, patchname, msg)
1355 msg = "%s%s%s" % (pfx, patchname, msg)
1356 if self.ui.interactive():
1356 if self.ui.interactive():
1357 msg = util.ellipsis(msg, util.termwidth())
1357 msg = util.ellipsis(msg, util.termwidth())
1358 self.ui.write(msg + '\n')
1358 self.ui.write(msg + '\n')
1359
1359
1360 applied = set([p.name for p in self.applied])
1360 applied = set([p.name for p in self.applied])
1361 if length is None:
1361 if length is None:
1362 length = len(self.series) - start
1362 length = len(self.series) - start
1363 if not missing:
1363 if not missing:
1364 if self.ui.verbose:
1364 if self.ui.verbose:
1365 idxwidth = len(str(start+length - 1))
1365 idxwidth = len(str(start+length - 1))
1366 for i in xrange(start, start+length):
1366 for i in xrange(start, start+length):
1367 patch = self.series[i]
1367 patch = self.series[i]
1368 if patch in applied:
1368 if patch in applied:
1369 stat = 'A'
1369 stat = 'A'
1370 elif self.pushable(i)[0]:
1370 elif self.pushable(i)[0]:
1371 stat = 'U'
1371 stat = 'U'
1372 else:
1372 else:
1373 stat = 'G'
1373 stat = 'G'
1374 pfx = ''
1374 pfx = ''
1375 if self.ui.verbose:
1375 if self.ui.verbose:
1376 pfx = '%*d %s ' % (idxwidth, i, stat)
1376 pfx = '%*d %s ' % (idxwidth, i, stat)
1377 elif status and status != stat:
1377 elif status and status != stat:
1378 continue
1378 continue
1379 displayname(pfx, patch)
1379 displayname(pfx, patch)
1380 else:
1380 else:
1381 msng_list = []
1381 msng_list = []
1382 for root, dirs, files in os.walk(self.path):
1382 for root, dirs, files in os.walk(self.path):
1383 d = root[len(self.path) + 1:]
1383 d = root[len(self.path) + 1:]
1384 for f in files:
1384 for f in files:
1385 fl = os.path.join(d, f)
1385 fl = os.path.join(d, f)
1386 if (fl not in self.series and
1386 if (fl not in self.series and
1387 fl not in (self.status_path, self.series_path,
1387 fl not in (self.status_path, self.series_path,
1388 self.guards_path)
1388 self.guards_path)
1389 and not fl.startswith('.')):
1389 and not fl.startswith('.')):
1390 msng_list.append(fl)
1390 msng_list.append(fl)
1391 for x in sorted(msng_list):
1391 for x in sorted(msng_list):
1392 pfx = self.ui.verbose and ('D ') or ''
1392 pfx = self.ui.verbose and ('D ') or ''
1393 displayname(pfx, x)
1393 displayname(pfx, x)
1394
1394
1395 def issaveline(self, l):
1395 def issaveline(self, l):
1396 if l.name == '.hg.patches.save.line':
1396 if l.name == '.hg.patches.save.line':
1397 return True
1397 return True
1398
1398
1399 def qrepo(self, create=False):
1399 def qrepo(self, create=False):
1400 if create or os.path.isdir(self.join(".hg")):
1400 if create or os.path.isdir(self.join(".hg")):
1401 return hg.repository(self.ui, path=self.path, create=create)
1401 return hg.repository(self.ui, path=self.path, create=create)
1402
1402
1403 def restore(self, repo, rev, delete=None, qupdate=None):
1403 def restore(self, repo, rev, delete=None, qupdate=None):
1404 c = repo.changelog.read(rev)
1404 c = repo.changelog.read(rev)
1405 desc = c[4].strip()
1405 desc = c[4].strip()
1406 lines = desc.splitlines()
1406 lines = desc.splitlines()
1407 i = 0
1407 i = 0
1408 datastart = None
1408 datastart = None
1409 series = []
1409 series = []
1410 applied = []
1410 applied = []
1411 qpp = None
1411 qpp = None
1412 for i, line in enumerate(lines):
1412 for i, line in enumerate(lines):
1413 if line == 'Patch Data:':
1413 if line == 'Patch Data:':
1414 datastart = i + 1
1414 datastart = i + 1
1415 elif line.startswith('Dirstate:'):
1415 elif line.startswith('Dirstate:'):
1416 l = line.rstrip()
1416 l = line.rstrip()
1417 l = l[10:].split(' ')
1417 l = l[10:].split(' ')
1418 qpp = [ bin(x) for x in l ]
1418 qpp = [ bin(x) for x in l ]
1419 elif datastart != None:
1419 elif datastart != None:
1420 l = line.rstrip()
1420 l = line.rstrip()
1421 se = statusentry(l)
1421 se = statusentry(l)
1422 file_ = se.name
1422 file_ = se.name
1423 if se.rev:
1423 if se.rev:
1424 applied.append(se)
1424 applied.append(se)
1425 else:
1425 else:
1426 series.append(file_)
1426 series.append(file_)
1427 if datastart is None:
1427 if datastart is None:
1428 self.ui.warn(_("No saved patch data found\n"))
1428 self.ui.warn(_("No saved patch data found\n"))
1429 return 1
1429 return 1
1430 self.ui.warn(_("restoring status: %s\n") % lines[0])
1430 self.ui.warn(_("restoring status: %s\n") % lines[0])
1431 self.full_series = series
1431 self.full_series = series
1432 self.applied = applied
1432 self.applied = applied
1433 self.parse_series()
1433 self.parse_series()
1434 self.series_dirty = 1
1434 self.series_dirty = 1
1435 self.applied_dirty = 1
1435 self.applied_dirty = 1
1436 heads = repo.changelog.heads()
1436 heads = repo.changelog.heads()
1437 if delete:
1437 if delete:
1438 if rev not in heads:
1438 if rev not in heads:
1439 self.ui.warn(_("save entry has children, leaving it alone\n"))
1439 self.ui.warn(_("save entry has children, leaving it alone\n"))
1440 else:
1440 else:
1441 self.ui.warn(_("removing save entry %s\n") % short(rev))
1441 self.ui.warn(_("removing save entry %s\n") % short(rev))
1442 pp = repo.dirstate.parents()
1442 pp = repo.dirstate.parents()
1443 if rev in pp:
1443 if rev in pp:
1444 update = True
1444 update = True
1445 else:
1445 else:
1446 update = False
1446 update = False
1447 self.strip(repo, rev, update=update, backup='strip')
1447 self.strip(repo, rev, update=update, backup='strip')
1448 if qpp:
1448 if qpp:
1449 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1449 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1450 (short(qpp[0]), short(qpp[1])))
1450 (short(qpp[0]), short(qpp[1])))
1451 if qupdate:
1451 if qupdate:
1452 self.ui.status(_("queue directory updating\n"))
1452 self.ui.status(_("queue directory updating\n"))
1453 r = self.qrepo()
1453 r = self.qrepo()
1454 if not r:
1454 if not r:
1455 self.ui.warn(_("Unable to load queue repository\n"))
1455 self.ui.warn(_("Unable to load queue repository\n"))
1456 return 1
1456 return 1
1457 hg.clean(r, qpp[0])
1457 hg.clean(r, qpp[0])
1458
1458
1459 def save(self, repo, msg=None):
1459 def save(self, repo, msg=None):
1460 if len(self.applied) == 0:
1460 if len(self.applied) == 0:
1461 self.ui.warn(_("save: no patches applied, exiting\n"))
1461 self.ui.warn(_("save: no patches applied, exiting\n"))
1462 return 1
1462 return 1
1463 if self.issaveline(self.applied[-1]):
1463 if self.issaveline(self.applied[-1]):
1464 self.ui.warn(_("status is already saved\n"))
1464 self.ui.warn(_("status is already saved\n"))
1465 return 1
1465 return 1
1466
1466
1467 ar = [ ':' + x for x in self.full_series ]
1467 ar = [ ':' + x for x in self.full_series ]
1468 if not msg:
1468 if not msg:
1469 msg = _("hg patches saved state")
1469 msg = _("hg patches saved state")
1470 else:
1470 else:
1471 msg = "hg patches: " + msg.rstrip('\r\n')
1471 msg = "hg patches: " + msg.rstrip('\r\n')
1472 r = self.qrepo()
1472 r = self.qrepo()
1473 if r:
1473 if r:
1474 pp = r.dirstate.parents()
1474 pp = r.dirstate.parents()
1475 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1475 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1476 msg += "\n\nPatch Data:\n"
1476 msg += "\n\nPatch Data:\n"
1477 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1477 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1478 "\n".join(ar) + '\n' or "")
1478 "\n".join(ar) + '\n' or "")
1479 n = repo.commit(text, force=True)
1479 n = repo.commit(text, force=True)
1480 if not n:
1480 if not n:
1481 self.ui.warn(_("repo commit failed\n"))
1481 self.ui.warn(_("repo commit failed\n"))
1482 return 1
1482 return 1
1483 self.applied.append(statusentry(hex(n),'.hg.patches.save.line'))
1483 self.applied.append(statusentry(hex(n),'.hg.patches.save.line'))
1484 self.applied_dirty = 1
1484 self.applied_dirty = 1
1485 self.removeundo(repo)
1485 self.removeundo(repo)
1486
1486
1487 def full_series_end(self):
1487 def full_series_end(self):
1488 if len(self.applied) > 0:
1488 if len(self.applied) > 0:
1489 p = self.applied[-1].name
1489 p = self.applied[-1].name
1490 end = self.find_series(p)
1490 end = self.find_series(p)
1491 if end is None:
1491 if end is None:
1492 return len(self.full_series)
1492 return len(self.full_series)
1493 return end + 1
1493 return end + 1
1494 return 0
1494 return 0
1495
1495
1496 def series_end(self, all_patches=False):
1496 def series_end(self, all_patches=False):
1497 """If all_patches is False, return the index of the next pushable patch
1497 """If all_patches is False, return the index of the next pushable patch
1498 in the series, or the series length. If all_patches is True, return the
1498 in the series, or the series length. If all_patches is True, return the
1499 index of the first patch past the last applied one.
1499 index of the first patch past the last applied one.
1500 """
1500 """
1501 end = 0
1501 end = 0
1502 def next(start):
1502 def next(start):
1503 if all_patches:
1503 if all_patches:
1504 return start
1504 return start
1505 i = start
1505 i = start
1506 while i < len(self.series):
1506 while i < len(self.series):
1507 p, reason = self.pushable(i)
1507 p, reason = self.pushable(i)
1508 if p:
1508 if p:
1509 break
1509 break
1510 self.explain_pushable(i)
1510 self.explain_pushable(i)
1511 i += 1
1511 i += 1
1512 return i
1512 return i
1513 if len(self.applied) > 0:
1513 if len(self.applied) > 0:
1514 p = self.applied[-1].name
1514 p = self.applied[-1].name
1515 try:
1515 try:
1516 end = self.series.index(p)
1516 end = self.series.index(p)
1517 except ValueError:
1517 except ValueError:
1518 return 0
1518 return 0
1519 return next(end + 1)
1519 return next(end + 1)
1520 return next(end)
1520 return next(end)
1521
1521
1522 def appliedname(self, index):
1522 def appliedname(self, index):
1523 pname = self.applied[index].name
1523 pname = self.applied[index].name
1524 if not self.ui.verbose:
1524 if not self.ui.verbose:
1525 p = pname
1525 p = pname
1526 else:
1526 else:
1527 p = str(self.series.index(pname)) + " " + pname
1527 p = str(self.series.index(pname)) + " " + pname
1528 return p
1528 return p
1529
1529
1530 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1530 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1531 force=None, git=False):
1531 force=None, git=False):
1532 def checkseries(patchname):
1532 def checkseries(patchname):
1533 if patchname in self.series:
1533 if patchname in self.series:
1534 raise util.Abort(_('patch %s is already in the series file')
1534 raise util.Abort(_('patch %s is already in the series file')
1535 % patchname)
1535 % patchname)
1536 def checkfile(patchname):
1536 def checkfile(patchname):
1537 if not force and os.path.exists(self.join(patchname)):
1537 if not force and os.path.exists(self.join(patchname)):
1538 raise util.Abort(_('patch "%s" already exists')
1538 raise util.Abort(_('patch "%s" already exists')
1539 % patchname)
1539 % patchname)
1540
1540
1541 if rev:
1541 if rev:
1542 if files:
1542 if files:
1543 raise util.Abort(_('option "-r" not valid when importing '
1543 raise util.Abort(_('option "-r" not valid when importing '
1544 'files'))
1544 'files'))
1545 rev = cmdutil.revrange(repo, rev)
1545 rev = cmdutil.revrange(repo, rev)
1546 rev.sort(reverse=True)
1546 rev.sort(reverse=True)
1547 if (len(files) > 1 or len(rev) > 1) and patchname:
1547 if (len(files) > 1 or len(rev) > 1) and patchname:
1548 raise util.Abort(_('option "-n" not valid when importing multiple '
1548 raise util.Abort(_('option "-n" not valid when importing multiple '
1549 'patches'))
1549 'patches'))
1550 i = 0
1550 i = 0
1551 added = []
1551 added = []
1552 if rev:
1552 if rev:
1553 # If mq patches are applied, we can only import revisions
1553 # If mq patches are applied, we can only import revisions
1554 # that form a linear path to qbase.
1554 # that form a linear path to qbase.
1555 # Otherwise, they should form a linear path to a head.
1555 # Otherwise, they should form a linear path to a head.
1556 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1556 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1557 if len(heads) > 1:
1557 if len(heads) > 1:
1558 raise util.Abort(_('revision %d is the root of more than one '
1558 raise util.Abort(_('revision %d is the root of more than one '
1559 'branch') % rev[-1])
1559 'branch') % rev[-1])
1560 if self.applied:
1560 if self.applied:
1561 base = hex(repo.changelog.node(rev[0]))
1561 base = hex(repo.changelog.node(rev[0]))
1562 if base in [n.rev for n in self.applied]:
1562 if base in [n.rev for n in self.applied]:
1563 raise util.Abort(_('revision %d is already managed')
1563 raise util.Abort(_('revision %d is already managed')
1564 % rev[0])
1564 % rev[0])
1565 if heads != [bin(self.applied[-1].rev)]:
1565 if heads != [bin(self.applied[-1].rev)]:
1566 raise util.Abort(_('revision %d is not the parent of '
1566 raise util.Abort(_('revision %d is not the parent of '
1567 'the queue') % rev[0])
1567 'the queue') % rev[0])
1568 base = repo.changelog.rev(bin(self.applied[0].rev))
1568 base = repo.changelog.rev(bin(self.applied[0].rev))
1569 lastparent = repo.changelog.parentrevs(base)[0]
1569 lastparent = repo.changelog.parentrevs(base)[0]
1570 else:
1570 else:
1571 if heads != [repo.changelog.node(rev[0])]:
1571 if heads != [repo.changelog.node(rev[0])]:
1572 raise util.Abort(_('revision %d has unmanaged children')
1572 raise util.Abort(_('revision %d has unmanaged children')
1573 % rev[0])
1573 % rev[0])
1574 lastparent = None
1574 lastparent = None
1575
1575
1576 if git:
1576 if git:
1577 self.diffopts().git = True
1577 self.diffopts().git = True
1578
1578
1579 for r in rev:
1579 for r in rev:
1580 p1, p2 = repo.changelog.parentrevs(r)
1580 p1, p2 = repo.changelog.parentrevs(r)
1581 n = repo.changelog.node(r)
1581 n = repo.changelog.node(r)
1582 if p2 != nullrev:
1582 if p2 != nullrev:
1583 raise util.Abort(_('cannot import merge revision %d') % r)
1583 raise util.Abort(_('cannot import merge revision %d') % r)
1584 if lastparent and lastparent != r:
1584 if lastparent and lastparent != r:
1585 raise util.Abort(_('revision %d is not the parent of %d')
1585 raise util.Abort(_('revision %d is not the parent of %d')
1586 % (r, lastparent))
1586 % (r, lastparent))
1587 lastparent = p1
1587 lastparent = p1
1588
1588
1589 if not patchname:
1589 if not patchname:
1590 patchname = normname('%d.diff' % r)
1590 patchname = normname('%d.diff' % r)
1591 self.check_reserved_name(patchname)
1591 self.check_reserved_name(patchname)
1592 checkseries(patchname)
1592 checkseries(patchname)
1593 checkfile(patchname)
1593 checkfile(patchname)
1594 self.full_series.insert(0, patchname)
1594 self.full_series.insert(0, patchname)
1595
1595
1596 patchf = self.opener(patchname, "w")
1596 patchf = self.opener(patchname, "w")
1597 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1597 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1598 patchf.close()
1598 patchf.close()
1599
1599
1600 se = statusentry(hex(n), patchname)
1600 se = statusentry(hex(n), patchname)
1601 self.applied.insert(0, se)
1601 self.applied.insert(0, se)
1602
1602
1603 added.append(patchname)
1603 added.append(patchname)
1604 patchname = None
1604 patchname = None
1605 self.parse_series()
1605 self.parse_series()
1606 self.applied_dirty = 1
1606 self.applied_dirty = 1
1607
1607
1608 for filename in files:
1608 for filename in files:
1609 if existing:
1609 if existing:
1610 if filename == '-':
1610 if filename == '-':
1611 raise util.Abort(_('-e is incompatible with import from -'))
1611 raise util.Abort(_('-e is incompatible with import from -'))
1612 if not patchname:
1612 if not patchname:
1613 patchname = normname(filename)
1613 patchname = normname(filename)
1614 self.check_reserved_name(patchname)
1614 self.check_reserved_name(patchname)
1615 if not os.path.isfile(self.join(patchname)):
1615 if not os.path.isfile(self.join(patchname)):
1616 raise util.Abort(_("patch %s does not exist") % patchname)
1616 raise util.Abort(_("patch %s does not exist") % patchname)
1617 else:
1617 else:
1618 try:
1618 try:
1619 if filename == '-':
1619 if filename == '-':
1620 if not patchname:
1620 if not patchname:
1621 raise util.Abort(_('need --name to import a patch from -'))
1621 raise util.Abort(_('need --name to import a patch from -'))
1622 text = sys.stdin.read()
1622 text = sys.stdin.read()
1623 else:
1623 else:
1624 text = url.open(self.ui, filename).read()
1624 text = url.open(self.ui, filename).read()
1625 except (OSError, IOError):
1625 except (OSError, IOError):
1626 raise util.Abort(_("unable to read %s") % filename)
1626 raise util.Abort(_("unable to read %s") % filename)
1627 if not patchname:
1627 if not patchname:
1628 patchname = normname(os.path.basename(filename))
1628 patchname = normname(os.path.basename(filename))
1629 self.check_reserved_name(patchname)
1629 self.check_reserved_name(patchname)
1630 checkfile(patchname)
1630 checkfile(patchname)
1631 patchf = self.opener(patchname, "w")
1631 patchf = self.opener(patchname, "w")
1632 patchf.write(text)
1632 patchf.write(text)
1633 if not force:
1633 if not force:
1634 checkseries(patchname)
1634 checkseries(patchname)
1635 if patchname not in self.series:
1635 if patchname not in self.series:
1636 index = self.full_series_end() + i
1636 index = self.full_series_end() + i
1637 self.full_series[index:index] = [patchname]
1637 self.full_series[index:index] = [patchname]
1638 self.parse_series()
1638 self.parse_series()
1639 self.ui.warn(_("adding %s to series file\n") % patchname)
1639 self.ui.warn(_("adding %s to series file\n") % patchname)
1640 i += 1
1640 i += 1
1641 added.append(patchname)
1641 added.append(patchname)
1642 patchname = None
1642 patchname = None
1643 self.series_dirty = 1
1643 self.series_dirty = 1
1644 qrepo = self.qrepo()
1644 qrepo = self.qrepo()
1645 if qrepo:
1645 if qrepo:
1646 qrepo.add(added)
1646 qrepo.add(added)
1647
1647
1648 def delete(ui, repo, *patches, **opts):
1648 def delete(ui, repo, *patches, **opts):
1649 """remove patches from queue
1649 """remove patches from queue
1650
1650
1651 The patches must not be applied, and at least one patch is required. With
1651 The patches must not be applied, and at least one patch is required. With
1652 -k/--keep, the patch files are preserved in the patch directory.
1652 -k/--keep, the patch files are preserved in the patch directory.
1653
1653
1654 To stop managing a patch and move it into permanent history,
1654 To stop managing a patch and move it into permanent history,
1655 use the qfinish command."""
1655 use the qfinish command."""
1656 q = repo.mq
1656 q = repo.mq
1657 q.delete(repo, patches, opts)
1657 q.delete(repo, patches, opts)
1658 q.save_dirty()
1658 q.save_dirty()
1659 return 0
1659 return 0
1660
1660
1661 def applied(ui, repo, patch=None, **opts):
1661 def applied(ui, repo, patch=None, **opts):
1662 """print the patches already applied"""
1662 """print the patches already applied"""
1663 q = repo.mq
1663 q = repo.mq
1664 if patch:
1664 if patch:
1665 if patch not in q.series:
1665 if patch not in q.series:
1666 raise util.Abort(_("patch %s is not in series file") % patch)
1666 raise util.Abort(_("patch %s is not in series file") % patch)
1667 end = q.series.index(patch) + 1
1667 end = q.series.index(patch) + 1
1668 else:
1668 else:
1669 end = q.series_end(True)
1669 end = q.series_end(True)
1670 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1670 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1671
1671
1672 def unapplied(ui, repo, patch=None, **opts):
1672 def unapplied(ui, repo, patch=None, **opts):
1673 """print the patches not yet applied"""
1673 """print the patches not yet applied"""
1674 q = repo.mq
1674 q = repo.mq
1675 if patch:
1675 if patch:
1676 if patch not in q.series:
1676 if patch not in q.series:
1677 raise util.Abort(_("patch %s is not in series file") % patch)
1677 raise util.Abort(_("patch %s is not in series file") % patch)
1678 start = q.series.index(patch) + 1
1678 start = q.series.index(patch) + 1
1679 else:
1679 else:
1680 start = q.series_end(True)
1680 start = q.series_end(True)
1681 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1681 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1682
1682
1683 def qimport(ui, repo, *filename, **opts):
1683 def qimport(ui, repo, *filename, **opts):
1684 """import a patch
1684 """import a patch
1685
1685
1686 The patch is inserted into the series after the last applied patch. If no
1686 The patch is inserted into the series after the last applied patch. If no
1687 patches have been applied, qimport prepends the patch to the series.
1687 patches have been applied, qimport prepends the patch to the series.
1688
1688
1689 The patch will have the same name as its source file unless you give it a
1689 The patch will have the same name as its source file unless you give it a
1690 new one with -n/--name.
1690 new one with -n/--name.
1691
1691
1692 You can register an existing patch inside the patch directory with the
1692 You can register an existing patch inside the patch directory with the
1693 -e/--existing flag.
1693 -e/--existing flag.
1694
1694
1695 With -f/--force, an existing patch of the same name will be overwritten.
1695 With -f/--force, an existing patch of the same name will be overwritten.
1696
1696
1697 An existing changeset may be placed under mq control with -r/--rev (e.g.
1697 An existing changeset may be placed under mq control with -r/--rev (e.g.
1698 qimport --rev tip -n patch will place tip under mq control). With
1698 qimport --rev tip -n patch will place tip under mq control). With
1699 -g/--git, patches imported with --rev will use the git diff format. See
1699 -g/--git, patches imported with --rev will use the git diff format. See
1700 the diffs help topic for information on why this is important for
1700 the diffs help topic for information on why this is important for
1701 preserving rename/copy information and permission changes.
1701 preserving rename/copy information and permission changes.
1702
1702
1703 To import a patch from standard input, pass - as the patch file. When
1703 To import a patch from standard input, pass - as the patch file. When
1704 importing from standard input, a patch name must be specified using the
1704 importing from standard input, a patch name must be specified using the
1705 --name flag.
1705 --name flag.
1706 """
1706 """
1707 q = repo.mq
1707 q = repo.mq
1708 q.qimport(repo, filename, patchname=opts['name'],
1708 q.qimport(repo, filename, patchname=opts['name'],
1709 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1709 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1710 git=opts['git'])
1710 git=opts['git'])
1711 q.save_dirty()
1711 q.save_dirty()
1712
1712
1713 if opts.get('push') and not opts.get('rev'):
1713 if opts.get('push') and not opts.get('rev'):
1714 return q.push(repo, None)
1714 return q.push(repo, None)
1715 return 0
1715 return 0
1716
1716
1717 def init(ui, repo, **opts):
1717 def init(ui, repo, **opts):
1718 """init a new queue repository
1718 """init a new queue repository
1719
1719
1720 The queue repository is unversioned by default. If -c/--create-repo is
1720 The queue repository is unversioned by default. If -c/--create-repo is
1721 specified, qinit will create a separate nested repository for patches
1721 specified, qinit will create a separate nested repository for patches
1722 (qinit -c may also be run later to convert an unversioned patch repository
1722 (qinit -c may also be run later to convert an unversioned patch repository
1723 into a versioned one). You can use qcommit to commit changes to this queue
1723 into a versioned one). You can use qcommit to commit changes to this queue
1724 repository.
1724 repository.
1725 """
1725 """
1726 q = repo.mq
1726 q = repo.mq
1727 r = q.init(repo, create=opts['create_repo'])
1727 r = q.init(repo, create=opts['create_repo'])
1728 q.save_dirty()
1728 q.save_dirty()
1729 if r:
1729 if r:
1730 if not os.path.exists(r.wjoin('.hgignore')):
1730 if not os.path.exists(r.wjoin('.hgignore')):
1731 fp = r.wopener('.hgignore', 'w')
1731 fp = r.wopener('.hgignore', 'w')
1732 fp.write('^\\.hg\n')
1732 fp.write('^\\.hg\n')
1733 fp.write('^\\.mq\n')
1733 fp.write('^\\.mq\n')
1734 fp.write('syntax: glob\n')
1734 fp.write('syntax: glob\n')
1735 fp.write('status\n')
1735 fp.write('status\n')
1736 fp.write('guards\n')
1736 fp.write('guards\n')
1737 fp.close()
1737 fp.close()
1738 if not os.path.exists(r.wjoin('series')):
1738 if not os.path.exists(r.wjoin('series')):
1739 r.wopener('series', 'w').close()
1739 r.wopener('series', 'w').close()
1740 r.add(['.hgignore', 'series'])
1740 r.add(['.hgignore', 'series'])
1741 commands.add(ui, r)
1741 commands.add(ui, r)
1742 return 0
1742 return 0
1743
1743
1744 def clone(ui, source, dest=None, **opts):
1744 def clone(ui, source, dest=None, **opts):
1745 '''clone main and patch repository at same time
1745 '''clone main and patch repository at same time
1746
1746
1747 If source is local, destination will have no patches applied. If source is
1747 If source is local, destination will have no patches applied. If source is
1748 remote, this command can not check if patches are applied in source, so
1748 remote, this command can not check if patches are applied in source, so
1749 cannot guarantee that patches are not applied in destination. If you clone
1749 cannot guarantee that patches are not applied in destination. If you clone
1750 remote repository, be sure before that it has no patches applied.
1750 remote repository, be sure before that it has no patches applied.
1751
1751
1752 Source patch repository is looked for in <src>/.hg/patches by default. Use
1752 Source patch repository is looked for in <src>/.hg/patches by default. Use
1753 -p <url> to change.
1753 -p <url> to change.
1754
1754
1755 The patch directory must be a nested Mercurial repository, as would be
1755 The patch directory must be a nested Mercurial repository, as would be
1756 created by qinit -c.
1756 created by qinit -c.
1757 '''
1757 '''
1758 def patchdir(repo):
1758 def patchdir(repo):
1759 url = repo.url()
1759 url = repo.url()
1760 if url.endswith('/'):
1760 if url.endswith('/'):
1761 url = url[:-1]
1761 url = url[:-1]
1762 return url + '/.hg/patches'
1762 return url + '/.hg/patches'
1763 if dest is None:
1763 if dest is None:
1764 dest = hg.defaultdest(source)
1764 dest = hg.defaultdest(source)
1765 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1765 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1766 if opts['patches']:
1766 if opts['patches']:
1767 patchespath = ui.expandpath(opts['patches'])
1767 patchespath = ui.expandpath(opts['patches'])
1768 else:
1768 else:
1769 patchespath = patchdir(sr)
1769 patchespath = patchdir(sr)
1770 try:
1770 try:
1771 hg.repository(ui, patchespath)
1771 hg.repository(ui, patchespath)
1772 except error.RepoError:
1772 except error.RepoError:
1773 raise util.Abort(_('versioned patch repository not found'
1773 raise util.Abort(_('versioned patch repository not found'
1774 ' (see qinit -c)'))
1774 ' (see qinit -c)'))
1775 qbase, destrev = None, None
1775 qbase, destrev = None, None
1776 if sr.local():
1776 if sr.local():
1777 if sr.mq.applied:
1777 if sr.mq.applied:
1778 qbase = bin(sr.mq.applied[0].rev)
1778 qbase = bin(sr.mq.applied[0].rev)
1779 if not hg.islocal(dest):
1779 if not hg.islocal(dest):
1780 heads = set(sr.heads())
1780 heads = set(sr.heads())
1781 destrev = list(heads.difference(sr.heads(qbase)))
1781 destrev = list(heads.difference(sr.heads(qbase)))
1782 destrev.append(sr.changelog.parents(qbase)[0])
1782 destrev.append(sr.changelog.parents(qbase)[0])
1783 elif sr.capable('lookup'):
1783 elif sr.capable('lookup'):
1784 try:
1784 try:
1785 qbase = sr.lookup('qbase')
1785 qbase = sr.lookup('qbase')
1786 except error.RepoError:
1786 except error.RepoError:
1787 pass
1787 pass
1788 ui.note(_('cloning main repository\n'))
1788 ui.note(_('cloning main repository\n'))
1789 sr, dr = hg.clone(ui, sr.url(), dest,
1789 sr, dr = hg.clone(ui, sr.url(), dest,
1790 pull=opts['pull'],
1790 pull=opts['pull'],
1791 rev=destrev,
1791 rev=destrev,
1792 update=False,
1792 update=False,
1793 stream=opts['uncompressed'])
1793 stream=opts['uncompressed'])
1794 ui.note(_('cloning patch repository\n'))
1794 ui.note(_('cloning patch repository\n'))
1795 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1795 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1796 pull=opts['pull'], update=not opts['noupdate'],
1796 pull=opts['pull'], update=not opts['noupdate'],
1797 stream=opts['uncompressed'])
1797 stream=opts['uncompressed'])
1798 if dr.local():
1798 if dr.local():
1799 if qbase:
1799 if qbase:
1800 ui.note(_('stripping applied patches from destination '
1800 ui.note(_('stripping applied patches from destination '
1801 'repository\n'))
1801 'repository\n'))
1802 dr.mq.strip(dr, qbase, update=False, backup=None)
1802 dr.mq.strip(dr, qbase, update=False, backup=None)
1803 if not opts['noupdate']:
1803 if not opts['noupdate']:
1804 ui.note(_('updating destination repository\n'))
1804 ui.note(_('updating destination repository\n'))
1805 hg.update(dr, dr.changelog.tip())
1805 hg.update(dr, dr.changelog.tip())
1806
1806
1807 def commit(ui, repo, *pats, **opts):
1807 def commit(ui, repo, *pats, **opts):
1808 """commit changes in the queue repository"""
1808 """commit changes in the queue repository"""
1809 q = repo.mq
1809 q = repo.mq
1810 r = q.qrepo()
1810 r = q.qrepo()
1811 if not r: raise util.Abort('no queue repository')
1811 if not r: raise util.Abort('no queue repository')
1812 commands.commit(r.ui, r, *pats, **opts)
1812 commands.commit(r.ui, r, *pats, **opts)
1813
1813
1814 def series(ui, repo, **opts):
1814 def series(ui, repo, **opts):
1815 """print the entire series file"""
1815 """print the entire series file"""
1816 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1816 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1817 return 0
1817 return 0
1818
1818
1819 def top(ui, repo, **opts):
1819 def top(ui, repo, **opts):
1820 """print the name of the current patch"""
1820 """print the name of the current patch"""
1821 q = repo.mq
1821 q = repo.mq
1822 t = q.applied and q.series_end(True) or 0
1822 t = q.applied and q.series_end(True) or 0
1823 if t:
1823 if t:
1824 return q.qseries(repo, start=t-1, length=1, status='A',
1824 return q.qseries(repo, start=t-1, length=1, status='A',
1825 summary=opts.get('summary'))
1825 summary=opts.get('summary'))
1826 else:
1826 else:
1827 ui.write(_("no patches applied\n"))
1827 ui.write(_("no patches applied\n"))
1828 return 1
1828 return 1
1829
1829
1830 def next(ui, repo, **opts):
1830 def next(ui, repo, **opts):
1831 """print the name of the next patch"""
1831 """print the name of the next patch"""
1832 q = repo.mq
1832 q = repo.mq
1833 end = q.series_end()
1833 end = q.series_end()
1834 if end == len(q.series):
1834 if end == len(q.series):
1835 ui.write(_("all patches applied\n"))
1835 ui.write(_("all patches applied\n"))
1836 return 1
1836 return 1
1837 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1837 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1838
1838
1839 def prev(ui, repo, **opts):
1839 def prev(ui, repo, **opts):
1840 """print the name of the previous patch"""
1840 """print the name of the previous patch"""
1841 q = repo.mq
1841 q = repo.mq
1842 l = len(q.applied)
1842 l = len(q.applied)
1843 if l == 1:
1843 if l == 1:
1844 ui.write(_("only one patch applied\n"))
1844 ui.write(_("only one patch applied\n"))
1845 return 1
1845 return 1
1846 if not l:
1846 if not l:
1847 ui.write(_("no patches applied\n"))
1847 ui.write(_("no patches applied\n"))
1848 return 1
1848 return 1
1849 return q.qseries(repo, start=l-2, length=1, status='A',
1849 return q.qseries(repo, start=l-2, length=1, status='A',
1850 summary=opts.get('summary'))
1850 summary=opts.get('summary'))
1851
1851
1852 def setupheaderopts(ui, opts):
1852 def setupheaderopts(ui, opts):
1853 def do(opt,val):
1853 def do(opt,val):
1854 if not opts[opt] and opts['current' + opt]:
1854 if not opts[opt] and opts['current' + opt]:
1855 opts[opt] = val
1855 opts[opt] = val
1856 do('user', ui.username())
1856 do('user', ui.username())
1857 do('date', "%d %d" % util.makedate())
1857 do('date', "%d %d" % util.makedate())
1858
1858
1859 def new(ui, repo, patch, *args, **opts):
1859 def new(ui, repo, patch, *args, **opts):
1860 """create a new patch
1860 """create a new patch
1861
1861
1862 qnew creates a new patch on top of the currently-applied patch (if any).
1862 qnew creates a new patch on top of the currently-applied patch (if any).
1863 It will refuse to run if there are any outstanding changes unless
1863 It will refuse to run if there are any outstanding changes unless
1864 -f/--force is specified, in which case the patch will be initialized with
1864 -f/--force is specified, in which case the patch will be initialized with
1865 them. You may also use -I/--include, -X/--exclude, and/or a list of files
1865 them. You may also use -I/--include, -X/--exclude, and/or a list of files
1866 after the patch name to add only changes to matching files to the new
1866 after the patch name to add only changes to matching files to the new
1867 patch, leaving the rest as uncommitted modifications.
1867 patch, leaving the rest as uncommitted modifications.
1868
1868
1869 -u/--user and -d/--date can be used to set the (given) user and date,
1869 -u/--user and -d/--date can be used to set the (given) user and date,
1870 respectively. -U/--currentuser and -D/--currentdate set user to current
1870 respectively. -U/--currentuser and -D/--currentdate set user to current
1871 user and date to current date.
1871 user and date to current date.
1872
1872
1873 -e/--edit, -m/--message or -l/--logfile set the patch header as well as
1873 -e/--edit, -m/--message or -l/--logfile set the patch header as well as
1874 the commit message. If none is specified, the header is empty and the
1874 the commit message. If none is specified, the header is empty and the
1875 commit message is '[mq]: PATCH'.
1875 commit message is '[mq]: PATCH'.
1876
1876
1877 Use the -g/--git option to keep the patch in the git extended diff format.
1877 Use the -g/--git option to keep the patch in the git extended diff format.
1878 Read the diffs help topic for more information on why this is important
1878 Read the diffs help topic for more information on why this is important
1879 for preserving permission changes and copy/rename information.
1879 for preserving permission changes and copy/rename information.
1880 """
1880 """
1881 msg = cmdutil.logmessage(opts)
1881 msg = cmdutil.logmessage(opts)
1882 def getmsg(): return ui.edit(msg, ui.username())
1882 def getmsg(): return ui.edit(msg, ui.username())
1883 q = repo.mq
1883 q = repo.mq
1884 opts['msg'] = msg
1884 opts['msg'] = msg
1885 if opts.get('edit'):
1885 if opts.get('edit'):
1886 opts['msg'] = getmsg
1886 opts['msg'] = getmsg
1887 else:
1887 else:
1888 opts['msg'] = msg
1888 opts['msg'] = msg
1889 setupheaderopts(ui, opts)
1889 setupheaderopts(ui, opts)
1890 q.new(repo, patch, *args, **opts)
1890 q.new(repo, patch, *args, **opts)
1891 q.save_dirty()
1891 q.save_dirty()
1892 return 0
1892 return 0
1893
1893
1894 def refresh(ui, repo, *pats, **opts):
1894 def refresh(ui, repo, *pats, **opts):
1895 """update the current patch
1895 """update the current patch
1896
1896
1897 If any file patterns are provided, the refreshed patch will contain only
1897 If any file patterns are provided, the refreshed patch will contain only
1898 the modifications that match those patterns; the remaining modifications
1898 the modifications that match those patterns; the remaining modifications
1899 will remain in the working directory.
1899 will remain in the working directory.
1900
1900
1901 If -s/--short is specified, files currently included in the patch will be
1901 If -s/--short is specified, files currently included in the patch will be
1902 refreshed just like matched files and remain in the patch.
1902 refreshed just like matched files and remain in the patch.
1903
1903
1904 hg add/remove/copy/rename work as usual, though you might want to use
1904 hg add/remove/copy/rename work as usual, though you might want to use
1905 git-style patches (-g/--git or [diff] git=1) to track copies and renames.
1905 git-style patches (-g/--git or [diff] git=1) to track copies and renames.
1906 See the diffs help topic for more information on the git diff format.
1906 See the diffs help topic for more information on the git diff format.
1907 """
1907 """
1908 q = repo.mq
1908 q = repo.mq
1909 message = cmdutil.logmessage(opts)
1909 message = cmdutil.logmessage(opts)
1910 if opts['edit']:
1910 if opts['edit']:
1911 if not q.applied:
1911 if not q.applied:
1912 ui.write(_("no patches applied\n"))
1912 ui.write(_("no patches applied\n"))
1913 return 1
1913 return 1
1914 if message:
1914 if message:
1915 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1915 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1916 patch = q.applied[-1].name
1916 patch = q.applied[-1].name
1917 ph = patchheader(q.join(patch))
1917 ph = patchheader(q.join(patch))
1918 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
1918 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
1919 setupheaderopts(ui, opts)
1919 setupheaderopts(ui, opts)
1920 ret = q.refresh(repo, pats, msg=message, **opts)
1920 ret = q.refresh(repo, pats, msg=message, **opts)
1921 q.save_dirty()
1921 q.save_dirty()
1922 return ret
1922 return ret
1923
1923
1924 def diff(ui, repo, *pats, **opts):
1924 def diff(ui, repo, *pats, **opts):
1925 """diff of the current patch and subsequent modifications
1925 """diff of the current patch and subsequent modifications
1926
1926
1927 Shows a diff which includes the current patch as well as any changes which
1927 Shows a diff which includes the current patch as well as any changes which
1928 have been made in the working directory since the last refresh (thus
1928 have been made in the working directory since the last refresh (thus
1929 showing what the current patch would become after a qrefresh).
1929 showing what the current patch would become after a qrefresh).
1930
1930
1931 Use 'hg diff' if you only want to see the changes made since the last
1931 Use 'hg diff' if you only want to see the changes made since the last
1932 qrefresh, or 'hg export qtip' if you want to see changes made by the
1932 qrefresh, or 'hg export qtip' if you want to see changes made by the
1933 current patch without including changes made since the qrefresh.
1933 current patch without including changes made since the qrefresh.
1934 """
1934 """
1935 repo.mq.diff(repo, pats, opts)
1935 repo.mq.diff(repo, pats, opts)
1936 return 0
1936 return 0
1937
1937
1938 def fold(ui, repo, *files, **opts):
1938 def fold(ui, repo, *files, **opts):
1939 """fold the named patches into the current patch
1939 """fold the named patches into the current patch
1940
1940
1941 Patches must not yet be applied. Each patch will be successively applied
1941 Patches must not yet be applied. Each patch will be successively applied
1942 to the current patch in the order given. If all the patches apply
1942 to the current patch in the order given. If all the patches apply
1943 successfully, the current patch will be refreshed with the new cumulative
1943 successfully, the current patch will be refreshed with the new cumulative
1944 patch, and the folded patches will be deleted. With -k/--keep, the folded
1944 patch, and the folded patches will be deleted. With -k/--keep, the folded
1945 patch files will not be removed afterwards.
1945 patch files will not be removed afterwards.
1946
1946
1947 The header for each folded patch will be concatenated with the current
1947 The header for each folded patch will be concatenated with the current
1948 patch header, separated by a line of '* * *'.
1948 patch header, separated by a line of '* * *'.
1949 """
1949 """
1950
1950
1951 q = repo.mq
1951 q = repo.mq
1952
1952
1953 if not files:
1953 if not files:
1954 raise util.Abort(_('qfold requires at least one patch name'))
1954 raise util.Abort(_('qfold requires at least one patch name'))
1955 if not q.check_toppatch(repo):
1955 if not q.check_toppatch(repo):
1956 raise util.Abort(_('No patches applied'))
1956 raise util.Abort(_('No patches applied'))
1957 q.check_localchanges(repo)
1957 q.check_localchanges(repo)
1958
1958
1959 message = cmdutil.logmessage(opts)
1959 message = cmdutil.logmessage(opts)
1960 if opts['edit']:
1960 if opts['edit']:
1961 if message:
1961 if message:
1962 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1962 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1963
1963
1964 parent = q.lookup('qtip')
1964 parent = q.lookup('qtip')
1965 patches = []
1965 patches = []
1966 messages = []
1966 messages = []
1967 for f in files:
1967 for f in files:
1968 p = q.lookup(f)
1968 p = q.lookup(f)
1969 if p in patches or p == parent:
1969 if p in patches or p == parent:
1970 ui.warn(_('Skipping already folded patch %s') % p)
1970 ui.warn(_('Skipping already folded patch %s') % p)
1971 if q.isapplied(p):
1971 if q.isapplied(p):
1972 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1972 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1973 patches.append(p)
1973 patches.append(p)
1974
1974
1975 for p in patches:
1975 for p in patches:
1976 if not message:
1976 if not message:
1977 ph = patchheader(q.join(p))
1977 ph = patchheader(q.join(p))
1978 if ph.message:
1978 if ph.message:
1979 messages.append(ph.message)
1979 messages.append(ph.message)
1980 pf = q.join(p)
1980 pf = q.join(p)
1981 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1981 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1982 if not patchsuccess:
1982 if not patchsuccess:
1983 raise util.Abort(_('Error folding patch %s') % p)
1983 raise util.Abort(_('Error folding patch %s') % p)
1984 patch.updatedir(ui, repo, files)
1984 patch.updatedir(ui, repo, files)
1985
1985
1986 if not message:
1986 if not message:
1987 ph = patchheader(q.join(parent))
1987 ph = patchheader(q.join(parent))
1988 message, user = ph.message, ph.user
1988 message, user = ph.message, ph.user
1989 for msg in messages:
1989 for msg in messages:
1990 message.append('* * *')
1990 message.append('* * *')
1991 message.extend(msg)
1991 message.extend(msg)
1992 message = '\n'.join(message)
1992 message = '\n'.join(message)
1993
1993
1994 if opts['edit']:
1994 if opts['edit']:
1995 message = ui.edit(message, user or ui.username())
1995 message = ui.edit(message, user or ui.username())
1996
1996
1997 q.refresh(repo, msg=message)
1997 q.refresh(repo, msg=message)
1998 q.delete(repo, patches, opts)
1998 q.delete(repo, patches, opts)
1999 q.save_dirty()
1999 q.save_dirty()
2000
2000
2001 def goto(ui, repo, patch, **opts):
2001 def goto(ui, repo, patch, **opts):
2002 '''push or pop patches until named patch is at top of stack'''
2002 '''push or pop patches until named patch is at top of stack'''
2003 q = repo.mq
2003 q = repo.mq
2004 patch = q.lookup(patch)
2004 patch = q.lookup(patch)
2005 if q.isapplied(patch):
2005 if q.isapplied(patch):
2006 ret = q.pop(repo, patch, force=opts['force'])
2006 ret = q.pop(repo, patch, force=opts['force'])
2007 else:
2007 else:
2008 ret = q.push(repo, patch, force=opts['force'])
2008 ret = q.push(repo, patch, force=opts['force'])
2009 q.save_dirty()
2009 q.save_dirty()
2010 return ret
2010 return ret
2011
2011
2012 def guard(ui, repo, *args, **opts):
2012 def guard(ui, repo, *args, **opts):
2013 '''set or print guards for a patch
2013 '''set or print guards for a patch
2014
2014
2015 Guards control whether a patch can be pushed. A patch with no guards is
2015 Guards control whether a patch can be pushed. A patch with no guards is
2016 always pushed. A patch with a positive guard ("+foo") is pushed only if
2016 always pushed. A patch with a positive guard ("+foo") is pushed only if
2017 the qselect command has activated it. A patch with a negative guard
2017 the qselect command has activated it. A patch with a negative guard
2018 ("-foo") is never pushed if the qselect command has activated it.
2018 ("-foo") is never pushed if the qselect command has activated it.
2019
2019
2020 With no arguments, print the currently active guards. With arguments, set
2020 With no arguments, print the currently active guards. With arguments, set
2021 guards for the named patch.
2021 guards for the named patch.
2022 NOTE: Specifying negative guards now requires '--'.
2022 NOTE: Specifying negative guards now requires '--'.
2023
2023
2024 To set guards on another patch:
2024 To set guards on another patch:
2025 hg qguard -- other.patch +2.6.17 -stable
2025 hg qguard -- other.patch +2.6.17 -stable
2026 '''
2026 '''
2027 def status(idx):
2027 def status(idx):
2028 guards = q.series_guards[idx] or ['unguarded']
2028 guards = q.series_guards[idx] or ['unguarded']
2029 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2029 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2030 q = repo.mq
2030 q = repo.mq
2031 patch = None
2031 patch = None
2032 args = list(args)
2032 args = list(args)
2033 if opts['list']:
2033 if opts['list']:
2034 if args or opts['none']:
2034 if args or opts['none']:
2035 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2035 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2036 for i in xrange(len(q.series)):
2036 for i in xrange(len(q.series)):
2037 status(i)
2037 status(i)
2038 return
2038 return
2039 if not args or args[0][0:1] in '-+':
2039 if not args or args[0][0:1] in '-+':
2040 if not q.applied:
2040 if not q.applied:
2041 raise util.Abort(_('no patches applied'))
2041 raise util.Abort(_('no patches applied'))
2042 patch = q.applied[-1].name
2042 patch = q.applied[-1].name
2043 if patch is None and args[0][0:1] not in '-+':
2043 if patch is None and args[0][0:1] not in '-+':
2044 patch = args.pop(0)
2044 patch = args.pop(0)
2045 if patch is None:
2045 if patch is None:
2046 raise util.Abort(_('no patch to work with'))
2046 raise util.Abort(_('no patch to work with'))
2047 if args or opts['none']:
2047 if args or opts['none']:
2048 idx = q.find_series(patch)
2048 idx = q.find_series(patch)
2049 if idx is None:
2049 if idx is None:
2050 raise util.Abort(_('no patch named %s') % patch)
2050 raise util.Abort(_('no patch named %s') % patch)
2051 q.set_guards(idx, args)
2051 q.set_guards(idx, args)
2052 q.save_dirty()
2052 q.save_dirty()
2053 else:
2053 else:
2054 status(q.series.index(q.lookup(patch)))
2054 status(q.series.index(q.lookup(patch)))
2055
2055
2056 def header(ui, repo, patch=None):
2056 def header(ui, repo, patch=None):
2057 """print the header of the topmost or specified patch"""
2057 """print the header of the topmost or specified patch"""
2058 q = repo.mq
2058 q = repo.mq
2059
2059
2060 if patch:
2060 if patch:
2061 patch = q.lookup(patch)
2061 patch = q.lookup(patch)
2062 else:
2062 else:
2063 if not q.applied:
2063 if not q.applied:
2064 ui.write('no patches applied\n')
2064 ui.write('no patches applied\n')
2065 return 1
2065 return 1
2066 patch = q.lookup('qtip')
2066 patch = q.lookup('qtip')
2067 ph = patchheader(repo.mq.join(patch))
2067 ph = patchheader(repo.mq.join(patch))
2068
2068
2069 ui.write('\n'.join(ph.message) + '\n')
2069 ui.write('\n'.join(ph.message) + '\n')
2070
2070
2071 def lastsavename(path):
2071 def lastsavename(path):
2072 (directory, base) = os.path.split(path)
2072 (directory, base) = os.path.split(path)
2073 names = os.listdir(directory)
2073 names = os.listdir(directory)
2074 namere = re.compile("%s.([0-9]+)" % base)
2074 namere = re.compile("%s.([0-9]+)" % base)
2075 maxindex = None
2075 maxindex = None
2076 maxname = None
2076 maxname = None
2077 for f in names:
2077 for f in names:
2078 m = namere.match(f)
2078 m = namere.match(f)
2079 if m:
2079 if m:
2080 index = int(m.group(1))
2080 index = int(m.group(1))
2081 if maxindex is None or index > maxindex:
2081 if maxindex is None or index > maxindex:
2082 maxindex = index
2082 maxindex = index
2083 maxname = f
2083 maxname = f
2084 if maxname:
2084 if maxname:
2085 return (os.path.join(directory, maxname), maxindex)
2085 return (os.path.join(directory, maxname), maxindex)
2086 return (None, None)
2086 return (None, None)
2087
2087
2088 def savename(path):
2088 def savename(path):
2089 (last, index) = lastsavename(path)
2089 (last, index) = lastsavename(path)
2090 if last is None:
2090 if last is None:
2091 index = 0
2091 index = 0
2092 newpath = path + ".%d" % (index + 1)
2092 newpath = path + ".%d" % (index + 1)
2093 return newpath
2093 return newpath
2094
2094
2095 def push(ui, repo, patch=None, **opts):
2095 def push(ui, repo, patch=None, **opts):
2096 """push the next patch onto the stack
2096 """push the next patch onto the stack
2097
2097
2098 When -f/--force is applied, all local changes in patched files will be
2098 When -f/--force is applied, all local changes in patched files will be
2099 lost.
2099 lost.
2100 """
2100 """
2101 q = repo.mq
2101 q = repo.mq
2102 mergeq = None
2102 mergeq = None
2103
2103
2104 if opts['merge']:
2104 if opts['merge']:
2105 if opts['name']:
2105 if opts['name']:
2106 newpath = repo.join(opts['name'])
2106 newpath = repo.join(opts['name'])
2107 else:
2107 else:
2108 newpath, i = lastsavename(q.path)
2108 newpath, i = lastsavename(q.path)
2109 if not newpath:
2109 if not newpath:
2110 ui.warn(_("no saved queues found, please use -n\n"))
2110 ui.warn(_("no saved queues found, please use -n\n"))
2111 return 1
2111 return 1
2112 mergeq = queue(ui, repo.join(""), newpath)
2112 mergeq = queue(ui, repo.join(""), newpath)
2113 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2113 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2114 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2114 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2115 mergeq=mergeq, all=opts.get('all'))
2115 mergeq=mergeq, all=opts.get('all'))
2116 return ret
2116 return ret
2117
2117
2118 def pop(ui, repo, patch=None, **opts):
2118 def pop(ui, repo, patch=None, **opts):
2119 """pop the current patch off the stack
2119 """pop the current patch off the stack
2120
2120
2121 By default, pops off the top of the patch stack. If given a patch name,
2121 By default, pops off the top of the patch stack. If given a patch name,
2122 keeps popping off patches until the named patch is at the top of the
2122 keeps popping off patches until the named patch is at the top of the
2123 stack.
2123 stack.
2124 """
2124 """
2125 localupdate = True
2125 localupdate = True
2126 if opts['name']:
2126 if opts['name']:
2127 q = queue(ui, repo.join(""), repo.join(opts['name']))
2127 q = queue(ui, repo.join(""), repo.join(opts['name']))
2128 ui.warn(_('using patch queue: %s\n') % q.path)
2128 ui.warn(_('using patch queue: %s\n') % q.path)
2129 localupdate = False
2129 localupdate = False
2130 else:
2130 else:
2131 q = repo.mq
2131 q = repo.mq
2132 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2132 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2133 all=opts['all'])
2133 all=opts['all'])
2134 q.save_dirty()
2134 q.save_dirty()
2135 return ret
2135 return ret
2136
2136
2137 def rename(ui, repo, patch, name=None, **opts):
2137 def rename(ui, repo, patch, name=None, **opts):
2138 """rename a patch
2138 """rename a patch
2139
2139
2140 With one argument, renames the current patch to PATCH1.
2140 With one argument, renames the current patch to PATCH1.
2141 With two arguments, renames PATCH1 to PATCH2."""
2141 With two arguments, renames PATCH1 to PATCH2."""
2142
2142
2143 q = repo.mq
2143 q = repo.mq
2144
2144
2145 if not name:
2145 if not name:
2146 name = patch
2146 name = patch
2147 patch = None
2147 patch = None
2148
2148
2149 if patch:
2149 if patch:
2150 patch = q.lookup(patch)
2150 patch = q.lookup(patch)
2151 else:
2151 else:
2152 if not q.applied:
2152 if not q.applied:
2153 ui.write(_('no patches applied\n'))
2153 ui.write(_('no patches applied\n'))
2154 return
2154 return
2155 patch = q.lookup('qtip')
2155 patch = q.lookup('qtip')
2156 absdest = q.join(name)
2156 absdest = q.join(name)
2157 if os.path.isdir(absdest):
2157 if os.path.isdir(absdest):
2158 name = normname(os.path.join(name, os.path.basename(patch)))
2158 name = normname(os.path.join(name, os.path.basename(patch)))
2159 absdest = q.join(name)
2159 absdest = q.join(name)
2160 if os.path.exists(absdest):
2160 if os.path.exists(absdest):
2161 raise util.Abort(_('%s already exists') % absdest)
2161 raise util.Abort(_('%s already exists') % absdest)
2162
2162
2163 if name in q.series:
2163 if name in q.series:
2164 raise util.Abort(_('A patch named %s already exists in the series file') % name)
2164 raise util.Abort(_('A patch named %s already exists in the series file') % name)
2165
2165
2166 if ui.verbose:
2166 if ui.verbose:
2167 ui.write('renaming %s to %s\n' % (patch, name))
2167 ui.write('renaming %s to %s\n' % (patch, name))
2168 i = q.find_series(patch)
2168 i = q.find_series(patch)
2169 guards = q.guard_re.findall(q.full_series[i])
2169 guards = q.guard_re.findall(q.full_series[i])
2170 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2170 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2171 q.parse_series()
2171 q.parse_series()
2172 q.series_dirty = 1
2172 q.series_dirty = 1
2173
2173
2174 info = q.isapplied(patch)
2174 info = q.isapplied(patch)
2175 if info:
2175 if info:
2176 q.applied[info[0]] = statusentry(info[1], name)
2176 q.applied[info[0]] = statusentry(info[1], name)
2177 q.applied_dirty = 1
2177 q.applied_dirty = 1
2178
2178
2179 util.rename(q.join(patch), absdest)
2179 util.rename(q.join(patch), absdest)
2180 r = q.qrepo()
2180 r = q.qrepo()
2181 if r:
2181 if r:
2182 wlock = r.wlock()
2182 wlock = r.wlock()
2183 try:
2183 try:
2184 if r.dirstate[patch] == 'a':
2184 if r.dirstate[patch] == 'a':
2185 r.dirstate.forget(patch)
2185 r.dirstate.forget(patch)
2186 r.dirstate.add(name)
2186 r.dirstate.add(name)
2187 else:
2187 else:
2188 if r.dirstate[name] == 'r':
2188 if r.dirstate[name] == 'r':
2189 r.undelete([name])
2189 r.undelete([name])
2190 r.copy(patch, name)
2190 r.copy(patch, name)
2191 r.remove([patch], False)
2191 r.remove([patch], False)
2192 finally:
2192 finally:
2193 wlock.release()
2193 wlock.release()
2194
2194
2195 q.save_dirty()
2195 q.save_dirty()
2196
2196
2197 def restore(ui, repo, rev, **opts):
2197 def restore(ui, repo, rev, **opts):
2198 """restore the queue state saved by a revision"""
2198 """restore the queue state saved by a revision"""
2199 rev = repo.lookup(rev)
2199 rev = repo.lookup(rev)
2200 q = repo.mq
2200 q = repo.mq
2201 q.restore(repo, rev, delete=opts['delete'],
2201 q.restore(repo, rev, delete=opts['delete'],
2202 qupdate=opts['update'])
2202 qupdate=opts['update'])
2203 q.save_dirty()
2203 q.save_dirty()
2204 return 0
2204 return 0
2205
2205
2206 def save(ui, repo, **opts):
2206 def save(ui, repo, **opts):
2207 """save current queue state"""
2207 """save current queue state"""
2208 q = repo.mq
2208 q = repo.mq
2209 message = cmdutil.logmessage(opts)
2209 message = cmdutil.logmessage(opts)
2210 ret = q.save(repo, msg=message)
2210 ret = q.save(repo, msg=message)
2211 if ret:
2211 if ret:
2212 return ret
2212 return ret
2213 q.save_dirty()
2213 q.save_dirty()
2214 if opts['copy']:
2214 if opts['copy']:
2215 path = q.path
2215 path = q.path
2216 if opts['name']:
2216 if opts['name']:
2217 newpath = os.path.join(q.basepath, opts['name'])
2217 newpath = os.path.join(q.basepath, opts['name'])
2218 if os.path.exists(newpath):
2218 if os.path.exists(newpath):
2219 if not os.path.isdir(newpath):
2219 if not os.path.isdir(newpath):
2220 raise util.Abort(_('destination %s exists and is not '
2220 raise util.Abort(_('destination %s exists and is not '
2221 'a directory') % newpath)
2221 'a directory') % newpath)
2222 if not opts['force']:
2222 if not opts['force']:
2223 raise util.Abort(_('destination %s exists, '
2223 raise util.Abort(_('destination %s exists, '
2224 'use -f to force') % newpath)
2224 'use -f to force') % newpath)
2225 else:
2225 else:
2226 newpath = savename(path)
2226 newpath = savename(path)
2227 ui.warn(_("copy %s to %s\n") % (path, newpath))
2227 ui.warn(_("copy %s to %s\n") % (path, newpath))
2228 util.copyfiles(path, newpath)
2228 util.copyfiles(path, newpath)
2229 if opts['empty']:
2229 if opts['empty']:
2230 try:
2230 try:
2231 os.unlink(q.join(q.status_path))
2231 os.unlink(q.join(q.status_path))
2232 except:
2232 except:
2233 pass
2233 pass
2234 return 0
2234 return 0
2235
2235
2236 def strip(ui, repo, rev, **opts):
2236 def strip(ui, repo, rev, **opts):
2237 """strip a revision and all its descendants from the repository
2237 """strip a revision and all its descendants from the repository
2238
2238
2239 If one of the working directory's parent revisions is stripped, the
2239 If one of the working directory's parent revisions is stripped, the
2240 working directory will be updated to the parent of the stripped revision.
2240 working directory will be updated to the parent of the stripped revision.
2241 """
2241 """
2242 backup = 'all'
2242 backup = 'all'
2243 if opts['backup']:
2243 if opts['backup']:
2244 backup = 'strip'
2244 backup = 'strip'
2245 elif opts['nobackup']:
2245 elif opts['nobackup']:
2246 backup = 'none'
2246 backup = 'none'
2247
2247
2248 rev = repo.lookup(rev)
2248 rev = repo.lookup(rev)
2249 p = repo.dirstate.parents()
2249 p = repo.dirstate.parents()
2250 cl = repo.changelog
2250 cl = repo.changelog
2251 update = True
2251 update = True
2252 if p[0] == nullid:
2252 if p[0] == nullid:
2253 update = False
2253 update = False
2254 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2254 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2255 update = False
2255 update = False
2256 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2256 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2257 update = False
2257 update = False
2258
2258
2259 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2259 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2260 return 0
2260 return 0
2261
2261
2262 def select(ui, repo, *args, **opts):
2262 def select(ui, repo, *args, **opts):
2263 '''set or print guarded patches to push
2263 '''set or print guarded patches to push
2264
2264
2265 Use the qguard command to set or print guards on patch, then use qselect
2265 Use the qguard command to set or print guards on patch, then use qselect
2266 to tell mq which guards to use. A patch will be pushed if it has no guards
2266 to tell mq which guards to use. A patch will be pushed if it has no guards
2267 or any positive guards match the currently selected guard, but will not be
2267 or any positive guards match the currently selected guard, but will not be
2268 pushed if any negative guards match the current guard. For example:
2268 pushed if any negative guards match the current guard. For example:
2269
2269
2270 qguard foo.patch -stable (negative guard)
2270 qguard foo.patch -stable (negative guard)
2271 qguard bar.patch +stable (positive guard)
2271 qguard bar.patch +stable (positive guard)
2272 qselect stable
2272 qselect stable
2273
2273
2274 This activates the "stable" guard. mq will skip foo.patch (because it has
2274 This activates the "stable" guard. mq will skip foo.patch (because it has
2275 a negative match) but push bar.patch (because it has a positive match).
2275 a negative match) but push bar.patch (because it has a positive match).
2276
2276
2277 With no arguments, prints the currently active guards. With one argument,
2277 With no arguments, prints the currently active guards. With one argument,
2278 sets the active guard.
2278 sets the active guard.
2279
2279
2280 Use -n/--none to deactivate guards (no other arguments needed). When no
2280 Use -n/--none to deactivate guards (no other arguments needed). When no
2281 guards are active, patches with positive guards are skipped and patches
2281 guards are active, patches with positive guards are skipped and patches
2282 with negative guards are pushed.
2282 with negative guards are pushed.
2283
2283
2284 qselect can change the guards on applied patches. It does not pop guarded
2284 qselect can change the guards on applied patches. It does not pop guarded
2285 patches by default. Use --pop to pop back to the last applied patch that
2285 patches by default. Use --pop to pop back to the last applied patch that
2286 is not guarded. Use --reapply (which implies --pop) to push back to the
2286 is not guarded. Use --reapply (which implies --pop) to push back to the
2287 current patch afterwards, but skip guarded patches.
2287 current patch afterwards, but skip guarded patches.
2288
2288
2289 Use -s/--series to print a list of all guards in the series file (no other
2289 Use -s/--series to print a list of all guards in the series file (no other
2290 arguments needed). Use -v for more information.
2290 arguments needed). Use -v for more information.
2291 '''
2291 '''
2292
2292
2293 q = repo.mq
2293 q = repo.mq
2294 guards = q.active()
2294 guards = q.active()
2295 if args or opts['none']:
2295 if args or opts['none']:
2296 old_unapplied = q.unapplied(repo)
2296 old_unapplied = q.unapplied(repo)
2297 old_guarded = [i for i in xrange(len(q.applied)) if
2297 old_guarded = [i for i in xrange(len(q.applied)) if
2298 not q.pushable(i)[0]]
2298 not q.pushable(i)[0]]
2299 q.set_active(args)
2299 q.set_active(args)
2300 q.save_dirty()
2300 q.save_dirty()
2301 if not args:
2301 if not args:
2302 ui.status(_('guards deactivated\n'))
2302 ui.status(_('guards deactivated\n'))
2303 if not opts['pop'] and not opts['reapply']:
2303 if not opts['pop'] and not opts['reapply']:
2304 unapplied = q.unapplied(repo)
2304 unapplied = q.unapplied(repo)
2305 guarded = [i for i in xrange(len(q.applied))
2305 guarded = [i for i in xrange(len(q.applied))
2306 if not q.pushable(i)[0]]
2306 if not q.pushable(i)[0]]
2307 if len(unapplied) != len(old_unapplied):
2307 if len(unapplied) != len(old_unapplied):
2308 ui.status(_('number of unguarded, unapplied patches has '
2308 ui.status(_('number of unguarded, unapplied patches has '
2309 'changed from %d to %d\n') %
2309 'changed from %d to %d\n') %
2310 (len(old_unapplied), len(unapplied)))
2310 (len(old_unapplied), len(unapplied)))
2311 if len(guarded) != len(old_guarded):
2311 if len(guarded) != len(old_guarded):
2312 ui.status(_('number of guarded, applied patches has changed '
2312 ui.status(_('number of guarded, applied patches has changed '
2313 'from %d to %d\n') %
2313 'from %d to %d\n') %
2314 (len(old_guarded), len(guarded)))
2314 (len(old_guarded), len(guarded)))
2315 elif opts['series']:
2315 elif opts['series']:
2316 guards = {}
2316 guards = {}
2317 noguards = 0
2317 noguards = 0
2318 for gs in q.series_guards:
2318 for gs in q.series_guards:
2319 if not gs:
2319 if not gs:
2320 noguards += 1
2320 noguards += 1
2321 for g in gs:
2321 for g in gs:
2322 guards.setdefault(g, 0)
2322 guards.setdefault(g, 0)
2323 guards[g] += 1
2323 guards[g] += 1
2324 if ui.verbose:
2324 if ui.verbose:
2325 guards['NONE'] = noguards
2325 guards['NONE'] = noguards
2326 guards = guards.items()
2326 guards = guards.items()
2327 guards.sort(key=lambda x: x[0][1:])
2327 guards.sort(key=lambda x: x[0][1:])
2328 if guards:
2328 if guards:
2329 ui.note(_('guards in series file:\n'))
2329 ui.note(_('guards in series file:\n'))
2330 for guard, count in guards:
2330 for guard, count in guards:
2331 ui.note('%2d ' % count)
2331 ui.note('%2d ' % count)
2332 ui.write(guard, '\n')
2332 ui.write(guard, '\n')
2333 else:
2333 else:
2334 ui.note(_('no guards in series file\n'))
2334 ui.note(_('no guards in series file\n'))
2335 else:
2335 else:
2336 if guards:
2336 if guards:
2337 ui.note(_('active guards:\n'))
2337 ui.note(_('active guards:\n'))
2338 for g in guards:
2338 for g in guards:
2339 ui.write(g, '\n')
2339 ui.write(g, '\n')
2340 else:
2340 else:
2341 ui.write(_('no active guards\n'))
2341 ui.write(_('no active guards\n'))
2342 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2342 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2343 popped = False
2343 popped = False
2344 if opts['pop'] or opts['reapply']:
2344 if opts['pop'] or opts['reapply']:
2345 for i in xrange(len(q.applied)):
2345 for i in xrange(len(q.applied)):
2346 pushable, reason = q.pushable(i)
2346 pushable, reason = q.pushable(i)
2347 if not pushable:
2347 if not pushable:
2348 ui.status(_('popping guarded patches\n'))
2348 ui.status(_('popping guarded patches\n'))
2349 popped = True
2349 popped = True
2350 if i == 0:
2350 if i == 0:
2351 q.pop(repo, all=True)
2351 q.pop(repo, all=True)
2352 else:
2352 else:
2353 q.pop(repo, i-1)
2353 q.pop(repo, i-1)
2354 break
2354 break
2355 if popped:
2355 if popped:
2356 try:
2356 try:
2357 if reapply:
2357 if reapply:
2358 ui.status(_('reapplying unguarded patches\n'))
2358 ui.status(_('reapplying unguarded patches\n'))
2359 q.push(repo, reapply)
2359 q.push(repo, reapply)
2360 finally:
2360 finally:
2361 q.save_dirty()
2361 q.save_dirty()
2362
2362
2363 def finish(ui, repo, *revrange, **opts):
2363 def finish(ui, repo, *revrange, **opts):
2364 """move applied patches into repository history
2364 """move applied patches into repository history
2365
2365
2366 Finishes the specified revisions (corresponding to applied patches) by
2366 Finishes the specified revisions (corresponding to applied patches) by
2367 moving them out of mq control into regular repository history.
2367 moving them out of mq control into regular repository history.
2368
2368
2369 Accepts a revision range or the -a/--applied option. If --applied is
2369 Accepts a revision range or the -a/--applied option. If --applied is
2370 specified, all applied mq revisions are removed from mq control.
2370 specified, all applied mq revisions are removed from mq control.
2371 Otherwise, the given revisions must be at the base of the stack of applied
2371 Otherwise, the given revisions must be at the base of the stack of applied
2372 patches.
2372 patches.
2373
2373
2374 This can be especially useful if your changes have been applied to an
2374 This can be especially useful if your changes have been applied to an
2375 upstream repository, or if you are about to push your changes to upstream.
2375 upstream repository, or if you are about to push your changes to upstream.
2376 """
2376 """
2377 if not opts['applied'] and not revrange:
2377 if not opts['applied'] and not revrange:
2378 raise util.Abort(_('no revisions specified'))
2378 raise util.Abort(_('no revisions specified'))
2379 elif opts['applied']:
2379 elif opts['applied']:
2380 revrange = ('qbase:qtip',) + revrange
2380 revrange = ('qbase:qtip',) + revrange
2381
2381
2382 q = repo.mq
2382 q = repo.mq
2383 if not q.applied:
2383 if not q.applied:
2384 ui.status(_('no patches applied\n'))
2384 ui.status(_('no patches applied\n'))
2385 return 0
2385 return 0
2386
2386
2387 revs = cmdutil.revrange(repo, revrange)
2387 revs = cmdutil.revrange(repo, revrange)
2388 q.finish(repo, revs)
2388 q.finish(repo, revs)
2389 q.save_dirty()
2389 q.save_dirty()
2390 return 0
2390 return 0
2391
2391
2392 def reposetup(ui, repo):
2392 def reposetup(ui, repo):
2393 class mqrepo(repo.__class__):
2393 class mqrepo(repo.__class__):
2394 @util.propertycache
2394 @util.propertycache
2395 def mq(self):
2395 def mq(self):
2396 return queue(self.ui, self.join(""))
2396 return queue(self.ui, self.join(""))
2397
2397
2398 def abort_if_wdir_patched(self, errmsg, force=False):
2398 def abort_if_wdir_patched(self, errmsg, force=False):
2399 if self.mq.applied and not force:
2399 if self.mq.applied and not force:
2400 parent = hex(self.dirstate.parents()[0])
2400 parent = hex(self.dirstate.parents()[0])
2401 if parent in [s.rev for s in self.mq.applied]:
2401 if parent in [s.rev for s in self.mq.applied]:
2402 raise util.Abort(errmsg)
2402 raise util.Abort(errmsg)
2403
2403
2404 def commit(self, text="", user=None, date=None, match=None,
2404 def commit(self, text="", user=None, date=None, match=None,
2405 force=False, editor=False, extra={}):
2405 force=False, editor=False, extra={}):
2406 self.abort_if_wdir_patched(
2406 self.abort_if_wdir_patched(
2407 _('cannot commit over an applied mq patch'),
2407 _('cannot commit over an applied mq patch'),
2408 force)
2408 force)
2409
2409
2410 return super(mqrepo, self).commit(text, user, date, match, force,
2410 return super(mqrepo, self).commit(text, user, date, match, force,
2411 editor, extra)
2411 editor, extra)
2412
2412
2413 def push(self, remote, force=False, revs=None):
2413 def push(self, remote, force=False, revs=None):
2414 if self.mq.applied and not force and not revs:
2414 if self.mq.applied and not force and not revs:
2415 raise util.Abort(_('source has mq patches applied'))
2415 raise util.Abort(_('source has mq patches applied'))
2416 return super(mqrepo, self).push(remote, force, revs)
2416 return super(mqrepo, self).push(remote, force, revs)
2417
2417
2418 def tags(self):
2418 def tags(self):
2419 if self.tagscache:
2419 if self.tagscache:
2420 return self.tagscache
2420 return self.tagscache
2421
2421
2422 tagscache = super(mqrepo, self).tags()
2422 tagscache = super(mqrepo, self).tags()
2423
2423
2424 q = self.mq
2424 q = self.mq
2425 if not q.applied:
2425 if not q.applied:
2426 return tagscache
2426 return tagscache
2427
2427
2428 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2428 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2429
2429
2430 if mqtags[-1][0] not in self.changelog.nodemap:
2430 if mqtags[-1][0] not in self.changelog.nodemap:
2431 self.ui.warn(_('mq status file refers to unknown node %s\n')
2431 self.ui.warn(_('mq status file refers to unknown node %s\n')
2432 % short(mqtags[-1][0]))
2432 % short(mqtags[-1][0]))
2433 return tagscache
2433 return tagscache
2434
2434
2435 mqtags.append((mqtags[-1][0], 'qtip'))
2435 mqtags.append((mqtags[-1][0], 'qtip'))
2436 mqtags.append((mqtags[0][0], 'qbase'))
2436 mqtags.append((mqtags[0][0], 'qbase'))
2437 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2437 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2438 for patch in mqtags:
2438 for patch in mqtags:
2439 if patch[1] in tagscache:
2439 if patch[1] in tagscache:
2440 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2440 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2441 % patch[1])
2441 % patch[1])
2442 else:
2442 else:
2443 tagscache[patch[1]] = patch[0]
2443 tagscache[patch[1]] = patch[0]
2444
2444
2445 return tagscache
2445 return tagscache
2446
2446
2447 def _branchtags(self, partial, lrev):
2447 def _branchtags(self, partial, lrev):
2448 q = self.mq
2448 q = self.mq
2449 if not q.applied:
2449 if not q.applied:
2450 return super(mqrepo, self)._branchtags(partial, lrev)
2450 return super(mqrepo, self)._branchtags(partial, lrev)
2451
2451
2452 cl = self.changelog
2452 cl = self.changelog
2453 qbasenode = bin(q.applied[0].rev)
2453 qbasenode = bin(q.applied[0].rev)
2454 if qbasenode not in cl.nodemap:
2454 if qbasenode not in cl.nodemap:
2455 self.ui.warn(_('mq status file refers to unknown node %s\n')
2455 self.ui.warn(_('mq status file refers to unknown node %s\n')
2456 % short(qbasenode))
2456 % short(qbasenode))
2457 return super(mqrepo, self)._branchtags(partial, lrev)
2457 return super(mqrepo, self)._branchtags(partial, lrev)
2458
2458
2459 qbase = cl.rev(qbasenode)
2459 qbase = cl.rev(qbasenode)
2460 start = lrev + 1
2460 start = lrev + 1
2461 if start < qbase:
2461 if start < qbase:
2462 # update the cache (excluding the patches) and save it
2462 # update the cache (excluding the patches) and save it
2463 self._updatebranchcache(partial, lrev+1, qbase)
2463 self._updatebranchcache(partial, lrev+1, qbase)
2464 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2464 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2465 start = qbase
2465 start = qbase
2466 # if start = qbase, the cache is as updated as it should be.
2466 # if start = qbase, the cache is as updated as it should be.
2467 # if start > qbase, the cache includes (part of) the patches.
2467 # if start > qbase, the cache includes (part of) the patches.
2468 # we might as well use it, but we won't save it.
2468 # we might as well use it, but we won't save it.
2469
2469
2470 # update the cache up to the tip
2470 # update the cache up to the tip
2471 self._updatebranchcache(partial, start, len(cl))
2471 self._updatebranchcache(partial, start, len(cl))
2472
2472
2473 return partial
2473 return partial
2474
2474
2475 if repo.local():
2475 if repo.local():
2476 repo.__class__ = mqrepo
2476 repo.__class__ = mqrepo
2477
2477
2478 def mqimport(orig, ui, repo, *args, **kwargs):
2478 def mqimport(orig, ui, repo, *args, **kwargs):
2479 if hasattr(repo, 'abort_if_wdir_patched'):
2479 if hasattr(repo, 'abort_if_wdir_patched'):
2480 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2480 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2481 kwargs.get('force'))
2481 kwargs.get('force'))
2482 return orig(ui, repo, *args, **kwargs)
2482 return orig(ui, repo, *args, **kwargs)
2483
2483
2484 def uisetup(ui):
2484 def uisetup(ui):
2485 extensions.wrapcommand(commands.table, 'import', mqimport)
2485 extensions.wrapcommand(commands.table, 'import', mqimport)
2486
2486
2487 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2487 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2488
2488
2489 cmdtable = {
2489 cmdtable = {
2490 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2490 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2491 "qclone":
2491 "qclone":
2492 (clone,
2492 (clone,
2493 [('', 'pull', None, _('use pull protocol to copy metadata')),
2493 [('', 'pull', None, _('use pull protocol to copy metadata')),
2494 ('U', 'noupdate', None, _('do not update the new working directories')),
2494 ('U', 'noupdate', None, _('do not update the new working directories')),
2495 ('', 'uncompressed', None,
2495 ('', 'uncompressed', None,
2496 _('use uncompressed transfer (fast over LAN)')),
2496 _('use uncompressed transfer (fast over LAN)')),
2497 ('p', 'patches', '', _('location of source patch repository')),
2497 ('p', 'patches', '', _('location of source patch repository')),
2498 ] + commands.remoteopts,
2498 ] + commands.remoteopts,
2499 _('hg qclone [OPTION]... SOURCE [DEST]')),
2499 _('hg qclone [OPTION]... SOURCE [DEST]')),
2500 "qcommit|qci":
2500 "qcommit|qci":
2501 (commit,
2501 (commit,
2502 commands.table["^commit|ci"][1],
2502 commands.table["^commit|ci"][1],
2503 _('hg qcommit [OPTION]... [FILE]...')),
2503 _('hg qcommit [OPTION]... [FILE]...')),
2504 "^qdiff":
2504 "^qdiff":
2505 (diff,
2505 (diff,
2506 commands.diffopts + commands.diffopts2 + commands.walkopts,
2506 commands.diffopts + commands.diffopts2 + commands.walkopts,
2507 _('hg qdiff [OPTION]... [FILE]...')),
2507 _('hg qdiff [OPTION]... [FILE]...')),
2508 "qdelete|qremove|qrm":
2508 "qdelete|qremove|qrm":
2509 (delete,
2509 (delete,
2510 [('k', 'keep', None, _('keep patch file')),
2510 [('k', 'keep', None, _('keep patch file')),
2511 ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))],
2511 ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))],
2512 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2512 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2513 'qfold':
2513 'qfold':
2514 (fold,
2514 (fold,
2515 [('e', 'edit', None, _('edit patch header')),
2515 [('e', 'edit', None, _('edit patch header')),
2516 ('k', 'keep', None, _('keep folded patch files')),
2516 ('k', 'keep', None, _('keep folded patch files')),
2517 ] + commands.commitopts,
2517 ] + commands.commitopts,
2518 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2518 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2519 'qgoto':
2519 'qgoto':
2520 (goto,
2520 (goto,
2521 [('f', 'force', None, _('overwrite any local changes'))],
2521 [('f', 'force', None, _('overwrite any local changes'))],
2522 _('hg qgoto [OPTION]... PATCH')),
2522 _('hg qgoto [OPTION]... PATCH')),
2523 'qguard':
2523 'qguard':
2524 (guard,
2524 (guard,
2525 [('l', 'list', None, _('list all patches and guards')),
2525 [('l', 'list', None, _('list all patches and guards')),
2526 ('n', 'none', None, _('drop all guards'))],
2526 ('n', 'none', None, _('drop all guards'))],
2527 _('hg qguard [-l] [-n] -- [PATCH] [+GUARD]... [-GUARD]...')),
2527 _('hg qguard [-l] [-n] -- [PATCH] [+GUARD]... [-GUARD]...')),
2528 'qheader': (header, [], _('hg qheader [PATCH]')),
2528 'qheader': (header, [], _('hg qheader [PATCH]')),
2529 "^qimport":
2529 "^qimport":
2530 (qimport,
2530 (qimport,
2531 [('e', 'existing', None, _('import file in patch directory')),
2531 [('e', 'existing', None, _('import file in patch directory')),
2532 ('n', 'name', '', _('name of patch file')),
2532 ('n', 'name', '', _('name of patch file')),
2533 ('f', 'force', None, _('overwrite existing files')),
2533 ('f', 'force', None, _('overwrite existing files')),
2534 ('r', 'rev', [], _('place existing revisions under mq control')),
2534 ('r', 'rev', [], _('place existing revisions under mq control')),
2535 ('g', 'git', None, _('use git extended diff format')),
2535 ('g', 'git', None, _('use git extended diff format')),
2536 ('P', 'push', None, _('qpush after importing'))],
2536 ('P', 'push', None, _('qpush after importing'))],
2537 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2537 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2538 "^qinit":
2538 "^qinit":
2539 (init,
2539 (init,
2540 [('c', 'create-repo', None, _('create queue repository'))],
2540 [('c', 'create-repo', None, _('create queue repository'))],
2541 _('hg qinit [-c]')),
2541 _('hg qinit [-c]')),
2542 "qnew":
2542 "qnew":
2543 (new,
2543 (new,
2544 [('e', 'edit', None, _('edit commit message')),
2544 [('e', 'edit', None, _('edit commit message')),
2545 ('f', 'force', None, _('import uncommitted changes into patch')),
2545 ('f', 'force', None, _('import uncommitted changes into patch')),
2546 ('g', 'git', None, _('use git extended diff format')),
2546 ('g', 'git', None, _('use git extended diff format')),
2547 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2547 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2548 ('u', 'user', '', _('add "From: <given user>" to patch')),
2548 ('u', 'user', '', _('add "From: <given user>" to patch')),
2549 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2549 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2550 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2550 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2551 ] + commands.walkopts + commands.commitopts,
2551 ] + commands.walkopts + commands.commitopts,
2552 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2552 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2553 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2553 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2554 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2554 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2555 "^qpop":
2555 "^qpop":
2556 (pop,
2556 (pop,
2557 [('a', 'all', None, _('pop all patches')),
2557 [('a', 'all', None, _('pop all patches')),
2558 ('n', 'name', '', _('queue name to pop')),
2558 ('n', 'name', '', _('queue name to pop')),
2559 ('f', 'force', None, _('forget any local changes'))],
2559 ('f', 'force', None, _('forget any local changes'))],
2560 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2560 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2561 "^qpush":
2561 "^qpush":
2562 (push,
2562 (push,
2563 [('f', 'force', None, _('apply if the patch has rejects')),
2563 [('f', 'force', None, _('apply if the patch has rejects')),
2564 ('l', 'list', None, _('list patch name in commit text')),
2564 ('l', 'list', None, _('list patch name in commit text')),
2565 ('a', 'all', None, _('apply all patches')),
2565 ('a', 'all', None, _('apply all patches')),
2566 ('m', 'merge', None, _('merge from another queue')),
2566 ('m', 'merge', None, _('merge from another queue')),
2567 ('n', 'name', '', _('merge queue name'))],
2567 ('n', 'name', '', _('merge queue name'))],
2568 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2568 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2569 "^qrefresh":
2569 "^qrefresh":
2570 (refresh,
2570 (refresh,
2571 [('e', 'edit', None, _('edit commit message')),
2571 [('e', 'edit', None, _('edit commit message')),
2572 ('g', 'git', None, _('use git extended diff format')),
2572 ('g', 'git', None, _('use git extended diff format')),
2573 ('s', 'short', None, _('refresh only files already in the patch and specified files')),
2573 ('s', 'short', None, _('refresh only files already in the patch and specified files')),
2574 ('U', 'currentuser', None, _('add/update "From: <current user>" in patch')),
2574 ('U', 'currentuser', None, _('add/update "From: <current user>" in patch')),
2575 ('u', 'user', '', _('add/update "From: <given user>" in patch')),
2575 ('u', 'user', '', _('add/update "From: <given user>" in patch')),
2576 ('D', 'currentdate', None, _('update "Date: <current date>" in patch (if present)')),
2576 ('D', 'currentdate', None, _('update "Date: <current date>" in patch (if present)')),
2577 ('d', 'date', '', _('update "Date: <given date>" in patch (if present)'))
2577 ('d', 'date', '', _('update "Date: <given date>" in patch (if present)'))
2578 ] + commands.walkopts + commands.commitopts,
2578 ] + commands.walkopts + commands.commitopts,
2579 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2579 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2580 'qrename|qmv':
2580 'qrename|qmv':
2581 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2581 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2582 "qrestore":
2582 "qrestore":
2583 (restore,
2583 (restore,
2584 [('d', 'delete', None, _('delete save entry')),
2584 [('d', 'delete', None, _('delete save entry')),
2585 ('u', 'update', None, _('update queue working directory'))],
2585 ('u', 'update', None, _('update queue working directory'))],
2586 _('hg qrestore [-d] [-u] REV')),
2586 _('hg qrestore [-d] [-u] REV')),
2587 "qsave":
2587 "qsave":
2588 (save,
2588 (save,
2589 [('c', 'copy', None, _('copy patch directory')),
2589 [('c', 'copy', None, _('copy patch directory')),
2590 ('n', 'name', '', _('copy directory name')),
2590 ('n', 'name', '', _('copy directory name')),
2591 ('e', 'empty', None, _('clear queue status file')),
2591 ('e', 'empty', None, _('clear queue status file')),
2592 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2592 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2593 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2593 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2594 "qselect":
2594 "qselect":
2595 (select,
2595 (select,
2596 [('n', 'none', None, _('disable all guards')),
2596 [('n', 'none', None, _('disable all guards')),
2597 ('s', 'series', None, _('list all guards in series file')),
2597 ('s', 'series', None, _('list all guards in series file')),
2598 ('', 'pop', None, _('pop to before first guarded applied patch')),
2598 ('', 'pop', None, _('pop to before first guarded applied patch')),
2599 ('', 'reapply', None, _('pop, then reapply patches'))],
2599 ('', 'reapply', None, _('pop, then reapply patches'))],
2600 _('hg qselect [OPTION]... [GUARD]...')),
2600 _('hg qselect [OPTION]... [GUARD]...')),
2601 "qseries":
2601 "qseries":
2602 (series,
2602 (series,
2603 [('m', 'missing', None, _('print patches not in series')),
2603 [('m', 'missing', None, _('print patches not in series')),
2604 ] + seriesopts,
2604 ] + seriesopts,
2605 _('hg qseries [-ms]')),
2605 _('hg qseries [-ms]')),
2606 "^strip":
2606 "^strip":
2607 (strip,
2607 (strip,
2608 [('f', 'force', None, _('force removal with local changes')),
2608 [('f', 'force', None, _('force removal with local changes')),
2609 ('b', 'backup', None, _('bundle unrelated changesets')),
2609 ('b', 'backup', None, _('bundle unrelated changesets')),
2610 ('n', 'nobackup', None, _('no backups'))],
2610 ('n', 'nobackup', None, _('no backups'))],
2611 _('hg strip [-f] [-b] [-n] REV')),
2611 _('hg strip [-f] [-b] [-n] REV')),
2612 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2612 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2613 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2613 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2614 "qfinish":
2614 "qfinish":
2615 (finish,
2615 (finish,
2616 [('a', 'applied', None, _('finish all applied changesets'))],
2616 [('a', 'applied', None, _('finish all applied changesets'))],
2617 _('hg qfinish [-a] [REV]...')),
2617 _('hg qfinish [-a] [REV]...')),
2618 }
2618 }
@@ -1,289 +1,291 b''
1 # notify.py - email notifications for mercurial
1 # notify.py - email notifications for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 '''hooks for sending email notifications at commit/push time
8 '''hooks for sending email notifications at commit/push time
9
9
10 Subscriptions can be managed through a hgrc file. Default mode is to print
10 Subscriptions can be managed through a hgrc file. Default mode is to print
11 messages to stdout, for testing and configuring.
11 messages to stdout, for testing and configuring.
12
12
13 To use, configure the notify extension and enable it in hgrc like this:
13 To use, configure the notify extension and enable it in hgrc like this::
14
14
15 [extensions]
15 [extensions]
16 hgext.notify =
16 hgext.notify =
17
17
18 [hooks]
18 [hooks]
19 # one email for each incoming changeset
19 # one email for each incoming changeset
20 incoming.notify = python:hgext.notify.hook
20 incoming.notify = python:hgext.notify.hook
21 # batch emails when many changesets incoming at one time
21 # batch emails when many changesets incoming at one time
22 changegroup.notify = python:hgext.notify.hook
22 changegroup.notify = python:hgext.notify.hook
23
23
24 [notify]
24 [notify]
25 # config items go here
25 # config items go here
26
26
27 Required configuration items:
27 Required configuration items::
28
28
29 config = /path/to/file # file containing subscriptions
29 config = /path/to/file # file containing subscriptions
30
30
31 Optional configuration items:
31 Optional configuration items::
32
32
33 test = True # print messages to stdout for testing
33 test = True # print messages to stdout for testing
34 strip = 3 # number of slashes to strip for url paths
34 strip = 3 # number of slashes to strip for url paths
35 domain = example.com # domain to use if committer missing domain
35 domain = example.com # domain to use if committer missing domain
36 style = ... # style file to use when formatting email
36 style = ... # style file to use when formatting email
37 template = ... # template to use when formatting email
37 template = ... # template to use when formatting email
38 incoming = ... # template to use when run as incoming hook
38 incoming = ... # template to use when run as incoming hook
39 changegroup = ... # template when run as changegroup hook
39 changegroup = ... # template when run as changegroup hook
40 maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
40 maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
41 maxsubject = 67 # truncate subject line longer than this
41 maxsubject = 67 # truncate subject line longer than this
42 diffstat = True # add a diffstat before the diff content
42 diffstat = True # add a diffstat before the diff content
43 sources = serve # notify if source of incoming changes in this list
43 sources = serve # notify if source of incoming changes in this list
44 # (serve == ssh or http, push, pull, bundle)
44 # (serve == ssh or http, push, pull, bundle)
45 [email]
45 [email]
46 from = user@host.com # email address to send as if none given
46 from = user@host.com # email address to send as if none given
47 [web]
47 [web]
48 baseurl = http://hgserver/... # root of hg web site for browsing commits
48 baseurl = http://hgserver/... # root of hg web site for browsing commits
49
49
50 The notify config file has same format as a regular hgrc file. It has two
50 The notify config file has same format as a regular hgrc file. It has two
51 sections so you can express subscriptions in whatever way is handier for you.
51 sections so you can express subscriptions in whatever way is handier for you.
52
52
53 ::
54
53 [usersubs]
55 [usersubs]
54 # key is subscriber email, value is ","-separated list of glob patterns
56 # key is subscriber email, value is ","-separated list of glob patterns
55 user@host = pattern
57 user@host = pattern
56
58
57 [reposubs]
59 [reposubs]
58 # key is glob pattern, value is ","-separated list of subscriber emails
60 # key is glob pattern, value is ","-separated list of subscriber emails
59 pattern = user@host
61 pattern = user@host
60
62
61 Glob patterns are matched against path to repository root.
63 Glob patterns are matched against path to repository root.
62
64
63 If you like, you can put notify config file in repository that users can push
65 If you like, you can put notify config file in repository that users can push
64 changes to, they can manage their own subscriptions.
66 changes to, they can manage their own subscriptions.
65 '''
67 '''
66
68
67 from mercurial.i18n import _
69 from mercurial.i18n import _
68 from mercurial import patch, cmdutil, templater, util, mail
70 from mercurial import patch, cmdutil, templater, util, mail
69 import email.Parser, fnmatch, socket, time
71 import email.Parser, fnmatch, socket, time
70
72
71 # template for single changeset can include email headers.
73 # template for single changeset can include email headers.
72 single_template = '''
74 single_template = '''
73 Subject: changeset in {webroot}: {desc|firstline|strip}
75 Subject: changeset in {webroot}: {desc|firstline|strip}
74 From: {author}
76 From: {author}
75
77
76 changeset {node|short} in {root}
78 changeset {node|short} in {root}
77 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
79 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
78 description:
80 description:
79 \t{desc|tabindent|strip}
81 \t{desc|tabindent|strip}
80 '''.lstrip()
82 '''.lstrip()
81
83
82 # template for multiple changesets should not contain email headers,
84 # template for multiple changesets should not contain email headers,
83 # because only first set of headers will be used and result will look
85 # because only first set of headers will be used and result will look
84 # strange.
86 # strange.
85 multiple_template = '''
87 multiple_template = '''
86 changeset {node|short} in {root}
88 changeset {node|short} in {root}
87 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
89 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
88 summary: {desc|firstline}
90 summary: {desc|firstline}
89 '''
91 '''
90
92
91 deftemplates = {
93 deftemplates = {
92 'changegroup': multiple_template,
94 'changegroup': multiple_template,
93 }
95 }
94
96
95 class notifier(object):
97 class notifier(object):
96 '''email notification class.'''
98 '''email notification class.'''
97
99
98 def __init__(self, ui, repo, hooktype):
100 def __init__(self, ui, repo, hooktype):
99 self.ui = ui
101 self.ui = ui
100 cfg = self.ui.config('notify', 'config')
102 cfg = self.ui.config('notify', 'config')
101 if cfg:
103 if cfg:
102 self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
104 self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
103 self.repo = repo
105 self.repo = repo
104 self.stripcount = int(self.ui.config('notify', 'strip', 0))
106 self.stripcount = int(self.ui.config('notify', 'strip', 0))
105 self.root = self.strip(self.repo.root)
107 self.root = self.strip(self.repo.root)
106 self.domain = self.ui.config('notify', 'domain')
108 self.domain = self.ui.config('notify', 'domain')
107 self.test = self.ui.configbool('notify', 'test', True)
109 self.test = self.ui.configbool('notify', 'test', True)
108 self.charsets = mail._charsets(self.ui)
110 self.charsets = mail._charsets(self.ui)
109 self.subs = self.subscribers()
111 self.subs = self.subscribers()
110
112
111 mapfile = self.ui.config('notify', 'style')
113 mapfile = self.ui.config('notify', 'style')
112 template = (self.ui.config('notify', hooktype) or
114 template = (self.ui.config('notify', hooktype) or
113 self.ui.config('notify', 'template'))
115 self.ui.config('notify', 'template'))
114 self.t = cmdutil.changeset_templater(self.ui, self.repo,
116 self.t = cmdutil.changeset_templater(self.ui, self.repo,
115 False, None, mapfile, False)
117 False, None, mapfile, False)
116 if not mapfile and not template:
118 if not mapfile and not template:
117 template = deftemplates.get(hooktype) or single_template
119 template = deftemplates.get(hooktype) or single_template
118 if template:
120 if template:
119 template = templater.parsestring(template, quoted=False)
121 template = templater.parsestring(template, quoted=False)
120 self.t.use_template(template)
122 self.t.use_template(template)
121
123
122 def strip(self, path):
124 def strip(self, path):
123 '''strip leading slashes from local path, turn into web-safe path.'''
125 '''strip leading slashes from local path, turn into web-safe path.'''
124
126
125 path = util.pconvert(path)
127 path = util.pconvert(path)
126 count = self.stripcount
128 count = self.stripcount
127 while count > 0:
129 while count > 0:
128 c = path.find('/')
130 c = path.find('/')
129 if c == -1:
131 if c == -1:
130 break
132 break
131 path = path[c+1:]
133 path = path[c+1:]
132 count -= 1
134 count -= 1
133 return path
135 return path
134
136
135 def fixmail(self, addr):
137 def fixmail(self, addr):
136 '''try to clean up email addresses.'''
138 '''try to clean up email addresses.'''
137
139
138 addr = util.email(addr.strip())
140 addr = util.email(addr.strip())
139 if self.domain:
141 if self.domain:
140 a = addr.find('@localhost')
142 a = addr.find('@localhost')
141 if a != -1:
143 if a != -1:
142 addr = addr[:a]
144 addr = addr[:a]
143 if '@' not in addr:
145 if '@' not in addr:
144 return addr + '@' + self.domain
146 return addr + '@' + self.domain
145 return addr
147 return addr
146
148
147 def subscribers(self):
149 def subscribers(self):
148 '''return list of email addresses of subscribers to this repo.'''
150 '''return list of email addresses of subscribers to this repo.'''
149 subs = set()
151 subs = set()
150 for user, pats in self.ui.configitems('usersubs'):
152 for user, pats in self.ui.configitems('usersubs'):
151 for pat in pats.split(','):
153 for pat in pats.split(','):
152 if fnmatch.fnmatch(self.repo.root, pat.strip()):
154 if fnmatch.fnmatch(self.repo.root, pat.strip()):
153 subs.add(self.fixmail(user))
155 subs.add(self.fixmail(user))
154 for pat, users in self.ui.configitems('reposubs'):
156 for pat, users in self.ui.configitems('reposubs'):
155 if fnmatch.fnmatch(self.repo.root, pat):
157 if fnmatch.fnmatch(self.repo.root, pat):
156 for user in users.split(','):
158 for user in users.split(','):
157 subs.add(self.fixmail(user))
159 subs.add(self.fixmail(user))
158 return [mail.addressencode(self.ui, s, self.charsets, self.test)
160 return [mail.addressencode(self.ui, s, self.charsets, self.test)
159 for s in sorted(subs)]
161 for s in sorted(subs)]
160
162
161 def url(self, path=None):
163 def url(self, path=None):
162 return self.ui.config('web', 'baseurl') + (path or self.root)
164 return self.ui.config('web', 'baseurl') + (path or self.root)
163
165
164 def node(self, ctx):
166 def node(self, ctx):
165 '''format one changeset.'''
167 '''format one changeset.'''
166 self.t.show(ctx, changes=ctx.changeset(),
168 self.t.show(ctx, changes=ctx.changeset(),
167 baseurl=self.ui.config('web', 'baseurl'),
169 baseurl=self.ui.config('web', 'baseurl'),
168 root=self.repo.root, webroot=self.root)
170 root=self.repo.root, webroot=self.root)
169
171
170 def skipsource(self, source):
172 def skipsource(self, source):
171 '''true if incoming changes from this source should be skipped.'''
173 '''true if incoming changes from this source should be skipped.'''
172 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
174 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
173 return source not in ok_sources
175 return source not in ok_sources
174
176
175 def send(self, ctx, count, data):
177 def send(self, ctx, count, data):
176 '''send message.'''
178 '''send message.'''
177
179
178 p = email.Parser.Parser()
180 p = email.Parser.Parser()
179 msg = p.parsestr(data)
181 msg = p.parsestr(data)
180
182
181 # store sender and subject
183 # store sender and subject
182 sender, subject = msg['From'], msg['Subject']
184 sender, subject = msg['From'], msg['Subject']
183 del msg['From'], msg['Subject']
185 del msg['From'], msg['Subject']
184 # store remaining headers
186 # store remaining headers
185 headers = msg.items()
187 headers = msg.items()
186 # create fresh mime message from msg body
188 # create fresh mime message from msg body
187 text = msg.get_payload()
189 text = msg.get_payload()
188 # for notification prefer readability over data precision
190 # for notification prefer readability over data precision
189 msg = mail.mimeencode(self.ui, text, self.charsets, self.test)
191 msg = mail.mimeencode(self.ui, text, self.charsets, self.test)
190 # reinstate custom headers
192 # reinstate custom headers
191 for k, v in headers:
193 for k, v in headers:
192 msg[k] = v
194 msg[k] = v
193
195
194 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
196 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
195
197
196 # try to make subject line exist and be useful
198 # try to make subject line exist and be useful
197 if not subject:
199 if not subject:
198 if count > 1:
200 if count > 1:
199 subject = _('%s: %d new changesets') % (self.root, count)
201 subject = _('%s: %d new changesets') % (self.root, count)
200 else:
202 else:
201 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
203 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
202 subject = '%s: %s' % (self.root, s)
204 subject = '%s: %s' % (self.root, s)
203 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
205 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
204 if maxsubject and len(subject) > maxsubject:
206 if maxsubject and len(subject) > maxsubject:
205 subject = subject[:maxsubject-3] + '...'
207 subject = subject[:maxsubject-3] + '...'
206 msg['Subject'] = mail.headencode(self.ui, subject,
208 msg['Subject'] = mail.headencode(self.ui, subject,
207 self.charsets, self.test)
209 self.charsets, self.test)
208
210
209 # try to make message have proper sender
211 # try to make message have proper sender
210 if not sender:
212 if not sender:
211 sender = self.ui.config('email', 'from') or self.ui.username()
213 sender = self.ui.config('email', 'from') or self.ui.username()
212 if '@' not in sender or '@localhost' in sender:
214 if '@' not in sender or '@localhost' in sender:
213 sender = self.fixmail(sender)
215 sender = self.fixmail(sender)
214 msg['From'] = mail.addressencode(self.ui, sender,
216 msg['From'] = mail.addressencode(self.ui, sender,
215 self.charsets, self.test)
217 self.charsets, self.test)
216
218
217 msg['X-Hg-Notification'] = 'changeset %s' % ctx
219 msg['X-Hg-Notification'] = 'changeset %s' % ctx
218 if not msg['Message-Id']:
220 if not msg['Message-Id']:
219 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
221 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
220 (ctx, int(time.time()),
222 (ctx, int(time.time()),
221 hash(self.repo.root), socket.getfqdn()))
223 hash(self.repo.root), socket.getfqdn()))
222 msg['To'] = ', '.join(self.subs)
224 msg['To'] = ', '.join(self.subs)
223
225
224 msgtext = msg.as_string()
226 msgtext = msg.as_string()
225 if self.test:
227 if self.test:
226 self.ui.write(msgtext)
228 self.ui.write(msgtext)
227 if not msgtext.endswith('\n'):
229 if not msgtext.endswith('\n'):
228 self.ui.write('\n')
230 self.ui.write('\n')
229 else:
231 else:
230 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
232 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
231 (len(self.subs), count))
233 (len(self.subs), count))
232 mail.sendmail(self.ui, util.email(msg['From']),
234 mail.sendmail(self.ui, util.email(msg['From']),
233 self.subs, msgtext)
235 self.subs, msgtext)
234
236
235 def diff(self, ctx, ref=None):
237 def diff(self, ctx, ref=None):
236
238
237 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
239 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
238 prev = ctx.parents()[0].node()
240 prev = ctx.parents()[0].node()
239 ref = ref and ref.node() or ctx.node()
241 ref = ref and ref.node() or ctx.node()
240 chunks = patch.diff(self.repo, prev, ref, opts=patch.diffopts(self.ui))
242 chunks = patch.diff(self.repo, prev, ref, opts=patch.diffopts(self.ui))
241 difflines = ''.join(chunks).splitlines()
243 difflines = ''.join(chunks).splitlines()
242
244
243 if self.ui.configbool('notify', 'diffstat', True):
245 if self.ui.configbool('notify', 'diffstat', True):
244 s = patch.diffstat(difflines)
246 s = patch.diffstat(difflines)
245 # s may be nil, don't include the header if it is
247 # s may be nil, don't include the header if it is
246 if s:
248 if s:
247 self.ui.write('\ndiffstat:\n\n%s' % s)
249 self.ui.write('\ndiffstat:\n\n%s' % s)
248
250
249 if maxdiff == 0:
251 if maxdiff == 0:
250 return
252 return
251 elif maxdiff > 0 and len(difflines) > maxdiff:
253 elif maxdiff > 0 and len(difflines) > maxdiff:
252 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
254 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
253 self.ui.write(msg % (len(difflines), maxdiff))
255 self.ui.write(msg % (len(difflines), maxdiff))
254 difflines = difflines[:maxdiff]
256 difflines = difflines[:maxdiff]
255 elif difflines:
257 elif difflines:
256 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
258 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
257
259
258 self.ui.write("\n".join(difflines))
260 self.ui.write("\n".join(difflines))
259
261
260 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
262 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
261 '''send email notifications to interested subscribers.
263 '''send email notifications to interested subscribers.
262
264
263 if used as changegroup hook, send one email for all changesets in
265 if used as changegroup hook, send one email for all changesets in
264 changegroup. else send one email per changeset.'''
266 changegroup. else send one email per changeset.'''
265
267
266 n = notifier(ui, repo, hooktype)
268 n = notifier(ui, repo, hooktype)
267 ctx = repo[node]
269 ctx = repo[node]
268
270
269 if not n.subs:
271 if not n.subs:
270 ui.debug(_('notify: no subscribers to repository %s\n') % n.root)
272 ui.debug(_('notify: no subscribers to repository %s\n') % n.root)
271 return
273 return
272 if n.skipsource(source):
274 if n.skipsource(source):
273 ui.debug(_('notify: changes have source "%s" - skipping\n') % source)
275 ui.debug(_('notify: changes have source "%s" - skipping\n') % source)
274 return
276 return
275
277
276 ui.pushbuffer()
278 ui.pushbuffer()
277 if hooktype == 'changegroup':
279 if hooktype == 'changegroup':
278 start, end = ctx.rev(), len(repo)
280 start, end = ctx.rev(), len(repo)
279 count = end - start
281 count = end - start
280 for rev in xrange(start, end):
282 for rev in xrange(start, end):
281 n.node(repo[rev])
283 n.node(repo[rev])
282 n.diff(ctx, repo['tip'])
284 n.diff(ctx, repo['tip'])
283 else:
285 else:
284 count = 1
286 count = 1
285 n.node(ctx)
287 n.node(ctx)
286 n.diff(ctx)
288 n.diff(ctx)
287
289
288 data = ui.popbuffer()
290 data = ui.popbuffer()
289 n.send(ctx, count, data)
291 n.send(ctx, count, data)
@@ -1,549 +1,549 b''
1 # record.py
1 # record.py
2 #
2 #
3 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
3 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 '''commands to interactively select changes for commit/qrefresh'''
8 '''commands to interactively select changes for commit/qrefresh'''
9
9
10 from mercurial.i18n import gettext, _
10 from mercurial.i18n import gettext, _
11 from mercurial import cmdutil, commands, extensions, hg, mdiff, patch
11 from mercurial import cmdutil, commands, extensions, hg, mdiff, patch
12 from mercurial import util
12 from mercurial import util
13 import copy, cStringIO, errno, operator, os, re, tempfile
13 import copy, cStringIO, errno, operator, os, re, tempfile
14
14
15 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
15 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
16
16
17 def scanpatch(fp):
17 def scanpatch(fp):
18 """like patch.iterhunks, but yield different events
18 """like patch.iterhunks, but yield different events
19
19
20 - ('file', [header_lines + fromfile + tofile])
20 - ('file', [header_lines + fromfile + tofile])
21 - ('context', [context_lines])
21 - ('context', [context_lines])
22 - ('hunk', [hunk_lines])
22 - ('hunk', [hunk_lines])
23 - ('range', (-start,len, +start,len, diffp))
23 - ('range', (-start,len, +start,len, diffp))
24 """
24 """
25 lr = patch.linereader(fp)
25 lr = patch.linereader(fp)
26
26
27 def scanwhile(first, p):
27 def scanwhile(first, p):
28 """scan lr while predicate holds"""
28 """scan lr while predicate holds"""
29 lines = [first]
29 lines = [first]
30 while True:
30 while True:
31 line = lr.readline()
31 line = lr.readline()
32 if not line:
32 if not line:
33 break
33 break
34 if p(line):
34 if p(line):
35 lines.append(line)
35 lines.append(line)
36 else:
36 else:
37 lr.push(line)
37 lr.push(line)
38 break
38 break
39 return lines
39 return lines
40
40
41 while True:
41 while True:
42 line = lr.readline()
42 line = lr.readline()
43 if not line:
43 if not line:
44 break
44 break
45 if line.startswith('diff --git a/'):
45 if line.startswith('diff --git a/'):
46 def notheader(line):
46 def notheader(line):
47 s = line.split(None, 1)
47 s = line.split(None, 1)
48 return not s or s[0] not in ('---', 'diff')
48 return not s or s[0] not in ('---', 'diff')
49 header = scanwhile(line, notheader)
49 header = scanwhile(line, notheader)
50 fromfile = lr.readline()
50 fromfile = lr.readline()
51 if fromfile.startswith('---'):
51 if fromfile.startswith('---'):
52 tofile = lr.readline()
52 tofile = lr.readline()
53 header += [fromfile, tofile]
53 header += [fromfile, tofile]
54 else:
54 else:
55 lr.push(fromfile)
55 lr.push(fromfile)
56 yield 'file', header
56 yield 'file', header
57 elif line[0] == ' ':
57 elif line[0] == ' ':
58 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
58 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
59 elif line[0] in '-+':
59 elif line[0] in '-+':
60 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
60 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
61 else:
61 else:
62 m = lines_re.match(line)
62 m = lines_re.match(line)
63 if m:
63 if m:
64 yield 'range', m.groups()
64 yield 'range', m.groups()
65 else:
65 else:
66 raise patch.PatchError('unknown patch content: %r' % line)
66 raise patch.PatchError('unknown patch content: %r' % line)
67
67
68 class header(object):
68 class header(object):
69 """patch header
69 """patch header
70
70
71 XXX shoudn't we move this to mercurial/patch.py ?
71 XXX shoudn't we move this to mercurial/patch.py ?
72 """
72 """
73 diff_re = re.compile('diff --git a/(.*) b/(.*)$')
73 diff_re = re.compile('diff --git a/(.*) b/(.*)$')
74 allhunks_re = re.compile('(?:index|new file|deleted file) ')
74 allhunks_re = re.compile('(?:index|new file|deleted file) ')
75 pretty_re = re.compile('(?:new file|deleted file) ')
75 pretty_re = re.compile('(?:new file|deleted file) ')
76 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
76 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
77
77
78 def __init__(self, header):
78 def __init__(self, header):
79 self.header = header
79 self.header = header
80 self.hunks = []
80 self.hunks = []
81
81
82 def binary(self):
82 def binary(self):
83 for h in self.header:
83 for h in self.header:
84 if h.startswith('index '):
84 if h.startswith('index '):
85 return True
85 return True
86
86
87 def pretty(self, fp):
87 def pretty(self, fp):
88 for h in self.header:
88 for h in self.header:
89 if h.startswith('index '):
89 if h.startswith('index '):
90 fp.write(_('this modifies a binary file (all or nothing)\n'))
90 fp.write(_('this modifies a binary file (all or nothing)\n'))
91 break
91 break
92 if self.pretty_re.match(h):
92 if self.pretty_re.match(h):
93 fp.write(h)
93 fp.write(h)
94 if self.binary():
94 if self.binary():
95 fp.write(_('this is a binary file\n'))
95 fp.write(_('this is a binary file\n'))
96 break
96 break
97 if h.startswith('---'):
97 if h.startswith('---'):
98 fp.write(_('%d hunks, %d lines changed\n') %
98 fp.write(_('%d hunks, %d lines changed\n') %
99 (len(self.hunks),
99 (len(self.hunks),
100 sum([h.added + h.removed for h in self.hunks])))
100 sum([h.added + h.removed for h in self.hunks])))
101 break
101 break
102 fp.write(h)
102 fp.write(h)
103
103
104 def write(self, fp):
104 def write(self, fp):
105 fp.write(''.join(self.header))
105 fp.write(''.join(self.header))
106
106
107 def allhunks(self):
107 def allhunks(self):
108 for h in self.header:
108 for h in self.header:
109 if self.allhunks_re.match(h):
109 if self.allhunks_re.match(h):
110 return True
110 return True
111
111
112 def files(self):
112 def files(self):
113 fromfile, tofile = self.diff_re.match(self.header[0]).groups()
113 fromfile, tofile = self.diff_re.match(self.header[0]).groups()
114 if fromfile == tofile:
114 if fromfile == tofile:
115 return [fromfile]
115 return [fromfile]
116 return [fromfile, tofile]
116 return [fromfile, tofile]
117
117
118 def filename(self):
118 def filename(self):
119 return self.files()[-1]
119 return self.files()[-1]
120
120
121 def __repr__(self):
121 def __repr__(self):
122 return '<header %s>' % (' '.join(map(repr, self.files())))
122 return '<header %s>' % (' '.join(map(repr, self.files())))
123
123
124 def special(self):
124 def special(self):
125 for h in self.header:
125 for h in self.header:
126 if self.special_re.match(h):
126 if self.special_re.match(h):
127 return True
127 return True
128
128
129 def countchanges(hunk):
129 def countchanges(hunk):
130 """hunk -> (n+,n-)"""
130 """hunk -> (n+,n-)"""
131 add = len([h for h in hunk if h[0] == '+'])
131 add = len([h for h in hunk if h[0] == '+'])
132 rem = len([h for h in hunk if h[0] == '-'])
132 rem = len([h for h in hunk if h[0] == '-'])
133 return add, rem
133 return add, rem
134
134
135 class hunk(object):
135 class hunk(object):
136 """patch hunk
136 """patch hunk
137
137
138 XXX shouldn't we merge this with patch.hunk ?
138 XXX shouldn't we merge this with patch.hunk ?
139 """
139 """
140 maxcontext = 3
140 maxcontext = 3
141
141
142 def __init__(self, header, fromline, toline, proc, before, hunk, after):
142 def __init__(self, header, fromline, toline, proc, before, hunk, after):
143 def trimcontext(number, lines):
143 def trimcontext(number, lines):
144 delta = len(lines) - self.maxcontext
144 delta = len(lines) - self.maxcontext
145 if False and delta > 0:
145 if False and delta > 0:
146 return number + delta, lines[:self.maxcontext]
146 return number + delta, lines[:self.maxcontext]
147 return number, lines
147 return number, lines
148
148
149 self.header = header
149 self.header = header
150 self.fromline, self.before = trimcontext(fromline, before)
150 self.fromline, self.before = trimcontext(fromline, before)
151 self.toline, self.after = trimcontext(toline, after)
151 self.toline, self.after = trimcontext(toline, after)
152 self.proc = proc
152 self.proc = proc
153 self.hunk = hunk
153 self.hunk = hunk
154 self.added, self.removed = countchanges(self.hunk)
154 self.added, self.removed = countchanges(self.hunk)
155
155
156 def write(self, fp):
156 def write(self, fp):
157 delta = len(self.before) + len(self.after)
157 delta = len(self.before) + len(self.after)
158 if self.after and self.after[-1] == '\\ No newline at end of file\n':
158 if self.after and self.after[-1] == '\\ No newline at end of file\n':
159 delta -= 1
159 delta -= 1
160 fromlen = delta + self.removed
160 fromlen = delta + self.removed
161 tolen = delta + self.added
161 tolen = delta + self.added
162 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
162 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
163 (self.fromline, fromlen, self.toline, tolen,
163 (self.fromline, fromlen, self.toline, tolen,
164 self.proc and (' ' + self.proc)))
164 self.proc and (' ' + self.proc)))
165 fp.write(''.join(self.before + self.hunk + self.after))
165 fp.write(''.join(self.before + self.hunk + self.after))
166
166
167 pretty = write
167 pretty = write
168
168
169 def filename(self):
169 def filename(self):
170 return self.header.filename()
170 return self.header.filename()
171
171
172 def __repr__(self):
172 def __repr__(self):
173 return '<hunk %r@%d>' % (self.filename(), self.fromline)
173 return '<hunk %r@%d>' % (self.filename(), self.fromline)
174
174
175 def parsepatch(fp):
175 def parsepatch(fp):
176 """patch -> [] of hunks """
176 """patch -> [] of hunks """
177 class parser(object):
177 class parser(object):
178 """patch parsing state machine"""
178 """patch parsing state machine"""
179 def __init__(self):
179 def __init__(self):
180 self.fromline = 0
180 self.fromline = 0
181 self.toline = 0
181 self.toline = 0
182 self.proc = ''
182 self.proc = ''
183 self.header = None
183 self.header = None
184 self.context = []
184 self.context = []
185 self.before = []
185 self.before = []
186 self.hunk = []
186 self.hunk = []
187 self.stream = []
187 self.stream = []
188
188
189 def addrange(self, (fromstart, fromend, tostart, toend, proc)):
189 def addrange(self, (fromstart, fromend, tostart, toend, proc)):
190 self.fromline = int(fromstart)
190 self.fromline = int(fromstart)
191 self.toline = int(tostart)
191 self.toline = int(tostart)
192 self.proc = proc
192 self.proc = proc
193
193
194 def addcontext(self, context):
194 def addcontext(self, context):
195 if self.hunk:
195 if self.hunk:
196 h = hunk(self.header, self.fromline, self.toline, self.proc,
196 h = hunk(self.header, self.fromline, self.toline, self.proc,
197 self.before, self.hunk, context)
197 self.before, self.hunk, context)
198 self.header.hunks.append(h)
198 self.header.hunks.append(h)
199 self.stream.append(h)
199 self.stream.append(h)
200 self.fromline += len(self.before) + h.removed
200 self.fromline += len(self.before) + h.removed
201 self.toline += len(self.before) + h.added
201 self.toline += len(self.before) + h.added
202 self.before = []
202 self.before = []
203 self.hunk = []
203 self.hunk = []
204 self.proc = ''
204 self.proc = ''
205 self.context = context
205 self.context = context
206
206
207 def addhunk(self, hunk):
207 def addhunk(self, hunk):
208 if self.context:
208 if self.context:
209 self.before = self.context
209 self.before = self.context
210 self.context = []
210 self.context = []
211 self.hunk = hunk
211 self.hunk = hunk
212
212
213 def newfile(self, hdr):
213 def newfile(self, hdr):
214 self.addcontext([])
214 self.addcontext([])
215 h = header(hdr)
215 h = header(hdr)
216 self.stream.append(h)
216 self.stream.append(h)
217 self.header = h
217 self.header = h
218
218
219 def finished(self):
219 def finished(self):
220 self.addcontext([])
220 self.addcontext([])
221 return self.stream
221 return self.stream
222
222
223 transitions = {
223 transitions = {
224 'file': {'context': addcontext,
224 'file': {'context': addcontext,
225 'file': newfile,
225 'file': newfile,
226 'hunk': addhunk,
226 'hunk': addhunk,
227 'range': addrange},
227 'range': addrange},
228 'context': {'file': newfile,
228 'context': {'file': newfile,
229 'hunk': addhunk,
229 'hunk': addhunk,
230 'range': addrange},
230 'range': addrange},
231 'hunk': {'context': addcontext,
231 'hunk': {'context': addcontext,
232 'file': newfile,
232 'file': newfile,
233 'range': addrange},
233 'range': addrange},
234 'range': {'context': addcontext,
234 'range': {'context': addcontext,
235 'hunk': addhunk},
235 'hunk': addhunk},
236 }
236 }
237
237
238 p = parser()
238 p = parser()
239
239
240 state = 'context'
240 state = 'context'
241 for newstate, data in scanpatch(fp):
241 for newstate, data in scanpatch(fp):
242 try:
242 try:
243 p.transitions[state][newstate](p, data)
243 p.transitions[state][newstate](p, data)
244 except KeyError:
244 except KeyError:
245 raise patch.PatchError('unhandled transition: %s -> %s' %
245 raise patch.PatchError('unhandled transition: %s -> %s' %
246 (state, newstate))
246 (state, newstate))
247 state = newstate
247 state = newstate
248 return p.finished()
248 return p.finished()
249
249
250 def filterpatch(ui, chunks):
250 def filterpatch(ui, chunks):
251 """Interactively filter patch chunks into applied-only chunks"""
251 """Interactively filter patch chunks into applied-only chunks"""
252 chunks = list(chunks)
252 chunks = list(chunks)
253 chunks.reverse()
253 chunks.reverse()
254 seen = set()
254 seen = set()
255 def consumefile():
255 def consumefile():
256 """fetch next portion from chunks until a 'header' is seen
256 """fetch next portion from chunks until a 'header' is seen
257 NB: header == new-file mark
257 NB: header == new-file mark
258 """
258 """
259 consumed = []
259 consumed = []
260 while chunks:
260 while chunks:
261 if isinstance(chunks[-1], header):
261 if isinstance(chunks[-1], header):
262 break
262 break
263 else:
263 else:
264 consumed.append(chunks.pop())
264 consumed.append(chunks.pop())
265 return consumed
265 return consumed
266
266
267 resp_all = [None] # this two are changed from inside prompt,
267 resp_all = [None] # this two are changed from inside prompt,
268 resp_file = [None] # so can't be usual variables
268 resp_file = [None] # so can't be usual variables
269 applied = {} # 'filename' -> [] of chunks
269 applied = {} # 'filename' -> [] of chunks
270 def prompt(query):
270 def prompt(query):
271 """prompt query, and process base inputs
271 """prompt query, and process base inputs
272
272
273 - y/n for the rest of file
273 - y/n for the rest of file
274 - y/n for the rest
274 - y/n for the rest
275 - ? (help)
275 - ? (help)
276 - q (quit)
276 - q (quit)
277
277
278 else, input is returned to the caller.
278 else, input is returned to the caller.
279 """
279 """
280 if resp_all[0] is not None:
280 if resp_all[0] is not None:
281 return resp_all[0]
281 return resp_all[0]
282 if resp_file[0] is not None:
282 if resp_file[0] is not None:
283 return resp_file[0]
283 return resp_file[0]
284 while True:
284 while True:
285 resps = _('[Ynsfdaq?]')
285 resps = _('[Ynsfdaq?]')
286 choices = (_('&Yes, record this change'),
286 choices = (_('&Yes, record this change'),
287 _('&No, skip this change'),
287 _('&No, skip this change'),
288 _('&Skip remaining changes to this file'),
288 _('&Skip remaining changes to this file'),
289 _('Record remaining changes to this &file'),
289 _('Record remaining changes to this &file'),
290 _('&Done, skip remaining changes and files'),
290 _('&Done, skip remaining changes and files'),
291 _('Record &all changes to all remaining files'),
291 _('Record &all changes to all remaining files'),
292 _('&Quit, recording no changes'),
292 _('&Quit, recording no changes'),
293 _('&?'))
293 _('&?'))
294 r = ui.promptchoice("%s %s " % (query, resps), choices)
294 r = ui.promptchoice("%s %s " % (query, resps), choices)
295 if r == 7: # ?
295 if r == 7: # ?
296 doc = gettext(record.__doc__)
296 doc = gettext(record.__doc__)
297 c = doc.find(_('y - record this change'))
297 c = doc.find(_('y - record this change'))
298 for l in doc[c:].splitlines():
298 for l in doc[c:].splitlines():
299 if l: ui.write(l.strip(), '\n')
299 if l: ui.write(l.strip(), '\n')
300 continue
300 continue
301 elif r == 0: # yes
301 elif r == 0: # yes
302 ret = 'y'
302 ret = 'y'
303 elif r == 1: # no
303 elif r == 1: # no
304 ret = 'n'
304 ret = 'n'
305 elif r == 2: # Skip
305 elif r == 2: # Skip
306 ret = resp_file[0] = 'n'
306 ret = resp_file[0] = 'n'
307 elif r == 3: # file (Record remaining)
307 elif r == 3: # file (Record remaining)
308 ret = resp_file[0] = 'y'
308 ret = resp_file[0] = 'y'
309 elif r == 4: # done, skip remaining
309 elif r == 4: # done, skip remaining
310 ret = resp_all[0] = 'n'
310 ret = resp_all[0] = 'n'
311 elif r == 5: # all
311 elif r == 5: # all
312 ret = resp_all[0] = 'y'
312 ret = resp_all[0] = 'y'
313 elif r == 6: # quit
313 elif r == 6: # quit
314 raise util.Abort(_('user quit'))
314 raise util.Abort(_('user quit'))
315 return ret
315 return ret
316 pos, total = 0, len(chunks) - 1
316 pos, total = 0, len(chunks) - 1
317 while chunks:
317 while chunks:
318 chunk = chunks.pop()
318 chunk = chunks.pop()
319 if isinstance(chunk, header):
319 if isinstance(chunk, header):
320 # new-file mark
320 # new-file mark
321 resp_file = [None]
321 resp_file = [None]
322 fixoffset = 0
322 fixoffset = 0
323 hdr = ''.join(chunk.header)
323 hdr = ''.join(chunk.header)
324 if hdr in seen:
324 if hdr in seen:
325 consumefile()
325 consumefile()
326 continue
326 continue
327 seen.add(hdr)
327 seen.add(hdr)
328 if resp_all[0] is None:
328 if resp_all[0] is None:
329 chunk.pretty(ui)
329 chunk.pretty(ui)
330 r = prompt(_('examine changes to %s?') %
330 r = prompt(_('examine changes to %s?') %
331 _(' and ').join(map(repr, chunk.files())))
331 _(' and ').join(map(repr, chunk.files())))
332 if r == _('y'):
332 if r == _('y'):
333 applied[chunk.filename()] = [chunk]
333 applied[chunk.filename()] = [chunk]
334 if chunk.allhunks():
334 if chunk.allhunks():
335 applied[chunk.filename()] += consumefile()
335 applied[chunk.filename()] += consumefile()
336 else:
336 else:
337 consumefile()
337 consumefile()
338 else:
338 else:
339 # new hunk
339 # new hunk
340 if resp_file[0] is None and resp_all[0] is None:
340 if resp_file[0] is None and resp_all[0] is None:
341 chunk.pretty(ui)
341 chunk.pretty(ui)
342 r = total == 1 and prompt(_('record this change to %r?') %
342 r = total == 1 and prompt(_('record this change to %r?') %
343 chunk.filename()) \
343 chunk.filename()) \
344 or prompt(_('record change %d/%d to %r?') %
344 or prompt(_('record change %d/%d to %r?') %
345 (pos, total, chunk.filename()))
345 (pos, total, chunk.filename()))
346 if r == _('y'):
346 if r == _('y'):
347 if fixoffset:
347 if fixoffset:
348 chunk = copy.copy(chunk)
348 chunk = copy.copy(chunk)
349 chunk.toline += fixoffset
349 chunk.toline += fixoffset
350 applied[chunk.filename()].append(chunk)
350 applied[chunk.filename()].append(chunk)
351 else:
351 else:
352 fixoffset += chunk.removed - chunk.added
352 fixoffset += chunk.removed - chunk.added
353 pos = pos + 1
353 pos = pos + 1
354 return reduce(operator.add, [h for h in applied.itervalues()
354 return reduce(operator.add, [h for h in applied.itervalues()
355 if h[0].special() or len(h) > 1], [])
355 if h[0].special() or len(h) > 1], [])
356
356
357 def record(ui, repo, *pats, **opts):
357 def record(ui, repo, *pats, **opts):
358 '''interactively select changes to commit
358 '''interactively select changes to commit
359
359
360 If a list of files is omitted, all changes reported by "hg status" will be
360 If a list of files is omitted, all changes reported by "hg status" will be
361 candidates for recording.
361 candidates for recording.
362
362
363 See 'hg help dates' for a list of formats valid for -d/--date.
363 See 'hg help dates' for a list of formats valid for -d/--date.
364
364
365 You will be prompted for whether to record changes to each modified file,
365 You will be prompted for whether to record changes to each modified file,
366 and for files with multiple changes, for each change to use. For each
366 and for files with multiple changes, for each change to use. For each
367 query, the following responses are possible:
367 query, the following responses are possible::
368
368
369 y - record this change
369 y - record this change
370 n - skip this change
370 n - skip this change
371
371
372 s - skip remaining changes to this file
372 s - skip remaining changes to this file
373 f - record remaining changes to this file
373 f - record remaining changes to this file
374
374
375 d - done, skip remaining changes and files
375 d - done, skip remaining changes and files
376 a - record all changes to all remaining files
376 a - record all changes to all remaining files
377 q - quit, recording no changes
377 q - quit, recording no changes
378
378
379 ? - display help'''
379 ? - display help'''
380
380
381 def record_committer(ui, repo, pats, opts):
381 def record_committer(ui, repo, pats, opts):
382 commands.commit(ui, repo, *pats, **opts)
382 commands.commit(ui, repo, *pats, **opts)
383
383
384 dorecord(ui, repo, record_committer, *pats, **opts)
384 dorecord(ui, repo, record_committer, *pats, **opts)
385
385
386
386
387 def qrecord(ui, repo, patch, *pats, **opts):
387 def qrecord(ui, repo, patch, *pats, **opts):
388 '''interactively record a new patch
388 '''interactively record a new patch
389
389
390 See 'hg help qnew' & 'hg help record' for more information and usage.
390 See 'hg help qnew' & 'hg help record' for more information and usage.
391 '''
391 '''
392
392
393 try:
393 try:
394 mq = extensions.find('mq')
394 mq = extensions.find('mq')
395 except KeyError:
395 except KeyError:
396 raise util.Abort(_("'mq' extension not loaded"))
396 raise util.Abort(_("'mq' extension not loaded"))
397
397
398 def qrecord_committer(ui, repo, pats, opts):
398 def qrecord_committer(ui, repo, pats, opts):
399 mq.new(ui, repo, patch, *pats, **opts)
399 mq.new(ui, repo, patch, *pats, **opts)
400
400
401 opts = opts.copy()
401 opts = opts.copy()
402 opts['force'] = True # always 'qnew -f'
402 opts['force'] = True # always 'qnew -f'
403 dorecord(ui, repo, qrecord_committer, *pats, **opts)
403 dorecord(ui, repo, qrecord_committer, *pats, **opts)
404
404
405
405
406 def dorecord(ui, repo, committer, *pats, **opts):
406 def dorecord(ui, repo, committer, *pats, **opts):
407 if not ui.interactive():
407 if not ui.interactive():
408 raise util.Abort(_('running non-interactively, use commit instead'))
408 raise util.Abort(_('running non-interactively, use commit instead'))
409
409
410 def recordfunc(ui, repo, message, match, opts):
410 def recordfunc(ui, repo, message, match, opts):
411 """This is generic record driver.
411 """This is generic record driver.
412
412
413 Its job is to interactively filter local changes, and accordingly
413 Its job is to interactively filter local changes, and accordingly
414 prepare working dir into a state, where the job can be delegated to
414 prepare working dir into a state, where the job can be delegated to
415 non-interactive commit command such as 'commit' or 'qrefresh'.
415 non-interactive commit command such as 'commit' or 'qrefresh'.
416
416
417 After the actual job is done by non-interactive command, working dir
417 After the actual job is done by non-interactive command, working dir
418 state is restored to original.
418 state is restored to original.
419
419
420 In the end we'll record intresting changes, and everything else will be
420 In the end we'll record intresting changes, and everything else will be
421 left in place, so the user can continue his work.
421 left in place, so the user can continue his work.
422 """
422 """
423
423
424 changes = repo.status(match=match)[:3]
424 changes = repo.status(match=match)[:3]
425 diffopts = mdiff.diffopts(git=True, nodates=True)
425 diffopts = mdiff.diffopts(git=True, nodates=True)
426 chunks = patch.diff(repo, changes=changes, opts=diffopts)
426 chunks = patch.diff(repo, changes=changes, opts=diffopts)
427 fp = cStringIO.StringIO()
427 fp = cStringIO.StringIO()
428 fp.write(''.join(chunks))
428 fp.write(''.join(chunks))
429 fp.seek(0)
429 fp.seek(0)
430
430
431 # 1. filter patch, so we have intending-to apply subset of it
431 # 1. filter patch, so we have intending-to apply subset of it
432 chunks = filterpatch(ui, parsepatch(fp))
432 chunks = filterpatch(ui, parsepatch(fp))
433 del fp
433 del fp
434
434
435 contenders = set()
435 contenders = set()
436 for h in chunks:
436 for h in chunks:
437 try: contenders.update(set(h.files()))
437 try: contenders.update(set(h.files()))
438 except AttributeError: pass
438 except AttributeError: pass
439
439
440 changed = changes[0] + changes[1] + changes[2]
440 changed = changes[0] + changes[1] + changes[2]
441 newfiles = [f for f in changed if f in contenders]
441 newfiles = [f for f in changed if f in contenders]
442 if not newfiles:
442 if not newfiles:
443 ui.status(_('no changes to record\n'))
443 ui.status(_('no changes to record\n'))
444 return 0
444 return 0
445
445
446 modified = set(changes[0])
446 modified = set(changes[0])
447
447
448 # 2. backup changed files, so we can restore them in the end
448 # 2. backup changed files, so we can restore them in the end
449 backups = {}
449 backups = {}
450 backupdir = repo.join('record-backups')
450 backupdir = repo.join('record-backups')
451 try:
451 try:
452 os.mkdir(backupdir)
452 os.mkdir(backupdir)
453 except OSError, err:
453 except OSError, err:
454 if err.errno != errno.EEXIST:
454 if err.errno != errno.EEXIST:
455 raise
455 raise
456 try:
456 try:
457 # backup continues
457 # backup continues
458 for f in newfiles:
458 for f in newfiles:
459 if f not in modified:
459 if f not in modified:
460 continue
460 continue
461 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
461 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
462 dir=backupdir)
462 dir=backupdir)
463 os.close(fd)
463 os.close(fd)
464 ui.debug(_('backup %r as %r\n') % (f, tmpname))
464 ui.debug(_('backup %r as %r\n') % (f, tmpname))
465 util.copyfile(repo.wjoin(f), tmpname)
465 util.copyfile(repo.wjoin(f), tmpname)
466 backups[f] = tmpname
466 backups[f] = tmpname
467
467
468 fp = cStringIO.StringIO()
468 fp = cStringIO.StringIO()
469 for c in chunks:
469 for c in chunks:
470 if c.filename() in backups:
470 if c.filename() in backups:
471 c.write(fp)
471 c.write(fp)
472 dopatch = fp.tell()
472 dopatch = fp.tell()
473 fp.seek(0)
473 fp.seek(0)
474
474
475 # 3a. apply filtered patch to clean repo (clean)
475 # 3a. apply filtered patch to clean repo (clean)
476 if backups:
476 if backups:
477 hg.revert(repo, repo.dirstate.parents()[0], backups.has_key)
477 hg.revert(repo, repo.dirstate.parents()[0], backups.has_key)
478
478
479 # 3b. (apply)
479 # 3b. (apply)
480 if dopatch:
480 if dopatch:
481 try:
481 try:
482 ui.debug(_('applying patch\n'))
482 ui.debug(_('applying patch\n'))
483 ui.debug(fp.getvalue())
483 ui.debug(fp.getvalue())
484 pfiles = {}
484 pfiles = {}
485 patch.internalpatch(fp, ui, 1, repo.root, files=pfiles,
485 patch.internalpatch(fp, ui, 1, repo.root, files=pfiles,
486 eolmode=None)
486 eolmode=None)
487 patch.updatedir(ui, repo, pfiles)
487 patch.updatedir(ui, repo, pfiles)
488 except patch.PatchError, err:
488 except patch.PatchError, err:
489 s = str(err)
489 s = str(err)
490 if s:
490 if s:
491 raise util.Abort(s)
491 raise util.Abort(s)
492 else:
492 else:
493 raise util.Abort(_('patch failed to apply'))
493 raise util.Abort(_('patch failed to apply'))
494 del fp
494 del fp
495
495
496 # 4. We prepared working directory according to filtered patch.
496 # 4. We prepared working directory according to filtered patch.
497 # Now is the time to delegate the job to commit/qrefresh or the like!
497 # Now is the time to delegate the job to commit/qrefresh or the like!
498
498
499 # it is important to first chdir to repo root -- we'll call a
499 # it is important to first chdir to repo root -- we'll call a
500 # highlevel command with list of pathnames relative to repo root
500 # highlevel command with list of pathnames relative to repo root
501 cwd = os.getcwd()
501 cwd = os.getcwd()
502 os.chdir(repo.root)
502 os.chdir(repo.root)
503 try:
503 try:
504 committer(ui, repo, newfiles, opts)
504 committer(ui, repo, newfiles, opts)
505 finally:
505 finally:
506 os.chdir(cwd)
506 os.chdir(cwd)
507
507
508 return 0
508 return 0
509 finally:
509 finally:
510 # 5. finally restore backed-up files
510 # 5. finally restore backed-up files
511 try:
511 try:
512 for realname, tmpname in backups.iteritems():
512 for realname, tmpname in backups.iteritems():
513 ui.debug(_('restoring %r to %r\n') % (tmpname, realname))
513 ui.debug(_('restoring %r to %r\n') % (tmpname, realname))
514 util.copyfile(tmpname, repo.wjoin(realname))
514 util.copyfile(tmpname, repo.wjoin(realname))
515 os.unlink(tmpname)
515 os.unlink(tmpname)
516 os.rmdir(backupdir)
516 os.rmdir(backupdir)
517 except OSError:
517 except OSError:
518 pass
518 pass
519 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
519 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
520
520
521 cmdtable = {
521 cmdtable = {
522 "record":
522 "record":
523 (record,
523 (record,
524
524
525 # add commit options
525 # add commit options
526 commands.table['^commit|ci'][1],
526 commands.table['^commit|ci'][1],
527
527
528 _('hg record [OPTION]... [FILE]...')),
528 _('hg record [OPTION]... [FILE]...')),
529 }
529 }
530
530
531
531
532 def extsetup():
532 def extsetup():
533 try:
533 try:
534 mq = extensions.find('mq')
534 mq = extensions.find('mq')
535 except KeyError:
535 except KeyError:
536 return
536 return
537
537
538 qcmdtable = {
538 qcmdtable = {
539 "qrecord":
539 "qrecord":
540 (qrecord,
540 (qrecord,
541
541
542 # add qnew options, except '--force'
542 # add qnew options, except '--force'
543 [opt for opt in mq.cmdtable['qnew'][1] if opt[1] != 'force'],
543 [opt for opt in mq.cmdtable['qnew'][1] if opt[1] != 'force'],
544
544
545 _('hg qrecord [OPTION]... PATCH [FILE]...')),
545 _('hg qrecord [OPTION]... PATCH [FILE]...')),
546 }
546 }
547
547
548 cmdtable.update(qcmdtable)
548 cmdtable.update(qcmdtable)
549
549
@@ -1,3510 +1,3515 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing 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 of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from lock import release
9 from lock import release
10 from i18n import _, gettext
10 from i18n import _, gettext
11 import os, re, sys, subprocess, difflib, time, tempfile
11 import os, re, sys, subprocess, difflib, time, tempfile
12 import hg, util, revlog, bundlerepo, extensions, copies, context, error
12 import hg, util, revlog, bundlerepo, extensions, copies, context, error
13 import patch, help, mdiff, url, encoding
13 import patch, help, mdiff, url, encoding
14 import archival, changegroup, cmdutil, sshserver, hbisect
14 import archival, changegroup, cmdutil, sshserver, hbisect
15 from hgweb import server
15 from hgweb import server
16 import merge as merge_
16 import merge as merge_
17 import minirst
17
18
18 # Commands start here, listed alphabetically
19 # Commands start here, listed alphabetically
19
20
20 def add(ui, repo, *pats, **opts):
21 def add(ui, repo, *pats, **opts):
21 """add the specified files on the next commit
22 """add the specified files on the next commit
22
23
23 Schedule files to be version controlled and added to the repository.
24 Schedule files to be version controlled and added to the repository.
24
25
25 The files will be added to the repository at the next commit. To undo an
26 The files will be added to the repository at the next commit. To undo an
26 add before that, see hg forget.
27 add before that, see hg forget.
27
28
28 If no names are given, add all files to the repository.
29 If no names are given, add all files to the repository.
29 """
30 """
30
31
31 bad = []
32 bad = []
32 exacts = {}
33 exacts = {}
33 names = []
34 names = []
34 m = cmdutil.match(repo, pats, opts)
35 m = cmdutil.match(repo, pats, opts)
35 oldbad = m.bad
36 oldbad = m.bad
36 m.bad = lambda x,y: bad.append(x) or oldbad(x,y)
37 m.bad = lambda x,y: bad.append(x) or oldbad(x,y)
37
38
38 for f in repo.walk(m):
39 for f in repo.walk(m):
39 exact = m.exact(f)
40 exact = m.exact(f)
40 if exact or f not in repo.dirstate:
41 if exact or f not in repo.dirstate:
41 names.append(f)
42 names.append(f)
42 if ui.verbose or not exact:
43 if ui.verbose or not exact:
43 ui.status(_('adding %s\n') % m.rel(f))
44 ui.status(_('adding %s\n') % m.rel(f))
44 if not opts.get('dry_run'):
45 if not opts.get('dry_run'):
45 bad += [f for f in repo.add(names) if f in m.files()]
46 bad += [f for f in repo.add(names) if f in m.files()]
46 return bad and 1 or 0
47 return bad and 1 or 0
47
48
48 def addremove(ui, repo, *pats, **opts):
49 def addremove(ui, repo, *pats, **opts):
49 """add all new files, delete all missing files
50 """add all new files, delete all missing files
50
51
51 Add all new files and remove all missing files from the repository.
52 Add all new files and remove all missing files from the repository.
52
53
53 New files are ignored if they match any of the patterns in .hgignore. As
54 New files are ignored if they match any of the patterns in .hgignore. As
54 with add, these changes take effect at the next commit.
55 with add, these changes take effect at the next commit.
55
56
56 Use the -s/--similarity option to detect renamed files. With a parameter
57 Use the -s/--similarity option to detect renamed files. With a parameter
57 greater than 0, this compares every removed file with every added file and
58 greater than 0, this compares every removed file with every added file and
58 records those similar enough as renames. This option takes a percentage
59 records those similar enough as renames. This option takes a percentage
59 between 0 (disabled) and 100 (files must be identical) as its parameter.
60 between 0 (disabled) and 100 (files must be identical) as its parameter.
60 Detecting renamed files this way can be expensive.
61 Detecting renamed files this way can be expensive.
61 """
62 """
62 try:
63 try:
63 sim = float(opts.get('similarity') or 0)
64 sim = float(opts.get('similarity') or 0)
64 except ValueError:
65 except ValueError:
65 raise util.Abort(_('similarity must be a number'))
66 raise util.Abort(_('similarity must be a number'))
66 if sim < 0 or sim > 100:
67 if sim < 0 or sim > 100:
67 raise util.Abort(_('similarity must be between 0 and 100'))
68 raise util.Abort(_('similarity must be between 0 and 100'))
68 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
69 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
69
70
70 def annotate(ui, repo, *pats, **opts):
71 def annotate(ui, repo, *pats, **opts):
71 """show changeset information by line for each file
72 """show changeset information by line for each file
72
73
73 List changes in files, showing the revision id responsible for each line
74 List changes in files, showing the revision id responsible for each line
74
75
75 This command is useful for discovering when a change was made and by whom.
76 This command is useful for discovering when a change was made and by whom.
76
77
77 Without the -a/--text option, annotate will avoid processing files it
78 Without the -a/--text option, annotate will avoid processing files it
78 detects as binary. With -a, annotate will annotate the file anyway,
79 detects as binary. With -a, annotate will annotate the file anyway,
79 although the results will probably be neither useful nor desirable.
80 although the results will probably be neither useful nor desirable.
80 """
81 """
81 datefunc = ui.quiet and util.shortdate or util.datestr
82 datefunc = ui.quiet and util.shortdate or util.datestr
82 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
83 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
83
84
84 if not pats:
85 if not pats:
85 raise util.Abort(_('at least one filename or pattern is required'))
86 raise util.Abort(_('at least one filename or pattern is required'))
86
87
87 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
88 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
88 ('number', lambda x: str(x[0].rev())),
89 ('number', lambda x: str(x[0].rev())),
89 ('changeset', lambda x: short(x[0].node())),
90 ('changeset', lambda x: short(x[0].node())),
90 ('date', getdate),
91 ('date', getdate),
91 ('follow', lambda x: x[0].path()),
92 ('follow', lambda x: x[0].path()),
92 ]
93 ]
93
94
94 if (not opts.get('user') and not opts.get('changeset') and not opts.get('date')
95 if (not opts.get('user') and not opts.get('changeset') and not opts.get('date')
95 and not opts.get('follow')):
96 and not opts.get('follow')):
96 opts['number'] = 1
97 opts['number'] = 1
97
98
98 linenumber = opts.get('line_number') is not None
99 linenumber = opts.get('line_number') is not None
99 if (linenumber and (not opts.get('changeset')) and (not opts.get('number'))):
100 if (linenumber and (not opts.get('changeset')) and (not opts.get('number'))):
100 raise util.Abort(_('at least one of -n/-c is required for -l'))
101 raise util.Abort(_('at least one of -n/-c is required for -l'))
101
102
102 funcmap = [func for op, func in opmap if opts.get(op)]
103 funcmap = [func for op, func in opmap if opts.get(op)]
103 if linenumber:
104 if linenumber:
104 lastfunc = funcmap[-1]
105 lastfunc = funcmap[-1]
105 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
106 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
106
107
107 ctx = repo[opts.get('rev')]
108 ctx = repo[opts.get('rev')]
108
109
109 m = cmdutil.match(repo, pats, opts)
110 m = cmdutil.match(repo, pats, opts)
110 for abs in ctx.walk(m):
111 for abs in ctx.walk(m):
111 fctx = ctx[abs]
112 fctx = ctx[abs]
112 if not opts.get('text') and util.binary(fctx.data()):
113 if not opts.get('text') and util.binary(fctx.data()):
113 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
114 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
114 continue
115 continue
115
116
116 lines = fctx.annotate(follow=opts.get('follow'),
117 lines = fctx.annotate(follow=opts.get('follow'),
117 linenumber=linenumber)
118 linenumber=linenumber)
118 pieces = []
119 pieces = []
119
120
120 for f in funcmap:
121 for f in funcmap:
121 l = [f(n) for n, dummy in lines]
122 l = [f(n) for n, dummy in lines]
122 if l:
123 if l:
123 ml = max(map(len, l))
124 ml = max(map(len, l))
124 pieces.append(["%*s" % (ml, x) for x in l])
125 pieces.append(["%*s" % (ml, x) for x in l])
125
126
126 if pieces:
127 if pieces:
127 for p, l in zip(zip(*pieces), lines):
128 for p, l in zip(zip(*pieces), lines):
128 ui.write("%s: %s" % (" ".join(p), l[1]))
129 ui.write("%s: %s" % (" ".join(p), l[1]))
129
130
130 def archive(ui, repo, dest, **opts):
131 def archive(ui, repo, dest, **opts):
131 '''create an unversioned archive of a repository revision
132 '''create an unversioned archive of a repository revision
132
133
133 By default, the revision used is the parent of the working directory; use
134 By default, the revision used is the parent of the working directory; use
134 -r/--rev to specify a different revision.
135 -r/--rev to specify a different revision.
135
136
136 To specify the type of archive to create, use -t/--type. Valid types are:
137 To specify the type of archive to create, use -t/--type. Valid types are::
137
138
138 "files" (default): a directory full of files
139 "files" (default): a directory full of files
139 "tar": tar archive, uncompressed
140 "tar": tar archive, uncompressed
140 "tbz2": tar archive, compressed using bzip2
141 "tbz2": tar archive, compressed using bzip2
141 "tgz": tar archive, compressed using gzip
142 "tgz": tar archive, compressed using gzip
142 "uzip": zip archive, uncompressed
143 "uzip": zip archive, uncompressed
143 "zip": zip archive, compressed using deflate
144 "zip": zip archive, compressed using deflate
144
145
145 The exact name of the destination archive or directory is given using a
146 The exact name of the destination archive or directory is given using a
146 format string; see 'hg help export' for details.
147 format string; see 'hg help export' for details.
147
148
148 Each member added to an archive file has a directory prefix prepended. Use
149 Each member added to an archive file has a directory prefix prepended. Use
149 -p/--prefix to specify a format string for the prefix. The default is the
150 -p/--prefix to specify a format string for the prefix. The default is the
150 basename of the archive, with suffixes removed.
151 basename of the archive, with suffixes removed.
151 '''
152 '''
152
153
153 ctx = repo[opts.get('rev')]
154 ctx = repo[opts.get('rev')]
154 if not ctx:
155 if not ctx:
155 raise util.Abort(_('no working directory: please specify a revision'))
156 raise util.Abort(_('no working directory: please specify a revision'))
156 node = ctx.node()
157 node = ctx.node()
157 dest = cmdutil.make_filename(repo, dest, node)
158 dest = cmdutil.make_filename(repo, dest, node)
158 if os.path.realpath(dest) == repo.root:
159 if os.path.realpath(dest) == repo.root:
159 raise util.Abort(_('repository root cannot be destination'))
160 raise util.Abort(_('repository root cannot be destination'))
160 matchfn = cmdutil.match(repo, [], opts)
161 matchfn = cmdutil.match(repo, [], opts)
161 kind = opts.get('type') or 'files'
162 kind = opts.get('type') or 'files'
162 prefix = opts.get('prefix')
163 prefix = opts.get('prefix')
163 if dest == '-':
164 if dest == '-':
164 if kind == 'files':
165 if kind == 'files':
165 raise util.Abort(_('cannot archive plain files to stdout'))
166 raise util.Abort(_('cannot archive plain files to stdout'))
166 dest = sys.stdout
167 dest = sys.stdout
167 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
168 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
168 prefix = cmdutil.make_filename(repo, prefix, node)
169 prefix = cmdutil.make_filename(repo, prefix, node)
169 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
170 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
170 matchfn, prefix)
171 matchfn, prefix)
171
172
172 def backout(ui, repo, node=None, rev=None, **opts):
173 def backout(ui, repo, node=None, rev=None, **opts):
173 '''reverse effect of earlier changeset
174 '''reverse effect of earlier changeset
174
175
175 Commit the backed out changes as a new changeset. The new changeset is a
176 Commit the backed out changes as a new changeset. The new changeset is a
176 child of the backed out changeset.
177 child of the backed out changeset.
177
178
178 If you backout a changeset other than the tip, a new head is created. This
179 If you backout a changeset other than the tip, a new head is created. This
179 head will be the new tip and you should merge this backout changeset with
180 head will be the new tip and you should merge this backout changeset with
180 another head.
181 another head.
181
182
182 The --merge option remembers the parent of the working directory before
183 The --merge option remembers the parent of the working directory before
183 starting the backout, then merges the new head with that changeset
184 starting the backout, then merges the new head with that changeset
184 afterwards. This saves you from doing the merge by hand. The result of
185 afterwards. This saves you from doing the merge by hand. The result of
185 this merge is not committed, as with a normal merge.
186 this merge is not committed, as with a normal merge.
186
187
187 See 'hg help dates' for a list of formats valid for -d/--date.
188 See 'hg help dates' for a list of formats valid for -d/--date.
188 '''
189 '''
189 if rev and node:
190 if rev and node:
190 raise util.Abort(_("please specify just one revision"))
191 raise util.Abort(_("please specify just one revision"))
191
192
192 if not rev:
193 if not rev:
193 rev = node
194 rev = node
194
195
195 if not rev:
196 if not rev:
196 raise util.Abort(_("please specify a revision to backout"))
197 raise util.Abort(_("please specify a revision to backout"))
197
198
198 date = opts.get('date')
199 date = opts.get('date')
199 if date:
200 if date:
200 opts['date'] = util.parsedate(date)
201 opts['date'] = util.parsedate(date)
201
202
202 cmdutil.bail_if_changed(repo)
203 cmdutil.bail_if_changed(repo)
203 node = repo.lookup(rev)
204 node = repo.lookup(rev)
204
205
205 op1, op2 = repo.dirstate.parents()
206 op1, op2 = repo.dirstate.parents()
206 a = repo.changelog.ancestor(op1, node)
207 a = repo.changelog.ancestor(op1, node)
207 if a != node:
208 if a != node:
208 raise util.Abort(_('cannot backout change on a different branch'))
209 raise util.Abort(_('cannot backout change on a different branch'))
209
210
210 p1, p2 = repo.changelog.parents(node)
211 p1, p2 = repo.changelog.parents(node)
211 if p1 == nullid:
212 if p1 == nullid:
212 raise util.Abort(_('cannot backout a change with no parents'))
213 raise util.Abort(_('cannot backout a change with no parents'))
213 if p2 != nullid:
214 if p2 != nullid:
214 if not opts.get('parent'):
215 if not opts.get('parent'):
215 raise util.Abort(_('cannot backout a merge changeset without '
216 raise util.Abort(_('cannot backout a merge changeset without '
216 '--parent'))
217 '--parent'))
217 p = repo.lookup(opts['parent'])
218 p = repo.lookup(opts['parent'])
218 if p not in (p1, p2):
219 if p not in (p1, p2):
219 raise util.Abort(_('%s is not a parent of %s') %
220 raise util.Abort(_('%s is not a parent of %s') %
220 (short(p), short(node)))
221 (short(p), short(node)))
221 parent = p
222 parent = p
222 else:
223 else:
223 if opts.get('parent'):
224 if opts.get('parent'):
224 raise util.Abort(_('cannot use --parent on non-merge changeset'))
225 raise util.Abort(_('cannot use --parent on non-merge changeset'))
225 parent = p1
226 parent = p1
226
227
227 # the backout should appear on the same branch
228 # the backout should appear on the same branch
228 branch = repo.dirstate.branch()
229 branch = repo.dirstate.branch()
229 hg.clean(repo, node, show_stats=False)
230 hg.clean(repo, node, show_stats=False)
230 repo.dirstate.setbranch(branch)
231 repo.dirstate.setbranch(branch)
231 revert_opts = opts.copy()
232 revert_opts = opts.copy()
232 revert_opts['date'] = None
233 revert_opts['date'] = None
233 revert_opts['all'] = True
234 revert_opts['all'] = True
234 revert_opts['rev'] = hex(parent)
235 revert_opts['rev'] = hex(parent)
235 revert_opts['no_backup'] = None
236 revert_opts['no_backup'] = None
236 revert(ui, repo, **revert_opts)
237 revert(ui, repo, **revert_opts)
237 commit_opts = opts.copy()
238 commit_opts = opts.copy()
238 commit_opts['addremove'] = False
239 commit_opts['addremove'] = False
239 if not commit_opts['message'] and not commit_opts['logfile']:
240 if not commit_opts['message'] and not commit_opts['logfile']:
240 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
241 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
241 commit_opts['force_editor'] = True
242 commit_opts['force_editor'] = True
242 commit(ui, repo, **commit_opts)
243 commit(ui, repo, **commit_opts)
243 def nice(node):
244 def nice(node):
244 return '%d:%s' % (repo.changelog.rev(node), short(node))
245 return '%d:%s' % (repo.changelog.rev(node), short(node))
245 ui.status(_('changeset %s backs out changeset %s\n') %
246 ui.status(_('changeset %s backs out changeset %s\n') %
246 (nice(repo.changelog.tip()), nice(node)))
247 (nice(repo.changelog.tip()), nice(node)))
247 if op1 != node:
248 if op1 != node:
248 hg.clean(repo, op1, show_stats=False)
249 hg.clean(repo, op1, show_stats=False)
249 if opts.get('merge'):
250 if opts.get('merge'):
250 ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip()))
251 ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip()))
251 hg.merge(repo, hex(repo.changelog.tip()))
252 hg.merge(repo, hex(repo.changelog.tip()))
252 else:
253 else:
253 ui.status(_('the backout changeset is a new head - '
254 ui.status(_('the backout changeset is a new head - '
254 'do not forget to merge\n'))
255 'do not forget to merge\n'))
255 ui.status(_('(use "backout --merge" '
256 ui.status(_('(use "backout --merge" '
256 'if you want to auto-merge)\n'))
257 'if you want to auto-merge)\n'))
257
258
258 def bisect(ui, repo, rev=None, extra=None, command=None,
259 def bisect(ui, repo, rev=None, extra=None, command=None,
259 reset=None, good=None, bad=None, skip=None, noupdate=None):
260 reset=None, good=None, bad=None, skip=None, noupdate=None):
260 """subdivision search of changesets
261 """subdivision search of changesets
261
262
262 This command helps to find changesets which introduce problems. To use,
263 This command helps to find changesets which introduce problems. To use,
263 mark the earliest changeset you know exhibits the problem as bad, then
264 mark the earliest changeset you know exhibits the problem as bad, then
264 mark the latest changeset which is free from the problem as good. Bisect
265 mark the latest changeset which is free from the problem as good. Bisect
265 will update your working directory to a revision for testing (unless the
266 will update your working directory to a revision for testing (unless the
266 -U/--noupdate option is specified). Once you have performed tests, mark
267 -U/--noupdate option is specified). Once you have performed tests, mark
267 the working directory as good or bad, and bisect will either update to
268 the working directory as good or bad, and bisect will either update to
268 another candidate changeset or announce that it has found the bad
269 another candidate changeset or announce that it has found the bad
269 revision.
270 revision.
270
271
271 As a shortcut, you can also use the revision argument to mark a revision
272 As a shortcut, you can also use the revision argument to mark a revision
272 as good or bad without checking it out first.
273 as good or bad without checking it out first.
273
274
274 If you supply a command, it will be used for automatic bisection. Its exit
275 If you supply a command, it will be used for automatic bisection. Its exit
275 status will be used to mark revisions as good or bad: status 0 means good,
276 status will be used to mark revisions as good or bad: status 0 means good,
276 125 means to skip the revision, 127 (command not found) will abort the
277 125 means to skip the revision, 127 (command not found) will abort the
277 bisection, and any other non-zero exit status means the revision is bad.
278 bisection, and any other non-zero exit status means the revision is bad.
278 """
279 """
279 def print_result(nodes, good):
280 def print_result(nodes, good):
280 displayer = cmdutil.show_changeset(ui, repo, {})
281 displayer = cmdutil.show_changeset(ui, repo, {})
281 if len(nodes) == 1:
282 if len(nodes) == 1:
282 # narrowed it down to a single revision
283 # narrowed it down to a single revision
283 if good:
284 if good:
284 ui.write(_("The first good revision is:\n"))
285 ui.write(_("The first good revision is:\n"))
285 else:
286 else:
286 ui.write(_("The first bad revision is:\n"))
287 ui.write(_("The first bad revision is:\n"))
287 displayer.show(repo[nodes[0]])
288 displayer.show(repo[nodes[0]])
288 else:
289 else:
289 # multiple possible revisions
290 # multiple possible revisions
290 if good:
291 if good:
291 ui.write(_("Due to skipped revisions, the first "
292 ui.write(_("Due to skipped revisions, the first "
292 "good revision could be any of:\n"))
293 "good revision could be any of:\n"))
293 else:
294 else:
294 ui.write(_("Due to skipped revisions, the first "
295 ui.write(_("Due to skipped revisions, the first "
295 "bad revision could be any of:\n"))
296 "bad revision could be any of:\n"))
296 for n in nodes:
297 for n in nodes:
297 displayer.show(repo[n])
298 displayer.show(repo[n])
298
299
299 def check_state(state, interactive=True):
300 def check_state(state, interactive=True):
300 if not state['good'] or not state['bad']:
301 if not state['good'] or not state['bad']:
301 if (good or bad or skip or reset) and interactive:
302 if (good or bad or skip or reset) and interactive:
302 return
303 return
303 if not state['good']:
304 if not state['good']:
304 raise util.Abort(_('cannot bisect (no known good revisions)'))
305 raise util.Abort(_('cannot bisect (no known good revisions)'))
305 else:
306 else:
306 raise util.Abort(_('cannot bisect (no known bad revisions)'))
307 raise util.Abort(_('cannot bisect (no known bad revisions)'))
307 return True
308 return True
308
309
309 # backward compatibility
310 # backward compatibility
310 if rev in "good bad reset init".split():
311 if rev in "good bad reset init".split():
311 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
312 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
312 cmd, rev, extra = rev, extra, None
313 cmd, rev, extra = rev, extra, None
313 if cmd == "good":
314 if cmd == "good":
314 good = True
315 good = True
315 elif cmd == "bad":
316 elif cmd == "bad":
316 bad = True
317 bad = True
317 else:
318 else:
318 reset = True
319 reset = True
319 elif extra or good + bad + skip + reset + bool(command) > 1:
320 elif extra or good + bad + skip + reset + bool(command) > 1:
320 raise util.Abort(_('incompatible arguments'))
321 raise util.Abort(_('incompatible arguments'))
321
322
322 if reset:
323 if reset:
323 p = repo.join("bisect.state")
324 p = repo.join("bisect.state")
324 if os.path.exists(p):
325 if os.path.exists(p):
325 os.unlink(p)
326 os.unlink(p)
326 return
327 return
327
328
328 state = hbisect.load_state(repo)
329 state = hbisect.load_state(repo)
329
330
330 if command:
331 if command:
331 commandpath = util.find_exe(command)
332 commandpath = util.find_exe(command)
332 if commandpath is None:
333 if commandpath is None:
333 raise util.Abort(_("cannot find executable: %s") % command)
334 raise util.Abort(_("cannot find executable: %s") % command)
334 changesets = 1
335 changesets = 1
335 try:
336 try:
336 while changesets:
337 while changesets:
337 # update state
338 # update state
338 status = subprocess.call([commandpath])
339 status = subprocess.call([commandpath])
339 if status == 125:
340 if status == 125:
340 transition = "skip"
341 transition = "skip"
341 elif status == 0:
342 elif status == 0:
342 transition = "good"
343 transition = "good"
343 # status < 0 means process was killed
344 # status < 0 means process was killed
344 elif status == 127:
345 elif status == 127:
345 raise util.Abort(_("failed to execute %s") % command)
346 raise util.Abort(_("failed to execute %s") % command)
346 elif status < 0:
347 elif status < 0:
347 raise util.Abort(_("%s killed") % command)
348 raise util.Abort(_("%s killed") % command)
348 else:
349 else:
349 transition = "bad"
350 transition = "bad"
350 ctx = repo[rev or '.']
351 ctx = repo[rev or '.']
351 state[transition].append(ctx.node())
352 state[transition].append(ctx.node())
352 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
353 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
353 check_state(state, interactive=False)
354 check_state(state, interactive=False)
354 # bisect
355 # bisect
355 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
356 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
356 # update to next check
357 # update to next check
357 cmdutil.bail_if_changed(repo)
358 cmdutil.bail_if_changed(repo)
358 hg.clean(repo, nodes[0], show_stats=False)
359 hg.clean(repo, nodes[0], show_stats=False)
359 finally:
360 finally:
360 hbisect.save_state(repo, state)
361 hbisect.save_state(repo, state)
361 return print_result(nodes, not status)
362 return print_result(nodes, not status)
362
363
363 # update state
364 # update state
364 node = repo.lookup(rev or '.')
365 node = repo.lookup(rev or '.')
365 if good:
366 if good:
366 state['good'].append(node)
367 state['good'].append(node)
367 elif bad:
368 elif bad:
368 state['bad'].append(node)
369 state['bad'].append(node)
369 elif skip:
370 elif skip:
370 state['skip'].append(node)
371 state['skip'].append(node)
371
372
372 hbisect.save_state(repo, state)
373 hbisect.save_state(repo, state)
373
374
374 if not check_state(state):
375 if not check_state(state):
375 return
376 return
376
377
377 # actually bisect
378 # actually bisect
378 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
379 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
379 if changesets == 0:
380 if changesets == 0:
380 print_result(nodes, good)
381 print_result(nodes, good)
381 else:
382 else:
382 assert len(nodes) == 1 # only a single node can be tested next
383 assert len(nodes) == 1 # only a single node can be tested next
383 node = nodes[0]
384 node = nodes[0]
384 # compute the approximate number of remaining tests
385 # compute the approximate number of remaining tests
385 tests, size = 0, 2
386 tests, size = 0, 2
386 while size <= changesets:
387 while size <= changesets:
387 tests, size = tests + 1, size * 2
388 tests, size = tests + 1, size * 2
388 rev = repo.changelog.rev(node)
389 rev = repo.changelog.rev(node)
389 ui.write(_("Testing changeset %d:%s "
390 ui.write(_("Testing changeset %d:%s "
390 "(%d changesets remaining, ~%d tests)\n")
391 "(%d changesets remaining, ~%d tests)\n")
391 % (rev, short(node), changesets, tests))
392 % (rev, short(node), changesets, tests))
392 if not noupdate:
393 if not noupdate:
393 cmdutil.bail_if_changed(repo)
394 cmdutil.bail_if_changed(repo)
394 return hg.clean(repo, node)
395 return hg.clean(repo, node)
395
396
396 def branch(ui, repo, label=None, **opts):
397 def branch(ui, repo, label=None, **opts):
397 """set or show the current branch name
398 """set or show the current branch name
398
399
399 With no argument, show the current branch name. With one argument, set the
400 With no argument, show the current branch name. With one argument, set the
400 working directory branch name (the branch will not exist in the repository
401 working directory branch name (the branch will not exist in the repository
401 until the next commit). Standard practice recommends that primary
402 until the next commit). Standard practice recommends that primary
402 development take place on the 'default' branch.
403 development take place on the 'default' branch.
403
404
404 Unless -f/--force is specified, branch will not let you set a branch name
405 Unless -f/--force is specified, branch will not let you set a branch name
405 that already exists, even if it's inactive.
406 that already exists, even if it's inactive.
406
407
407 Use -C/--clean to reset the working directory branch to that of the parent
408 Use -C/--clean to reset the working directory branch to that of the parent
408 of the working directory, negating a previous branch change.
409 of the working directory, negating a previous branch change.
409
410
410 Use the command 'hg update' to switch to an existing branch. Use 'hg
411 Use the command 'hg update' to switch to an existing branch. Use 'hg
411 commit --close-branch' to mark this branch as closed.
412 commit --close-branch' to mark this branch as closed.
412 """
413 """
413
414
414 if opts.get('clean'):
415 if opts.get('clean'):
415 label = repo[None].parents()[0].branch()
416 label = repo[None].parents()[0].branch()
416 repo.dirstate.setbranch(label)
417 repo.dirstate.setbranch(label)
417 ui.status(_('reset working directory to branch %s\n') % label)
418 ui.status(_('reset working directory to branch %s\n') % label)
418 elif label:
419 elif label:
419 if not opts.get('force') and label in repo.branchtags():
420 if not opts.get('force') and label in repo.branchtags():
420 if label not in [p.branch() for p in repo.parents()]:
421 if label not in [p.branch() for p in repo.parents()]:
421 raise util.Abort(_('a branch of the same name already exists'
422 raise util.Abort(_('a branch of the same name already exists'
422 ' (use --force to override)'))
423 ' (use --force to override)'))
423 repo.dirstate.setbranch(encoding.fromlocal(label))
424 repo.dirstate.setbranch(encoding.fromlocal(label))
424 ui.status(_('marked working directory as branch %s\n') % label)
425 ui.status(_('marked working directory as branch %s\n') % label)
425 else:
426 else:
426 ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
427 ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
427
428
428 def branches(ui, repo, active=False, closed=False):
429 def branches(ui, repo, active=False, closed=False):
429 """list repository named branches
430 """list repository named branches
430
431
431 List the repository's named branches, indicating which ones are inactive.
432 List the repository's named branches, indicating which ones are inactive.
432 If -c/--closed is specified, also list branches which have been marked
433 If -c/--closed is specified, also list branches which have been marked
433 closed (see hg commit --close-branch).
434 closed (see hg commit --close-branch).
434
435
435 If -a/--active is specified, only show active branches. A branch is
436 If -a/--active is specified, only show active branches. A branch is
436 considered active if it contains repository heads.
437 considered active if it contains repository heads.
437
438
438 Use the command 'hg update' to switch to an existing branch.
439 Use the command 'hg update' to switch to an existing branch.
439 """
440 """
440
441
441 hexfunc = ui.debugflag and hex or short
442 hexfunc = ui.debugflag and hex or short
442 activebranches = [encoding.tolocal(repo[n].branch())
443 activebranches = [encoding.tolocal(repo[n].branch())
443 for n in repo.heads()]
444 for n in repo.heads()]
444 def testactive(tag, node):
445 def testactive(tag, node):
445 realhead = tag in activebranches
446 realhead = tag in activebranches
446 open = node in repo.branchheads(tag, closed=False)
447 open = node in repo.branchheads(tag, closed=False)
447 return realhead and open
448 return realhead and open
448 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
449 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
449 for tag, node in repo.branchtags().items()],
450 for tag, node in repo.branchtags().items()],
450 reverse=True)
451 reverse=True)
451
452
452 for isactive, node, tag in branches:
453 for isactive, node, tag in branches:
453 if (not active) or isactive:
454 if (not active) or isactive:
454 if ui.quiet:
455 if ui.quiet:
455 ui.write("%s\n" % tag)
456 ui.write("%s\n" % tag)
456 else:
457 else:
457 hn = repo.lookup(node)
458 hn = repo.lookup(node)
458 if isactive:
459 if isactive:
459 notice = ''
460 notice = ''
460 elif hn not in repo.branchheads(tag, closed=False):
461 elif hn not in repo.branchheads(tag, closed=False):
461 if not closed:
462 if not closed:
462 continue
463 continue
463 notice = ' (closed)'
464 notice = ' (closed)'
464 else:
465 else:
465 notice = ' (inactive)'
466 notice = ' (inactive)'
466 rev = str(node).rjust(31 - encoding.colwidth(tag))
467 rev = str(node).rjust(31 - encoding.colwidth(tag))
467 data = tag, rev, hexfunc(hn), notice
468 data = tag, rev, hexfunc(hn), notice
468 ui.write("%s %s:%s%s\n" % data)
469 ui.write("%s %s:%s%s\n" % data)
469
470
470 def bundle(ui, repo, fname, dest=None, **opts):
471 def bundle(ui, repo, fname, dest=None, **opts):
471 """create a changegroup file
472 """create a changegroup file
472
473
473 Generate a compressed changegroup file collecting changesets not known to
474 Generate a compressed changegroup file collecting changesets not known to
474 be in another repository.
475 be in another repository.
475
476
476 If no destination repository is specified the destination is assumed to
477 If no destination repository is specified the destination is assumed to
477 have all the nodes specified by one or more --base parameters. To create a
478 have all the nodes specified by one or more --base parameters. To create a
478 bundle containing all changesets, use -a/--all (or --base null).
479 bundle containing all changesets, use -a/--all (or --base null).
479
480
480 You can change compression method with the -t/--type option. The available
481 You can change compression method with the -t/--type option. The available
481 compression methods are: none, bzip2, and gzip (by default, bundles are
482 compression methods are: none, bzip2, and gzip (by default, bundles are
482 compressed using bzip2).
483 compressed using bzip2).
483
484
484 The bundle file can then be transferred using conventional means and
485 The bundle file can then be transferred using conventional means and
485 applied to another repository with the unbundle or pull command. This is
486 applied to another repository with the unbundle or pull command. This is
486 useful when direct push and pull are not available or when exporting an
487 useful when direct push and pull are not available or when exporting an
487 entire repository is undesirable.
488 entire repository is undesirable.
488
489
489 Applying bundles preserves all changeset contents including permissions,
490 Applying bundles preserves all changeset contents including permissions,
490 copy/rename information, and revision history.
491 copy/rename information, and revision history.
491 """
492 """
492 revs = opts.get('rev') or None
493 revs = opts.get('rev') or None
493 if revs:
494 if revs:
494 revs = [repo.lookup(rev) for rev in revs]
495 revs = [repo.lookup(rev) for rev in revs]
495 if opts.get('all'):
496 if opts.get('all'):
496 base = ['null']
497 base = ['null']
497 else:
498 else:
498 base = opts.get('base')
499 base = opts.get('base')
499 if base:
500 if base:
500 if dest:
501 if dest:
501 raise util.Abort(_("--base is incompatible with specifying "
502 raise util.Abort(_("--base is incompatible with specifying "
502 "a destination"))
503 "a destination"))
503 base = [repo.lookup(rev) for rev in base]
504 base = [repo.lookup(rev) for rev in base]
504 # create the right base
505 # create the right base
505 # XXX: nodesbetween / changegroup* should be "fixed" instead
506 # XXX: nodesbetween / changegroup* should be "fixed" instead
506 o = []
507 o = []
507 has = set((nullid,))
508 has = set((nullid,))
508 for n in base:
509 for n in base:
509 has.update(repo.changelog.reachable(n))
510 has.update(repo.changelog.reachable(n))
510 if revs:
511 if revs:
511 visit = list(revs)
512 visit = list(revs)
512 else:
513 else:
513 visit = repo.changelog.heads()
514 visit = repo.changelog.heads()
514 seen = {}
515 seen = {}
515 while visit:
516 while visit:
516 n = visit.pop(0)
517 n = visit.pop(0)
517 parents = [p for p in repo.changelog.parents(n) if p not in has]
518 parents = [p for p in repo.changelog.parents(n) if p not in has]
518 if len(parents) == 0:
519 if len(parents) == 0:
519 o.insert(0, n)
520 o.insert(0, n)
520 else:
521 else:
521 for p in parents:
522 for p in parents:
522 if p not in seen:
523 if p not in seen:
523 seen[p] = 1
524 seen[p] = 1
524 visit.append(p)
525 visit.append(p)
525 else:
526 else:
526 dest, revs, checkout = hg.parseurl(
527 dest, revs, checkout = hg.parseurl(
527 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
528 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
528 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
529 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
529 o = repo.findoutgoing(other, force=opts.get('force'))
530 o = repo.findoutgoing(other, force=opts.get('force'))
530
531
531 if revs:
532 if revs:
532 cg = repo.changegroupsubset(o, revs, 'bundle')
533 cg = repo.changegroupsubset(o, revs, 'bundle')
533 else:
534 else:
534 cg = repo.changegroup(o, 'bundle')
535 cg = repo.changegroup(o, 'bundle')
535
536
536 bundletype = opts.get('type', 'bzip2').lower()
537 bundletype = opts.get('type', 'bzip2').lower()
537 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
538 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
538 bundletype = btypes.get(bundletype)
539 bundletype = btypes.get(bundletype)
539 if bundletype not in changegroup.bundletypes:
540 if bundletype not in changegroup.bundletypes:
540 raise util.Abort(_('unknown bundle type specified with --type'))
541 raise util.Abort(_('unknown bundle type specified with --type'))
541
542
542 changegroup.writebundle(cg, fname, bundletype)
543 changegroup.writebundle(cg, fname, bundletype)
543
544
544 def cat(ui, repo, file1, *pats, **opts):
545 def cat(ui, repo, file1, *pats, **opts):
545 """output the current or given revision of files
546 """output the current or given revision of files
546
547
547 Print the specified files as they were at the given revision. If no
548 Print the specified files as they were at the given revision. If no
548 revision is given, the parent of the working directory is used, or tip if
549 revision is given, the parent of the working directory is used, or tip if
549 no revision is checked out.
550 no revision is checked out.
550
551
551 Output may be to a file, in which case the name of the file is given using
552 Output may be to a file, in which case the name of the file is given using
552 a format string. The formatting rules are the same as for the export
553 a format string. The formatting rules are the same as for the export
553 command, with the following additions:
554 command, with the following additions::
554
555
555 %s basename of file being printed
556 %s basename of file being printed
556 %d dirname of file being printed, or '.' if in repository root
557 %d dirname of file being printed, or '.' if in repository root
557 %p root-relative path name of file being printed
558 %p root-relative path name of file being printed
558 """
559 """
559 ctx = repo[opts.get('rev')]
560 ctx = repo[opts.get('rev')]
560 err = 1
561 err = 1
561 m = cmdutil.match(repo, (file1,) + pats, opts)
562 m = cmdutil.match(repo, (file1,) + pats, opts)
562 for abs in ctx.walk(m):
563 for abs in ctx.walk(m):
563 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
564 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
564 data = ctx[abs].data()
565 data = ctx[abs].data()
565 if opts.get('decode'):
566 if opts.get('decode'):
566 data = repo.wwritedata(abs, data)
567 data = repo.wwritedata(abs, data)
567 fp.write(data)
568 fp.write(data)
568 err = 0
569 err = 0
569 return err
570 return err
570
571
571 def clone(ui, source, dest=None, **opts):
572 def clone(ui, source, dest=None, **opts):
572 """make a copy of an existing repository
573 """make a copy of an existing repository
573
574
574 Create a copy of an existing repository in a new directory.
575 Create a copy of an existing repository in a new directory.
575
576
576 If no destination directory name is specified, it defaults to the basename
577 If no destination directory name is specified, it defaults to the basename
577 of the source.
578 of the source.
578
579
579 The location of the source is added to the new repository's .hg/hgrc file,
580 The location of the source is added to the new repository's .hg/hgrc file,
580 as the default to be used for future pulls.
581 as the default to be used for future pulls.
581
582
582 If you use the -r/--rev option to clone up to a specific revision, no
583 If you use the -r/--rev option to clone up to a specific revision, no
583 subsequent revisions (including subsequent tags) will be present in the
584 subsequent revisions (including subsequent tags) will be present in the
584 cloned repository. This option implies --pull, even on local repositories.
585 cloned repository. This option implies --pull, even on local repositories.
585
586
586 By default, clone will check out the head of the 'default' branch. If the
587 By default, clone will check out the head of the 'default' branch. If the
587 -U/--noupdate option is used, the new clone will contain only a repository
588 -U/--noupdate option is used, the new clone will contain only a repository
588 (.hg) and no working copy (the working copy parent is the null revision).
589 (.hg) and no working copy (the working copy parent is the null revision).
589
590
590 See 'hg help urls' for valid source format details.
591 See 'hg help urls' for valid source format details.
591
592
592 It is possible to specify an ssh:// URL as the destination, but no
593 It is possible to specify an ssh:// URL as the destination, but no
593 .hg/hgrc and working directory will be created on the remote side. Please
594 .hg/hgrc and working directory will be created on the remote side. Please
594 see 'hg help urls' for important details about ssh:// URLs.
595 see 'hg help urls' for important details about ssh:// URLs.
595
596
596 For efficiency, hardlinks are used for cloning whenever the source and
597 For efficiency, hardlinks are used for cloning whenever the source and
597 destination are on the same filesystem (note this applies only to the
598 destination are on the same filesystem (note this applies only to the
598 repository data, not to the checked out files). Some filesystems, such as
599 repository data, not to the checked out files). Some filesystems, such as
599 AFS, implement hardlinking incorrectly, but do not report errors. In these
600 AFS, implement hardlinking incorrectly, but do not report errors. In these
600 cases, use the --pull option to avoid hardlinking.
601 cases, use the --pull option to avoid hardlinking.
601
602
602 In some cases, you can clone repositories and checked out files using full
603 In some cases, you can clone repositories and checked out files using full
603 hardlinks with
604 hardlinks with ::
604
605
605 $ cp -al REPO REPOCLONE
606 $ cp -al REPO REPOCLONE
606
607
607 This is the fastest way to clone, but it is not always safe. The operation
608 This is the fastest way to clone, but it is not always safe. The operation
608 is not atomic (making sure REPO is not modified during the operation is up
609 is not atomic (making sure REPO is not modified during the operation is up
609 to you) and you have to make sure your editor breaks hardlinks (Emacs and
610 to you) and you have to make sure your editor breaks hardlinks (Emacs and
610 most Linux Kernel tools do so). Also, this is not compatible with certain
611 most Linux Kernel tools do so). Also, this is not compatible with certain
611 extensions that place their metadata under the .hg directory, such as mq.
612 extensions that place their metadata under the .hg directory, such as mq.
612 """
613 """
613 hg.clone(cmdutil.remoteui(ui, opts), source, dest,
614 hg.clone(cmdutil.remoteui(ui, opts), source, dest,
614 pull=opts.get('pull'),
615 pull=opts.get('pull'),
615 stream=opts.get('uncompressed'),
616 stream=opts.get('uncompressed'),
616 rev=opts.get('rev'),
617 rev=opts.get('rev'),
617 update=not opts.get('noupdate'))
618 update=not opts.get('noupdate'))
618
619
619 def commit(ui, repo, *pats, **opts):
620 def commit(ui, repo, *pats, **opts):
620 """commit the specified files or all outstanding changes
621 """commit the specified files or all outstanding changes
621
622
622 Commit changes to the given files into the repository. Unlike a
623 Commit changes to the given files into the repository. Unlike a
623 centralized RCS, this operation is a local operation. See hg push for a
624 centralized RCS, this operation is a local operation. See hg push for a
624 way to actively distribute your changes.
625 way to actively distribute your changes.
625
626
626 If a list of files is omitted, all changes reported by "hg status" will be
627 If a list of files is omitted, all changes reported by "hg status" will be
627 committed.
628 committed.
628
629
629 If you are committing the result of a merge, do not provide any filenames
630 If you are committing the result of a merge, do not provide any filenames
630 or -I/-X filters.
631 or -I/-X filters.
631
632
632 If no commit message is specified, the configured editor is started to
633 If no commit message is specified, the configured editor is started to
633 prompt you for a message.
634 prompt you for a message.
634
635
635 See 'hg help dates' for a list of formats valid for -d/--date.
636 See 'hg help dates' for a list of formats valid for -d/--date.
636 """
637 """
637 extra = {}
638 extra = {}
638 if opts.get('close_branch'):
639 if opts.get('close_branch'):
639 extra['close'] = 1
640 extra['close'] = 1
640 e = cmdutil.commiteditor
641 e = cmdutil.commiteditor
641 if opts.get('force_editor'):
642 if opts.get('force_editor'):
642 e = cmdutil.commitforceeditor
643 e = cmdutil.commitforceeditor
643
644
644 def commitfunc(ui, repo, message, match, opts):
645 def commitfunc(ui, repo, message, match, opts):
645 return repo.commit(message, opts.get('user'), opts.get('date'), match,
646 return repo.commit(message, opts.get('user'), opts.get('date'), match,
646 editor=e, extra=extra)
647 editor=e, extra=extra)
647
648
648 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
649 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
649 if not node:
650 if not node:
650 ui.status(_("nothing changed\n"))
651 ui.status(_("nothing changed\n"))
651 return
652 return
652 cl = repo.changelog
653 cl = repo.changelog
653 rev = cl.rev(node)
654 rev = cl.rev(node)
654 parents = cl.parentrevs(rev)
655 parents = cl.parentrevs(rev)
655 if rev - 1 in parents:
656 if rev - 1 in parents:
656 # one of the parents was the old tip
657 # one of the parents was the old tip
657 pass
658 pass
658 elif (parents == (nullrev, nullrev) or
659 elif (parents == (nullrev, nullrev) or
659 len(cl.heads(cl.node(parents[0]))) > 1 and
660 len(cl.heads(cl.node(parents[0]))) > 1 and
660 (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
661 (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
661 ui.status(_('created new head\n'))
662 ui.status(_('created new head\n'))
662
663
663 if ui.debugflag:
664 if ui.debugflag:
664 ui.write(_('committed changeset %d:%s\n') % (rev,hex(node)))
665 ui.write(_('committed changeset %d:%s\n') % (rev,hex(node)))
665 elif ui.verbose:
666 elif ui.verbose:
666 ui.write(_('committed changeset %d:%s\n') % (rev,short(node)))
667 ui.write(_('committed changeset %d:%s\n') % (rev,short(node)))
667
668
668 def copy(ui, repo, *pats, **opts):
669 def copy(ui, repo, *pats, **opts):
669 """mark files as copied for the next commit
670 """mark files as copied for the next commit
670
671
671 Mark dest as having copies of source files. If dest is a directory, copies
672 Mark dest as having copies of source files. If dest is a directory, copies
672 are put in that directory. If dest is a file, the source must be a single
673 are put in that directory. If dest is a file, the source must be a single
673 file.
674 file.
674
675
675 By default, this command copies the contents of files as they exist in the
676 By default, this command copies the contents of files as they exist in the
676 working directory. If invoked with -A/--after, the operation is recorded,
677 working directory. If invoked with -A/--after, the operation is recorded,
677 but no copying is performed.
678 but no copying is performed.
678
679
679 This command takes effect with the next commit. To undo a copy before
680 This command takes effect with the next commit. To undo a copy before
680 that, see hg revert.
681 that, see hg revert.
681 """
682 """
682 wlock = repo.wlock(False)
683 wlock = repo.wlock(False)
683 try:
684 try:
684 return cmdutil.copy(ui, repo, pats, opts)
685 return cmdutil.copy(ui, repo, pats, opts)
685 finally:
686 finally:
686 wlock.release()
687 wlock.release()
687
688
688 def debugancestor(ui, repo, *args):
689 def debugancestor(ui, repo, *args):
689 """find the ancestor revision of two revisions in a given index"""
690 """find the ancestor revision of two revisions in a given index"""
690 if len(args) == 3:
691 if len(args) == 3:
691 index, rev1, rev2 = args
692 index, rev1, rev2 = args
692 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
693 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
693 lookup = r.lookup
694 lookup = r.lookup
694 elif len(args) == 2:
695 elif len(args) == 2:
695 if not repo:
696 if not repo:
696 raise util.Abort(_("There is no Mercurial repository here "
697 raise util.Abort(_("There is no Mercurial repository here "
697 "(.hg not found)"))
698 "(.hg not found)"))
698 rev1, rev2 = args
699 rev1, rev2 = args
699 r = repo.changelog
700 r = repo.changelog
700 lookup = repo.lookup
701 lookup = repo.lookup
701 else:
702 else:
702 raise util.Abort(_('either two or three arguments required'))
703 raise util.Abort(_('either two or three arguments required'))
703 a = r.ancestor(lookup(rev1), lookup(rev2))
704 a = r.ancestor(lookup(rev1), lookup(rev2))
704 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
705 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
705
706
706 def debugcommands(ui, cmd='', *args):
707 def debugcommands(ui, cmd='', *args):
707 for cmd, vals in sorted(table.iteritems()):
708 for cmd, vals in sorted(table.iteritems()):
708 cmd = cmd.split('|')[0].strip('^')
709 cmd = cmd.split('|')[0].strip('^')
709 opts = ', '.join([i[1] for i in vals[1]])
710 opts = ', '.join([i[1] for i in vals[1]])
710 ui.write('%s: %s\n' % (cmd, opts))
711 ui.write('%s: %s\n' % (cmd, opts))
711
712
712 def debugcomplete(ui, cmd='', **opts):
713 def debugcomplete(ui, cmd='', **opts):
713 """returns the completion list associated with the given command"""
714 """returns the completion list associated with the given command"""
714
715
715 if opts.get('options'):
716 if opts.get('options'):
716 options = []
717 options = []
717 otables = [globalopts]
718 otables = [globalopts]
718 if cmd:
719 if cmd:
719 aliases, entry = cmdutil.findcmd(cmd, table, False)
720 aliases, entry = cmdutil.findcmd(cmd, table, False)
720 otables.append(entry[1])
721 otables.append(entry[1])
721 for t in otables:
722 for t in otables:
722 for o in t:
723 for o in t:
723 if o[0]:
724 if o[0]:
724 options.append('-%s' % o[0])
725 options.append('-%s' % o[0])
725 options.append('--%s' % o[1])
726 options.append('--%s' % o[1])
726 ui.write("%s\n" % "\n".join(options))
727 ui.write("%s\n" % "\n".join(options))
727 return
728 return
728
729
729 cmdlist = cmdutil.findpossible(cmd, table)
730 cmdlist = cmdutil.findpossible(cmd, table)
730 if ui.verbose:
731 if ui.verbose:
731 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
732 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
732 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
733 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
733
734
734 def debugfsinfo(ui, path = "."):
735 def debugfsinfo(ui, path = "."):
735 open('.debugfsinfo', 'w').write('')
736 open('.debugfsinfo', 'w').write('')
736 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
737 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
737 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
738 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
738 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
739 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
739 and 'yes' or 'no'))
740 and 'yes' or 'no'))
740 os.unlink('.debugfsinfo')
741 os.unlink('.debugfsinfo')
741
742
742 def debugrebuildstate(ui, repo, rev="tip"):
743 def debugrebuildstate(ui, repo, rev="tip"):
743 """rebuild the dirstate as it would look like for the given revision"""
744 """rebuild the dirstate as it would look like for the given revision"""
744 ctx = repo[rev]
745 ctx = repo[rev]
745 wlock = repo.wlock()
746 wlock = repo.wlock()
746 try:
747 try:
747 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
748 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
748 finally:
749 finally:
749 wlock.release()
750 wlock.release()
750
751
751 def debugcheckstate(ui, repo):
752 def debugcheckstate(ui, repo):
752 """validate the correctness of the current dirstate"""
753 """validate the correctness of the current dirstate"""
753 parent1, parent2 = repo.dirstate.parents()
754 parent1, parent2 = repo.dirstate.parents()
754 m1 = repo[parent1].manifest()
755 m1 = repo[parent1].manifest()
755 m2 = repo[parent2].manifest()
756 m2 = repo[parent2].manifest()
756 errors = 0
757 errors = 0
757 for f in repo.dirstate:
758 for f in repo.dirstate:
758 state = repo.dirstate[f]
759 state = repo.dirstate[f]
759 if state in "nr" and f not in m1:
760 if state in "nr" and f not in m1:
760 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
761 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
761 errors += 1
762 errors += 1
762 if state in "a" and f in m1:
763 if state in "a" and f in m1:
763 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
764 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
764 errors += 1
765 errors += 1
765 if state in "m" and f not in m1 and f not in m2:
766 if state in "m" and f not in m1 and f not in m2:
766 ui.warn(_("%s in state %s, but not in either manifest\n") %
767 ui.warn(_("%s in state %s, but not in either manifest\n") %
767 (f, state))
768 (f, state))
768 errors += 1
769 errors += 1
769 for f in m1:
770 for f in m1:
770 state = repo.dirstate[f]
771 state = repo.dirstate[f]
771 if state not in "nrm":
772 if state not in "nrm":
772 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
773 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
773 errors += 1
774 errors += 1
774 if errors:
775 if errors:
775 error = _(".hg/dirstate inconsistent with current parent's manifest")
776 error = _(".hg/dirstate inconsistent with current parent's manifest")
776 raise util.Abort(error)
777 raise util.Abort(error)
777
778
778 def showconfig(ui, repo, *values, **opts):
779 def showconfig(ui, repo, *values, **opts):
779 """show combined config settings from all hgrc files
780 """show combined config settings from all hgrc files
780
781
781 With no arguments, print names and values of all config items.
782 With no arguments, print names and values of all config items.
782
783
783 With one argument of the form section.name, print just the value of that
784 With one argument of the form section.name, print just the value of that
784 config item.
785 config item.
785
786
786 With multiple arguments, print names and values of all config items with
787 With multiple arguments, print names and values of all config items with
787 matching section names.
788 matching section names.
788
789
789 With --debug, the source (filename and line number) is printed for each
790 With --debug, the source (filename and line number) is printed for each
790 config item.
791 config item.
791 """
792 """
792
793
793 untrusted = bool(opts.get('untrusted'))
794 untrusted = bool(opts.get('untrusted'))
794 if values:
795 if values:
795 if len([v for v in values if '.' in v]) > 1:
796 if len([v for v in values if '.' in v]) > 1:
796 raise util.Abort(_('only one config item permitted'))
797 raise util.Abort(_('only one config item permitted'))
797 for section, name, value in ui.walkconfig(untrusted=untrusted):
798 for section, name, value in ui.walkconfig(untrusted=untrusted):
798 sectname = section + '.' + name
799 sectname = section + '.' + name
799 if values:
800 if values:
800 for v in values:
801 for v in values:
801 if v == section:
802 if v == section:
802 ui.debug('%s: ' %
803 ui.debug('%s: ' %
803 ui.configsource(section, name, untrusted))
804 ui.configsource(section, name, untrusted))
804 ui.write('%s=%s\n' % (sectname, value))
805 ui.write('%s=%s\n' % (sectname, value))
805 elif v == sectname:
806 elif v == sectname:
806 ui.debug('%s: ' %
807 ui.debug('%s: ' %
807 ui.configsource(section, name, untrusted))
808 ui.configsource(section, name, untrusted))
808 ui.write(value, '\n')
809 ui.write(value, '\n')
809 else:
810 else:
810 ui.debug('%s: ' %
811 ui.debug('%s: ' %
811 ui.configsource(section, name, untrusted))
812 ui.configsource(section, name, untrusted))
812 ui.write('%s=%s\n' % (sectname, value))
813 ui.write('%s=%s\n' % (sectname, value))
813
814
814 def debugsetparents(ui, repo, rev1, rev2=None):
815 def debugsetparents(ui, repo, rev1, rev2=None):
815 """manually set the parents of the current working directory
816 """manually set the parents of the current working directory
816
817
817 This is useful for writing repository conversion tools, but should be used
818 This is useful for writing repository conversion tools, but should be used
818 with care.
819 with care.
819 """
820 """
820
821
821 if not rev2:
822 if not rev2:
822 rev2 = hex(nullid)
823 rev2 = hex(nullid)
823
824
824 wlock = repo.wlock()
825 wlock = repo.wlock()
825 try:
826 try:
826 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
827 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
827 finally:
828 finally:
828 wlock.release()
829 wlock.release()
829
830
830 def debugstate(ui, repo, nodates=None):
831 def debugstate(ui, repo, nodates=None):
831 """show the contents of the current dirstate"""
832 """show the contents of the current dirstate"""
832 timestr = ""
833 timestr = ""
833 showdate = not nodates
834 showdate = not nodates
834 for file_, ent in sorted(repo.dirstate._map.iteritems()):
835 for file_, ent in sorted(repo.dirstate._map.iteritems()):
835 if showdate:
836 if showdate:
836 if ent[3] == -1:
837 if ent[3] == -1:
837 # Pad or slice to locale representation
838 # Pad or slice to locale representation
838 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(0)))
839 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(0)))
839 timestr = 'unset'
840 timestr = 'unset'
840 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
841 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
841 else:
842 else:
842 timestr = time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(ent[3]))
843 timestr = time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(ent[3]))
843 if ent[1] & 020000:
844 if ent[1] & 020000:
844 mode = 'lnk'
845 mode = 'lnk'
845 else:
846 else:
846 mode = '%3o' % (ent[1] & 0777)
847 mode = '%3o' % (ent[1] & 0777)
847 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
848 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
848 for f in repo.dirstate.copies():
849 for f in repo.dirstate.copies():
849 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
850 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
850
851
851 def debugsub(ui, repo, rev=None):
852 def debugsub(ui, repo, rev=None):
852 if rev == '':
853 if rev == '':
853 rev = None
854 rev = None
854 for k,v in sorted(repo[rev].substate.items()):
855 for k,v in sorted(repo[rev].substate.items()):
855 ui.write('path %s\n' % k)
856 ui.write('path %s\n' % k)
856 ui.write(' source %s\n' % v[0])
857 ui.write(' source %s\n' % v[0])
857 ui.write(' revision %s\n' % v[1])
858 ui.write(' revision %s\n' % v[1])
858
859
859 def debugdata(ui, file_, rev):
860 def debugdata(ui, file_, rev):
860 """dump the contents of a data file revision"""
861 """dump the contents of a data file revision"""
861 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
862 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
862 try:
863 try:
863 ui.write(r.revision(r.lookup(rev)))
864 ui.write(r.revision(r.lookup(rev)))
864 except KeyError:
865 except KeyError:
865 raise util.Abort(_('invalid revision identifier %s') % rev)
866 raise util.Abort(_('invalid revision identifier %s') % rev)
866
867
867 def debugdate(ui, date, range=None, **opts):
868 def debugdate(ui, date, range=None, **opts):
868 """parse and display a date"""
869 """parse and display a date"""
869 if opts["extended"]:
870 if opts["extended"]:
870 d = util.parsedate(date, util.extendeddateformats)
871 d = util.parsedate(date, util.extendeddateformats)
871 else:
872 else:
872 d = util.parsedate(date)
873 d = util.parsedate(date)
873 ui.write("internal: %s %s\n" % d)
874 ui.write("internal: %s %s\n" % d)
874 ui.write("standard: %s\n" % util.datestr(d))
875 ui.write("standard: %s\n" % util.datestr(d))
875 if range:
876 if range:
876 m = util.matchdate(range)
877 m = util.matchdate(range)
877 ui.write("match: %s\n" % m(d[0]))
878 ui.write("match: %s\n" % m(d[0]))
878
879
879 def debugindex(ui, file_):
880 def debugindex(ui, file_):
880 """dump the contents of an index file"""
881 """dump the contents of an index file"""
881 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
882 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
882 ui.write(" rev offset length base linkrev"
883 ui.write(" rev offset length base linkrev"
883 " nodeid p1 p2\n")
884 " nodeid p1 p2\n")
884 for i in r:
885 for i in r:
885 node = r.node(i)
886 node = r.node(i)
886 try:
887 try:
887 pp = r.parents(node)
888 pp = r.parents(node)
888 except:
889 except:
889 pp = [nullid, nullid]
890 pp = [nullid, nullid]
890 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
891 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
891 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
892 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
892 short(node), short(pp[0]), short(pp[1])))
893 short(node), short(pp[0]), short(pp[1])))
893
894
894 def debugindexdot(ui, file_):
895 def debugindexdot(ui, file_):
895 """dump an index DAG as a graphviz dot file"""
896 """dump an index DAG as a graphviz dot file"""
896 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
897 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
897 ui.write("digraph G {\n")
898 ui.write("digraph G {\n")
898 for i in r:
899 for i in r:
899 node = r.node(i)
900 node = r.node(i)
900 pp = r.parents(node)
901 pp = r.parents(node)
901 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
902 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
902 if pp[1] != nullid:
903 if pp[1] != nullid:
903 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
904 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
904 ui.write("}\n")
905 ui.write("}\n")
905
906
906 def debuginstall(ui):
907 def debuginstall(ui):
907 '''test Mercurial installation'''
908 '''test Mercurial installation'''
908
909
909 def writetemp(contents):
910 def writetemp(contents):
910 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
911 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
911 f = os.fdopen(fd, "wb")
912 f = os.fdopen(fd, "wb")
912 f.write(contents)
913 f.write(contents)
913 f.close()
914 f.close()
914 return name
915 return name
915
916
916 problems = 0
917 problems = 0
917
918
918 # encoding
919 # encoding
919 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
920 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
920 try:
921 try:
921 encoding.fromlocal("test")
922 encoding.fromlocal("test")
922 except util.Abort, inst:
923 except util.Abort, inst:
923 ui.write(" %s\n" % inst)
924 ui.write(" %s\n" % inst)
924 ui.write(_(" (check that your locale is properly set)\n"))
925 ui.write(_(" (check that your locale is properly set)\n"))
925 problems += 1
926 problems += 1
926
927
927 # compiled modules
928 # compiled modules
928 ui.status(_("Checking extensions...\n"))
929 ui.status(_("Checking extensions...\n"))
929 try:
930 try:
930 import bdiff, mpatch, base85
931 import bdiff, mpatch, base85
931 except Exception, inst:
932 except Exception, inst:
932 ui.write(" %s\n" % inst)
933 ui.write(" %s\n" % inst)
933 ui.write(_(" One or more extensions could not be found"))
934 ui.write(_(" One or more extensions could not be found"))
934 ui.write(_(" (check that you compiled the extensions)\n"))
935 ui.write(_(" (check that you compiled the extensions)\n"))
935 problems += 1
936 problems += 1
936
937
937 # templates
938 # templates
938 ui.status(_("Checking templates...\n"))
939 ui.status(_("Checking templates...\n"))
939 try:
940 try:
940 import templater
941 import templater
941 templater.templater(templater.templatepath("map-cmdline.default"))
942 templater.templater(templater.templatepath("map-cmdline.default"))
942 except Exception, inst:
943 except Exception, inst:
943 ui.write(" %s\n" % inst)
944 ui.write(" %s\n" % inst)
944 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
945 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
945 problems += 1
946 problems += 1
946
947
947 # patch
948 # patch
948 ui.status(_("Checking patch...\n"))
949 ui.status(_("Checking patch...\n"))
949 patchproblems = 0
950 patchproblems = 0
950 a = "1\n2\n3\n4\n"
951 a = "1\n2\n3\n4\n"
951 b = "1\n2\n3\ninsert\n4\n"
952 b = "1\n2\n3\ninsert\n4\n"
952 fa = writetemp(a)
953 fa = writetemp(a)
953 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
954 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
954 os.path.basename(fa))
955 os.path.basename(fa))
955 fd = writetemp(d)
956 fd = writetemp(d)
956
957
957 files = {}
958 files = {}
958 try:
959 try:
959 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
960 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
960 except util.Abort, e:
961 except util.Abort, e:
961 ui.write(_(" patch call failed:\n"))
962 ui.write(_(" patch call failed:\n"))
962 ui.write(" " + str(e) + "\n")
963 ui.write(" " + str(e) + "\n")
963 patchproblems += 1
964 patchproblems += 1
964 else:
965 else:
965 if list(files) != [os.path.basename(fa)]:
966 if list(files) != [os.path.basename(fa)]:
966 ui.write(_(" unexpected patch output!\n"))
967 ui.write(_(" unexpected patch output!\n"))
967 patchproblems += 1
968 patchproblems += 1
968 a = open(fa).read()
969 a = open(fa).read()
969 if a != b:
970 if a != b:
970 ui.write(_(" patch test failed!\n"))
971 ui.write(_(" patch test failed!\n"))
971 patchproblems += 1
972 patchproblems += 1
972
973
973 if patchproblems:
974 if patchproblems:
974 if ui.config('ui', 'patch'):
975 if ui.config('ui', 'patch'):
975 ui.write(_(" (Current patch tool may be incompatible with patch,"
976 ui.write(_(" (Current patch tool may be incompatible with patch,"
976 " or misconfigured. Please check your .hgrc file)\n"))
977 " or misconfigured. Please check your .hgrc file)\n"))
977 else:
978 else:
978 ui.write(_(" Internal patcher failure, please report this error"
979 ui.write(_(" Internal patcher failure, please report this error"
979 " to http://mercurial.selenic.com/bts/\n"))
980 " to http://mercurial.selenic.com/bts/\n"))
980 problems += patchproblems
981 problems += patchproblems
981
982
982 os.unlink(fa)
983 os.unlink(fa)
983 os.unlink(fd)
984 os.unlink(fd)
984
985
985 # editor
986 # editor
986 ui.status(_("Checking commit editor...\n"))
987 ui.status(_("Checking commit editor...\n"))
987 editor = ui.geteditor()
988 editor = ui.geteditor()
988 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
989 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
989 if not cmdpath:
990 if not cmdpath:
990 if editor == 'vi':
991 if editor == 'vi':
991 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
992 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
992 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
993 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
993 else:
994 else:
994 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
995 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
995 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
996 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
996 problems += 1
997 problems += 1
997
998
998 # check username
999 # check username
999 ui.status(_("Checking username...\n"))
1000 ui.status(_("Checking username...\n"))
1000 user = os.environ.get("HGUSER")
1001 user = os.environ.get("HGUSER")
1001 if user is None:
1002 if user is None:
1002 user = ui.config("ui", "username")
1003 user = ui.config("ui", "username")
1003 if user is None:
1004 if user is None:
1004 user = os.environ.get("EMAIL")
1005 user = os.environ.get("EMAIL")
1005 if not user:
1006 if not user:
1006 ui.warn(" ")
1007 ui.warn(" ")
1007 ui.username()
1008 ui.username()
1008 ui.write(_(" (specify a username in your .hgrc file)\n"))
1009 ui.write(_(" (specify a username in your .hgrc file)\n"))
1009
1010
1010 if not problems:
1011 if not problems:
1011 ui.status(_("No problems detected\n"))
1012 ui.status(_("No problems detected\n"))
1012 else:
1013 else:
1013 ui.write(_("%s problems detected,"
1014 ui.write(_("%s problems detected,"
1014 " please check your install!\n") % problems)
1015 " please check your install!\n") % problems)
1015
1016
1016 return problems
1017 return problems
1017
1018
1018 def debugrename(ui, repo, file1, *pats, **opts):
1019 def debugrename(ui, repo, file1, *pats, **opts):
1019 """dump rename information"""
1020 """dump rename information"""
1020
1021
1021 ctx = repo[opts.get('rev')]
1022 ctx = repo[opts.get('rev')]
1022 m = cmdutil.match(repo, (file1,) + pats, opts)
1023 m = cmdutil.match(repo, (file1,) + pats, opts)
1023 for abs in ctx.walk(m):
1024 for abs in ctx.walk(m):
1024 fctx = ctx[abs]
1025 fctx = ctx[abs]
1025 o = fctx.filelog().renamed(fctx.filenode())
1026 o = fctx.filelog().renamed(fctx.filenode())
1026 rel = m.rel(abs)
1027 rel = m.rel(abs)
1027 if o:
1028 if o:
1028 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1029 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1029 else:
1030 else:
1030 ui.write(_("%s not renamed\n") % rel)
1031 ui.write(_("%s not renamed\n") % rel)
1031
1032
1032 def debugwalk(ui, repo, *pats, **opts):
1033 def debugwalk(ui, repo, *pats, **opts):
1033 """show how files match on given patterns"""
1034 """show how files match on given patterns"""
1034 m = cmdutil.match(repo, pats, opts)
1035 m = cmdutil.match(repo, pats, opts)
1035 items = list(repo.walk(m))
1036 items = list(repo.walk(m))
1036 if not items:
1037 if not items:
1037 return
1038 return
1038 fmt = 'f %%-%ds %%-%ds %%s' % (
1039 fmt = 'f %%-%ds %%-%ds %%s' % (
1039 max([len(abs) for abs in items]),
1040 max([len(abs) for abs in items]),
1040 max([len(m.rel(abs)) for abs in items]))
1041 max([len(m.rel(abs)) for abs in items]))
1041 for abs in items:
1042 for abs in items:
1042 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1043 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1043 ui.write("%s\n" % line.rstrip())
1044 ui.write("%s\n" % line.rstrip())
1044
1045
1045 def diff(ui, repo, *pats, **opts):
1046 def diff(ui, repo, *pats, **opts):
1046 """diff repository (or selected files)
1047 """diff repository (or selected files)
1047
1048
1048 Show differences between revisions for the specified files.
1049 Show differences between revisions for the specified files.
1049
1050
1050 Differences between files are shown using the unified diff format.
1051 Differences between files are shown using the unified diff format.
1051
1052
1052 NOTE: diff may generate unexpected results for merges, as it will default
1053 NOTE: diff may generate unexpected results for merges, as it will default
1053 to comparing against the working directory's first parent changeset if no
1054 to comparing against the working directory's first parent changeset if no
1054 revisions are specified.
1055 revisions are specified.
1055
1056
1056 When two revision arguments are given, then changes are shown between
1057 When two revision arguments are given, then changes are shown between
1057 those revisions. If only one revision is specified then that revision is
1058 those revisions. If only one revision is specified then that revision is
1058 compared to the working directory, and, when no revisions are specified,
1059 compared to the working directory, and, when no revisions are specified,
1059 the working directory files are compared to its parent.
1060 the working directory files are compared to its parent.
1060
1061
1061 Without the -a/--text option, diff will avoid generating diffs of files it
1062 Without the -a/--text option, diff will avoid generating diffs of files it
1062 detects as binary. With -a, diff will generate a diff anyway, probably
1063 detects as binary. With -a, diff will generate a diff anyway, probably
1063 with undesirable results.
1064 with undesirable results.
1064
1065
1065 Use the -g/--git option to generate diffs in the git extended diff format.
1066 Use the -g/--git option to generate diffs in the git extended diff format.
1066 For more information, read 'hg help diffs'.
1067 For more information, read 'hg help diffs'.
1067 """
1068 """
1068
1069
1069 revs = opts.get('rev')
1070 revs = opts.get('rev')
1070 change = opts.get('change')
1071 change = opts.get('change')
1071
1072
1072 if revs and change:
1073 if revs and change:
1073 msg = _('cannot specify --rev and --change at the same time')
1074 msg = _('cannot specify --rev and --change at the same time')
1074 raise util.Abort(msg)
1075 raise util.Abort(msg)
1075 elif change:
1076 elif change:
1076 node2 = repo.lookup(change)
1077 node2 = repo.lookup(change)
1077 node1 = repo[node2].parents()[0].node()
1078 node1 = repo[node2].parents()[0].node()
1078 else:
1079 else:
1079 node1, node2 = cmdutil.revpair(repo, revs)
1080 node1, node2 = cmdutil.revpair(repo, revs)
1080
1081
1081 m = cmdutil.match(repo, pats, opts)
1082 m = cmdutil.match(repo, pats, opts)
1082 it = patch.diff(repo, node1, node2, match=m, opts=patch.diffopts(ui, opts))
1083 it = patch.diff(repo, node1, node2, match=m, opts=patch.diffopts(ui, opts))
1083 for chunk in it:
1084 for chunk in it:
1084 ui.write(chunk)
1085 ui.write(chunk)
1085
1086
1086 def export(ui, repo, *changesets, **opts):
1087 def export(ui, repo, *changesets, **opts):
1087 """dump the header and diffs for one or more changesets
1088 """dump the header and diffs for one or more changesets
1088
1089
1089 Print the changeset header and diffs for one or more revisions.
1090 Print the changeset header and diffs for one or more revisions.
1090
1091
1091 The information shown in the changeset header is: author, changeset hash,
1092 The information shown in the changeset header is: author, changeset hash,
1092 parent(s) and commit comment.
1093 parent(s) and commit comment.
1093
1094
1094 NOTE: export may generate unexpected diff output for merge changesets, as
1095 NOTE: export may generate unexpected diff output for merge changesets, as
1095 it will compare the merge changeset against its first parent only.
1096 it will compare the merge changeset against its first parent only.
1096
1097
1097 Output may be to a file, in which case the name of the file is given using
1098 Output may be to a file, in which case the name of the file is given using
1098 a format string. The formatting rules are as follows:
1099 a format string. The formatting rules are as follows::
1099
1100
1100 %% literal "%" character
1101 %% literal "%" character
1101 %H changeset hash (40 bytes of hexadecimal)
1102 %H changeset hash (40 bytes of hexadecimal)
1102 %N number of patches being generated
1103 %N number of patches being generated
1103 %R changeset revision number
1104 %R changeset revision number
1104 %b basename of the exporting repository
1105 %b basename of the exporting repository
1105 %h short-form changeset hash (12 bytes of hexadecimal)
1106 %h short-form changeset hash (12 bytes of hexadecimal)
1106 %n zero-padded sequence number, starting at 1
1107 %n zero-padded sequence number, starting at 1
1107 %r zero-padded changeset revision number
1108 %r zero-padded changeset revision number
1108
1109
1109 Without the -a/--text option, export will avoid generating diffs of files
1110 Without the -a/--text option, export will avoid generating diffs of files
1110 it detects as binary. With -a, export will generate a diff anyway,
1111 it detects as binary. With -a, export will generate a diff anyway,
1111 probably with undesirable results.
1112 probably with undesirable results.
1112
1113
1113 Use the -g/--git option to generate diffs in the git extended diff format.
1114 Use the -g/--git option to generate diffs in the git extended diff format.
1114 See 'hg help diffs' for more information.
1115 See 'hg help diffs' for more information.
1115
1116
1116 With the --switch-parent option, the diff will be against the second
1117 With the --switch-parent option, the diff will be against the second
1117 parent. It can be useful to review a merge.
1118 parent. It can be useful to review a merge.
1118 """
1119 """
1119 if not changesets:
1120 if not changesets:
1120 raise util.Abort(_("export requires at least one changeset"))
1121 raise util.Abort(_("export requires at least one changeset"))
1121 revs = cmdutil.revrange(repo, changesets)
1122 revs = cmdutil.revrange(repo, changesets)
1122 if len(revs) > 1:
1123 if len(revs) > 1:
1123 ui.note(_('exporting patches:\n'))
1124 ui.note(_('exporting patches:\n'))
1124 else:
1125 else:
1125 ui.note(_('exporting patch:\n'))
1126 ui.note(_('exporting patch:\n'))
1126 patch.export(repo, revs, template=opts.get('output'),
1127 patch.export(repo, revs, template=opts.get('output'),
1127 switch_parent=opts.get('switch_parent'),
1128 switch_parent=opts.get('switch_parent'),
1128 opts=patch.diffopts(ui, opts))
1129 opts=patch.diffopts(ui, opts))
1129
1130
1130 def forget(ui, repo, *pats, **opts):
1131 def forget(ui, repo, *pats, **opts):
1131 """forget the specified files on the next commit
1132 """forget the specified files on the next commit
1132
1133
1133 Mark the specified files so they will no longer be tracked after the next
1134 Mark the specified files so they will no longer be tracked after the next
1134 commit.
1135 commit.
1135
1136
1136 This only removes files from the current branch, not from the entire
1137 This only removes files from the current branch, not from the entire
1137 project history, and it does not delete them from the working directory.
1138 project history, and it does not delete them from the working directory.
1138
1139
1139 To undo a forget before the next commit, see hg add.
1140 To undo a forget before the next commit, see hg add.
1140 """
1141 """
1141
1142
1142 if not pats:
1143 if not pats:
1143 raise util.Abort(_('no files specified'))
1144 raise util.Abort(_('no files specified'))
1144
1145
1145 m = cmdutil.match(repo, pats, opts)
1146 m = cmdutil.match(repo, pats, opts)
1146 s = repo.status(match=m, clean=True)
1147 s = repo.status(match=m, clean=True)
1147 forget = sorted(s[0] + s[1] + s[3] + s[6])
1148 forget = sorted(s[0] + s[1] + s[3] + s[6])
1148
1149
1149 for f in m.files():
1150 for f in m.files():
1150 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1151 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1151 ui.warn(_('not removing %s: file is already untracked\n')
1152 ui.warn(_('not removing %s: file is already untracked\n')
1152 % m.rel(f))
1153 % m.rel(f))
1153
1154
1154 for f in forget:
1155 for f in forget:
1155 if ui.verbose or not m.exact(f):
1156 if ui.verbose or not m.exact(f):
1156 ui.status(_('removing %s\n') % m.rel(f))
1157 ui.status(_('removing %s\n') % m.rel(f))
1157
1158
1158 repo.remove(forget, unlink=False)
1159 repo.remove(forget, unlink=False)
1159
1160
1160 def grep(ui, repo, pattern, *pats, **opts):
1161 def grep(ui, repo, pattern, *pats, **opts):
1161 """search for a pattern in specified files and revisions
1162 """search for a pattern in specified files and revisions
1162
1163
1163 Search revisions of files for a regular expression.
1164 Search revisions of files for a regular expression.
1164
1165
1165 This command behaves differently than Unix grep. It only accepts
1166 This command behaves differently than Unix grep. It only accepts
1166 Python/Perl regexps. It searches repository history, not the working
1167 Python/Perl regexps. It searches repository history, not the working
1167 directory. It always prints the revision number in which a match appears.
1168 directory. It always prints the revision number in which a match appears.
1168
1169
1169 By default, grep only prints output for the first revision of a file in
1170 By default, grep only prints output for the first revision of a file in
1170 which it finds a match. To get it to print every revision that contains a
1171 which it finds a match. To get it to print every revision that contains a
1171 change in match status ("-" for a match that becomes a non-match, or "+"
1172 change in match status ("-" for a match that becomes a non-match, or "+"
1172 for a non-match that becomes a match), use the --all flag.
1173 for a non-match that becomes a match), use the --all flag.
1173 """
1174 """
1174 reflags = 0
1175 reflags = 0
1175 if opts.get('ignore_case'):
1176 if opts.get('ignore_case'):
1176 reflags |= re.I
1177 reflags |= re.I
1177 try:
1178 try:
1178 regexp = re.compile(pattern, reflags)
1179 regexp = re.compile(pattern, reflags)
1179 except Exception, inst:
1180 except Exception, inst:
1180 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1181 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1181 return None
1182 return None
1182 sep, eol = ':', '\n'
1183 sep, eol = ':', '\n'
1183 if opts.get('print0'):
1184 if opts.get('print0'):
1184 sep = eol = '\0'
1185 sep = eol = '\0'
1185
1186
1186 getfile = util.lrucachefunc(repo.file)
1187 getfile = util.lrucachefunc(repo.file)
1187
1188
1188 def matchlines(body):
1189 def matchlines(body):
1189 begin = 0
1190 begin = 0
1190 linenum = 0
1191 linenum = 0
1191 while True:
1192 while True:
1192 match = regexp.search(body, begin)
1193 match = regexp.search(body, begin)
1193 if not match:
1194 if not match:
1194 break
1195 break
1195 mstart, mend = match.span()
1196 mstart, mend = match.span()
1196 linenum += body.count('\n', begin, mstart) + 1
1197 linenum += body.count('\n', begin, mstart) + 1
1197 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1198 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1198 begin = body.find('\n', mend) + 1 or len(body)
1199 begin = body.find('\n', mend) + 1 or len(body)
1199 lend = begin - 1
1200 lend = begin - 1
1200 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1201 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1201
1202
1202 class linestate(object):
1203 class linestate(object):
1203 def __init__(self, line, linenum, colstart, colend):
1204 def __init__(self, line, linenum, colstart, colend):
1204 self.line = line
1205 self.line = line
1205 self.linenum = linenum
1206 self.linenum = linenum
1206 self.colstart = colstart
1207 self.colstart = colstart
1207 self.colend = colend
1208 self.colend = colend
1208
1209
1209 def __hash__(self):
1210 def __hash__(self):
1210 return hash((self.linenum, self.line))
1211 return hash((self.linenum, self.line))
1211
1212
1212 def __eq__(self, other):
1213 def __eq__(self, other):
1213 return self.line == other.line
1214 return self.line == other.line
1214
1215
1215 matches = {}
1216 matches = {}
1216 copies = {}
1217 copies = {}
1217 def grepbody(fn, rev, body):
1218 def grepbody(fn, rev, body):
1218 matches[rev].setdefault(fn, [])
1219 matches[rev].setdefault(fn, [])
1219 m = matches[rev][fn]
1220 m = matches[rev][fn]
1220 for lnum, cstart, cend, line in matchlines(body):
1221 for lnum, cstart, cend, line in matchlines(body):
1221 s = linestate(line, lnum, cstart, cend)
1222 s = linestate(line, lnum, cstart, cend)
1222 m.append(s)
1223 m.append(s)
1223
1224
1224 def difflinestates(a, b):
1225 def difflinestates(a, b):
1225 sm = difflib.SequenceMatcher(None, a, b)
1226 sm = difflib.SequenceMatcher(None, a, b)
1226 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1227 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1227 if tag == 'insert':
1228 if tag == 'insert':
1228 for i in xrange(blo, bhi):
1229 for i in xrange(blo, bhi):
1229 yield ('+', b[i])
1230 yield ('+', b[i])
1230 elif tag == 'delete':
1231 elif tag == 'delete':
1231 for i in xrange(alo, ahi):
1232 for i in xrange(alo, ahi):
1232 yield ('-', a[i])
1233 yield ('-', a[i])
1233 elif tag == 'replace':
1234 elif tag == 'replace':
1234 for i in xrange(alo, ahi):
1235 for i in xrange(alo, ahi):
1235 yield ('-', a[i])
1236 yield ('-', a[i])
1236 for i in xrange(blo, bhi):
1237 for i in xrange(blo, bhi):
1237 yield ('+', b[i])
1238 yield ('+', b[i])
1238
1239
1239 def display(fn, r, pstates, states):
1240 def display(fn, r, pstates, states):
1240 datefunc = ui.quiet and util.shortdate or util.datestr
1241 datefunc = ui.quiet and util.shortdate or util.datestr
1241 found = False
1242 found = False
1242 filerevmatches = {}
1243 filerevmatches = {}
1243 if opts.get('all'):
1244 if opts.get('all'):
1244 iter = difflinestates(pstates, states)
1245 iter = difflinestates(pstates, states)
1245 else:
1246 else:
1246 iter = [('', l) for l in states]
1247 iter = [('', l) for l in states]
1247 for change, l in iter:
1248 for change, l in iter:
1248 cols = [fn, str(r)]
1249 cols = [fn, str(r)]
1249 if opts.get('line_number'):
1250 if opts.get('line_number'):
1250 cols.append(str(l.linenum))
1251 cols.append(str(l.linenum))
1251 if opts.get('all'):
1252 if opts.get('all'):
1252 cols.append(change)
1253 cols.append(change)
1253 if opts.get('user'):
1254 if opts.get('user'):
1254 cols.append(ui.shortuser(get(r)[1]))
1255 cols.append(ui.shortuser(get(r)[1]))
1255 if opts.get('date'):
1256 if opts.get('date'):
1256 cols.append(datefunc(get(r)[2]))
1257 cols.append(datefunc(get(r)[2]))
1257 if opts.get('files_with_matches'):
1258 if opts.get('files_with_matches'):
1258 c = (fn, r)
1259 c = (fn, r)
1259 if c in filerevmatches:
1260 if c in filerevmatches:
1260 continue
1261 continue
1261 filerevmatches[c] = 1
1262 filerevmatches[c] = 1
1262 else:
1263 else:
1263 cols.append(l.line)
1264 cols.append(l.line)
1264 ui.write(sep.join(cols), eol)
1265 ui.write(sep.join(cols), eol)
1265 found = True
1266 found = True
1266 return found
1267 return found
1267
1268
1268 skip = {}
1269 skip = {}
1269 revfiles = {}
1270 revfiles = {}
1270 get = util.cachefunc(lambda r: repo[r].changeset())
1271 get = util.cachefunc(lambda r: repo[r].changeset())
1271 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1272 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1272 found = False
1273 found = False
1273 follow = opts.get('follow')
1274 follow = opts.get('follow')
1274 for st, rev, fns in changeiter:
1275 for st, rev, fns in changeiter:
1275 if st == 'window':
1276 if st == 'window':
1276 matches.clear()
1277 matches.clear()
1277 revfiles.clear()
1278 revfiles.clear()
1278 elif st == 'add':
1279 elif st == 'add':
1279 ctx = repo[rev]
1280 ctx = repo[rev]
1280 pctx = ctx.parents()[0]
1281 pctx = ctx.parents()[0]
1281 parent = pctx.rev()
1282 parent = pctx.rev()
1282 matches.setdefault(rev, {})
1283 matches.setdefault(rev, {})
1283 matches.setdefault(parent, {})
1284 matches.setdefault(parent, {})
1284 files = revfiles.setdefault(rev, [])
1285 files = revfiles.setdefault(rev, [])
1285 for fn in fns:
1286 for fn in fns:
1286 flog = getfile(fn)
1287 flog = getfile(fn)
1287 try:
1288 try:
1288 fnode = ctx.filenode(fn)
1289 fnode = ctx.filenode(fn)
1289 except error.LookupError:
1290 except error.LookupError:
1290 continue
1291 continue
1291
1292
1292 copied = flog.renamed(fnode)
1293 copied = flog.renamed(fnode)
1293 copy = follow and copied and copied[0]
1294 copy = follow and copied and copied[0]
1294 if copy:
1295 if copy:
1295 copies.setdefault(rev, {})[fn] = copy
1296 copies.setdefault(rev, {})[fn] = copy
1296 if fn in skip:
1297 if fn in skip:
1297 if copy:
1298 if copy:
1298 skip[copy] = True
1299 skip[copy] = True
1299 continue
1300 continue
1300 files.append(fn)
1301 files.append(fn)
1301
1302
1302 if not matches[rev].has_key(fn):
1303 if not matches[rev].has_key(fn):
1303 grepbody(fn, rev, flog.read(fnode))
1304 grepbody(fn, rev, flog.read(fnode))
1304
1305
1305 pfn = copy or fn
1306 pfn = copy or fn
1306 if not matches[parent].has_key(pfn):
1307 if not matches[parent].has_key(pfn):
1307 try:
1308 try:
1308 fnode = pctx.filenode(pfn)
1309 fnode = pctx.filenode(pfn)
1309 grepbody(pfn, parent, flog.read(fnode))
1310 grepbody(pfn, parent, flog.read(fnode))
1310 except error.LookupError:
1311 except error.LookupError:
1311 pass
1312 pass
1312 elif st == 'iter':
1313 elif st == 'iter':
1313 parent = repo[rev].parents()[0].rev()
1314 parent = repo[rev].parents()[0].rev()
1314 for fn in sorted(revfiles.get(rev, [])):
1315 for fn in sorted(revfiles.get(rev, [])):
1315 states = matches[rev][fn]
1316 states = matches[rev][fn]
1316 copy = copies.get(rev, {}).get(fn)
1317 copy = copies.get(rev, {}).get(fn)
1317 if fn in skip:
1318 if fn in skip:
1318 if copy:
1319 if copy:
1319 skip[copy] = True
1320 skip[copy] = True
1320 continue
1321 continue
1321 pstates = matches.get(parent, {}).get(copy or fn, [])
1322 pstates = matches.get(parent, {}).get(copy or fn, [])
1322 if pstates or states:
1323 if pstates or states:
1323 r = display(fn, rev, pstates, states)
1324 r = display(fn, rev, pstates, states)
1324 found = found or r
1325 found = found or r
1325 if r and not opts.get('all'):
1326 if r and not opts.get('all'):
1326 skip[fn] = True
1327 skip[fn] = True
1327 if copy:
1328 if copy:
1328 skip[copy] = True
1329 skip[copy] = True
1329
1330
1330 def heads(ui, repo, *branchrevs, **opts):
1331 def heads(ui, repo, *branchrevs, **opts):
1331 """show current repository heads or show branch heads
1332 """show current repository heads or show branch heads
1332
1333
1333 With no arguments, show all repository head changesets.
1334 With no arguments, show all repository head changesets.
1334
1335
1335 Repository "heads" are changesets that don't have child changesets. They
1336 Repository "heads" are changesets that don't have child changesets. They
1336 are where development generally takes place and are the usual targets for
1337 are where development generally takes place and are the usual targets for
1337 update and merge operations.
1338 update and merge operations.
1338
1339
1339 If one or more REV is given, the "branch heads" will be shown for the
1340 If one or more REV is given, the "branch heads" will be shown for the
1340 named branch associated with that revision. The name of the branch is
1341 named branch associated with that revision. The name of the branch is
1341 called the revision's branch tag.
1342 called the revision's branch tag.
1342
1343
1343 Branch heads are revisions on a given named branch that do not have any
1344 Branch heads are revisions on a given named branch that do not have any
1344 descendants on the same branch. A branch head could be a true head or it
1345 descendants on the same branch. A branch head could be a true head or it
1345 could be the last changeset on a branch before a new branch was created.
1346 could be the last changeset on a branch before a new branch was created.
1346 If none of the branch heads are true heads, the branch is considered
1347 If none of the branch heads are true heads, the branch is considered
1347 inactive. If -c/--closed is specified, also show branch heads marked
1348 inactive. If -c/--closed is specified, also show branch heads marked
1348 closed (see hg commit --close-branch).
1349 closed (see hg commit --close-branch).
1349
1350
1350 If STARTREV is specified only those heads (or branch heads) that are
1351 If STARTREV is specified only those heads (or branch heads) that are
1351 descendants of STARTREV will be displayed.
1352 descendants of STARTREV will be displayed.
1352 """
1353 """
1353 if opts.get('rev'):
1354 if opts.get('rev'):
1354 start = repo.lookup(opts['rev'])
1355 start = repo.lookup(opts['rev'])
1355 else:
1356 else:
1356 start = None
1357 start = None
1357 closed = opts.get('closed')
1358 closed = opts.get('closed')
1358 hideinactive, _heads = opts.get('active'), None
1359 hideinactive, _heads = opts.get('active'), None
1359 if not branchrevs:
1360 if not branchrevs:
1360 # Assume we're looking repo-wide heads if no revs were specified.
1361 # Assume we're looking repo-wide heads if no revs were specified.
1361 heads = repo.heads(start)
1362 heads = repo.heads(start)
1362 else:
1363 else:
1363 if hideinactive:
1364 if hideinactive:
1364 _heads = repo.heads(start)
1365 _heads = repo.heads(start)
1365 heads = []
1366 heads = []
1366 visitedset = set()
1367 visitedset = set()
1367 for branchrev in branchrevs:
1368 for branchrev in branchrevs:
1368 branch = repo[branchrev].branch()
1369 branch = repo[branchrev].branch()
1369 if branch in visitedset:
1370 if branch in visitedset:
1370 continue
1371 continue
1371 visitedset.add(branch)
1372 visitedset.add(branch)
1372 bheads = repo.branchheads(branch, start, closed=closed)
1373 bheads = repo.branchheads(branch, start, closed=closed)
1373 if not bheads:
1374 if not bheads:
1374 if not opts.get('rev'):
1375 if not opts.get('rev'):
1375 ui.warn(_("no open branch heads on branch %s\n") % branch)
1376 ui.warn(_("no open branch heads on branch %s\n") % branch)
1376 elif branch != branchrev:
1377 elif branch != branchrev:
1377 ui.warn(_("no changes on branch %s containing %s are "
1378 ui.warn(_("no changes on branch %s containing %s are "
1378 "reachable from %s\n")
1379 "reachable from %s\n")
1379 % (branch, branchrev, opts.get('rev')))
1380 % (branch, branchrev, opts.get('rev')))
1380 else:
1381 else:
1381 ui.warn(_("no changes on branch %s are reachable from %s\n")
1382 ui.warn(_("no changes on branch %s are reachable from %s\n")
1382 % (branch, opts.get('rev')))
1383 % (branch, opts.get('rev')))
1383 if hideinactive:
1384 if hideinactive:
1384 bheads = [bhead for bhead in bheads if bhead in _heads]
1385 bheads = [bhead for bhead in bheads if bhead in _heads]
1385 heads.extend(bheads)
1386 heads.extend(bheads)
1386 if not heads:
1387 if not heads:
1387 return 1
1388 return 1
1388 displayer = cmdutil.show_changeset(ui, repo, opts)
1389 displayer = cmdutil.show_changeset(ui, repo, opts)
1389 for n in heads:
1390 for n in heads:
1390 displayer.show(repo[n])
1391 displayer.show(repo[n])
1391
1392
1392 def help_(ui, name=None, with_version=False):
1393 def help_(ui, name=None, with_version=False):
1393 """show help for a given topic or a help overview
1394 """show help for a given topic or a help overview
1394
1395
1395 With no arguments, print a list of commands with short help messages.
1396 With no arguments, print a list of commands with short help messages.
1396
1397
1397 Given a topic, extension, or command name, print help for that topic.
1398 Given a topic, extension, or command name, print help for that topic.
1398 """
1399 """
1399 option_lists = []
1400 option_lists = []
1401 textwidth = util.termwidth() - 2
1400
1402
1401 def addglobalopts(aliases):
1403 def addglobalopts(aliases):
1402 if ui.verbose:
1404 if ui.verbose:
1403 option_lists.append((_("global options:"), globalopts))
1405 option_lists.append((_("global options:"), globalopts))
1404 if name == 'shortlist':
1406 if name == 'shortlist':
1405 option_lists.append((_('use "hg help" for the full list '
1407 option_lists.append((_('use "hg help" for the full list '
1406 'of commands'), ()))
1408 'of commands'), ()))
1407 else:
1409 else:
1408 if name == 'shortlist':
1410 if name == 'shortlist':
1409 msg = _('use "hg help" for the full list of commands '
1411 msg = _('use "hg help" for the full list of commands '
1410 'or "hg -v" for details')
1412 'or "hg -v" for details')
1411 elif aliases:
1413 elif aliases:
1412 msg = _('use "hg -v help%s" to show aliases and '
1414 msg = _('use "hg -v help%s" to show aliases and '
1413 'global options') % (name and " " + name or "")
1415 'global options') % (name and " " + name or "")
1414 else:
1416 else:
1415 msg = _('use "hg -v help %s" to show global options') % name
1417 msg = _('use "hg -v help %s" to show global options') % name
1416 option_lists.append((msg, ()))
1418 option_lists.append((msg, ()))
1417
1419
1418 def helpcmd(name):
1420 def helpcmd(name):
1419 if with_version:
1421 if with_version:
1420 version_(ui)
1422 version_(ui)
1421 ui.write('\n')
1423 ui.write('\n')
1422
1424
1423 try:
1425 try:
1424 aliases, i = cmdutil.findcmd(name, table, False)
1426 aliases, i = cmdutil.findcmd(name, table, False)
1425 except error.AmbiguousCommand, inst:
1427 except error.AmbiguousCommand, inst:
1426 # py3k fix: except vars can't be used outside the scope of the
1428 # py3k fix: except vars can't be used outside the scope of the
1427 # except block, nor can be used inside a lambda. python issue4617
1429 # except block, nor can be used inside a lambda. python issue4617
1428 prefix = inst.args[0]
1430 prefix = inst.args[0]
1429 select = lambda c: c.lstrip('^').startswith(prefix)
1431 select = lambda c: c.lstrip('^').startswith(prefix)
1430 helplist(_('list of commands:\n\n'), select)
1432 helplist(_('list of commands:\n\n'), select)
1431 return
1433 return
1432
1434
1433 # synopsis
1435 # synopsis
1434 if len(i) > 2:
1436 if len(i) > 2:
1435 if i[2].startswith('hg'):
1437 if i[2].startswith('hg'):
1436 ui.write("%s\n" % i[2])
1438 ui.write("%s\n" % i[2])
1437 else:
1439 else:
1438 ui.write('hg %s %s\n' % (aliases[0], i[2]))
1440 ui.write('hg %s %s\n' % (aliases[0], i[2]))
1439 else:
1441 else:
1440 ui.write('hg %s\n' % aliases[0])
1442 ui.write('hg %s\n' % aliases[0])
1441
1443
1442 # aliases
1444 # aliases
1443 if not ui.quiet and len(aliases) > 1:
1445 if not ui.quiet and len(aliases) > 1:
1444 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1446 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1445
1447
1446 # description
1448 # description
1447 doc = gettext(i[0].__doc__)
1449 doc = gettext(i[0].__doc__)
1448 if not doc:
1450 if not doc:
1449 doc = _("(no help text available)")
1451 doc = _("(no help text available)")
1450 if ui.quiet:
1452 if ui.quiet:
1451 doc = doc.splitlines()[0]
1453 doc = doc.splitlines()[0]
1452 ui.write("\n%s\n" % doc.rstrip())
1454 ui.write("\n%s\n" % minirst.format(doc, textwidth))
1453
1455
1454 if not ui.quiet:
1456 if not ui.quiet:
1455 # options
1457 # options
1456 if i[1]:
1458 if i[1]:
1457 option_lists.append((_("options:\n"), i[1]))
1459 option_lists.append((_("options:\n"), i[1]))
1458
1460
1459 addglobalopts(False)
1461 addglobalopts(False)
1460
1462
1461 def helplist(header, select=None):
1463 def helplist(header, select=None):
1462 h = {}
1464 h = {}
1463 cmds = {}
1465 cmds = {}
1464 for c, e in table.iteritems():
1466 for c, e in table.iteritems():
1465 f = c.split("|", 1)[0]
1467 f = c.split("|", 1)[0]
1466 if select and not select(f):
1468 if select and not select(f):
1467 continue
1469 continue
1468 if (not select and name != 'shortlist' and
1470 if (not select and name != 'shortlist' and
1469 e[0].__module__ != __name__):
1471 e[0].__module__ != __name__):
1470 continue
1472 continue
1471 if name == "shortlist" and not f.startswith("^"):
1473 if name == "shortlist" and not f.startswith("^"):
1472 continue
1474 continue
1473 f = f.lstrip("^")
1475 f = f.lstrip("^")
1474 if not ui.debugflag and f.startswith("debug"):
1476 if not ui.debugflag and f.startswith("debug"):
1475 continue
1477 continue
1476 doc = e[0].__doc__
1478 doc = e[0].__doc__
1477 if doc and 'DEPRECATED' in doc and not ui.verbose:
1479 if doc and 'DEPRECATED' in doc and not ui.verbose:
1478 continue
1480 continue
1479 doc = gettext(doc)
1481 doc = gettext(doc)
1480 if not doc:
1482 if not doc:
1481 doc = _("(no help text available)")
1483 doc = _("(no help text available)")
1482 h[f] = doc.splitlines()[0].rstrip()
1484 h[f] = doc.splitlines()[0].rstrip()
1483 cmds[f] = c.lstrip("^")
1485 cmds[f] = c.lstrip("^")
1484
1486
1485 if not h:
1487 if not h:
1486 ui.status(_('no commands defined\n'))
1488 ui.status(_('no commands defined\n'))
1487 return
1489 return
1488
1490
1489 ui.status(header)
1491 ui.status(header)
1490 fns = sorted(h)
1492 fns = sorted(h)
1491 m = max(map(len, fns))
1493 m = max(map(len, fns))
1492 for f in fns:
1494 for f in fns:
1493 if ui.verbose:
1495 if ui.verbose:
1494 commands = cmds[f].replace("|",", ")
1496 commands = cmds[f].replace("|",", ")
1495 ui.write(" %s:\n %s\n"%(commands, h[f]))
1497 ui.write(" %s:\n %s\n"%(commands, h[f]))
1496 else:
1498 else:
1497 ui.write(' %-*s %s\n' % (m, f, util.wrap(h[f], m + 4)))
1499 ui.write(' %-*s %s\n' % (m, f, util.wrap(h[f], m + 4)))
1498
1500
1499 if name != 'shortlist':
1501 if name != 'shortlist':
1500 exts, maxlength = extensions.enabled()
1502 exts, maxlength = extensions.enabled()
1501 ui.write(help.listexts(_('enabled extensions:'), exts, maxlength))
1503 text = help.listexts(_('enabled extensions:'), exts, maxlength)
1504 if text:
1505 ui.write("\n%s\n" % minirst.format(text, textwidth))
1502
1506
1503 if not ui.quiet:
1507 if not ui.quiet:
1504 addglobalopts(True)
1508 addglobalopts(True)
1505
1509
1506 def helptopic(name):
1510 def helptopic(name):
1507 for names, header, doc in help.helptable:
1511 for names, header, doc in help.helptable:
1508 if name in names:
1512 if name in names:
1509 break
1513 break
1510 else:
1514 else:
1511 raise error.UnknownCommand(name)
1515 raise error.UnknownCommand(name)
1512
1516
1513 # description
1517 # description
1514 if not doc:
1518 if not doc:
1515 doc = _("(no help text available)")
1519 doc = _("(no help text available)")
1516 if hasattr(doc, '__call__'):
1520 if hasattr(doc, '__call__'):
1517 doc = doc()
1521 doc = doc()
1518
1522
1519 ui.write("%s\n" % header)
1523 ui.write("%s\n\n" % header)
1520 ui.write("%s\n" % doc.rstrip())
1524 ui.write("%s\n" % minirst.format(doc, textwidth))
1521
1525
1522 def helpext(name):
1526 def helpext(name):
1523 try:
1527 try:
1524 mod = extensions.find(name)
1528 mod = extensions.find(name)
1525 except KeyError:
1529 except KeyError:
1526 raise error.UnknownCommand(name)
1530 raise error.UnknownCommand(name)
1527
1531
1528 doc = gettext(mod.__doc__) or _('no help text available')
1532 doc = gettext(mod.__doc__) or _('no help text available')
1529 doc = doc.splitlines()
1533 head, tail = doc.split('\n', 1)
1530 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1534 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
1531 for d in doc[1:]:
1535 if tail:
1532 ui.write(d, '\n')
1536 ui.write(minirst.format(tail, textwidth))
1533
1537 ui.status('\n\n')
1534 ui.status('\n')
1535
1538
1536 try:
1539 try:
1537 ct = mod.cmdtable
1540 ct = mod.cmdtable
1538 except AttributeError:
1541 except AttributeError:
1539 ct = {}
1542 ct = {}
1540
1543
1541 modcmds = set([c.split('|', 1)[0] for c in ct])
1544 modcmds = set([c.split('|', 1)[0] for c in ct])
1542 helplist(_('list of commands:\n\n'), modcmds.__contains__)
1545 helplist(_('list of commands:\n\n'), modcmds.__contains__)
1543
1546
1544 if name and name != 'shortlist':
1547 if name and name != 'shortlist':
1545 i = None
1548 i = None
1546 for f in (helptopic, helpcmd, helpext):
1549 for f in (helptopic, helpcmd, helpext):
1547 try:
1550 try:
1548 f(name)
1551 f(name)
1549 i = None
1552 i = None
1550 break
1553 break
1551 except error.UnknownCommand, inst:
1554 except error.UnknownCommand, inst:
1552 i = inst
1555 i = inst
1553 if i:
1556 if i:
1554 raise i
1557 raise i
1555
1558
1556 else:
1559 else:
1557 # program name
1560 # program name
1558 if ui.verbose or with_version:
1561 if ui.verbose or with_version:
1559 version_(ui)
1562 version_(ui)
1560 else:
1563 else:
1561 ui.status(_("Mercurial Distributed SCM\n"))
1564 ui.status(_("Mercurial Distributed SCM\n"))
1562 ui.status('\n')
1565 ui.status('\n')
1563
1566
1564 # list of commands
1567 # list of commands
1565 if name == "shortlist":
1568 if name == "shortlist":
1566 header = _('basic commands:\n\n')
1569 header = _('basic commands:\n\n')
1567 else:
1570 else:
1568 header = _('list of commands:\n\n')
1571 header = _('list of commands:\n\n')
1569
1572
1570 helplist(header)
1573 helplist(header)
1571
1574
1572 # list all option lists
1575 # list all option lists
1573 opt_output = []
1576 opt_output = []
1574 for title, options in option_lists:
1577 for title, options in option_lists:
1575 opt_output.append(("\n%s" % title, None))
1578 opt_output.append(("\n%s" % title, None))
1576 for shortopt, longopt, default, desc in options:
1579 for shortopt, longopt, default, desc in options:
1577 if "DEPRECATED" in desc and not ui.verbose: continue
1580 if "DEPRECATED" in desc and not ui.verbose: continue
1578 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1581 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1579 longopt and " --%s" % longopt),
1582 longopt and " --%s" % longopt),
1580 "%s%s" % (desc,
1583 "%s%s" % (desc,
1581 default
1584 default
1582 and _(" (default: %s)") % default
1585 and _(" (default: %s)") % default
1583 or "")))
1586 or "")))
1584
1587
1585 if not name:
1588 if not name:
1586 ui.write(_("\nadditional help topics:\n\n"))
1589 ui.write(_("\nadditional help topics:\n\n"))
1587 topics = []
1590 topics = []
1588 for names, header, doc in help.helptable:
1591 for names, header, doc in help.helptable:
1589 names = [(-len(name), name) for name in names]
1592 names = [(-len(name), name) for name in names]
1590 names.sort()
1593 names.sort()
1591 topics.append((names[0][1], header))
1594 topics.append((names[0][1], header))
1592 topics_len = max([len(s[0]) for s in topics])
1595 topics_len = max([len(s[0]) for s in topics])
1593 for t, desc in topics:
1596 for t, desc in topics:
1594 ui.write(" %-*s %s\n" % (topics_len, t, desc))
1597 ui.write(" %-*s %s\n" % (topics_len, t, desc))
1595
1598
1596 if opt_output:
1599 if opt_output:
1597 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1600 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1598 for first, second in opt_output:
1601 for first, second in opt_output:
1599 if second:
1602 if second:
1600 second = util.wrap(second, opts_len + 3)
1603 second = util.wrap(second, opts_len + 3)
1601 ui.write(" %-*s %s\n" % (opts_len, first, second))
1604 ui.write(" %-*s %s\n" % (opts_len, first, second))
1602 else:
1605 else:
1603 ui.write("%s\n" % first)
1606 ui.write("%s\n" % first)
1604
1607
1605 def identify(ui, repo, source=None,
1608 def identify(ui, repo, source=None,
1606 rev=None, num=None, id=None, branch=None, tags=None):
1609 rev=None, num=None, id=None, branch=None, tags=None):
1607 """identify the working copy or specified revision
1610 """identify the working copy or specified revision
1608
1611
1609 With no revision, print a summary of the current state of the repository.
1612 With no revision, print a summary of the current state of the repository.
1610
1613
1611 Specifying a path to a repository root or Mercurial bundle will cause
1614 Specifying a path to a repository root or Mercurial bundle will cause
1612 lookup to operate on that repository/bundle.
1615 lookup to operate on that repository/bundle.
1613
1616
1614 This summary identifies the repository state using one or two parent hash
1617 This summary identifies the repository state using one or two parent hash
1615 identifiers, followed by a "+" if there are uncommitted changes in the
1618 identifiers, followed by a "+" if there are uncommitted changes in the
1616 working directory, a list of tags for this revision and a branch name for
1619 working directory, a list of tags for this revision and a branch name for
1617 non-default branches.
1620 non-default branches.
1618 """
1621 """
1619
1622
1620 if not repo and not source:
1623 if not repo and not source:
1621 raise util.Abort(_("There is no Mercurial repository here "
1624 raise util.Abort(_("There is no Mercurial repository here "
1622 "(.hg not found)"))
1625 "(.hg not found)"))
1623
1626
1624 hexfunc = ui.debugflag and hex or short
1627 hexfunc = ui.debugflag and hex or short
1625 default = not (num or id or branch or tags)
1628 default = not (num or id or branch or tags)
1626 output = []
1629 output = []
1627
1630
1628 revs = []
1631 revs = []
1629 if source:
1632 if source:
1630 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1633 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1631 repo = hg.repository(ui, source)
1634 repo = hg.repository(ui, source)
1632
1635
1633 if not repo.local():
1636 if not repo.local():
1634 if not rev and revs:
1637 if not rev and revs:
1635 rev = revs[0]
1638 rev = revs[0]
1636 if not rev:
1639 if not rev:
1637 rev = "tip"
1640 rev = "tip"
1638 if num or branch or tags:
1641 if num or branch or tags:
1639 raise util.Abort(
1642 raise util.Abort(
1640 "can't query remote revision number, branch, or tags")
1643 "can't query remote revision number, branch, or tags")
1641 output = [hexfunc(repo.lookup(rev))]
1644 output = [hexfunc(repo.lookup(rev))]
1642 elif not rev:
1645 elif not rev:
1643 ctx = repo[None]
1646 ctx = repo[None]
1644 parents = ctx.parents()
1647 parents = ctx.parents()
1645 changed = False
1648 changed = False
1646 if default or id or num:
1649 if default or id or num:
1647 changed = ctx.files() + ctx.deleted()
1650 changed = ctx.files() + ctx.deleted()
1648 if default or id:
1651 if default or id:
1649 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1652 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1650 (changed) and "+" or "")]
1653 (changed) and "+" or "")]
1651 if num:
1654 if num:
1652 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1655 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1653 (changed) and "+" or ""))
1656 (changed) and "+" or ""))
1654 else:
1657 else:
1655 ctx = repo[rev]
1658 ctx = repo[rev]
1656 if default or id:
1659 if default or id:
1657 output = [hexfunc(ctx.node())]
1660 output = [hexfunc(ctx.node())]
1658 if num:
1661 if num:
1659 output.append(str(ctx.rev()))
1662 output.append(str(ctx.rev()))
1660
1663
1661 if repo.local() and default and not ui.quiet:
1664 if repo.local() and default and not ui.quiet:
1662 b = encoding.tolocal(ctx.branch())
1665 b = encoding.tolocal(ctx.branch())
1663 if b != 'default':
1666 if b != 'default':
1664 output.append("(%s)" % b)
1667 output.append("(%s)" % b)
1665
1668
1666 # multiple tags for a single parent separated by '/'
1669 # multiple tags for a single parent separated by '/'
1667 t = "/".join(ctx.tags())
1670 t = "/".join(ctx.tags())
1668 if t:
1671 if t:
1669 output.append(t)
1672 output.append(t)
1670
1673
1671 if branch:
1674 if branch:
1672 output.append(encoding.tolocal(ctx.branch()))
1675 output.append(encoding.tolocal(ctx.branch()))
1673
1676
1674 if tags:
1677 if tags:
1675 output.extend(ctx.tags())
1678 output.extend(ctx.tags())
1676
1679
1677 ui.write("%s\n" % ' '.join(output))
1680 ui.write("%s\n" % ' '.join(output))
1678
1681
1679 def import_(ui, repo, patch1, *patches, **opts):
1682 def import_(ui, repo, patch1, *patches, **opts):
1680 """import an ordered set of patches
1683 """import an ordered set of patches
1681
1684
1682 Import a list of patches and commit them individually.
1685 Import a list of patches and commit them individually.
1683
1686
1684 If there are outstanding changes in the working directory, import will
1687 If there are outstanding changes in the working directory, import will
1685 abort unless given the -f/--force flag.
1688 abort unless given the -f/--force flag.
1686
1689
1687 You can import a patch straight from a mail message. Even patches as
1690 You can import a patch straight from a mail message. Even patches as
1688 attachments work (to use the body part, it must have type text/plain or
1691 attachments work (to use the body part, it must have type text/plain or
1689 text/x-patch). From and Subject headers of email message are used as
1692 text/x-patch). From and Subject headers of email message are used as
1690 default committer and commit message. All text/plain body parts before
1693 default committer and commit message. All text/plain body parts before
1691 first diff are added to commit message.
1694 first diff are added to commit message.
1692
1695
1693 If the imported patch was generated by hg export, user and description
1696 If the imported patch was generated by hg export, user and description
1694 from patch override values from message headers and body. Values given on
1697 from patch override values from message headers and body. Values given on
1695 command line with -m/--message and -u/--user override these.
1698 command line with -m/--message and -u/--user override these.
1696
1699
1697 If --exact is specified, import will set the working directory to the
1700 If --exact is specified, import will set the working directory to the
1698 parent of each patch before applying it, and will abort if the resulting
1701 parent of each patch before applying it, and will abort if the resulting
1699 changeset has a different ID than the one recorded in the patch. This may
1702 changeset has a different ID than the one recorded in the patch. This may
1700 happen due to character set problems or other deficiencies in the text
1703 happen due to character set problems or other deficiencies in the text
1701 patch format.
1704 patch format.
1702
1705
1703 With -s/--similarity, hg will attempt to discover renames and copies in
1706 With -s/--similarity, hg will attempt to discover renames and copies in
1704 the patch in the same way as 'addremove'.
1707 the patch in the same way as 'addremove'.
1705
1708
1706 To read a patch from standard input, use "-" as the patch name. If a URL
1709 To read a patch from standard input, use "-" as the patch name. If a URL
1707 is specified, the patch will be downloaded from it. See 'hg help dates'
1710 is specified, the patch will be downloaded from it. See 'hg help dates'
1708 for a list of formats valid for -d/--date.
1711 for a list of formats valid for -d/--date.
1709 """
1712 """
1710 patches = (patch1,) + patches
1713 patches = (patch1,) + patches
1711
1714
1712 date = opts.get('date')
1715 date = opts.get('date')
1713 if date:
1716 if date:
1714 opts['date'] = util.parsedate(date)
1717 opts['date'] = util.parsedate(date)
1715
1718
1716 try:
1719 try:
1717 sim = float(opts.get('similarity') or 0)
1720 sim = float(opts.get('similarity') or 0)
1718 except ValueError:
1721 except ValueError:
1719 raise util.Abort(_('similarity must be a number'))
1722 raise util.Abort(_('similarity must be a number'))
1720 if sim < 0 or sim > 100:
1723 if sim < 0 or sim > 100:
1721 raise util.Abort(_('similarity must be between 0 and 100'))
1724 raise util.Abort(_('similarity must be between 0 and 100'))
1722
1725
1723 if opts.get('exact') or not opts.get('force'):
1726 if opts.get('exact') or not opts.get('force'):
1724 cmdutil.bail_if_changed(repo)
1727 cmdutil.bail_if_changed(repo)
1725
1728
1726 d = opts["base"]
1729 d = opts["base"]
1727 strip = opts["strip"]
1730 strip = opts["strip"]
1728 wlock = lock = None
1731 wlock = lock = None
1729 try:
1732 try:
1730 wlock = repo.wlock()
1733 wlock = repo.wlock()
1731 lock = repo.lock()
1734 lock = repo.lock()
1732 for p in patches:
1735 for p in patches:
1733 pf = os.path.join(d, p)
1736 pf = os.path.join(d, p)
1734
1737
1735 if pf == '-':
1738 if pf == '-':
1736 ui.status(_("applying patch from stdin\n"))
1739 ui.status(_("applying patch from stdin\n"))
1737 pf = sys.stdin
1740 pf = sys.stdin
1738 else:
1741 else:
1739 ui.status(_("applying %s\n") % p)
1742 ui.status(_("applying %s\n") % p)
1740 pf = url.open(ui, pf)
1743 pf = url.open(ui, pf)
1741 data = patch.extract(ui, pf)
1744 data = patch.extract(ui, pf)
1742 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1745 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1743
1746
1744 if tmpname is None:
1747 if tmpname is None:
1745 raise util.Abort(_('no diffs found'))
1748 raise util.Abort(_('no diffs found'))
1746
1749
1747 try:
1750 try:
1748 cmdline_message = cmdutil.logmessage(opts)
1751 cmdline_message = cmdutil.logmessage(opts)
1749 if cmdline_message:
1752 if cmdline_message:
1750 # pickup the cmdline msg
1753 # pickup the cmdline msg
1751 message = cmdline_message
1754 message = cmdline_message
1752 elif message:
1755 elif message:
1753 # pickup the patch msg
1756 # pickup the patch msg
1754 message = message.strip()
1757 message = message.strip()
1755 else:
1758 else:
1756 # launch the editor
1759 # launch the editor
1757 message = None
1760 message = None
1758 ui.debug(_('message:\n%s\n') % message)
1761 ui.debug(_('message:\n%s\n') % message)
1759
1762
1760 wp = repo.parents()
1763 wp = repo.parents()
1761 if opts.get('exact'):
1764 if opts.get('exact'):
1762 if not nodeid or not p1:
1765 if not nodeid or not p1:
1763 raise util.Abort(_('not a Mercurial patch'))
1766 raise util.Abort(_('not a Mercurial patch'))
1764 p1 = repo.lookup(p1)
1767 p1 = repo.lookup(p1)
1765 p2 = repo.lookup(p2 or hex(nullid))
1768 p2 = repo.lookup(p2 or hex(nullid))
1766
1769
1767 if p1 != wp[0].node():
1770 if p1 != wp[0].node():
1768 hg.clean(repo, p1)
1771 hg.clean(repo, p1)
1769 repo.dirstate.setparents(p1, p2)
1772 repo.dirstate.setparents(p1, p2)
1770 elif p2:
1773 elif p2:
1771 try:
1774 try:
1772 p1 = repo.lookup(p1)
1775 p1 = repo.lookup(p1)
1773 p2 = repo.lookup(p2)
1776 p2 = repo.lookup(p2)
1774 if p1 == wp[0].node():
1777 if p1 == wp[0].node():
1775 repo.dirstate.setparents(p1, p2)
1778 repo.dirstate.setparents(p1, p2)
1776 except error.RepoError:
1779 except error.RepoError:
1777 pass
1780 pass
1778 if opts.get('exact') or opts.get('import_branch'):
1781 if opts.get('exact') or opts.get('import_branch'):
1779 repo.dirstate.setbranch(branch or 'default')
1782 repo.dirstate.setbranch(branch or 'default')
1780
1783
1781 files = {}
1784 files = {}
1782 try:
1785 try:
1783 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1786 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1784 files=files, eolmode=None)
1787 files=files, eolmode=None)
1785 finally:
1788 finally:
1786 files = patch.updatedir(ui, repo, files, similarity=sim/100.)
1789 files = patch.updatedir(ui, repo, files, similarity=sim/100.)
1787 if not opts.get('no_commit'):
1790 if not opts.get('no_commit'):
1788 m = cmdutil.matchfiles(repo, files or [])
1791 m = cmdutil.matchfiles(repo, files or [])
1789 n = repo.commit(message, opts.get('user') or user,
1792 n = repo.commit(message, opts.get('user') or user,
1790 opts.get('date') or date, match=m,
1793 opts.get('date') or date, match=m,
1791 editor=cmdutil.commiteditor)
1794 editor=cmdutil.commiteditor)
1792 if opts.get('exact'):
1795 if opts.get('exact'):
1793 if hex(n) != nodeid:
1796 if hex(n) != nodeid:
1794 repo.rollback()
1797 repo.rollback()
1795 raise util.Abort(_('patch is damaged'
1798 raise util.Abort(_('patch is damaged'
1796 ' or loses information'))
1799 ' or loses information'))
1797 # Force a dirstate write so that the next transaction
1800 # Force a dirstate write so that the next transaction
1798 # backups an up-do-date file.
1801 # backups an up-do-date file.
1799 repo.dirstate.write()
1802 repo.dirstate.write()
1800 finally:
1803 finally:
1801 os.unlink(tmpname)
1804 os.unlink(tmpname)
1802 finally:
1805 finally:
1803 release(lock, wlock)
1806 release(lock, wlock)
1804
1807
1805 def incoming(ui, repo, source="default", **opts):
1808 def incoming(ui, repo, source="default", **opts):
1806 """show new changesets found in source
1809 """show new changesets found in source
1807
1810
1808 Show new changesets found in the specified path/URL or the default pull
1811 Show new changesets found in the specified path/URL or the default pull
1809 location. These are the changesets that would have been pulled if a pull
1812 location. These are the changesets that would have been pulled if a pull
1810 at the time you issued this command.
1813 at the time you issued this command.
1811
1814
1812 For remote repository, using --bundle avoids downloading the changesets
1815 For remote repository, using --bundle avoids downloading the changesets
1813 twice if the incoming is followed by a pull.
1816 twice if the incoming is followed by a pull.
1814
1817
1815 See pull for valid source format details.
1818 See pull for valid source format details.
1816 """
1819 """
1817 limit = cmdutil.loglimit(opts)
1820 limit = cmdutil.loglimit(opts)
1818 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
1821 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
1819 other = hg.repository(cmdutil.remoteui(repo, opts), source)
1822 other = hg.repository(cmdutil.remoteui(repo, opts), source)
1820 ui.status(_('comparing with %s\n') % url.hidepassword(source))
1823 ui.status(_('comparing with %s\n') % url.hidepassword(source))
1821 if revs:
1824 if revs:
1822 revs = [other.lookup(rev) for rev in revs]
1825 revs = [other.lookup(rev) for rev in revs]
1823 common, incoming, rheads = repo.findcommonincoming(other, heads=revs,
1826 common, incoming, rheads = repo.findcommonincoming(other, heads=revs,
1824 force=opts["force"])
1827 force=opts["force"])
1825 if not incoming:
1828 if not incoming:
1826 try:
1829 try:
1827 os.unlink(opts["bundle"])
1830 os.unlink(opts["bundle"])
1828 except:
1831 except:
1829 pass
1832 pass
1830 ui.status(_("no changes found\n"))
1833 ui.status(_("no changes found\n"))
1831 return 1
1834 return 1
1832
1835
1833 cleanup = None
1836 cleanup = None
1834 try:
1837 try:
1835 fname = opts["bundle"]
1838 fname = opts["bundle"]
1836 if fname or not other.local():
1839 if fname or not other.local():
1837 # create a bundle (uncompressed if other repo is not local)
1840 # create a bundle (uncompressed if other repo is not local)
1838
1841
1839 if revs is None and other.capable('changegroupsubset'):
1842 if revs is None and other.capable('changegroupsubset'):
1840 revs = rheads
1843 revs = rheads
1841
1844
1842 if revs is None:
1845 if revs is None:
1843 cg = other.changegroup(incoming, "incoming")
1846 cg = other.changegroup(incoming, "incoming")
1844 else:
1847 else:
1845 cg = other.changegroupsubset(incoming, revs, 'incoming')
1848 cg = other.changegroupsubset(incoming, revs, 'incoming')
1846 bundletype = other.local() and "HG10BZ" or "HG10UN"
1849 bundletype = other.local() and "HG10BZ" or "HG10UN"
1847 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1850 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1848 # keep written bundle?
1851 # keep written bundle?
1849 if opts["bundle"]:
1852 if opts["bundle"]:
1850 cleanup = None
1853 cleanup = None
1851 if not other.local():
1854 if not other.local():
1852 # use the created uncompressed bundlerepo
1855 # use the created uncompressed bundlerepo
1853 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1856 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1854
1857
1855 o = other.changelog.nodesbetween(incoming, revs)[0]
1858 o = other.changelog.nodesbetween(incoming, revs)[0]
1856 if opts.get('newest_first'):
1859 if opts.get('newest_first'):
1857 o.reverse()
1860 o.reverse()
1858 displayer = cmdutil.show_changeset(ui, other, opts)
1861 displayer = cmdutil.show_changeset(ui, other, opts)
1859 count = 0
1862 count = 0
1860 for n in o:
1863 for n in o:
1861 if count >= limit:
1864 if count >= limit:
1862 break
1865 break
1863 parents = [p for p in other.changelog.parents(n) if p != nullid]
1866 parents = [p for p in other.changelog.parents(n) if p != nullid]
1864 if opts.get('no_merges') and len(parents) == 2:
1867 if opts.get('no_merges') and len(parents) == 2:
1865 continue
1868 continue
1866 count += 1
1869 count += 1
1867 displayer.show(other[n])
1870 displayer.show(other[n])
1868 finally:
1871 finally:
1869 if hasattr(other, 'close'):
1872 if hasattr(other, 'close'):
1870 other.close()
1873 other.close()
1871 if cleanup:
1874 if cleanup:
1872 os.unlink(cleanup)
1875 os.unlink(cleanup)
1873
1876
1874 def init(ui, dest=".", **opts):
1877 def init(ui, dest=".", **opts):
1875 """create a new repository in the given directory
1878 """create a new repository in the given directory
1876
1879
1877 Initialize a new repository in the given directory. If the given directory
1880 Initialize a new repository in the given directory. If the given directory
1878 does not exist, it will be created.
1881 does not exist, it will be created.
1879
1882
1880 If no directory is given, the current directory is used.
1883 If no directory is given, the current directory is used.
1881
1884
1882 It is possible to specify an ssh:// URL as the destination. See 'hg help
1885 It is possible to specify an ssh:// URL as the destination. See 'hg help
1883 urls' for more information.
1886 urls' for more information.
1884 """
1887 """
1885 hg.repository(cmdutil.remoteui(ui, opts), dest, create=1)
1888 hg.repository(cmdutil.remoteui(ui, opts), dest, create=1)
1886
1889
1887 def locate(ui, repo, *pats, **opts):
1890 def locate(ui, repo, *pats, **opts):
1888 """locate files matching specific patterns
1891 """locate files matching specific patterns
1889
1892
1890 Print files under Mercurial control in the working directory whose names
1893 Print files under Mercurial control in the working directory whose names
1891 match the given patterns.
1894 match the given patterns.
1892
1895
1893 By default, this command searches all directories in the working
1896 By default, this command searches all directories in the working
1894 directory. To search just the current directory and its subdirectories,
1897 directory. To search just the current directory and its subdirectories,
1895 use "--include .".
1898 use "--include .".
1896
1899
1897 If no patterns are given to match, this command prints the names of all
1900 If no patterns are given to match, this command prints the names of all
1898 files under Mercurial control in the working directory.
1901 files under Mercurial control in the working directory.
1899
1902
1900 If you want to feed the output of this command into the "xargs" command,
1903 If you want to feed the output of this command into the "xargs" command,
1901 use the -0 option to both this command and "xargs". This will avoid the
1904 use the -0 option to both this command and "xargs". This will avoid the
1902 problem of "xargs" treating single filenames that contain whitespace as
1905 problem of "xargs" treating single filenames that contain whitespace as
1903 multiple filenames.
1906 multiple filenames.
1904 """
1907 """
1905 end = opts.get('print0') and '\0' or '\n'
1908 end = opts.get('print0') and '\0' or '\n'
1906 rev = opts.get('rev') or None
1909 rev = opts.get('rev') or None
1907
1910
1908 ret = 1
1911 ret = 1
1909 m = cmdutil.match(repo, pats, opts, default='relglob')
1912 m = cmdutil.match(repo, pats, opts, default='relglob')
1910 m.bad = lambda x,y: False
1913 m.bad = lambda x,y: False
1911 for abs in repo[rev].walk(m):
1914 for abs in repo[rev].walk(m):
1912 if not rev and abs not in repo.dirstate:
1915 if not rev and abs not in repo.dirstate:
1913 continue
1916 continue
1914 if opts.get('fullpath'):
1917 if opts.get('fullpath'):
1915 ui.write(repo.wjoin(abs), end)
1918 ui.write(repo.wjoin(abs), end)
1916 else:
1919 else:
1917 ui.write(((pats and m.rel(abs)) or abs), end)
1920 ui.write(((pats and m.rel(abs)) or abs), end)
1918 ret = 0
1921 ret = 0
1919
1922
1920 return ret
1923 return ret
1921
1924
1922 def log(ui, repo, *pats, **opts):
1925 def log(ui, repo, *pats, **opts):
1923 """show revision history of entire repository or files
1926 """show revision history of entire repository or files
1924
1927
1925 Print the revision history of the specified files or the entire project.
1928 Print the revision history of the specified files or the entire project.
1926
1929
1927 File history is shown without following rename or copy history of files.
1930 File history is shown without following rename or copy history of files.
1928 Use -f/--follow with a filename to follow history across renames and
1931 Use -f/--follow with a filename to follow history across renames and
1929 copies. --follow without a filename will only show ancestors or
1932 copies. --follow without a filename will only show ancestors or
1930 descendants of the starting revision. --follow-first only follows the
1933 descendants of the starting revision. --follow-first only follows the
1931 first parent of merge revisions.
1934 first parent of merge revisions.
1932
1935
1933 If no revision range is specified, the default is tip:0 unless --follow is
1936 If no revision range is specified, the default is tip:0 unless --follow is
1934 set, in which case the working directory parent is used as the starting
1937 set, in which case the working directory parent is used as the starting
1935 revision.
1938 revision.
1936
1939
1937 See 'hg help dates' for a list of formats valid for -d/--date.
1940 See 'hg help dates' for a list of formats valid for -d/--date.
1938
1941
1939 By default this command prints revision number and changeset id, tags,
1942 By default this command prints revision number and changeset id, tags,
1940 non-trivial parents, user, date and time, and a summary for each commit.
1943 non-trivial parents, user, date and time, and a summary for each commit.
1941 When the -v/--verbose switch is used, the list of changed files and full
1944 When the -v/--verbose switch is used, the list of changed files and full
1942 commit message are shown.
1945 commit message are shown.
1943
1946
1944 NOTE: log -p/--patch may generate unexpected diff output for merge
1947 NOTE: log -p/--patch may generate unexpected diff output for merge
1945 changesets, as it will only compare the merge changeset against its first
1948 changesets, as it will only compare the merge changeset against its first
1946 parent. Also, only files different from BOTH parents will appear in
1949 parent. Also, only files different from BOTH parents will appear in
1947 files:.
1950 files:.
1948 """
1951 """
1949
1952
1950 get = util.cachefunc(lambda r: repo[r].changeset())
1953 get = util.cachefunc(lambda r: repo[r].changeset())
1951 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1954 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1952
1955
1953 limit = cmdutil.loglimit(opts)
1956 limit = cmdutil.loglimit(opts)
1954 count = 0
1957 count = 0
1955
1958
1956 if opts.get('copies') and opts.get('rev'):
1959 if opts.get('copies') and opts.get('rev'):
1957 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
1960 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
1958 else:
1961 else:
1959 endrev = len(repo)
1962 endrev = len(repo)
1960 rcache = {}
1963 rcache = {}
1961 ncache = {}
1964 ncache = {}
1962 def getrenamed(fn, rev):
1965 def getrenamed(fn, rev):
1963 '''looks up all renames for a file (up to endrev) the first
1966 '''looks up all renames for a file (up to endrev) the first
1964 time the file is given. It indexes on the changerev and only
1967 time the file is given. It indexes on the changerev and only
1965 parses the manifest if linkrev != changerev.
1968 parses the manifest if linkrev != changerev.
1966 Returns rename info for fn at changerev rev.'''
1969 Returns rename info for fn at changerev rev.'''
1967 if fn not in rcache:
1970 if fn not in rcache:
1968 rcache[fn] = {}
1971 rcache[fn] = {}
1969 ncache[fn] = {}
1972 ncache[fn] = {}
1970 fl = repo.file(fn)
1973 fl = repo.file(fn)
1971 for i in fl:
1974 for i in fl:
1972 node = fl.node(i)
1975 node = fl.node(i)
1973 lr = fl.linkrev(i)
1976 lr = fl.linkrev(i)
1974 renamed = fl.renamed(node)
1977 renamed = fl.renamed(node)
1975 rcache[fn][lr] = renamed
1978 rcache[fn][lr] = renamed
1976 if renamed:
1979 if renamed:
1977 ncache[fn][node] = renamed
1980 ncache[fn][node] = renamed
1978 if lr >= endrev:
1981 if lr >= endrev:
1979 break
1982 break
1980 if rev in rcache[fn]:
1983 if rev in rcache[fn]:
1981 return rcache[fn][rev]
1984 return rcache[fn][rev]
1982
1985
1983 # If linkrev != rev (i.e. rev not found in rcache) fallback to
1986 # If linkrev != rev (i.e. rev not found in rcache) fallback to
1984 # filectx logic.
1987 # filectx logic.
1985
1988
1986 try:
1989 try:
1987 return repo[rev][fn].renamed()
1990 return repo[rev][fn].renamed()
1988 except error.LookupError:
1991 except error.LookupError:
1989 pass
1992 pass
1990 return None
1993 return None
1991
1994
1992 df = False
1995 df = False
1993 if opts["date"]:
1996 if opts["date"]:
1994 df = util.matchdate(opts["date"])
1997 df = util.matchdate(opts["date"])
1995
1998
1996 only_branches = opts.get('only_branch')
1999 only_branches = opts.get('only_branch')
1997
2000
1998 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
2001 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1999 for st, rev, fns in changeiter:
2002 for st, rev, fns in changeiter:
2000 if st == 'add':
2003 if st == 'add':
2001 parents = [p for p in repo.changelog.parentrevs(rev)
2004 parents = [p for p in repo.changelog.parentrevs(rev)
2002 if p != nullrev]
2005 if p != nullrev]
2003 if opts.get('no_merges') and len(parents) == 2:
2006 if opts.get('no_merges') and len(parents) == 2:
2004 continue
2007 continue
2005 if opts.get('only_merges') and len(parents) != 2:
2008 if opts.get('only_merges') and len(parents) != 2:
2006 continue
2009 continue
2007
2010
2008 if only_branches:
2011 if only_branches:
2009 revbranch = get(rev)[5]['branch']
2012 revbranch = get(rev)[5]['branch']
2010 if revbranch not in only_branches:
2013 if revbranch not in only_branches:
2011 continue
2014 continue
2012
2015
2013 if df:
2016 if df:
2014 changes = get(rev)
2017 changes = get(rev)
2015 if not df(changes[2][0]):
2018 if not df(changes[2][0]):
2016 continue
2019 continue
2017
2020
2018 if opts.get('keyword'):
2021 if opts.get('keyword'):
2019 changes = get(rev)
2022 changes = get(rev)
2020 miss = 0
2023 miss = 0
2021 for k in [kw.lower() for kw in opts['keyword']]:
2024 for k in [kw.lower() for kw in opts['keyword']]:
2022 if not (k in changes[1].lower() or
2025 if not (k in changes[1].lower() or
2023 k in changes[4].lower() or
2026 k in changes[4].lower() or
2024 k in " ".join(changes[3]).lower()):
2027 k in " ".join(changes[3]).lower()):
2025 miss = 1
2028 miss = 1
2026 break
2029 break
2027 if miss:
2030 if miss:
2028 continue
2031 continue
2029
2032
2030 if opts['user']:
2033 if opts['user']:
2031 changes = get(rev)
2034 changes = get(rev)
2032 if not [k for k in opts['user'] if k in changes[1]]:
2035 if not [k for k in opts['user'] if k in changes[1]]:
2033 continue
2036 continue
2034
2037
2035 copies = []
2038 copies = []
2036 if opts.get('copies') and rev:
2039 if opts.get('copies') and rev:
2037 for fn in get(rev)[3]:
2040 for fn in get(rev)[3]:
2038 rename = getrenamed(fn, rev)
2041 rename = getrenamed(fn, rev)
2039 if rename:
2042 if rename:
2040 copies.append((fn, rename[0]))
2043 copies.append((fn, rename[0]))
2041 displayer.show(context.changectx(repo, rev), copies=copies)
2044 displayer.show(context.changectx(repo, rev), copies=copies)
2042 elif st == 'iter':
2045 elif st == 'iter':
2043 if count == limit: break
2046 if count == limit: break
2044 if displayer.flush(rev):
2047 if displayer.flush(rev):
2045 count += 1
2048 count += 1
2046
2049
2047 def manifest(ui, repo, node=None, rev=None):
2050 def manifest(ui, repo, node=None, rev=None):
2048 """output the current or given revision of the project manifest
2051 """output the current or given revision of the project manifest
2049
2052
2050 Print a list of version controlled files for the given revision. If no
2053 Print a list of version controlled files for the given revision. If no
2051 revision is given, the first parent of the working directory is used, or
2054 revision is given, the first parent of the working directory is used, or
2052 the null revision if no revision is checked out.
2055 the null revision if no revision is checked out.
2053
2056
2054 With -v, print file permissions, symlink and executable bits.
2057 With -v, print file permissions, symlink and executable bits.
2055 With --debug, print file revision hashes.
2058 With --debug, print file revision hashes.
2056 """
2059 """
2057
2060
2058 if rev and node:
2061 if rev and node:
2059 raise util.Abort(_("please specify just one revision"))
2062 raise util.Abort(_("please specify just one revision"))
2060
2063
2061 if not node:
2064 if not node:
2062 node = rev
2065 node = rev
2063
2066
2064 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2067 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2065 ctx = repo[node]
2068 ctx = repo[node]
2066 for f in ctx:
2069 for f in ctx:
2067 if ui.debugflag:
2070 if ui.debugflag:
2068 ui.write("%40s " % hex(ctx.manifest()[f]))
2071 ui.write("%40s " % hex(ctx.manifest()[f]))
2069 if ui.verbose:
2072 if ui.verbose:
2070 ui.write(decor[ctx.flags(f)])
2073 ui.write(decor[ctx.flags(f)])
2071 ui.write("%s\n" % f)
2074 ui.write("%s\n" % f)
2072
2075
2073 def merge(ui, repo, node=None, **opts):
2076 def merge(ui, repo, node=None, **opts):
2074 """merge working directory with another revision
2077 """merge working directory with another revision
2075
2078
2076 The current working directory is updated with all changes made in the
2079 The current working directory is updated with all changes made in the
2077 requested revision since the last common predecessor revision.
2080 requested revision since the last common predecessor revision.
2078
2081
2079 Files that changed between either parent are marked as changed for the
2082 Files that changed between either parent are marked as changed for the
2080 next commit and a commit must be performed before any further updates to
2083 next commit and a commit must be performed before any further updates to
2081 the repository are allowed. The next commit will have two parents.
2084 the repository are allowed. The next commit will have two parents.
2082
2085
2083 If no revision is specified, the working directory's parent is a head
2086 If no revision is specified, the working directory's parent is a head
2084 revision, and the current branch contains exactly one other head, the
2087 revision, and the current branch contains exactly one other head, the
2085 other head is merged with by default. Otherwise, an explicit revision with
2088 other head is merged with by default. Otherwise, an explicit revision with
2086 which to merge with must be provided.
2089 which to merge with must be provided.
2087 """
2090 """
2088
2091
2089 if opts.get('rev') and node:
2092 if opts.get('rev') and node:
2090 raise util.Abort(_("please specify just one revision"))
2093 raise util.Abort(_("please specify just one revision"))
2091 if not node:
2094 if not node:
2092 node = opts.get('rev')
2095 node = opts.get('rev')
2093
2096
2094 if not node:
2097 if not node:
2095 branch = repo.changectx(None).branch()
2098 branch = repo.changectx(None).branch()
2096 bheads = repo.branchheads(branch)
2099 bheads = repo.branchheads(branch)
2097 if len(bheads) > 2:
2100 if len(bheads) > 2:
2098 raise util.Abort(_("branch '%s' has %d heads - "
2101 raise util.Abort(_("branch '%s' has %d heads - "
2099 "please merge with an explicit rev") %
2102 "please merge with an explicit rev") %
2100 (branch, len(bheads)))
2103 (branch, len(bheads)))
2101
2104
2102 parent = repo.dirstate.parents()[0]
2105 parent = repo.dirstate.parents()[0]
2103 if len(bheads) == 1:
2106 if len(bheads) == 1:
2104 if len(repo.heads()) > 1:
2107 if len(repo.heads()) > 1:
2105 raise util.Abort(_("branch '%s' has one head - "
2108 raise util.Abort(_("branch '%s' has one head - "
2106 "please merge with an explicit rev") %
2109 "please merge with an explicit rev") %
2107 branch)
2110 branch)
2108 msg = _('there is nothing to merge')
2111 msg = _('there is nothing to merge')
2109 if parent != repo.lookup(repo[None].branch()):
2112 if parent != repo.lookup(repo[None].branch()):
2110 msg = _('%s - use "hg update" instead') % msg
2113 msg = _('%s - use "hg update" instead') % msg
2111 raise util.Abort(msg)
2114 raise util.Abort(msg)
2112
2115
2113 if parent not in bheads:
2116 if parent not in bheads:
2114 raise util.Abort(_('working dir not at a head rev - '
2117 raise util.Abort(_('working dir not at a head rev - '
2115 'use "hg update" or merge with an explicit rev'))
2118 'use "hg update" or merge with an explicit rev'))
2116 node = parent == bheads[0] and bheads[-1] or bheads[0]
2119 node = parent == bheads[0] and bheads[-1] or bheads[0]
2117
2120
2118 if opts.get('preview'):
2121 if opts.get('preview'):
2119 p1 = repo['.']
2122 p1 = repo['.']
2120 p2 = repo[node]
2123 p2 = repo[node]
2121 common = p1.ancestor(p2)
2124 common = p1.ancestor(p2)
2122 roots, heads = [common.node()], [p2.node()]
2125 roots, heads = [common.node()], [p2.node()]
2123 displayer = cmdutil.show_changeset(ui, repo, opts)
2126 displayer = cmdutil.show_changeset(ui, repo, opts)
2124 for node in repo.changelog.nodesbetween(roots=roots, heads=heads)[0]:
2127 for node in repo.changelog.nodesbetween(roots=roots, heads=heads)[0]:
2125 displayer.show(repo[node])
2128 displayer.show(repo[node])
2126 return 0
2129 return 0
2127
2130
2128 return hg.merge(repo, node, force=opts.get('force'))
2131 return hg.merge(repo, node, force=opts.get('force'))
2129
2132
2130 def outgoing(ui, repo, dest=None, **opts):
2133 def outgoing(ui, repo, dest=None, **opts):
2131 """show changesets not found in destination
2134 """show changesets not found in destination
2132
2135
2133 Show changesets not found in the specified destination repository or the
2136 Show changesets not found in the specified destination repository or the
2134 default push location. These are the changesets that would be pushed if a
2137 default push location. These are the changesets that would be pushed if a
2135 push was requested.
2138 push was requested.
2136
2139
2137 See pull for valid destination format details.
2140 See pull for valid destination format details.
2138 """
2141 """
2139 limit = cmdutil.loglimit(opts)
2142 limit = cmdutil.loglimit(opts)
2140 dest, revs, checkout = hg.parseurl(
2143 dest, revs, checkout = hg.parseurl(
2141 ui.expandpath(dest or 'default-push', dest or 'default'), opts.get('rev'))
2144 ui.expandpath(dest or 'default-push', dest or 'default'), opts.get('rev'))
2142 if revs:
2145 if revs:
2143 revs = [repo.lookup(rev) for rev in revs]
2146 revs = [repo.lookup(rev) for rev in revs]
2144
2147
2145 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2148 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2146 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2149 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2147 o = repo.findoutgoing(other, force=opts.get('force'))
2150 o = repo.findoutgoing(other, force=opts.get('force'))
2148 if not o:
2151 if not o:
2149 ui.status(_("no changes found\n"))
2152 ui.status(_("no changes found\n"))
2150 return 1
2153 return 1
2151 o = repo.changelog.nodesbetween(o, revs)[0]
2154 o = repo.changelog.nodesbetween(o, revs)[0]
2152 if opts.get('newest_first'):
2155 if opts.get('newest_first'):
2153 o.reverse()
2156 o.reverse()
2154 displayer = cmdutil.show_changeset(ui, repo, opts)
2157 displayer = cmdutil.show_changeset(ui, repo, opts)
2155 count = 0
2158 count = 0
2156 for n in o:
2159 for n in o:
2157 if count >= limit:
2160 if count >= limit:
2158 break
2161 break
2159 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2162 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2160 if opts.get('no_merges') and len(parents) == 2:
2163 if opts.get('no_merges') and len(parents) == 2:
2161 continue
2164 continue
2162 count += 1
2165 count += 1
2163 displayer.show(repo[n])
2166 displayer.show(repo[n])
2164
2167
2165 def parents(ui, repo, file_=None, **opts):
2168 def parents(ui, repo, file_=None, **opts):
2166 """show the parents of the working directory or revision
2169 """show the parents of the working directory or revision
2167
2170
2168 Print the working directory's parent revisions. If a revision is given via
2171 Print the working directory's parent revisions. If a revision is given via
2169 -r/--rev, the parent of that revision will be printed. If a file argument
2172 -r/--rev, the parent of that revision will be printed. If a file argument
2170 is given, the revision in which the file was last changed (before the
2173 is given, the revision in which the file was last changed (before the
2171 working directory revision or the argument to --rev if given) is printed.
2174 working directory revision or the argument to --rev if given) is printed.
2172 """
2175 """
2173 rev = opts.get('rev')
2176 rev = opts.get('rev')
2174 if rev:
2177 if rev:
2175 ctx = repo[rev]
2178 ctx = repo[rev]
2176 else:
2179 else:
2177 ctx = repo[None]
2180 ctx = repo[None]
2178
2181
2179 if file_:
2182 if file_:
2180 m = cmdutil.match(repo, (file_,), opts)
2183 m = cmdutil.match(repo, (file_,), opts)
2181 if m.anypats() or len(m.files()) != 1:
2184 if m.anypats() or len(m.files()) != 1:
2182 raise util.Abort(_('can only specify an explicit filename'))
2185 raise util.Abort(_('can only specify an explicit filename'))
2183 file_ = m.files()[0]
2186 file_ = m.files()[0]
2184 filenodes = []
2187 filenodes = []
2185 for cp in ctx.parents():
2188 for cp in ctx.parents():
2186 if not cp:
2189 if not cp:
2187 continue
2190 continue
2188 try:
2191 try:
2189 filenodes.append(cp.filenode(file_))
2192 filenodes.append(cp.filenode(file_))
2190 except error.LookupError:
2193 except error.LookupError:
2191 pass
2194 pass
2192 if not filenodes:
2195 if not filenodes:
2193 raise util.Abort(_("'%s' not found in manifest!") % file_)
2196 raise util.Abort(_("'%s' not found in manifest!") % file_)
2194 fl = repo.file(file_)
2197 fl = repo.file(file_)
2195 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2198 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2196 else:
2199 else:
2197 p = [cp.node() for cp in ctx.parents()]
2200 p = [cp.node() for cp in ctx.parents()]
2198
2201
2199 displayer = cmdutil.show_changeset(ui, repo, opts)
2202 displayer = cmdutil.show_changeset(ui, repo, opts)
2200 for n in p:
2203 for n in p:
2201 if n != nullid:
2204 if n != nullid:
2202 displayer.show(repo[n])
2205 displayer.show(repo[n])
2203
2206
2204 def paths(ui, repo, search=None):
2207 def paths(ui, repo, search=None):
2205 """show aliases for remote repositories
2208 """show aliases for remote repositories
2206
2209
2207 Show definition of symbolic path name NAME. If no name is given, show
2210 Show definition of symbolic path name NAME. If no name is given, show
2208 definition of all available names.
2211 definition of all available names.
2209
2212
2210 Path names are defined in the [paths] section of /etc/mercurial/hgrc and
2213 Path names are defined in the [paths] section of /etc/mercurial/hgrc and
2211 $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2214 $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2212
2215
2213 See 'hg help urls' for more information.
2216 See 'hg help urls' for more information.
2214 """
2217 """
2215 if search:
2218 if search:
2216 for name, path in ui.configitems("paths"):
2219 for name, path in ui.configitems("paths"):
2217 if name == search:
2220 if name == search:
2218 ui.write("%s\n" % url.hidepassword(path))
2221 ui.write("%s\n" % url.hidepassword(path))
2219 return
2222 return
2220 ui.warn(_("not found!\n"))
2223 ui.warn(_("not found!\n"))
2221 return 1
2224 return 1
2222 else:
2225 else:
2223 for name, path in ui.configitems("paths"):
2226 for name, path in ui.configitems("paths"):
2224 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2227 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2225
2228
2226 def postincoming(ui, repo, modheads, optupdate, checkout):
2229 def postincoming(ui, repo, modheads, optupdate, checkout):
2227 if modheads == 0:
2230 if modheads == 0:
2228 return
2231 return
2229 if optupdate:
2232 if optupdate:
2230 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2233 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2231 return hg.update(repo, checkout)
2234 return hg.update(repo, checkout)
2232 else:
2235 else:
2233 ui.status(_("not updating, since new heads added\n"))
2236 ui.status(_("not updating, since new heads added\n"))
2234 if modheads > 1:
2237 if modheads > 1:
2235 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2238 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2236 else:
2239 else:
2237 ui.status(_("(run 'hg update' to get a working copy)\n"))
2240 ui.status(_("(run 'hg update' to get a working copy)\n"))
2238
2241
2239 def pull(ui, repo, source="default", **opts):
2242 def pull(ui, repo, source="default", **opts):
2240 """pull changes from the specified source
2243 """pull changes from the specified source
2241
2244
2242 Pull changes from a remote repository to a local one.
2245 Pull changes from a remote repository to a local one.
2243
2246
2244 This finds all changes from the repository at the specified path or URL
2247 This finds all changes from the repository at the specified path or URL
2245 and adds them to a local repository (the current one unless -R is
2248 and adds them to a local repository (the current one unless -R is
2246 specified). By default, this does not update the copy of the project in
2249 specified). By default, this does not update the copy of the project in
2247 the working directory.
2250 the working directory.
2248
2251
2249 Use hg incoming if you want to see what would have been added by a pull at
2252 Use hg incoming if you want to see what would have been added by a pull at
2250 the time you issued this command. If you then decide to added those
2253 the time you issued this command. If you then decide to added those
2251 changes to the repository, you should use pull -r X where X is the last
2254 changes to the repository, you should use pull -r X where X is the last
2252 changeset listed by hg incoming.
2255 changeset listed by hg incoming.
2253
2256
2254 If SOURCE is omitted, the 'default' path will be used. See 'hg help urls'
2257 If SOURCE is omitted, the 'default' path will be used. See 'hg help urls'
2255 for more information.
2258 for more information.
2256 """
2259 """
2257 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
2260 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
2258 other = hg.repository(cmdutil.remoteui(repo, opts), source)
2261 other = hg.repository(cmdutil.remoteui(repo, opts), source)
2259 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2262 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2260 if revs:
2263 if revs:
2261 try:
2264 try:
2262 revs = [other.lookup(rev) for rev in revs]
2265 revs = [other.lookup(rev) for rev in revs]
2263 except error.CapabilityError:
2266 except error.CapabilityError:
2264 err = _("Other repository doesn't support revision lookup, "
2267 err = _("Other repository doesn't support revision lookup, "
2265 "so a rev cannot be specified.")
2268 "so a rev cannot be specified.")
2266 raise util.Abort(err)
2269 raise util.Abort(err)
2267
2270
2268 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2271 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2269 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2272 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2270
2273
2271 def push(ui, repo, dest=None, **opts):
2274 def push(ui, repo, dest=None, **opts):
2272 """push changes to the specified destination
2275 """push changes to the specified destination
2273
2276
2274 Push changes from the local repository to the given destination.
2277 Push changes from the local repository to the given destination.
2275
2278
2276 This is the symmetrical operation for pull. It moves changes from the
2279 This is the symmetrical operation for pull. It moves changes from the
2277 current repository to a different one. If the destination is local this is
2280 current repository to a different one. If the destination is local this is
2278 identical to a pull in that directory from the current one.
2281 identical to a pull in that directory from the current one.
2279
2282
2280 By default, push will refuse to run if it detects the result would
2283 By default, push will refuse to run if it detects the result would
2281 increase the number of remote heads. This generally indicates the user
2284 increase the number of remote heads. This generally indicates the user
2282 forgot to pull and merge before pushing.
2285 forgot to pull and merge before pushing.
2283
2286
2284 If -r/--rev is used, the named revision and all its ancestors will be
2287 If -r/--rev is used, the named revision and all its ancestors will be
2285 pushed to the remote repository.
2288 pushed to the remote repository.
2286
2289
2287 Please see 'hg help urls' for important details about ssh:// URLs. If
2290 Please see 'hg help urls' for important details about ssh:// URLs. If
2288 DESTINATION is omitted, a default path will be used.
2291 DESTINATION is omitted, a default path will be used.
2289 """
2292 """
2290 dest, revs, checkout = hg.parseurl(
2293 dest, revs, checkout = hg.parseurl(
2291 ui.expandpath(dest or 'default-push', dest or 'default'), opts.get('rev'))
2294 ui.expandpath(dest or 'default-push', dest or 'default'), opts.get('rev'))
2292 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2295 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2293 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2296 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2294 if revs:
2297 if revs:
2295 revs = [repo.lookup(rev) for rev in revs]
2298 revs = [repo.lookup(rev) for rev in revs]
2296
2299
2297 # push subrepos depth-first for coherent ordering
2300 # push subrepos depth-first for coherent ordering
2298 c = repo['']
2301 c = repo['']
2299 subs = c.substate # only repos that are committed
2302 subs = c.substate # only repos that are committed
2300 for s in sorted(subs):
2303 for s in sorted(subs):
2301 c.sub(s).push(opts.get('force'))
2304 c.sub(s).push(opts.get('force'))
2302
2305
2303 r = repo.push(other, opts.get('force'), revs=revs)
2306 r = repo.push(other, opts.get('force'), revs=revs)
2304 return r == 0
2307 return r == 0
2305
2308
2306 def recover(ui, repo):
2309 def recover(ui, repo):
2307 """roll back an interrupted transaction
2310 """roll back an interrupted transaction
2308
2311
2309 Recover from an interrupted commit or pull.
2312 Recover from an interrupted commit or pull.
2310
2313
2311 This command tries to fix the repository status after an interrupted
2314 This command tries to fix the repository status after an interrupted
2312 operation. It should only be necessary when Mercurial suggests it.
2315 operation. It should only be necessary when Mercurial suggests it.
2313 """
2316 """
2314 if repo.recover():
2317 if repo.recover():
2315 return hg.verify(repo)
2318 return hg.verify(repo)
2316 return 1
2319 return 1
2317
2320
2318 def remove(ui, repo, *pats, **opts):
2321 def remove(ui, repo, *pats, **opts):
2319 """remove the specified files on the next commit
2322 """remove the specified files on the next commit
2320
2323
2321 Schedule the indicated files for removal from the repository.
2324 Schedule the indicated files for removal from the repository.
2322
2325
2323 This only removes files from the current branch, not from the entire
2326 This only removes files from the current branch, not from the entire
2324 project history. -A/--after can be used to remove only files that have
2327 project history. -A/--after can be used to remove only files that have
2325 already been deleted, -f/--force can be used to force deletion, and -Af
2328 already been deleted, -f/--force can be used to force deletion, and -Af
2326 can be used to remove files from the next revision without deleting them
2329 can be used to remove files from the next revision without deleting them
2327 from the working directory.
2330 from the working directory.
2328
2331
2329 The following table details the behavior of remove for different file
2332 The following table details the behavior of remove for different file
2330 states (columns) and option combinations (rows). The file states are Added
2333 states (columns) and option combinations (rows). The file states are Added
2331 [A], Clean [C], Modified [M] and Missing [!] (as reported by hg status).
2334 [A], Clean [C], Modified [M] and Missing [!] (as reported by hg status).
2332 The actions are Warn, Remove (from branch) and Delete (from disk).
2335 The actions are Warn, Remove (from branch) and Delete (from disk)::
2333
2336
2334 A C M !
2337 A C M !
2335 none W RD W R
2338 none W RD W R
2336 -f R RD RD R
2339 -f R RD RD R
2337 -A W W W R
2340 -A W W W R
2338 -Af R R R R
2341 -Af R R R R
2339
2342
2340 This command schedules the files to be removed at the next commit. To undo
2343 This command schedules the files to be removed at the next commit. To undo
2341 a remove before that, see hg revert.
2344 a remove before that, see hg revert.
2342 """
2345 """
2343
2346
2344 after, force = opts.get('after'), opts.get('force')
2347 after, force = opts.get('after'), opts.get('force')
2345 if not pats and not after:
2348 if not pats and not after:
2346 raise util.Abort(_('no files specified'))
2349 raise util.Abort(_('no files specified'))
2347
2350
2348 m = cmdutil.match(repo, pats, opts)
2351 m = cmdutil.match(repo, pats, opts)
2349 s = repo.status(match=m, clean=True)
2352 s = repo.status(match=m, clean=True)
2350 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2353 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2351
2354
2352 for f in m.files():
2355 for f in m.files():
2353 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2356 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2354 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
2357 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
2355
2358
2356 def warn(files, reason):
2359 def warn(files, reason):
2357 for f in files:
2360 for f in files:
2358 ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
2361 ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
2359 % (m.rel(f), reason))
2362 % (m.rel(f), reason))
2360
2363
2361 if force:
2364 if force:
2362 remove, forget = modified + deleted + clean, added
2365 remove, forget = modified + deleted + clean, added
2363 elif after:
2366 elif after:
2364 remove, forget = deleted, []
2367 remove, forget = deleted, []
2365 warn(modified + added + clean, _('still exists'))
2368 warn(modified + added + clean, _('still exists'))
2366 else:
2369 else:
2367 remove, forget = deleted + clean, []
2370 remove, forget = deleted + clean, []
2368 warn(modified, _('is modified'))
2371 warn(modified, _('is modified'))
2369 warn(added, _('has been marked for add'))
2372 warn(added, _('has been marked for add'))
2370
2373
2371 for f in sorted(remove + forget):
2374 for f in sorted(remove + forget):
2372 if ui.verbose or not m.exact(f):
2375 if ui.verbose or not m.exact(f):
2373 ui.status(_('removing %s\n') % m.rel(f))
2376 ui.status(_('removing %s\n') % m.rel(f))
2374
2377
2375 repo.forget(forget)
2378 repo.forget(forget)
2376 repo.remove(remove, unlink=not after)
2379 repo.remove(remove, unlink=not after)
2377
2380
2378 def rename(ui, repo, *pats, **opts):
2381 def rename(ui, repo, *pats, **opts):
2379 """rename files; equivalent of copy + remove
2382 """rename files; equivalent of copy + remove
2380
2383
2381 Mark dest as copies of sources; mark sources for deletion. If dest is a
2384 Mark dest as copies of sources; mark sources for deletion. If dest is a
2382 directory, copies are put in that directory. If dest is a file, there can
2385 directory, copies are put in that directory. If dest is a file, there can
2383 only be one source.
2386 only be one source.
2384
2387
2385 By default, this command copies the contents of files as they exist in the
2388 By default, this command copies the contents of files as they exist in the
2386 working directory. If invoked with -A/--after, the operation is recorded,
2389 working directory. If invoked with -A/--after, the operation is recorded,
2387 but no copying is performed.
2390 but no copying is performed.
2388
2391
2389 This command takes effect at the next commit. To undo a rename before
2392 This command takes effect at the next commit. To undo a rename before
2390 that, see hg revert.
2393 that, see hg revert.
2391 """
2394 """
2392 wlock = repo.wlock(False)
2395 wlock = repo.wlock(False)
2393 try:
2396 try:
2394 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2397 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2395 finally:
2398 finally:
2396 wlock.release()
2399 wlock.release()
2397
2400
2398 def resolve(ui, repo, *pats, **opts):
2401 def resolve(ui, repo, *pats, **opts):
2399 """retry file merges from a merge or update
2402 """retry file merges from a merge or update
2400
2403
2401 This command will cleanly retry unresolved file merges using file
2404 This command will cleanly retry unresolved file merges using file
2402 revisions preserved from the last update or merge. To attempt to resolve
2405 revisions preserved from the last update or merge. To attempt to resolve
2403 all unresolved files, use the -a/--all switch.
2406 all unresolved files, use the -a/--all switch.
2404
2407
2405 If a conflict is resolved manually, please note that the changes will be
2408 If a conflict is resolved manually, please note that the changes will be
2406 overwritten if the merge is retried with resolve. The -m/--mark switch
2409 overwritten if the merge is retried with resolve. The -m/--mark switch
2407 should be used to mark the file as resolved.
2410 should be used to mark the file as resolved.
2408
2411
2409 This command also allows listing resolved files and manually indicating
2412 This command also allows listing resolved files and manually indicating
2410 whether or not files are resolved. All files must be marked as resolved
2413 whether or not files are resolved. All files must be marked as resolved
2411 before a commit is permitted.
2414 before a commit is permitted.
2412
2415
2413 The codes used to show the status of files are:
2416 The codes used to show the status of files are::
2417
2414 U = unresolved
2418 U = unresolved
2415 R = resolved
2419 R = resolved
2416 """
2420 """
2417
2421
2418 all, mark, unmark, show = [opts.get(o) for o in 'all mark unmark list'.split()]
2422 all, mark, unmark, show = [opts.get(o) for o in 'all mark unmark list'.split()]
2419
2423
2420 if (show and (mark or unmark)) or (mark and unmark):
2424 if (show and (mark or unmark)) or (mark and unmark):
2421 raise util.Abort(_("too many options specified"))
2425 raise util.Abort(_("too many options specified"))
2422 if pats and all:
2426 if pats and all:
2423 raise util.Abort(_("can't specify --all and patterns"))
2427 raise util.Abort(_("can't specify --all and patterns"))
2424 if not (all or pats or show or mark or unmark):
2428 if not (all or pats or show or mark or unmark):
2425 raise util.Abort(_('no files or directories specified; '
2429 raise util.Abort(_('no files or directories specified; '
2426 'use --all to remerge all files'))
2430 'use --all to remerge all files'))
2427
2431
2428 ms = merge_.mergestate(repo)
2432 ms = merge_.mergestate(repo)
2429 m = cmdutil.match(repo, pats, opts)
2433 m = cmdutil.match(repo, pats, opts)
2430
2434
2431 for f in ms:
2435 for f in ms:
2432 if m(f):
2436 if m(f):
2433 if show:
2437 if show:
2434 ui.write("%s %s\n" % (ms[f].upper(), f))
2438 ui.write("%s %s\n" % (ms[f].upper(), f))
2435 elif mark:
2439 elif mark:
2436 ms.mark(f, "r")
2440 ms.mark(f, "r")
2437 elif unmark:
2441 elif unmark:
2438 ms.mark(f, "u")
2442 ms.mark(f, "u")
2439 else:
2443 else:
2440 wctx = repo[None]
2444 wctx = repo[None]
2441 mctx = wctx.parents()[-1]
2445 mctx = wctx.parents()[-1]
2442
2446
2443 # backup pre-resolve (merge uses .orig for its own purposes)
2447 # backup pre-resolve (merge uses .orig for its own purposes)
2444 a = repo.wjoin(f)
2448 a = repo.wjoin(f)
2445 util.copyfile(a, a + ".resolve")
2449 util.copyfile(a, a + ".resolve")
2446
2450
2447 # resolve file
2451 # resolve file
2448 ms.resolve(f, wctx, mctx)
2452 ms.resolve(f, wctx, mctx)
2449
2453
2450 # replace filemerge's .orig file with our resolve file
2454 # replace filemerge's .orig file with our resolve file
2451 util.rename(a + ".resolve", a + ".orig")
2455 util.rename(a + ".resolve", a + ".orig")
2452
2456
2453 def revert(ui, repo, *pats, **opts):
2457 def revert(ui, repo, *pats, **opts):
2454 """restore individual files or directories to an earlier state
2458 """restore individual files or directories to an earlier state
2455
2459
2456 (Use update -r to check out earlier revisions, revert does not change the
2460 (Use update -r to check out earlier revisions, revert does not change the
2457 working directory parents.)
2461 working directory parents.)
2458
2462
2459 With no revision specified, revert the named files or directories to the
2463 With no revision specified, revert the named files or directories to the
2460 contents they had in the parent of the working directory. This restores
2464 contents they had in the parent of the working directory. This restores
2461 the contents of the affected files to an unmodified state and unschedules
2465 the contents of the affected files to an unmodified state and unschedules
2462 adds, removes, copies, and renames. If the working directory has two
2466 adds, removes, copies, and renames. If the working directory has two
2463 parents, you must explicitly specify the revision to revert to.
2467 parents, you must explicitly specify the revision to revert to.
2464
2468
2465 Using the -r/--rev option, revert the given files or directories to their
2469 Using the -r/--rev option, revert the given files or directories to their
2466 contents as of a specific revision. This can be helpful to "roll back"
2470 contents as of a specific revision. This can be helpful to "roll back"
2467 some or all of an earlier change. See 'hg help dates' for a list of
2471 some or all of an earlier change. See 'hg help dates' for a list of
2468 formats valid for -d/--date.
2472 formats valid for -d/--date.
2469
2473
2470 Revert modifies the working directory. It does not commit any changes, or
2474 Revert modifies the working directory. It does not commit any changes, or
2471 change the parent of the working directory. If you revert to a revision
2475 change the parent of the working directory. If you revert to a revision
2472 other than the parent of the working directory, the reverted files will
2476 other than the parent of the working directory, the reverted files will
2473 thus appear modified afterwards.
2477 thus appear modified afterwards.
2474
2478
2475 If a file has been deleted, it is restored. If the executable mode of a
2479 If a file has been deleted, it is restored. If the executable mode of a
2476 file was changed, it is reset.
2480 file was changed, it is reset.
2477
2481
2478 If names are given, all files matching the names are reverted. If no
2482 If names are given, all files matching the names are reverted. If no
2479 arguments are given, no files are reverted.
2483 arguments are given, no files are reverted.
2480
2484
2481 Modified files are saved with a .orig suffix before reverting. To disable
2485 Modified files are saved with a .orig suffix before reverting. To disable
2482 these backups, use --no-backup.
2486 these backups, use --no-backup.
2483 """
2487 """
2484
2488
2485 if opts["date"]:
2489 if opts["date"]:
2486 if opts["rev"]:
2490 if opts["rev"]:
2487 raise util.Abort(_("you can't specify a revision and a date"))
2491 raise util.Abort(_("you can't specify a revision and a date"))
2488 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2492 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2489
2493
2490 if not pats and not opts.get('all'):
2494 if not pats and not opts.get('all'):
2491 raise util.Abort(_('no files or directories specified; '
2495 raise util.Abort(_('no files or directories specified; '
2492 'use --all to revert the whole repo'))
2496 'use --all to revert the whole repo'))
2493
2497
2494 parent, p2 = repo.dirstate.parents()
2498 parent, p2 = repo.dirstate.parents()
2495 if not opts.get('rev') and p2 != nullid:
2499 if not opts.get('rev') and p2 != nullid:
2496 raise util.Abort(_('uncommitted merge - please provide a '
2500 raise util.Abort(_('uncommitted merge - please provide a '
2497 'specific revision'))
2501 'specific revision'))
2498 ctx = repo[opts.get('rev')]
2502 ctx = repo[opts.get('rev')]
2499 node = ctx.node()
2503 node = ctx.node()
2500 mf = ctx.manifest()
2504 mf = ctx.manifest()
2501 if node == parent:
2505 if node == parent:
2502 pmf = mf
2506 pmf = mf
2503 else:
2507 else:
2504 pmf = None
2508 pmf = None
2505
2509
2506 # need all matching names in dirstate and manifest of target rev,
2510 # need all matching names in dirstate and manifest of target rev,
2507 # so have to walk both. do not print errors if files exist in one
2511 # so have to walk both. do not print errors if files exist in one
2508 # but not other.
2512 # but not other.
2509
2513
2510 names = {}
2514 names = {}
2511
2515
2512 wlock = repo.wlock()
2516 wlock = repo.wlock()
2513 try:
2517 try:
2514 # walk dirstate.
2518 # walk dirstate.
2515
2519
2516 m = cmdutil.match(repo, pats, opts)
2520 m = cmdutil.match(repo, pats, opts)
2517 m.bad = lambda x,y: False
2521 m.bad = lambda x,y: False
2518 for abs in repo.walk(m):
2522 for abs in repo.walk(m):
2519 names[abs] = m.rel(abs), m.exact(abs)
2523 names[abs] = m.rel(abs), m.exact(abs)
2520
2524
2521 # walk target manifest.
2525 # walk target manifest.
2522
2526
2523 def badfn(path, msg):
2527 def badfn(path, msg):
2524 if path in names:
2528 if path in names:
2525 return
2529 return
2526 path_ = path + '/'
2530 path_ = path + '/'
2527 for f in names:
2531 for f in names:
2528 if f.startswith(path_):
2532 if f.startswith(path_):
2529 return
2533 return
2530 ui.warn("%s: %s\n" % (m.rel(path), msg))
2534 ui.warn("%s: %s\n" % (m.rel(path), msg))
2531
2535
2532 m = cmdutil.match(repo, pats, opts)
2536 m = cmdutil.match(repo, pats, opts)
2533 m.bad = badfn
2537 m.bad = badfn
2534 for abs in repo[node].walk(m):
2538 for abs in repo[node].walk(m):
2535 if abs not in names:
2539 if abs not in names:
2536 names[abs] = m.rel(abs), m.exact(abs)
2540 names[abs] = m.rel(abs), m.exact(abs)
2537
2541
2538 m = cmdutil.matchfiles(repo, names)
2542 m = cmdutil.matchfiles(repo, names)
2539 changes = repo.status(match=m)[:4]
2543 changes = repo.status(match=m)[:4]
2540 modified, added, removed, deleted = map(set, changes)
2544 modified, added, removed, deleted = map(set, changes)
2541
2545
2542 # if f is a rename, also revert the source
2546 # if f is a rename, also revert the source
2543 cwd = repo.getcwd()
2547 cwd = repo.getcwd()
2544 for f in added:
2548 for f in added:
2545 src = repo.dirstate.copied(f)
2549 src = repo.dirstate.copied(f)
2546 if src and src not in names and repo.dirstate[src] == 'r':
2550 if src and src not in names and repo.dirstate[src] == 'r':
2547 removed.add(src)
2551 removed.add(src)
2548 names[src] = (repo.pathto(src, cwd), True)
2552 names[src] = (repo.pathto(src, cwd), True)
2549
2553
2550 def removeforget(abs):
2554 def removeforget(abs):
2551 if repo.dirstate[abs] == 'a':
2555 if repo.dirstate[abs] == 'a':
2552 return _('forgetting %s\n')
2556 return _('forgetting %s\n')
2553 return _('removing %s\n')
2557 return _('removing %s\n')
2554
2558
2555 revert = ([], _('reverting %s\n'))
2559 revert = ([], _('reverting %s\n'))
2556 add = ([], _('adding %s\n'))
2560 add = ([], _('adding %s\n'))
2557 remove = ([], removeforget)
2561 remove = ([], removeforget)
2558 undelete = ([], _('undeleting %s\n'))
2562 undelete = ([], _('undeleting %s\n'))
2559
2563
2560 disptable = (
2564 disptable = (
2561 # dispatch table:
2565 # dispatch table:
2562 # file state
2566 # file state
2563 # action if in target manifest
2567 # action if in target manifest
2564 # action if not in target manifest
2568 # action if not in target manifest
2565 # make backup if in target manifest
2569 # make backup if in target manifest
2566 # make backup if not in target manifest
2570 # make backup if not in target manifest
2567 (modified, revert, remove, True, True),
2571 (modified, revert, remove, True, True),
2568 (added, revert, remove, True, False),
2572 (added, revert, remove, True, False),
2569 (removed, undelete, None, False, False),
2573 (removed, undelete, None, False, False),
2570 (deleted, revert, remove, False, False),
2574 (deleted, revert, remove, False, False),
2571 )
2575 )
2572
2576
2573 for abs, (rel, exact) in sorted(names.items()):
2577 for abs, (rel, exact) in sorted(names.items()):
2574 mfentry = mf.get(abs)
2578 mfentry = mf.get(abs)
2575 target = repo.wjoin(abs)
2579 target = repo.wjoin(abs)
2576 def handle(xlist, dobackup):
2580 def handle(xlist, dobackup):
2577 xlist[0].append(abs)
2581 xlist[0].append(abs)
2578 if dobackup and not opts.get('no_backup') and util.lexists(target):
2582 if dobackup and not opts.get('no_backup') and util.lexists(target):
2579 bakname = "%s.orig" % rel
2583 bakname = "%s.orig" % rel
2580 ui.note(_('saving current version of %s as %s\n') %
2584 ui.note(_('saving current version of %s as %s\n') %
2581 (rel, bakname))
2585 (rel, bakname))
2582 if not opts.get('dry_run'):
2586 if not opts.get('dry_run'):
2583 util.copyfile(target, bakname)
2587 util.copyfile(target, bakname)
2584 if ui.verbose or not exact:
2588 if ui.verbose or not exact:
2585 msg = xlist[1]
2589 msg = xlist[1]
2586 if not isinstance(msg, basestring):
2590 if not isinstance(msg, basestring):
2587 msg = msg(abs)
2591 msg = msg(abs)
2588 ui.status(msg % rel)
2592 ui.status(msg % rel)
2589 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2593 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2590 if abs not in table: continue
2594 if abs not in table: continue
2591 # file has changed in dirstate
2595 # file has changed in dirstate
2592 if mfentry:
2596 if mfentry:
2593 handle(hitlist, backuphit)
2597 handle(hitlist, backuphit)
2594 elif misslist is not None:
2598 elif misslist is not None:
2595 handle(misslist, backupmiss)
2599 handle(misslist, backupmiss)
2596 break
2600 break
2597 else:
2601 else:
2598 if abs not in repo.dirstate:
2602 if abs not in repo.dirstate:
2599 if mfentry:
2603 if mfentry:
2600 handle(add, True)
2604 handle(add, True)
2601 elif exact:
2605 elif exact:
2602 ui.warn(_('file not managed: %s\n') % rel)
2606 ui.warn(_('file not managed: %s\n') % rel)
2603 continue
2607 continue
2604 # file has not changed in dirstate
2608 # file has not changed in dirstate
2605 if node == parent:
2609 if node == parent:
2606 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2610 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2607 continue
2611 continue
2608 if pmf is None:
2612 if pmf is None:
2609 # only need parent manifest in this unlikely case,
2613 # only need parent manifest in this unlikely case,
2610 # so do not read by default
2614 # so do not read by default
2611 pmf = repo[parent].manifest()
2615 pmf = repo[parent].manifest()
2612 if abs in pmf:
2616 if abs in pmf:
2613 if mfentry:
2617 if mfentry:
2614 # if version of file is same in parent and target
2618 # if version of file is same in parent and target
2615 # manifests, do nothing
2619 # manifests, do nothing
2616 if (pmf[abs] != mfentry or
2620 if (pmf[abs] != mfentry or
2617 pmf.flags(abs) != mf.flags(abs)):
2621 pmf.flags(abs) != mf.flags(abs)):
2618 handle(revert, False)
2622 handle(revert, False)
2619 else:
2623 else:
2620 handle(remove, False)
2624 handle(remove, False)
2621
2625
2622 if not opts.get('dry_run'):
2626 if not opts.get('dry_run'):
2623 def checkout(f):
2627 def checkout(f):
2624 fc = ctx[f]
2628 fc = ctx[f]
2625 repo.wwrite(f, fc.data(), fc.flags())
2629 repo.wwrite(f, fc.data(), fc.flags())
2626
2630
2627 audit_path = util.path_auditor(repo.root)
2631 audit_path = util.path_auditor(repo.root)
2628 for f in remove[0]:
2632 for f in remove[0]:
2629 if repo.dirstate[f] == 'a':
2633 if repo.dirstate[f] == 'a':
2630 repo.dirstate.forget(f)
2634 repo.dirstate.forget(f)
2631 continue
2635 continue
2632 audit_path(f)
2636 audit_path(f)
2633 try:
2637 try:
2634 util.unlink(repo.wjoin(f))
2638 util.unlink(repo.wjoin(f))
2635 except OSError:
2639 except OSError:
2636 pass
2640 pass
2637 repo.dirstate.remove(f)
2641 repo.dirstate.remove(f)
2638
2642
2639 normal = None
2643 normal = None
2640 if node == parent:
2644 if node == parent:
2641 # We're reverting to our parent. If possible, we'd like status
2645 # We're reverting to our parent. If possible, we'd like status
2642 # to report the file as clean. We have to use normallookup for
2646 # to report the file as clean. We have to use normallookup for
2643 # merges to avoid losing information about merged/dirty files.
2647 # merges to avoid losing information about merged/dirty files.
2644 if p2 != nullid:
2648 if p2 != nullid:
2645 normal = repo.dirstate.normallookup
2649 normal = repo.dirstate.normallookup
2646 else:
2650 else:
2647 normal = repo.dirstate.normal
2651 normal = repo.dirstate.normal
2648 for f in revert[0]:
2652 for f in revert[0]:
2649 checkout(f)
2653 checkout(f)
2650 if normal:
2654 if normal:
2651 normal(f)
2655 normal(f)
2652
2656
2653 for f in add[0]:
2657 for f in add[0]:
2654 checkout(f)
2658 checkout(f)
2655 repo.dirstate.add(f)
2659 repo.dirstate.add(f)
2656
2660
2657 normal = repo.dirstate.normallookup
2661 normal = repo.dirstate.normallookup
2658 if node == parent and p2 == nullid:
2662 if node == parent and p2 == nullid:
2659 normal = repo.dirstate.normal
2663 normal = repo.dirstate.normal
2660 for f in undelete[0]:
2664 for f in undelete[0]:
2661 checkout(f)
2665 checkout(f)
2662 normal(f)
2666 normal(f)
2663
2667
2664 finally:
2668 finally:
2665 wlock.release()
2669 wlock.release()
2666
2670
2667 def rollback(ui, repo):
2671 def rollback(ui, repo):
2668 """roll back the last transaction
2672 """roll back the last transaction
2669
2673
2670 This command should be used with care. There is only one level of
2674 This command should be used with care. There is only one level of
2671 rollback, and there is no way to undo a rollback. It will also restore the
2675 rollback, and there is no way to undo a rollback. It will also restore the
2672 dirstate at the time of the last transaction, losing any dirstate changes
2676 dirstate at the time of the last transaction, losing any dirstate changes
2673 since that time. This command does not alter the working directory.
2677 since that time. This command does not alter the working directory.
2674
2678
2675 Transactions are used to encapsulate the effects of all commands that
2679 Transactions are used to encapsulate the effects of all commands that
2676 create new changesets or propagate existing changesets into a repository.
2680 create new changesets or propagate existing changesets into a repository.
2677 For example, the following commands are transactional, and their effects
2681 For example, the following commands are transactional, and their effects
2678 can be rolled back:
2682 can be rolled back::
2679
2683
2680 commit
2684 commit
2681 import
2685 import
2682 pull
2686 pull
2683 push (with this repository as destination)
2687 push (with this repository as destination)
2684 unbundle
2688 unbundle
2685
2689
2686 This command is not intended for use on public repositories. Once changes
2690 This command is not intended for use on public repositories. Once changes
2687 are visible for pull by other users, rolling a transaction back locally is
2691 are visible for pull by other users, rolling a transaction back locally is
2688 ineffective (someone else may already have pulled the changes).
2692 ineffective (someone else may already have pulled the changes).
2689 Furthermore, a race is possible with readers of the repository; for
2693 Furthermore, a race is possible with readers of the repository; for
2690 example an in-progress pull from the repository may fail if a rollback is
2694 example an in-progress pull from the repository may fail if a rollback is
2691 performed.
2695 performed.
2692 """
2696 """
2693 repo.rollback()
2697 repo.rollback()
2694
2698
2695 def root(ui, repo):
2699 def root(ui, repo):
2696 """print the root (top) of the current working directory
2700 """print the root (top) of the current working directory
2697
2701
2698 Print the root directory of the current repository.
2702 Print the root directory of the current repository.
2699 """
2703 """
2700 ui.write(repo.root + "\n")
2704 ui.write(repo.root + "\n")
2701
2705
2702 def serve(ui, repo, **opts):
2706 def serve(ui, repo, **opts):
2703 """export the repository via HTTP
2707 """export the repository via HTTP
2704
2708
2705 Start a local HTTP repository browser and pull server.
2709 Start a local HTTP repository browser and pull server.
2706
2710
2707 By default, the server logs accesses to stdout and errors to stderr. Use
2711 By default, the server logs accesses to stdout and errors to stderr. Use
2708 the -A/--accesslog and -E/--errorlog options to log to files.
2712 the -A/--accesslog and -E/--errorlog options to log to files.
2709 """
2713 """
2710
2714
2711 if opts["stdio"]:
2715 if opts["stdio"]:
2712 if repo is None:
2716 if repo is None:
2713 raise error.RepoError(_("There is no Mercurial repository here"
2717 raise error.RepoError(_("There is no Mercurial repository here"
2714 " (.hg not found)"))
2718 " (.hg not found)"))
2715 s = sshserver.sshserver(ui, repo)
2719 s = sshserver.sshserver(ui, repo)
2716 s.serve_forever()
2720 s.serve_forever()
2717
2721
2718 baseui = repo and repo.baseui or ui
2722 baseui = repo and repo.baseui or ui
2719 optlist = ("name templates style address port prefix ipv6"
2723 optlist = ("name templates style address port prefix ipv6"
2720 " accesslog errorlog webdir_conf certificate encoding")
2724 " accesslog errorlog webdir_conf certificate encoding")
2721 for o in optlist.split():
2725 for o in optlist.split():
2722 if opts.get(o, None):
2726 if opts.get(o, None):
2723 baseui.setconfig("web", o, str(opts[o]))
2727 baseui.setconfig("web", o, str(opts[o]))
2724 if (repo is not None) and (repo.ui != baseui):
2728 if (repo is not None) and (repo.ui != baseui):
2725 repo.ui.setconfig("web", o, str(opts[o]))
2729 repo.ui.setconfig("web", o, str(opts[o]))
2726
2730
2727 if repo is None and not ui.config("web", "webdir_conf"):
2731 if repo is None and not ui.config("web", "webdir_conf"):
2728 raise error.RepoError(_("There is no Mercurial repository here"
2732 raise error.RepoError(_("There is no Mercurial repository here"
2729 " (.hg not found)"))
2733 " (.hg not found)"))
2730
2734
2731 class service(object):
2735 class service(object):
2732 def init(self):
2736 def init(self):
2733 util.set_signal_handler()
2737 util.set_signal_handler()
2734 self.httpd = server.create_server(baseui, repo)
2738 self.httpd = server.create_server(baseui, repo)
2735
2739
2736 if not ui.verbose: return
2740 if not ui.verbose: return
2737
2741
2738 if self.httpd.prefix:
2742 if self.httpd.prefix:
2739 prefix = self.httpd.prefix.strip('/') + '/'
2743 prefix = self.httpd.prefix.strip('/') + '/'
2740 else:
2744 else:
2741 prefix = ''
2745 prefix = ''
2742
2746
2743 port = ':%d' % self.httpd.port
2747 port = ':%d' % self.httpd.port
2744 if port == ':80':
2748 if port == ':80':
2745 port = ''
2749 port = ''
2746
2750
2747 bindaddr = self.httpd.addr
2751 bindaddr = self.httpd.addr
2748 if bindaddr == '0.0.0.0':
2752 if bindaddr == '0.0.0.0':
2749 bindaddr = '*'
2753 bindaddr = '*'
2750 elif ':' in bindaddr: # IPv6
2754 elif ':' in bindaddr: # IPv6
2751 bindaddr = '[%s]' % bindaddr
2755 bindaddr = '[%s]' % bindaddr
2752
2756
2753 fqaddr = self.httpd.fqaddr
2757 fqaddr = self.httpd.fqaddr
2754 if ':' in fqaddr:
2758 if ':' in fqaddr:
2755 fqaddr = '[%s]' % fqaddr
2759 fqaddr = '[%s]' % fqaddr
2756 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2760 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2757 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2761 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2758
2762
2759 def run(self):
2763 def run(self):
2760 self.httpd.serve_forever()
2764 self.httpd.serve_forever()
2761
2765
2762 service = service()
2766 service = service()
2763
2767
2764 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2768 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2765
2769
2766 def status(ui, repo, *pats, **opts):
2770 def status(ui, repo, *pats, **opts):
2767 """show changed files in the working directory
2771 """show changed files in the working directory
2768
2772
2769 Show status of files in the repository. If names are given, only files
2773 Show status of files in the repository. If names are given, only files
2770 that match are shown. Files that are clean or ignored or the source of a
2774 that match are shown. Files that are clean or ignored or the source of a
2771 copy/move operation, are not listed unless -c/--clean, -i/--ignored,
2775 copy/move operation, are not listed unless -c/--clean, -i/--ignored,
2772 -C/--copies or -A/--all are given. Unless options described with "show
2776 -C/--copies or -A/--all are given. Unless options described with "show
2773 only ..." are given, the options -mardu are used.
2777 only ..." are given, the options -mardu are used.
2774
2778
2775 Option -q/--quiet hides untracked (unknown and ignored) files unless
2779 Option -q/--quiet hides untracked (unknown and ignored) files unless
2776 explicitly requested with -u/--unknown or -i/--ignored.
2780 explicitly requested with -u/--unknown or -i/--ignored.
2777
2781
2778 NOTE: status may appear to disagree with diff if permissions have changed
2782 NOTE: status may appear to disagree with diff if permissions have changed
2779 or a merge has occurred. The standard diff format does not report
2783 or a merge has occurred. The standard diff format does not report
2780 permission changes and diff only reports changes relative to one merge
2784 permission changes and diff only reports changes relative to one merge
2781 parent.
2785 parent.
2782
2786
2783 If one revision is given, it is used as the base revision. If two
2787 If one revision is given, it is used as the base revision. If two
2784 revisions are given, the differences between them are shown.
2788 revisions are given, the differences between them are shown.
2785
2789
2786 The codes used to show the status of files are:
2790 The codes used to show the status of files are::
2791
2787 M = modified
2792 M = modified
2788 A = added
2793 A = added
2789 R = removed
2794 R = removed
2790 C = clean
2795 C = clean
2791 ! = missing (deleted by non-hg command, but still tracked)
2796 ! = missing (deleted by non-hg command, but still tracked)
2792 ? = not tracked
2797 ? = not tracked
2793 I = ignored
2798 I = ignored
2794 = origin of the previous file listed as A (added)
2799 = origin of the previous file listed as A (added)
2795 """
2800 """
2796
2801
2797 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2802 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2798 cwd = (pats and repo.getcwd()) or ''
2803 cwd = (pats and repo.getcwd()) or ''
2799 end = opts.get('print0') and '\0' or '\n'
2804 end = opts.get('print0') and '\0' or '\n'
2800 copy = {}
2805 copy = {}
2801 states = 'modified added removed deleted unknown ignored clean'.split()
2806 states = 'modified added removed deleted unknown ignored clean'.split()
2802 show = [k for k in states if opts.get(k)]
2807 show = [k for k in states if opts.get(k)]
2803 if opts.get('all'):
2808 if opts.get('all'):
2804 show += ui.quiet and (states[:4] + ['clean']) or states
2809 show += ui.quiet and (states[:4] + ['clean']) or states
2805 if not show:
2810 if not show:
2806 show = ui.quiet and states[:4] or states[:5]
2811 show = ui.quiet and states[:4] or states[:5]
2807
2812
2808 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
2813 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
2809 'ignored' in show, 'clean' in show, 'unknown' in show)
2814 'ignored' in show, 'clean' in show, 'unknown' in show)
2810 changestates = zip(states, 'MAR!?IC', stat)
2815 changestates = zip(states, 'MAR!?IC', stat)
2811
2816
2812 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
2817 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
2813 ctxn = repo[nullid]
2818 ctxn = repo[nullid]
2814 ctx1 = repo[node1]
2819 ctx1 = repo[node1]
2815 ctx2 = repo[node2]
2820 ctx2 = repo[node2]
2816 added = stat[1]
2821 added = stat[1]
2817 if node2 is None:
2822 if node2 is None:
2818 added = stat[0] + stat[1] # merged?
2823 added = stat[0] + stat[1] # merged?
2819
2824
2820 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
2825 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
2821 if k in added:
2826 if k in added:
2822 copy[k] = v
2827 copy[k] = v
2823 elif v in added:
2828 elif v in added:
2824 copy[v] = k
2829 copy[v] = k
2825
2830
2826 for state, char, files in changestates:
2831 for state, char, files in changestates:
2827 if state in show:
2832 if state in show:
2828 format = "%s %%s%s" % (char, end)
2833 format = "%s %%s%s" % (char, end)
2829 if opts.get('no_status'):
2834 if opts.get('no_status'):
2830 format = "%%s%s" % end
2835 format = "%%s%s" % end
2831
2836
2832 for f in files:
2837 for f in files:
2833 ui.write(format % repo.pathto(f, cwd))
2838 ui.write(format % repo.pathto(f, cwd))
2834 if f in copy:
2839 if f in copy:
2835 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
2840 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
2836
2841
2837 def tag(ui, repo, name1, *names, **opts):
2842 def tag(ui, repo, name1, *names, **opts):
2838 """add one or more tags for the current or given revision
2843 """add one or more tags for the current or given revision
2839
2844
2840 Name a particular revision using <name>.
2845 Name a particular revision using <name>.
2841
2846
2842 Tags are used to name particular revisions of the repository and are very
2847 Tags are used to name particular revisions of the repository and are very
2843 useful to compare different revisions, to go back to significant earlier
2848 useful to compare different revisions, to go back to significant earlier
2844 versions or to mark branch points as releases, etc.
2849 versions or to mark branch points as releases, etc.
2845
2850
2846 If no revision is given, the parent of the working directory is used, or
2851 If no revision is given, the parent of the working directory is used, or
2847 tip if no revision is checked out.
2852 tip if no revision is checked out.
2848
2853
2849 To facilitate version control, distribution, and merging of tags, they are
2854 To facilitate version control, distribution, and merging of tags, they are
2850 stored as a file named ".hgtags" which is managed similarly to other
2855 stored as a file named ".hgtags" which is managed similarly to other
2851 project files and can be hand-edited if necessary. The file
2856 project files and can be hand-edited if necessary. The file
2852 '.hg/localtags' is used for local tags (not shared among repositories).
2857 '.hg/localtags' is used for local tags (not shared among repositories).
2853
2858
2854 See 'hg help dates' for a list of formats valid for -d/--date.
2859 See 'hg help dates' for a list of formats valid for -d/--date.
2855 """
2860 """
2856
2861
2857 rev_ = "."
2862 rev_ = "."
2858 names = (name1,) + names
2863 names = (name1,) + names
2859 if len(names) != len(set(names)):
2864 if len(names) != len(set(names)):
2860 raise util.Abort(_('tag names must be unique'))
2865 raise util.Abort(_('tag names must be unique'))
2861 for n in names:
2866 for n in names:
2862 if n in ['tip', '.', 'null']:
2867 if n in ['tip', '.', 'null']:
2863 raise util.Abort(_('the name \'%s\' is reserved') % n)
2868 raise util.Abort(_('the name \'%s\' is reserved') % n)
2864 if opts.get('rev') and opts.get('remove'):
2869 if opts.get('rev') and opts.get('remove'):
2865 raise util.Abort(_("--rev and --remove are incompatible"))
2870 raise util.Abort(_("--rev and --remove are incompatible"))
2866 if opts.get('rev'):
2871 if opts.get('rev'):
2867 rev_ = opts['rev']
2872 rev_ = opts['rev']
2868 message = opts.get('message')
2873 message = opts.get('message')
2869 if opts.get('remove'):
2874 if opts.get('remove'):
2870 expectedtype = opts.get('local') and 'local' or 'global'
2875 expectedtype = opts.get('local') and 'local' or 'global'
2871 for n in names:
2876 for n in names:
2872 if not repo.tagtype(n):
2877 if not repo.tagtype(n):
2873 raise util.Abort(_('tag \'%s\' does not exist') % n)
2878 raise util.Abort(_('tag \'%s\' does not exist') % n)
2874 if repo.tagtype(n) != expectedtype:
2879 if repo.tagtype(n) != expectedtype:
2875 if expectedtype == 'global':
2880 if expectedtype == 'global':
2876 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
2881 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
2877 else:
2882 else:
2878 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
2883 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
2879 rev_ = nullid
2884 rev_ = nullid
2880 if not message:
2885 if not message:
2881 message = _('Removed tag %s') % ', '.join(names)
2886 message = _('Removed tag %s') % ', '.join(names)
2882 elif not opts.get('force'):
2887 elif not opts.get('force'):
2883 for n in names:
2888 for n in names:
2884 if n in repo.tags():
2889 if n in repo.tags():
2885 raise util.Abort(_('tag \'%s\' already exists '
2890 raise util.Abort(_('tag \'%s\' already exists '
2886 '(use -f to force)') % n)
2891 '(use -f to force)') % n)
2887 if not rev_ and repo.dirstate.parents()[1] != nullid:
2892 if not rev_ and repo.dirstate.parents()[1] != nullid:
2888 raise util.Abort(_('uncommitted merge - please provide a '
2893 raise util.Abort(_('uncommitted merge - please provide a '
2889 'specific revision'))
2894 'specific revision'))
2890 r = repo[rev_].node()
2895 r = repo[rev_].node()
2891
2896
2892 if not message:
2897 if not message:
2893 message = (_('Added tag %s for changeset %s') %
2898 message = (_('Added tag %s for changeset %s') %
2894 (', '.join(names), short(r)))
2899 (', '.join(names), short(r)))
2895
2900
2896 date = opts.get('date')
2901 date = opts.get('date')
2897 if date:
2902 if date:
2898 date = util.parsedate(date)
2903 date = util.parsedate(date)
2899
2904
2900 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
2905 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
2901
2906
2902 def tags(ui, repo):
2907 def tags(ui, repo):
2903 """list repository tags
2908 """list repository tags
2904
2909
2905 This lists both regular and local tags. When the -v/--verbose switch is
2910 This lists both regular and local tags. When the -v/--verbose switch is
2906 used, a third column "local" is printed for local tags.
2911 used, a third column "local" is printed for local tags.
2907 """
2912 """
2908
2913
2909 hexfunc = ui.debugflag and hex or short
2914 hexfunc = ui.debugflag and hex or short
2910 tagtype = ""
2915 tagtype = ""
2911
2916
2912 for t, n in reversed(repo.tagslist()):
2917 for t, n in reversed(repo.tagslist()):
2913 if ui.quiet:
2918 if ui.quiet:
2914 ui.write("%s\n" % t)
2919 ui.write("%s\n" % t)
2915 continue
2920 continue
2916
2921
2917 try:
2922 try:
2918 hn = hexfunc(n)
2923 hn = hexfunc(n)
2919 r = "%5d:%s" % (repo.changelog.rev(n), hn)
2924 r = "%5d:%s" % (repo.changelog.rev(n), hn)
2920 except error.LookupError:
2925 except error.LookupError:
2921 r = " ?:%s" % hn
2926 r = " ?:%s" % hn
2922 else:
2927 else:
2923 spaces = " " * (30 - encoding.colwidth(t))
2928 spaces = " " * (30 - encoding.colwidth(t))
2924 if ui.verbose:
2929 if ui.verbose:
2925 if repo.tagtype(t) == 'local':
2930 if repo.tagtype(t) == 'local':
2926 tagtype = " local"
2931 tagtype = " local"
2927 else:
2932 else:
2928 tagtype = ""
2933 tagtype = ""
2929 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
2934 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
2930
2935
2931 def tip(ui, repo, **opts):
2936 def tip(ui, repo, **opts):
2932 """show the tip revision
2937 """show the tip revision
2933
2938
2934 The tip revision (usually just called the tip) is the changeset most
2939 The tip revision (usually just called the tip) is the changeset most
2935 recently added to the repository (and therefore the most recently changed
2940 recently added to the repository (and therefore the most recently changed
2936 head).
2941 head).
2937
2942
2938 If you have just made a commit, that commit will be the tip. If you have
2943 If you have just made a commit, that commit will be the tip. If you have
2939 just pulled changes from another repository, the tip of that repository
2944 just pulled changes from another repository, the tip of that repository
2940 becomes the current tip. The "tip" tag is special and cannot be renamed or
2945 becomes the current tip. The "tip" tag is special and cannot be renamed or
2941 assigned to a different changeset.
2946 assigned to a different changeset.
2942 """
2947 """
2943 cmdutil.show_changeset(ui, repo, opts).show(repo[len(repo) - 1])
2948 cmdutil.show_changeset(ui, repo, opts).show(repo[len(repo) - 1])
2944
2949
2945 def unbundle(ui, repo, fname1, *fnames, **opts):
2950 def unbundle(ui, repo, fname1, *fnames, **opts):
2946 """apply one or more changegroup files
2951 """apply one or more changegroup files
2947
2952
2948 Apply one or more compressed changegroup files generated by the bundle
2953 Apply one or more compressed changegroup files generated by the bundle
2949 command.
2954 command.
2950 """
2955 """
2951 fnames = (fname1,) + fnames
2956 fnames = (fname1,) + fnames
2952
2957
2953 lock = repo.lock()
2958 lock = repo.lock()
2954 try:
2959 try:
2955 for fname in fnames:
2960 for fname in fnames:
2956 f = url.open(ui, fname)
2961 f = url.open(ui, fname)
2957 gen = changegroup.readbundle(f, fname)
2962 gen = changegroup.readbundle(f, fname)
2958 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2963 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2959 finally:
2964 finally:
2960 lock.release()
2965 lock.release()
2961
2966
2962 return postincoming(ui, repo, modheads, opts.get('update'), None)
2967 return postincoming(ui, repo, modheads, opts.get('update'), None)
2963
2968
2964 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
2969 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
2965 """update working directory
2970 """update working directory
2966
2971
2967 Update the repository's working directory to the specified revision, or
2972 Update the repository's working directory to the specified revision, or
2968 the tip of the current branch if none is specified. Use null as the
2973 the tip of the current branch if none is specified. Use null as the
2969 revision to remove the working copy (like 'hg clone -U').
2974 revision to remove the working copy (like 'hg clone -U').
2970
2975
2971 When the working directory contains no uncommitted changes, it will be
2976 When the working directory contains no uncommitted changes, it will be
2972 replaced by the state of the requested revision from the repository. When
2977 replaced by the state of the requested revision from the repository. When
2973 the requested revision is on a different branch, the working directory
2978 the requested revision is on a different branch, the working directory
2974 will additionally be switched to that branch.
2979 will additionally be switched to that branch.
2975
2980
2976 When there are uncommitted changes, use option -C/--clean to discard them,
2981 When there are uncommitted changes, use option -C/--clean to discard them,
2977 forcibly replacing the state of the working directory with the requested
2982 forcibly replacing the state of the working directory with the requested
2978 revision. Alternately, use -c/--check to abort.
2983 revision. Alternately, use -c/--check to abort.
2979
2984
2980 When there are uncommitted changes and option -C/--clean is not used, and
2985 When there are uncommitted changes and option -C/--clean is not used, and
2981 the parent revision and requested revision are on the same branch, and one
2986 the parent revision and requested revision are on the same branch, and one
2982 of them is an ancestor of the other, then the new working directory will
2987 of them is an ancestor of the other, then the new working directory will
2983 contain the requested revision merged with the uncommitted changes.
2988 contain the requested revision merged with the uncommitted changes.
2984 Otherwise, the update will fail with a suggestion to use 'merge' or
2989 Otherwise, the update will fail with a suggestion to use 'merge' or
2985 'update -C' instead.
2990 'update -C' instead.
2986
2991
2987 If you want to update just one file to an older revision, use revert.
2992 If you want to update just one file to an older revision, use revert.
2988
2993
2989 See 'hg help dates' for a list of formats valid for -d/--date.
2994 See 'hg help dates' for a list of formats valid for -d/--date.
2990 """
2995 """
2991 if rev and node:
2996 if rev and node:
2992 raise util.Abort(_("please specify just one revision"))
2997 raise util.Abort(_("please specify just one revision"))
2993
2998
2994 if not rev:
2999 if not rev:
2995 rev = node
3000 rev = node
2996
3001
2997 if not clean and check:
3002 if not clean and check:
2998 # we could use dirty() but we can ignore merge and branch trivia
3003 # we could use dirty() but we can ignore merge and branch trivia
2999 c = repo[None]
3004 c = repo[None]
3000 if c.modified() or c.added() or c.removed():
3005 if c.modified() or c.added() or c.removed():
3001 raise util.Abort(_("uncommitted local changes"))
3006 raise util.Abort(_("uncommitted local changes"))
3002
3007
3003 if date:
3008 if date:
3004 if rev:
3009 if rev:
3005 raise util.Abort(_("you can't specify a revision and a date"))
3010 raise util.Abort(_("you can't specify a revision and a date"))
3006 rev = cmdutil.finddate(ui, repo, date)
3011 rev = cmdutil.finddate(ui, repo, date)
3007
3012
3008 if clean:
3013 if clean:
3009 return hg.clean(repo, rev)
3014 return hg.clean(repo, rev)
3010 else:
3015 else:
3011 return hg.update(repo, rev)
3016 return hg.update(repo, rev)
3012
3017
3013 def verify(ui, repo):
3018 def verify(ui, repo):
3014 """verify the integrity of the repository
3019 """verify the integrity of the repository
3015
3020
3016 Verify the integrity of the current repository.
3021 Verify the integrity of the current repository.
3017
3022
3018 This will perform an extensive check of the repository's integrity,
3023 This will perform an extensive check of the repository's integrity,
3019 validating the hashes and checksums of each entry in the changelog,
3024 validating the hashes and checksums of each entry in the changelog,
3020 manifest, and tracked files, as well as the integrity of their crosslinks
3025 manifest, and tracked files, as well as the integrity of their crosslinks
3021 and indices.
3026 and indices.
3022 """
3027 """
3023 return hg.verify(repo)
3028 return hg.verify(repo)
3024
3029
3025 def version_(ui):
3030 def version_(ui):
3026 """output version and copyright information"""
3031 """output version and copyright information"""
3027 ui.write(_("Mercurial Distributed SCM (version %s)\n")
3032 ui.write(_("Mercurial Distributed SCM (version %s)\n")
3028 % util.version())
3033 % util.version())
3029 ui.status(_(
3034 ui.status(_(
3030 "\nCopyright (C) 2005-2009 Matt Mackall <mpm@selenic.com> and others\n"
3035 "\nCopyright (C) 2005-2009 Matt Mackall <mpm@selenic.com> and others\n"
3031 "This is free software; see the source for copying conditions. "
3036 "This is free software; see the source for copying conditions. "
3032 "There is NO\nwarranty; "
3037 "There is NO\nwarranty; "
3033 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
3038 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
3034 ))
3039 ))
3035
3040
3036 # Command options and aliases are listed here, alphabetically
3041 # Command options and aliases are listed here, alphabetically
3037
3042
3038 globalopts = [
3043 globalopts = [
3039 ('R', 'repository', '',
3044 ('R', 'repository', '',
3040 _('repository root directory or symbolic path name')),
3045 _('repository root directory or symbolic path name')),
3041 ('', 'cwd', '', _('change working directory')),
3046 ('', 'cwd', '', _('change working directory')),
3042 ('y', 'noninteractive', None,
3047 ('y', 'noninteractive', None,
3043 _('do not prompt, assume \'yes\' for any required answers')),
3048 _('do not prompt, assume \'yes\' for any required answers')),
3044 ('q', 'quiet', None, _('suppress output')),
3049 ('q', 'quiet', None, _('suppress output')),
3045 ('v', 'verbose', None, _('enable additional output')),
3050 ('v', 'verbose', None, _('enable additional output')),
3046 ('', 'config', [], _('set/override config option')),
3051 ('', 'config', [], _('set/override config option')),
3047 ('', 'debug', None, _('enable debugging output')),
3052 ('', 'debug', None, _('enable debugging output')),
3048 ('', 'debugger', None, _('start debugger')),
3053 ('', 'debugger', None, _('start debugger')),
3049 ('', 'encoding', encoding.encoding, _('set the charset encoding')),
3054 ('', 'encoding', encoding.encoding, _('set the charset encoding')),
3050 ('', 'encodingmode', encoding.encodingmode,
3055 ('', 'encodingmode', encoding.encodingmode,
3051 _('set the charset encoding mode')),
3056 _('set the charset encoding mode')),
3052 ('', 'traceback', None, _('print traceback on exception')),
3057 ('', 'traceback', None, _('print traceback on exception')),
3053 ('', 'time', None, _('time how long the command takes')),
3058 ('', 'time', None, _('time how long the command takes')),
3054 ('', 'profile', None, _('print command execution profile')),
3059 ('', 'profile', None, _('print command execution profile')),
3055 ('', 'version', None, _('output version information and exit')),
3060 ('', 'version', None, _('output version information and exit')),
3056 ('h', 'help', None, _('display help and exit')),
3061 ('h', 'help', None, _('display help and exit')),
3057 ]
3062 ]
3058
3063
3059 dryrunopts = [('n', 'dry-run', None,
3064 dryrunopts = [('n', 'dry-run', None,
3060 _('do not perform actions, just print output'))]
3065 _('do not perform actions, just print output'))]
3061
3066
3062 remoteopts = [
3067 remoteopts = [
3063 ('e', 'ssh', '', _('specify ssh command to use')),
3068 ('e', 'ssh', '', _('specify ssh command to use')),
3064 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
3069 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
3065 ]
3070 ]
3066
3071
3067 walkopts = [
3072 walkopts = [
3068 ('I', 'include', [], _('include names matching the given patterns')),
3073 ('I', 'include', [], _('include names matching the given patterns')),
3069 ('X', 'exclude', [], _('exclude names matching the given patterns')),
3074 ('X', 'exclude', [], _('exclude names matching the given patterns')),
3070 ]
3075 ]
3071
3076
3072 commitopts = [
3077 commitopts = [
3073 ('m', 'message', '', _('use <text> as commit message')),
3078 ('m', 'message', '', _('use <text> as commit message')),
3074 ('l', 'logfile', '', _('read commit message from <file>')),
3079 ('l', 'logfile', '', _('read commit message from <file>')),
3075 ]
3080 ]
3076
3081
3077 commitopts2 = [
3082 commitopts2 = [
3078 ('d', 'date', '', _('record datecode as commit date')),
3083 ('d', 'date', '', _('record datecode as commit date')),
3079 ('u', 'user', '', _('record the specified user as committer')),
3084 ('u', 'user', '', _('record the specified user as committer')),
3080 ]
3085 ]
3081
3086
3082 templateopts = [
3087 templateopts = [
3083 ('', 'style', '', _('display using template map file')),
3088 ('', 'style', '', _('display using template map file')),
3084 ('', 'template', '', _('display with template')),
3089 ('', 'template', '', _('display with template')),
3085 ]
3090 ]
3086
3091
3087 logopts = [
3092 logopts = [
3088 ('p', 'patch', None, _('show patch')),
3093 ('p', 'patch', None, _('show patch')),
3089 ('g', 'git', None, _('use git extended diff format')),
3094 ('g', 'git', None, _('use git extended diff format')),
3090 ('l', 'limit', '', _('limit number of changes displayed')),
3095 ('l', 'limit', '', _('limit number of changes displayed')),
3091 ('M', 'no-merges', None, _('do not show merges')),
3096 ('M', 'no-merges', None, _('do not show merges')),
3092 ] + templateopts
3097 ] + templateopts
3093
3098
3094 diffopts = [
3099 diffopts = [
3095 ('a', 'text', None, _('treat all files as text')),
3100 ('a', 'text', None, _('treat all files as text')),
3096 ('g', 'git', None, _('use git extended diff format')),
3101 ('g', 'git', None, _('use git extended diff format')),
3097 ('', 'nodates', None, _("don't include dates in diff headers"))
3102 ('', 'nodates', None, _("don't include dates in diff headers"))
3098 ]
3103 ]
3099
3104
3100 diffopts2 = [
3105 diffopts2 = [
3101 ('p', 'show-function', None, _('show which function each change is in')),
3106 ('p', 'show-function', None, _('show which function each change is in')),
3102 ('w', 'ignore-all-space', None,
3107 ('w', 'ignore-all-space', None,
3103 _('ignore white space when comparing lines')),
3108 _('ignore white space when comparing lines')),
3104 ('b', 'ignore-space-change', None,
3109 ('b', 'ignore-space-change', None,
3105 _('ignore changes in the amount of white space')),
3110 _('ignore changes in the amount of white space')),
3106 ('B', 'ignore-blank-lines', None,
3111 ('B', 'ignore-blank-lines', None,
3107 _('ignore changes whose lines are all blank')),
3112 _('ignore changes whose lines are all blank')),
3108 ('U', 'unified', '', _('number of lines of context to show'))
3113 ('U', 'unified', '', _('number of lines of context to show'))
3109 ]
3114 ]
3110
3115
3111 similarityopts = [
3116 similarityopts = [
3112 ('s', 'similarity', '',
3117 ('s', 'similarity', '',
3113 _('guess renamed files by similarity (0<=s<=100)'))
3118 _('guess renamed files by similarity (0<=s<=100)'))
3114 ]
3119 ]
3115
3120
3116 table = {
3121 table = {
3117 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
3122 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
3118 "addremove":
3123 "addremove":
3119 (addremove, similarityopts + walkopts + dryrunopts,
3124 (addremove, similarityopts + walkopts + dryrunopts,
3120 _('[OPTION]... [FILE]...')),
3125 _('[OPTION]... [FILE]...')),
3121 "^annotate|blame":
3126 "^annotate|blame":
3122 (annotate,
3127 (annotate,
3123 [('r', 'rev', '', _('annotate the specified revision')),
3128 [('r', 'rev', '', _('annotate the specified revision')),
3124 ('f', 'follow', None, _('follow file copies and renames')),
3129 ('f', 'follow', None, _('follow file copies and renames')),
3125 ('a', 'text', None, _('treat all files as text')),
3130 ('a', 'text', None, _('treat all files as text')),
3126 ('u', 'user', None, _('list the author (long with -v)')),
3131 ('u', 'user', None, _('list the author (long with -v)')),
3127 ('d', 'date', None, _('list the date (short with -q)')),
3132 ('d', 'date', None, _('list the date (short with -q)')),
3128 ('n', 'number', None, _('list the revision number (default)')),
3133 ('n', 'number', None, _('list the revision number (default)')),
3129 ('c', 'changeset', None, _('list the changeset')),
3134 ('c', 'changeset', None, _('list the changeset')),
3130 ('l', 'line-number', None,
3135 ('l', 'line-number', None,
3131 _('show line number at the first appearance'))
3136 _('show line number at the first appearance'))
3132 ] + walkopts,
3137 ] + walkopts,
3133 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
3138 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
3134 "archive":
3139 "archive":
3135 (archive,
3140 (archive,
3136 [('', 'no-decode', None, _('do not pass files through decoders')),
3141 [('', 'no-decode', None, _('do not pass files through decoders')),
3137 ('p', 'prefix', '', _('directory prefix for files in archive')),
3142 ('p', 'prefix', '', _('directory prefix for files in archive')),
3138 ('r', 'rev', '', _('revision to distribute')),
3143 ('r', 'rev', '', _('revision to distribute')),
3139 ('t', 'type', '', _('type of distribution to create')),
3144 ('t', 'type', '', _('type of distribution to create')),
3140 ] + walkopts,
3145 ] + walkopts,
3141 _('[OPTION]... DEST')),
3146 _('[OPTION]... DEST')),
3142 "backout":
3147 "backout":
3143 (backout,
3148 (backout,
3144 [('', 'merge', None,
3149 [('', 'merge', None,
3145 _('merge with old dirstate parent after backout')),
3150 _('merge with old dirstate parent after backout')),
3146 ('', 'parent', '', _('parent to choose when backing out merge')),
3151 ('', 'parent', '', _('parent to choose when backing out merge')),
3147 ('r', 'rev', '', _('revision to backout')),
3152 ('r', 'rev', '', _('revision to backout')),
3148 ] + walkopts + commitopts + commitopts2,
3153 ] + walkopts + commitopts + commitopts2,
3149 _('[OPTION]... [-r] REV')),
3154 _('[OPTION]... [-r] REV')),
3150 "bisect":
3155 "bisect":
3151 (bisect,
3156 (bisect,
3152 [('r', 'reset', False, _('reset bisect state')),
3157 [('r', 'reset', False, _('reset bisect state')),
3153 ('g', 'good', False, _('mark changeset good')),
3158 ('g', 'good', False, _('mark changeset good')),
3154 ('b', 'bad', False, _('mark changeset bad')),
3159 ('b', 'bad', False, _('mark changeset bad')),
3155 ('s', 'skip', False, _('skip testing changeset')),
3160 ('s', 'skip', False, _('skip testing changeset')),
3156 ('c', 'command', '', _('use command to check changeset state')),
3161 ('c', 'command', '', _('use command to check changeset state')),
3157 ('U', 'noupdate', False, _('do not update to target'))],
3162 ('U', 'noupdate', False, _('do not update to target'))],
3158 _("[-gbsr] [-c CMD] [REV]")),
3163 _("[-gbsr] [-c CMD] [REV]")),
3159 "branch":
3164 "branch":
3160 (branch,
3165 (branch,
3161 [('f', 'force', None,
3166 [('f', 'force', None,
3162 _('set branch name even if it shadows an existing branch')),
3167 _('set branch name even if it shadows an existing branch')),
3163 ('C', 'clean', None, _('reset branch name to parent branch name'))],
3168 ('C', 'clean', None, _('reset branch name to parent branch name'))],
3164 _('[-fC] [NAME]')),
3169 _('[-fC] [NAME]')),
3165 "branches":
3170 "branches":
3166 (branches,
3171 (branches,
3167 [('a', 'active', False,
3172 [('a', 'active', False,
3168 _('show only branches that have unmerged heads')),
3173 _('show only branches that have unmerged heads')),
3169 ('c', 'closed', False,
3174 ('c', 'closed', False,
3170 _('show normal and closed heads'))],
3175 _('show normal and closed heads'))],
3171 _('[-a]')),
3176 _('[-a]')),
3172 "bundle":
3177 "bundle":
3173 (bundle,
3178 (bundle,
3174 [('f', 'force', None,
3179 [('f', 'force', None,
3175 _('run even when remote repository is unrelated')),
3180 _('run even when remote repository is unrelated')),
3176 ('r', 'rev', [],
3181 ('r', 'rev', [],
3177 _('a changeset up to which you would like to bundle')),
3182 _('a changeset up to which you would like to bundle')),
3178 ('', 'base', [],
3183 ('', 'base', [],
3179 _('a base changeset to specify instead of a destination')),
3184 _('a base changeset to specify instead of a destination')),
3180 ('a', 'all', None, _('bundle all changesets in the repository')),
3185 ('a', 'all', None, _('bundle all changesets in the repository')),
3181 ('t', 'type', 'bzip2', _('bundle compression type to use')),
3186 ('t', 'type', 'bzip2', _('bundle compression type to use')),
3182 ] + remoteopts,
3187 ] + remoteopts,
3183 _('[-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3188 _('[-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3184 "cat":
3189 "cat":
3185 (cat,
3190 (cat,
3186 [('o', 'output', '', _('print output to file with formatted name')),
3191 [('o', 'output', '', _('print output to file with formatted name')),
3187 ('r', 'rev', '', _('print the given revision')),
3192 ('r', 'rev', '', _('print the given revision')),
3188 ('', 'decode', None, _('apply any matching decode filter')),
3193 ('', 'decode', None, _('apply any matching decode filter')),
3189 ] + walkopts,
3194 ] + walkopts,
3190 _('[OPTION]... FILE...')),
3195 _('[OPTION]... FILE...')),
3191 "^clone":
3196 "^clone":
3192 (clone,
3197 (clone,
3193 [('U', 'noupdate', None,
3198 [('U', 'noupdate', None,
3194 _('the clone will only contain a repository (no working copy)')),
3199 _('the clone will only contain a repository (no working copy)')),
3195 ('r', 'rev', [],
3200 ('r', 'rev', [],
3196 _('a changeset you would like to have after cloning')),
3201 _('a changeset you would like to have after cloning')),
3197 ('', 'pull', None, _('use pull protocol to copy metadata')),
3202 ('', 'pull', None, _('use pull protocol to copy metadata')),
3198 ('', 'uncompressed', None,
3203 ('', 'uncompressed', None,
3199 _('use uncompressed transfer (fast over LAN)')),
3204 _('use uncompressed transfer (fast over LAN)')),
3200 ] + remoteopts,
3205 ] + remoteopts,
3201 _('[OPTION]... SOURCE [DEST]')),
3206 _('[OPTION]... SOURCE [DEST]')),
3202 "^commit|ci":
3207 "^commit|ci":
3203 (commit,
3208 (commit,
3204 [('A', 'addremove', None,
3209 [('A', 'addremove', None,
3205 _('mark new/missing files as added/removed before committing')),
3210 _('mark new/missing files as added/removed before committing')),
3206 ('', 'close-branch', None,
3211 ('', 'close-branch', None,
3207 _('mark a branch as closed, hiding it from the branch list')),
3212 _('mark a branch as closed, hiding it from the branch list')),
3208 ] + walkopts + commitopts + commitopts2,
3213 ] + walkopts + commitopts + commitopts2,
3209 _('[OPTION]... [FILE]...')),
3214 _('[OPTION]... [FILE]...')),
3210 "copy|cp":
3215 "copy|cp":
3211 (copy,
3216 (copy,
3212 [('A', 'after', None, _('record a copy that has already occurred')),
3217 [('A', 'after', None, _('record a copy that has already occurred')),
3213 ('f', 'force', None,
3218 ('f', 'force', None,
3214 _('forcibly copy over an existing managed file')),
3219 _('forcibly copy over an existing managed file')),
3215 ] + walkopts + dryrunopts,
3220 ] + walkopts + dryrunopts,
3216 _('[OPTION]... [SOURCE]... DEST')),
3221 _('[OPTION]... [SOURCE]... DEST')),
3217 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
3222 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
3218 "debugcheckstate": (debugcheckstate, []),
3223 "debugcheckstate": (debugcheckstate, []),
3219 "debugcommands": (debugcommands, [], _('[COMMAND]')),
3224 "debugcommands": (debugcommands, [], _('[COMMAND]')),
3220 "debugcomplete":
3225 "debugcomplete":
3221 (debugcomplete,
3226 (debugcomplete,
3222 [('o', 'options', None, _('show the command options'))],
3227 [('o', 'options', None, _('show the command options'))],
3223 _('[-o] CMD')),
3228 _('[-o] CMD')),
3224 "debugdate":
3229 "debugdate":
3225 (debugdate,
3230 (debugdate,
3226 [('e', 'extended', None, _('try extended date formats'))],
3231 [('e', 'extended', None, _('try extended date formats'))],
3227 _('[-e] DATE [RANGE]')),
3232 _('[-e] DATE [RANGE]')),
3228 "debugdata": (debugdata, [], _('FILE REV')),
3233 "debugdata": (debugdata, [], _('FILE REV')),
3229 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
3234 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
3230 "debugindex": (debugindex, [], _('FILE')),
3235 "debugindex": (debugindex, [], _('FILE')),
3231 "debugindexdot": (debugindexdot, [], _('FILE')),
3236 "debugindexdot": (debugindexdot, [], _('FILE')),
3232 "debuginstall": (debuginstall, []),
3237 "debuginstall": (debuginstall, []),
3233 "debugrebuildstate":
3238 "debugrebuildstate":
3234 (debugrebuildstate,
3239 (debugrebuildstate,
3235 [('r', 'rev', '', _('revision to rebuild to'))],
3240 [('r', 'rev', '', _('revision to rebuild to'))],
3236 _('[-r REV] [REV]')),
3241 _('[-r REV] [REV]')),
3237 "debugrename":
3242 "debugrename":
3238 (debugrename,
3243 (debugrename,
3239 [('r', 'rev', '', _('revision to debug'))],
3244 [('r', 'rev', '', _('revision to debug'))],
3240 _('[-r REV] FILE')),
3245 _('[-r REV] FILE')),
3241 "debugsetparents":
3246 "debugsetparents":
3242 (debugsetparents, [], _('REV1 [REV2]')),
3247 (debugsetparents, [], _('REV1 [REV2]')),
3243 "debugstate":
3248 "debugstate":
3244 (debugstate,
3249 (debugstate,
3245 [('', 'nodates', None, _('do not display the saved mtime'))],
3250 [('', 'nodates', None, _('do not display the saved mtime'))],
3246 _('[OPTION]...')),
3251 _('[OPTION]...')),
3247 "debugsub":
3252 "debugsub":
3248 (debugsub,
3253 (debugsub,
3249 [('r', 'rev', '', _('revision to check'))],
3254 [('r', 'rev', '', _('revision to check'))],
3250 _('[-r REV] [REV]')),
3255 _('[-r REV] [REV]')),
3251 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
3256 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
3252 "^diff":
3257 "^diff":
3253 (diff,
3258 (diff,
3254 [('r', 'rev', [], _('revision')),
3259 [('r', 'rev', [], _('revision')),
3255 ('c', 'change', '', _('change made by revision'))
3260 ('c', 'change', '', _('change made by revision'))
3256 ] + diffopts + diffopts2 + walkopts,
3261 ] + diffopts + diffopts2 + walkopts,
3257 _('[OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
3262 _('[OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
3258 "^export":
3263 "^export":
3259 (export,
3264 (export,
3260 [('o', 'output', '', _('print output to file with formatted name')),
3265 [('o', 'output', '', _('print output to file with formatted name')),
3261 ('', 'switch-parent', None, _('diff against the second parent'))
3266 ('', 'switch-parent', None, _('diff against the second parent'))
3262 ] + diffopts,
3267 ] + diffopts,
3263 _('[OPTION]... [-o OUTFILESPEC] REV...')),
3268 _('[OPTION]... [-o OUTFILESPEC] REV...')),
3264 "^forget":
3269 "^forget":
3265 (forget,
3270 (forget,
3266 [] + walkopts,
3271 [] + walkopts,
3267 _('[OPTION]... FILE...')),
3272 _('[OPTION]... FILE...')),
3268 "grep":
3273 "grep":
3269 (grep,
3274 (grep,
3270 [('0', 'print0', None, _('end fields with NUL')),
3275 [('0', 'print0', None, _('end fields with NUL')),
3271 ('', 'all', None, _('print all revisions that match')),
3276 ('', 'all', None, _('print all revisions that match')),
3272 ('f', 'follow', None,
3277 ('f', 'follow', None,
3273 _('follow changeset history, or file history across copies and renames')),
3278 _('follow changeset history, or file history across copies and renames')),
3274 ('i', 'ignore-case', None, _('ignore case when matching')),
3279 ('i', 'ignore-case', None, _('ignore case when matching')),
3275 ('l', 'files-with-matches', None,
3280 ('l', 'files-with-matches', None,
3276 _('print only filenames and revisions that match')),
3281 _('print only filenames and revisions that match')),
3277 ('n', 'line-number', None, _('print matching line numbers')),
3282 ('n', 'line-number', None, _('print matching line numbers')),
3278 ('r', 'rev', [], _('search in given revision range')),
3283 ('r', 'rev', [], _('search in given revision range')),
3279 ('u', 'user', None, _('list the author (long with -v)')),
3284 ('u', 'user', None, _('list the author (long with -v)')),
3280 ('d', 'date', None, _('list the date (short with -q)')),
3285 ('d', 'date', None, _('list the date (short with -q)')),
3281 ] + walkopts,
3286 ] + walkopts,
3282 _('[OPTION]... PATTERN [FILE]...')),
3287 _('[OPTION]... PATTERN [FILE]...')),
3283 "heads":
3288 "heads":
3284 (heads,
3289 (heads,
3285 [('r', 'rev', '', _('show only heads which are descendants of REV')),
3290 [('r', 'rev', '', _('show only heads which are descendants of REV')),
3286 ('a', 'active', False,
3291 ('a', 'active', False,
3287 _('show only the active heads from open branches')),
3292 _('show only the active heads from open branches')),
3288 ('c', 'closed', False,
3293 ('c', 'closed', False,
3289 _('show normal and closed heads')),
3294 _('show normal and closed heads')),
3290 ] + templateopts,
3295 ] + templateopts,
3291 _('[-r STARTREV] [REV]...')),
3296 _('[-r STARTREV] [REV]...')),
3292 "help": (help_, [], _('[TOPIC]')),
3297 "help": (help_, [], _('[TOPIC]')),
3293 "identify|id":
3298 "identify|id":
3294 (identify,
3299 (identify,
3295 [('r', 'rev', '', _('identify the specified revision')),
3300 [('r', 'rev', '', _('identify the specified revision')),
3296 ('n', 'num', None, _('show local revision number')),
3301 ('n', 'num', None, _('show local revision number')),
3297 ('i', 'id', None, _('show global revision id')),
3302 ('i', 'id', None, _('show global revision id')),
3298 ('b', 'branch', None, _('show branch')),
3303 ('b', 'branch', None, _('show branch')),
3299 ('t', 'tags', None, _('show tags'))],
3304 ('t', 'tags', None, _('show tags'))],
3300 _('[-nibt] [-r REV] [SOURCE]')),
3305 _('[-nibt] [-r REV] [SOURCE]')),
3301 "import|patch":
3306 "import|patch":
3302 (import_,
3307 (import_,
3303 [('p', 'strip', 1,
3308 [('p', 'strip', 1,
3304 _('directory strip option for patch. This has the same '
3309 _('directory strip option for patch. This has the same '
3305 'meaning as the corresponding patch option')),
3310 'meaning as the corresponding patch option')),
3306 ('b', 'base', '', _('base path')),
3311 ('b', 'base', '', _('base path')),
3307 ('f', 'force', None,
3312 ('f', 'force', None,
3308 _('skip check for outstanding uncommitted changes')),
3313 _('skip check for outstanding uncommitted changes')),
3309 ('', 'no-commit', None, _("don't commit, just update the working directory")),
3314 ('', 'no-commit', None, _("don't commit, just update the working directory")),
3310 ('', 'exact', None,
3315 ('', 'exact', None,
3311 _('apply patch to the nodes from which it was generated')),
3316 _('apply patch to the nodes from which it was generated')),
3312 ('', 'import-branch', None,
3317 ('', 'import-branch', None,
3313 _('use any branch information in patch (implied by --exact)'))] +
3318 _('use any branch information in patch (implied by --exact)'))] +
3314 commitopts + commitopts2 + similarityopts,
3319 commitopts + commitopts2 + similarityopts,
3315 _('[OPTION]... PATCH...')),
3320 _('[OPTION]... PATCH...')),
3316 "incoming|in":
3321 "incoming|in":
3317 (incoming,
3322 (incoming,
3318 [('f', 'force', None,
3323 [('f', 'force', None,
3319 _('run even when remote repository is unrelated')),
3324 _('run even when remote repository is unrelated')),
3320 ('n', 'newest-first', None, _('show newest record first')),
3325 ('n', 'newest-first', None, _('show newest record first')),
3321 ('', 'bundle', '', _('file to store the bundles into')),
3326 ('', 'bundle', '', _('file to store the bundles into')),
3322 ('r', 'rev', [],
3327 ('r', 'rev', [],
3323 _('a specific revision up to which you would like to pull')),
3328 _('a specific revision up to which you would like to pull')),
3324 ] + logopts + remoteopts,
3329 ] + logopts + remoteopts,
3325 _('[-p] [-n] [-M] [-f] [-r REV]...'
3330 _('[-p] [-n] [-M] [-f] [-r REV]...'
3326 ' [--bundle FILENAME] [SOURCE]')),
3331 ' [--bundle FILENAME] [SOURCE]')),
3327 "^init":
3332 "^init":
3328 (init,
3333 (init,
3329 remoteopts,
3334 remoteopts,
3330 _('[-e CMD] [--remotecmd CMD] [DEST]')),
3335 _('[-e CMD] [--remotecmd CMD] [DEST]')),
3331 "locate":
3336 "locate":
3332 (locate,
3337 (locate,
3333 [('r', 'rev', '', _('search the repository as it stood at REV')),
3338 [('r', 'rev', '', _('search the repository as it stood at REV')),
3334 ('0', 'print0', None,
3339 ('0', 'print0', None,
3335 _('end filenames with NUL, for use with xargs')),
3340 _('end filenames with NUL, for use with xargs')),
3336 ('f', 'fullpath', None,
3341 ('f', 'fullpath', None,
3337 _('print complete paths from the filesystem root')),
3342 _('print complete paths from the filesystem root')),
3338 ] + walkopts,
3343 ] + walkopts,
3339 _('[OPTION]... [PATTERN]...')),
3344 _('[OPTION]... [PATTERN]...')),
3340 "^log|history":
3345 "^log|history":
3341 (log,
3346 (log,
3342 [('f', 'follow', None,
3347 [('f', 'follow', None,
3343 _('follow changeset history, or file history across copies and renames')),
3348 _('follow changeset history, or file history across copies and renames')),
3344 ('', 'follow-first', None,
3349 ('', 'follow-first', None,
3345 _('only follow the first parent of merge changesets')),
3350 _('only follow the first parent of merge changesets')),
3346 ('d', 'date', '', _('show revisions matching date spec')),
3351 ('d', 'date', '', _('show revisions matching date spec')),
3347 ('C', 'copies', None, _('show copied files')),
3352 ('C', 'copies', None, _('show copied files')),
3348 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3353 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3349 ('r', 'rev', [], _('show the specified revision or range')),
3354 ('r', 'rev', [], _('show the specified revision or range')),
3350 ('', 'removed', None, _('include revisions where files were removed')),
3355 ('', 'removed', None, _('include revisions where files were removed')),
3351 ('m', 'only-merges', None, _('show only merges')),
3356 ('m', 'only-merges', None, _('show only merges')),
3352 ('u', 'user', [], _('revisions committed by user')),
3357 ('u', 'user', [], _('revisions committed by user')),
3353 ('b', 'only-branch', [],
3358 ('b', 'only-branch', [],
3354 _('show only changesets within the given named branch')),
3359 _('show only changesets within the given named branch')),
3355 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3360 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3356 ] + logopts + walkopts,
3361 ] + logopts + walkopts,
3357 _('[OPTION]... [FILE]')),
3362 _('[OPTION]... [FILE]')),
3358 "manifest":
3363 "manifest":
3359 (manifest,
3364 (manifest,
3360 [('r', 'rev', '', _('revision to display'))],
3365 [('r', 'rev', '', _('revision to display'))],
3361 _('[-r REV]')),
3366 _('[-r REV]')),
3362 "^merge":
3367 "^merge":
3363 (merge,
3368 (merge,
3364 [('f', 'force', None, _('force a merge with outstanding changes')),
3369 [('f', 'force', None, _('force a merge with outstanding changes')),
3365 ('r', 'rev', '', _('revision to merge')),
3370 ('r', 'rev', '', _('revision to merge')),
3366 ('P', 'preview', None,
3371 ('P', 'preview', None,
3367 _('review revisions to merge (no merge is performed)'))],
3372 _('review revisions to merge (no merge is performed)'))],
3368 _('[-f] [[-r] REV]')),
3373 _('[-f] [[-r] REV]')),
3369 "outgoing|out":
3374 "outgoing|out":
3370 (outgoing,
3375 (outgoing,
3371 [('f', 'force', None,
3376 [('f', 'force', None,
3372 _('run even when remote repository is unrelated')),
3377 _('run even when remote repository is unrelated')),
3373 ('r', 'rev', [],
3378 ('r', 'rev', [],
3374 _('a specific revision up to which you would like to push')),
3379 _('a specific revision up to which you would like to push')),
3375 ('n', 'newest-first', None, _('show newest record first')),
3380 ('n', 'newest-first', None, _('show newest record first')),
3376 ] + logopts + remoteopts,
3381 ] + logopts + remoteopts,
3377 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3382 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3378 "^parents":
3383 "^parents":
3379 (parents,
3384 (parents,
3380 [('r', 'rev', '', _('show parents from the specified revision')),
3385 [('r', 'rev', '', _('show parents from the specified revision')),
3381 ] + templateopts,
3386 ] + templateopts,
3382 _('[-r REV] [FILE]')),
3387 _('[-r REV] [FILE]')),
3383 "paths": (paths, [], _('[NAME]')),
3388 "paths": (paths, [], _('[NAME]')),
3384 "^pull":
3389 "^pull":
3385 (pull,
3390 (pull,
3386 [('u', 'update', None,
3391 [('u', 'update', None,
3387 _('update to new tip if changesets were pulled')),
3392 _('update to new tip if changesets were pulled')),
3388 ('f', 'force', None,
3393 ('f', 'force', None,
3389 _('run even when remote repository is unrelated')),
3394 _('run even when remote repository is unrelated')),
3390 ('r', 'rev', [],
3395 ('r', 'rev', [],
3391 _('a specific revision up to which you would like to pull')),
3396 _('a specific revision up to which you would like to pull')),
3392 ] + remoteopts,
3397 ] + remoteopts,
3393 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3398 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3394 "^push":
3399 "^push":
3395 (push,
3400 (push,
3396 [('f', 'force', None, _('force push')),
3401 [('f', 'force', None, _('force push')),
3397 ('r', 'rev', [],
3402 ('r', 'rev', [],
3398 _('a specific revision up to which you would like to push')),
3403 _('a specific revision up to which you would like to push')),
3399 ] + remoteopts,
3404 ] + remoteopts,
3400 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3405 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3401 "recover": (recover, []),
3406 "recover": (recover, []),
3402 "^remove|rm":
3407 "^remove|rm":
3403 (remove,
3408 (remove,
3404 [('A', 'after', None, _('record delete for missing files')),
3409 [('A', 'after', None, _('record delete for missing files')),
3405 ('f', 'force', None,
3410 ('f', 'force', None,
3406 _('remove (and delete) file even if added or modified')),
3411 _('remove (and delete) file even if added or modified')),
3407 ] + walkopts,
3412 ] + walkopts,
3408 _('[OPTION]... FILE...')),
3413 _('[OPTION]... FILE...')),
3409 "rename|mv":
3414 "rename|mv":
3410 (rename,
3415 (rename,
3411 [('A', 'after', None, _('record a rename that has already occurred')),
3416 [('A', 'after', None, _('record a rename that has already occurred')),
3412 ('f', 'force', None,
3417 ('f', 'force', None,
3413 _('forcibly copy over an existing managed file')),
3418 _('forcibly copy over an existing managed file')),
3414 ] + walkopts + dryrunopts,
3419 ] + walkopts + dryrunopts,
3415 _('[OPTION]... SOURCE... DEST')),
3420 _('[OPTION]... SOURCE... DEST')),
3416 "resolve":
3421 "resolve":
3417 (resolve,
3422 (resolve,
3418 [('a', 'all', None, _('remerge all unresolved files')),
3423 [('a', 'all', None, _('remerge all unresolved files')),
3419 ('l', 'list', None, _('list state of files needing merge')),
3424 ('l', 'list', None, _('list state of files needing merge')),
3420 ('m', 'mark', None, _('mark files as resolved')),
3425 ('m', 'mark', None, _('mark files as resolved')),
3421 ('u', 'unmark', None, _('unmark files as resolved'))]
3426 ('u', 'unmark', None, _('unmark files as resolved'))]
3422 + walkopts,
3427 + walkopts,
3423 _('[OPTION]... [FILE]...')),
3428 _('[OPTION]... [FILE]...')),
3424 "revert":
3429 "revert":
3425 (revert,
3430 (revert,
3426 [('a', 'all', None, _('revert all changes when no arguments given')),
3431 [('a', 'all', None, _('revert all changes when no arguments given')),
3427 ('d', 'date', '', _('tipmost revision matching date')),
3432 ('d', 'date', '', _('tipmost revision matching date')),
3428 ('r', 'rev', '', _('revision to revert to')),
3433 ('r', 'rev', '', _('revision to revert to')),
3429 ('', 'no-backup', None, _('do not save backup copies of files')),
3434 ('', 'no-backup', None, _('do not save backup copies of files')),
3430 ] + walkopts + dryrunopts,
3435 ] + walkopts + dryrunopts,
3431 _('[OPTION]... [-r REV] [NAME]...')),
3436 _('[OPTION]... [-r REV] [NAME]...')),
3432 "rollback": (rollback, []),
3437 "rollback": (rollback, []),
3433 "root": (root, []),
3438 "root": (root, []),
3434 "^serve":
3439 "^serve":
3435 (serve,
3440 (serve,
3436 [('A', 'accesslog', '', _('name of access log file to write to')),
3441 [('A', 'accesslog', '', _('name of access log file to write to')),
3437 ('d', 'daemon', None, _('run server in background')),
3442 ('d', 'daemon', None, _('run server in background')),
3438 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3443 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3439 ('E', 'errorlog', '', _('name of error log file to write to')),
3444 ('E', 'errorlog', '', _('name of error log file to write to')),
3440 ('p', 'port', 0, _('port to listen on (default: 8000)')),
3445 ('p', 'port', 0, _('port to listen on (default: 8000)')),
3441 ('a', 'address', '', _('address to listen on (default: all interfaces)')),
3446 ('a', 'address', '', _('address to listen on (default: all interfaces)')),
3442 ('', 'prefix', '', _('prefix path to serve from (default: server root)')),
3447 ('', 'prefix', '', _('prefix path to serve from (default: server root)')),
3443 ('n', 'name', '',
3448 ('n', 'name', '',
3444 _('name to show in web pages (default: working directory)')),
3449 _('name to show in web pages (default: working directory)')),
3445 ('', 'webdir-conf', '', _('name of the webdir config file'
3450 ('', 'webdir-conf', '', _('name of the webdir config file'
3446 ' (serve more than one repository)')),
3451 ' (serve more than one repository)')),
3447 ('', 'pid-file', '', _('name of file to write process ID to')),
3452 ('', 'pid-file', '', _('name of file to write process ID to')),
3448 ('', 'stdio', None, _('for remote clients')),
3453 ('', 'stdio', None, _('for remote clients')),
3449 ('t', 'templates', '', _('web templates to use')),
3454 ('t', 'templates', '', _('web templates to use')),
3450 ('', 'style', '', _('template style to use')),
3455 ('', 'style', '', _('template style to use')),
3451 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3456 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3452 ('', 'certificate', '', _('SSL certificate file'))],
3457 ('', 'certificate', '', _('SSL certificate file'))],
3453 _('[OPTION]...')),
3458 _('[OPTION]...')),
3454 "showconfig|debugconfig":
3459 "showconfig|debugconfig":
3455 (showconfig,
3460 (showconfig,
3456 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3461 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3457 _('[-u] [NAME]...')),
3462 _('[-u] [NAME]...')),
3458 "^status|st":
3463 "^status|st":
3459 (status,
3464 (status,
3460 [('A', 'all', None, _('show status of all files')),
3465 [('A', 'all', None, _('show status of all files')),
3461 ('m', 'modified', None, _('show only modified files')),
3466 ('m', 'modified', None, _('show only modified files')),
3462 ('a', 'added', None, _('show only added files')),
3467 ('a', 'added', None, _('show only added files')),
3463 ('r', 'removed', None, _('show only removed files')),
3468 ('r', 'removed', None, _('show only removed files')),
3464 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3469 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3465 ('c', 'clean', None, _('show only files without changes')),
3470 ('c', 'clean', None, _('show only files without changes')),
3466 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3471 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3467 ('i', 'ignored', None, _('show only ignored files')),
3472 ('i', 'ignored', None, _('show only ignored files')),
3468 ('n', 'no-status', None, _('hide status prefix')),
3473 ('n', 'no-status', None, _('hide status prefix')),
3469 ('C', 'copies', None, _('show source of copied files')),
3474 ('C', 'copies', None, _('show source of copied files')),
3470 ('0', 'print0', None,
3475 ('0', 'print0', None,
3471 _('end filenames with NUL, for use with xargs')),
3476 _('end filenames with NUL, for use with xargs')),
3472 ('', 'rev', [], _('show difference from revision')),
3477 ('', 'rev', [], _('show difference from revision')),
3473 ] + walkopts,
3478 ] + walkopts,
3474 _('[OPTION]... [FILE]...')),
3479 _('[OPTION]... [FILE]...')),
3475 "tag":
3480 "tag":
3476 (tag,
3481 (tag,
3477 [('f', 'force', None, _('replace existing tag')),
3482 [('f', 'force', None, _('replace existing tag')),
3478 ('l', 'local', None, _('make the tag local')),
3483 ('l', 'local', None, _('make the tag local')),
3479 ('r', 'rev', '', _('revision to tag')),
3484 ('r', 'rev', '', _('revision to tag')),
3480 ('', 'remove', None, _('remove a tag')),
3485 ('', 'remove', None, _('remove a tag')),
3481 # -l/--local is already there, commitopts cannot be used
3486 # -l/--local is already there, commitopts cannot be used
3482 ('m', 'message', '', _('use <text> as commit message')),
3487 ('m', 'message', '', _('use <text> as commit message')),
3483 ] + commitopts2,
3488 ] + commitopts2,
3484 _('[-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
3489 _('[-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
3485 "tags": (tags, []),
3490 "tags": (tags, []),
3486 "tip":
3491 "tip":
3487 (tip,
3492 (tip,
3488 [('p', 'patch', None, _('show patch')),
3493 [('p', 'patch', None, _('show patch')),
3489 ('g', 'git', None, _('use git extended diff format')),
3494 ('g', 'git', None, _('use git extended diff format')),
3490 ] + templateopts,
3495 ] + templateopts,
3491 _('[-p]')),
3496 _('[-p]')),
3492 "unbundle":
3497 "unbundle":
3493 (unbundle,
3498 (unbundle,
3494 [('u', 'update', None,
3499 [('u', 'update', None,
3495 _('update to new tip if changesets were unbundled'))],
3500 _('update to new tip if changesets were unbundled'))],
3496 _('[-u] FILE...')),
3501 _('[-u] FILE...')),
3497 "^update|up|checkout|co":
3502 "^update|up|checkout|co":
3498 (update,
3503 (update,
3499 [('C', 'clean', None, _('overwrite locally modified files (no backup)')),
3504 [('C', 'clean', None, _('overwrite locally modified files (no backup)')),
3500 ('c', 'check', None, _('check for uncommitted changes')),
3505 ('c', 'check', None, _('check for uncommitted changes')),
3501 ('d', 'date', '', _('tipmost revision matching date')),
3506 ('d', 'date', '', _('tipmost revision matching date')),
3502 ('r', 'rev', '', _('revision'))],
3507 ('r', 'rev', '', _('revision'))],
3503 _('[-C] [-d DATE] [[-r] REV]')),
3508 _('[-C] [-d DATE] [[-r] REV]')),
3504 "verify": (verify, []),
3509 "verify": (verify, []),
3505 "version": (version_, []),
3510 "version": (version_, []),
3506 }
3511 }
3507
3512
3508 norepo = ("clone init version help debugcommands debugcomplete debugdata"
3513 norepo = ("clone init version help debugcommands debugcomplete debugdata"
3509 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3514 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3510 optionalrepo = ("identify paths serve showconfig debugancestor")
3515 optionalrepo = ("identify paths serve showconfig debugancestor")
@@ -1,491 +1,500 b''
1 # help.py - help data for mercurial
1 # help.py - help data for mercurial
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 import extensions, util
9 import extensions, util
10
10
11
11
12 def moduledoc(file):
12 def moduledoc(file):
13 '''return the top-level python documentation for the given file
13 '''return the top-level python documentation for the given file
14
14
15 Loosely inspired by pydoc.source_synopsis(), but rewritten to handle \'''
15 Loosely inspired by pydoc.source_synopsis(), but rewritten to handle \'''
16 as well as """ and to return the whole text instead of just the synopsis'''
16 as well as """ and to return the whole text instead of just the synopsis'''
17 result = []
17 result = []
18
18
19 line = file.readline()
19 line = file.readline()
20 while line[:1] == '#' or not line.strip():
20 while line[:1] == '#' or not line.strip():
21 line = file.readline()
21 line = file.readline()
22 if not line: break
22 if not line: break
23
23
24 start = line[:3]
24 start = line[:3]
25 if start == '"""' or start == "'''":
25 if start == '"""' or start == "'''":
26 line = line[3:]
26 line = line[3:]
27 while line:
27 while line:
28 if line.rstrip().endswith(start):
28 if line.rstrip().endswith(start):
29 line = line.split(start)[0]
29 line = line.split(start)[0]
30 if line:
30 if line:
31 result.append(line)
31 result.append(line)
32 break
32 break
33 elif not line:
33 elif not line:
34 return None # unmatched delimiter
34 return None # unmatched delimiter
35 result.append(line)
35 result.append(line)
36 line = file.readline()
36 line = file.readline()
37 else:
37 else:
38 return None
38 return None
39
39
40 return ''.join(result)
40 return ''.join(result)
41
41
42 def listexts(header, exts, maxlength):
42 def listexts(header, exts, maxlength):
43 '''return a text listing of the given extensions'''
43 '''return a text listing of the given extensions'''
44 if not exts:
44 if not exts:
45 return ''
45 return ''
46 result = '\n%s\n\n' % header
46 # TODO: literal block is wrong, should be a field list or a simple table.
47 result = '\n%s\n\n ::\n\n' % header
47 for name, desc in sorted(exts.iteritems()):
48 for name, desc in sorted(exts.iteritems()):
48 desc = util.wrap(desc, maxlength + 4)
49 desc = util.wrap(desc, maxlength + 5)
49 result += ' %s %s\n' % (name.ljust(maxlength), desc)
50 result += ' %s %s\n' % (name.ljust(maxlength), desc)
50 return result
51 return result
51
52
52 def extshelp():
53 def extshelp():
53 doc = _(r'''
54 doc = _(r'''
54 Mercurial has the ability to add new features through the use of
55 Mercurial has the ability to add new features through the use of
55 extensions. Extensions may add new commands, add options to existing
56 extensions. Extensions may add new commands, add options to existing
56 commands, change the default behavior of commands, or implement hooks.
57 commands, change the default behavior of commands, or implement hooks.
57
58
58 Extensions are not loaded by default for a variety of reasons: they can
59 Extensions are not loaded by default for a variety of reasons: they can
59 increase startup overhead; they may be meant for advanced usage only; they
60 increase startup overhead; they may be meant for advanced usage only; they
60 may provide potentially dangerous abilities (such as letting you destroy
61 may provide potentially dangerous abilities (such as letting you destroy
61 or modify history); they might not be ready for prime time; or they may
62 or modify history); they might not be ready for prime time; or they may
62 alter some usual behaviors of stock Mercurial. It is thus up to the user
63 alter some usual behaviors of stock Mercurial. It is thus up to the user
63 to activate extensions as needed.
64 to activate extensions as needed.
64
65
65 To enable the "foo" extension, either shipped with Mercurial or in the
66 To enable the "foo" extension, either shipped with Mercurial or in the
66 Python search path, create an entry for it in your hgrc, like this:
67 Python search path, create an entry for it in your hgrc, like this::
67
68
68 [extensions]
69 [extensions]
69 foo =
70 foo =
70
71
71 You may also specify the full path to an extension:
72 You may also specify the full path to an extension::
72
73
73 [extensions]
74 [extensions]
74 myfeature = ~/.hgext/myfeature.py
75 myfeature = ~/.hgext/myfeature.py
75
76
76 To explicitly disable an extension enabled in an hgrc of broader scope,
77 To explicitly disable an extension enabled in an hgrc of broader scope,
77 prepend its path with !:
78 prepend its path with !::
78
79
79 [extensions]
80 [extensions]
80 # disabling extension bar residing in /path/to/extension/bar.py
81 # disabling extension bar residing in /path/to/extension/bar.py
81 hgext.bar = !/path/to/extension/bar.py
82 hgext.bar = !/path/to/extension/bar.py
82 # ditto, but no path was supplied for extension baz
83 # ditto, but no path was supplied for extension baz
83 hgext.baz = !
84 hgext.baz = !
84 ''')
85 ''')
85
86
86 exts, maxlength = extensions.enabled()
87 exts, maxlength = extensions.enabled()
87 doc += listexts(_('enabled extensions:'), exts, maxlength)
88 doc += listexts(_('enabled extensions:'), exts, maxlength)
88
89
89 exts, maxlength = extensions.disabled()
90 exts, maxlength = extensions.disabled()
90 doc += listexts(_('disabled extensions:'), exts, maxlength)
91 doc += listexts(_('disabled extensions:'), exts, maxlength)
91
92
92 return doc
93 return doc
93
94
94 helptable = (
95 helptable = (
95 (["dates"], _("Date Formats"),
96 (["dates"], _("Date Formats"),
96 _(r'''
97 _(r'''
97 Some commands allow the user to specify a date, e.g.:
98 Some commands allow the user to specify a date, e.g.:
98 * backout, commit, import, tag: Specify the commit date.
99 * log, revert, update: Select revision(s) by date.
100
99
101 Many date formats are valid. Here are some examples:
100 - backout, commit, import, tag: Specify the commit date.
101 - log, revert, update: Select revision(s) by date.
102
103 Many date formats are valid. Here are some examples::
102
104
103 "Wed Dec 6 13:18:29 2006" (local timezone assumed)
105 "Wed Dec 6 13:18:29 2006" (local timezone assumed)
104 "Dec 6 13:18 -0600" (year assumed, time offset provided)
106 "Dec 6 13:18 -0600" (year assumed, time offset provided)
105 "Dec 6 13:18 UTC" (UTC and GMT are aliases for +0000)
107 "Dec 6 13:18 UTC" (UTC and GMT are aliases for +0000)
106 "Dec 6" (midnight)
108 "Dec 6" (midnight)
107 "13:18" (today assumed)
109 "13:18" (today assumed)
108 "3:39" (3:39AM assumed)
110 "3:39" (3:39AM assumed)
109 "3:39pm" (15:39)
111 "3:39pm" (15:39)
110 "2006-12-06 13:18:29" (ISO 8601 format)
112 "2006-12-06 13:18:29" (ISO 8601 format)
111 "2006-12-6 13:18"
113 "2006-12-6 13:18"
112 "2006-12-6"
114 "2006-12-6"
113 "12-6"
115 "12-6"
114 "12/6"
116 "12/6"
115 "12/6/6" (Dec 6 2006)
117 "12/6/6" (Dec 6 2006)
116
118
117 Lastly, there is Mercurial's internal format:
119 Lastly, there is Mercurial's internal format:
118
120
119 "1165432709 0" (Wed Dec 6 13:18:29 2006 UTC)
121 "1165432709 0" (Wed Dec 6 13:18:29 2006 UTC)
120
122
121 This is the internal representation format for dates. unixtime is the
123 This is the internal representation format for dates. unixtime is the
122 number of seconds since the epoch (1970-01-01 00:00 UTC). offset is the
124 number of seconds since the epoch (1970-01-01 00:00 UTC). offset is the
123 offset of the local timezone, in seconds west of UTC (negative if the
125 offset of the local timezone, in seconds west of UTC (negative if the
124 timezone is east of UTC).
126 timezone is east of UTC).
125
127
126 The log command also accepts date ranges:
128 The log command also accepts date ranges::
127
129
128 "<{datetime}" - at or before a given date/time
130 "<{datetime}" - at or before a given date/time
129 ">{datetime}" - on or after a given date/time
131 ">{datetime}" - on or after a given date/time
130 "{datetime} to {datetime}" - a date range, inclusive
132 "{datetime} to {datetime}" - a date range, inclusive
131 "-{days}" - within a given number of days of today
133 "-{days}" - within a given number of days of today
132 ''')),
134 ''')),
133
135
134 (["patterns"], _("File Name Patterns"),
136 (["patterns"], _("File Name Patterns"),
135 _(r'''
137 _(r'''
136 Mercurial accepts several notations for identifying one or more files at a
138 Mercurial accepts several notations for identifying one or more files at a
137 time.
139 time.
138
140
139 By default, Mercurial treats filenames as shell-style extended glob
141 By default, Mercurial treats filenames as shell-style extended glob
140 patterns.
142 patterns.
141
143
142 Alternate pattern notations must be specified explicitly.
144 Alternate pattern notations must be specified explicitly.
143
145
144 To use a plain path name without any pattern matching, start it with
146 To use a plain path name without any pattern matching, start it with
145 "path:". These path names must completely match starting at the current
147 "path:". These path names must completely match starting at the current
146 repository root.
148 repository root.
147
149
148 To use an extended glob, start a name with "glob:". Globs are rooted at
150 To use an extended glob, start a name with "glob:". Globs are rooted at
149 the current directory; a glob such as "*.c" will only match files in the
151 the current directory; a glob such as "*.c" will only match files in the
150 current directory ending with ".c".
152 current directory ending with ".c".
151
153
152 The supported glob syntax extensions are "**" to match any string across
154 The supported glob syntax extensions are "**" to match any string across
153 path separators and "{a,b}" to mean "a or b".
155 path separators and "{a,b}" to mean "a or b".
154
156
155 To use a Perl/Python regular expression, start a name with "re:". Regexp
157 To use a Perl/Python regular expression, start a name with "re:". Regexp
156 pattern matching is anchored at the root of the repository.
158 pattern matching is anchored at the root of the repository.
157
159
158 Plain examples:
160 Plain examples::
159
161
160 path:foo/bar a name bar in a directory named foo in the root of
162 path:foo/bar a name bar in a directory named foo in the root of
161 the repository
163 the repository
162 path:path:name a file or directory named "path:name"
164 path:path:name a file or directory named "path:name"
163
165
164 Glob examples:
166 Glob examples::
165
167
166 glob:*.c any name ending in ".c" in the current directory
168 glob:*.c any name ending in ".c" in the current directory
167 *.c any name ending in ".c" in the current directory
169 *.c any name ending in ".c" in the current directory
168 **.c any name ending in ".c" in any subdirectory of the current
170 **.c any name ending in ".c" in any subdirectory of the
169 directory including itself.
171 current directory including itself.
170 foo/*.c any name ending in ".c" in the directory foo
172 foo/*.c any name ending in ".c" in the directory foo
171 foo/**.c any name ending in ".c" in any subdirectory of foo
173 foo/**.c any name ending in ".c" in any subdirectory of foo
172 including itself.
174 including itself.
173
175
174 Regexp examples:
176 Regexp examples::
175
177
176 re:.*\.c$ any name ending in ".c", anywhere in the repository
178 re:.*\.c$ any name ending in ".c", anywhere in the repository
177
179
178 ''')),
180 ''')),
179
181
180 (['environment', 'env'], _('Environment Variables'),
182 (['environment', 'env'], _('Environment Variables'),
181 _(r'''
183 _(r'''
182 HG::
184 HG
183 Path to the 'hg' executable, automatically passed when running hooks,
185 Path to the 'hg' executable, automatically passed when running hooks,
184 extensions or external tools. If unset or empty, this is the hg
186 extensions or external tools. If unset or empty, this is the hg
185 executable's name if it's frozen, or an executable named 'hg' (with
187 executable's name if it's frozen, or an executable named 'hg' (with
186 %PATHEXT% [defaulting to COM/EXE/BAT/CMD] extensions on Windows) is
188 %PATHEXT% [defaulting to COM/EXE/BAT/CMD] extensions on Windows) is
187 searched.
189 searched.
188
190
189 HGEDITOR::
191 HGEDITOR
190 This is the name of the editor to run when committing. See EDITOR.
192 This is the name of the editor to run when committing. See EDITOR.
191
193
192 (deprecated, use .hgrc)
194 (deprecated, use .hgrc)
193
195
194 HGENCODING::
196 HGENCODING
195 This overrides the default locale setting detected by Mercurial. This
197 This overrides the default locale setting detected by Mercurial. This
196 setting is used to convert data including usernames, changeset
198 setting is used to convert data including usernames, changeset
197 descriptions, tag names, and branches. This setting can be overridden with
199 descriptions, tag names, and branches. This setting can be overridden with
198 the --encoding command-line option.
200 the --encoding command-line option.
199
201
200 HGENCODINGMODE::
202 HGENCODINGMODE
201 This sets Mercurial's behavior for handling unknown characters while
203 This sets Mercurial's behavior for handling unknown characters while
202 transcoding user input. The default is "strict", which causes Mercurial to
204 transcoding user input. The default is "strict", which causes Mercurial to
203 abort if it can't map a character. Other settings include "replace", which
205 abort if it can't map a character. Other settings include "replace", which
204 replaces unknown characters, and "ignore", which drops them. This setting
206 replaces unknown characters, and "ignore", which drops them. This setting
205 can be overridden with the --encodingmode command-line option.
207 can be overridden with the --encodingmode command-line option.
206
208
207 HGMERGE::
209 HGMERGE
208 An executable to use for resolving merge conflicts. The program will be
210 An executable to use for resolving merge conflicts. The program will be
209 executed with three arguments: local file, remote file, ancestor file.
211 executed with three arguments: local file, remote file, ancestor file.
210
212
211 (deprecated, use .hgrc)
213 (deprecated, use .hgrc)
212
214
213 HGRCPATH::
215 HGRCPATH
214 A list of files or directories to search for hgrc files. Item separator is
216 A list of files or directories to search for hgrc files. Item separator is
215 ":" on Unix, ";" on Windows. If HGRCPATH is not set, platform default
217 ":" on Unix, ";" on Windows. If HGRCPATH is not set, platform default
216 search path is used. If empty, only the .hg/hgrc from the current
218 search path is used. If empty, only the .hg/hgrc from the current
217 repository is read.
219 repository is read.
218
220
219 For each element in HGRCPATH:
221 For each element in HGRCPATH:
220 * if it's a directory, all files ending with .rc are added
221 * otherwise, the file itself will be added
222
222
223 HGUSER::
223 - if it's a directory, all files ending with .rc are added
224 - otherwise, the file itself will be added
225
226 HGUSER
224 This is the string used as the author of a commit. If not set, available
227 This is the string used as the author of a commit. If not set, available
225 values will be considered in this order:
228 values will be considered in this order:
226
229
227 * HGUSER (deprecated)
230 - HGUSER (deprecated)
228 * hgrc files from the HGRCPATH
231 - hgrc files from the HGRCPATH
229 * EMAIL
232 - EMAIL
230 * interactive prompt
233 - interactive prompt
231 * LOGNAME (with '@hostname' appended)
234 - LOGNAME (with '@hostname' appended)
232
235
233 (deprecated, use .hgrc)
236 (deprecated, use .hgrc)
234
237
235 EMAIL::
238 EMAIL
236 May be used as the author of a commit; see HGUSER.
239 May be used as the author of a commit; see HGUSER.
237
240
238 LOGNAME::
241 LOGNAME
239 May be used as the author of a commit; see HGUSER.
242 May be used as the author of a commit; see HGUSER.
240
243
241 VISUAL::
244 VISUAL
242 This is the name of the editor to use when committing. See EDITOR.
245 This is the name of the editor to use when committing. See EDITOR.
243
246
244 EDITOR::
247 EDITOR
245 Sometimes Mercurial needs to open a text file in an editor for a user to
248 Sometimes Mercurial needs to open a text file in an editor for a user to
246 modify, for example when writing commit messages. The editor it uses is
249 modify, for example when writing commit messages. The editor it uses is
247 determined by looking at the environment variables HGEDITOR, VISUAL and
250 determined by looking at the environment variables HGEDITOR, VISUAL and
248 EDITOR, in that order. The first non-empty one is chosen. If all of them
251 EDITOR, in that order. The first non-empty one is chosen. If all of them
249 are empty, the editor defaults to 'vi'.
252 are empty, the editor defaults to 'vi'.
250
253
251 PYTHONPATH::
254 PYTHONPATH
252 This is used by Python to find imported modules and may need to be set
255 This is used by Python to find imported modules and may need to be set
253 appropriately if this Mercurial is not installed system-wide.
256 appropriately if this Mercurial is not installed system-wide.
254 ''')),
257 ''')),
255
258
256 (['revs', 'revisions'], _('Specifying Single Revisions'),
259 (['revs', 'revisions'], _('Specifying Single Revisions'),
257 _(r'''
260 _(r'''
258 Mercurial supports several ways to specify individual revisions.
261 Mercurial supports several ways to specify individual revisions.
259
262
260 A plain integer is treated as a revision number. Negative integers are
263 A plain integer is treated as a revision number. Negative integers are
261 treated as topological offsets from the tip, with -1 denoting the tip. As
264 treated as topological offsets from the tip, with -1 denoting the tip. As
262 such, negative numbers are only useful if you've memorized your local tree
265 such, negative numbers are only useful if you've memorized your local tree
263 numbers and want to save typing a single digit. This editor suggests copy
266 numbers and want to save typing a single digit. This editor suggests copy
264 and paste.
267 and paste.
265
268
266 A 40-digit hexadecimal string is treated as a unique revision identifier.
269 A 40-digit hexadecimal string is treated as a unique revision identifier.
267
270
268 A hexadecimal string less than 40 characters long is treated as a unique
271 A hexadecimal string less than 40 characters long is treated as a unique
269 revision identifier, and referred to as a short-form identifier. A
272 revision identifier, and referred to as a short-form identifier. A
270 short-form identifier is only valid if it is the prefix of exactly one
273 short-form identifier is only valid if it is the prefix of exactly one
271 full-length identifier.
274 full-length identifier.
272
275
273 Any other string is treated as a tag name, which is a symbolic name
276 Any other string is treated as a tag name, which is a symbolic name
274 associated with a revision identifier. Tag names may not contain the ":"
277 associated with a revision identifier. Tag names may not contain the ":"
275 character.
278 character.
276
279
277 The reserved name "tip" is a special tag that always identifies the most
280 The reserved name "tip" is a special tag that always identifies the most
278 recent revision.
281 recent revision.
279
282
280 The reserved name "null" indicates the null revision. This is the revision
283 The reserved name "null" indicates the null revision. This is the revision
281 of an empty repository, and the parent of revision 0.
284 of an empty repository, and the parent of revision 0.
282
285
283 The reserved name "." indicates the working directory parent. If no
286 The reserved name "." indicates the working directory parent. If no
284 working directory is checked out, it is equivalent to null. If an
287 working directory is checked out, it is equivalent to null. If an
285 uncommitted merge is in progress, "." is the revision of the first parent.
288 uncommitted merge is in progress, "." is the revision of the first parent.
286 ''')),
289 ''')),
287
290
288 (['mrevs', 'multirevs'], _('Specifying Multiple Revisions'),
291 (['mrevs', 'multirevs'], _('Specifying Multiple Revisions'),
289 _(r'''
292 _(r'''
290 When Mercurial accepts more than one revision, they may be specified
293 When Mercurial accepts more than one revision, they may be specified
291 individually, or provided as a topologically continuous range, separated
294 individually, or provided as a topologically continuous range, separated
292 by the ":" character.
295 by the ":" character.
293
296
294 The syntax of range notation is [BEGIN]:[END], where BEGIN and END are
297 The syntax of range notation is [BEGIN]:[END], where BEGIN and END are
295 revision identifiers. Both BEGIN and END are optional. If BEGIN is not
298 revision identifiers. Both BEGIN and END are optional. If BEGIN is not
296 specified, it defaults to revision number 0. If END is not specified, it
299 specified, it defaults to revision number 0. If END is not specified, it
297 defaults to the tip. The range ":" thus means "all revisions".
300 defaults to the tip. The range ":" thus means "all revisions".
298
301
299 If BEGIN is greater than END, revisions are treated in reverse order.
302 If BEGIN is greater than END, revisions are treated in reverse order.
300
303
301 A range acts as a closed interval. This means that a range of 3:5 gives 3,
304 A range acts as a closed interval. This means that a range of 3:5 gives 3,
302 4 and 5. Similarly, a range of 9:6 gives 9, 8, 7, and 6.
305 4 and 5. Similarly, a range of 9:6 gives 9, 8, 7, and 6.
303 ''')),
306 ''')),
304
307
305 (['diffs'], _('Diff Formats'),
308 (['diffs'], _('Diff Formats'),
306 _(r'''
309 _(r'''
307 Mercurial's default format for showing changes between two versions of a
310 Mercurial's default format for showing changes between two versions of a
308 file is compatible with the unified format of GNU diff, which can be used
311 file is compatible with the unified format of GNU diff, which can be used
309 by GNU patch and many other standard tools.
312 by GNU patch and many other standard tools.
310
313
311 While this standard format is often enough, it does not encode the
314 While this standard format is often enough, it does not encode the
312 following information:
315 following information:
313
316
314 - executable status and other permission bits
317 - executable status and other permission bits
315 - copy or rename information
318 - copy or rename information
316 - changes in binary files
319 - changes in binary files
317 - creation or deletion of empty files
320 - creation or deletion of empty files
318
321
319 Mercurial also supports the extended diff format from the git VCS which
322 Mercurial also supports the extended diff format from the git VCS which
320 addresses these limitations. The git diff format is not produced by
323 addresses these limitations. The git diff format is not produced by
321 default because a few widespread tools still do not understand this
324 default because a few widespread tools still do not understand this
322 format.
325 format.
323
326
324 This means that when generating diffs from a Mercurial repository (e.g.
327 This means that when generating diffs from a Mercurial repository (e.g.
325 with "hg export"), you should be careful about things like file copies and
328 with "hg export"), you should be careful about things like file copies and
326 renames or other things mentioned above, because when applying a standard
329 renames or other things mentioned above, because when applying a standard
327 diff to a different repository, this extra information is lost.
330 diff to a different repository, this extra information is lost.
328 Mercurial's internal operations (like push and pull) are not affected by
331 Mercurial's internal operations (like push and pull) are not affected by
329 this, because they use an internal binary format for communicating
332 this, because they use an internal binary format for communicating
330 changes.
333 changes.
331
334
332 To make Mercurial produce the git extended diff format, use the --git
335 To make Mercurial produce the git extended diff format, use the --git
333 option available for many commands, or set 'git = True' in the [diff]
336 option available for many commands, or set 'git = True' in the [diff]
334 section of your hgrc. You do not need to set this option when importing
337 section of your hgrc. You do not need to set this option when importing
335 diffs in this format or using them in the mq extension.
338 diffs in this format or using them in the mq extension.
336 ''')),
339 ''')),
337 (['templating'], _('Template Usage'),
340 (['templating'], _('Template Usage'),
338 _(r'''
341 _(r'''
339 Mercurial allows you to customize output of commands through templates.
342 Mercurial allows you to customize output of commands through templates.
340 You can either pass in a template from the command line, via the
343 You can either pass in a template from the command line, via the
341 --template option, or select an existing template-style (--style).
344 --template option, or select an existing template-style (--style).
342
345
343 You can customize output for any "log-like" command: log, outgoing,
346 You can customize output for any "log-like" command: log, outgoing,
344 incoming, tip, parents, heads and glog.
347 incoming, tip, parents, heads and glog.
345
348
346 Three styles are packaged with Mercurial: default (the style used when no
349 Three styles are packaged with Mercurial: default (the style used when no
347 explicit preference is passed), compact and changelog. Usage:
350 explicit preference is passed), compact and changelog. Usage:
348
351
349 $ hg log -r1 --style changelog
352 $ hg log -r1 --style changelog
350
353
351 A template is a piece of text, with markup to invoke variable expansion:
354 A template is a piece of text, with markup to invoke variable expansion:
352
355
353 $ hg log -r1 --template "{node}\n"
356 $ hg log -r1 --template "{node}\n"
354 b56ce7b07c52de7d5fd79fb89701ea538af65746
357 b56ce7b07c52de7d5fd79fb89701ea538af65746
355
358
356 Strings in curly braces are called keywords. The availability of keywords
359 Strings in curly braces are called keywords. The availability of keywords
357 depends on the exact context of the templater. These keywords are usually
360 depends on the exact context of the templater. These keywords are usually
358 available for templating a log-like command:
361 available for templating a log-like command:
359
362
360 - author: String. The unmodified author of the changeset.
363 - author: String. The unmodified author of the changeset.
361 - branches: String. The name of the branch on which the changeset was
364 - branches: String. The name of the branch on which the changeset was
362 committed. Will be empty if the branch name was default.
365 committed. Will be empty if the branch name was default.
363 - date: Date information. The date when the changeset was committed.
366 - date: Date information. The date when the changeset was committed.
364 - desc: String. The text of the changeset description.
367 - desc: String. The text of the changeset description.
365 - diffstat: String. Statistics of changes with the following format:
368 - diffstat: String. Statistics of changes with the following format:
366 "modified files: +added/-removed lines"
369 "modified files: +added/-removed lines"
367 - files: List of strings. All files modified, added, or removed by this
370 - files: List of strings. All files modified, added, or removed by this
368 changeset.
371 changeset.
369 - file_adds: List of strings. Files added by this changeset.
372 - file_adds: List of strings. Files added by this changeset.
370 - file_mods: List of strings. Files modified by this changeset.
373 - file_mods: List of strings. Files modified by this changeset.
371 - file_dels: List of strings. Files removed by this changeset.
374 - file_dels: List of strings. Files removed by this changeset.
372 - node: String. The changeset identification hash, as a 40-character
375 - node: String. The changeset identification hash, as a 40-character
373 hexadecimal string.
376 hexadecimal string.
374 - parents: List of strings. The parents of the changeset.
377 - parents: List of strings. The parents of the changeset.
375 - rev: Integer. The repository-local changeset revision number.
378 - rev: Integer. The repository-local changeset revision number.
376 - tags: List of strings. Any tags associated with the changeset.
379 - tags: List of strings. Any tags associated with the changeset.
377
380
378 The "date" keyword does not produce human-readable output. If you want to
381 The "date" keyword does not produce human-readable output. If you want to
379 use a date in your output, you can use a filter to process it. Filters are
382 use a date in your output, you can use a filter to process it. Filters are
380 functions which return a string based on the input variable. You can also
383 functions which return a string based on the input variable. You can also
381 use a chain of filters to get the desired output:
384 use a chain of filters to get the desired output:
382
385
383 $ hg tip --template "{date|isodate}\n"
386 $ hg tip --template "{date|isodate}\n"
384 2008-08-21 18:22 +0000
387 2008-08-21 18:22 +0000
385
388
386 List of filters:
389 List of filters:
387
390
388 - addbreaks: Any text. Add an XHTML "<br />" tag before the end of every
391 - addbreaks: Any text. Add an XHTML "<br />" tag before the end of every
389 line except the last.
392 line except the last.
390 - age: Date. Returns a human-readable date/time difference between the
393 - age: Date. Returns a human-readable date/time difference between the
391 given date/time and the current date/time.
394 given date/time and the current date/time.
392 - basename: Any text. Treats the text as a path, and returns the last
395 - basename: Any text. Treats the text as a path, and returns the last
393 component of the path after splitting by the path separator
396 component of the path after splitting by the path separator
394 (ignoring trailing separators). For example, "foo/bar/baz" becomes
397 (ignoring trailing separators). For example, "foo/bar/baz" becomes
395 "baz" and "foo/bar//" becomes "bar".
398 "baz" and "foo/bar//" becomes "bar".
396 - stripdir: Treat the text as path and strip a directory level, if
399 - stripdir: Treat the text as path and strip a directory level, if
397 possible. For example, "foo" and "foo/bar" becomes "foo".
400 possible. For example, "foo" and "foo/bar" becomes "foo".
398 - date: Date. Returns a date in a Unix date format, including the
401 - date: Date. Returns a date in a Unix date format, including the
399 timezone: "Mon Sep 04 15:13:13 2006 0700".
402 timezone: "Mon Sep 04 15:13:13 2006 0700".
400 - domain: Any text. Finds the first string that looks like an email
403 - domain: Any text. Finds the first string that looks like an email
401 address, and extracts just the domain component. Example: 'User
404 address, and extracts just the domain component. Example: 'User
402 <user@example.com>' becomes 'example.com'.
405 <user@example.com>' becomes 'example.com'.
403 - email: Any text. Extracts the first string that looks like an email
406 - email: Any text. Extracts the first string that looks like an email
404 address. Example: 'User <user@example.com>' becomes
407 address. Example: 'User <user@example.com>' becomes
405 'user@example.com'.
408 'user@example.com'.
406 - escape: Any text. Replaces the special XML/XHTML characters "&", "<" and
409 - escape: Any text. Replaces the special XML/XHTML characters "&", "<" and
407 ">" with XML entities.
410 ">" with XML entities.
408 - fill68: Any text. Wraps the text to fit in 68 columns.
411 - fill68: Any text. Wraps the text to fit in 68 columns.
409 - fill76: Any text. Wraps the text to fit in 76 columns.
412 - fill76: Any text. Wraps the text to fit in 76 columns.
410 - firstline: Any text. Returns the first line of text.
413 - firstline: Any text. Returns the first line of text.
411 - nonempty: Any text. Returns '(none)' if the string is empty.
414 - nonempty: Any text. Returns '(none)' if the string is empty.
412 - hgdate: Date. Returns the date as a pair of numbers: "1157407993 25200"
415 - hgdate: Date. Returns the date as a pair of numbers: "1157407993 25200"
413 (Unix timestamp, timezone offset).
416 (Unix timestamp, timezone offset).
414 - isodate: Date. Returns the date in ISO 8601 format.
417 - isodate: Date. Returns the date in ISO 8601 format.
415 - localdate: Date. Converts a date to local date.
418 - localdate: Date. Converts a date to local date.
416 - obfuscate: Any text. Returns the input text rendered as a sequence of
419 - obfuscate: Any text. Returns the input text rendered as a sequence of
417 XML entities.
420 XML entities.
418 - person: Any text. Returns the text before an email address.
421 - person: Any text. Returns the text before an email address.
419 - rfc822date: Date. Returns a date using the same format used in email
422 - rfc822date: Date. Returns a date using the same format used in email
420 headers.
423 headers.
421 - short: Changeset hash. Returns the short form of a changeset hash, i.e.
424 - short: Changeset hash. Returns the short form of a changeset hash, i.e.
422 a 12-byte hexadecimal string.
425 a 12-byte hexadecimal string.
423 - shortdate: Date. Returns a date like "2006-09-18".
426 - shortdate: Date. Returns a date like "2006-09-18".
424 - strip: Any text. Strips all leading and trailing whitespace.
427 - strip: Any text. Strips all leading and trailing whitespace.
425 - tabindent: Any text. Returns the text, with every line except the first
428 - tabindent: Any text. Returns the text, with every line except the first
426 starting with a tab character.
429 starting with a tab character.
427 - urlescape: Any text. Escapes all "special" characters. For example, "foo
430 - urlescape: Any text. Escapes all "special" characters. For example, "foo
428 bar" becomes "foo%20bar".
431 bar" becomes "foo%20bar".
429 - user: Any text. Returns the user portion of an email address.
432 - user: Any text. Returns the user portion of an email address.
430 ''')),
433 ''')),
431
434
432 (['urls'], _('URL Paths'),
435 (['urls'], _('URL Paths'),
433 _(r'''
436 _(r'''
434 Valid URLs are of the form:
437 Valid URLs are of the form::
435
438
436 local/filesystem/path[#revision]
439 local/filesystem/path[#revision]
437 file://local/filesystem/path[#revision]
440 file://local/filesystem/path[#revision]
438 http://[user[:pass]@]host[:port]/[path][#revision]
441 http://[user[:pass]@]host[:port]/[path][#revision]
439 https://[user[:pass]@]host[:port]/[path][#revision]
442 https://[user[:pass]@]host[:port]/[path][#revision]
440 ssh://[user[:pass]@]host[:port]/[path][#revision]
443 ssh://[user[:pass]@]host[:port]/[path][#revision]
441
444
442 Paths in the local filesystem can either point to Mercurial repositories
445 Paths in the local filesystem can either point to Mercurial repositories
443 or to bundle files (as created by 'hg bundle' or 'hg incoming --bundle').
446 or to bundle files (as created by 'hg bundle' or 'hg incoming --bundle').
444
447
445 An optional identifier after # indicates a particular branch, tag, or
448 An optional identifier after # indicates a particular branch, tag, or
446 changeset to use from the remote repository. See also 'hg help revisions'.
449 changeset to use from the remote repository. See also 'hg help revisions'.
447
450
448 Some features, such as pushing to http:// and https:// URLs are only
451 Some features, such as pushing to http:// and https:// URLs are only
449 possible if the feature is explicitly enabled on the remote Mercurial
452 possible if the feature is explicitly enabled on the remote Mercurial
450 server.
453 server.
451
454
452 Some notes about using SSH with Mercurial:
455 Some notes about using SSH with Mercurial:
456
453 - SSH requires an accessible shell account on the destination machine and
457 - SSH requires an accessible shell account on the destination machine and
454 a copy of hg in the remote path or specified with as remotecmd.
458 a copy of hg in the remote path or specified with as remotecmd.
455 - path is relative to the remote user's home directory by default. Use an
459 - path is relative to the remote user's home directory by default. Use an
456 extra slash at the start of a path to specify an absolute path:
460 extra slash at the start of a path to specify an absolute path::
461
457 ssh://example.com//tmp/repository
462 ssh://example.com//tmp/repository
463
458 - Mercurial doesn't use its own compression via SSH; the right thing to do
464 - Mercurial doesn't use its own compression via SSH; the right thing to do
459 is to configure it in your ~/.ssh/config, e.g.:
465 is to configure it in your ~/.ssh/config, e.g.::
466
460 Host *.mylocalnetwork.example.com
467 Host *.mylocalnetwork.example.com
461 Compression no
468 Compression no
462 Host *
469 Host *
463 Compression yes
470 Compression yes
471
464 Alternatively specify "ssh -C" as your ssh command in your hgrc or with
472 Alternatively specify "ssh -C" as your ssh command in your hgrc or with
465 the --ssh command line option.
473 the --ssh command line option.
466
474
467 These URLs can all be stored in your hgrc with path aliases under the
475 These URLs can all be stored in your hgrc with path aliases under the
468 [paths] section like so:
476 [paths] section like so::
477
469 [paths]
478 [paths]
470 alias1 = URL1
479 alias1 = URL1
471 alias2 = URL2
480 alias2 = URL2
472 ...
481 ...
473
482
474 You can then use the alias for any command that uses a URL (for example
483 You can then use the alias for any command that uses a URL (for example
475 'hg pull alias1' would pull from the 'alias1' path).
484 'hg pull alias1' would pull from the 'alias1' path).
476
485
477 Two path aliases are special because they are used as defaults when you do
486 Two path aliases are special because they are used as defaults when you do
478 not provide the URL to a command:
487 not provide the URL to a command:
479
488
480 default:
489 default:
481 When you create a repository with hg clone, the clone command saves the
490 When you create a repository with hg clone, the clone command saves the
482 location of the source repository as the new repository's 'default'
491 location of the source repository as the new repository's 'default'
483 path. This is then used when you omit path from push- and pull-like
492 path. This is then used when you omit path from push- and pull-like
484 commands (including incoming and outgoing).
493 commands (including incoming and outgoing).
485
494
486 default-push:
495 default-push:
487 The push command will look for a path named 'default-push', and prefer
496 The push command will look for a path named 'default-push', and prefer
488 it over 'default' if both are defined.
497 it over 'default' if both are defined.
489 ''')),
498 ''')),
490 (["extensions"], _("Using additional features"), extshelp),
499 (["extensions"], _("Using additional features"), extshelp),
491 )
500 )
@@ -1,270 +1,272 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
6 - Mercurial [hg]
7 - Mercurial [hg]
7 - CVS [cvs]
8 - CVS [cvs]
8 - Darcs [darcs]
9 - Darcs [darcs]
9 - git [git]
10 - git [git]
10 - Subversion [svn]
11 - Subversion [svn]
11 - Monotone [mtn]
12 - Monotone [mtn]
12 - GNU Arch [gnuarch]
13 - GNU Arch [gnuarch]
13 - Bazaar [bzr]
14 - Bazaar [bzr]
14 - Perforce [p4]
15 - Perforce [p4]
15
16
16 Accepted destination formats [identifiers]:
17 Accepted destination formats [identifiers]:
18
17 - Mercurial [hg]
19 - Mercurial [hg]
18 - Subversion [svn] (history on branches is not preserved)
20 - Subversion [svn] (history on branches is not preserved)
19
21
20 If no revision is given, all revisions will be converted. Otherwise,
22 If no revision is given, all revisions will be converted. Otherwise,
21 convert will only import up to the named revision (given in a format
23 convert will only import up to the named revision (given in a format
22 understood by the source).
24 understood by the source).
23
25
24 If no destination directory name is specified, it defaults to the basename
26 If no destination directory name is specified, it defaults to the basename
25 of the source with '-hg' appended. If the destination repository doesn't
27 of the source with '-hg' appended. If the destination repository doesn't
26 exist, it will be created.
28 exist, it will be created.
27
29
28 By default, all sources except Mercurial will use --branchsort. Mercurial
30 By default, all sources except Mercurial will use --branchsort. Mercurial
29 uses --sourcesort to preserve original revision numbers order. Sort modes
31 uses --sourcesort to preserve original revision numbers order. Sort modes
30 have the following effects:
32 have the following effects:
31
33
32 --branchsort: convert from parent to child revision when possible, which
34 --branchsort convert from parent to child revision when possible, which
33 means branches are usually converted one after the other. It generates
35 means branches are usually converted one after the other. It
34 more compact repositories.
36 generates more compact repositories.
35 --datesort: sort revisions by date. Converted repositories have
37 --datesort sort revisions by date. Converted repositories have good-
36 good-looking changelogs but are often an order of magnitude larger than
38 looking changelogs but are often an order of magnitude
37 the same ones generated by --branchsort.
39 larger than the same ones generated by --branchsort.
38 --sourcesort: try to preserve source revisions order, only supported by
40 --sourcesort try to preserve source revisions order, only supported by
39 Mercurial sources.
41 Mercurial sources.
40
42
41 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
42 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file that
44 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file that
43 maps each source commit ID to the destination ID for that revision, like
45 maps each source commit ID to the destination ID for that revision, like
44 so:
46 so:
45
47
46 <source ID> <destination ID>
48 <source ID> <destination ID>
47
49
48 If the file doesn't exist, it's automatically created. It's updated on
50 If the file doesn't exist, it's automatically created. It's updated on
49 each commit copied, so convert-repo can be interrupted and can be run
51 each commit copied, so convert-repo can be interrupted and can be run
50 repeatedly to copy new commits.
52 repeatedly to copy new commits.
51
53
52 The [username mapping] file is a simple text file that maps each source
54 The [username mapping] file is a simple text file that maps each source
53 commit author to a destination commit author. It is handy for source SCMs
55 commit author to a destination commit author. It is handy for source SCMs
54 that use unix logins to identify authors (eg: CVS). One line per author
56 that use unix logins to identify authors (eg: CVS). One line per author
55 mapping and the line format is: srcauthor=whatever string you want
57 mapping and the line format is: srcauthor=whatever string you want
56
58
57 The filemap is a file that allows filtering and remapping of files and
59 The filemap is a file that allows filtering and remapping of files and
58 directories. Comment lines start with '#'. Each line can contain one of
60 directories. Comment lines start with '#'. Each line can contain one of
59 the following directives:
61 the following directives:
60
62
61 include path/to/file
63 include path/to/file
62
64
63 exclude path/to/file
65 exclude path/to/file
64
66
65 rename from/file to/file
67 rename from/file to/file
66
68
67 The 'include' directive causes a file, or all files under a directory, to
69 The 'include' directive causes a file, or all files under a directory, to
68 be included in the destination repository, and the exclusion of all other
70 be included in the destination repository, and the exclusion of all other
69 files and directories not explicitly included. The 'exclude' directive
71 files and directories not explicitly included. The 'exclude' directive
70 causes files or directories to be omitted. The 'rename' directive renames
72 causes files or directories to be omitted. The 'rename' directive renames
71 a file or directory. To rename from a subdirectory into the root of the
73 a file or directory. To rename from a subdirectory into the root of the
72 repository, use '.' as the path to rename to.
74 repository, use '.' as the path to rename to.
73
75
74 The splicemap is a file that allows insertion of synthetic history,
76 The splicemap is a file that allows insertion of synthetic history,
75 letting you specify the parents of a revision. This is useful if you want
77 letting you specify the parents of a revision. This is useful if you want
76 to e.g. give a Subversion merge two parents, or graft two disconnected
78 to e.g. give a Subversion merge two parents, or graft two disconnected
77 series of history together. Each entry contains a key, followed by a
79 series of history together. Each entry contains a key, followed by a
78 space, followed by one or two comma-separated values. The key is the
80 space, followed by one or two comma-separated values. The key is the
79 revision ID in the source revision control system whose parents should be
81 revision ID in the source revision control system whose parents should be
80 modified (same format as a key in .hg/shamap). The values are the revision
82 modified (same format as a key in .hg/shamap). The values are the revision
81 IDs (in either the source or destination revision control system) that
83 IDs (in either the source or destination revision control system) that
82 should be used as the new parents for that node.
84 should be used as the new parents for that node.
83
85
84 The branchmap is a file that allows you to rename a branch when it is
86 The branchmap is a file that allows you to rename a branch when it is
85 being brought in from whatever external repository. When used in
87 being brought in from whatever external repository. When used in
86 conjunction with a splicemap, it allows for a powerful combination to help
88 conjunction with a splicemap, it allows for a powerful combination to help
87 fix even the most badly mismanaged repositories and turn them into nicely
89 fix even the most badly mismanaged repositories and turn them into nicely
88 structured Mercurial repositories. The branchmap contains lines of the
90 structured Mercurial repositories. The branchmap contains lines of the
89 form "original_branch_name new_branch_name". "original_branch_name" is the
91 form "original_branch_name new_branch_name". "original_branch_name" is the
90 name of the branch in the source repository, and "new_branch_name" is the
92 name of the branch in the source repository, and "new_branch_name" is the
91 name of the branch is the destination repository. This can be used to (for
93 name of the branch is the destination repository. This can be used to (for
92 instance) move code in one repository from "default" to a named branch.
94 instance) move code in one repository from "default" to a named branch.
93
95
94 Mercurial Source
96 Mercurial Source
95 ----------------
97 ----------------
96
98
97 --config convert.hg.ignoreerrors=False (boolean)
99 --config convert.hg.ignoreerrors=False (boolean)
98 ignore integrity errors when reading. Use it to fix Mercurial
100 ignore integrity errors when reading. Use it to fix Mercurial
99 repositories with missing revlogs, by converting from and to
101 repositories with missing revlogs, by converting from and to
100 Mercurial.
102 Mercurial.
101 --config convert.hg.saverev=False (boolean)
103 --config convert.hg.saverev=False (boolean)
102 store original revision ID in changeset (forces target IDs to change)
104 store original revision ID in changeset (forces target IDs to change)
103 --config convert.hg.startrev=0 (hg revision identifier)
105 --config convert.hg.startrev=0 (hg revision identifier)
104 convert start revision and its descendants
106 convert start revision and its descendants
105
107
106 CVS Source
108 CVS Source
107 ----------
109 ----------
108
110
109 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
111 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
110 indicate the starting point of what will be converted. Direct access to
112 indicate the starting point of what will be converted. Direct access to
111 the repository files is not needed, unless of course the repository is
113 the repository files is not needed, unless of course the repository is
112 :local:. The conversion uses the top level directory in the sandbox to
114 :local:. The conversion uses the top level directory in the sandbox to
113 find the CVS repository, and then uses CVS rlog commands to find files to
115 find the CVS repository, and then uses CVS rlog commands to find files to
114 convert. This means that unless a filemap is given, all files under the
116 convert. This means that unless a filemap is given, all files under the
115 starting directory will be converted, and that any directory
117 starting directory will be converted, and that any directory
116 reorganization in the CVS sandbox is ignored.
118 reorganization in the CVS sandbox is ignored.
117
119
118 Because CVS does not have changesets, it is necessary to collect
120 Because CVS does not have changesets, it is necessary to collect
119 individual commits to CVS and merge them into changesets. CVS source uses
121 individual commits to CVS and merge them into changesets. CVS source uses
120 its internal changeset merging code by default but can be configured to
122 its internal changeset merging code by default but can be configured to
121 call the external 'cvsps' program by setting:
123 call the external 'cvsps' program by setting:
122
124
123 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
125 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
124
126
125 This option is deprecated and will be removed in Mercurial 1.4.
127 This option is deprecated and will be removed in Mercurial 1.4.
126
128
127 The options shown are the defaults.
129 The options shown are the defaults.
128
130
129 Internal cvsps is selected by setting
131 Internal cvsps is selected by setting
130
132
131 --config convert.cvsps=builtin
133 --config convert.cvsps=builtin
132
134
133 and has a few more configurable options:
135 and has a few more configurable options:
134
136
135 --config convert.cvsps.cache=True (boolean)
137 --config convert.cvsps.cache=True (boolean)
136 Set to False to disable remote log caching, for testing and debugging
138 Set to False to disable remote log caching, for testing and debugging
137 purposes.
139 purposes.
138 --config convert.cvsps.fuzz=60 (integer)
140 --config convert.cvsps.fuzz=60 (integer)
139 Specify the maximum time (in seconds) that is allowed between commits
141 Specify the maximum time (in seconds) that is allowed between commits
140 with identical user and log message in a single changeset. When very
142 with identical user and log message in a single changeset. When very
141 large files were checked in as part of a changeset then the default
143 large files were checked in as part of a changeset then the default
142 may not be long enough.
144 may not be long enough.
143 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
145 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
144 Specify a regular expression to which commit log messages are matched.
146 Specify a regular expression to which commit log messages are matched.
145 If a match occurs, then the conversion process will insert a dummy
147 If a match occurs, then the conversion process will insert a dummy
146 revision merging the branch on which this log message occurs to the
148 revision merging the branch on which this log message occurs to the
147 branch indicated in the regex.
149 branch indicated in the regex.
148 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
150 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
149 Specify a regular expression to which commit log messages are matched.
151 Specify a regular expression to which commit log messages are matched.
150 If a match occurs, then the conversion process will add the most
152 If a match occurs, then the conversion process will add the most
151 recent revision on the branch indicated in the regex as the second
153 recent revision on the branch indicated in the regex as the second
152 parent of the changeset.
154 parent of the changeset.
153
155
154 The hgext/convert/cvsps wrapper script allows the builtin changeset
156 The hgext/convert/cvsps wrapper script allows the builtin changeset
155 merging code to be run without doing a conversion. Its parameters and
157 merging code to be run without doing a conversion. Its parameters and
156 output are similar to that of cvsps 2.1.
158 output are similar to that of cvsps 2.1.
157
159
158 Subversion Source
160 Subversion Source
159 -----------------
161 -----------------
160
162
161 Subversion source detects classical trunk/branches/tags layouts. By
163 Subversion source detects classical trunk/branches/tags layouts. By
162 default, the supplied "svn://repo/path/" source URL is converted as a
164 default, the supplied "svn://repo/path/" source URL is converted as a
163 single branch. If "svn://repo/path/trunk" exists it replaces the default
165 single branch. If "svn://repo/path/trunk" exists it replaces the default
164 branch. If "svn://repo/path/branches" exists, its subdirectories are
166 branch. If "svn://repo/path/branches" exists, its subdirectories are
165 listed as possible branches. If "svn://repo/path/tags" exists, it is
167 listed as possible branches. If "svn://repo/path/tags" exists, it is
166 looked for tags referencing converted branches. Default "trunk",
168 looked for tags referencing converted branches. Default "trunk",
167 "branches" and "tags" values can be overridden with following options. Set
169 "branches" and "tags" values can be overridden with following options. Set
168 them to paths relative to the source URL, or leave them blank to disable
170 them to paths relative to the source URL, or leave them blank to disable
169 auto detection.
171 auto detection.
170
172
171 --config convert.svn.branches=branches (directory name)
173 --config convert.svn.branches=branches (directory name)
172 specify the directory containing branches
174 specify the directory containing branches
173 --config convert.svn.tags=tags (directory name)
175 --config convert.svn.tags=tags (directory name)
174 specify the directory containing tags
176 specify the directory containing tags
175 --config convert.svn.trunk=trunk (directory name)
177 --config convert.svn.trunk=trunk (directory name)
176 specify the name of the trunk branch
178 specify the name of the trunk branch
177
179
178 Source history can be retrieved starting at a specific revision, instead
180 Source history can be retrieved starting at a specific revision, instead
179 of being integrally converted. Only single branch conversions are
181 of being integrally converted. Only single branch conversions are
180 supported.
182 supported.
181
183
182 --config convert.svn.startrev=0 (svn revision number)
184 --config convert.svn.startrev=0 (svn revision number)
183 specify start Subversion revision.
185 specify start Subversion revision.
184
186
185 Perforce Source
187 Perforce Source
186 ---------------
188 ---------------
187
189
188 The Perforce (P4) importer can be given a p4 depot path or a client
190 The Perforce (P4) importer can be given a p4 depot path or a client
189 specification as source. It will convert all files in the source to a flat
191 specification as source. It will convert all files in the source to a flat
190 Mercurial repository, ignoring labels, branches and integrations. Note
192 Mercurial repository, ignoring labels, branches and integrations. Note
191 that when a depot path is given you then usually should specify a target
193 that when a depot path is given you then usually should specify a target
192 directory, because otherwise the target may be named ...-hg.
194 directory, because otherwise the target may be named ...-hg.
193
195
194 It is possible to limit the amount of source history to be converted by
196 It is possible to limit the amount of source history to be converted by
195 specifying an initial Perforce revision.
197 specifying an initial Perforce revision.
196
198
197 --config convert.p4.startrev=0 (perforce changelist number)
199 --config convert.p4.startrev=0 (perforce changelist number)
198 specify initial Perforce revision.
200 specify initial Perforce revision.
199
201
200 Mercurial Destination
202 Mercurial Destination
201 ---------------------
203 ---------------------
202
204
203 --config convert.hg.clonebranches=False (boolean)
205 --config convert.hg.clonebranches=False (boolean)
204 dispatch source branches in separate clones.
206 dispatch source branches in separate clones.
205 --config convert.hg.tagsbranch=default (branch name)
207 --config convert.hg.tagsbranch=default (branch name)
206 tag revisions branch name
208 tag revisions branch name
207 --config convert.hg.usebranchnames=True (boolean)
209 --config convert.hg.usebranchnames=True (boolean)
208 preserve branch names
210 preserve branch names
209
211
210 options:
212 options:
211
213
212 -A --authors username mapping filename
214 -A --authors username mapping filename
213 -d --dest-type destination repository type
215 -d --dest-type destination repository type
214 --filemap remap file names using contents of file
216 --filemap remap file names using contents of file
215 -r --rev import up to target revision REV
217 -r --rev import up to target revision REV
216 -s --source-type source repository type
218 -s --source-type source repository type
217 --splicemap splice synthesized history into place
219 --splicemap splice synthesized history into place
218 --branchmap change branch names while converting
220 --branchmap change branch names while converting
219 --branchsort try to sort changesets by branches
221 --branchsort try to sort changesets by branches
220 --datesort try to sort changesets by date
222 --datesort try to sort changesets by date
221 --sourcesort preserve source changesets order
223 --sourcesort preserve source changesets order
222
224
223 use "hg -v help convert" to show global options
225 use "hg -v help convert" to show global options
224 adding a
226 adding a
225 assuming destination a-hg
227 assuming destination a-hg
226 initializing destination a-hg repository
228 initializing destination a-hg repository
227 scanning source...
229 scanning source...
228 sorting...
230 sorting...
229 converting...
231 converting...
230 4 a
232 4 a
231 3 b
233 3 b
232 2 c
234 2 c
233 1 d
235 1 d
234 0 e
236 0 e
235 pulling from ../a
237 pulling from ../a
236 searching for changes
238 searching for changes
237 no changes found
239 no changes found
238 % should fail
240 % should fail
239 initializing destination bogusfile repository
241 initializing destination bogusfile repository
240 abort: cannot create new bundle repository
242 abort: cannot create new bundle repository
241 % should fail
243 % should fail
242 abort: Permission denied: bogusdir
244 abort: Permission denied: bogusdir
243 % should succeed
245 % should succeed
244 initializing destination bogusdir repository
246 initializing destination bogusdir repository
245 scanning source...
247 scanning source...
246 sorting...
248 sorting...
247 converting...
249 converting...
248 4 a
250 4 a
249 3 b
251 3 b
250 2 c
252 2 c
251 1 d
253 1 d
252 0 e
254 0 e
253 % test pre and post conversion actions
255 % test pre and post conversion actions
254 run hg source pre-conversion action
256 run hg source pre-conversion action
255 run hg sink pre-conversion action
257 run hg sink pre-conversion action
256 run hg sink post-conversion action
258 run hg sink post-conversion action
257 run hg source post-conversion action
259 run hg source post-conversion action
258 % converting empty dir should fail nicely
260 % converting empty dir should fail nicely
259 assuming destination emptydir-hg
261 assuming destination emptydir-hg
260 initializing destination emptydir-hg repository
262 initializing destination emptydir-hg repository
261 emptydir does not look like a CVS checkout
263 emptydir does not look like a CVS checkout
262 emptydir does not look like a Git repo
264 emptydir does not look like a Git repo
263 emptydir does not look like a Subversion repo
265 emptydir does not look like a Subversion repo
264 emptydir is not a local Mercurial repo
266 emptydir is not a local Mercurial repo
265 emptydir does not look like a darcs repo
267 emptydir does not look like a darcs repo
266 emptydir does not look like a monotone repo
268 emptydir does not look like a monotone repo
267 emptydir does not look like a GNU Arch repo
269 emptydir does not look like a GNU Arch repo
268 emptydir does not look like a Bazaar repo
270 emptydir does not look like a Bazaar repo
269 cannot find required "p4" tool
271 cannot find required "p4" tool
270 abort: emptydir: missing or unsupported repository
272 abort: emptydir: missing or unsupported repository
@@ -1,347 +1,348 b''
1 Mercurial Distributed SCM
1 Mercurial Distributed SCM
2
2
3 basic commands:
3 basic commands:
4
4
5 add add the specified files on the next commit
5 add add the specified files on the next commit
6 annotate show changeset information by line for each file
6 annotate show changeset information by line for each file
7 clone make a copy of an existing repository
7 clone make a copy of an existing repository
8 commit commit the specified files or all outstanding changes
8 commit commit the specified files or all outstanding changes
9 diff diff repository (or selected files)
9 diff diff repository (or selected files)
10 export dump the header and diffs for one or more changesets
10 export dump the header and diffs for one or more changesets
11 forget forget the specified files on the next commit
11 forget forget the specified files on the next commit
12 init create a new repository in the given directory
12 init create a new repository in the given directory
13 log show revision history of entire repository or files
13 log show revision history of entire repository or files
14 merge merge working directory with another revision
14 merge merge working directory with another revision
15 parents show the parents of the working directory or revision
15 parents show the parents of the working directory or revision
16 pull pull changes from the specified source
16 pull pull changes from the specified source
17 push push changes to the specified destination
17 push push changes to the specified destination
18 remove remove the specified files on the next commit
18 remove remove the specified files on the next commit
19 serve export the repository via HTTP
19 serve export the repository via HTTP
20 status show changed files in the working directory
20 status show changed files in the working directory
21 update update working directory
21 update update working directory
22
22
23 use "hg help" for the full list of commands or "hg -v" for details
23 use "hg help" for the full list of commands or "hg -v" for details
24 add add the specified files on the next commit
24 add add the specified files on the next commit
25 annotate show changeset information by line for each file
25 annotate show changeset information by line for each file
26 clone make a copy of an existing repository
26 clone make a copy of an existing repository
27 commit commit the specified files or all outstanding changes
27 commit commit the specified files or all outstanding changes
28 diff diff repository (or selected files)
28 diff diff repository (or selected files)
29 export dump the header and diffs for one or more changesets
29 export dump the header and diffs for one or more changesets
30 forget forget the specified files on the next commit
30 forget forget the specified files on the next commit
31 init create a new repository in the given directory
31 init create a new repository in the given directory
32 log show revision history of entire repository or files
32 log show revision history of entire repository or files
33 merge merge working directory with another revision
33 merge merge working directory with another revision
34 parents show the parents of the working directory or revision
34 parents show the parents of the working directory or revision
35 pull pull changes from the specified source
35 pull pull changes from the specified source
36 push push changes to the specified destination
36 push push changes to the specified destination
37 remove remove the specified files on the next commit
37 remove remove the specified files on the next commit
38 serve export the repository via HTTP
38 serve export the repository via HTTP
39 status show changed files in the working directory
39 status show changed files in the working directory
40 update update working directory
40 update update working directory
41 Mercurial Distributed SCM
41 Mercurial Distributed SCM
42
42
43 list of commands:
43 list of commands:
44
44
45 add add the specified files on the next commit
45 add add the specified files on the next commit
46 addremove add all new files, delete all missing files
46 addremove add all new files, delete all missing files
47 annotate show changeset information by line for each file
47 annotate show changeset information by line for each file
48 archive create an unversioned archive of a repository revision
48 archive create an unversioned archive of a repository revision
49 backout reverse effect of earlier changeset
49 backout reverse effect of earlier changeset
50 bisect subdivision search of changesets
50 bisect subdivision search of changesets
51 branch set or show the current branch name
51 branch set or show the current branch name
52 branches list repository named branches
52 branches list repository named branches
53 bundle create a changegroup file
53 bundle create a changegroup file
54 cat output the current or given revision of files
54 cat output the current or given revision of files
55 clone make a copy of an existing repository
55 clone make a copy of an existing repository
56 commit commit the specified files or all outstanding changes
56 commit commit the specified files or all outstanding changes
57 copy mark files as copied for the next commit
57 copy mark files as copied for the next commit
58 diff diff repository (or selected files)
58 diff diff repository (or selected files)
59 export dump the header and diffs for one or more changesets
59 export dump the header and diffs for one or more changesets
60 forget forget the specified files on the next commit
60 forget forget the specified files on the next commit
61 grep search for a pattern in specified files and revisions
61 grep search for a pattern in specified files and revisions
62 heads show current repository heads or show branch heads
62 heads show current repository heads or show branch heads
63 help show help for a given topic or a help overview
63 help show help for a given topic or a help overview
64 identify identify the working copy or specified revision
64 identify identify the working copy or specified revision
65 import import an ordered set of patches
65 import import an ordered set of patches
66 incoming show new changesets found in source
66 incoming show new changesets found in source
67 init create a new repository in the given directory
67 init create a new repository in the given directory
68 locate locate files matching specific patterns
68 locate locate files matching specific patterns
69 log show revision history of entire repository or files
69 log show revision history of entire repository or files
70 manifest output the current or given revision of the project manifest
70 manifest output the current or given revision of the project manifest
71 merge merge working directory with another revision
71 merge merge working directory with another revision
72 outgoing show changesets not found in destination
72 outgoing show changesets not found in destination
73 parents show the parents of the working directory or revision
73 parents show the parents of the working directory or revision
74 paths show aliases for remote repositories
74 paths show aliases for remote repositories
75 pull pull changes from the specified source
75 pull pull changes from the specified source
76 push push changes to the specified destination
76 push push changes to the specified destination
77 recover roll back an interrupted transaction
77 recover roll back an interrupted transaction
78 remove remove the specified files on the next commit
78 remove remove the specified files on the next commit
79 rename rename files; equivalent of copy + remove
79 rename rename files; equivalent of copy + remove
80 resolve retry file merges from a merge or update
80 resolve retry file merges from a merge or update
81 revert restore individual files or directories to an earlier state
81 revert restore individual files or directories to an earlier state
82 rollback roll back the last transaction
82 rollback roll back the last transaction
83 root print the root (top) of the current working directory
83 root print the root (top) of the current working directory
84 serve export the repository via HTTP
84 serve export the repository via HTTP
85 showconfig show combined config settings from all hgrc files
85 showconfig show combined config settings from all hgrc files
86 status show changed files in the working directory
86 status show changed files in the working directory
87 tag add one or more tags for the current or given revision
87 tag add one or more tags for the current or given revision
88 tags list repository tags
88 tags list repository tags
89 tip show the tip revision
89 tip show the tip revision
90 unbundle apply one or more changegroup files
90 unbundle apply one or more changegroup files
91 update update working directory
91 update update working directory
92 verify verify the integrity of the repository
92 verify verify the integrity of the repository
93 version output version and copyright information
93 version output version and copyright information
94
94
95 additional help topics:
95 additional help topics:
96
96
97 dates Date Formats
97 dates Date Formats
98 patterns File Name Patterns
98 patterns File Name Patterns
99 environment Environment Variables
99 environment Environment Variables
100 revisions Specifying Single Revisions
100 revisions Specifying Single Revisions
101 multirevs Specifying Multiple Revisions
101 multirevs Specifying Multiple Revisions
102 diffs Diff Formats
102 diffs Diff Formats
103 templating Template Usage
103 templating Template Usage
104 urls URL Paths
104 urls URL Paths
105 extensions Using additional features
105 extensions Using additional features
106
106
107 use "hg -v help" to show aliases and global options
107 use "hg -v help" to show aliases and global options
108 add add the specified files on the next commit
108 add add the specified files on the next commit
109 addremove add all new files, delete all missing files
109 addremove add all new files, delete all missing files
110 annotate show changeset information by line for each file
110 annotate show changeset information by line for each file
111 archive create an unversioned archive of a repository revision
111 archive create an unversioned archive of a repository revision
112 backout reverse effect of earlier changeset
112 backout reverse effect of earlier changeset
113 bisect subdivision search of changesets
113 bisect subdivision search of changesets
114 branch set or show the current branch name
114 branch set or show the current branch name
115 branches list repository named branches
115 branches list repository named branches
116 bundle create a changegroup file
116 bundle create a changegroup file
117 cat output the current or given revision of files
117 cat output the current or given revision of files
118 clone make a copy of an existing repository
118 clone make a copy of an existing repository
119 commit commit the specified files or all outstanding changes
119 commit commit the specified files or all outstanding changes
120 copy mark files as copied for the next commit
120 copy mark files as copied for the next commit
121 diff diff repository (or selected files)
121 diff diff repository (or selected files)
122 export dump the header and diffs for one or more changesets
122 export dump the header and diffs for one or more changesets
123 forget forget the specified files on the next commit
123 forget forget the specified files on the next commit
124 grep search for a pattern in specified files and revisions
124 grep search for a pattern in specified files and revisions
125 heads show current repository heads or show branch heads
125 heads show current repository heads or show branch heads
126 help show help for a given topic or a help overview
126 help show help for a given topic or a help overview
127 identify identify the working copy or specified revision
127 identify identify the working copy or specified revision
128 import import an ordered set of patches
128 import import an ordered set of patches
129 incoming show new changesets found in source
129 incoming show new changesets found in source
130 init create a new repository in the given directory
130 init create a new repository in the given directory
131 locate locate files matching specific patterns
131 locate locate files matching specific patterns
132 log show revision history of entire repository or files
132 log show revision history of entire repository or files
133 manifest output the current or given revision of the project manifest
133 manifest output the current or given revision of the project manifest
134 merge merge working directory with another revision
134 merge merge working directory with another revision
135 outgoing show changesets not found in destination
135 outgoing show changesets not found in destination
136 parents show the parents of the working directory or revision
136 parents show the parents of the working directory or revision
137 paths show aliases for remote repositories
137 paths show aliases for remote repositories
138 pull pull changes from the specified source
138 pull pull changes from the specified source
139 push push changes to the specified destination
139 push push changes to the specified destination
140 recover roll back an interrupted transaction
140 recover roll back an interrupted transaction
141 remove remove the specified files on the next commit
141 remove remove the specified files on the next commit
142 rename rename files; equivalent of copy + remove
142 rename rename files; equivalent of copy + remove
143 resolve retry file merges from a merge or update
143 resolve retry file merges from a merge or update
144 revert restore individual files or directories to an earlier state
144 revert restore individual files or directories to an earlier state
145 rollback roll back the last transaction
145 rollback roll back the last transaction
146 root print the root (top) of the current working directory
146 root print the root (top) of the current working directory
147 serve export the repository via HTTP
147 serve export the repository via HTTP
148 showconfig show combined config settings from all hgrc files
148 showconfig show combined config settings from all hgrc files
149 status show changed files in the working directory
149 status show changed files in the working directory
150 tag add one or more tags for the current or given revision
150 tag add one or more tags for the current or given revision
151 tags list repository tags
151 tags list repository tags
152 tip show the tip revision
152 tip show the tip revision
153 unbundle apply one or more changegroup files
153 unbundle apply one or more changegroup files
154 update update working directory
154 update update working directory
155 verify verify the integrity of the repository
155 verify verify the integrity of the repository
156 version output version and copyright information
156 version output version and copyright information
157
157
158 additional help topics:
158 additional help topics:
159
159
160 dates Date Formats
160 dates Date Formats
161 patterns File Name Patterns
161 patterns File Name Patterns
162 environment Environment Variables
162 environment Environment Variables
163 revisions Specifying Single Revisions
163 revisions Specifying Single Revisions
164 multirevs Specifying Multiple Revisions
164 multirevs Specifying Multiple Revisions
165 diffs Diff Formats
165 diffs Diff Formats
166 templating Template Usage
166 templating Template Usage
167 urls URL Paths
167 urls URL Paths
168 extensions Using additional features
168 extensions Using additional features
169 hg add [OPTION]... [FILE]...
169 hg add [OPTION]... [FILE]...
170
170
171 add the specified files on the next commit
171 add the specified files on the next commit
172
172
173 Schedule files to be version controlled and added to the repository.
173 Schedule files to be version controlled and added to the repository.
174
174
175 The files will be added to the repository at the next commit. To undo an
175 The files will be added to the repository at the next commit. To undo an
176 add before that, see hg forget.
176 add before that, see hg forget.
177
177
178 If no names are given, add all files to the repository.
178 If no names are given, add all files to the repository.
179
179
180 options:
180 options:
181
181
182 -I --include include names matching the given patterns
182 -I --include include names matching the given patterns
183 -X --exclude exclude names matching the given patterns
183 -X --exclude exclude names matching the given patterns
184 -n --dry-run do not perform actions, just print output
184 -n --dry-run do not perform actions, just print output
185
185
186 use "hg -v help add" to show global options
186 use "hg -v help add" to show global options
187 hg add: option --skjdfks not recognized
187 hg add: option --skjdfks not recognized
188 hg add [OPTION]... [FILE]...
188 hg add [OPTION]... [FILE]...
189
189
190 add the specified files on the next commit
190 add the specified files on the next commit
191
191
192 Schedule files to be version controlled and added to the repository.
192 Schedule files to be version controlled and added to the repository.
193
193
194 The files will be added to the repository at the next commit. To undo an
194 The files will be added to the repository at the next commit. To undo an
195 add before that, see hg forget.
195 add before that, see hg forget.
196
196
197 If no names are given, add all files to the repository.
197 If no names are given, add all files to the repository.
198
198
199 options:
199 options:
200
200
201 -I --include include names matching the given patterns
201 -I --include include names matching the given patterns
202 -X --exclude exclude names matching the given patterns
202 -X --exclude exclude names matching the given patterns
203 -n --dry-run do not perform actions, just print output
203 -n --dry-run do not perform actions, just print output
204
204
205 use "hg -v help add" to show global options
205 use "hg -v help add" to show global options
206 hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...
206 hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...
207
207
208 diff repository (or selected files)
208 diff repository (or selected files)
209
209
210 Show differences between revisions for the specified files.
210 Show differences between revisions for the specified files.
211
211
212 Differences between files are shown using the unified diff format.
212 Differences between files are shown using the unified diff format.
213
213
214 NOTE: diff may generate unexpected results for merges, as it will default
214 NOTE: diff may generate unexpected results for merges, as it will default
215 to comparing against the working directory's first parent changeset if no
215 to comparing against the working directory's first parent changeset if no
216 revisions are specified.
216 revisions are specified.
217
217
218 When two revision arguments are given, then changes are shown between
218 When two revision arguments are given, then changes are shown between
219 those revisions. If only one revision is specified then that revision is
219 those revisions. If only one revision is specified then that revision is
220 compared to the working directory, and, when no revisions are specified,
220 compared to the working directory, and, when no revisions are specified,
221 the working directory files are compared to its parent.
221 the working directory files are compared to its parent.
222
222
223 Without the -a/--text option, diff will avoid generating diffs of files it
223 Without the -a/--text option, diff will avoid generating diffs of files it
224 detects as binary. With -a, diff will generate a diff anyway, probably
224 detects as binary. With -a, diff will generate a diff anyway, probably
225 with undesirable results.
225 with undesirable results.
226
226
227 Use the -g/--git option to generate diffs in the git extended diff format.
227 Use the -g/--git option to generate diffs in the git extended diff format.
228 For more information, read 'hg help diffs'.
228 For more information, read 'hg help diffs'.
229
229
230 options:
230 options:
231
231
232 -r --rev revision
232 -r --rev revision
233 -c --change change made by revision
233 -c --change change made by revision
234 -a --text treat all files as text
234 -a --text treat all files as text
235 -g --git use git extended diff format
235 -g --git use git extended diff format
236 --nodates don't include dates in diff headers
236 --nodates don't include dates in diff headers
237 -p --show-function show which function each change is in
237 -p --show-function show which function each change is in
238 -w --ignore-all-space ignore white space when comparing lines
238 -w --ignore-all-space ignore white space when comparing lines
239 -b --ignore-space-change ignore changes in the amount of white space
239 -b --ignore-space-change ignore changes in the amount of white space
240 -B --ignore-blank-lines ignore changes whose lines are all blank
240 -B --ignore-blank-lines ignore changes whose lines are all blank
241 -U --unified number of lines of context to show
241 -U --unified number of lines of context to show
242 -I --include include names matching the given patterns
242 -I --include include names matching the given patterns
243 -X --exclude exclude names matching the given patterns
243 -X --exclude exclude names matching the given patterns
244
244
245 use "hg -v help diff" to show global options
245 use "hg -v help diff" to show global options
246 hg status [OPTION]... [FILE]...
246 hg status [OPTION]... [FILE]...
247
247
248 aliases: st
248 aliases: st
249
249
250 show changed files in the working directory
250 show changed files in the working directory
251
251
252 Show status of files in the repository. If names are given, only files
252 Show status of files in the repository. If names are given, only files
253 that match are shown. Files that are clean or ignored or the source of a
253 that match are shown. Files that are clean or ignored or the source of a
254 copy/move operation, are not listed unless -c/--clean, -i/--ignored,
254 copy/move operation, are not listed unless -c/--clean, -i/--ignored,
255 -C/--copies or -A/--all are given. Unless options described with "show
255 -C/--copies or -A/--all are given. Unless options described with "show
256 only ..." are given, the options -mardu are used.
256 only ..." are given, the options -mardu are used.
257
257
258 Option -q/--quiet hides untracked (unknown and ignored) files unless
258 Option -q/--quiet hides untracked (unknown and ignored) files unless
259 explicitly requested with -u/--unknown or -i/--ignored.
259 explicitly requested with -u/--unknown or -i/--ignored.
260
260
261 NOTE: status may appear to disagree with diff if permissions have changed
261 NOTE: status may appear to disagree with diff if permissions have changed
262 or a merge has occurred. The standard diff format does not report
262 or a merge has occurred. The standard diff format does not report
263 permission changes and diff only reports changes relative to one merge
263 permission changes and diff only reports changes relative to one merge
264 parent.
264 parent.
265
265
266 If one revision is given, it is used as the base revision. If two
266 If one revision is given, it is used as the base revision. If two
267 revisions are given, the differences between them are shown.
267 revisions are given, the differences between them are shown.
268
268
269 The codes used to show the status of files are:
269 The codes used to show the status of files are:
270
270 M = modified
271 M = modified
271 A = added
272 A = added
272 R = removed
273 R = removed
273 C = clean
274 C = clean
274 ! = missing (deleted by non-hg command, but still tracked)
275 ! = missing (deleted by non-hg command, but still tracked)
275 ? = not tracked
276 ? = not tracked
276 I = ignored
277 I = ignored
277 = origin of the previous file listed as A (added)
278 = origin of the previous file listed as A (added)
278
279
279 options:
280 options:
280
281
281 -A --all show status of all files
282 -A --all show status of all files
282 -m --modified show only modified files
283 -m --modified show only modified files
283 -a --added show only added files
284 -a --added show only added files
284 -r --removed show only removed files
285 -r --removed show only removed files
285 -d --deleted show only deleted (but tracked) files
286 -d --deleted show only deleted (but tracked) files
286 -c --clean show only files without changes
287 -c --clean show only files without changes
287 -u --unknown show only unknown (not tracked) files
288 -u --unknown show only unknown (not tracked) files
288 -i --ignored show only ignored files
289 -i --ignored show only ignored files
289 -n --no-status hide status prefix
290 -n --no-status hide status prefix
290 -C --copies show source of copied files
291 -C --copies show source of copied files
291 -0 --print0 end filenames with NUL, for use with xargs
292 -0 --print0 end filenames with NUL, for use with xargs
292 --rev show difference from revision
293 --rev show difference from revision
293 -I --include include names matching the given patterns
294 -I --include include names matching the given patterns
294 -X --exclude exclude names matching the given patterns
295 -X --exclude exclude names matching the given patterns
295
296
296 use "hg -v help status" to show global options
297 use "hg -v help status" to show global options
297 hg status [OPTION]... [FILE]...
298 hg status [OPTION]... [FILE]...
298
299
299 show changed files in the working directory
300 show changed files in the working directory
300 hg: unknown command 'foo'
301 hg: unknown command 'foo'
301 Mercurial Distributed SCM
302 Mercurial Distributed SCM
302
303
303 basic commands:
304 basic commands:
304
305
305 add add the specified files on the next commit
306 add add the specified files on the next commit
306 annotate show changeset information by line for each file
307 annotate show changeset information by line for each file
307 clone make a copy of an existing repository
308 clone make a copy of an existing repository
308 commit commit the specified files or all outstanding changes
309 commit commit the specified files or all outstanding changes
309 diff diff repository (or selected files)
310 diff diff repository (or selected files)
310 export dump the header and diffs for one or more changesets
311 export dump the header and diffs for one or more changesets
311 forget forget the specified files on the next commit
312 forget forget the specified files on the next commit
312 init create a new repository in the given directory
313 init create a new repository in the given directory
313 log show revision history of entire repository or files
314 log show revision history of entire repository or files
314 merge merge working directory with another revision
315 merge merge working directory with another revision
315 parents show the parents of the working directory or revision
316 parents show the parents of the working directory or revision
316 pull pull changes from the specified source
317 pull pull changes from the specified source
317 push push changes to the specified destination
318 push push changes to the specified destination
318 remove remove the specified files on the next commit
319 remove remove the specified files on the next commit
319 serve export the repository via HTTP
320 serve export the repository via HTTP
320 status show changed files in the working directory
321 status show changed files in the working directory
321 update update working directory
322 update update working directory
322
323
323 use "hg help" for the full list of commands or "hg -v" for details
324 use "hg help" for the full list of commands or "hg -v" for details
324 hg: unknown command 'skjdfks'
325 hg: unknown command 'skjdfks'
325 Mercurial Distributed SCM
326 Mercurial Distributed SCM
326
327
327 basic commands:
328 basic commands:
328
329
329 add add the specified files on the next commit
330 add add the specified files on the next commit
330 annotate show changeset information by line for each file
331 annotate show changeset information by line for each file
331 clone make a copy of an existing repository
332 clone make a copy of an existing repository
332 commit commit the specified files or all outstanding changes
333 commit commit the specified files or all outstanding changes
333 diff diff repository (or selected files)
334 diff diff repository (or selected files)
334 export dump the header and diffs for one or more changesets
335 export dump the header and diffs for one or more changesets
335 forget forget the specified files on the next commit
336 forget forget the specified files on the next commit
336 init create a new repository in the given directory
337 init create a new repository in the given directory
337 log show revision history of entire repository or files
338 log show revision history of entire repository or files
338 merge merge working directory with another revision
339 merge merge working directory with another revision
339 parents show the parents of the working directory or revision
340 parents show the parents of the working directory or revision
340 pull pull changes from the specified source
341 pull pull changes from the specified source
341 push push changes to the specified destination
342 push push changes to the specified destination
342 remove remove the specified files on the next commit
343 remove remove the specified files on the next commit
343 serve export the repository via HTTP
344 serve export the repository via HTTP
344 status show changed files in the working directory
345 status show changed files in the working directory
345 update update working directory
346 update update working directory
346
347
347 use "hg help" for the full list of commands or "hg -v" for details
348 use "hg help" for the full list of commands or "hg -v" for details
General Comments 0
You need to be logged in to leave comments. Login now