##// END OF EJS Templates
log: introduce struct that carries log traversal options...
Yuya Nishihara -
r46139:c1d0f83d default
parent child Browse files
Show More
@@ -1,440 +1,440 b''
1 # sparse.py - allow sparse checkouts of the working directory
1 # sparse.py - allow sparse checkouts of the working directory
2 #
2 #
3 # Copyright 2014 Facebook, Inc.
3 # Copyright 2014 Facebook, Inc.
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 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """allow sparse checkouts of the working directory (EXPERIMENTAL)
8 """allow sparse checkouts of the working directory (EXPERIMENTAL)
9
9
10 (This extension is not yet protected by backwards compatibility
10 (This extension is not yet protected by backwards compatibility
11 guarantees. Any aspect may break in future releases until this
11 guarantees. Any aspect may break in future releases until this
12 notice is removed.)
12 notice is removed.)
13
13
14 This extension allows the working directory to only consist of a
14 This extension allows the working directory to only consist of a
15 subset of files for the revision. This allows specific files or
15 subset of files for the revision. This allows specific files or
16 directories to be explicitly included or excluded. Many repository
16 directories to be explicitly included or excluded. Many repository
17 operations have performance proportional to the number of files in
17 operations have performance proportional to the number of files in
18 the working directory. So only realizing a subset of files in the
18 the working directory. So only realizing a subset of files in the
19 working directory can improve performance.
19 working directory can improve performance.
20
20
21 Sparse Config Files
21 Sparse Config Files
22 -------------------
22 -------------------
23
23
24 The set of files that are part of a sparse checkout are defined by
24 The set of files that are part of a sparse checkout are defined by
25 a sparse config file. The file defines 3 things: includes (files to
25 a sparse config file. The file defines 3 things: includes (files to
26 include in the sparse checkout), excludes (files to exclude from the
26 include in the sparse checkout), excludes (files to exclude from the
27 sparse checkout), and profiles (links to other config files).
27 sparse checkout), and profiles (links to other config files).
28
28
29 The file format is newline delimited. Empty lines and lines beginning
29 The file format is newline delimited. Empty lines and lines beginning
30 with ``#`` are ignored.
30 with ``#`` are ignored.
31
31
32 Lines beginning with ``%include `` denote another sparse config file
32 Lines beginning with ``%include `` denote another sparse config file
33 to include. e.g. ``%include tests.sparse``. The filename is relative
33 to include. e.g. ``%include tests.sparse``. The filename is relative
34 to the repository root.
34 to the repository root.
35
35
36 The special lines ``[include]`` and ``[exclude]`` denote the section
36 The special lines ``[include]`` and ``[exclude]`` denote the section
37 for includes and excludes that follow, respectively. It is illegal to
37 for includes and excludes that follow, respectively. It is illegal to
38 have ``[include]`` after ``[exclude]``.
38 have ``[include]`` after ``[exclude]``.
39
39
40 Non-special lines resemble file patterns to be added to either includes
40 Non-special lines resemble file patterns to be added to either includes
41 or excludes. The syntax of these lines is documented by :hg:`help patterns`.
41 or excludes. The syntax of these lines is documented by :hg:`help patterns`.
42 Patterns are interpreted as ``glob:`` by default and match against the
42 Patterns are interpreted as ``glob:`` by default and match against the
43 root of the repository.
43 root of the repository.
44
44
45 Exclusion patterns take precedence over inclusion patterns. So even
45 Exclusion patterns take precedence over inclusion patterns. So even
46 if a file is explicitly included, an ``[exclude]`` entry can remove it.
46 if a file is explicitly included, an ``[exclude]`` entry can remove it.
47
47
48 For example, say you have a repository with 3 directories, ``frontend/``,
48 For example, say you have a repository with 3 directories, ``frontend/``,
49 ``backend/``, and ``tools/``. ``frontend/`` and ``backend/`` correspond
49 ``backend/``, and ``tools/``. ``frontend/`` and ``backend/`` correspond
50 to different projects and it is uncommon for someone working on one
50 to different projects and it is uncommon for someone working on one
51 to need the files for the other. But ``tools/`` contains files shared
51 to need the files for the other. But ``tools/`` contains files shared
52 between both projects. Your sparse config files may resemble::
52 between both projects. Your sparse config files may resemble::
53
53
54 # frontend.sparse
54 # frontend.sparse
55 frontend/**
55 frontend/**
56 tools/**
56 tools/**
57
57
58 # backend.sparse
58 # backend.sparse
59 backend/**
59 backend/**
60 tools/**
60 tools/**
61
61
62 Say the backend grows in size. Or there's a directory with thousands
62 Say the backend grows in size. Or there's a directory with thousands
63 of files you wish to exclude. You can modify the profile to exclude
63 of files you wish to exclude. You can modify the profile to exclude
64 certain files::
64 certain files::
65
65
66 [include]
66 [include]
67 backend/**
67 backend/**
68 tools/**
68 tools/**
69
69
70 [exclude]
70 [exclude]
71 tools/tests/**
71 tools/tests/**
72 """
72 """
73
73
74 from __future__ import absolute_import
74 from __future__ import absolute_import
75
75
76 from mercurial.i18n import _
76 from mercurial.i18n import _
77 from mercurial.pycompat import setattr
77 from mercurial.pycompat import setattr
78 from mercurial import (
78 from mercurial import (
79 commands,
79 commands,
80 dirstate,
80 dirstate,
81 error,
81 error,
82 extensions,
82 extensions,
83 hg,
83 hg,
84 logcmdutil,
84 logcmdutil,
85 match as matchmod,
85 match as matchmod,
86 pycompat,
86 pycompat,
87 registrar,
87 registrar,
88 sparse,
88 sparse,
89 util,
89 util,
90 )
90 )
91
91
92 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
92 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
93 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
93 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
94 # be specifying the version(s) of Mercurial they are tested with, or
94 # be specifying the version(s) of Mercurial they are tested with, or
95 # leave the attribute unspecified.
95 # leave the attribute unspecified.
96 testedwith = b'ships-with-hg-core'
96 testedwith = b'ships-with-hg-core'
97
97
98 cmdtable = {}
98 cmdtable = {}
99 command = registrar.command(cmdtable)
99 command = registrar.command(cmdtable)
100
100
101
101
102 def extsetup(ui):
102 def extsetup(ui):
103 sparse.enabled = True
103 sparse.enabled = True
104
104
105 _setupclone(ui)
105 _setupclone(ui)
106 _setuplog(ui)
106 _setuplog(ui)
107 _setupadd(ui)
107 _setupadd(ui)
108 _setupdirstate(ui)
108 _setupdirstate(ui)
109
109
110
110
111 def replacefilecache(cls, propname, replacement):
111 def replacefilecache(cls, propname, replacement):
112 """Replace a filecache property with a new class. This allows changing the
112 """Replace a filecache property with a new class. This allows changing the
113 cache invalidation condition."""
113 cache invalidation condition."""
114 origcls = cls
114 origcls = cls
115 assert callable(replacement)
115 assert callable(replacement)
116 while cls is not object:
116 while cls is not object:
117 if propname in cls.__dict__:
117 if propname in cls.__dict__:
118 orig = cls.__dict__[propname]
118 orig = cls.__dict__[propname]
119 setattr(cls, propname, replacement(orig))
119 setattr(cls, propname, replacement(orig))
120 break
120 break
121 cls = cls.__bases__[0]
121 cls = cls.__bases__[0]
122
122
123 if cls is object:
123 if cls is object:
124 raise AttributeError(
124 raise AttributeError(
125 _(b"type '%s' has no property '%s'") % (origcls, propname)
125 _(b"type '%s' has no property '%s'") % (origcls, propname)
126 )
126 )
127
127
128
128
129 def _setuplog(ui):
129 def _setuplog(ui):
130 entry = commands.table[b'log|history']
130 entry = commands.table[b'log|history']
131 entry[1].append(
131 entry[1].append(
132 (
132 (
133 b'',
133 b'',
134 b'sparse',
134 b'sparse',
135 None,
135 None,
136 b"limit to changesets affecting the sparse checkout",
136 b"limit to changesets affecting the sparse checkout",
137 )
137 )
138 )
138 )
139
139
140 def _initialrevs(orig, repo, opts):
140 def _initialrevs(orig, repo, wopts):
141 revs = orig(repo, opts)
141 revs = orig(repo, wopts)
142 if opts.get(b'sparse'):
142 if wopts.opts.get(b'sparse'):
143 sparsematch = sparse.matcher(repo)
143 sparsematch = sparse.matcher(repo)
144
144
145 def ctxmatch(rev):
145 def ctxmatch(rev):
146 ctx = repo[rev]
146 ctx = repo[rev]
147 return any(f for f in ctx.files() if sparsematch(f))
147 return any(f for f in ctx.files() if sparsematch(f))
148
148
149 revs = revs.filter(ctxmatch)
149 revs = revs.filter(ctxmatch)
150 return revs
150 return revs
151
151
152 extensions.wrapfunction(logcmdutil, b'_initialrevs', _initialrevs)
152 extensions.wrapfunction(logcmdutil, b'_initialrevs', _initialrevs)
153
153
154
154
155 def _clonesparsecmd(orig, ui, repo, *args, **opts):
155 def _clonesparsecmd(orig, ui, repo, *args, **opts):
156 include_pat = opts.get('include')
156 include_pat = opts.get('include')
157 exclude_pat = opts.get('exclude')
157 exclude_pat = opts.get('exclude')
158 enableprofile_pat = opts.get('enable_profile')
158 enableprofile_pat = opts.get('enable_profile')
159 narrow_pat = opts.get('narrow')
159 narrow_pat = opts.get('narrow')
160 include = exclude = enableprofile = False
160 include = exclude = enableprofile = False
161 if include_pat:
161 if include_pat:
162 pat = include_pat
162 pat = include_pat
163 include = True
163 include = True
164 if exclude_pat:
164 if exclude_pat:
165 pat = exclude_pat
165 pat = exclude_pat
166 exclude = True
166 exclude = True
167 if enableprofile_pat:
167 if enableprofile_pat:
168 pat = enableprofile_pat
168 pat = enableprofile_pat
169 enableprofile = True
169 enableprofile = True
170 if sum([include, exclude, enableprofile]) > 1:
170 if sum([include, exclude, enableprofile]) > 1:
171 raise error.Abort(_(b"too many flags specified."))
171 raise error.Abort(_(b"too many flags specified."))
172 # if --narrow is passed, it means they are includes and excludes for narrow
172 # if --narrow is passed, it means they are includes and excludes for narrow
173 # clone
173 # clone
174 if not narrow_pat and (include or exclude or enableprofile):
174 if not narrow_pat and (include or exclude or enableprofile):
175
175
176 def clonesparse(orig, self, node, overwrite, *args, **kwargs):
176 def clonesparse(orig, self, node, overwrite, *args, **kwargs):
177 sparse.updateconfig(
177 sparse.updateconfig(
178 self.unfiltered(),
178 self.unfiltered(),
179 pat,
179 pat,
180 {},
180 {},
181 include=include,
181 include=include,
182 exclude=exclude,
182 exclude=exclude,
183 enableprofile=enableprofile,
183 enableprofile=enableprofile,
184 usereporootpaths=True,
184 usereporootpaths=True,
185 )
185 )
186 return orig(self, node, overwrite, *args, **kwargs)
186 return orig(self, node, overwrite, *args, **kwargs)
187
187
188 extensions.wrapfunction(hg, b'updaterepo', clonesparse)
188 extensions.wrapfunction(hg, b'updaterepo', clonesparse)
189 return orig(ui, repo, *args, **opts)
189 return orig(ui, repo, *args, **opts)
190
190
191
191
192 def _setupclone(ui):
192 def _setupclone(ui):
193 entry = commands.table[b'clone']
193 entry = commands.table[b'clone']
194 entry[1].append((b'', b'enable-profile', [], b'enable a sparse profile'))
194 entry[1].append((b'', b'enable-profile', [], b'enable a sparse profile'))
195 entry[1].append((b'', b'include', [], b'include sparse pattern'))
195 entry[1].append((b'', b'include', [], b'include sparse pattern'))
196 entry[1].append((b'', b'exclude', [], b'exclude sparse pattern'))
196 entry[1].append((b'', b'exclude', [], b'exclude sparse pattern'))
197 extensions.wrapcommand(commands.table, b'clone', _clonesparsecmd)
197 extensions.wrapcommand(commands.table, b'clone', _clonesparsecmd)
198
198
199
199
200 def _setupadd(ui):
200 def _setupadd(ui):
201 entry = commands.table[b'add']
201 entry = commands.table[b'add']
202 entry[1].append(
202 entry[1].append(
203 (
203 (
204 b's',
204 b's',
205 b'sparse',
205 b'sparse',
206 None,
206 None,
207 b'also include directories of added files in sparse config',
207 b'also include directories of added files in sparse config',
208 )
208 )
209 )
209 )
210
210
211 def _add(orig, ui, repo, *pats, **opts):
211 def _add(orig, ui, repo, *pats, **opts):
212 if opts.get('sparse'):
212 if opts.get('sparse'):
213 dirs = set()
213 dirs = set()
214 for pat in pats:
214 for pat in pats:
215 dirname, basename = util.split(pat)
215 dirname, basename = util.split(pat)
216 dirs.add(dirname)
216 dirs.add(dirname)
217 sparse.updateconfig(repo, list(dirs), opts, include=True)
217 sparse.updateconfig(repo, list(dirs), opts, include=True)
218 return orig(ui, repo, *pats, **opts)
218 return orig(ui, repo, *pats, **opts)
219
219
220 extensions.wrapcommand(commands.table, b'add', _add)
220 extensions.wrapcommand(commands.table, b'add', _add)
221
221
222
222
223 def _setupdirstate(ui):
223 def _setupdirstate(ui):
224 """Modify the dirstate to prevent stat'ing excluded files,
224 """Modify the dirstate to prevent stat'ing excluded files,
225 and to prevent modifications to files outside the checkout.
225 and to prevent modifications to files outside the checkout.
226 """
226 """
227
227
228 def walk(orig, self, match, subrepos, unknown, ignored, full=True):
228 def walk(orig, self, match, subrepos, unknown, ignored, full=True):
229 # hack to not exclude explicitly-specified paths so that they can
229 # hack to not exclude explicitly-specified paths so that they can
230 # be warned later on e.g. dirstate.add()
230 # be warned later on e.g. dirstate.add()
231 em = matchmod.exact(match.files())
231 em = matchmod.exact(match.files())
232 sm = matchmod.unionmatcher([self._sparsematcher, em])
232 sm = matchmod.unionmatcher([self._sparsematcher, em])
233 match = matchmod.intersectmatchers(match, sm)
233 match = matchmod.intersectmatchers(match, sm)
234 return orig(self, match, subrepos, unknown, ignored, full)
234 return orig(self, match, subrepos, unknown, ignored, full)
235
235
236 extensions.wrapfunction(dirstate.dirstate, b'walk', walk)
236 extensions.wrapfunction(dirstate.dirstate, b'walk', walk)
237
237
238 # dirstate.rebuild should not add non-matching files
238 # dirstate.rebuild should not add non-matching files
239 def _rebuild(orig, self, parent, allfiles, changedfiles=None):
239 def _rebuild(orig, self, parent, allfiles, changedfiles=None):
240 matcher = self._sparsematcher
240 matcher = self._sparsematcher
241 if not matcher.always():
241 if not matcher.always():
242 allfiles = [f for f in allfiles if matcher(f)]
242 allfiles = [f for f in allfiles if matcher(f)]
243 if changedfiles:
243 if changedfiles:
244 changedfiles = [f for f in changedfiles if matcher(f)]
244 changedfiles = [f for f in changedfiles if matcher(f)]
245
245
246 if changedfiles is not None:
246 if changedfiles is not None:
247 # In _rebuild, these files will be deleted from the dirstate
247 # In _rebuild, these files will be deleted from the dirstate
248 # when they are not found to be in allfiles
248 # when they are not found to be in allfiles
249 dirstatefilestoremove = {f for f in self if not matcher(f)}
249 dirstatefilestoremove = {f for f in self if not matcher(f)}
250 changedfiles = dirstatefilestoremove.union(changedfiles)
250 changedfiles = dirstatefilestoremove.union(changedfiles)
251
251
252 return orig(self, parent, allfiles, changedfiles)
252 return orig(self, parent, allfiles, changedfiles)
253
253
254 extensions.wrapfunction(dirstate.dirstate, b'rebuild', _rebuild)
254 extensions.wrapfunction(dirstate.dirstate, b'rebuild', _rebuild)
255
255
256 # Prevent adding files that are outside the sparse checkout
256 # Prevent adding files that are outside the sparse checkout
257 editfuncs = [
257 editfuncs = [
258 b'normal',
258 b'normal',
259 b'add',
259 b'add',
260 b'normallookup',
260 b'normallookup',
261 b'copy',
261 b'copy',
262 b'remove',
262 b'remove',
263 b'merge',
263 b'merge',
264 ]
264 ]
265 hint = _(
265 hint = _(
266 b'include file with `hg debugsparse --include <pattern>` or use '
266 b'include file with `hg debugsparse --include <pattern>` or use '
267 + b'`hg add -s <file>` to include file directory while adding'
267 + b'`hg add -s <file>` to include file directory while adding'
268 )
268 )
269 for func in editfuncs:
269 for func in editfuncs:
270
270
271 def _wrapper(orig, self, *args, **kwargs):
271 def _wrapper(orig, self, *args, **kwargs):
272 sparsematch = self._sparsematcher
272 sparsematch = self._sparsematcher
273 if not sparsematch.always():
273 if not sparsematch.always():
274 for f in args:
274 for f in args:
275 if f is not None and not sparsematch(f) and f not in self:
275 if f is not None and not sparsematch(f) and f not in self:
276 raise error.Abort(
276 raise error.Abort(
277 _(
277 _(
278 b"cannot add '%s' - it is outside "
278 b"cannot add '%s' - it is outside "
279 b"the sparse checkout"
279 b"the sparse checkout"
280 )
280 )
281 % f,
281 % f,
282 hint=hint,
282 hint=hint,
283 )
283 )
284 return orig(self, *args, **kwargs)
284 return orig(self, *args, **kwargs)
285
285
286 extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
286 extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
287
287
288
288
289 @command(
289 @command(
290 b'debugsparse',
290 b'debugsparse',
291 [
291 [
292 (b'I', b'include', False, _(b'include files in the sparse checkout')),
292 (b'I', b'include', False, _(b'include files in the sparse checkout')),
293 (b'X', b'exclude', False, _(b'exclude files in the sparse checkout')),
293 (b'X', b'exclude', False, _(b'exclude files in the sparse checkout')),
294 (b'd', b'delete', False, _(b'delete an include/exclude rule')),
294 (b'd', b'delete', False, _(b'delete an include/exclude rule')),
295 (
295 (
296 b'f',
296 b'f',
297 b'force',
297 b'force',
298 False,
298 False,
299 _(b'allow changing rules even with pending changes'),
299 _(b'allow changing rules even with pending changes'),
300 ),
300 ),
301 (b'', b'enable-profile', False, _(b'enables the specified profile')),
301 (b'', b'enable-profile', False, _(b'enables the specified profile')),
302 (b'', b'disable-profile', False, _(b'disables the specified profile')),
302 (b'', b'disable-profile', False, _(b'disables the specified profile')),
303 (b'', b'import-rules', False, _(b'imports rules from a file')),
303 (b'', b'import-rules', False, _(b'imports rules from a file')),
304 (b'', b'clear-rules', False, _(b'clears local include/exclude rules')),
304 (b'', b'clear-rules', False, _(b'clears local include/exclude rules')),
305 (
305 (
306 b'',
306 b'',
307 b'refresh',
307 b'refresh',
308 False,
308 False,
309 _(b'updates the working after sparseness changes'),
309 _(b'updates the working after sparseness changes'),
310 ),
310 ),
311 (b'', b'reset', False, _(b'makes the repo full again')),
311 (b'', b'reset', False, _(b'makes the repo full again')),
312 ]
312 ]
313 + commands.templateopts,
313 + commands.templateopts,
314 _(b'[--OPTION] PATTERN...'),
314 _(b'[--OPTION] PATTERN...'),
315 helpbasic=True,
315 helpbasic=True,
316 )
316 )
317 def debugsparse(ui, repo, *pats, **opts):
317 def debugsparse(ui, repo, *pats, **opts):
318 """make the current checkout sparse, or edit the existing checkout
318 """make the current checkout sparse, or edit the existing checkout
319
319
320 The sparse command is used to make the current checkout sparse.
320 The sparse command is used to make the current checkout sparse.
321 This means files that don't meet the sparse condition will not be
321 This means files that don't meet the sparse condition will not be
322 written to disk, or show up in any working copy operations. It does
322 written to disk, or show up in any working copy operations. It does
323 not affect files in history in any way.
323 not affect files in history in any way.
324
324
325 Passing no arguments prints the currently applied sparse rules.
325 Passing no arguments prints the currently applied sparse rules.
326
326
327 --include and --exclude are used to add and remove files from the sparse
327 --include and --exclude are used to add and remove files from the sparse
328 checkout. The effects of adding an include or exclude rule are applied
328 checkout. The effects of adding an include or exclude rule are applied
329 immediately. If applying the new rule would cause a file with pending
329 immediately. If applying the new rule would cause a file with pending
330 changes to be added or removed, the command will fail. Pass --force to
330 changes to be added or removed, the command will fail. Pass --force to
331 force a rule change even with pending changes (the changes on disk will
331 force a rule change even with pending changes (the changes on disk will
332 be preserved).
332 be preserved).
333
333
334 --delete removes an existing include/exclude rule. The effects are
334 --delete removes an existing include/exclude rule. The effects are
335 immediate.
335 immediate.
336
336
337 --refresh refreshes the files on disk based on the sparse rules. This is
337 --refresh refreshes the files on disk based on the sparse rules. This is
338 only necessary if .hg/sparse was changed by hand.
338 only necessary if .hg/sparse was changed by hand.
339
339
340 --enable-profile and --disable-profile accept a path to a .hgsparse file.
340 --enable-profile and --disable-profile accept a path to a .hgsparse file.
341 This allows defining sparse checkouts and tracking them inside the
341 This allows defining sparse checkouts and tracking them inside the
342 repository. This is useful for defining commonly used sparse checkouts for
342 repository. This is useful for defining commonly used sparse checkouts for
343 many people to use. As the profile definition changes over time, the sparse
343 many people to use. As the profile definition changes over time, the sparse
344 checkout will automatically be updated appropriately, depending on which
344 checkout will automatically be updated appropriately, depending on which
345 changeset is checked out. Changes to .hgsparse are not applied until they
345 changeset is checked out. Changes to .hgsparse are not applied until they
346 have been committed.
346 have been committed.
347
347
348 --import-rules accepts a path to a file containing rules in the .hgsparse
348 --import-rules accepts a path to a file containing rules in the .hgsparse
349 format, allowing you to add --include, --exclude and --enable-profile rules
349 format, allowing you to add --include, --exclude and --enable-profile rules
350 in bulk. Like the --include, --exclude and --enable-profile switches, the
350 in bulk. Like the --include, --exclude and --enable-profile switches, the
351 changes are applied immediately.
351 changes are applied immediately.
352
352
353 --clear-rules removes all local include and exclude rules, while leaving
353 --clear-rules removes all local include and exclude rules, while leaving
354 any enabled profiles in place.
354 any enabled profiles in place.
355
355
356 Returns 0 if editing the sparse checkout succeeds.
356 Returns 0 if editing the sparse checkout succeeds.
357 """
357 """
358 opts = pycompat.byteskwargs(opts)
358 opts = pycompat.byteskwargs(opts)
359 include = opts.get(b'include')
359 include = opts.get(b'include')
360 exclude = opts.get(b'exclude')
360 exclude = opts.get(b'exclude')
361 force = opts.get(b'force')
361 force = opts.get(b'force')
362 enableprofile = opts.get(b'enable_profile')
362 enableprofile = opts.get(b'enable_profile')
363 disableprofile = opts.get(b'disable_profile')
363 disableprofile = opts.get(b'disable_profile')
364 importrules = opts.get(b'import_rules')
364 importrules = opts.get(b'import_rules')
365 clearrules = opts.get(b'clear_rules')
365 clearrules = opts.get(b'clear_rules')
366 delete = opts.get(b'delete')
366 delete = opts.get(b'delete')
367 refresh = opts.get(b'refresh')
367 refresh = opts.get(b'refresh')
368 reset = opts.get(b'reset')
368 reset = opts.get(b'reset')
369 count = sum(
369 count = sum(
370 [
370 [
371 include,
371 include,
372 exclude,
372 exclude,
373 enableprofile,
373 enableprofile,
374 disableprofile,
374 disableprofile,
375 delete,
375 delete,
376 importrules,
376 importrules,
377 refresh,
377 refresh,
378 clearrules,
378 clearrules,
379 reset,
379 reset,
380 ]
380 ]
381 )
381 )
382 if count > 1:
382 if count > 1:
383 raise error.Abort(_(b"too many flags specified"))
383 raise error.Abort(_(b"too many flags specified"))
384
384
385 if count == 0:
385 if count == 0:
386 if repo.vfs.exists(b'sparse'):
386 if repo.vfs.exists(b'sparse'):
387 ui.status(repo.vfs.read(b"sparse") + b"\n")
387 ui.status(repo.vfs.read(b"sparse") + b"\n")
388 temporaryincludes = sparse.readtemporaryincludes(repo)
388 temporaryincludes = sparse.readtemporaryincludes(repo)
389 if temporaryincludes:
389 if temporaryincludes:
390 ui.status(
390 ui.status(
391 _(b"Temporarily Included Files (for merge/rebase):\n")
391 _(b"Temporarily Included Files (for merge/rebase):\n")
392 )
392 )
393 ui.status((b"\n".join(temporaryincludes) + b"\n"))
393 ui.status((b"\n".join(temporaryincludes) + b"\n"))
394 return
394 return
395 else:
395 else:
396 raise error.Abort(
396 raise error.Abort(
397 _(
397 _(
398 b'the debugsparse command is only supported on'
398 b'the debugsparse command is only supported on'
399 b' sparse repositories'
399 b' sparse repositories'
400 )
400 )
401 )
401 )
402
402
403 if include or exclude or delete or reset or enableprofile or disableprofile:
403 if include or exclude or delete or reset or enableprofile or disableprofile:
404 sparse.updateconfig(
404 sparse.updateconfig(
405 repo,
405 repo,
406 pats,
406 pats,
407 opts,
407 opts,
408 include=include,
408 include=include,
409 exclude=exclude,
409 exclude=exclude,
410 reset=reset,
410 reset=reset,
411 delete=delete,
411 delete=delete,
412 enableprofile=enableprofile,
412 enableprofile=enableprofile,
413 disableprofile=disableprofile,
413 disableprofile=disableprofile,
414 force=force,
414 force=force,
415 )
415 )
416
416
417 if importrules:
417 if importrules:
418 sparse.importfromfiles(repo, opts, pats, force=force)
418 sparse.importfromfiles(repo, opts, pats, force=force)
419
419
420 if clearrules:
420 if clearrules:
421 sparse.clearrules(repo, force=force)
421 sparse.clearrules(repo, force=force)
422
422
423 if refresh:
423 if refresh:
424 try:
424 try:
425 wlock = repo.wlock()
425 wlock = repo.wlock()
426 fcounts = map(
426 fcounts = map(
427 len,
427 len,
428 sparse.refreshwdir(
428 sparse.refreshwdir(
429 repo, repo.status(), sparse.matcher(repo), force=force
429 repo, repo.status(), sparse.matcher(repo), force=force
430 ),
430 ),
431 )
431 )
432 sparse.printchanges(
432 sparse.printchanges(
433 ui,
433 ui,
434 opts,
434 opts,
435 added=fcounts[0],
435 added=fcounts[0],
436 dropped=fcounts[1],
436 dropped=fcounts[1],
437 conflicting=fcounts[2],
437 conflicting=fcounts[2],
438 )
438 )
439 finally:
439 finally:
440 wlock.release()
440 wlock.release()
@@ -1,7808 +1,7810 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 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import sys
14 import sys
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 wdirhex,
22 wdirhex,
23 wdirrev,
23 wdirrev,
24 )
24 )
25 from .pycompat import open
25 from .pycompat import open
26 from . import (
26 from . import (
27 archival,
27 archival,
28 bookmarks,
28 bookmarks,
29 bundle2,
29 bundle2,
30 changegroup,
30 changegroup,
31 cmdutil,
31 cmdutil,
32 copies,
32 copies,
33 debugcommands as debugcommandsmod,
33 debugcommands as debugcommandsmod,
34 destutil,
34 destutil,
35 dirstateguard,
35 dirstateguard,
36 discovery,
36 discovery,
37 encoding,
37 encoding,
38 error,
38 error,
39 exchange,
39 exchange,
40 extensions,
40 extensions,
41 filemerge,
41 filemerge,
42 formatter,
42 formatter,
43 graphmod,
43 graphmod,
44 hbisect,
44 hbisect,
45 help,
45 help,
46 hg,
46 hg,
47 logcmdutil,
47 logcmdutil,
48 merge as mergemod,
48 merge as mergemod,
49 mergestate as mergestatemod,
49 mergestate as mergestatemod,
50 narrowspec,
50 narrowspec,
51 obsolete,
51 obsolete,
52 obsutil,
52 obsutil,
53 patch,
53 patch,
54 phases,
54 phases,
55 pycompat,
55 pycompat,
56 rcutil,
56 rcutil,
57 registrar,
57 registrar,
58 requirements,
58 requirements,
59 revsetlang,
59 revsetlang,
60 rewriteutil,
60 rewriteutil,
61 scmutil,
61 scmutil,
62 server,
62 server,
63 shelve as shelvemod,
63 shelve as shelvemod,
64 state as statemod,
64 state as statemod,
65 streamclone,
65 streamclone,
66 tags as tagsmod,
66 tags as tagsmod,
67 ui as uimod,
67 ui as uimod,
68 util,
68 util,
69 verify as verifymod,
69 verify as verifymod,
70 vfs as vfsmod,
70 vfs as vfsmod,
71 wireprotoserver,
71 wireprotoserver,
72 )
72 )
73 from .utils import (
73 from .utils import (
74 dateutil,
74 dateutil,
75 stringutil,
75 stringutil,
76 )
76 )
77
77
78 table = {}
78 table = {}
79 table.update(debugcommandsmod.command._table)
79 table.update(debugcommandsmod.command._table)
80
80
81 command = registrar.command(table)
81 command = registrar.command(table)
82 INTENT_READONLY = registrar.INTENT_READONLY
82 INTENT_READONLY = registrar.INTENT_READONLY
83
83
84 # common command options
84 # common command options
85
85
86 globalopts = [
86 globalopts = [
87 (
87 (
88 b'R',
88 b'R',
89 b'repository',
89 b'repository',
90 b'',
90 b'',
91 _(b'repository root directory or name of overlay bundle file'),
91 _(b'repository root directory or name of overlay bundle file'),
92 _(b'REPO'),
92 _(b'REPO'),
93 ),
93 ),
94 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
94 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
95 (
95 (
96 b'y',
96 b'y',
97 b'noninteractive',
97 b'noninteractive',
98 None,
98 None,
99 _(
99 _(
100 b'do not prompt, automatically pick the first choice for all prompts'
100 b'do not prompt, automatically pick the first choice for all prompts'
101 ),
101 ),
102 ),
102 ),
103 (b'q', b'quiet', None, _(b'suppress output')),
103 (b'q', b'quiet', None, _(b'suppress output')),
104 (b'v', b'verbose', None, _(b'enable additional output')),
104 (b'v', b'verbose', None, _(b'enable additional output')),
105 (
105 (
106 b'',
106 b'',
107 b'color',
107 b'color',
108 b'',
108 b'',
109 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
109 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
110 # and should not be translated
110 # and should not be translated
111 _(b"when to colorize (boolean, always, auto, never, or debug)"),
111 _(b"when to colorize (boolean, always, auto, never, or debug)"),
112 _(b'TYPE'),
112 _(b'TYPE'),
113 ),
113 ),
114 (
114 (
115 b'',
115 b'',
116 b'config',
116 b'config',
117 [],
117 [],
118 _(b'set/override config option (use \'section.name=value\')'),
118 _(b'set/override config option (use \'section.name=value\')'),
119 _(b'CONFIG'),
119 _(b'CONFIG'),
120 ),
120 ),
121 (b'', b'debug', None, _(b'enable debugging output')),
121 (b'', b'debug', None, _(b'enable debugging output')),
122 (b'', b'debugger', None, _(b'start debugger')),
122 (b'', b'debugger', None, _(b'start debugger')),
123 (
123 (
124 b'',
124 b'',
125 b'encoding',
125 b'encoding',
126 encoding.encoding,
126 encoding.encoding,
127 _(b'set the charset encoding'),
127 _(b'set the charset encoding'),
128 _(b'ENCODE'),
128 _(b'ENCODE'),
129 ),
129 ),
130 (
130 (
131 b'',
131 b'',
132 b'encodingmode',
132 b'encodingmode',
133 encoding.encodingmode,
133 encoding.encodingmode,
134 _(b'set the charset encoding mode'),
134 _(b'set the charset encoding mode'),
135 _(b'MODE'),
135 _(b'MODE'),
136 ),
136 ),
137 (b'', b'traceback', None, _(b'always print a traceback on exception')),
137 (b'', b'traceback', None, _(b'always print a traceback on exception')),
138 (b'', b'time', None, _(b'time how long the command takes')),
138 (b'', b'time', None, _(b'time how long the command takes')),
139 (b'', b'profile', None, _(b'print command execution profile')),
139 (b'', b'profile', None, _(b'print command execution profile')),
140 (b'', b'version', None, _(b'output version information and exit')),
140 (b'', b'version', None, _(b'output version information and exit')),
141 (b'h', b'help', None, _(b'display help and exit')),
141 (b'h', b'help', None, _(b'display help and exit')),
142 (b'', b'hidden', False, _(b'consider hidden changesets')),
142 (b'', b'hidden', False, _(b'consider hidden changesets')),
143 (
143 (
144 b'',
144 b'',
145 b'pager',
145 b'pager',
146 b'auto',
146 b'auto',
147 _(b"when to paginate (boolean, always, auto, or never)"),
147 _(b"when to paginate (boolean, always, auto, or never)"),
148 _(b'TYPE'),
148 _(b'TYPE'),
149 ),
149 ),
150 ]
150 ]
151
151
152 dryrunopts = cmdutil.dryrunopts
152 dryrunopts = cmdutil.dryrunopts
153 remoteopts = cmdutil.remoteopts
153 remoteopts = cmdutil.remoteopts
154 walkopts = cmdutil.walkopts
154 walkopts = cmdutil.walkopts
155 commitopts = cmdutil.commitopts
155 commitopts = cmdutil.commitopts
156 commitopts2 = cmdutil.commitopts2
156 commitopts2 = cmdutil.commitopts2
157 commitopts3 = cmdutil.commitopts3
157 commitopts3 = cmdutil.commitopts3
158 formatteropts = cmdutil.formatteropts
158 formatteropts = cmdutil.formatteropts
159 templateopts = cmdutil.templateopts
159 templateopts = cmdutil.templateopts
160 logopts = cmdutil.logopts
160 logopts = cmdutil.logopts
161 diffopts = cmdutil.diffopts
161 diffopts = cmdutil.diffopts
162 diffwsopts = cmdutil.diffwsopts
162 diffwsopts = cmdutil.diffwsopts
163 diffopts2 = cmdutil.diffopts2
163 diffopts2 = cmdutil.diffopts2
164 mergetoolopts = cmdutil.mergetoolopts
164 mergetoolopts = cmdutil.mergetoolopts
165 similarityopts = cmdutil.similarityopts
165 similarityopts = cmdutil.similarityopts
166 subrepoopts = cmdutil.subrepoopts
166 subrepoopts = cmdutil.subrepoopts
167 debugrevlogopts = cmdutil.debugrevlogopts
167 debugrevlogopts = cmdutil.debugrevlogopts
168
168
169 # Commands start here, listed alphabetically
169 # Commands start here, listed alphabetically
170
170
171
171
172 @command(
172 @command(
173 b'abort',
173 b'abort',
174 dryrunopts,
174 dryrunopts,
175 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
175 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
176 helpbasic=True,
176 helpbasic=True,
177 )
177 )
178 def abort(ui, repo, **opts):
178 def abort(ui, repo, **opts):
179 """abort an unfinished operation (EXPERIMENTAL)
179 """abort an unfinished operation (EXPERIMENTAL)
180
180
181 Aborts a multistep operation like graft, histedit, rebase, merge,
181 Aborts a multistep operation like graft, histedit, rebase, merge,
182 and unshelve if they are in an unfinished state.
182 and unshelve if they are in an unfinished state.
183
183
184 use --dry-run/-n to dry run the command.
184 use --dry-run/-n to dry run the command.
185 """
185 """
186 dryrun = opts.get('dry_run')
186 dryrun = opts.get('dry_run')
187 abortstate = cmdutil.getunfinishedstate(repo)
187 abortstate = cmdutil.getunfinishedstate(repo)
188 if not abortstate:
188 if not abortstate:
189 raise error.Abort(_(b'no operation in progress'))
189 raise error.Abort(_(b'no operation in progress'))
190 if not abortstate.abortfunc:
190 if not abortstate.abortfunc:
191 raise error.Abort(
191 raise error.Abort(
192 (
192 (
193 _(b"%s in progress but does not support 'hg abort'")
193 _(b"%s in progress but does not support 'hg abort'")
194 % (abortstate._opname)
194 % (abortstate._opname)
195 ),
195 ),
196 hint=abortstate.hint(),
196 hint=abortstate.hint(),
197 )
197 )
198 if dryrun:
198 if dryrun:
199 ui.status(
199 ui.status(
200 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
200 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
201 )
201 )
202 return
202 return
203 return abortstate.abortfunc(ui, repo)
203 return abortstate.abortfunc(ui, repo)
204
204
205
205
206 @command(
206 @command(
207 b'add',
207 b'add',
208 walkopts + subrepoopts + dryrunopts,
208 walkopts + subrepoopts + dryrunopts,
209 _(b'[OPTION]... [FILE]...'),
209 _(b'[OPTION]... [FILE]...'),
210 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
210 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
211 helpbasic=True,
211 helpbasic=True,
212 inferrepo=True,
212 inferrepo=True,
213 )
213 )
214 def add(ui, repo, *pats, **opts):
214 def add(ui, repo, *pats, **opts):
215 """add the specified files on the next commit
215 """add the specified files on the next commit
216
216
217 Schedule files to be version controlled and added to the
217 Schedule files to be version controlled and added to the
218 repository.
218 repository.
219
219
220 The files will be added to the repository at the next commit. To
220 The files will be added to the repository at the next commit. To
221 undo an add before that, see :hg:`forget`.
221 undo an add before that, see :hg:`forget`.
222
222
223 If no names are given, add all files to the repository (except
223 If no names are given, add all files to the repository (except
224 files matching ``.hgignore``).
224 files matching ``.hgignore``).
225
225
226 .. container:: verbose
226 .. container:: verbose
227
227
228 Examples:
228 Examples:
229
229
230 - New (unknown) files are added
230 - New (unknown) files are added
231 automatically by :hg:`add`::
231 automatically by :hg:`add`::
232
232
233 $ ls
233 $ ls
234 foo.c
234 foo.c
235 $ hg status
235 $ hg status
236 ? foo.c
236 ? foo.c
237 $ hg add
237 $ hg add
238 adding foo.c
238 adding foo.c
239 $ hg status
239 $ hg status
240 A foo.c
240 A foo.c
241
241
242 - Specific files to be added can be specified::
242 - Specific files to be added can be specified::
243
243
244 $ ls
244 $ ls
245 bar.c foo.c
245 bar.c foo.c
246 $ hg status
246 $ hg status
247 ? bar.c
247 ? bar.c
248 ? foo.c
248 ? foo.c
249 $ hg add bar.c
249 $ hg add bar.c
250 $ hg status
250 $ hg status
251 A bar.c
251 A bar.c
252 ? foo.c
252 ? foo.c
253
253
254 Returns 0 if all files are successfully added.
254 Returns 0 if all files are successfully added.
255 """
255 """
256
256
257 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
257 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
258 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
258 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
259 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
259 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
260 return rejected and 1 or 0
260 return rejected and 1 or 0
261
261
262
262
263 @command(
263 @command(
264 b'addremove',
264 b'addremove',
265 similarityopts + subrepoopts + walkopts + dryrunopts,
265 similarityopts + subrepoopts + walkopts + dryrunopts,
266 _(b'[OPTION]... [FILE]...'),
266 _(b'[OPTION]... [FILE]...'),
267 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
267 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
268 inferrepo=True,
268 inferrepo=True,
269 )
269 )
270 def addremove(ui, repo, *pats, **opts):
270 def addremove(ui, repo, *pats, **opts):
271 """add all new files, delete all missing files
271 """add all new files, delete all missing files
272
272
273 Add all new files and remove all missing files from the
273 Add all new files and remove all missing files from the
274 repository.
274 repository.
275
275
276 Unless names are given, new files are ignored if they match any of
276 Unless names are given, new files are ignored if they match any of
277 the patterns in ``.hgignore``. As with add, these changes take
277 the patterns in ``.hgignore``. As with add, these changes take
278 effect at the next commit.
278 effect at the next commit.
279
279
280 Use the -s/--similarity option to detect renamed files. This
280 Use the -s/--similarity option to detect renamed files. This
281 option takes a percentage between 0 (disabled) and 100 (files must
281 option takes a percentage between 0 (disabled) and 100 (files must
282 be identical) as its parameter. With a parameter greater than 0,
282 be identical) as its parameter. With a parameter greater than 0,
283 this compares every removed file with every added file and records
283 this compares every removed file with every added file and records
284 those similar enough as renames. Detecting renamed files this way
284 those similar enough as renames. Detecting renamed files this way
285 can be expensive. After using this option, :hg:`status -C` can be
285 can be expensive. After using this option, :hg:`status -C` can be
286 used to check which files were identified as moved or renamed. If
286 used to check which files were identified as moved or renamed. If
287 not specified, -s/--similarity defaults to 100 and only renames of
287 not specified, -s/--similarity defaults to 100 and only renames of
288 identical files are detected.
288 identical files are detected.
289
289
290 .. container:: verbose
290 .. container:: verbose
291
291
292 Examples:
292 Examples:
293
293
294 - A number of files (bar.c and foo.c) are new,
294 - A number of files (bar.c and foo.c) are new,
295 while foobar.c has been removed (without using :hg:`remove`)
295 while foobar.c has been removed (without using :hg:`remove`)
296 from the repository::
296 from the repository::
297
297
298 $ ls
298 $ ls
299 bar.c foo.c
299 bar.c foo.c
300 $ hg status
300 $ hg status
301 ! foobar.c
301 ! foobar.c
302 ? bar.c
302 ? bar.c
303 ? foo.c
303 ? foo.c
304 $ hg addremove
304 $ hg addremove
305 adding bar.c
305 adding bar.c
306 adding foo.c
306 adding foo.c
307 removing foobar.c
307 removing foobar.c
308 $ hg status
308 $ hg status
309 A bar.c
309 A bar.c
310 A foo.c
310 A foo.c
311 R foobar.c
311 R foobar.c
312
312
313 - A file foobar.c was moved to foo.c without using :hg:`rename`.
313 - A file foobar.c was moved to foo.c without using :hg:`rename`.
314 Afterwards, it was edited slightly::
314 Afterwards, it was edited slightly::
315
315
316 $ ls
316 $ ls
317 foo.c
317 foo.c
318 $ hg status
318 $ hg status
319 ! foobar.c
319 ! foobar.c
320 ? foo.c
320 ? foo.c
321 $ hg addremove --similarity 90
321 $ hg addremove --similarity 90
322 removing foobar.c
322 removing foobar.c
323 adding foo.c
323 adding foo.c
324 recording removal of foobar.c as rename to foo.c (94% similar)
324 recording removal of foobar.c as rename to foo.c (94% similar)
325 $ hg status -C
325 $ hg status -C
326 A foo.c
326 A foo.c
327 foobar.c
327 foobar.c
328 R foobar.c
328 R foobar.c
329
329
330 Returns 0 if all files are successfully added.
330 Returns 0 if all files are successfully added.
331 """
331 """
332 opts = pycompat.byteskwargs(opts)
332 opts = pycompat.byteskwargs(opts)
333 if not opts.get(b'similarity'):
333 if not opts.get(b'similarity'):
334 opts[b'similarity'] = b'100'
334 opts[b'similarity'] = b'100'
335 matcher = scmutil.match(repo[None], pats, opts)
335 matcher = scmutil.match(repo[None], pats, opts)
336 relative = scmutil.anypats(pats, opts)
336 relative = scmutil.anypats(pats, opts)
337 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
337 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
338 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
338 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
339
339
340
340
341 @command(
341 @command(
342 b'annotate|blame',
342 b'annotate|blame',
343 [
343 [
344 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
344 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
345 (
345 (
346 b'',
346 b'',
347 b'follow',
347 b'follow',
348 None,
348 None,
349 _(b'follow copies/renames and list the filename (DEPRECATED)'),
349 _(b'follow copies/renames and list the filename (DEPRECATED)'),
350 ),
350 ),
351 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
351 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
352 (b'a', b'text', None, _(b'treat all files as text')),
352 (b'a', b'text', None, _(b'treat all files as text')),
353 (b'u', b'user', None, _(b'list the author (long with -v)')),
353 (b'u', b'user', None, _(b'list the author (long with -v)')),
354 (b'f', b'file', None, _(b'list the filename')),
354 (b'f', b'file', None, _(b'list the filename')),
355 (b'd', b'date', None, _(b'list the date (short with -q)')),
355 (b'd', b'date', None, _(b'list the date (short with -q)')),
356 (b'n', b'number', None, _(b'list the revision number (default)')),
356 (b'n', b'number', None, _(b'list the revision number (default)')),
357 (b'c', b'changeset', None, _(b'list the changeset')),
357 (b'c', b'changeset', None, _(b'list the changeset')),
358 (
358 (
359 b'l',
359 b'l',
360 b'line-number',
360 b'line-number',
361 None,
361 None,
362 _(b'show line number at the first appearance'),
362 _(b'show line number at the first appearance'),
363 ),
363 ),
364 (
364 (
365 b'',
365 b'',
366 b'skip',
366 b'skip',
367 [],
367 [],
368 _(b'revset to not display (EXPERIMENTAL)'),
368 _(b'revset to not display (EXPERIMENTAL)'),
369 _(b'REV'),
369 _(b'REV'),
370 ),
370 ),
371 ]
371 ]
372 + diffwsopts
372 + diffwsopts
373 + walkopts
373 + walkopts
374 + formatteropts,
374 + formatteropts,
375 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
375 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
376 helpcategory=command.CATEGORY_FILE_CONTENTS,
376 helpcategory=command.CATEGORY_FILE_CONTENTS,
377 helpbasic=True,
377 helpbasic=True,
378 inferrepo=True,
378 inferrepo=True,
379 )
379 )
380 def annotate(ui, repo, *pats, **opts):
380 def annotate(ui, repo, *pats, **opts):
381 """show changeset information by line for each file
381 """show changeset information by line for each file
382
382
383 List changes in files, showing the revision id responsible for
383 List changes in files, showing the revision id responsible for
384 each line.
384 each line.
385
385
386 This command is useful for discovering when a change was made and
386 This command is useful for discovering when a change was made and
387 by whom.
387 by whom.
388
388
389 If you include --file, --user, or --date, the revision number is
389 If you include --file, --user, or --date, the revision number is
390 suppressed unless you also include --number.
390 suppressed unless you also include --number.
391
391
392 Without the -a/--text option, annotate will avoid processing files
392 Without the -a/--text option, annotate will avoid processing files
393 it detects as binary. With -a, annotate will annotate the file
393 it detects as binary. With -a, annotate will annotate the file
394 anyway, although the results will probably be neither useful
394 anyway, although the results will probably be neither useful
395 nor desirable.
395 nor desirable.
396
396
397 .. container:: verbose
397 .. container:: verbose
398
398
399 Template:
399 Template:
400
400
401 The following keywords are supported in addition to the common template
401 The following keywords are supported in addition to the common template
402 keywords and functions. See also :hg:`help templates`.
402 keywords and functions. See also :hg:`help templates`.
403
403
404 :lines: List of lines with annotation data.
404 :lines: List of lines with annotation data.
405 :path: String. Repository-absolute path of the specified file.
405 :path: String. Repository-absolute path of the specified file.
406
406
407 And each entry of ``{lines}`` provides the following sub-keywords in
407 And each entry of ``{lines}`` provides the following sub-keywords in
408 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
408 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
409
409
410 :line: String. Line content.
410 :line: String. Line content.
411 :lineno: Integer. Line number at that revision.
411 :lineno: Integer. Line number at that revision.
412 :path: String. Repository-absolute path of the file at that revision.
412 :path: String. Repository-absolute path of the file at that revision.
413
413
414 See :hg:`help templates.operators` for the list expansion syntax.
414 See :hg:`help templates.operators` for the list expansion syntax.
415
415
416 Returns 0 on success.
416 Returns 0 on success.
417 """
417 """
418 opts = pycompat.byteskwargs(opts)
418 opts = pycompat.byteskwargs(opts)
419 if not pats:
419 if not pats:
420 raise error.Abort(_(b'at least one filename or pattern is required'))
420 raise error.Abort(_(b'at least one filename or pattern is required'))
421
421
422 if opts.get(b'follow'):
422 if opts.get(b'follow'):
423 # --follow is deprecated and now just an alias for -f/--file
423 # --follow is deprecated and now just an alias for -f/--file
424 # to mimic the behavior of Mercurial before version 1.5
424 # to mimic the behavior of Mercurial before version 1.5
425 opts[b'file'] = True
425 opts[b'file'] = True
426
426
427 if (
427 if (
428 not opts.get(b'user')
428 not opts.get(b'user')
429 and not opts.get(b'changeset')
429 and not opts.get(b'changeset')
430 and not opts.get(b'date')
430 and not opts.get(b'date')
431 and not opts.get(b'file')
431 and not opts.get(b'file')
432 ):
432 ):
433 opts[b'number'] = True
433 opts[b'number'] = True
434
434
435 linenumber = opts.get(b'line_number') is not None
435 linenumber = opts.get(b'line_number') is not None
436 if (
436 if (
437 linenumber
437 linenumber
438 and (not opts.get(b'changeset'))
438 and (not opts.get(b'changeset'))
439 and (not opts.get(b'number'))
439 and (not opts.get(b'number'))
440 ):
440 ):
441 raise error.Abort(_(b'at least one of -n/-c is required for -l'))
441 raise error.Abort(_(b'at least one of -n/-c is required for -l'))
442
442
443 rev = opts.get(b'rev')
443 rev = opts.get(b'rev')
444 if rev:
444 if rev:
445 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
445 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
446 ctx = scmutil.revsingle(repo, rev)
446 ctx = scmutil.revsingle(repo, rev)
447
447
448 ui.pager(b'annotate')
448 ui.pager(b'annotate')
449 rootfm = ui.formatter(b'annotate', opts)
449 rootfm = ui.formatter(b'annotate', opts)
450 if ui.debugflag:
450 if ui.debugflag:
451 shorthex = pycompat.identity
451 shorthex = pycompat.identity
452 else:
452 else:
453
453
454 def shorthex(h):
454 def shorthex(h):
455 return h[:12]
455 return h[:12]
456
456
457 if ui.quiet:
457 if ui.quiet:
458 datefunc = dateutil.shortdate
458 datefunc = dateutil.shortdate
459 else:
459 else:
460 datefunc = dateutil.datestr
460 datefunc = dateutil.datestr
461 if ctx.rev() is None:
461 if ctx.rev() is None:
462 if opts.get(b'changeset'):
462 if opts.get(b'changeset'):
463 # omit "+" suffix which is appended to node hex
463 # omit "+" suffix which is appended to node hex
464 def formatrev(rev):
464 def formatrev(rev):
465 if rev == wdirrev:
465 if rev == wdirrev:
466 return b'%d' % ctx.p1().rev()
466 return b'%d' % ctx.p1().rev()
467 else:
467 else:
468 return b'%d' % rev
468 return b'%d' % rev
469
469
470 else:
470 else:
471
471
472 def formatrev(rev):
472 def formatrev(rev):
473 if rev == wdirrev:
473 if rev == wdirrev:
474 return b'%d+' % ctx.p1().rev()
474 return b'%d+' % ctx.p1().rev()
475 else:
475 else:
476 return b'%d ' % rev
476 return b'%d ' % rev
477
477
478 def formathex(h):
478 def formathex(h):
479 if h == wdirhex:
479 if h == wdirhex:
480 return b'%s+' % shorthex(hex(ctx.p1().node()))
480 return b'%s+' % shorthex(hex(ctx.p1().node()))
481 else:
481 else:
482 return b'%s ' % shorthex(h)
482 return b'%s ' % shorthex(h)
483
483
484 else:
484 else:
485 formatrev = b'%d'.__mod__
485 formatrev = b'%d'.__mod__
486 formathex = shorthex
486 formathex = shorthex
487
487
488 opmap = [
488 opmap = [
489 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
489 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
490 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
490 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
491 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
491 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
492 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
492 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
493 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
493 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
494 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
494 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
495 ]
495 ]
496 opnamemap = {
496 opnamemap = {
497 b'rev': b'number',
497 b'rev': b'number',
498 b'node': b'changeset',
498 b'node': b'changeset',
499 b'path': b'file',
499 b'path': b'file',
500 b'lineno': b'line_number',
500 b'lineno': b'line_number',
501 }
501 }
502
502
503 if rootfm.isplain():
503 if rootfm.isplain():
504
504
505 def makefunc(get, fmt):
505 def makefunc(get, fmt):
506 return lambda x: fmt(get(x))
506 return lambda x: fmt(get(x))
507
507
508 else:
508 else:
509
509
510 def makefunc(get, fmt):
510 def makefunc(get, fmt):
511 return get
511 return get
512
512
513 datahint = rootfm.datahint()
513 datahint = rootfm.datahint()
514 funcmap = [
514 funcmap = [
515 (makefunc(get, fmt), sep)
515 (makefunc(get, fmt), sep)
516 for fn, sep, get, fmt in opmap
516 for fn, sep, get, fmt in opmap
517 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
517 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
518 ]
518 ]
519 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
519 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
520 fields = b' '.join(
520 fields = b' '.join(
521 fn
521 fn
522 for fn, sep, get, fmt in opmap
522 for fn, sep, get, fmt in opmap
523 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
523 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
524 )
524 )
525
525
526 def bad(x, y):
526 def bad(x, y):
527 raise error.Abort(b"%s: %s" % (x, y))
527 raise error.Abort(b"%s: %s" % (x, y))
528
528
529 m = scmutil.match(ctx, pats, opts, badfn=bad)
529 m = scmutil.match(ctx, pats, opts, badfn=bad)
530
530
531 follow = not opts.get(b'no_follow')
531 follow = not opts.get(b'no_follow')
532 diffopts = patch.difffeatureopts(
532 diffopts = patch.difffeatureopts(
533 ui, opts, section=b'annotate', whitespace=True
533 ui, opts, section=b'annotate', whitespace=True
534 )
534 )
535 skiprevs = opts.get(b'skip')
535 skiprevs = opts.get(b'skip')
536 if skiprevs:
536 if skiprevs:
537 skiprevs = scmutil.revrange(repo, skiprevs)
537 skiprevs = scmutil.revrange(repo, skiprevs)
538
538
539 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
539 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
540 for abs in ctx.walk(m):
540 for abs in ctx.walk(m):
541 fctx = ctx[abs]
541 fctx = ctx[abs]
542 rootfm.startitem()
542 rootfm.startitem()
543 rootfm.data(path=abs)
543 rootfm.data(path=abs)
544 if not opts.get(b'text') and fctx.isbinary():
544 if not opts.get(b'text') and fctx.isbinary():
545 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
545 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
546 continue
546 continue
547
547
548 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
548 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
549 lines = fctx.annotate(
549 lines = fctx.annotate(
550 follow=follow, skiprevs=skiprevs, diffopts=diffopts
550 follow=follow, skiprevs=skiprevs, diffopts=diffopts
551 )
551 )
552 if not lines:
552 if not lines:
553 fm.end()
553 fm.end()
554 continue
554 continue
555 formats = []
555 formats = []
556 pieces = []
556 pieces = []
557
557
558 for f, sep in funcmap:
558 for f, sep in funcmap:
559 l = [f(n) for n in lines]
559 l = [f(n) for n in lines]
560 if fm.isplain():
560 if fm.isplain():
561 sizes = [encoding.colwidth(x) for x in l]
561 sizes = [encoding.colwidth(x) for x in l]
562 ml = max(sizes)
562 ml = max(sizes)
563 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
563 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
564 else:
564 else:
565 formats.append([b'%s'] * len(l))
565 formats.append([b'%s'] * len(l))
566 pieces.append(l)
566 pieces.append(l)
567
567
568 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
568 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
569 fm.startitem()
569 fm.startitem()
570 fm.context(fctx=n.fctx)
570 fm.context(fctx=n.fctx)
571 fm.write(fields, b"".join(f), *p)
571 fm.write(fields, b"".join(f), *p)
572 if n.skip:
572 if n.skip:
573 fmt = b"* %s"
573 fmt = b"* %s"
574 else:
574 else:
575 fmt = b": %s"
575 fmt = b": %s"
576 fm.write(b'line', fmt, n.text)
576 fm.write(b'line', fmt, n.text)
577
577
578 if not lines[-1].text.endswith(b'\n'):
578 if not lines[-1].text.endswith(b'\n'):
579 fm.plain(b'\n')
579 fm.plain(b'\n')
580 fm.end()
580 fm.end()
581
581
582 rootfm.end()
582 rootfm.end()
583
583
584
584
585 @command(
585 @command(
586 b'archive',
586 b'archive',
587 [
587 [
588 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
588 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
589 (
589 (
590 b'p',
590 b'p',
591 b'prefix',
591 b'prefix',
592 b'',
592 b'',
593 _(b'directory prefix for files in archive'),
593 _(b'directory prefix for files in archive'),
594 _(b'PREFIX'),
594 _(b'PREFIX'),
595 ),
595 ),
596 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
596 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
597 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
597 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
598 ]
598 ]
599 + subrepoopts
599 + subrepoopts
600 + walkopts,
600 + walkopts,
601 _(b'[OPTION]... DEST'),
601 _(b'[OPTION]... DEST'),
602 helpcategory=command.CATEGORY_IMPORT_EXPORT,
602 helpcategory=command.CATEGORY_IMPORT_EXPORT,
603 )
603 )
604 def archive(ui, repo, dest, **opts):
604 def archive(ui, repo, dest, **opts):
605 '''create an unversioned archive of a repository revision
605 '''create an unversioned archive of a repository revision
606
606
607 By default, the revision used is the parent of the working
607 By default, the revision used is the parent of the working
608 directory; use -r/--rev to specify a different revision.
608 directory; use -r/--rev to specify a different revision.
609
609
610 The archive type is automatically detected based on file
610 The archive type is automatically detected based on file
611 extension (to override, use -t/--type).
611 extension (to override, use -t/--type).
612
612
613 .. container:: verbose
613 .. container:: verbose
614
614
615 Examples:
615 Examples:
616
616
617 - create a zip file containing the 1.0 release::
617 - create a zip file containing the 1.0 release::
618
618
619 hg archive -r 1.0 project-1.0.zip
619 hg archive -r 1.0 project-1.0.zip
620
620
621 - create a tarball excluding .hg files::
621 - create a tarball excluding .hg files::
622
622
623 hg archive project.tar.gz -X ".hg*"
623 hg archive project.tar.gz -X ".hg*"
624
624
625 Valid types are:
625 Valid types are:
626
626
627 :``files``: a directory full of files (default)
627 :``files``: a directory full of files (default)
628 :``tar``: tar archive, uncompressed
628 :``tar``: tar archive, uncompressed
629 :``tbz2``: tar archive, compressed using bzip2
629 :``tbz2``: tar archive, compressed using bzip2
630 :``tgz``: tar archive, compressed using gzip
630 :``tgz``: tar archive, compressed using gzip
631 :``txz``: tar archive, compressed using lzma (only in Python 3)
631 :``txz``: tar archive, compressed using lzma (only in Python 3)
632 :``uzip``: zip archive, uncompressed
632 :``uzip``: zip archive, uncompressed
633 :``zip``: zip archive, compressed using deflate
633 :``zip``: zip archive, compressed using deflate
634
634
635 The exact name of the destination archive or directory is given
635 The exact name of the destination archive or directory is given
636 using a format string; see :hg:`help export` for details.
636 using a format string; see :hg:`help export` for details.
637
637
638 Each member added to an archive file has a directory prefix
638 Each member added to an archive file has a directory prefix
639 prepended. Use -p/--prefix to specify a format string for the
639 prepended. Use -p/--prefix to specify a format string for the
640 prefix. The default is the basename of the archive, with suffixes
640 prefix. The default is the basename of the archive, with suffixes
641 removed.
641 removed.
642
642
643 Returns 0 on success.
643 Returns 0 on success.
644 '''
644 '''
645
645
646 opts = pycompat.byteskwargs(opts)
646 opts = pycompat.byteskwargs(opts)
647 rev = opts.get(b'rev')
647 rev = opts.get(b'rev')
648 if rev:
648 if rev:
649 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
649 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
650 ctx = scmutil.revsingle(repo, rev)
650 ctx = scmutil.revsingle(repo, rev)
651 if not ctx:
651 if not ctx:
652 raise error.Abort(_(b'no working directory: please specify a revision'))
652 raise error.Abort(_(b'no working directory: please specify a revision'))
653 node = ctx.node()
653 node = ctx.node()
654 dest = cmdutil.makefilename(ctx, dest)
654 dest = cmdutil.makefilename(ctx, dest)
655 if os.path.realpath(dest) == repo.root:
655 if os.path.realpath(dest) == repo.root:
656 raise error.Abort(_(b'repository root cannot be destination'))
656 raise error.Abort(_(b'repository root cannot be destination'))
657
657
658 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
658 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
659 prefix = opts.get(b'prefix')
659 prefix = opts.get(b'prefix')
660
660
661 if dest == b'-':
661 if dest == b'-':
662 if kind == b'files':
662 if kind == b'files':
663 raise error.Abort(_(b'cannot archive plain files to stdout'))
663 raise error.Abort(_(b'cannot archive plain files to stdout'))
664 dest = cmdutil.makefileobj(ctx, dest)
664 dest = cmdutil.makefileobj(ctx, dest)
665 if not prefix:
665 if not prefix:
666 prefix = os.path.basename(repo.root) + b'-%h'
666 prefix = os.path.basename(repo.root) + b'-%h'
667
667
668 prefix = cmdutil.makefilename(ctx, prefix)
668 prefix = cmdutil.makefilename(ctx, prefix)
669 match = scmutil.match(ctx, [], opts)
669 match = scmutil.match(ctx, [], opts)
670 archival.archive(
670 archival.archive(
671 repo,
671 repo,
672 dest,
672 dest,
673 node,
673 node,
674 kind,
674 kind,
675 not opts.get(b'no_decode'),
675 not opts.get(b'no_decode'),
676 match,
676 match,
677 prefix,
677 prefix,
678 subrepos=opts.get(b'subrepos'),
678 subrepos=opts.get(b'subrepos'),
679 )
679 )
680
680
681
681
682 @command(
682 @command(
683 b'backout',
683 b'backout',
684 [
684 [
685 (
685 (
686 b'',
686 b'',
687 b'merge',
687 b'merge',
688 None,
688 None,
689 _(b'merge with old dirstate parent after backout'),
689 _(b'merge with old dirstate parent after backout'),
690 ),
690 ),
691 (
691 (
692 b'',
692 b'',
693 b'commit',
693 b'commit',
694 None,
694 None,
695 _(b'commit if no conflicts were encountered (DEPRECATED)'),
695 _(b'commit if no conflicts were encountered (DEPRECATED)'),
696 ),
696 ),
697 (b'', b'no-commit', None, _(b'do not commit')),
697 (b'', b'no-commit', None, _(b'do not commit')),
698 (
698 (
699 b'',
699 b'',
700 b'parent',
700 b'parent',
701 b'',
701 b'',
702 _(b'parent to choose when backing out merge (DEPRECATED)'),
702 _(b'parent to choose when backing out merge (DEPRECATED)'),
703 _(b'REV'),
703 _(b'REV'),
704 ),
704 ),
705 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
705 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
706 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
706 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
707 ]
707 ]
708 + mergetoolopts
708 + mergetoolopts
709 + walkopts
709 + walkopts
710 + commitopts
710 + commitopts
711 + commitopts2,
711 + commitopts2,
712 _(b'[OPTION]... [-r] REV'),
712 _(b'[OPTION]... [-r] REV'),
713 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
713 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
714 )
714 )
715 def backout(ui, repo, node=None, rev=None, **opts):
715 def backout(ui, repo, node=None, rev=None, **opts):
716 '''reverse effect of earlier changeset
716 '''reverse effect of earlier changeset
717
717
718 Prepare a new changeset with the effect of REV undone in the
718 Prepare a new changeset with the effect of REV undone in the
719 current working directory. If no conflicts were encountered,
719 current working directory. If no conflicts were encountered,
720 it will be committed immediately.
720 it will be committed immediately.
721
721
722 If REV is the parent of the working directory, then this new changeset
722 If REV is the parent of the working directory, then this new changeset
723 is committed automatically (unless --no-commit is specified).
723 is committed automatically (unless --no-commit is specified).
724
724
725 .. note::
725 .. note::
726
726
727 :hg:`backout` cannot be used to fix either an unwanted or
727 :hg:`backout` cannot be used to fix either an unwanted or
728 incorrect merge.
728 incorrect merge.
729
729
730 .. container:: verbose
730 .. container:: verbose
731
731
732 Examples:
732 Examples:
733
733
734 - Reverse the effect of the parent of the working directory.
734 - Reverse the effect of the parent of the working directory.
735 This backout will be committed immediately::
735 This backout will be committed immediately::
736
736
737 hg backout -r .
737 hg backout -r .
738
738
739 - Reverse the effect of previous bad revision 23::
739 - Reverse the effect of previous bad revision 23::
740
740
741 hg backout -r 23
741 hg backout -r 23
742
742
743 - Reverse the effect of previous bad revision 23 and
743 - Reverse the effect of previous bad revision 23 and
744 leave changes uncommitted::
744 leave changes uncommitted::
745
745
746 hg backout -r 23 --no-commit
746 hg backout -r 23 --no-commit
747 hg commit -m "Backout revision 23"
747 hg commit -m "Backout revision 23"
748
748
749 By default, the pending changeset will have one parent,
749 By default, the pending changeset will have one parent,
750 maintaining a linear history. With --merge, the pending
750 maintaining a linear history. With --merge, the pending
751 changeset will instead have two parents: the old parent of the
751 changeset will instead have two parents: the old parent of the
752 working directory and a new child of REV that simply undoes REV.
752 working directory and a new child of REV that simply undoes REV.
753
753
754 Before version 1.7, the behavior without --merge was equivalent
754 Before version 1.7, the behavior without --merge was equivalent
755 to specifying --merge followed by :hg:`update --clean .` to
755 to specifying --merge followed by :hg:`update --clean .` to
756 cancel the merge and leave the child of REV as a head to be
756 cancel the merge and leave the child of REV as a head to be
757 merged separately.
757 merged separately.
758
758
759 See :hg:`help dates` for a list of formats valid for -d/--date.
759 See :hg:`help dates` for a list of formats valid for -d/--date.
760
760
761 See :hg:`help revert` for a way to restore files to the state
761 See :hg:`help revert` for a way to restore files to the state
762 of another revision.
762 of another revision.
763
763
764 Returns 0 on success, 1 if nothing to backout or there are unresolved
764 Returns 0 on success, 1 if nothing to backout or there are unresolved
765 files.
765 files.
766 '''
766 '''
767 with repo.wlock(), repo.lock():
767 with repo.wlock(), repo.lock():
768 return _dobackout(ui, repo, node, rev, **opts)
768 return _dobackout(ui, repo, node, rev, **opts)
769
769
770
770
771 def _dobackout(ui, repo, node=None, rev=None, **opts):
771 def _dobackout(ui, repo, node=None, rev=None, **opts):
772 opts = pycompat.byteskwargs(opts)
772 opts = pycompat.byteskwargs(opts)
773 if opts.get(b'commit') and opts.get(b'no_commit'):
773 if opts.get(b'commit') and opts.get(b'no_commit'):
774 raise error.Abort(_(b"cannot use --commit with --no-commit"))
774 raise error.Abort(_(b"cannot use --commit with --no-commit"))
775 if opts.get(b'merge') and opts.get(b'no_commit'):
775 if opts.get(b'merge') and opts.get(b'no_commit'):
776 raise error.Abort(_(b"cannot use --merge with --no-commit"))
776 raise error.Abort(_(b"cannot use --merge with --no-commit"))
777
777
778 if rev and node:
778 if rev and node:
779 raise error.Abort(_(b"please specify just one revision"))
779 raise error.Abort(_(b"please specify just one revision"))
780
780
781 if not rev:
781 if not rev:
782 rev = node
782 rev = node
783
783
784 if not rev:
784 if not rev:
785 raise error.Abort(_(b"please specify a revision to backout"))
785 raise error.Abort(_(b"please specify a revision to backout"))
786
786
787 date = opts.get(b'date')
787 date = opts.get(b'date')
788 if date:
788 if date:
789 opts[b'date'] = dateutil.parsedate(date)
789 opts[b'date'] = dateutil.parsedate(date)
790
790
791 cmdutil.checkunfinished(repo)
791 cmdutil.checkunfinished(repo)
792 cmdutil.bailifchanged(repo)
792 cmdutil.bailifchanged(repo)
793 ctx = scmutil.revsingle(repo, rev)
793 ctx = scmutil.revsingle(repo, rev)
794 node = ctx.node()
794 node = ctx.node()
795
795
796 op1, op2 = repo.dirstate.parents()
796 op1, op2 = repo.dirstate.parents()
797 if not repo.changelog.isancestor(node, op1):
797 if not repo.changelog.isancestor(node, op1):
798 raise error.Abort(_(b'cannot backout change that is not an ancestor'))
798 raise error.Abort(_(b'cannot backout change that is not an ancestor'))
799
799
800 p1, p2 = repo.changelog.parents(node)
800 p1, p2 = repo.changelog.parents(node)
801 if p1 == nullid:
801 if p1 == nullid:
802 raise error.Abort(_(b'cannot backout a change with no parents'))
802 raise error.Abort(_(b'cannot backout a change with no parents'))
803 if p2 != nullid:
803 if p2 != nullid:
804 if not opts.get(b'parent'):
804 if not opts.get(b'parent'):
805 raise error.Abort(_(b'cannot backout a merge changeset'))
805 raise error.Abort(_(b'cannot backout a merge changeset'))
806 p = repo.lookup(opts[b'parent'])
806 p = repo.lookup(opts[b'parent'])
807 if p not in (p1, p2):
807 if p not in (p1, p2):
808 raise error.Abort(
808 raise error.Abort(
809 _(b'%s is not a parent of %s') % (short(p), short(node))
809 _(b'%s is not a parent of %s') % (short(p), short(node))
810 )
810 )
811 parent = p
811 parent = p
812 else:
812 else:
813 if opts.get(b'parent'):
813 if opts.get(b'parent'):
814 raise error.Abort(_(b'cannot use --parent on non-merge changeset'))
814 raise error.Abort(_(b'cannot use --parent on non-merge changeset'))
815 parent = p1
815 parent = p1
816
816
817 # the backout should appear on the same branch
817 # the backout should appear on the same branch
818 branch = repo.dirstate.branch()
818 branch = repo.dirstate.branch()
819 bheads = repo.branchheads(branch)
819 bheads = repo.branchheads(branch)
820 rctx = scmutil.revsingle(repo, hex(parent))
820 rctx = scmutil.revsingle(repo, hex(parent))
821 if not opts.get(b'merge') and op1 != node:
821 if not opts.get(b'merge') and op1 != node:
822 with dirstateguard.dirstateguard(repo, b'backout'):
822 with dirstateguard.dirstateguard(repo, b'backout'):
823 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
823 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
824 with ui.configoverride(overrides, b'backout'):
824 with ui.configoverride(overrides, b'backout'):
825 stats = mergemod.back_out(ctx, parent=repo[parent])
825 stats = mergemod.back_out(ctx, parent=repo[parent])
826 repo.setparents(op1, op2)
826 repo.setparents(op1, op2)
827 hg._showstats(repo, stats)
827 hg._showstats(repo, stats)
828 if stats.unresolvedcount:
828 if stats.unresolvedcount:
829 repo.ui.status(
829 repo.ui.status(
830 _(b"use 'hg resolve' to retry unresolved file merges\n")
830 _(b"use 'hg resolve' to retry unresolved file merges\n")
831 )
831 )
832 return 1
832 return 1
833 else:
833 else:
834 hg.clean(repo, node, show_stats=False)
834 hg.clean(repo, node, show_stats=False)
835 repo.dirstate.setbranch(branch)
835 repo.dirstate.setbranch(branch)
836 cmdutil.revert(ui, repo, rctx)
836 cmdutil.revert(ui, repo, rctx)
837
837
838 if opts.get(b'no_commit'):
838 if opts.get(b'no_commit'):
839 msg = _(b"changeset %s backed out, don't forget to commit.\n")
839 msg = _(b"changeset %s backed out, don't forget to commit.\n")
840 ui.status(msg % short(node))
840 ui.status(msg % short(node))
841 return 0
841 return 0
842
842
843 def commitfunc(ui, repo, message, match, opts):
843 def commitfunc(ui, repo, message, match, opts):
844 editform = b'backout'
844 editform = b'backout'
845 e = cmdutil.getcommiteditor(
845 e = cmdutil.getcommiteditor(
846 editform=editform, **pycompat.strkwargs(opts)
846 editform=editform, **pycompat.strkwargs(opts)
847 )
847 )
848 if not message:
848 if not message:
849 # we don't translate commit messages
849 # we don't translate commit messages
850 message = b"Backed out changeset %s" % short(node)
850 message = b"Backed out changeset %s" % short(node)
851 e = cmdutil.getcommiteditor(edit=True, editform=editform)
851 e = cmdutil.getcommiteditor(edit=True, editform=editform)
852 return repo.commit(
852 return repo.commit(
853 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
853 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
854 )
854 )
855
855
856 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
856 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
857 if not newnode:
857 if not newnode:
858 ui.status(_(b"nothing changed\n"))
858 ui.status(_(b"nothing changed\n"))
859 return 1
859 return 1
860 cmdutil.commitstatus(repo, newnode, branch, bheads)
860 cmdutil.commitstatus(repo, newnode, branch, bheads)
861
861
862 def nice(node):
862 def nice(node):
863 return b'%d:%s' % (repo.changelog.rev(node), short(node))
863 return b'%d:%s' % (repo.changelog.rev(node), short(node))
864
864
865 ui.status(
865 ui.status(
866 _(b'changeset %s backs out changeset %s\n')
866 _(b'changeset %s backs out changeset %s\n')
867 % (nice(repo.changelog.tip()), nice(node))
867 % (nice(repo.changelog.tip()), nice(node))
868 )
868 )
869 if opts.get(b'merge') and op1 != node:
869 if opts.get(b'merge') and op1 != node:
870 hg.clean(repo, op1, show_stats=False)
870 hg.clean(repo, op1, show_stats=False)
871 ui.status(
871 ui.status(
872 _(b'merging with changeset %s\n') % nice(repo.changelog.tip())
872 _(b'merging with changeset %s\n') % nice(repo.changelog.tip())
873 )
873 )
874 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
874 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
875 with ui.configoverride(overrides, b'backout'):
875 with ui.configoverride(overrides, b'backout'):
876 return hg.merge(repo[b'tip'])
876 return hg.merge(repo[b'tip'])
877 return 0
877 return 0
878
878
879
879
880 @command(
880 @command(
881 b'bisect',
881 b'bisect',
882 [
882 [
883 (b'r', b'reset', False, _(b'reset bisect state')),
883 (b'r', b'reset', False, _(b'reset bisect state')),
884 (b'g', b'good', False, _(b'mark changeset good')),
884 (b'g', b'good', False, _(b'mark changeset good')),
885 (b'b', b'bad', False, _(b'mark changeset bad')),
885 (b'b', b'bad', False, _(b'mark changeset bad')),
886 (b's', b'skip', False, _(b'skip testing changeset')),
886 (b's', b'skip', False, _(b'skip testing changeset')),
887 (b'e', b'extend', False, _(b'extend the bisect range')),
887 (b'e', b'extend', False, _(b'extend the bisect range')),
888 (
888 (
889 b'c',
889 b'c',
890 b'command',
890 b'command',
891 b'',
891 b'',
892 _(b'use command to check changeset state'),
892 _(b'use command to check changeset state'),
893 _(b'CMD'),
893 _(b'CMD'),
894 ),
894 ),
895 (b'U', b'noupdate', False, _(b'do not update to target')),
895 (b'U', b'noupdate', False, _(b'do not update to target')),
896 ],
896 ],
897 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
897 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
898 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
898 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
899 )
899 )
900 def bisect(
900 def bisect(
901 ui,
901 ui,
902 repo,
902 repo,
903 rev=None,
903 rev=None,
904 extra=None,
904 extra=None,
905 command=None,
905 command=None,
906 reset=None,
906 reset=None,
907 good=None,
907 good=None,
908 bad=None,
908 bad=None,
909 skip=None,
909 skip=None,
910 extend=None,
910 extend=None,
911 noupdate=None,
911 noupdate=None,
912 ):
912 ):
913 """subdivision search of changesets
913 """subdivision search of changesets
914
914
915 This command helps to find changesets which introduce problems. To
915 This command helps to find changesets which introduce problems. To
916 use, mark the earliest changeset you know exhibits the problem as
916 use, mark the earliest changeset you know exhibits the problem as
917 bad, then mark the latest changeset which is free from the problem
917 bad, then mark the latest changeset which is free from the problem
918 as good. Bisect will update your working directory to a revision
918 as good. Bisect will update your working directory to a revision
919 for testing (unless the -U/--noupdate option is specified). Once
919 for testing (unless the -U/--noupdate option is specified). Once
920 you have performed tests, mark the working directory as good or
920 you have performed tests, mark the working directory as good or
921 bad, and bisect will either update to another candidate changeset
921 bad, and bisect will either update to another candidate changeset
922 or announce that it has found the bad revision.
922 or announce that it has found the bad revision.
923
923
924 As a shortcut, you can also use the revision argument to mark a
924 As a shortcut, you can also use the revision argument to mark a
925 revision as good or bad without checking it out first.
925 revision as good or bad without checking it out first.
926
926
927 If you supply a command, it will be used for automatic bisection.
927 If you supply a command, it will be used for automatic bisection.
928 The environment variable HG_NODE will contain the ID of the
928 The environment variable HG_NODE will contain the ID of the
929 changeset being tested. The exit status of the command will be
929 changeset being tested. The exit status of the command will be
930 used to mark revisions as good or bad: status 0 means good, 125
930 used to mark revisions as good or bad: status 0 means good, 125
931 means to skip the revision, 127 (command not found) will abort the
931 means to skip the revision, 127 (command not found) will abort the
932 bisection, and any other non-zero exit status means the revision
932 bisection, and any other non-zero exit status means the revision
933 is bad.
933 is bad.
934
934
935 .. container:: verbose
935 .. container:: verbose
936
936
937 Some examples:
937 Some examples:
938
938
939 - start a bisection with known bad revision 34, and good revision 12::
939 - start a bisection with known bad revision 34, and good revision 12::
940
940
941 hg bisect --bad 34
941 hg bisect --bad 34
942 hg bisect --good 12
942 hg bisect --good 12
943
943
944 - advance the current bisection by marking current revision as good or
944 - advance the current bisection by marking current revision as good or
945 bad::
945 bad::
946
946
947 hg bisect --good
947 hg bisect --good
948 hg bisect --bad
948 hg bisect --bad
949
949
950 - mark the current revision, or a known revision, to be skipped (e.g. if
950 - mark the current revision, or a known revision, to be skipped (e.g. if
951 that revision is not usable because of another issue)::
951 that revision is not usable because of another issue)::
952
952
953 hg bisect --skip
953 hg bisect --skip
954 hg bisect --skip 23
954 hg bisect --skip 23
955
955
956 - skip all revisions that do not touch directories ``foo`` or ``bar``::
956 - skip all revisions that do not touch directories ``foo`` or ``bar``::
957
957
958 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
958 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
959
959
960 - forget the current bisection::
960 - forget the current bisection::
961
961
962 hg bisect --reset
962 hg bisect --reset
963
963
964 - use 'make && make tests' to automatically find the first broken
964 - use 'make && make tests' to automatically find the first broken
965 revision::
965 revision::
966
966
967 hg bisect --reset
967 hg bisect --reset
968 hg bisect --bad 34
968 hg bisect --bad 34
969 hg bisect --good 12
969 hg bisect --good 12
970 hg bisect --command "make && make tests"
970 hg bisect --command "make && make tests"
971
971
972 - see all changesets whose states are already known in the current
972 - see all changesets whose states are already known in the current
973 bisection::
973 bisection::
974
974
975 hg log -r "bisect(pruned)"
975 hg log -r "bisect(pruned)"
976
976
977 - see the changeset currently being bisected (especially useful
977 - see the changeset currently being bisected (especially useful
978 if running with -U/--noupdate)::
978 if running with -U/--noupdate)::
979
979
980 hg log -r "bisect(current)"
980 hg log -r "bisect(current)"
981
981
982 - see all changesets that took part in the current bisection::
982 - see all changesets that took part in the current bisection::
983
983
984 hg log -r "bisect(range)"
984 hg log -r "bisect(range)"
985
985
986 - you can even get a nice graph::
986 - you can even get a nice graph::
987
987
988 hg log --graph -r "bisect(range)"
988 hg log --graph -r "bisect(range)"
989
989
990 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
990 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
991
991
992 Returns 0 on success.
992 Returns 0 on success.
993 """
993 """
994 # backward compatibility
994 # backward compatibility
995 if rev in b"good bad reset init".split():
995 if rev in b"good bad reset init".split():
996 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
996 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
997 cmd, rev, extra = rev, extra, None
997 cmd, rev, extra = rev, extra, None
998 if cmd == b"good":
998 if cmd == b"good":
999 good = True
999 good = True
1000 elif cmd == b"bad":
1000 elif cmd == b"bad":
1001 bad = True
1001 bad = True
1002 else:
1002 else:
1003 reset = True
1003 reset = True
1004 elif extra:
1004 elif extra:
1005 raise error.Abort(_(b'incompatible arguments'))
1005 raise error.Abort(_(b'incompatible arguments'))
1006
1006
1007 incompatibles = {
1007 incompatibles = {
1008 b'--bad': bad,
1008 b'--bad': bad,
1009 b'--command': bool(command),
1009 b'--command': bool(command),
1010 b'--extend': extend,
1010 b'--extend': extend,
1011 b'--good': good,
1011 b'--good': good,
1012 b'--reset': reset,
1012 b'--reset': reset,
1013 b'--skip': skip,
1013 b'--skip': skip,
1014 }
1014 }
1015
1015
1016 enabled = [x for x in incompatibles if incompatibles[x]]
1016 enabled = [x for x in incompatibles if incompatibles[x]]
1017
1017
1018 if len(enabled) > 1:
1018 if len(enabled) > 1:
1019 raise error.Abort(
1019 raise error.Abort(
1020 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1020 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1021 )
1021 )
1022
1022
1023 if reset:
1023 if reset:
1024 hbisect.resetstate(repo)
1024 hbisect.resetstate(repo)
1025 return
1025 return
1026
1026
1027 state = hbisect.load_state(repo)
1027 state = hbisect.load_state(repo)
1028
1028
1029 # update state
1029 # update state
1030 if good or bad or skip:
1030 if good or bad or skip:
1031 if rev:
1031 if rev:
1032 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
1032 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
1033 else:
1033 else:
1034 nodes = [repo.lookup(b'.')]
1034 nodes = [repo.lookup(b'.')]
1035 if good:
1035 if good:
1036 state[b'good'] += nodes
1036 state[b'good'] += nodes
1037 elif bad:
1037 elif bad:
1038 state[b'bad'] += nodes
1038 state[b'bad'] += nodes
1039 elif skip:
1039 elif skip:
1040 state[b'skip'] += nodes
1040 state[b'skip'] += nodes
1041 hbisect.save_state(repo, state)
1041 hbisect.save_state(repo, state)
1042 if not (state[b'good'] and state[b'bad']):
1042 if not (state[b'good'] and state[b'bad']):
1043 return
1043 return
1044
1044
1045 def mayupdate(repo, node, show_stats=True):
1045 def mayupdate(repo, node, show_stats=True):
1046 """common used update sequence"""
1046 """common used update sequence"""
1047 if noupdate:
1047 if noupdate:
1048 return
1048 return
1049 cmdutil.checkunfinished(repo)
1049 cmdutil.checkunfinished(repo)
1050 cmdutil.bailifchanged(repo)
1050 cmdutil.bailifchanged(repo)
1051 return hg.clean(repo, node, show_stats=show_stats)
1051 return hg.clean(repo, node, show_stats=show_stats)
1052
1052
1053 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1053 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1054
1054
1055 if command:
1055 if command:
1056 changesets = 1
1056 changesets = 1
1057 if noupdate:
1057 if noupdate:
1058 try:
1058 try:
1059 node = state[b'current'][0]
1059 node = state[b'current'][0]
1060 except LookupError:
1060 except LookupError:
1061 raise error.Abort(
1061 raise error.Abort(
1062 _(
1062 _(
1063 b'current bisect revision is unknown - '
1063 b'current bisect revision is unknown - '
1064 b'start a new bisect to fix'
1064 b'start a new bisect to fix'
1065 )
1065 )
1066 )
1066 )
1067 else:
1067 else:
1068 node, p2 = repo.dirstate.parents()
1068 node, p2 = repo.dirstate.parents()
1069 if p2 != nullid:
1069 if p2 != nullid:
1070 raise error.Abort(_(b'current bisect revision is a merge'))
1070 raise error.Abort(_(b'current bisect revision is a merge'))
1071 if rev:
1071 if rev:
1072 node = repo[scmutil.revsingle(repo, rev, node)].node()
1072 node = repo[scmutil.revsingle(repo, rev, node)].node()
1073 with hbisect.restore_state(repo, state, node):
1073 with hbisect.restore_state(repo, state, node):
1074 while changesets:
1074 while changesets:
1075 # update state
1075 # update state
1076 state[b'current'] = [node]
1076 state[b'current'] = [node]
1077 hbisect.save_state(repo, state)
1077 hbisect.save_state(repo, state)
1078 status = ui.system(
1078 status = ui.system(
1079 command,
1079 command,
1080 environ={b'HG_NODE': hex(node)},
1080 environ={b'HG_NODE': hex(node)},
1081 blockedtag=b'bisect_check',
1081 blockedtag=b'bisect_check',
1082 )
1082 )
1083 if status == 125:
1083 if status == 125:
1084 transition = b"skip"
1084 transition = b"skip"
1085 elif status == 0:
1085 elif status == 0:
1086 transition = b"good"
1086 transition = b"good"
1087 # status < 0 means process was killed
1087 # status < 0 means process was killed
1088 elif status == 127:
1088 elif status == 127:
1089 raise error.Abort(_(b"failed to execute %s") % command)
1089 raise error.Abort(_(b"failed to execute %s") % command)
1090 elif status < 0:
1090 elif status < 0:
1091 raise error.Abort(_(b"%s killed") % command)
1091 raise error.Abort(_(b"%s killed") % command)
1092 else:
1092 else:
1093 transition = b"bad"
1093 transition = b"bad"
1094 state[transition].append(node)
1094 state[transition].append(node)
1095 ctx = repo[node]
1095 ctx = repo[node]
1096 ui.status(
1096 ui.status(
1097 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1097 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1098 )
1098 )
1099 hbisect.checkstate(state)
1099 hbisect.checkstate(state)
1100 # bisect
1100 # bisect
1101 nodes, changesets, bgood = hbisect.bisect(repo, state)
1101 nodes, changesets, bgood = hbisect.bisect(repo, state)
1102 # update to next check
1102 # update to next check
1103 node = nodes[0]
1103 node = nodes[0]
1104 mayupdate(repo, node, show_stats=False)
1104 mayupdate(repo, node, show_stats=False)
1105 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1105 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1106 return
1106 return
1107
1107
1108 hbisect.checkstate(state)
1108 hbisect.checkstate(state)
1109
1109
1110 # actually bisect
1110 # actually bisect
1111 nodes, changesets, good = hbisect.bisect(repo, state)
1111 nodes, changesets, good = hbisect.bisect(repo, state)
1112 if extend:
1112 if extend:
1113 if not changesets:
1113 if not changesets:
1114 extendnode = hbisect.extendrange(repo, state, nodes, good)
1114 extendnode = hbisect.extendrange(repo, state, nodes, good)
1115 if extendnode is not None:
1115 if extendnode is not None:
1116 ui.write(
1116 ui.write(
1117 _(b"Extending search to changeset %d:%s\n")
1117 _(b"Extending search to changeset %d:%s\n")
1118 % (extendnode.rev(), extendnode)
1118 % (extendnode.rev(), extendnode)
1119 )
1119 )
1120 state[b'current'] = [extendnode.node()]
1120 state[b'current'] = [extendnode.node()]
1121 hbisect.save_state(repo, state)
1121 hbisect.save_state(repo, state)
1122 return mayupdate(repo, extendnode.node())
1122 return mayupdate(repo, extendnode.node())
1123 raise error.Abort(_(b"nothing to extend"))
1123 raise error.Abort(_(b"nothing to extend"))
1124
1124
1125 if changesets == 0:
1125 if changesets == 0:
1126 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1126 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1127 else:
1127 else:
1128 assert len(nodes) == 1 # only a single node can be tested next
1128 assert len(nodes) == 1 # only a single node can be tested next
1129 node = nodes[0]
1129 node = nodes[0]
1130 # compute the approximate number of remaining tests
1130 # compute the approximate number of remaining tests
1131 tests, size = 0, 2
1131 tests, size = 0, 2
1132 while size <= changesets:
1132 while size <= changesets:
1133 tests, size = tests + 1, size * 2
1133 tests, size = tests + 1, size * 2
1134 rev = repo.changelog.rev(node)
1134 rev = repo.changelog.rev(node)
1135 ui.write(
1135 ui.write(
1136 _(
1136 _(
1137 b"Testing changeset %d:%s "
1137 b"Testing changeset %d:%s "
1138 b"(%d changesets remaining, ~%d tests)\n"
1138 b"(%d changesets remaining, ~%d tests)\n"
1139 )
1139 )
1140 % (rev, short(node), changesets, tests)
1140 % (rev, short(node), changesets, tests)
1141 )
1141 )
1142 state[b'current'] = [node]
1142 state[b'current'] = [node]
1143 hbisect.save_state(repo, state)
1143 hbisect.save_state(repo, state)
1144 return mayupdate(repo, node)
1144 return mayupdate(repo, node)
1145
1145
1146
1146
1147 @command(
1147 @command(
1148 b'bookmarks|bookmark',
1148 b'bookmarks|bookmark',
1149 [
1149 [
1150 (b'f', b'force', False, _(b'force')),
1150 (b'f', b'force', False, _(b'force')),
1151 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1151 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1152 (b'd', b'delete', False, _(b'delete a given bookmark')),
1152 (b'd', b'delete', False, _(b'delete a given bookmark')),
1153 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1153 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1154 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1154 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1155 (b'l', b'list', False, _(b'list existing bookmarks')),
1155 (b'l', b'list', False, _(b'list existing bookmarks')),
1156 ]
1156 ]
1157 + formatteropts,
1157 + formatteropts,
1158 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1158 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1159 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1159 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1160 )
1160 )
1161 def bookmark(ui, repo, *names, **opts):
1161 def bookmark(ui, repo, *names, **opts):
1162 '''create a new bookmark or list existing bookmarks
1162 '''create a new bookmark or list existing bookmarks
1163
1163
1164 Bookmarks are labels on changesets to help track lines of development.
1164 Bookmarks are labels on changesets to help track lines of development.
1165 Bookmarks are unversioned and can be moved, renamed and deleted.
1165 Bookmarks are unversioned and can be moved, renamed and deleted.
1166 Deleting or moving a bookmark has no effect on the associated changesets.
1166 Deleting or moving a bookmark has no effect on the associated changesets.
1167
1167
1168 Creating or updating to a bookmark causes it to be marked as 'active'.
1168 Creating or updating to a bookmark causes it to be marked as 'active'.
1169 The active bookmark is indicated with a '*'.
1169 The active bookmark is indicated with a '*'.
1170 When a commit is made, the active bookmark will advance to the new commit.
1170 When a commit is made, the active bookmark will advance to the new commit.
1171 A plain :hg:`update` will also advance an active bookmark, if possible.
1171 A plain :hg:`update` will also advance an active bookmark, if possible.
1172 Updating away from a bookmark will cause it to be deactivated.
1172 Updating away from a bookmark will cause it to be deactivated.
1173
1173
1174 Bookmarks can be pushed and pulled between repositories (see
1174 Bookmarks can be pushed and pulled between repositories (see
1175 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1175 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1176 diverged, a new 'divergent bookmark' of the form 'name@path' will
1176 diverged, a new 'divergent bookmark' of the form 'name@path' will
1177 be created. Using :hg:`merge` will resolve the divergence.
1177 be created. Using :hg:`merge` will resolve the divergence.
1178
1178
1179 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1179 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1180 the active bookmark's name.
1180 the active bookmark's name.
1181
1181
1182 A bookmark named '@' has the special property that :hg:`clone` will
1182 A bookmark named '@' has the special property that :hg:`clone` will
1183 check it out by default if it exists.
1183 check it out by default if it exists.
1184
1184
1185 .. container:: verbose
1185 .. container:: verbose
1186
1186
1187 Template:
1187 Template:
1188
1188
1189 The following keywords are supported in addition to the common template
1189 The following keywords are supported in addition to the common template
1190 keywords and functions such as ``{bookmark}``. See also
1190 keywords and functions such as ``{bookmark}``. See also
1191 :hg:`help templates`.
1191 :hg:`help templates`.
1192
1192
1193 :active: Boolean. True if the bookmark is active.
1193 :active: Boolean. True if the bookmark is active.
1194
1194
1195 Examples:
1195 Examples:
1196
1196
1197 - create an active bookmark for a new line of development::
1197 - create an active bookmark for a new line of development::
1198
1198
1199 hg book new-feature
1199 hg book new-feature
1200
1200
1201 - create an inactive bookmark as a place marker::
1201 - create an inactive bookmark as a place marker::
1202
1202
1203 hg book -i reviewed
1203 hg book -i reviewed
1204
1204
1205 - create an inactive bookmark on another changeset::
1205 - create an inactive bookmark on another changeset::
1206
1206
1207 hg book -r .^ tested
1207 hg book -r .^ tested
1208
1208
1209 - rename bookmark turkey to dinner::
1209 - rename bookmark turkey to dinner::
1210
1210
1211 hg book -m turkey dinner
1211 hg book -m turkey dinner
1212
1212
1213 - move the '@' bookmark from another branch::
1213 - move the '@' bookmark from another branch::
1214
1214
1215 hg book -f @
1215 hg book -f @
1216
1216
1217 - print only the active bookmark name::
1217 - print only the active bookmark name::
1218
1218
1219 hg book -ql .
1219 hg book -ql .
1220 '''
1220 '''
1221 opts = pycompat.byteskwargs(opts)
1221 opts = pycompat.byteskwargs(opts)
1222 force = opts.get(b'force')
1222 force = opts.get(b'force')
1223 rev = opts.get(b'rev')
1223 rev = opts.get(b'rev')
1224 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1224 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1225
1225
1226 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1226 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1227 if action:
1227 if action:
1228 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1228 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1229 elif names or rev:
1229 elif names or rev:
1230 action = b'add'
1230 action = b'add'
1231 elif inactive:
1231 elif inactive:
1232 action = b'inactive' # meaning deactivate
1232 action = b'inactive' # meaning deactivate
1233 else:
1233 else:
1234 action = b'list'
1234 action = b'list'
1235
1235
1236 cmdutil.check_incompatible_arguments(
1236 cmdutil.check_incompatible_arguments(
1237 opts, b'inactive', [b'delete', b'list']
1237 opts, b'inactive', [b'delete', b'list']
1238 )
1238 )
1239 if not names and action in {b'add', b'delete'}:
1239 if not names and action in {b'add', b'delete'}:
1240 raise error.Abort(_(b"bookmark name required"))
1240 raise error.Abort(_(b"bookmark name required"))
1241
1241
1242 if action in {b'add', b'delete', b'rename', b'inactive'}:
1242 if action in {b'add', b'delete', b'rename', b'inactive'}:
1243 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1243 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1244 if action == b'delete':
1244 if action == b'delete':
1245 names = pycompat.maplist(repo._bookmarks.expandname, names)
1245 names = pycompat.maplist(repo._bookmarks.expandname, names)
1246 bookmarks.delete(repo, tr, names)
1246 bookmarks.delete(repo, tr, names)
1247 elif action == b'rename':
1247 elif action == b'rename':
1248 if not names:
1248 if not names:
1249 raise error.Abort(_(b"new bookmark name required"))
1249 raise error.Abort(_(b"new bookmark name required"))
1250 elif len(names) > 1:
1250 elif len(names) > 1:
1251 raise error.Abort(_(b"only one new bookmark name allowed"))
1251 raise error.Abort(_(b"only one new bookmark name allowed"))
1252 oldname = repo._bookmarks.expandname(opts[b'rename'])
1252 oldname = repo._bookmarks.expandname(opts[b'rename'])
1253 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1253 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1254 elif action == b'add':
1254 elif action == b'add':
1255 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1255 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1256 elif action == b'inactive':
1256 elif action == b'inactive':
1257 if len(repo._bookmarks) == 0:
1257 if len(repo._bookmarks) == 0:
1258 ui.status(_(b"no bookmarks set\n"))
1258 ui.status(_(b"no bookmarks set\n"))
1259 elif not repo._activebookmark:
1259 elif not repo._activebookmark:
1260 ui.status(_(b"no active bookmark\n"))
1260 ui.status(_(b"no active bookmark\n"))
1261 else:
1261 else:
1262 bookmarks.deactivate(repo)
1262 bookmarks.deactivate(repo)
1263 elif action == b'list':
1263 elif action == b'list':
1264 names = pycompat.maplist(repo._bookmarks.expandname, names)
1264 names = pycompat.maplist(repo._bookmarks.expandname, names)
1265 with ui.formatter(b'bookmarks', opts) as fm:
1265 with ui.formatter(b'bookmarks', opts) as fm:
1266 bookmarks.printbookmarks(ui, repo, fm, names)
1266 bookmarks.printbookmarks(ui, repo, fm, names)
1267 else:
1267 else:
1268 raise error.ProgrammingError(b'invalid action: %s' % action)
1268 raise error.ProgrammingError(b'invalid action: %s' % action)
1269
1269
1270
1270
1271 @command(
1271 @command(
1272 b'branch',
1272 b'branch',
1273 [
1273 [
1274 (
1274 (
1275 b'f',
1275 b'f',
1276 b'force',
1276 b'force',
1277 None,
1277 None,
1278 _(b'set branch name even if it shadows an existing branch'),
1278 _(b'set branch name even if it shadows an existing branch'),
1279 ),
1279 ),
1280 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1280 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1281 (
1281 (
1282 b'r',
1282 b'r',
1283 b'rev',
1283 b'rev',
1284 [],
1284 [],
1285 _(b'change branches of the given revs (EXPERIMENTAL)'),
1285 _(b'change branches of the given revs (EXPERIMENTAL)'),
1286 ),
1286 ),
1287 ],
1287 ],
1288 _(b'[-fC] [NAME]'),
1288 _(b'[-fC] [NAME]'),
1289 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1289 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1290 )
1290 )
1291 def branch(ui, repo, label=None, **opts):
1291 def branch(ui, repo, label=None, **opts):
1292 """set or show the current branch name
1292 """set or show the current branch name
1293
1293
1294 .. note::
1294 .. note::
1295
1295
1296 Branch names are permanent and global. Use :hg:`bookmark` to create a
1296 Branch names are permanent and global. Use :hg:`bookmark` to create a
1297 light-weight bookmark instead. See :hg:`help glossary` for more
1297 light-weight bookmark instead. See :hg:`help glossary` for more
1298 information about named branches and bookmarks.
1298 information about named branches and bookmarks.
1299
1299
1300 With no argument, show the current branch name. With one argument,
1300 With no argument, show the current branch name. With one argument,
1301 set the working directory branch name (the branch will not exist
1301 set the working directory branch name (the branch will not exist
1302 in the repository until the next commit). Standard practice
1302 in the repository until the next commit). Standard practice
1303 recommends that primary development take place on the 'default'
1303 recommends that primary development take place on the 'default'
1304 branch.
1304 branch.
1305
1305
1306 Unless -f/--force is specified, branch will not let you set a
1306 Unless -f/--force is specified, branch will not let you set a
1307 branch name that already exists.
1307 branch name that already exists.
1308
1308
1309 Use -C/--clean to reset the working directory branch to that of
1309 Use -C/--clean to reset the working directory branch to that of
1310 the parent of the working directory, negating a previous branch
1310 the parent of the working directory, negating a previous branch
1311 change.
1311 change.
1312
1312
1313 Use the command :hg:`update` to switch to an existing branch. Use
1313 Use the command :hg:`update` to switch to an existing branch. Use
1314 :hg:`commit --close-branch` to mark this branch head as closed.
1314 :hg:`commit --close-branch` to mark this branch head as closed.
1315 When all heads of a branch are closed, the branch will be
1315 When all heads of a branch are closed, the branch will be
1316 considered closed.
1316 considered closed.
1317
1317
1318 Returns 0 on success.
1318 Returns 0 on success.
1319 """
1319 """
1320 opts = pycompat.byteskwargs(opts)
1320 opts = pycompat.byteskwargs(opts)
1321 revs = opts.get(b'rev')
1321 revs = opts.get(b'rev')
1322 if label:
1322 if label:
1323 label = label.strip()
1323 label = label.strip()
1324
1324
1325 if not opts.get(b'clean') and not label:
1325 if not opts.get(b'clean') and not label:
1326 if revs:
1326 if revs:
1327 raise error.Abort(_(b"no branch name specified for the revisions"))
1327 raise error.Abort(_(b"no branch name specified for the revisions"))
1328 ui.write(b"%s\n" % repo.dirstate.branch())
1328 ui.write(b"%s\n" % repo.dirstate.branch())
1329 return
1329 return
1330
1330
1331 with repo.wlock():
1331 with repo.wlock():
1332 if opts.get(b'clean'):
1332 if opts.get(b'clean'):
1333 label = repo[b'.'].branch()
1333 label = repo[b'.'].branch()
1334 repo.dirstate.setbranch(label)
1334 repo.dirstate.setbranch(label)
1335 ui.status(_(b'reset working directory to branch %s\n') % label)
1335 ui.status(_(b'reset working directory to branch %s\n') % label)
1336 elif label:
1336 elif label:
1337
1337
1338 scmutil.checknewlabel(repo, label, b'branch')
1338 scmutil.checknewlabel(repo, label, b'branch')
1339 if revs:
1339 if revs:
1340 return cmdutil.changebranch(ui, repo, revs, label, opts)
1340 return cmdutil.changebranch(ui, repo, revs, label, opts)
1341
1341
1342 if not opts.get(b'force') and label in repo.branchmap():
1342 if not opts.get(b'force') and label in repo.branchmap():
1343 if label not in [p.branch() for p in repo[None].parents()]:
1343 if label not in [p.branch() for p in repo[None].parents()]:
1344 raise error.Abort(
1344 raise error.Abort(
1345 _(b'a branch of the same name already exists'),
1345 _(b'a branch of the same name already exists'),
1346 # i18n: "it" refers to an existing branch
1346 # i18n: "it" refers to an existing branch
1347 hint=_(b"use 'hg update' to switch to it"),
1347 hint=_(b"use 'hg update' to switch to it"),
1348 )
1348 )
1349
1349
1350 repo.dirstate.setbranch(label)
1350 repo.dirstate.setbranch(label)
1351 ui.status(_(b'marked working directory as branch %s\n') % label)
1351 ui.status(_(b'marked working directory as branch %s\n') % label)
1352
1352
1353 # find any open named branches aside from default
1353 # find any open named branches aside from default
1354 for n, h, t, c in repo.branchmap().iterbranches():
1354 for n, h, t, c in repo.branchmap().iterbranches():
1355 if n != b"default" and not c:
1355 if n != b"default" and not c:
1356 return 0
1356 return 0
1357 ui.status(
1357 ui.status(
1358 _(
1358 _(
1359 b'(branches are permanent and global, '
1359 b'(branches are permanent and global, '
1360 b'did you want a bookmark?)\n'
1360 b'did you want a bookmark?)\n'
1361 )
1361 )
1362 )
1362 )
1363
1363
1364
1364
1365 @command(
1365 @command(
1366 b'branches',
1366 b'branches',
1367 [
1367 [
1368 (
1368 (
1369 b'a',
1369 b'a',
1370 b'active',
1370 b'active',
1371 False,
1371 False,
1372 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1372 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1373 ),
1373 ),
1374 (b'c', b'closed', False, _(b'show normal and closed branches')),
1374 (b'c', b'closed', False, _(b'show normal and closed branches')),
1375 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1375 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1376 ]
1376 ]
1377 + formatteropts,
1377 + formatteropts,
1378 _(b'[-c]'),
1378 _(b'[-c]'),
1379 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1379 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1380 intents={INTENT_READONLY},
1380 intents={INTENT_READONLY},
1381 )
1381 )
1382 def branches(ui, repo, active=False, closed=False, **opts):
1382 def branches(ui, repo, active=False, closed=False, **opts):
1383 """list repository named branches
1383 """list repository named branches
1384
1384
1385 List the repository's named branches, indicating which ones are
1385 List the repository's named branches, indicating which ones are
1386 inactive. If -c/--closed is specified, also list branches which have
1386 inactive. If -c/--closed is specified, also list branches which have
1387 been marked closed (see :hg:`commit --close-branch`).
1387 been marked closed (see :hg:`commit --close-branch`).
1388
1388
1389 Use the command :hg:`update` to switch to an existing branch.
1389 Use the command :hg:`update` to switch to an existing branch.
1390
1390
1391 .. container:: verbose
1391 .. container:: verbose
1392
1392
1393 Template:
1393 Template:
1394
1394
1395 The following keywords are supported in addition to the common template
1395 The following keywords are supported in addition to the common template
1396 keywords and functions such as ``{branch}``. See also
1396 keywords and functions such as ``{branch}``. See also
1397 :hg:`help templates`.
1397 :hg:`help templates`.
1398
1398
1399 :active: Boolean. True if the branch is active.
1399 :active: Boolean. True if the branch is active.
1400 :closed: Boolean. True if the branch is closed.
1400 :closed: Boolean. True if the branch is closed.
1401 :current: Boolean. True if it is the current branch.
1401 :current: Boolean. True if it is the current branch.
1402
1402
1403 Returns 0.
1403 Returns 0.
1404 """
1404 """
1405
1405
1406 opts = pycompat.byteskwargs(opts)
1406 opts = pycompat.byteskwargs(opts)
1407 revs = opts.get(b'rev')
1407 revs = opts.get(b'rev')
1408 selectedbranches = None
1408 selectedbranches = None
1409 if revs:
1409 if revs:
1410 revs = scmutil.revrange(repo, revs)
1410 revs = scmutil.revrange(repo, revs)
1411 getbi = repo.revbranchcache().branchinfo
1411 getbi = repo.revbranchcache().branchinfo
1412 selectedbranches = {getbi(r)[0] for r in revs}
1412 selectedbranches = {getbi(r)[0] for r in revs}
1413
1413
1414 ui.pager(b'branches')
1414 ui.pager(b'branches')
1415 fm = ui.formatter(b'branches', opts)
1415 fm = ui.formatter(b'branches', opts)
1416 hexfunc = fm.hexfunc
1416 hexfunc = fm.hexfunc
1417
1417
1418 allheads = set(repo.heads())
1418 allheads = set(repo.heads())
1419 branches = []
1419 branches = []
1420 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1420 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1421 if selectedbranches is not None and tag not in selectedbranches:
1421 if selectedbranches is not None and tag not in selectedbranches:
1422 continue
1422 continue
1423 isactive = False
1423 isactive = False
1424 if not isclosed:
1424 if not isclosed:
1425 openheads = set(repo.branchmap().iteropen(heads))
1425 openheads = set(repo.branchmap().iteropen(heads))
1426 isactive = bool(openheads & allheads)
1426 isactive = bool(openheads & allheads)
1427 branches.append((tag, repo[tip], isactive, not isclosed))
1427 branches.append((tag, repo[tip], isactive, not isclosed))
1428 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1428 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1429
1429
1430 for tag, ctx, isactive, isopen in branches:
1430 for tag, ctx, isactive, isopen in branches:
1431 if active and not isactive:
1431 if active and not isactive:
1432 continue
1432 continue
1433 if isactive:
1433 if isactive:
1434 label = b'branches.active'
1434 label = b'branches.active'
1435 notice = b''
1435 notice = b''
1436 elif not isopen:
1436 elif not isopen:
1437 if not closed:
1437 if not closed:
1438 continue
1438 continue
1439 label = b'branches.closed'
1439 label = b'branches.closed'
1440 notice = _(b' (closed)')
1440 notice = _(b' (closed)')
1441 else:
1441 else:
1442 label = b'branches.inactive'
1442 label = b'branches.inactive'
1443 notice = _(b' (inactive)')
1443 notice = _(b' (inactive)')
1444 current = tag == repo.dirstate.branch()
1444 current = tag == repo.dirstate.branch()
1445 if current:
1445 if current:
1446 label = b'branches.current'
1446 label = b'branches.current'
1447
1447
1448 fm.startitem()
1448 fm.startitem()
1449 fm.write(b'branch', b'%s', tag, label=label)
1449 fm.write(b'branch', b'%s', tag, label=label)
1450 rev = ctx.rev()
1450 rev = ctx.rev()
1451 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1451 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1452 fmt = b' ' * padsize + b' %d:%s'
1452 fmt = b' ' * padsize + b' %d:%s'
1453 fm.condwrite(
1453 fm.condwrite(
1454 not ui.quiet,
1454 not ui.quiet,
1455 b'rev node',
1455 b'rev node',
1456 fmt,
1456 fmt,
1457 rev,
1457 rev,
1458 hexfunc(ctx.node()),
1458 hexfunc(ctx.node()),
1459 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1459 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1460 )
1460 )
1461 fm.context(ctx=ctx)
1461 fm.context(ctx=ctx)
1462 fm.data(active=isactive, closed=not isopen, current=current)
1462 fm.data(active=isactive, closed=not isopen, current=current)
1463 if not ui.quiet:
1463 if not ui.quiet:
1464 fm.plain(notice)
1464 fm.plain(notice)
1465 fm.plain(b'\n')
1465 fm.plain(b'\n')
1466 fm.end()
1466 fm.end()
1467
1467
1468
1468
1469 @command(
1469 @command(
1470 b'bundle',
1470 b'bundle',
1471 [
1471 [
1472 (
1472 (
1473 b'f',
1473 b'f',
1474 b'force',
1474 b'force',
1475 None,
1475 None,
1476 _(b'run even when the destination is unrelated'),
1476 _(b'run even when the destination is unrelated'),
1477 ),
1477 ),
1478 (
1478 (
1479 b'r',
1479 b'r',
1480 b'rev',
1480 b'rev',
1481 [],
1481 [],
1482 _(b'a changeset intended to be added to the destination'),
1482 _(b'a changeset intended to be added to the destination'),
1483 _(b'REV'),
1483 _(b'REV'),
1484 ),
1484 ),
1485 (
1485 (
1486 b'b',
1486 b'b',
1487 b'branch',
1487 b'branch',
1488 [],
1488 [],
1489 _(b'a specific branch you would like to bundle'),
1489 _(b'a specific branch you would like to bundle'),
1490 _(b'BRANCH'),
1490 _(b'BRANCH'),
1491 ),
1491 ),
1492 (
1492 (
1493 b'',
1493 b'',
1494 b'base',
1494 b'base',
1495 [],
1495 [],
1496 _(b'a base changeset assumed to be available at the destination'),
1496 _(b'a base changeset assumed to be available at the destination'),
1497 _(b'REV'),
1497 _(b'REV'),
1498 ),
1498 ),
1499 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1499 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1500 (
1500 (
1501 b't',
1501 b't',
1502 b'type',
1502 b'type',
1503 b'bzip2',
1503 b'bzip2',
1504 _(b'bundle compression type to use'),
1504 _(b'bundle compression type to use'),
1505 _(b'TYPE'),
1505 _(b'TYPE'),
1506 ),
1506 ),
1507 ]
1507 ]
1508 + remoteopts,
1508 + remoteopts,
1509 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1509 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1510 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1510 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1511 )
1511 )
1512 def bundle(ui, repo, fname, dest=None, **opts):
1512 def bundle(ui, repo, fname, dest=None, **opts):
1513 """create a bundle file
1513 """create a bundle file
1514
1514
1515 Generate a bundle file containing data to be transferred to another
1515 Generate a bundle file containing data to be transferred to another
1516 repository.
1516 repository.
1517
1517
1518 To create a bundle containing all changesets, use -a/--all
1518 To create a bundle containing all changesets, use -a/--all
1519 (or --base null). Otherwise, hg assumes the destination will have
1519 (or --base null). Otherwise, hg assumes the destination will have
1520 all the nodes you specify with --base parameters. Otherwise, hg
1520 all the nodes you specify with --base parameters. Otherwise, hg
1521 will assume the repository has all the nodes in destination, or
1521 will assume the repository has all the nodes in destination, or
1522 default-push/default if no destination is specified, where destination
1522 default-push/default if no destination is specified, where destination
1523 is the repository you provide through DEST option.
1523 is the repository you provide through DEST option.
1524
1524
1525 You can change bundle format with the -t/--type option. See
1525 You can change bundle format with the -t/--type option. See
1526 :hg:`help bundlespec` for documentation on this format. By default,
1526 :hg:`help bundlespec` for documentation on this format. By default,
1527 the most appropriate format is used and compression defaults to
1527 the most appropriate format is used and compression defaults to
1528 bzip2.
1528 bzip2.
1529
1529
1530 The bundle file can then be transferred using conventional means
1530 The bundle file can then be transferred using conventional means
1531 and applied to another repository with the unbundle or pull
1531 and applied to another repository with the unbundle or pull
1532 command. This is useful when direct push and pull are not
1532 command. This is useful when direct push and pull are not
1533 available or when exporting an entire repository is undesirable.
1533 available or when exporting an entire repository is undesirable.
1534
1534
1535 Applying bundles preserves all changeset contents including
1535 Applying bundles preserves all changeset contents including
1536 permissions, copy/rename information, and revision history.
1536 permissions, copy/rename information, and revision history.
1537
1537
1538 Returns 0 on success, 1 if no changes found.
1538 Returns 0 on success, 1 if no changes found.
1539 """
1539 """
1540 opts = pycompat.byteskwargs(opts)
1540 opts = pycompat.byteskwargs(opts)
1541 revs = None
1541 revs = None
1542 if b'rev' in opts:
1542 if b'rev' in opts:
1543 revstrings = opts[b'rev']
1543 revstrings = opts[b'rev']
1544 revs = scmutil.revrange(repo, revstrings)
1544 revs = scmutil.revrange(repo, revstrings)
1545 if revstrings and not revs:
1545 if revstrings and not revs:
1546 raise error.Abort(_(b'no commits to bundle'))
1546 raise error.Abort(_(b'no commits to bundle'))
1547
1547
1548 bundletype = opts.get(b'type', b'bzip2').lower()
1548 bundletype = opts.get(b'type', b'bzip2').lower()
1549 try:
1549 try:
1550 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1550 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1551 except error.UnsupportedBundleSpecification as e:
1551 except error.UnsupportedBundleSpecification as e:
1552 raise error.Abort(
1552 raise error.Abort(
1553 pycompat.bytestr(e),
1553 pycompat.bytestr(e),
1554 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1554 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1555 )
1555 )
1556 cgversion = bundlespec.contentopts[b"cg.version"]
1556 cgversion = bundlespec.contentopts[b"cg.version"]
1557
1557
1558 # Packed bundles are a pseudo bundle format for now.
1558 # Packed bundles are a pseudo bundle format for now.
1559 if cgversion == b's1':
1559 if cgversion == b's1':
1560 raise error.Abort(
1560 raise error.Abort(
1561 _(b'packed bundles cannot be produced by "hg bundle"'),
1561 _(b'packed bundles cannot be produced by "hg bundle"'),
1562 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1562 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1563 )
1563 )
1564
1564
1565 if opts.get(b'all'):
1565 if opts.get(b'all'):
1566 if dest:
1566 if dest:
1567 raise error.Abort(
1567 raise error.Abort(
1568 _(b"--all is incompatible with specifying a destination")
1568 _(b"--all is incompatible with specifying a destination")
1569 )
1569 )
1570 if opts.get(b'base'):
1570 if opts.get(b'base'):
1571 ui.warn(_(b"ignoring --base because --all was specified\n"))
1571 ui.warn(_(b"ignoring --base because --all was specified\n"))
1572 base = [nullrev]
1572 base = [nullrev]
1573 else:
1573 else:
1574 base = scmutil.revrange(repo, opts.get(b'base'))
1574 base = scmutil.revrange(repo, opts.get(b'base'))
1575 if cgversion not in changegroup.supportedoutgoingversions(repo):
1575 if cgversion not in changegroup.supportedoutgoingversions(repo):
1576 raise error.Abort(
1576 raise error.Abort(
1577 _(b"repository does not support bundle version %s") % cgversion
1577 _(b"repository does not support bundle version %s") % cgversion
1578 )
1578 )
1579
1579
1580 if base:
1580 if base:
1581 if dest:
1581 if dest:
1582 raise error.Abort(
1582 raise error.Abort(
1583 _(b"--base is incompatible with specifying a destination")
1583 _(b"--base is incompatible with specifying a destination")
1584 )
1584 )
1585 common = [repo[rev].node() for rev in base]
1585 common = [repo[rev].node() for rev in base]
1586 heads = [repo[r].node() for r in revs] if revs else None
1586 heads = [repo[r].node() for r in revs] if revs else None
1587 outgoing = discovery.outgoing(repo, common, heads)
1587 outgoing = discovery.outgoing(repo, common, heads)
1588 else:
1588 else:
1589 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1589 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1590 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1590 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1591 other = hg.peer(repo, opts, dest)
1591 other = hg.peer(repo, opts, dest)
1592 revs = [repo[r].hex() for r in revs]
1592 revs = [repo[r].hex() for r in revs]
1593 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1593 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1594 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1594 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1595 outgoing = discovery.findcommonoutgoing(
1595 outgoing = discovery.findcommonoutgoing(
1596 repo,
1596 repo,
1597 other,
1597 other,
1598 onlyheads=heads,
1598 onlyheads=heads,
1599 force=opts.get(b'force'),
1599 force=opts.get(b'force'),
1600 portable=True,
1600 portable=True,
1601 )
1601 )
1602
1602
1603 if not outgoing.missing:
1603 if not outgoing.missing:
1604 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1604 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1605 return 1
1605 return 1
1606
1606
1607 if cgversion == b'01': # bundle1
1607 if cgversion == b'01': # bundle1
1608 bversion = b'HG10' + bundlespec.wirecompression
1608 bversion = b'HG10' + bundlespec.wirecompression
1609 bcompression = None
1609 bcompression = None
1610 elif cgversion in (b'02', b'03'):
1610 elif cgversion in (b'02', b'03'):
1611 bversion = b'HG20'
1611 bversion = b'HG20'
1612 bcompression = bundlespec.wirecompression
1612 bcompression = bundlespec.wirecompression
1613 else:
1613 else:
1614 raise error.ProgrammingError(
1614 raise error.ProgrammingError(
1615 b'bundle: unexpected changegroup version %s' % cgversion
1615 b'bundle: unexpected changegroup version %s' % cgversion
1616 )
1616 )
1617
1617
1618 # TODO compression options should be derived from bundlespec parsing.
1618 # TODO compression options should be derived from bundlespec parsing.
1619 # This is a temporary hack to allow adjusting bundle compression
1619 # This is a temporary hack to allow adjusting bundle compression
1620 # level without a) formalizing the bundlespec changes to declare it
1620 # level without a) formalizing the bundlespec changes to declare it
1621 # b) introducing a command flag.
1621 # b) introducing a command flag.
1622 compopts = {}
1622 compopts = {}
1623 complevel = ui.configint(
1623 complevel = ui.configint(
1624 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1624 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1625 )
1625 )
1626 if complevel is None:
1626 if complevel is None:
1627 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1627 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1628 if complevel is not None:
1628 if complevel is not None:
1629 compopts[b'level'] = complevel
1629 compopts[b'level'] = complevel
1630
1630
1631 # Allow overriding the bundling of obsmarker in phases through
1631 # Allow overriding the bundling of obsmarker in phases through
1632 # configuration while we don't have a bundle version that include them
1632 # configuration while we don't have a bundle version that include them
1633 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1633 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1634 bundlespec.contentopts[b'obsolescence'] = True
1634 bundlespec.contentopts[b'obsolescence'] = True
1635 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1635 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1636 bundlespec.contentopts[b'phases'] = True
1636 bundlespec.contentopts[b'phases'] = True
1637
1637
1638 bundle2.writenewbundle(
1638 bundle2.writenewbundle(
1639 ui,
1639 ui,
1640 repo,
1640 repo,
1641 b'bundle',
1641 b'bundle',
1642 fname,
1642 fname,
1643 bversion,
1643 bversion,
1644 outgoing,
1644 outgoing,
1645 bundlespec.contentopts,
1645 bundlespec.contentopts,
1646 compression=bcompression,
1646 compression=bcompression,
1647 compopts=compopts,
1647 compopts=compopts,
1648 )
1648 )
1649
1649
1650
1650
1651 @command(
1651 @command(
1652 b'cat',
1652 b'cat',
1653 [
1653 [
1654 (
1654 (
1655 b'o',
1655 b'o',
1656 b'output',
1656 b'output',
1657 b'',
1657 b'',
1658 _(b'print output to file with formatted name'),
1658 _(b'print output to file with formatted name'),
1659 _(b'FORMAT'),
1659 _(b'FORMAT'),
1660 ),
1660 ),
1661 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1661 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1662 (b'', b'decode', None, _(b'apply any matching decode filter')),
1662 (b'', b'decode', None, _(b'apply any matching decode filter')),
1663 ]
1663 ]
1664 + walkopts
1664 + walkopts
1665 + formatteropts,
1665 + formatteropts,
1666 _(b'[OPTION]... FILE...'),
1666 _(b'[OPTION]... FILE...'),
1667 helpcategory=command.CATEGORY_FILE_CONTENTS,
1667 helpcategory=command.CATEGORY_FILE_CONTENTS,
1668 inferrepo=True,
1668 inferrepo=True,
1669 intents={INTENT_READONLY},
1669 intents={INTENT_READONLY},
1670 )
1670 )
1671 def cat(ui, repo, file1, *pats, **opts):
1671 def cat(ui, repo, file1, *pats, **opts):
1672 """output the current or given revision of files
1672 """output the current or given revision of files
1673
1673
1674 Print the specified files as they were at the given revision. If
1674 Print the specified files as they were at the given revision. If
1675 no revision is given, the parent of the working directory is used.
1675 no revision is given, the parent of the working directory is used.
1676
1676
1677 Output may be to a file, in which case the name of the file is
1677 Output may be to a file, in which case the name of the file is
1678 given using a template string. See :hg:`help templates`. In addition
1678 given using a template string. See :hg:`help templates`. In addition
1679 to the common template keywords, the following formatting rules are
1679 to the common template keywords, the following formatting rules are
1680 supported:
1680 supported:
1681
1681
1682 :``%%``: literal "%" character
1682 :``%%``: literal "%" character
1683 :``%s``: basename of file being printed
1683 :``%s``: basename of file being printed
1684 :``%d``: dirname of file being printed, or '.' if in repository root
1684 :``%d``: dirname of file being printed, or '.' if in repository root
1685 :``%p``: root-relative path name of file being printed
1685 :``%p``: root-relative path name of file being printed
1686 :``%H``: changeset hash (40 hexadecimal digits)
1686 :``%H``: changeset hash (40 hexadecimal digits)
1687 :``%R``: changeset revision number
1687 :``%R``: changeset revision number
1688 :``%h``: short-form changeset hash (12 hexadecimal digits)
1688 :``%h``: short-form changeset hash (12 hexadecimal digits)
1689 :``%r``: zero-padded changeset revision number
1689 :``%r``: zero-padded changeset revision number
1690 :``%b``: basename of the exporting repository
1690 :``%b``: basename of the exporting repository
1691 :``\\``: literal "\\" character
1691 :``\\``: literal "\\" character
1692
1692
1693 .. container:: verbose
1693 .. container:: verbose
1694
1694
1695 Template:
1695 Template:
1696
1696
1697 The following keywords are supported in addition to the common template
1697 The following keywords are supported in addition to the common template
1698 keywords and functions. See also :hg:`help templates`.
1698 keywords and functions. See also :hg:`help templates`.
1699
1699
1700 :data: String. File content.
1700 :data: String. File content.
1701 :path: String. Repository-absolute path of the file.
1701 :path: String. Repository-absolute path of the file.
1702
1702
1703 Returns 0 on success.
1703 Returns 0 on success.
1704 """
1704 """
1705 opts = pycompat.byteskwargs(opts)
1705 opts = pycompat.byteskwargs(opts)
1706 rev = opts.get(b'rev')
1706 rev = opts.get(b'rev')
1707 if rev:
1707 if rev:
1708 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1708 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1709 ctx = scmutil.revsingle(repo, rev)
1709 ctx = scmutil.revsingle(repo, rev)
1710 m = scmutil.match(ctx, (file1,) + pats, opts)
1710 m = scmutil.match(ctx, (file1,) + pats, opts)
1711 fntemplate = opts.pop(b'output', b'')
1711 fntemplate = opts.pop(b'output', b'')
1712 if cmdutil.isstdiofilename(fntemplate):
1712 if cmdutil.isstdiofilename(fntemplate):
1713 fntemplate = b''
1713 fntemplate = b''
1714
1714
1715 if fntemplate:
1715 if fntemplate:
1716 fm = formatter.nullformatter(ui, b'cat', opts)
1716 fm = formatter.nullformatter(ui, b'cat', opts)
1717 else:
1717 else:
1718 ui.pager(b'cat')
1718 ui.pager(b'cat')
1719 fm = ui.formatter(b'cat', opts)
1719 fm = ui.formatter(b'cat', opts)
1720 with fm:
1720 with fm:
1721 return cmdutil.cat(
1721 return cmdutil.cat(
1722 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1722 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1723 )
1723 )
1724
1724
1725
1725
1726 @command(
1726 @command(
1727 b'clone',
1727 b'clone',
1728 [
1728 [
1729 (
1729 (
1730 b'U',
1730 b'U',
1731 b'noupdate',
1731 b'noupdate',
1732 None,
1732 None,
1733 _(
1733 _(
1734 b'the clone will include an empty working '
1734 b'the clone will include an empty working '
1735 b'directory (only a repository)'
1735 b'directory (only a repository)'
1736 ),
1736 ),
1737 ),
1737 ),
1738 (
1738 (
1739 b'u',
1739 b'u',
1740 b'updaterev',
1740 b'updaterev',
1741 b'',
1741 b'',
1742 _(b'revision, tag, or branch to check out'),
1742 _(b'revision, tag, or branch to check out'),
1743 _(b'REV'),
1743 _(b'REV'),
1744 ),
1744 ),
1745 (
1745 (
1746 b'r',
1746 b'r',
1747 b'rev',
1747 b'rev',
1748 [],
1748 [],
1749 _(
1749 _(
1750 b'do not clone everything, but include this changeset'
1750 b'do not clone everything, but include this changeset'
1751 b' and its ancestors'
1751 b' and its ancestors'
1752 ),
1752 ),
1753 _(b'REV'),
1753 _(b'REV'),
1754 ),
1754 ),
1755 (
1755 (
1756 b'b',
1756 b'b',
1757 b'branch',
1757 b'branch',
1758 [],
1758 [],
1759 _(
1759 _(
1760 b'do not clone everything, but include this branch\'s'
1760 b'do not clone everything, but include this branch\'s'
1761 b' changesets and their ancestors'
1761 b' changesets and their ancestors'
1762 ),
1762 ),
1763 _(b'BRANCH'),
1763 _(b'BRANCH'),
1764 ),
1764 ),
1765 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1765 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1766 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1766 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1767 (b'', b'stream', None, _(b'clone with minimal data processing')),
1767 (b'', b'stream', None, _(b'clone with minimal data processing')),
1768 ]
1768 ]
1769 + remoteopts,
1769 + remoteopts,
1770 _(b'[OPTION]... SOURCE [DEST]'),
1770 _(b'[OPTION]... SOURCE [DEST]'),
1771 helpcategory=command.CATEGORY_REPO_CREATION,
1771 helpcategory=command.CATEGORY_REPO_CREATION,
1772 helpbasic=True,
1772 helpbasic=True,
1773 norepo=True,
1773 norepo=True,
1774 )
1774 )
1775 def clone(ui, source, dest=None, **opts):
1775 def clone(ui, source, dest=None, **opts):
1776 """make a copy of an existing repository
1776 """make a copy of an existing repository
1777
1777
1778 Create a copy of an existing repository in a new directory.
1778 Create a copy of an existing repository in a new directory.
1779
1779
1780 If no destination directory name is specified, it defaults to the
1780 If no destination directory name is specified, it defaults to the
1781 basename of the source.
1781 basename of the source.
1782
1782
1783 The location of the source is added to the new repository's
1783 The location of the source is added to the new repository's
1784 ``.hg/hgrc`` file, as the default to be used for future pulls.
1784 ``.hg/hgrc`` file, as the default to be used for future pulls.
1785
1785
1786 Only local paths and ``ssh://`` URLs are supported as
1786 Only local paths and ``ssh://`` URLs are supported as
1787 destinations. For ``ssh://`` destinations, no working directory or
1787 destinations. For ``ssh://`` destinations, no working directory or
1788 ``.hg/hgrc`` will be created on the remote side.
1788 ``.hg/hgrc`` will be created on the remote side.
1789
1789
1790 If the source repository has a bookmark called '@' set, that
1790 If the source repository has a bookmark called '@' set, that
1791 revision will be checked out in the new repository by default.
1791 revision will be checked out in the new repository by default.
1792
1792
1793 To check out a particular version, use -u/--update, or
1793 To check out a particular version, use -u/--update, or
1794 -U/--noupdate to create a clone with no working directory.
1794 -U/--noupdate to create a clone with no working directory.
1795
1795
1796 To pull only a subset of changesets, specify one or more revisions
1796 To pull only a subset of changesets, specify one or more revisions
1797 identifiers with -r/--rev or branches with -b/--branch. The
1797 identifiers with -r/--rev or branches with -b/--branch. The
1798 resulting clone will contain only the specified changesets and
1798 resulting clone will contain only the specified changesets and
1799 their ancestors. These options (or 'clone src#rev dest') imply
1799 their ancestors. These options (or 'clone src#rev dest') imply
1800 --pull, even for local source repositories.
1800 --pull, even for local source repositories.
1801
1801
1802 In normal clone mode, the remote normalizes repository data into a common
1802 In normal clone mode, the remote normalizes repository data into a common
1803 exchange format and the receiving end translates this data into its local
1803 exchange format and the receiving end translates this data into its local
1804 storage format. --stream activates a different clone mode that essentially
1804 storage format. --stream activates a different clone mode that essentially
1805 copies repository files from the remote with minimal data processing. This
1805 copies repository files from the remote with minimal data processing. This
1806 significantly reduces the CPU cost of a clone both remotely and locally.
1806 significantly reduces the CPU cost of a clone both remotely and locally.
1807 However, it often increases the transferred data size by 30-40%. This can
1807 However, it often increases the transferred data size by 30-40%. This can
1808 result in substantially faster clones where I/O throughput is plentiful,
1808 result in substantially faster clones where I/O throughput is plentiful,
1809 especially for larger repositories. A side-effect of --stream clones is
1809 especially for larger repositories. A side-effect of --stream clones is
1810 that storage settings and requirements on the remote are applied locally:
1810 that storage settings and requirements on the remote are applied locally:
1811 a modern client may inherit legacy or inefficient storage used by the
1811 a modern client may inherit legacy or inefficient storage used by the
1812 remote or a legacy Mercurial client may not be able to clone from a
1812 remote or a legacy Mercurial client may not be able to clone from a
1813 modern Mercurial remote.
1813 modern Mercurial remote.
1814
1814
1815 .. note::
1815 .. note::
1816
1816
1817 Specifying a tag will include the tagged changeset but not the
1817 Specifying a tag will include the tagged changeset but not the
1818 changeset containing the tag.
1818 changeset containing the tag.
1819
1819
1820 .. container:: verbose
1820 .. container:: verbose
1821
1821
1822 For efficiency, hardlinks are used for cloning whenever the
1822 For efficiency, hardlinks are used for cloning whenever the
1823 source and destination are on the same filesystem (note this
1823 source and destination are on the same filesystem (note this
1824 applies only to the repository data, not to the working
1824 applies only to the repository data, not to the working
1825 directory). Some filesystems, such as AFS, implement hardlinking
1825 directory). Some filesystems, such as AFS, implement hardlinking
1826 incorrectly, but do not report errors. In these cases, use the
1826 incorrectly, but do not report errors. In these cases, use the
1827 --pull option to avoid hardlinking.
1827 --pull option to avoid hardlinking.
1828
1828
1829 Mercurial will update the working directory to the first applicable
1829 Mercurial will update the working directory to the first applicable
1830 revision from this list:
1830 revision from this list:
1831
1831
1832 a) null if -U or the source repository has no changesets
1832 a) null if -U or the source repository has no changesets
1833 b) if -u . and the source repository is local, the first parent of
1833 b) if -u . and the source repository is local, the first parent of
1834 the source repository's working directory
1834 the source repository's working directory
1835 c) the changeset specified with -u (if a branch name, this means the
1835 c) the changeset specified with -u (if a branch name, this means the
1836 latest head of that branch)
1836 latest head of that branch)
1837 d) the changeset specified with -r
1837 d) the changeset specified with -r
1838 e) the tipmost head specified with -b
1838 e) the tipmost head specified with -b
1839 f) the tipmost head specified with the url#branch source syntax
1839 f) the tipmost head specified with the url#branch source syntax
1840 g) the revision marked with the '@' bookmark, if present
1840 g) the revision marked with the '@' bookmark, if present
1841 h) the tipmost head of the default branch
1841 h) the tipmost head of the default branch
1842 i) tip
1842 i) tip
1843
1843
1844 When cloning from servers that support it, Mercurial may fetch
1844 When cloning from servers that support it, Mercurial may fetch
1845 pre-generated data from a server-advertised URL or inline from the
1845 pre-generated data from a server-advertised URL or inline from the
1846 same stream. When this is done, hooks operating on incoming changesets
1846 same stream. When this is done, hooks operating on incoming changesets
1847 and changegroups may fire more than once, once for each pre-generated
1847 and changegroups may fire more than once, once for each pre-generated
1848 bundle and as well as for any additional remaining data. In addition,
1848 bundle and as well as for any additional remaining data. In addition,
1849 if an error occurs, the repository may be rolled back to a partial
1849 if an error occurs, the repository may be rolled back to a partial
1850 clone. This behavior may change in future releases.
1850 clone. This behavior may change in future releases.
1851 See :hg:`help -e clonebundles` for more.
1851 See :hg:`help -e clonebundles` for more.
1852
1852
1853 Examples:
1853 Examples:
1854
1854
1855 - clone a remote repository to a new directory named hg/::
1855 - clone a remote repository to a new directory named hg/::
1856
1856
1857 hg clone https://www.mercurial-scm.org/repo/hg/
1857 hg clone https://www.mercurial-scm.org/repo/hg/
1858
1858
1859 - create a lightweight local clone::
1859 - create a lightweight local clone::
1860
1860
1861 hg clone project/ project-feature/
1861 hg clone project/ project-feature/
1862
1862
1863 - clone from an absolute path on an ssh server (note double-slash)::
1863 - clone from an absolute path on an ssh server (note double-slash)::
1864
1864
1865 hg clone ssh://user@server//home/projects/alpha/
1865 hg clone ssh://user@server//home/projects/alpha/
1866
1866
1867 - do a streaming clone while checking out a specified version::
1867 - do a streaming clone while checking out a specified version::
1868
1868
1869 hg clone --stream http://server/repo -u 1.5
1869 hg clone --stream http://server/repo -u 1.5
1870
1870
1871 - create a repository without changesets after a particular revision::
1871 - create a repository without changesets after a particular revision::
1872
1872
1873 hg clone -r 04e544 experimental/ good/
1873 hg clone -r 04e544 experimental/ good/
1874
1874
1875 - clone (and track) a particular named branch::
1875 - clone (and track) a particular named branch::
1876
1876
1877 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1877 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1878
1878
1879 See :hg:`help urls` for details on specifying URLs.
1879 See :hg:`help urls` for details on specifying URLs.
1880
1880
1881 Returns 0 on success.
1881 Returns 0 on success.
1882 """
1882 """
1883 opts = pycompat.byteskwargs(opts)
1883 opts = pycompat.byteskwargs(opts)
1884 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1884 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1885
1885
1886 # --include/--exclude can come from narrow or sparse.
1886 # --include/--exclude can come from narrow or sparse.
1887 includepats, excludepats = None, None
1887 includepats, excludepats = None, None
1888
1888
1889 # hg.clone() differentiates between None and an empty set. So make sure
1889 # hg.clone() differentiates between None and an empty set. So make sure
1890 # patterns are sets if narrow is requested without patterns.
1890 # patterns are sets if narrow is requested without patterns.
1891 if opts.get(b'narrow'):
1891 if opts.get(b'narrow'):
1892 includepats = set()
1892 includepats = set()
1893 excludepats = set()
1893 excludepats = set()
1894
1894
1895 if opts.get(b'include'):
1895 if opts.get(b'include'):
1896 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1896 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1897 if opts.get(b'exclude'):
1897 if opts.get(b'exclude'):
1898 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1898 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1899
1899
1900 r = hg.clone(
1900 r = hg.clone(
1901 ui,
1901 ui,
1902 opts,
1902 opts,
1903 source,
1903 source,
1904 dest,
1904 dest,
1905 pull=opts.get(b'pull'),
1905 pull=opts.get(b'pull'),
1906 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1906 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1907 revs=opts.get(b'rev'),
1907 revs=opts.get(b'rev'),
1908 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1908 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1909 branch=opts.get(b'branch'),
1909 branch=opts.get(b'branch'),
1910 shareopts=opts.get(b'shareopts'),
1910 shareopts=opts.get(b'shareopts'),
1911 storeincludepats=includepats,
1911 storeincludepats=includepats,
1912 storeexcludepats=excludepats,
1912 storeexcludepats=excludepats,
1913 depth=opts.get(b'depth') or None,
1913 depth=opts.get(b'depth') or None,
1914 )
1914 )
1915
1915
1916 return r is None
1916 return r is None
1917
1917
1918
1918
1919 @command(
1919 @command(
1920 b'commit|ci',
1920 b'commit|ci',
1921 [
1921 [
1922 (
1922 (
1923 b'A',
1923 b'A',
1924 b'addremove',
1924 b'addremove',
1925 None,
1925 None,
1926 _(b'mark new/missing files as added/removed before committing'),
1926 _(b'mark new/missing files as added/removed before committing'),
1927 ),
1927 ),
1928 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1928 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1929 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1929 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1930 (b's', b'secret', None, _(b'use the secret phase for committing')),
1930 (b's', b'secret', None, _(b'use the secret phase for committing')),
1931 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1931 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1932 (
1932 (
1933 b'',
1933 b'',
1934 b'force-close-branch',
1934 b'force-close-branch',
1935 None,
1935 None,
1936 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1936 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1937 ),
1937 ),
1938 (b'i', b'interactive', None, _(b'use interactive mode')),
1938 (b'i', b'interactive', None, _(b'use interactive mode')),
1939 ]
1939 ]
1940 + walkopts
1940 + walkopts
1941 + commitopts
1941 + commitopts
1942 + commitopts2
1942 + commitopts2
1943 + subrepoopts,
1943 + subrepoopts,
1944 _(b'[OPTION]... [FILE]...'),
1944 _(b'[OPTION]... [FILE]...'),
1945 helpcategory=command.CATEGORY_COMMITTING,
1945 helpcategory=command.CATEGORY_COMMITTING,
1946 helpbasic=True,
1946 helpbasic=True,
1947 inferrepo=True,
1947 inferrepo=True,
1948 )
1948 )
1949 def commit(ui, repo, *pats, **opts):
1949 def commit(ui, repo, *pats, **opts):
1950 """commit the specified files or all outstanding changes
1950 """commit the specified files or all outstanding changes
1951
1951
1952 Commit changes to the given files into the repository. Unlike a
1952 Commit changes to the given files into the repository. Unlike a
1953 centralized SCM, this operation is a local operation. See
1953 centralized SCM, this operation is a local operation. See
1954 :hg:`push` for a way to actively distribute your changes.
1954 :hg:`push` for a way to actively distribute your changes.
1955
1955
1956 If a list of files is omitted, all changes reported by :hg:`status`
1956 If a list of files is omitted, all changes reported by :hg:`status`
1957 will be committed.
1957 will be committed.
1958
1958
1959 If you are committing the result of a merge, do not provide any
1959 If you are committing the result of a merge, do not provide any
1960 filenames or -I/-X filters.
1960 filenames or -I/-X filters.
1961
1961
1962 If no commit message is specified, Mercurial starts your
1962 If no commit message is specified, Mercurial starts your
1963 configured editor where you can enter a message. In case your
1963 configured editor where you can enter a message. In case your
1964 commit fails, you will find a backup of your message in
1964 commit fails, you will find a backup of your message in
1965 ``.hg/last-message.txt``.
1965 ``.hg/last-message.txt``.
1966
1966
1967 The --close-branch flag can be used to mark the current branch
1967 The --close-branch flag can be used to mark the current branch
1968 head closed. When all heads of a branch are closed, the branch
1968 head closed. When all heads of a branch are closed, the branch
1969 will be considered closed and no longer listed.
1969 will be considered closed and no longer listed.
1970
1970
1971 The --amend flag can be used to amend the parent of the
1971 The --amend flag can be used to amend the parent of the
1972 working directory with a new commit that contains the changes
1972 working directory with a new commit that contains the changes
1973 in the parent in addition to those currently reported by :hg:`status`,
1973 in the parent in addition to those currently reported by :hg:`status`,
1974 if there are any. The old commit is stored in a backup bundle in
1974 if there are any. The old commit is stored in a backup bundle in
1975 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1975 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1976 on how to restore it).
1976 on how to restore it).
1977
1977
1978 Message, user and date are taken from the amended commit unless
1978 Message, user and date are taken from the amended commit unless
1979 specified. When a message isn't specified on the command line,
1979 specified. When a message isn't specified on the command line,
1980 the editor will open with the message of the amended commit.
1980 the editor will open with the message of the amended commit.
1981
1981
1982 It is not possible to amend public changesets (see :hg:`help phases`)
1982 It is not possible to amend public changesets (see :hg:`help phases`)
1983 or changesets that have children.
1983 or changesets that have children.
1984
1984
1985 See :hg:`help dates` for a list of formats valid for -d/--date.
1985 See :hg:`help dates` for a list of formats valid for -d/--date.
1986
1986
1987 Returns 0 on success, 1 if nothing changed.
1987 Returns 0 on success, 1 if nothing changed.
1988
1988
1989 .. container:: verbose
1989 .. container:: verbose
1990
1990
1991 Examples:
1991 Examples:
1992
1992
1993 - commit all files ending in .py::
1993 - commit all files ending in .py::
1994
1994
1995 hg commit --include "set:**.py"
1995 hg commit --include "set:**.py"
1996
1996
1997 - commit all non-binary files::
1997 - commit all non-binary files::
1998
1998
1999 hg commit --exclude "set:binary()"
1999 hg commit --exclude "set:binary()"
2000
2000
2001 - amend the current commit and set the date to now::
2001 - amend the current commit and set the date to now::
2002
2002
2003 hg commit --amend --date now
2003 hg commit --amend --date now
2004 """
2004 """
2005 with repo.wlock(), repo.lock():
2005 with repo.wlock(), repo.lock():
2006 return _docommit(ui, repo, *pats, **opts)
2006 return _docommit(ui, repo, *pats, **opts)
2007
2007
2008
2008
2009 def _docommit(ui, repo, *pats, **opts):
2009 def _docommit(ui, repo, *pats, **opts):
2010 if opts.get('interactive'):
2010 if opts.get('interactive'):
2011 opts.pop('interactive')
2011 opts.pop('interactive')
2012 ret = cmdutil.dorecord(
2012 ret = cmdutil.dorecord(
2013 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2013 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2014 )
2014 )
2015 # ret can be 0 (no changes to record) or the value returned by
2015 # ret can be 0 (no changes to record) or the value returned by
2016 # commit(), 1 if nothing changed or None on success.
2016 # commit(), 1 if nothing changed or None on success.
2017 return 1 if ret == 0 else ret
2017 return 1 if ret == 0 else ret
2018
2018
2019 opts = pycompat.byteskwargs(opts)
2019 opts = pycompat.byteskwargs(opts)
2020 if opts.get(b'subrepos'):
2020 if opts.get(b'subrepos'):
2021 if opts.get(b'amend'):
2021 if opts.get(b'amend'):
2022 raise error.Abort(_(b'cannot amend with --subrepos'))
2022 raise error.Abort(_(b'cannot amend with --subrepos'))
2023 # Let --subrepos on the command line override config setting.
2023 # Let --subrepos on the command line override config setting.
2024 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2024 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2025
2025
2026 cmdutil.checkunfinished(repo, commit=True)
2026 cmdutil.checkunfinished(repo, commit=True)
2027
2027
2028 branch = repo[None].branch()
2028 branch = repo[None].branch()
2029 bheads = repo.branchheads(branch)
2029 bheads = repo.branchheads(branch)
2030
2030
2031 extra = {}
2031 extra = {}
2032 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2032 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2033 extra[b'close'] = b'1'
2033 extra[b'close'] = b'1'
2034
2034
2035 if repo[b'.'].closesbranch():
2035 if repo[b'.'].closesbranch():
2036 raise error.Abort(
2036 raise error.Abort(
2037 _(b'current revision is already a branch closing head')
2037 _(b'current revision is already a branch closing head')
2038 )
2038 )
2039 elif not bheads:
2039 elif not bheads:
2040 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2040 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2041 elif (
2041 elif (
2042 branch == repo[b'.'].branch()
2042 branch == repo[b'.'].branch()
2043 and repo[b'.'].node() not in bheads
2043 and repo[b'.'].node() not in bheads
2044 and not opts.get(b'force_close_branch')
2044 and not opts.get(b'force_close_branch')
2045 ):
2045 ):
2046 hint = _(
2046 hint = _(
2047 b'use --force-close-branch to close branch from a non-head'
2047 b'use --force-close-branch to close branch from a non-head'
2048 b' changeset'
2048 b' changeset'
2049 )
2049 )
2050 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2050 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2051 elif opts.get(b'amend'):
2051 elif opts.get(b'amend'):
2052 if (
2052 if (
2053 repo[b'.'].p1().branch() != branch
2053 repo[b'.'].p1().branch() != branch
2054 and repo[b'.'].p2().branch() != branch
2054 and repo[b'.'].p2().branch() != branch
2055 ):
2055 ):
2056 raise error.Abort(_(b'can only close branch heads'))
2056 raise error.Abort(_(b'can only close branch heads'))
2057
2057
2058 if opts.get(b'amend'):
2058 if opts.get(b'amend'):
2059 if ui.configbool(b'ui', b'commitsubrepos'):
2059 if ui.configbool(b'ui', b'commitsubrepos'):
2060 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2060 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2061
2061
2062 old = repo[b'.']
2062 old = repo[b'.']
2063 rewriteutil.precheck(repo, [old.rev()], b'amend')
2063 rewriteutil.precheck(repo, [old.rev()], b'amend')
2064
2064
2065 # Currently histedit gets confused if an amend happens while histedit
2065 # Currently histedit gets confused if an amend happens while histedit
2066 # is in progress. Since we have a checkunfinished command, we are
2066 # is in progress. Since we have a checkunfinished command, we are
2067 # temporarily honoring it.
2067 # temporarily honoring it.
2068 #
2068 #
2069 # Note: eventually this guard will be removed. Please do not expect
2069 # Note: eventually this guard will be removed. Please do not expect
2070 # this behavior to remain.
2070 # this behavior to remain.
2071 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2071 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2072 cmdutil.checkunfinished(repo)
2072 cmdutil.checkunfinished(repo)
2073
2073
2074 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2074 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2075 if node == old.node():
2075 if node == old.node():
2076 ui.status(_(b"nothing changed\n"))
2076 ui.status(_(b"nothing changed\n"))
2077 return 1
2077 return 1
2078 else:
2078 else:
2079
2079
2080 def commitfunc(ui, repo, message, match, opts):
2080 def commitfunc(ui, repo, message, match, opts):
2081 overrides = {}
2081 overrides = {}
2082 if opts.get(b'secret'):
2082 if opts.get(b'secret'):
2083 overrides[(b'phases', b'new-commit')] = b'secret'
2083 overrides[(b'phases', b'new-commit')] = b'secret'
2084
2084
2085 baseui = repo.baseui
2085 baseui = repo.baseui
2086 with baseui.configoverride(overrides, b'commit'):
2086 with baseui.configoverride(overrides, b'commit'):
2087 with ui.configoverride(overrides, b'commit'):
2087 with ui.configoverride(overrides, b'commit'):
2088 editform = cmdutil.mergeeditform(
2088 editform = cmdutil.mergeeditform(
2089 repo[None], b'commit.normal'
2089 repo[None], b'commit.normal'
2090 )
2090 )
2091 editor = cmdutil.getcommiteditor(
2091 editor = cmdutil.getcommiteditor(
2092 editform=editform, **pycompat.strkwargs(opts)
2092 editform=editform, **pycompat.strkwargs(opts)
2093 )
2093 )
2094 return repo.commit(
2094 return repo.commit(
2095 message,
2095 message,
2096 opts.get(b'user'),
2096 opts.get(b'user'),
2097 opts.get(b'date'),
2097 opts.get(b'date'),
2098 match,
2098 match,
2099 editor=editor,
2099 editor=editor,
2100 extra=extra,
2100 extra=extra,
2101 )
2101 )
2102
2102
2103 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2103 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2104
2104
2105 if not node:
2105 if not node:
2106 stat = cmdutil.postcommitstatus(repo, pats, opts)
2106 stat = cmdutil.postcommitstatus(repo, pats, opts)
2107 if stat.deleted:
2107 if stat.deleted:
2108 ui.status(
2108 ui.status(
2109 _(
2109 _(
2110 b"nothing changed (%d missing files, see "
2110 b"nothing changed (%d missing files, see "
2111 b"'hg status')\n"
2111 b"'hg status')\n"
2112 )
2112 )
2113 % len(stat.deleted)
2113 % len(stat.deleted)
2114 )
2114 )
2115 else:
2115 else:
2116 ui.status(_(b"nothing changed\n"))
2116 ui.status(_(b"nothing changed\n"))
2117 return 1
2117 return 1
2118
2118
2119 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2119 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2120
2120
2121 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2121 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2122 status(
2122 status(
2123 ui,
2123 ui,
2124 repo,
2124 repo,
2125 modified=True,
2125 modified=True,
2126 added=True,
2126 added=True,
2127 removed=True,
2127 removed=True,
2128 deleted=True,
2128 deleted=True,
2129 unknown=True,
2129 unknown=True,
2130 subrepos=opts.get(b'subrepos'),
2130 subrepos=opts.get(b'subrepos'),
2131 )
2131 )
2132
2132
2133
2133
2134 @command(
2134 @command(
2135 b'config|showconfig|debugconfig',
2135 b'config|showconfig|debugconfig',
2136 [
2136 [
2137 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2137 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2138 (b'e', b'edit', None, _(b'edit user config')),
2138 (b'e', b'edit', None, _(b'edit user config')),
2139 (b'l', b'local', None, _(b'edit repository config')),
2139 (b'l', b'local', None, _(b'edit repository config')),
2140 (
2140 (
2141 b'',
2141 b'',
2142 b'shared',
2142 b'shared',
2143 None,
2143 None,
2144 _(b'edit shared source repository config (EXPERIMENTAL)'),
2144 _(b'edit shared source repository config (EXPERIMENTAL)'),
2145 ),
2145 ),
2146 (b'g', b'global', None, _(b'edit global config')),
2146 (b'g', b'global', None, _(b'edit global config')),
2147 ]
2147 ]
2148 + formatteropts,
2148 + formatteropts,
2149 _(b'[-u] [NAME]...'),
2149 _(b'[-u] [NAME]...'),
2150 helpcategory=command.CATEGORY_HELP,
2150 helpcategory=command.CATEGORY_HELP,
2151 optionalrepo=True,
2151 optionalrepo=True,
2152 intents={INTENT_READONLY},
2152 intents={INTENT_READONLY},
2153 )
2153 )
2154 def config(ui, repo, *values, **opts):
2154 def config(ui, repo, *values, **opts):
2155 """show combined config settings from all hgrc files
2155 """show combined config settings from all hgrc files
2156
2156
2157 With no arguments, print names and values of all config items.
2157 With no arguments, print names and values of all config items.
2158
2158
2159 With one argument of the form section.name, print just the value
2159 With one argument of the form section.name, print just the value
2160 of that config item.
2160 of that config item.
2161
2161
2162 With multiple arguments, print names and values of all config
2162 With multiple arguments, print names and values of all config
2163 items with matching section names or section.names.
2163 items with matching section names or section.names.
2164
2164
2165 With --edit, start an editor on the user-level config file. With
2165 With --edit, start an editor on the user-level config file. With
2166 --global, edit the system-wide config file. With --local, edit the
2166 --global, edit the system-wide config file. With --local, edit the
2167 repository-level config file.
2167 repository-level config file.
2168
2168
2169 With --debug, the source (filename and line number) is printed
2169 With --debug, the source (filename and line number) is printed
2170 for each config item.
2170 for each config item.
2171
2171
2172 See :hg:`help config` for more information about config files.
2172 See :hg:`help config` for more information about config files.
2173
2173
2174 .. container:: verbose
2174 .. container:: verbose
2175
2175
2176 Template:
2176 Template:
2177
2177
2178 The following keywords are supported. See also :hg:`help templates`.
2178 The following keywords are supported. See also :hg:`help templates`.
2179
2179
2180 :name: String. Config name.
2180 :name: String. Config name.
2181 :source: String. Filename and line number where the item is defined.
2181 :source: String. Filename and line number where the item is defined.
2182 :value: String. Config value.
2182 :value: String. Config value.
2183
2183
2184 The --shared flag can be used to edit the config file of shared source
2184 The --shared flag can be used to edit the config file of shared source
2185 repository. It only works when you have shared using the experimental
2185 repository. It only works when you have shared using the experimental
2186 share safe feature.
2186 share safe feature.
2187
2187
2188 Returns 0 on success, 1 if NAME does not exist.
2188 Returns 0 on success, 1 if NAME does not exist.
2189
2189
2190 """
2190 """
2191
2191
2192 opts = pycompat.byteskwargs(opts)
2192 opts = pycompat.byteskwargs(opts)
2193 editopts = (b'edit', b'local', b'global', b'shared')
2193 editopts = (b'edit', b'local', b'global', b'shared')
2194 if any(opts.get(o) for o in editopts):
2194 if any(opts.get(o) for o in editopts):
2195 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2195 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2196 if opts.get(b'local'):
2196 if opts.get(b'local'):
2197 if not repo:
2197 if not repo:
2198 raise error.Abort(_(b"can't use --local outside a repository"))
2198 raise error.Abort(_(b"can't use --local outside a repository"))
2199 paths = [repo.vfs.join(b'hgrc')]
2199 paths = [repo.vfs.join(b'hgrc')]
2200 elif opts.get(b'global'):
2200 elif opts.get(b'global'):
2201 paths = rcutil.systemrcpath()
2201 paths = rcutil.systemrcpath()
2202 elif opts.get(b'shared'):
2202 elif opts.get(b'shared'):
2203 if not repo.shared():
2203 if not repo.shared():
2204 raise error.Abort(
2204 raise error.Abort(
2205 _(b"repository is not shared; can't use --shared")
2205 _(b"repository is not shared; can't use --shared")
2206 )
2206 )
2207 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2207 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2208 raise error.Abort(
2208 raise error.Abort(
2209 _(
2209 _(
2210 b"share safe feature not unabled; "
2210 b"share safe feature not unabled; "
2211 b"unable to edit shared source repository config"
2211 b"unable to edit shared source repository config"
2212 )
2212 )
2213 )
2213 )
2214 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2214 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2215 else:
2215 else:
2216 paths = rcutil.userrcpath()
2216 paths = rcutil.userrcpath()
2217
2217
2218 for f in paths:
2218 for f in paths:
2219 if os.path.exists(f):
2219 if os.path.exists(f):
2220 break
2220 break
2221 else:
2221 else:
2222 if opts.get(b'global'):
2222 if opts.get(b'global'):
2223 samplehgrc = uimod.samplehgrcs[b'global']
2223 samplehgrc = uimod.samplehgrcs[b'global']
2224 elif opts.get(b'local'):
2224 elif opts.get(b'local'):
2225 samplehgrc = uimod.samplehgrcs[b'local']
2225 samplehgrc = uimod.samplehgrcs[b'local']
2226 else:
2226 else:
2227 samplehgrc = uimod.samplehgrcs[b'user']
2227 samplehgrc = uimod.samplehgrcs[b'user']
2228
2228
2229 f = paths[0]
2229 f = paths[0]
2230 fp = open(f, b"wb")
2230 fp = open(f, b"wb")
2231 fp.write(util.tonativeeol(samplehgrc))
2231 fp.write(util.tonativeeol(samplehgrc))
2232 fp.close()
2232 fp.close()
2233
2233
2234 editor = ui.geteditor()
2234 editor = ui.geteditor()
2235 ui.system(
2235 ui.system(
2236 b"%s \"%s\"" % (editor, f),
2236 b"%s \"%s\"" % (editor, f),
2237 onerr=error.Abort,
2237 onerr=error.Abort,
2238 errprefix=_(b"edit failed"),
2238 errprefix=_(b"edit failed"),
2239 blockedtag=b'config_edit',
2239 blockedtag=b'config_edit',
2240 )
2240 )
2241 return
2241 return
2242 ui.pager(b'config')
2242 ui.pager(b'config')
2243 fm = ui.formatter(b'config', opts)
2243 fm = ui.formatter(b'config', opts)
2244 for t, f in rcutil.rccomponents():
2244 for t, f in rcutil.rccomponents():
2245 if t == b'path':
2245 if t == b'path':
2246 ui.debug(b'read config from: %s\n' % f)
2246 ui.debug(b'read config from: %s\n' % f)
2247 elif t == b'resource':
2247 elif t == b'resource':
2248 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2248 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2249 elif t == b'items':
2249 elif t == b'items':
2250 # Don't print anything for 'items'.
2250 # Don't print anything for 'items'.
2251 pass
2251 pass
2252 else:
2252 else:
2253 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2253 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2254 untrusted = bool(opts.get(b'untrusted'))
2254 untrusted = bool(opts.get(b'untrusted'))
2255
2255
2256 selsections = selentries = []
2256 selsections = selentries = []
2257 if values:
2257 if values:
2258 selsections = [v for v in values if b'.' not in v]
2258 selsections = [v for v in values if b'.' not in v]
2259 selentries = [v for v in values if b'.' in v]
2259 selentries = [v for v in values if b'.' in v]
2260 uniquesel = len(selentries) == 1 and not selsections
2260 uniquesel = len(selentries) == 1 and not selsections
2261 selsections = set(selsections)
2261 selsections = set(selsections)
2262 selentries = set(selentries)
2262 selentries = set(selentries)
2263
2263
2264 matched = False
2264 matched = False
2265 for section, name, value in ui.walkconfig(untrusted=untrusted):
2265 for section, name, value in ui.walkconfig(untrusted=untrusted):
2266 source = ui.configsource(section, name, untrusted)
2266 source = ui.configsource(section, name, untrusted)
2267 value = pycompat.bytestr(value)
2267 value = pycompat.bytestr(value)
2268 defaultvalue = ui.configdefault(section, name)
2268 defaultvalue = ui.configdefault(section, name)
2269 if fm.isplain():
2269 if fm.isplain():
2270 source = source or b'none'
2270 source = source or b'none'
2271 value = value.replace(b'\n', b'\\n')
2271 value = value.replace(b'\n', b'\\n')
2272 entryname = section + b'.' + name
2272 entryname = section + b'.' + name
2273 if values and not (section in selsections or entryname in selentries):
2273 if values and not (section in selsections or entryname in selentries):
2274 continue
2274 continue
2275 fm.startitem()
2275 fm.startitem()
2276 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2276 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2277 if uniquesel:
2277 if uniquesel:
2278 fm.data(name=entryname)
2278 fm.data(name=entryname)
2279 fm.write(b'value', b'%s\n', value)
2279 fm.write(b'value', b'%s\n', value)
2280 else:
2280 else:
2281 fm.write(b'name value', b'%s=%s\n', entryname, value)
2281 fm.write(b'name value', b'%s=%s\n', entryname, value)
2282 if formatter.isprintable(defaultvalue):
2282 if formatter.isprintable(defaultvalue):
2283 fm.data(defaultvalue=defaultvalue)
2283 fm.data(defaultvalue=defaultvalue)
2284 elif isinstance(defaultvalue, list) and all(
2284 elif isinstance(defaultvalue, list) and all(
2285 formatter.isprintable(e) for e in defaultvalue
2285 formatter.isprintable(e) for e in defaultvalue
2286 ):
2286 ):
2287 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2287 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2288 # TODO: no idea how to process unsupported defaultvalue types
2288 # TODO: no idea how to process unsupported defaultvalue types
2289 matched = True
2289 matched = True
2290 fm.end()
2290 fm.end()
2291 if matched:
2291 if matched:
2292 return 0
2292 return 0
2293 return 1
2293 return 1
2294
2294
2295
2295
2296 @command(
2296 @command(
2297 b'continue',
2297 b'continue',
2298 dryrunopts,
2298 dryrunopts,
2299 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2299 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2300 helpbasic=True,
2300 helpbasic=True,
2301 )
2301 )
2302 def continuecmd(ui, repo, **opts):
2302 def continuecmd(ui, repo, **opts):
2303 """resumes an interrupted operation (EXPERIMENTAL)
2303 """resumes an interrupted operation (EXPERIMENTAL)
2304
2304
2305 Finishes a multistep operation like graft, histedit, rebase, merge,
2305 Finishes a multistep operation like graft, histedit, rebase, merge,
2306 and unshelve if they are in an interrupted state.
2306 and unshelve if they are in an interrupted state.
2307
2307
2308 use --dry-run/-n to dry run the command.
2308 use --dry-run/-n to dry run the command.
2309 """
2309 """
2310 dryrun = opts.get('dry_run')
2310 dryrun = opts.get('dry_run')
2311 contstate = cmdutil.getunfinishedstate(repo)
2311 contstate = cmdutil.getunfinishedstate(repo)
2312 if not contstate:
2312 if not contstate:
2313 raise error.Abort(_(b'no operation in progress'))
2313 raise error.Abort(_(b'no operation in progress'))
2314 if not contstate.continuefunc:
2314 if not contstate.continuefunc:
2315 raise error.Abort(
2315 raise error.Abort(
2316 (
2316 (
2317 _(b"%s in progress but does not support 'hg continue'")
2317 _(b"%s in progress but does not support 'hg continue'")
2318 % (contstate._opname)
2318 % (contstate._opname)
2319 ),
2319 ),
2320 hint=contstate.continuemsg(),
2320 hint=contstate.continuemsg(),
2321 )
2321 )
2322 if dryrun:
2322 if dryrun:
2323 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2323 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2324 return
2324 return
2325 return contstate.continuefunc(ui, repo)
2325 return contstate.continuefunc(ui, repo)
2326
2326
2327
2327
2328 @command(
2328 @command(
2329 b'copy|cp',
2329 b'copy|cp',
2330 [
2330 [
2331 (b'', b'forget', None, _(b'unmark a file as copied')),
2331 (b'', b'forget', None, _(b'unmark a file as copied')),
2332 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2332 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2333 (
2333 (
2334 b'',
2334 b'',
2335 b'at-rev',
2335 b'at-rev',
2336 b'',
2336 b'',
2337 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2337 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2338 _(b'REV'),
2338 _(b'REV'),
2339 ),
2339 ),
2340 (
2340 (
2341 b'f',
2341 b'f',
2342 b'force',
2342 b'force',
2343 None,
2343 None,
2344 _(b'forcibly copy over an existing managed file'),
2344 _(b'forcibly copy over an existing managed file'),
2345 ),
2345 ),
2346 ]
2346 ]
2347 + walkopts
2347 + walkopts
2348 + dryrunopts,
2348 + dryrunopts,
2349 _(b'[OPTION]... SOURCE... DEST'),
2349 _(b'[OPTION]... SOURCE... DEST'),
2350 helpcategory=command.CATEGORY_FILE_CONTENTS,
2350 helpcategory=command.CATEGORY_FILE_CONTENTS,
2351 )
2351 )
2352 def copy(ui, repo, *pats, **opts):
2352 def copy(ui, repo, *pats, **opts):
2353 """mark files as copied for the next commit
2353 """mark files as copied for the next commit
2354
2354
2355 Mark dest as having copies of source files. If dest is a
2355 Mark dest as having copies of source files. If dest is a
2356 directory, copies are put in that directory. If dest is a file,
2356 directory, copies are put in that directory. If dest is a file,
2357 the source must be a single file.
2357 the source must be a single file.
2358
2358
2359 By default, this command copies the contents of files as they
2359 By default, this command copies the contents of files as they
2360 exist in the working directory. If invoked with -A/--after, the
2360 exist in the working directory. If invoked with -A/--after, the
2361 operation is recorded, but no copying is performed.
2361 operation is recorded, but no copying is performed.
2362
2362
2363 To undo marking a file as copied, use --forget. With that option,
2363 To undo marking a file as copied, use --forget. With that option,
2364 all given (positional) arguments are unmarked as copies. The destination
2364 all given (positional) arguments are unmarked as copies. The destination
2365 file(s) will be left in place (still tracked).
2365 file(s) will be left in place (still tracked).
2366
2366
2367 This command takes effect with the next commit by default.
2367 This command takes effect with the next commit by default.
2368
2368
2369 Returns 0 on success, 1 if errors are encountered.
2369 Returns 0 on success, 1 if errors are encountered.
2370 """
2370 """
2371 opts = pycompat.byteskwargs(opts)
2371 opts = pycompat.byteskwargs(opts)
2372 with repo.wlock():
2372 with repo.wlock():
2373 return cmdutil.copy(ui, repo, pats, opts)
2373 return cmdutil.copy(ui, repo, pats, opts)
2374
2374
2375
2375
2376 @command(
2376 @command(
2377 b'debugcommands',
2377 b'debugcommands',
2378 [],
2378 [],
2379 _(b'[COMMAND]'),
2379 _(b'[COMMAND]'),
2380 helpcategory=command.CATEGORY_HELP,
2380 helpcategory=command.CATEGORY_HELP,
2381 norepo=True,
2381 norepo=True,
2382 )
2382 )
2383 def debugcommands(ui, cmd=b'', *args):
2383 def debugcommands(ui, cmd=b'', *args):
2384 """list all available commands and options"""
2384 """list all available commands and options"""
2385 for cmd, vals in sorted(pycompat.iteritems(table)):
2385 for cmd, vals in sorted(pycompat.iteritems(table)):
2386 cmd = cmd.split(b'|')[0]
2386 cmd = cmd.split(b'|')[0]
2387 opts = b', '.join([i[1] for i in vals[1]])
2387 opts = b', '.join([i[1] for i in vals[1]])
2388 ui.write(b'%s: %s\n' % (cmd, opts))
2388 ui.write(b'%s: %s\n' % (cmd, opts))
2389
2389
2390
2390
2391 @command(
2391 @command(
2392 b'debugcomplete',
2392 b'debugcomplete',
2393 [(b'o', b'options', None, _(b'show the command options'))],
2393 [(b'o', b'options', None, _(b'show the command options'))],
2394 _(b'[-o] CMD'),
2394 _(b'[-o] CMD'),
2395 helpcategory=command.CATEGORY_HELP,
2395 helpcategory=command.CATEGORY_HELP,
2396 norepo=True,
2396 norepo=True,
2397 )
2397 )
2398 def debugcomplete(ui, cmd=b'', **opts):
2398 def debugcomplete(ui, cmd=b'', **opts):
2399 """returns the completion list associated with the given command"""
2399 """returns the completion list associated with the given command"""
2400
2400
2401 if opts.get('options'):
2401 if opts.get('options'):
2402 options = []
2402 options = []
2403 otables = [globalopts]
2403 otables = [globalopts]
2404 if cmd:
2404 if cmd:
2405 aliases, entry = cmdutil.findcmd(cmd, table, False)
2405 aliases, entry = cmdutil.findcmd(cmd, table, False)
2406 otables.append(entry[1])
2406 otables.append(entry[1])
2407 for t in otables:
2407 for t in otables:
2408 for o in t:
2408 for o in t:
2409 if b"(DEPRECATED)" in o[3]:
2409 if b"(DEPRECATED)" in o[3]:
2410 continue
2410 continue
2411 if o[0]:
2411 if o[0]:
2412 options.append(b'-%s' % o[0])
2412 options.append(b'-%s' % o[0])
2413 options.append(b'--%s' % o[1])
2413 options.append(b'--%s' % o[1])
2414 ui.write(b"%s\n" % b"\n".join(options))
2414 ui.write(b"%s\n" % b"\n".join(options))
2415 return
2415 return
2416
2416
2417 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2417 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2418 if ui.verbose:
2418 if ui.verbose:
2419 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2419 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2420 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2420 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2421
2421
2422
2422
2423 @command(
2423 @command(
2424 b'diff',
2424 b'diff',
2425 [
2425 [
2426 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2426 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2427 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2427 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2428 ]
2428 ]
2429 + diffopts
2429 + diffopts
2430 + diffopts2
2430 + diffopts2
2431 + walkopts
2431 + walkopts
2432 + subrepoopts,
2432 + subrepoopts,
2433 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2433 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2434 helpcategory=command.CATEGORY_FILE_CONTENTS,
2434 helpcategory=command.CATEGORY_FILE_CONTENTS,
2435 helpbasic=True,
2435 helpbasic=True,
2436 inferrepo=True,
2436 inferrepo=True,
2437 intents={INTENT_READONLY},
2437 intents={INTENT_READONLY},
2438 )
2438 )
2439 def diff(ui, repo, *pats, **opts):
2439 def diff(ui, repo, *pats, **opts):
2440 """diff repository (or selected files)
2440 """diff repository (or selected files)
2441
2441
2442 Show differences between revisions for the specified files.
2442 Show differences between revisions for the specified files.
2443
2443
2444 Differences between files are shown using the unified diff format.
2444 Differences between files are shown using the unified diff format.
2445
2445
2446 .. note::
2446 .. note::
2447
2447
2448 :hg:`diff` may generate unexpected results for merges, as it will
2448 :hg:`diff` may generate unexpected results for merges, as it will
2449 default to comparing against the working directory's first
2449 default to comparing against the working directory's first
2450 parent changeset if no revisions are specified.
2450 parent changeset if no revisions are specified.
2451
2451
2452 When two revision arguments are given, then changes are shown
2452 When two revision arguments are given, then changes are shown
2453 between those revisions. If only one revision is specified then
2453 between those revisions. If only one revision is specified then
2454 that revision is compared to the working directory, and, when no
2454 that revision is compared to the working directory, and, when no
2455 revisions are specified, the working directory files are compared
2455 revisions are specified, the working directory files are compared
2456 to its first parent.
2456 to its first parent.
2457
2457
2458 Alternatively you can specify -c/--change with a revision to see
2458 Alternatively you can specify -c/--change with a revision to see
2459 the changes in that changeset relative to its first parent.
2459 the changes in that changeset relative to its first parent.
2460
2460
2461 Without the -a/--text option, diff will avoid generating diffs of
2461 Without the -a/--text option, diff will avoid generating diffs of
2462 files it detects as binary. With -a, diff will generate a diff
2462 files it detects as binary. With -a, diff will generate a diff
2463 anyway, probably with undesirable results.
2463 anyway, probably with undesirable results.
2464
2464
2465 Use the -g/--git option to generate diffs in the git extended diff
2465 Use the -g/--git option to generate diffs in the git extended diff
2466 format. For more information, read :hg:`help diffs`.
2466 format. For more information, read :hg:`help diffs`.
2467
2467
2468 .. container:: verbose
2468 .. container:: verbose
2469
2469
2470 Examples:
2470 Examples:
2471
2471
2472 - compare a file in the current working directory to its parent::
2472 - compare a file in the current working directory to its parent::
2473
2473
2474 hg diff foo.c
2474 hg diff foo.c
2475
2475
2476 - compare two historical versions of a directory, with rename info::
2476 - compare two historical versions of a directory, with rename info::
2477
2477
2478 hg diff --git -r 1.0:1.2 lib/
2478 hg diff --git -r 1.0:1.2 lib/
2479
2479
2480 - get change stats relative to the last change on some date::
2480 - get change stats relative to the last change on some date::
2481
2481
2482 hg diff --stat -r "date('may 2')"
2482 hg diff --stat -r "date('may 2')"
2483
2483
2484 - diff all newly-added files that contain a keyword::
2484 - diff all newly-added files that contain a keyword::
2485
2485
2486 hg diff "set:added() and grep(GNU)"
2486 hg diff "set:added() and grep(GNU)"
2487
2487
2488 - compare a revision and its parents::
2488 - compare a revision and its parents::
2489
2489
2490 hg diff -c 9353 # compare against first parent
2490 hg diff -c 9353 # compare against first parent
2491 hg diff -r 9353^:9353 # same using revset syntax
2491 hg diff -r 9353^:9353 # same using revset syntax
2492 hg diff -r 9353^2:9353 # compare against the second parent
2492 hg diff -r 9353^2:9353 # compare against the second parent
2493
2493
2494 Returns 0 on success.
2494 Returns 0 on success.
2495 """
2495 """
2496
2496
2497 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2497 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2498 opts = pycompat.byteskwargs(opts)
2498 opts = pycompat.byteskwargs(opts)
2499 revs = opts.get(b'rev')
2499 revs = opts.get(b'rev')
2500 change = opts.get(b'change')
2500 change = opts.get(b'change')
2501 stat = opts.get(b'stat')
2501 stat = opts.get(b'stat')
2502 reverse = opts.get(b'reverse')
2502 reverse = opts.get(b'reverse')
2503
2503
2504 if change:
2504 if change:
2505 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2505 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2506 ctx2 = scmutil.revsingle(repo, change, None)
2506 ctx2 = scmutil.revsingle(repo, change, None)
2507 ctx1 = ctx2.p1()
2507 ctx1 = ctx2.p1()
2508 else:
2508 else:
2509 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2509 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2510 ctx1, ctx2 = scmutil.revpair(repo, revs)
2510 ctx1, ctx2 = scmutil.revpair(repo, revs)
2511
2511
2512 if reverse:
2512 if reverse:
2513 ctxleft = ctx2
2513 ctxleft = ctx2
2514 ctxright = ctx1
2514 ctxright = ctx1
2515 else:
2515 else:
2516 ctxleft = ctx1
2516 ctxleft = ctx1
2517 ctxright = ctx2
2517 ctxright = ctx2
2518
2518
2519 diffopts = patch.diffallopts(ui, opts)
2519 diffopts = patch.diffallopts(ui, opts)
2520 m = scmutil.match(ctx2, pats, opts)
2520 m = scmutil.match(ctx2, pats, opts)
2521 m = repo.narrowmatch(m)
2521 m = repo.narrowmatch(m)
2522 ui.pager(b'diff')
2522 ui.pager(b'diff')
2523 logcmdutil.diffordiffstat(
2523 logcmdutil.diffordiffstat(
2524 ui,
2524 ui,
2525 repo,
2525 repo,
2526 diffopts,
2526 diffopts,
2527 ctxleft,
2527 ctxleft,
2528 ctxright,
2528 ctxright,
2529 m,
2529 m,
2530 stat=stat,
2530 stat=stat,
2531 listsubrepos=opts.get(b'subrepos'),
2531 listsubrepos=opts.get(b'subrepos'),
2532 root=opts.get(b'root'),
2532 root=opts.get(b'root'),
2533 )
2533 )
2534
2534
2535
2535
2536 @command(
2536 @command(
2537 b'export',
2537 b'export',
2538 [
2538 [
2539 (
2539 (
2540 b'B',
2540 b'B',
2541 b'bookmark',
2541 b'bookmark',
2542 b'',
2542 b'',
2543 _(b'export changes only reachable by given bookmark'),
2543 _(b'export changes only reachable by given bookmark'),
2544 _(b'BOOKMARK'),
2544 _(b'BOOKMARK'),
2545 ),
2545 ),
2546 (
2546 (
2547 b'o',
2547 b'o',
2548 b'output',
2548 b'output',
2549 b'',
2549 b'',
2550 _(b'print output to file with formatted name'),
2550 _(b'print output to file with formatted name'),
2551 _(b'FORMAT'),
2551 _(b'FORMAT'),
2552 ),
2552 ),
2553 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2553 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2554 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2554 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2555 ]
2555 ]
2556 + diffopts
2556 + diffopts
2557 + formatteropts,
2557 + formatteropts,
2558 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2558 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2559 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2559 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2560 helpbasic=True,
2560 helpbasic=True,
2561 intents={INTENT_READONLY},
2561 intents={INTENT_READONLY},
2562 )
2562 )
2563 def export(ui, repo, *changesets, **opts):
2563 def export(ui, repo, *changesets, **opts):
2564 """dump the header and diffs for one or more changesets
2564 """dump the header and diffs for one or more changesets
2565
2565
2566 Print the changeset header and diffs for one or more revisions.
2566 Print the changeset header and diffs for one or more revisions.
2567 If no revision is given, the parent of the working directory is used.
2567 If no revision is given, the parent of the working directory is used.
2568
2568
2569 The information shown in the changeset header is: author, date,
2569 The information shown in the changeset header is: author, date,
2570 branch name (if non-default), changeset hash, parent(s) and commit
2570 branch name (if non-default), changeset hash, parent(s) and commit
2571 comment.
2571 comment.
2572
2572
2573 .. note::
2573 .. note::
2574
2574
2575 :hg:`export` may generate unexpected diff output for merge
2575 :hg:`export` may generate unexpected diff output for merge
2576 changesets, as it will compare the merge changeset against its
2576 changesets, as it will compare the merge changeset against its
2577 first parent only.
2577 first parent only.
2578
2578
2579 Output may be to a file, in which case the name of the file is
2579 Output may be to a file, in which case the name of the file is
2580 given using a template string. See :hg:`help templates`. In addition
2580 given using a template string. See :hg:`help templates`. In addition
2581 to the common template keywords, the following formatting rules are
2581 to the common template keywords, the following formatting rules are
2582 supported:
2582 supported:
2583
2583
2584 :``%%``: literal "%" character
2584 :``%%``: literal "%" character
2585 :``%H``: changeset hash (40 hexadecimal digits)
2585 :``%H``: changeset hash (40 hexadecimal digits)
2586 :``%N``: number of patches being generated
2586 :``%N``: number of patches being generated
2587 :``%R``: changeset revision number
2587 :``%R``: changeset revision number
2588 :``%b``: basename of the exporting repository
2588 :``%b``: basename of the exporting repository
2589 :``%h``: short-form changeset hash (12 hexadecimal digits)
2589 :``%h``: short-form changeset hash (12 hexadecimal digits)
2590 :``%m``: first line of the commit message (only alphanumeric characters)
2590 :``%m``: first line of the commit message (only alphanumeric characters)
2591 :``%n``: zero-padded sequence number, starting at 1
2591 :``%n``: zero-padded sequence number, starting at 1
2592 :``%r``: zero-padded changeset revision number
2592 :``%r``: zero-padded changeset revision number
2593 :``\\``: literal "\\" character
2593 :``\\``: literal "\\" character
2594
2594
2595 Without the -a/--text option, export will avoid generating diffs
2595 Without the -a/--text option, export will avoid generating diffs
2596 of files it detects as binary. With -a, export will generate a
2596 of files it detects as binary. With -a, export will generate a
2597 diff anyway, probably with undesirable results.
2597 diff anyway, probably with undesirable results.
2598
2598
2599 With -B/--bookmark changesets reachable by the given bookmark are
2599 With -B/--bookmark changesets reachable by the given bookmark are
2600 selected.
2600 selected.
2601
2601
2602 Use the -g/--git option to generate diffs in the git extended diff
2602 Use the -g/--git option to generate diffs in the git extended diff
2603 format. See :hg:`help diffs` for more information.
2603 format. See :hg:`help diffs` for more information.
2604
2604
2605 With the --switch-parent option, the diff will be against the
2605 With the --switch-parent option, the diff will be against the
2606 second parent. It can be useful to review a merge.
2606 second parent. It can be useful to review a merge.
2607
2607
2608 .. container:: verbose
2608 .. container:: verbose
2609
2609
2610 Template:
2610 Template:
2611
2611
2612 The following keywords are supported in addition to the common template
2612 The following keywords are supported in addition to the common template
2613 keywords and functions. See also :hg:`help templates`.
2613 keywords and functions. See also :hg:`help templates`.
2614
2614
2615 :diff: String. Diff content.
2615 :diff: String. Diff content.
2616 :parents: List of strings. Parent nodes of the changeset.
2616 :parents: List of strings. Parent nodes of the changeset.
2617
2617
2618 Examples:
2618 Examples:
2619
2619
2620 - use export and import to transplant a bugfix to the current
2620 - use export and import to transplant a bugfix to the current
2621 branch::
2621 branch::
2622
2622
2623 hg export -r 9353 | hg import -
2623 hg export -r 9353 | hg import -
2624
2624
2625 - export all the changesets between two revisions to a file with
2625 - export all the changesets between two revisions to a file with
2626 rename information::
2626 rename information::
2627
2627
2628 hg export --git -r 123:150 > changes.txt
2628 hg export --git -r 123:150 > changes.txt
2629
2629
2630 - split outgoing changes into a series of patches with
2630 - split outgoing changes into a series of patches with
2631 descriptive names::
2631 descriptive names::
2632
2632
2633 hg export -r "outgoing()" -o "%n-%m.patch"
2633 hg export -r "outgoing()" -o "%n-%m.patch"
2634
2634
2635 Returns 0 on success.
2635 Returns 0 on success.
2636 """
2636 """
2637 opts = pycompat.byteskwargs(opts)
2637 opts = pycompat.byteskwargs(opts)
2638 bookmark = opts.get(b'bookmark')
2638 bookmark = opts.get(b'bookmark')
2639 changesets += tuple(opts.get(b'rev', []))
2639 changesets += tuple(opts.get(b'rev', []))
2640
2640
2641 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2641 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2642
2642
2643 if bookmark:
2643 if bookmark:
2644 if bookmark not in repo._bookmarks:
2644 if bookmark not in repo._bookmarks:
2645 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2645 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2646
2646
2647 revs = scmutil.bookmarkrevs(repo, bookmark)
2647 revs = scmutil.bookmarkrevs(repo, bookmark)
2648 else:
2648 else:
2649 if not changesets:
2649 if not changesets:
2650 changesets = [b'.']
2650 changesets = [b'.']
2651
2651
2652 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2652 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2653 revs = scmutil.revrange(repo, changesets)
2653 revs = scmutil.revrange(repo, changesets)
2654
2654
2655 if not revs:
2655 if not revs:
2656 raise error.Abort(_(b"export requires at least one changeset"))
2656 raise error.Abort(_(b"export requires at least one changeset"))
2657 if len(revs) > 1:
2657 if len(revs) > 1:
2658 ui.note(_(b'exporting patches:\n'))
2658 ui.note(_(b'exporting patches:\n'))
2659 else:
2659 else:
2660 ui.note(_(b'exporting patch:\n'))
2660 ui.note(_(b'exporting patch:\n'))
2661
2661
2662 fntemplate = opts.get(b'output')
2662 fntemplate = opts.get(b'output')
2663 if cmdutil.isstdiofilename(fntemplate):
2663 if cmdutil.isstdiofilename(fntemplate):
2664 fntemplate = b''
2664 fntemplate = b''
2665
2665
2666 if fntemplate:
2666 if fntemplate:
2667 fm = formatter.nullformatter(ui, b'export', opts)
2667 fm = formatter.nullformatter(ui, b'export', opts)
2668 else:
2668 else:
2669 ui.pager(b'export')
2669 ui.pager(b'export')
2670 fm = ui.formatter(b'export', opts)
2670 fm = ui.formatter(b'export', opts)
2671 with fm:
2671 with fm:
2672 cmdutil.export(
2672 cmdutil.export(
2673 repo,
2673 repo,
2674 revs,
2674 revs,
2675 fm,
2675 fm,
2676 fntemplate=fntemplate,
2676 fntemplate=fntemplate,
2677 switch_parent=opts.get(b'switch_parent'),
2677 switch_parent=opts.get(b'switch_parent'),
2678 opts=patch.diffallopts(ui, opts),
2678 opts=patch.diffallopts(ui, opts),
2679 )
2679 )
2680
2680
2681
2681
2682 @command(
2682 @command(
2683 b'files',
2683 b'files',
2684 [
2684 [
2685 (
2685 (
2686 b'r',
2686 b'r',
2687 b'rev',
2687 b'rev',
2688 b'',
2688 b'',
2689 _(b'search the repository as it is in REV'),
2689 _(b'search the repository as it is in REV'),
2690 _(b'REV'),
2690 _(b'REV'),
2691 ),
2691 ),
2692 (
2692 (
2693 b'0',
2693 b'0',
2694 b'print0',
2694 b'print0',
2695 None,
2695 None,
2696 _(b'end filenames with NUL, for use with xargs'),
2696 _(b'end filenames with NUL, for use with xargs'),
2697 ),
2697 ),
2698 ]
2698 ]
2699 + walkopts
2699 + walkopts
2700 + formatteropts
2700 + formatteropts
2701 + subrepoopts,
2701 + subrepoopts,
2702 _(b'[OPTION]... [FILE]...'),
2702 _(b'[OPTION]... [FILE]...'),
2703 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2703 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2704 intents={INTENT_READONLY},
2704 intents={INTENT_READONLY},
2705 )
2705 )
2706 def files(ui, repo, *pats, **opts):
2706 def files(ui, repo, *pats, **opts):
2707 """list tracked files
2707 """list tracked files
2708
2708
2709 Print files under Mercurial control in the working directory or
2709 Print files under Mercurial control in the working directory or
2710 specified revision for given files (excluding removed files).
2710 specified revision for given files (excluding removed files).
2711 Files can be specified as filenames or filesets.
2711 Files can be specified as filenames or filesets.
2712
2712
2713 If no files are given to match, this command prints the names
2713 If no files are given to match, this command prints the names
2714 of all files under Mercurial control.
2714 of all files under Mercurial control.
2715
2715
2716 .. container:: verbose
2716 .. container:: verbose
2717
2717
2718 Template:
2718 Template:
2719
2719
2720 The following keywords are supported in addition to the common template
2720 The following keywords are supported in addition to the common template
2721 keywords and functions. See also :hg:`help templates`.
2721 keywords and functions. See also :hg:`help templates`.
2722
2722
2723 :flags: String. Character denoting file's symlink and executable bits.
2723 :flags: String. Character denoting file's symlink and executable bits.
2724 :path: String. Repository-absolute path of the file.
2724 :path: String. Repository-absolute path of the file.
2725 :size: Integer. Size of the file in bytes.
2725 :size: Integer. Size of the file in bytes.
2726
2726
2727 Examples:
2727 Examples:
2728
2728
2729 - list all files under the current directory::
2729 - list all files under the current directory::
2730
2730
2731 hg files .
2731 hg files .
2732
2732
2733 - shows sizes and flags for current revision::
2733 - shows sizes and flags for current revision::
2734
2734
2735 hg files -vr .
2735 hg files -vr .
2736
2736
2737 - list all files named README::
2737 - list all files named README::
2738
2738
2739 hg files -I "**/README"
2739 hg files -I "**/README"
2740
2740
2741 - list all binary files::
2741 - list all binary files::
2742
2742
2743 hg files "set:binary()"
2743 hg files "set:binary()"
2744
2744
2745 - find files containing a regular expression::
2745 - find files containing a regular expression::
2746
2746
2747 hg files "set:grep('bob')"
2747 hg files "set:grep('bob')"
2748
2748
2749 - search tracked file contents with xargs and grep::
2749 - search tracked file contents with xargs and grep::
2750
2750
2751 hg files -0 | xargs -0 grep foo
2751 hg files -0 | xargs -0 grep foo
2752
2752
2753 See :hg:`help patterns` and :hg:`help filesets` for more information
2753 See :hg:`help patterns` and :hg:`help filesets` for more information
2754 on specifying file patterns.
2754 on specifying file patterns.
2755
2755
2756 Returns 0 if a match is found, 1 otherwise.
2756 Returns 0 if a match is found, 1 otherwise.
2757
2757
2758 """
2758 """
2759
2759
2760 opts = pycompat.byteskwargs(opts)
2760 opts = pycompat.byteskwargs(opts)
2761 rev = opts.get(b'rev')
2761 rev = opts.get(b'rev')
2762 if rev:
2762 if rev:
2763 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2763 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2764 ctx = scmutil.revsingle(repo, rev, None)
2764 ctx = scmutil.revsingle(repo, rev, None)
2765
2765
2766 end = b'\n'
2766 end = b'\n'
2767 if opts.get(b'print0'):
2767 if opts.get(b'print0'):
2768 end = b'\0'
2768 end = b'\0'
2769 fmt = b'%s' + end
2769 fmt = b'%s' + end
2770
2770
2771 m = scmutil.match(ctx, pats, opts)
2771 m = scmutil.match(ctx, pats, opts)
2772 ui.pager(b'files')
2772 ui.pager(b'files')
2773 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2773 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2774 with ui.formatter(b'files', opts) as fm:
2774 with ui.formatter(b'files', opts) as fm:
2775 return cmdutil.files(
2775 return cmdutil.files(
2776 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2776 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2777 )
2777 )
2778
2778
2779
2779
2780 @command(
2780 @command(
2781 b'forget',
2781 b'forget',
2782 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2782 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2783 + walkopts
2783 + walkopts
2784 + dryrunopts,
2784 + dryrunopts,
2785 _(b'[OPTION]... FILE...'),
2785 _(b'[OPTION]... FILE...'),
2786 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2786 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2787 helpbasic=True,
2787 helpbasic=True,
2788 inferrepo=True,
2788 inferrepo=True,
2789 )
2789 )
2790 def forget(ui, repo, *pats, **opts):
2790 def forget(ui, repo, *pats, **opts):
2791 """forget the specified files on the next commit
2791 """forget the specified files on the next commit
2792
2792
2793 Mark the specified files so they will no longer be tracked
2793 Mark the specified files so they will no longer be tracked
2794 after the next commit.
2794 after the next commit.
2795
2795
2796 This only removes files from the current branch, not from the
2796 This only removes files from the current branch, not from the
2797 entire project history, and it does not delete them from the
2797 entire project history, and it does not delete them from the
2798 working directory.
2798 working directory.
2799
2799
2800 To delete the file from the working directory, see :hg:`remove`.
2800 To delete the file from the working directory, see :hg:`remove`.
2801
2801
2802 To undo a forget before the next commit, see :hg:`add`.
2802 To undo a forget before the next commit, see :hg:`add`.
2803
2803
2804 .. container:: verbose
2804 .. container:: verbose
2805
2805
2806 Examples:
2806 Examples:
2807
2807
2808 - forget newly-added binary files::
2808 - forget newly-added binary files::
2809
2809
2810 hg forget "set:added() and binary()"
2810 hg forget "set:added() and binary()"
2811
2811
2812 - forget files that would be excluded by .hgignore::
2812 - forget files that would be excluded by .hgignore::
2813
2813
2814 hg forget "set:hgignore()"
2814 hg forget "set:hgignore()"
2815
2815
2816 Returns 0 on success.
2816 Returns 0 on success.
2817 """
2817 """
2818
2818
2819 opts = pycompat.byteskwargs(opts)
2819 opts = pycompat.byteskwargs(opts)
2820 if not pats:
2820 if not pats:
2821 raise error.Abort(_(b'no files specified'))
2821 raise error.Abort(_(b'no files specified'))
2822
2822
2823 m = scmutil.match(repo[None], pats, opts)
2823 m = scmutil.match(repo[None], pats, opts)
2824 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2824 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2825 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2825 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2826 rejected = cmdutil.forget(
2826 rejected = cmdutil.forget(
2827 ui,
2827 ui,
2828 repo,
2828 repo,
2829 m,
2829 m,
2830 prefix=b"",
2830 prefix=b"",
2831 uipathfn=uipathfn,
2831 uipathfn=uipathfn,
2832 explicitonly=False,
2832 explicitonly=False,
2833 dryrun=dryrun,
2833 dryrun=dryrun,
2834 interactive=interactive,
2834 interactive=interactive,
2835 )[0]
2835 )[0]
2836 return rejected and 1 or 0
2836 return rejected and 1 or 0
2837
2837
2838
2838
2839 @command(
2839 @command(
2840 b'graft',
2840 b'graft',
2841 [
2841 [
2842 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2842 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2843 (
2843 (
2844 b'',
2844 b'',
2845 b'base',
2845 b'base',
2846 b'',
2846 b'',
2847 _(b'base revision when doing the graft merge (ADVANCED)'),
2847 _(b'base revision when doing the graft merge (ADVANCED)'),
2848 _(b'REV'),
2848 _(b'REV'),
2849 ),
2849 ),
2850 (b'c', b'continue', False, _(b'resume interrupted graft')),
2850 (b'c', b'continue', False, _(b'resume interrupted graft')),
2851 (b'', b'stop', False, _(b'stop interrupted graft')),
2851 (b'', b'stop', False, _(b'stop interrupted graft')),
2852 (b'', b'abort', False, _(b'abort interrupted graft')),
2852 (b'', b'abort', False, _(b'abort interrupted graft')),
2853 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2853 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2854 (b'', b'log', None, _(b'append graft info to log message')),
2854 (b'', b'log', None, _(b'append graft info to log message')),
2855 (
2855 (
2856 b'',
2856 b'',
2857 b'no-commit',
2857 b'no-commit',
2858 None,
2858 None,
2859 _(b"don't commit, just apply the changes in working directory"),
2859 _(b"don't commit, just apply the changes in working directory"),
2860 ),
2860 ),
2861 (b'f', b'force', False, _(b'force graft')),
2861 (b'f', b'force', False, _(b'force graft')),
2862 (
2862 (
2863 b'D',
2863 b'D',
2864 b'currentdate',
2864 b'currentdate',
2865 False,
2865 False,
2866 _(b'record the current date as commit date'),
2866 _(b'record the current date as commit date'),
2867 ),
2867 ),
2868 (
2868 (
2869 b'U',
2869 b'U',
2870 b'currentuser',
2870 b'currentuser',
2871 False,
2871 False,
2872 _(b'record the current user as committer'),
2872 _(b'record the current user as committer'),
2873 ),
2873 ),
2874 ]
2874 ]
2875 + commitopts2
2875 + commitopts2
2876 + mergetoolopts
2876 + mergetoolopts
2877 + dryrunopts,
2877 + dryrunopts,
2878 _(b'[OPTION]... [-r REV]... REV...'),
2878 _(b'[OPTION]... [-r REV]... REV...'),
2879 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2879 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2880 )
2880 )
2881 def graft(ui, repo, *revs, **opts):
2881 def graft(ui, repo, *revs, **opts):
2882 '''copy changes from other branches onto the current branch
2882 '''copy changes from other branches onto the current branch
2883
2883
2884 This command uses Mercurial's merge logic to copy individual
2884 This command uses Mercurial's merge logic to copy individual
2885 changes from other branches without merging branches in the
2885 changes from other branches without merging branches in the
2886 history graph. This is sometimes known as 'backporting' or
2886 history graph. This is sometimes known as 'backporting' or
2887 'cherry-picking'. By default, graft will copy user, date, and
2887 'cherry-picking'. By default, graft will copy user, date, and
2888 description from the source changesets.
2888 description from the source changesets.
2889
2889
2890 Changesets that are ancestors of the current revision, that have
2890 Changesets that are ancestors of the current revision, that have
2891 already been grafted, or that are merges will be skipped.
2891 already been grafted, or that are merges will be skipped.
2892
2892
2893 If --log is specified, log messages will have a comment appended
2893 If --log is specified, log messages will have a comment appended
2894 of the form::
2894 of the form::
2895
2895
2896 (grafted from CHANGESETHASH)
2896 (grafted from CHANGESETHASH)
2897
2897
2898 If --force is specified, revisions will be grafted even if they
2898 If --force is specified, revisions will be grafted even if they
2899 are already ancestors of, or have been grafted to, the destination.
2899 are already ancestors of, or have been grafted to, the destination.
2900 This is useful when the revisions have since been backed out.
2900 This is useful when the revisions have since been backed out.
2901
2901
2902 If a graft merge results in conflicts, the graft process is
2902 If a graft merge results in conflicts, the graft process is
2903 interrupted so that the current merge can be manually resolved.
2903 interrupted so that the current merge can be manually resolved.
2904 Once all conflicts are addressed, the graft process can be
2904 Once all conflicts are addressed, the graft process can be
2905 continued with the -c/--continue option.
2905 continued with the -c/--continue option.
2906
2906
2907 The -c/--continue option reapplies all the earlier options.
2907 The -c/--continue option reapplies all the earlier options.
2908
2908
2909 .. container:: verbose
2909 .. container:: verbose
2910
2910
2911 The --base option exposes more of how graft internally uses merge with a
2911 The --base option exposes more of how graft internally uses merge with a
2912 custom base revision. --base can be used to specify another ancestor than
2912 custom base revision. --base can be used to specify another ancestor than
2913 the first and only parent.
2913 the first and only parent.
2914
2914
2915 The command::
2915 The command::
2916
2916
2917 hg graft -r 345 --base 234
2917 hg graft -r 345 --base 234
2918
2918
2919 is thus pretty much the same as::
2919 is thus pretty much the same as::
2920
2920
2921 hg diff -r 234 -r 345 | hg import
2921 hg diff -r 234 -r 345 | hg import
2922
2922
2923 but using merge to resolve conflicts and track moved files.
2923 but using merge to resolve conflicts and track moved files.
2924
2924
2925 The result of a merge can thus be backported as a single commit by
2925 The result of a merge can thus be backported as a single commit by
2926 specifying one of the merge parents as base, and thus effectively
2926 specifying one of the merge parents as base, and thus effectively
2927 grafting the changes from the other side.
2927 grafting the changes from the other side.
2928
2928
2929 It is also possible to collapse multiple changesets and clean up history
2929 It is also possible to collapse multiple changesets and clean up history
2930 by specifying another ancestor as base, much like rebase --collapse
2930 by specifying another ancestor as base, much like rebase --collapse
2931 --keep.
2931 --keep.
2932
2932
2933 The commit message can be tweaked after the fact using commit --amend .
2933 The commit message can be tweaked after the fact using commit --amend .
2934
2934
2935 For using non-ancestors as the base to backout changes, see the backout
2935 For using non-ancestors as the base to backout changes, see the backout
2936 command and the hidden --parent option.
2936 command and the hidden --parent option.
2937
2937
2938 .. container:: verbose
2938 .. container:: verbose
2939
2939
2940 Examples:
2940 Examples:
2941
2941
2942 - copy a single change to the stable branch and edit its description::
2942 - copy a single change to the stable branch and edit its description::
2943
2943
2944 hg update stable
2944 hg update stable
2945 hg graft --edit 9393
2945 hg graft --edit 9393
2946
2946
2947 - graft a range of changesets with one exception, updating dates::
2947 - graft a range of changesets with one exception, updating dates::
2948
2948
2949 hg graft -D "2085::2093 and not 2091"
2949 hg graft -D "2085::2093 and not 2091"
2950
2950
2951 - continue a graft after resolving conflicts::
2951 - continue a graft after resolving conflicts::
2952
2952
2953 hg graft -c
2953 hg graft -c
2954
2954
2955 - show the source of a grafted changeset::
2955 - show the source of a grafted changeset::
2956
2956
2957 hg log --debug -r .
2957 hg log --debug -r .
2958
2958
2959 - show revisions sorted by date::
2959 - show revisions sorted by date::
2960
2960
2961 hg log -r "sort(all(), date)"
2961 hg log -r "sort(all(), date)"
2962
2962
2963 - backport the result of a merge as a single commit::
2963 - backport the result of a merge as a single commit::
2964
2964
2965 hg graft -r 123 --base 123^
2965 hg graft -r 123 --base 123^
2966
2966
2967 - land a feature branch as one changeset::
2967 - land a feature branch as one changeset::
2968
2968
2969 hg up -cr default
2969 hg up -cr default
2970 hg graft -r featureX --base "ancestor('featureX', 'default')"
2970 hg graft -r featureX --base "ancestor('featureX', 'default')"
2971
2971
2972 See :hg:`help revisions` for more about specifying revisions.
2972 See :hg:`help revisions` for more about specifying revisions.
2973
2973
2974 Returns 0 on successful completion, 1 if there are unresolved files.
2974 Returns 0 on successful completion, 1 if there are unresolved files.
2975 '''
2975 '''
2976 with repo.wlock():
2976 with repo.wlock():
2977 return _dograft(ui, repo, *revs, **opts)
2977 return _dograft(ui, repo, *revs, **opts)
2978
2978
2979
2979
2980 def _dograft(ui, repo, *revs, **opts):
2980 def _dograft(ui, repo, *revs, **opts):
2981 opts = pycompat.byteskwargs(opts)
2981 opts = pycompat.byteskwargs(opts)
2982 if revs and opts.get(b'rev'):
2982 if revs and opts.get(b'rev'):
2983 ui.warn(
2983 ui.warn(
2984 _(
2984 _(
2985 b'warning: inconsistent use of --rev might give unexpected '
2985 b'warning: inconsistent use of --rev might give unexpected '
2986 b'revision ordering!\n'
2986 b'revision ordering!\n'
2987 )
2987 )
2988 )
2988 )
2989
2989
2990 revs = list(revs)
2990 revs = list(revs)
2991 revs.extend(opts.get(b'rev'))
2991 revs.extend(opts.get(b'rev'))
2992 # a dict of data to be stored in state file
2992 # a dict of data to be stored in state file
2993 statedata = {}
2993 statedata = {}
2994 # list of new nodes created by ongoing graft
2994 # list of new nodes created by ongoing graft
2995 statedata[b'newnodes'] = []
2995 statedata[b'newnodes'] = []
2996
2996
2997 cmdutil.resolvecommitoptions(ui, opts)
2997 cmdutil.resolvecommitoptions(ui, opts)
2998
2998
2999 editor = cmdutil.getcommiteditor(
2999 editor = cmdutil.getcommiteditor(
3000 editform=b'graft', **pycompat.strkwargs(opts)
3000 editform=b'graft', **pycompat.strkwargs(opts)
3001 )
3001 )
3002
3002
3003 cmdutil.check_at_most_one_arg(opts, b'abort', b'stop', b'continue')
3003 cmdutil.check_at_most_one_arg(opts, b'abort', b'stop', b'continue')
3004
3004
3005 cont = False
3005 cont = False
3006 if opts.get(b'no_commit'):
3006 if opts.get(b'no_commit'):
3007 cmdutil.check_incompatible_arguments(
3007 cmdutil.check_incompatible_arguments(
3008 opts,
3008 opts,
3009 b'no_commit',
3009 b'no_commit',
3010 [b'edit', b'currentuser', b'currentdate', b'log'],
3010 [b'edit', b'currentuser', b'currentdate', b'log'],
3011 )
3011 )
3012
3012
3013 graftstate = statemod.cmdstate(repo, b'graftstate')
3013 graftstate = statemod.cmdstate(repo, b'graftstate')
3014
3014
3015 if opts.get(b'stop'):
3015 if opts.get(b'stop'):
3016 cmdutil.check_incompatible_arguments(
3016 cmdutil.check_incompatible_arguments(
3017 opts,
3017 opts,
3018 b'stop',
3018 b'stop',
3019 [
3019 [
3020 b'edit',
3020 b'edit',
3021 b'log',
3021 b'log',
3022 b'user',
3022 b'user',
3023 b'date',
3023 b'date',
3024 b'currentdate',
3024 b'currentdate',
3025 b'currentuser',
3025 b'currentuser',
3026 b'rev',
3026 b'rev',
3027 ],
3027 ],
3028 )
3028 )
3029 return _stopgraft(ui, repo, graftstate)
3029 return _stopgraft(ui, repo, graftstate)
3030 elif opts.get(b'abort'):
3030 elif opts.get(b'abort'):
3031 cmdutil.check_incompatible_arguments(
3031 cmdutil.check_incompatible_arguments(
3032 opts,
3032 opts,
3033 b'abort',
3033 b'abort',
3034 [
3034 [
3035 b'edit',
3035 b'edit',
3036 b'log',
3036 b'log',
3037 b'user',
3037 b'user',
3038 b'date',
3038 b'date',
3039 b'currentdate',
3039 b'currentdate',
3040 b'currentuser',
3040 b'currentuser',
3041 b'rev',
3041 b'rev',
3042 ],
3042 ],
3043 )
3043 )
3044 return cmdutil.abortgraft(ui, repo, graftstate)
3044 return cmdutil.abortgraft(ui, repo, graftstate)
3045 elif opts.get(b'continue'):
3045 elif opts.get(b'continue'):
3046 cont = True
3046 cont = True
3047 if revs:
3047 if revs:
3048 raise error.Abort(_(b"can't specify --continue and revisions"))
3048 raise error.Abort(_(b"can't specify --continue and revisions"))
3049 # read in unfinished revisions
3049 # read in unfinished revisions
3050 if graftstate.exists():
3050 if graftstate.exists():
3051 statedata = cmdutil.readgraftstate(repo, graftstate)
3051 statedata = cmdutil.readgraftstate(repo, graftstate)
3052 if statedata.get(b'date'):
3052 if statedata.get(b'date'):
3053 opts[b'date'] = statedata[b'date']
3053 opts[b'date'] = statedata[b'date']
3054 if statedata.get(b'user'):
3054 if statedata.get(b'user'):
3055 opts[b'user'] = statedata[b'user']
3055 opts[b'user'] = statedata[b'user']
3056 if statedata.get(b'log'):
3056 if statedata.get(b'log'):
3057 opts[b'log'] = True
3057 opts[b'log'] = True
3058 if statedata.get(b'no_commit'):
3058 if statedata.get(b'no_commit'):
3059 opts[b'no_commit'] = statedata.get(b'no_commit')
3059 opts[b'no_commit'] = statedata.get(b'no_commit')
3060 if statedata.get(b'base'):
3060 if statedata.get(b'base'):
3061 opts[b'base'] = statedata.get(b'base')
3061 opts[b'base'] = statedata.get(b'base')
3062 nodes = statedata[b'nodes']
3062 nodes = statedata[b'nodes']
3063 revs = [repo[node].rev() for node in nodes]
3063 revs = [repo[node].rev() for node in nodes]
3064 else:
3064 else:
3065 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3065 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3066 else:
3066 else:
3067 if not revs:
3067 if not revs:
3068 raise error.Abort(_(b'no revisions specified'))
3068 raise error.Abort(_(b'no revisions specified'))
3069 cmdutil.checkunfinished(repo)
3069 cmdutil.checkunfinished(repo)
3070 cmdutil.bailifchanged(repo)
3070 cmdutil.bailifchanged(repo)
3071 revs = scmutil.revrange(repo, revs)
3071 revs = scmutil.revrange(repo, revs)
3072
3072
3073 skipped = set()
3073 skipped = set()
3074 basectx = None
3074 basectx = None
3075 if opts.get(b'base'):
3075 if opts.get(b'base'):
3076 basectx = scmutil.revsingle(repo, opts[b'base'], None)
3076 basectx = scmutil.revsingle(repo, opts[b'base'], None)
3077 if basectx is None:
3077 if basectx is None:
3078 # check for merges
3078 # check for merges
3079 for rev in repo.revs(b'%ld and merge()', revs):
3079 for rev in repo.revs(b'%ld and merge()', revs):
3080 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3080 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3081 skipped.add(rev)
3081 skipped.add(rev)
3082 revs = [r for r in revs if r not in skipped]
3082 revs = [r for r in revs if r not in skipped]
3083 if not revs:
3083 if not revs:
3084 return -1
3084 return -1
3085 if basectx is not None and len(revs) != 1:
3085 if basectx is not None and len(revs) != 1:
3086 raise error.Abort(_(b'only one revision allowed with --base '))
3086 raise error.Abort(_(b'only one revision allowed with --base '))
3087
3087
3088 # Don't check in the --continue case, in effect retaining --force across
3088 # Don't check in the --continue case, in effect retaining --force across
3089 # --continues. That's because without --force, any revisions we decided to
3089 # --continues. That's because without --force, any revisions we decided to
3090 # skip would have been filtered out here, so they wouldn't have made their
3090 # skip would have been filtered out here, so they wouldn't have made their
3091 # way to the graftstate. With --force, any revisions we would have otherwise
3091 # way to the graftstate. With --force, any revisions we would have otherwise
3092 # skipped would not have been filtered out, and if they hadn't been applied
3092 # skipped would not have been filtered out, and if they hadn't been applied
3093 # already, they'd have been in the graftstate.
3093 # already, they'd have been in the graftstate.
3094 if not (cont or opts.get(b'force')) and basectx is None:
3094 if not (cont or opts.get(b'force')) and basectx is None:
3095 # check for ancestors of dest branch
3095 # check for ancestors of dest branch
3096 ancestors = repo.revs(b'%ld & (::.)', revs)
3096 ancestors = repo.revs(b'%ld & (::.)', revs)
3097 for rev in ancestors:
3097 for rev in ancestors:
3098 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3098 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3099
3099
3100 revs = [r for r in revs if r not in ancestors]
3100 revs = [r for r in revs if r not in ancestors]
3101
3101
3102 if not revs:
3102 if not revs:
3103 return -1
3103 return -1
3104
3104
3105 # analyze revs for earlier grafts
3105 # analyze revs for earlier grafts
3106 ids = {}
3106 ids = {}
3107 for ctx in repo.set(b"%ld", revs):
3107 for ctx in repo.set(b"%ld", revs):
3108 ids[ctx.hex()] = ctx.rev()
3108 ids[ctx.hex()] = ctx.rev()
3109 n = ctx.extra().get(b'source')
3109 n = ctx.extra().get(b'source')
3110 if n:
3110 if n:
3111 ids[n] = ctx.rev()
3111 ids[n] = ctx.rev()
3112
3112
3113 # check ancestors for earlier grafts
3113 # check ancestors for earlier grafts
3114 ui.debug(b'scanning for duplicate grafts\n')
3114 ui.debug(b'scanning for duplicate grafts\n')
3115
3115
3116 # The only changesets we can be sure doesn't contain grafts of any
3116 # The only changesets we can be sure doesn't contain grafts of any
3117 # revs, are the ones that are common ancestors of *all* revs:
3117 # revs, are the ones that are common ancestors of *all* revs:
3118 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3118 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3119 ctx = repo[rev]
3119 ctx = repo[rev]
3120 n = ctx.extra().get(b'source')
3120 n = ctx.extra().get(b'source')
3121 if n in ids:
3121 if n in ids:
3122 try:
3122 try:
3123 r = repo[n].rev()
3123 r = repo[n].rev()
3124 except error.RepoLookupError:
3124 except error.RepoLookupError:
3125 r = None
3125 r = None
3126 if r in revs:
3126 if r in revs:
3127 ui.warn(
3127 ui.warn(
3128 _(
3128 _(
3129 b'skipping revision %d:%s '
3129 b'skipping revision %d:%s '
3130 b'(already grafted to %d:%s)\n'
3130 b'(already grafted to %d:%s)\n'
3131 )
3131 )
3132 % (r, repo[r], rev, ctx)
3132 % (r, repo[r], rev, ctx)
3133 )
3133 )
3134 revs.remove(r)
3134 revs.remove(r)
3135 elif ids[n] in revs:
3135 elif ids[n] in revs:
3136 if r is None:
3136 if r is None:
3137 ui.warn(
3137 ui.warn(
3138 _(
3138 _(
3139 b'skipping already grafted revision %d:%s '
3139 b'skipping already grafted revision %d:%s '
3140 b'(%d:%s also has unknown origin %s)\n'
3140 b'(%d:%s also has unknown origin %s)\n'
3141 )
3141 )
3142 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3142 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3143 )
3143 )
3144 else:
3144 else:
3145 ui.warn(
3145 ui.warn(
3146 _(
3146 _(
3147 b'skipping already grafted revision %d:%s '
3147 b'skipping already grafted revision %d:%s '
3148 b'(%d:%s also has origin %d:%s)\n'
3148 b'(%d:%s also has origin %d:%s)\n'
3149 )
3149 )
3150 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3150 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3151 )
3151 )
3152 revs.remove(ids[n])
3152 revs.remove(ids[n])
3153 elif ctx.hex() in ids:
3153 elif ctx.hex() in ids:
3154 r = ids[ctx.hex()]
3154 r = ids[ctx.hex()]
3155 if r in revs:
3155 if r in revs:
3156 ui.warn(
3156 ui.warn(
3157 _(
3157 _(
3158 b'skipping already grafted revision %d:%s '
3158 b'skipping already grafted revision %d:%s '
3159 b'(was grafted from %d:%s)\n'
3159 b'(was grafted from %d:%s)\n'
3160 )
3160 )
3161 % (r, repo[r], rev, ctx)
3161 % (r, repo[r], rev, ctx)
3162 )
3162 )
3163 revs.remove(r)
3163 revs.remove(r)
3164 if not revs:
3164 if not revs:
3165 return -1
3165 return -1
3166
3166
3167 if opts.get(b'no_commit'):
3167 if opts.get(b'no_commit'):
3168 statedata[b'no_commit'] = True
3168 statedata[b'no_commit'] = True
3169 if opts.get(b'base'):
3169 if opts.get(b'base'):
3170 statedata[b'base'] = opts[b'base']
3170 statedata[b'base'] = opts[b'base']
3171 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3171 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3172 desc = b'%d:%s "%s"' % (
3172 desc = b'%d:%s "%s"' % (
3173 ctx.rev(),
3173 ctx.rev(),
3174 ctx,
3174 ctx,
3175 ctx.description().split(b'\n', 1)[0],
3175 ctx.description().split(b'\n', 1)[0],
3176 )
3176 )
3177 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3177 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3178 if names:
3178 if names:
3179 desc += b' (%s)' % b' '.join(names)
3179 desc += b' (%s)' % b' '.join(names)
3180 ui.status(_(b'grafting %s\n') % desc)
3180 ui.status(_(b'grafting %s\n') % desc)
3181 if opts.get(b'dry_run'):
3181 if opts.get(b'dry_run'):
3182 continue
3182 continue
3183
3183
3184 source = ctx.extra().get(b'source')
3184 source = ctx.extra().get(b'source')
3185 extra = {}
3185 extra = {}
3186 if source:
3186 if source:
3187 extra[b'source'] = source
3187 extra[b'source'] = source
3188 extra[b'intermediate-source'] = ctx.hex()
3188 extra[b'intermediate-source'] = ctx.hex()
3189 else:
3189 else:
3190 extra[b'source'] = ctx.hex()
3190 extra[b'source'] = ctx.hex()
3191 user = ctx.user()
3191 user = ctx.user()
3192 if opts.get(b'user'):
3192 if opts.get(b'user'):
3193 user = opts[b'user']
3193 user = opts[b'user']
3194 statedata[b'user'] = user
3194 statedata[b'user'] = user
3195 date = ctx.date()
3195 date = ctx.date()
3196 if opts.get(b'date'):
3196 if opts.get(b'date'):
3197 date = opts[b'date']
3197 date = opts[b'date']
3198 statedata[b'date'] = date
3198 statedata[b'date'] = date
3199 message = ctx.description()
3199 message = ctx.description()
3200 if opts.get(b'log'):
3200 if opts.get(b'log'):
3201 message += b'\n(grafted from %s)' % ctx.hex()
3201 message += b'\n(grafted from %s)' % ctx.hex()
3202 statedata[b'log'] = True
3202 statedata[b'log'] = True
3203
3203
3204 # we don't merge the first commit when continuing
3204 # we don't merge the first commit when continuing
3205 if not cont:
3205 if not cont:
3206 # perform the graft merge with p1(rev) as 'ancestor'
3206 # perform the graft merge with p1(rev) as 'ancestor'
3207 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3207 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3208 base = ctx.p1() if basectx is None else basectx
3208 base = ctx.p1() if basectx is None else basectx
3209 with ui.configoverride(overrides, b'graft'):
3209 with ui.configoverride(overrides, b'graft'):
3210 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3210 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3211 # report any conflicts
3211 # report any conflicts
3212 if stats.unresolvedcount > 0:
3212 if stats.unresolvedcount > 0:
3213 # write out state for --continue
3213 # write out state for --continue
3214 nodes = [repo[rev].hex() for rev in revs[pos:]]
3214 nodes = [repo[rev].hex() for rev in revs[pos:]]
3215 statedata[b'nodes'] = nodes
3215 statedata[b'nodes'] = nodes
3216 stateversion = 1
3216 stateversion = 1
3217 graftstate.save(stateversion, statedata)
3217 graftstate.save(stateversion, statedata)
3218 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3218 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3219 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3219 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3220 return 1
3220 return 1
3221 else:
3221 else:
3222 cont = False
3222 cont = False
3223
3223
3224 # commit if --no-commit is false
3224 # commit if --no-commit is false
3225 if not opts.get(b'no_commit'):
3225 if not opts.get(b'no_commit'):
3226 node = repo.commit(
3226 node = repo.commit(
3227 text=message, user=user, date=date, extra=extra, editor=editor
3227 text=message, user=user, date=date, extra=extra, editor=editor
3228 )
3228 )
3229 if node is None:
3229 if node is None:
3230 ui.warn(
3230 ui.warn(
3231 _(b'note: graft of %d:%s created no changes to commit\n')
3231 _(b'note: graft of %d:%s created no changes to commit\n')
3232 % (ctx.rev(), ctx)
3232 % (ctx.rev(), ctx)
3233 )
3233 )
3234 # checking that newnodes exist because old state files won't have it
3234 # checking that newnodes exist because old state files won't have it
3235 elif statedata.get(b'newnodes') is not None:
3235 elif statedata.get(b'newnodes') is not None:
3236 statedata[b'newnodes'].append(node)
3236 statedata[b'newnodes'].append(node)
3237
3237
3238 # remove state when we complete successfully
3238 # remove state when we complete successfully
3239 if not opts.get(b'dry_run'):
3239 if not opts.get(b'dry_run'):
3240 graftstate.delete()
3240 graftstate.delete()
3241
3241
3242 return 0
3242 return 0
3243
3243
3244
3244
3245 def _stopgraft(ui, repo, graftstate):
3245 def _stopgraft(ui, repo, graftstate):
3246 """stop the interrupted graft"""
3246 """stop the interrupted graft"""
3247 if not graftstate.exists():
3247 if not graftstate.exists():
3248 raise error.Abort(_(b"no interrupted graft found"))
3248 raise error.Abort(_(b"no interrupted graft found"))
3249 pctx = repo[b'.']
3249 pctx = repo[b'.']
3250 mergemod.clean_update(pctx)
3250 mergemod.clean_update(pctx)
3251 graftstate.delete()
3251 graftstate.delete()
3252 ui.status(_(b"stopped the interrupted graft\n"))
3252 ui.status(_(b"stopped the interrupted graft\n"))
3253 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3253 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3254 return 0
3254 return 0
3255
3255
3256
3256
3257 statemod.addunfinished(
3257 statemod.addunfinished(
3258 b'graft',
3258 b'graft',
3259 fname=b'graftstate',
3259 fname=b'graftstate',
3260 clearable=True,
3260 clearable=True,
3261 stopflag=True,
3261 stopflag=True,
3262 continueflag=True,
3262 continueflag=True,
3263 abortfunc=cmdutil.hgabortgraft,
3263 abortfunc=cmdutil.hgabortgraft,
3264 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3264 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3265 )
3265 )
3266
3266
3267
3267
3268 @command(
3268 @command(
3269 b'grep',
3269 b'grep',
3270 [
3270 [
3271 (b'0', b'print0', None, _(b'end fields with NUL')),
3271 (b'0', b'print0', None, _(b'end fields with NUL')),
3272 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3272 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3273 (
3273 (
3274 b'',
3274 b'',
3275 b'diff',
3275 b'diff',
3276 None,
3276 None,
3277 _(
3277 _(
3278 b'search revision differences for when the pattern was added '
3278 b'search revision differences for when the pattern was added '
3279 b'or removed'
3279 b'or removed'
3280 ),
3280 ),
3281 ),
3281 ),
3282 (b'a', b'text', None, _(b'treat all files as text')),
3282 (b'a', b'text', None, _(b'treat all files as text')),
3283 (
3283 (
3284 b'f',
3284 b'f',
3285 b'follow',
3285 b'follow',
3286 None,
3286 None,
3287 _(
3287 _(
3288 b'follow changeset history,'
3288 b'follow changeset history,'
3289 b' or file history across copies and renames'
3289 b' or file history across copies and renames'
3290 ),
3290 ),
3291 ),
3291 ),
3292 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3292 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3293 (
3293 (
3294 b'l',
3294 b'l',
3295 b'files-with-matches',
3295 b'files-with-matches',
3296 None,
3296 None,
3297 _(b'print only filenames and revisions that match'),
3297 _(b'print only filenames and revisions that match'),
3298 ),
3298 ),
3299 (b'n', b'line-number', None, _(b'print matching line numbers')),
3299 (b'n', b'line-number', None, _(b'print matching line numbers')),
3300 (
3300 (
3301 b'r',
3301 b'r',
3302 b'rev',
3302 b'rev',
3303 [],
3303 [],
3304 _(b'search files changed within revision range'),
3304 _(b'search files changed within revision range'),
3305 _(b'REV'),
3305 _(b'REV'),
3306 ),
3306 ),
3307 (
3307 (
3308 b'',
3308 b'',
3309 b'all-files',
3309 b'all-files',
3310 None,
3310 None,
3311 _(
3311 _(
3312 b'include all files in the changeset while grepping (DEPRECATED)'
3312 b'include all files in the changeset while grepping (DEPRECATED)'
3313 ),
3313 ),
3314 ),
3314 ),
3315 (b'u', b'user', None, _(b'list the author (long with -v)')),
3315 (b'u', b'user', None, _(b'list the author (long with -v)')),
3316 (b'd', b'date', None, _(b'list the date (short with -q)')),
3316 (b'd', b'date', None, _(b'list the date (short with -q)')),
3317 ]
3317 ]
3318 + formatteropts
3318 + formatteropts
3319 + walkopts,
3319 + walkopts,
3320 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3320 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3321 helpcategory=command.CATEGORY_FILE_CONTENTS,
3321 helpcategory=command.CATEGORY_FILE_CONTENTS,
3322 inferrepo=True,
3322 inferrepo=True,
3323 intents={INTENT_READONLY},
3323 intents={INTENT_READONLY},
3324 )
3324 )
3325 def grep(ui, repo, pattern, *pats, **opts):
3325 def grep(ui, repo, pattern, *pats, **opts):
3326 """search for a pattern in specified files
3326 """search for a pattern in specified files
3327
3327
3328 Search the working directory or revision history for a regular
3328 Search the working directory or revision history for a regular
3329 expression in the specified files for the entire repository.
3329 expression in the specified files for the entire repository.
3330
3330
3331 By default, grep searches the repository files in the working
3331 By default, grep searches the repository files in the working
3332 directory and prints the files where it finds a match. To specify
3332 directory and prints the files where it finds a match. To specify
3333 historical revisions instead of the working directory, use the
3333 historical revisions instead of the working directory, use the
3334 --rev flag.
3334 --rev flag.
3335
3335
3336 To search instead historical revision differences that contains a
3336 To search instead historical revision differences that contains a
3337 change in match status ("-" for a match that becomes a non-match,
3337 change in match status ("-" for a match that becomes a non-match,
3338 or "+" for a non-match that becomes a match), use the --diff flag.
3338 or "+" for a non-match that becomes a match), use the --diff flag.
3339
3339
3340 PATTERN can be any Python (roughly Perl-compatible) regular
3340 PATTERN can be any Python (roughly Perl-compatible) regular
3341 expression.
3341 expression.
3342
3342
3343 If no FILEs are specified and the --rev flag isn't supplied, all
3343 If no FILEs are specified and the --rev flag isn't supplied, all
3344 files in the working directory are searched. When using the --rev
3344 files in the working directory are searched. When using the --rev
3345 flag and specifying FILEs, use the --follow argument to also
3345 flag and specifying FILEs, use the --follow argument to also
3346 follow the specified FILEs across renames and copies.
3346 follow the specified FILEs across renames and copies.
3347
3347
3348 .. container:: verbose
3348 .. container:: verbose
3349
3349
3350 Template:
3350 Template:
3351
3351
3352 The following keywords are supported in addition to the common template
3352 The following keywords are supported in addition to the common template
3353 keywords and functions. See also :hg:`help templates`.
3353 keywords and functions. See also :hg:`help templates`.
3354
3354
3355 :change: String. Character denoting insertion ``+`` or removal ``-``.
3355 :change: String. Character denoting insertion ``+`` or removal ``-``.
3356 Available if ``--diff`` is specified.
3356 Available if ``--diff`` is specified.
3357 :lineno: Integer. Line number of the match.
3357 :lineno: Integer. Line number of the match.
3358 :path: String. Repository-absolute path of the file.
3358 :path: String. Repository-absolute path of the file.
3359 :texts: List of text chunks.
3359 :texts: List of text chunks.
3360
3360
3361 And each entry of ``{texts}`` provides the following sub-keywords.
3361 And each entry of ``{texts}`` provides the following sub-keywords.
3362
3362
3363 :matched: Boolean. True if the chunk matches the specified pattern.
3363 :matched: Boolean. True if the chunk matches the specified pattern.
3364 :text: String. Chunk content.
3364 :text: String. Chunk content.
3365
3365
3366 See :hg:`help templates.operators` for the list expansion syntax.
3366 See :hg:`help templates.operators` for the list expansion syntax.
3367
3367
3368 Returns 0 if a match is found, 1 otherwise.
3368 Returns 0 if a match is found, 1 otherwise.
3369
3369
3370 """
3370 """
3371 opts = pycompat.byteskwargs(opts)
3371 opts = pycompat.byteskwargs(opts)
3372 diff = opts.get(b'all') or opts.get(b'diff')
3372 diff = opts.get(b'all') or opts.get(b'diff')
3373 if diff and opts.get(b'all_files'):
3373 if diff and opts.get(b'all_files'):
3374 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3374 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3375 if opts.get(b'all_files') is None and not diff:
3375 if opts.get(b'all_files') is None and not diff:
3376 opts[b'all_files'] = True
3376 opts[b'all_files'] = True
3377 plaingrep = (
3377 plaingrep = (
3378 opts.get(b'all_files')
3378 opts.get(b'all_files')
3379 and not opts.get(b'rev')
3379 and not opts.get(b'rev')
3380 and not opts.get(b'follow')
3380 and not opts.get(b'follow')
3381 )
3381 )
3382 all_files = opts.get(b'all_files')
3382 all_files = opts.get(b'all_files')
3383 if plaingrep:
3383 if plaingrep:
3384 opts[b'rev'] = [b'wdir()']
3384 opts[b'rev'] = [b'wdir()']
3385
3385
3386 reflags = re.M
3386 reflags = re.M
3387 if opts.get(b'ignore_case'):
3387 if opts.get(b'ignore_case'):
3388 reflags |= re.I
3388 reflags |= re.I
3389 try:
3389 try:
3390 regexp = util.re.compile(pattern, reflags)
3390 regexp = util.re.compile(pattern, reflags)
3391 except re.error as inst:
3391 except re.error as inst:
3392 ui.warn(
3392 ui.warn(
3393 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3393 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3394 )
3394 )
3395 return 1
3395 return 1
3396 sep, eol = b':', b'\n'
3396 sep, eol = b':', b'\n'
3397 if opts.get(b'print0'):
3397 if opts.get(b'print0'):
3398 sep = eol = b'\0'
3398 sep = eol = b'\0'
3399
3399
3400 getfile = util.lrucachefunc(repo.file)
3400 getfile = util.lrucachefunc(repo.file)
3401
3401
3402 def matchlines(body):
3402 def matchlines(body):
3403 begin = 0
3403 begin = 0
3404 linenum = 0
3404 linenum = 0
3405 while begin < len(body):
3405 while begin < len(body):
3406 match = regexp.search(body, begin)
3406 match = regexp.search(body, begin)
3407 if not match:
3407 if not match:
3408 break
3408 break
3409 mstart, mend = match.span()
3409 mstart, mend = match.span()
3410 linenum += body.count(b'\n', begin, mstart) + 1
3410 linenum += body.count(b'\n', begin, mstart) + 1
3411 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3411 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3412 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3412 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3413 lend = begin - 1
3413 lend = begin - 1
3414 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3414 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3415
3415
3416 class linestate(object):
3416 class linestate(object):
3417 def __init__(self, line, linenum, colstart, colend):
3417 def __init__(self, line, linenum, colstart, colend):
3418 self.line = line
3418 self.line = line
3419 self.linenum = linenum
3419 self.linenum = linenum
3420 self.colstart = colstart
3420 self.colstart = colstart
3421 self.colend = colend
3421 self.colend = colend
3422
3422
3423 def __hash__(self):
3423 def __hash__(self):
3424 return hash(self.line)
3424 return hash(self.line)
3425
3425
3426 def __eq__(self, other):
3426 def __eq__(self, other):
3427 return self.line == other.line
3427 return self.line == other.line
3428
3428
3429 def findpos(self):
3429 def findpos(self):
3430 """Iterate all (start, end) indices of matches"""
3430 """Iterate all (start, end) indices of matches"""
3431 yield self.colstart, self.colend
3431 yield self.colstart, self.colend
3432 p = self.colend
3432 p = self.colend
3433 while p < len(self.line):
3433 while p < len(self.line):
3434 m = regexp.search(self.line, p)
3434 m = regexp.search(self.line, p)
3435 if not m:
3435 if not m:
3436 break
3436 break
3437 if m.end() == p:
3437 if m.end() == p:
3438 p += 1
3438 p += 1
3439 else:
3439 else:
3440 yield m.span()
3440 yield m.span()
3441 p = m.end()
3441 p = m.end()
3442
3442
3443 matches = {}
3443 matches = {}
3444 copies = {}
3444 copies = {}
3445
3445
3446 def grepbody(fn, rev, body):
3446 def grepbody(fn, rev, body):
3447 matches[rev].setdefault(fn, [])
3447 matches[rev].setdefault(fn, [])
3448 m = matches[rev][fn]
3448 m = matches[rev][fn]
3449 if body is None:
3449 if body is None:
3450 return
3450 return
3451
3451
3452 for lnum, cstart, cend, line in matchlines(body):
3452 for lnum, cstart, cend, line in matchlines(body):
3453 s = linestate(line, lnum, cstart, cend)
3453 s = linestate(line, lnum, cstart, cend)
3454 m.append(s)
3454 m.append(s)
3455
3455
3456 def difflinestates(a, b):
3456 def difflinestates(a, b):
3457 sm = difflib.SequenceMatcher(None, a, b)
3457 sm = difflib.SequenceMatcher(None, a, b)
3458 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3458 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3459 if tag == 'insert':
3459 if tag == 'insert':
3460 for i in pycompat.xrange(blo, bhi):
3460 for i in pycompat.xrange(blo, bhi):
3461 yield (b'+', b[i])
3461 yield (b'+', b[i])
3462 elif tag == 'delete':
3462 elif tag == 'delete':
3463 for i in pycompat.xrange(alo, ahi):
3463 for i in pycompat.xrange(alo, ahi):
3464 yield (b'-', a[i])
3464 yield (b'-', a[i])
3465 elif tag == 'replace':
3465 elif tag == 'replace':
3466 for i in pycompat.xrange(alo, ahi):
3466 for i in pycompat.xrange(alo, ahi):
3467 yield (b'-', a[i])
3467 yield (b'-', a[i])
3468 for i in pycompat.xrange(blo, bhi):
3468 for i in pycompat.xrange(blo, bhi):
3469 yield (b'+', b[i])
3469 yield (b'+', b[i])
3470
3470
3471 uipathfn = scmutil.getuipathfn(repo)
3471 uipathfn = scmutil.getuipathfn(repo)
3472
3472
3473 def display(fm, fn, ctx, pstates, states):
3473 def display(fm, fn, ctx, pstates, states):
3474 rev = scmutil.intrev(ctx)
3474 rev = scmutil.intrev(ctx)
3475 if fm.isplain():
3475 if fm.isplain():
3476 formatuser = ui.shortuser
3476 formatuser = ui.shortuser
3477 else:
3477 else:
3478 formatuser = pycompat.bytestr
3478 formatuser = pycompat.bytestr
3479 if ui.quiet:
3479 if ui.quiet:
3480 datefmt = b'%Y-%m-%d'
3480 datefmt = b'%Y-%m-%d'
3481 else:
3481 else:
3482 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3482 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3483 found = False
3483 found = False
3484
3484
3485 @util.cachefunc
3485 @util.cachefunc
3486 def binary():
3486 def binary():
3487 flog = getfile(fn)
3487 flog = getfile(fn)
3488 try:
3488 try:
3489 return stringutil.binary(flog.read(ctx.filenode(fn)))
3489 return stringutil.binary(flog.read(ctx.filenode(fn)))
3490 except error.WdirUnsupported:
3490 except error.WdirUnsupported:
3491 return ctx[fn].isbinary()
3491 return ctx[fn].isbinary()
3492
3492
3493 fieldnamemap = {b'linenumber': b'lineno'}
3493 fieldnamemap = {b'linenumber': b'lineno'}
3494 if diff:
3494 if diff:
3495 iter = difflinestates(pstates, states)
3495 iter = difflinestates(pstates, states)
3496 else:
3496 else:
3497 iter = [(b'', l) for l in states]
3497 iter = [(b'', l) for l in states]
3498 for change, l in iter:
3498 for change, l in iter:
3499 fm.startitem()
3499 fm.startitem()
3500 fm.context(ctx=ctx)
3500 fm.context(ctx=ctx)
3501 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3501 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3502 fm.plain(uipathfn(fn), label=b'grep.filename')
3502 fm.plain(uipathfn(fn), label=b'grep.filename')
3503
3503
3504 cols = [
3504 cols = [
3505 (b'rev', b'%d', rev, not plaingrep, b''),
3505 (b'rev', b'%d', rev, not plaingrep, b''),
3506 (
3506 (
3507 b'linenumber',
3507 b'linenumber',
3508 b'%d',
3508 b'%d',
3509 l.linenum,
3509 l.linenum,
3510 opts.get(b'line_number'),
3510 opts.get(b'line_number'),
3511 b'',
3511 b'',
3512 ),
3512 ),
3513 ]
3513 ]
3514 if diff:
3514 if diff:
3515 cols.append(
3515 cols.append(
3516 (
3516 (
3517 b'change',
3517 b'change',
3518 b'%s',
3518 b'%s',
3519 change,
3519 change,
3520 True,
3520 True,
3521 b'grep.inserted '
3521 b'grep.inserted '
3522 if change == b'+'
3522 if change == b'+'
3523 else b'grep.deleted ',
3523 else b'grep.deleted ',
3524 )
3524 )
3525 )
3525 )
3526 cols.extend(
3526 cols.extend(
3527 [
3527 [
3528 (
3528 (
3529 b'user',
3529 b'user',
3530 b'%s',
3530 b'%s',
3531 formatuser(ctx.user()),
3531 formatuser(ctx.user()),
3532 opts.get(b'user'),
3532 opts.get(b'user'),
3533 b'',
3533 b'',
3534 ),
3534 ),
3535 (
3535 (
3536 b'date',
3536 b'date',
3537 b'%s',
3537 b'%s',
3538 fm.formatdate(ctx.date(), datefmt),
3538 fm.formatdate(ctx.date(), datefmt),
3539 opts.get(b'date'),
3539 opts.get(b'date'),
3540 b'',
3540 b'',
3541 ),
3541 ),
3542 ]
3542 ]
3543 )
3543 )
3544 for name, fmt, data, cond, extra_label in cols:
3544 for name, fmt, data, cond, extra_label in cols:
3545 if cond:
3545 if cond:
3546 fm.plain(sep, label=b'grep.sep')
3546 fm.plain(sep, label=b'grep.sep')
3547 field = fieldnamemap.get(name, name)
3547 field = fieldnamemap.get(name, name)
3548 label = extra_label + (b'grep.%s' % name)
3548 label = extra_label + (b'grep.%s' % name)
3549 fm.condwrite(cond, field, fmt, data, label=label)
3549 fm.condwrite(cond, field, fmt, data, label=label)
3550 if not opts.get(b'files_with_matches'):
3550 if not opts.get(b'files_with_matches'):
3551 fm.plain(sep, label=b'grep.sep')
3551 fm.plain(sep, label=b'grep.sep')
3552 if not opts.get(b'text') and binary():
3552 if not opts.get(b'text') and binary():
3553 fm.plain(_(b" Binary file matches"))
3553 fm.plain(_(b" Binary file matches"))
3554 else:
3554 else:
3555 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3555 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3556 fm.plain(eol)
3556 fm.plain(eol)
3557 found = True
3557 found = True
3558 if opts.get(b'files_with_matches'):
3558 if opts.get(b'files_with_matches'):
3559 break
3559 break
3560 return found
3560 return found
3561
3561
3562 def displaymatches(fm, l):
3562 def displaymatches(fm, l):
3563 p = 0
3563 p = 0
3564 for s, e in l.findpos():
3564 for s, e in l.findpos():
3565 if p < s:
3565 if p < s:
3566 fm.startitem()
3566 fm.startitem()
3567 fm.write(b'text', b'%s', l.line[p:s])
3567 fm.write(b'text', b'%s', l.line[p:s])
3568 fm.data(matched=False)
3568 fm.data(matched=False)
3569 fm.startitem()
3569 fm.startitem()
3570 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3570 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3571 fm.data(matched=True)
3571 fm.data(matched=True)
3572 p = e
3572 p = e
3573 if p < len(l.line):
3573 if p < len(l.line):
3574 fm.startitem()
3574 fm.startitem()
3575 fm.write(b'text', b'%s', l.line[p:])
3575 fm.write(b'text', b'%s', l.line[p:])
3576 fm.data(matched=False)
3576 fm.data(matched=False)
3577 fm.end()
3577 fm.end()
3578
3578
3579 skip = set()
3579 skip = set()
3580 revfiles = {}
3580 revfiles = {}
3581 match = scmutil.match(repo[None], pats, opts)
3581 match = scmutil.match(repo[None], pats, opts)
3582 found = False
3582 found = False
3583 follow = opts.get(b'follow')
3583 follow = opts.get(b'follow')
3584
3584
3585 getrenamed = scmutil.getrenamedfn(repo)
3585 getrenamed = scmutil.getrenamedfn(repo)
3586
3586
3587 def readfile(ctx, fn):
3587 def readfile(ctx, fn):
3588 rev = ctx.rev()
3588 rev = ctx.rev()
3589 if rev is None:
3589 if rev is None:
3590 fctx = ctx[fn]
3590 fctx = ctx[fn]
3591 try:
3591 try:
3592 return fctx.data()
3592 return fctx.data()
3593 except IOError as e:
3593 except IOError as e:
3594 if e.errno != errno.ENOENT:
3594 if e.errno != errno.ENOENT:
3595 raise
3595 raise
3596 else:
3596 else:
3597 flog = getfile(fn)
3597 flog = getfile(fn)
3598 fnode = ctx.filenode(fn)
3598 fnode = ctx.filenode(fn)
3599 try:
3599 try:
3600 return flog.read(fnode)
3600 return flog.read(fnode)
3601 except error.CensoredNodeError:
3601 except error.CensoredNodeError:
3602 ui.warn(
3602 ui.warn(
3603 _(
3603 _(
3604 b'cannot search in censored file: %(filename)s:%(revnum)s\n'
3604 b'cannot search in censored file: %(filename)s:%(revnum)s\n'
3605 )
3605 )
3606 % {b'filename': fn, b'revnum': pycompat.bytestr(rev),}
3606 % {b'filename': fn, b'revnum': pycompat.bytestr(rev),}
3607 )
3607 )
3608
3608
3609 def prep(ctx, fns):
3609 def prep(ctx, fns):
3610 rev = ctx.rev()
3610 rev = ctx.rev()
3611 pctx = ctx.p1()
3611 pctx = ctx.p1()
3612 matches.setdefault(rev, {})
3612 matches.setdefault(rev, {})
3613 if diff:
3613 if diff:
3614 parent = pctx.rev()
3614 parent = pctx.rev()
3615 matches.setdefault(parent, {})
3615 matches.setdefault(parent, {})
3616 files = revfiles.setdefault(rev, [])
3616 files = revfiles.setdefault(rev, [])
3617 if rev is None:
3617 if rev is None:
3618 # in `hg grep pattern`, 2/3 of the time is spent is spent in
3618 # in `hg grep pattern`, 2/3 of the time is spent is spent in
3619 # pathauditor checks without this in mozilla-central
3619 # pathauditor checks without this in mozilla-central
3620 contextmanager = repo.wvfs.audit.cached
3620 contextmanager = repo.wvfs.audit.cached
3621 else:
3621 else:
3622 contextmanager = util.nullcontextmanager
3622 contextmanager = util.nullcontextmanager
3623 with contextmanager():
3623 with contextmanager():
3624 for fn in fns:
3624 for fn in fns:
3625 # fn might not exist in the revision (could be a file removed by
3625 # fn might not exist in the revision (could be a file removed by
3626 # the revision). We could check `fn not in ctx` even when rev is
3626 # the revision). We could check `fn not in ctx` even when rev is
3627 # None, but it's less racy to protect againt that in readfile.
3627 # None, but it's less racy to protect againt that in readfile.
3628 if rev is not None and fn not in ctx:
3628 if rev is not None and fn not in ctx:
3629 continue
3629 continue
3630
3630
3631 copy = None
3631 copy = None
3632 if follow:
3632 if follow:
3633 copy = getrenamed(fn, rev)
3633 copy = getrenamed(fn, rev)
3634 if copy:
3634 if copy:
3635 copies.setdefault(rev, {})[fn] = copy
3635 copies.setdefault(rev, {})[fn] = copy
3636 if fn in skip:
3636 if fn in skip:
3637 skip.add(copy)
3637 skip.add(copy)
3638 if fn in skip:
3638 if fn in skip:
3639 continue
3639 continue
3640 files.append(fn)
3640 files.append(fn)
3641
3641
3642 if fn not in matches[rev]:
3642 if fn not in matches[rev]:
3643 grepbody(fn, rev, readfile(ctx, fn))
3643 grepbody(fn, rev, readfile(ctx, fn))
3644
3644
3645 if diff:
3645 if diff:
3646 pfn = copy or fn
3646 pfn = copy or fn
3647 if pfn not in matches[parent] and pfn in pctx:
3647 if pfn not in matches[parent] and pfn in pctx:
3648 grepbody(pfn, parent, readfile(pctx, pfn))
3648 grepbody(pfn, parent, readfile(pctx, pfn))
3649
3649
3650 ui.pager(b'grep')
3650 ui.pager(b'grep')
3651 fm = ui.formatter(b'grep', opts)
3651 fm = ui.formatter(b'grep', opts)
3652 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3652 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3653 rev = ctx.rev()
3653 rev = ctx.rev()
3654 parent = ctx.p1().rev()
3654 parent = ctx.p1().rev()
3655 for fn in sorted(revfiles.get(rev, [])):
3655 for fn in sorted(revfiles.get(rev, [])):
3656 states = matches[rev][fn]
3656 states = matches[rev][fn]
3657 copy = copies.get(rev, {}).get(fn)
3657 copy = copies.get(rev, {}).get(fn)
3658 if fn in skip:
3658 if fn in skip:
3659 if copy:
3659 if copy:
3660 skip.add(copy)
3660 skip.add(copy)
3661 continue
3661 continue
3662 pstates = matches.get(parent, {}).get(copy or fn, [])
3662 pstates = matches.get(parent, {}).get(copy or fn, [])
3663 if pstates or states:
3663 if pstates or states:
3664 r = display(fm, fn, ctx, pstates, states)
3664 r = display(fm, fn, ctx, pstates, states)
3665 found = found or r
3665 found = found or r
3666 if r and not diff and not all_files:
3666 if r and not diff and not all_files:
3667 skip.add(fn)
3667 skip.add(fn)
3668 if copy:
3668 if copy:
3669 skip.add(copy)
3669 skip.add(copy)
3670 del revfiles[rev]
3670 del revfiles[rev]
3671 # We will keep the matches dict for the duration of the window
3671 # We will keep the matches dict for the duration of the window
3672 # clear the matches dict once the window is over
3672 # clear the matches dict once the window is over
3673 if not revfiles:
3673 if not revfiles:
3674 matches.clear()
3674 matches.clear()
3675 fm.end()
3675 fm.end()
3676
3676
3677 return not found
3677 return not found
3678
3678
3679
3679
3680 @command(
3680 @command(
3681 b'heads',
3681 b'heads',
3682 [
3682 [
3683 (
3683 (
3684 b'r',
3684 b'r',
3685 b'rev',
3685 b'rev',
3686 b'',
3686 b'',
3687 _(b'show only heads which are descendants of STARTREV'),
3687 _(b'show only heads which are descendants of STARTREV'),
3688 _(b'STARTREV'),
3688 _(b'STARTREV'),
3689 ),
3689 ),
3690 (b't', b'topo', False, _(b'show topological heads only')),
3690 (b't', b'topo', False, _(b'show topological heads only')),
3691 (
3691 (
3692 b'a',
3692 b'a',
3693 b'active',
3693 b'active',
3694 False,
3694 False,
3695 _(b'show active branchheads only (DEPRECATED)'),
3695 _(b'show active branchheads only (DEPRECATED)'),
3696 ),
3696 ),
3697 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3697 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3698 ]
3698 ]
3699 + templateopts,
3699 + templateopts,
3700 _(b'[-ct] [-r STARTREV] [REV]...'),
3700 _(b'[-ct] [-r STARTREV] [REV]...'),
3701 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3701 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3702 intents={INTENT_READONLY},
3702 intents={INTENT_READONLY},
3703 )
3703 )
3704 def heads(ui, repo, *branchrevs, **opts):
3704 def heads(ui, repo, *branchrevs, **opts):
3705 """show branch heads
3705 """show branch heads
3706
3706
3707 With no arguments, show all open branch heads in the repository.
3707 With no arguments, show all open branch heads in the repository.
3708 Branch heads are changesets that have no descendants on the
3708 Branch heads are changesets that have no descendants on the
3709 same branch. They are where development generally takes place and
3709 same branch. They are where development generally takes place and
3710 are the usual targets for update and merge operations.
3710 are the usual targets for update and merge operations.
3711
3711
3712 If one or more REVs are given, only open branch heads on the
3712 If one or more REVs are given, only open branch heads on the
3713 branches associated with the specified changesets are shown. This
3713 branches associated with the specified changesets are shown. This
3714 means that you can use :hg:`heads .` to see the heads on the
3714 means that you can use :hg:`heads .` to see the heads on the
3715 currently checked-out branch.
3715 currently checked-out branch.
3716
3716
3717 If -c/--closed is specified, also show branch heads marked closed
3717 If -c/--closed is specified, also show branch heads marked closed
3718 (see :hg:`commit --close-branch`).
3718 (see :hg:`commit --close-branch`).
3719
3719
3720 If STARTREV is specified, only those heads that are descendants of
3720 If STARTREV is specified, only those heads that are descendants of
3721 STARTREV will be displayed.
3721 STARTREV will be displayed.
3722
3722
3723 If -t/--topo is specified, named branch mechanics will be ignored and only
3723 If -t/--topo is specified, named branch mechanics will be ignored and only
3724 topological heads (changesets with no children) will be shown.
3724 topological heads (changesets with no children) will be shown.
3725
3725
3726 Returns 0 if matching heads are found, 1 if not.
3726 Returns 0 if matching heads are found, 1 if not.
3727 """
3727 """
3728
3728
3729 opts = pycompat.byteskwargs(opts)
3729 opts = pycompat.byteskwargs(opts)
3730 start = None
3730 start = None
3731 rev = opts.get(b'rev')
3731 rev = opts.get(b'rev')
3732 if rev:
3732 if rev:
3733 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3733 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3734 start = scmutil.revsingle(repo, rev, None).node()
3734 start = scmutil.revsingle(repo, rev, None).node()
3735
3735
3736 if opts.get(b'topo'):
3736 if opts.get(b'topo'):
3737 heads = [repo[h] for h in repo.heads(start)]
3737 heads = [repo[h] for h in repo.heads(start)]
3738 else:
3738 else:
3739 heads = []
3739 heads = []
3740 for branch in repo.branchmap():
3740 for branch in repo.branchmap():
3741 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3741 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3742 heads = [repo[h] for h in heads]
3742 heads = [repo[h] for h in heads]
3743
3743
3744 if branchrevs:
3744 if branchrevs:
3745 branches = {
3745 branches = {
3746 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3746 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3747 }
3747 }
3748 heads = [h for h in heads if h.branch() in branches]
3748 heads = [h for h in heads if h.branch() in branches]
3749
3749
3750 if opts.get(b'active') and branchrevs:
3750 if opts.get(b'active') and branchrevs:
3751 dagheads = repo.heads(start)
3751 dagheads = repo.heads(start)
3752 heads = [h for h in heads if h.node() in dagheads]
3752 heads = [h for h in heads if h.node() in dagheads]
3753
3753
3754 if branchrevs:
3754 if branchrevs:
3755 haveheads = {h.branch() for h in heads}
3755 haveheads = {h.branch() for h in heads}
3756 if branches - haveheads:
3756 if branches - haveheads:
3757 headless = b', '.join(b for b in branches - haveheads)
3757 headless = b', '.join(b for b in branches - haveheads)
3758 msg = _(b'no open branch heads found on branches %s')
3758 msg = _(b'no open branch heads found on branches %s')
3759 if opts.get(b'rev'):
3759 if opts.get(b'rev'):
3760 msg += _(b' (started at %s)') % opts[b'rev']
3760 msg += _(b' (started at %s)') % opts[b'rev']
3761 ui.warn((msg + b'\n') % headless)
3761 ui.warn((msg + b'\n') % headless)
3762
3762
3763 if not heads:
3763 if not heads:
3764 return 1
3764 return 1
3765
3765
3766 ui.pager(b'heads')
3766 ui.pager(b'heads')
3767 heads = sorted(heads, key=lambda x: -(x.rev()))
3767 heads = sorted(heads, key=lambda x: -(x.rev()))
3768 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3768 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3769 for ctx in heads:
3769 for ctx in heads:
3770 displayer.show(ctx)
3770 displayer.show(ctx)
3771 displayer.close()
3771 displayer.close()
3772
3772
3773
3773
3774 @command(
3774 @command(
3775 b'help',
3775 b'help',
3776 [
3776 [
3777 (b'e', b'extension', None, _(b'show only help for extensions')),
3777 (b'e', b'extension', None, _(b'show only help for extensions')),
3778 (b'c', b'command', None, _(b'show only help for commands')),
3778 (b'c', b'command', None, _(b'show only help for commands')),
3779 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3779 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3780 (
3780 (
3781 b's',
3781 b's',
3782 b'system',
3782 b'system',
3783 [],
3783 [],
3784 _(b'show help for specific platform(s)'),
3784 _(b'show help for specific platform(s)'),
3785 _(b'PLATFORM'),
3785 _(b'PLATFORM'),
3786 ),
3786 ),
3787 ],
3787 ],
3788 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3788 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3789 helpcategory=command.CATEGORY_HELP,
3789 helpcategory=command.CATEGORY_HELP,
3790 norepo=True,
3790 norepo=True,
3791 intents={INTENT_READONLY},
3791 intents={INTENT_READONLY},
3792 )
3792 )
3793 def help_(ui, name=None, **opts):
3793 def help_(ui, name=None, **opts):
3794 """show help for a given topic or a help overview
3794 """show help for a given topic or a help overview
3795
3795
3796 With no arguments, print a list of commands with short help messages.
3796 With no arguments, print a list of commands with short help messages.
3797
3797
3798 Given a topic, extension, or command name, print help for that
3798 Given a topic, extension, or command name, print help for that
3799 topic.
3799 topic.
3800
3800
3801 Returns 0 if successful.
3801 Returns 0 if successful.
3802 """
3802 """
3803
3803
3804 keep = opts.get('system') or []
3804 keep = opts.get('system') or []
3805 if len(keep) == 0:
3805 if len(keep) == 0:
3806 if pycompat.sysplatform.startswith(b'win'):
3806 if pycompat.sysplatform.startswith(b'win'):
3807 keep.append(b'windows')
3807 keep.append(b'windows')
3808 elif pycompat.sysplatform == b'OpenVMS':
3808 elif pycompat.sysplatform == b'OpenVMS':
3809 keep.append(b'vms')
3809 keep.append(b'vms')
3810 elif pycompat.sysplatform == b'plan9':
3810 elif pycompat.sysplatform == b'plan9':
3811 keep.append(b'plan9')
3811 keep.append(b'plan9')
3812 else:
3812 else:
3813 keep.append(b'unix')
3813 keep.append(b'unix')
3814 keep.append(pycompat.sysplatform.lower())
3814 keep.append(pycompat.sysplatform.lower())
3815 if ui.verbose:
3815 if ui.verbose:
3816 keep.append(b'verbose')
3816 keep.append(b'verbose')
3817
3817
3818 commands = sys.modules[__name__]
3818 commands = sys.modules[__name__]
3819 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3819 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3820 ui.pager(b'help')
3820 ui.pager(b'help')
3821 ui.write(formatted)
3821 ui.write(formatted)
3822
3822
3823
3823
3824 @command(
3824 @command(
3825 b'identify|id',
3825 b'identify|id',
3826 [
3826 [
3827 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3827 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3828 (b'n', b'num', None, _(b'show local revision number')),
3828 (b'n', b'num', None, _(b'show local revision number')),
3829 (b'i', b'id', None, _(b'show global revision id')),
3829 (b'i', b'id', None, _(b'show global revision id')),
3830 (b'b', b'branch', None, _(b'show branch')),
3830 (b'b', b'branch', None, _(b'show branch')),
3831 (b't', b'tags', None, _(b'show tags')),
3831 (b't', b'tags', None, _(b'show tags')),
3832 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3832 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3833 ]
3833 ]
3834 + remoteopts
3834 + remoteopts
3835 + formatteropts,
3835 + formatteropts,
3836 _(b'[-nibtB] [-r REV] [SOURCE]'),
3836 _(b'[-nibtB] [-r REV] [SOURCE]'),
3837 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3837 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3838 optionalrepo=True,
3838 optionalrepo=True,
3839 intents={INTENT_READONLY},
3839 intents={INTENT_READONLY},
3840 )
3840 )
3841 def identify(
3841 def identify(
3842 ui,
3842 ui,
3843 repo,
3843 repo,
3844 source=None,
3844 source=None,
3845 rev=None,
3845 rev=None,
3846 num=None,
3846 num=None,
3847 id=None,
3847 id=None,
3848 branch=None,
3848 branch=None,
3849 tags=None,
3849 tags=None,
3850 bookmarks=None,
3850 bookmarks=None,
3851 **opts
3851 **opts
3852 ):
3852 ):
3853 """identify the working directory or specified revision
3853 """identify the working directory or specified revision
3854
3854
3855 Print a summary identifying the repository state at REV using one or
3855 Print a summary identifying the repository state at REV using one or
3856 two parent hash identifiers, followed by a "+" if the working
3856 two parent hash identifiers, followed by a "+" if the working
3857 directory has uncommitted changes, the branch name (if not default),
3857 directory has uncommitted changes, the branch name (if not default),
3858 a list of tags, and a list of bookmarks.
3858 a list of tags, and a list of bookmarks.
3859
3859
3860 When REV is not given, print a summary of the current state of the
3860 When REV is not given, print a summary of the current state of the
3861 repository including the working directory. Specify -r. to get information
3861 repository including the working directory. Specify -r. to get information
3862 of the working directory parent without scanning uncommitted changes.
3862 of the working directory parent without scanning uncommitted changes.
3863
3863
3864 Specifying a path to a repository root or Mercurial bundle will
3864 Specifying a path to a repository root or Mercurial bundle will
3865 cause lookup to operate on that repository/bundle.
3865 cause lookup to operate on that repository/bundle.
3866
3866
3867 .. container:: verbose
3867 .. container:: verbose
3868
3868
3869 Template:
3869 Template:
3870
3870
3871 The following keywords are supported in addition to the common template
3871 The following keywords are supported in addition to the common template
3872 keywords and functions. See also :hg:`help templates`.
3872 keywords and functions. See also :hg:`help templates`.
3873
3873
3874 :dirty: String. Character ``+`` denoting if the working directory has
3874 :dirty: String. Character ``+`` denoting if the working directory has
3875 uncommitted changes.
3875 uncommitted changes.
3876 :id: String. One or two nodes, optionally followed by ``+``.
3876 :id: String. One or two nodes, optionally followed by ``+``.
3877 :parents: List of strings. Parent nodes of the changeset.
3877 :parents: List of strings. Parent nodes of the changeset.
3878
3878
3879 Examples:
3879 Examples:
3880
3880
3881 - generate a build identifier for the working directory::
3881 - generate a build identifier for the working directory::
3882
3882
3883 hg id --id > build-id.dat
3883 hg id --id > build-id.dat
3884
3884
3885 - find the revision corresponding to a tag::
3885 - find the revision corresponding to a tag::
3886
3886
3887 hg id -n -r 1.3
3887 hg id -n -r 1.3
3888
3888
3889 - check the most recent revision of a remote repository::
3889 - check the most recent revision of a remote repository::
3890
3890
3891 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3891 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3892
3892
3893 See :hg:`log` for generating more information about specific revisions,
3893 See :hg:`log` for generating more information about specific revisions,
3894 including full hash identifiers.
3894 including full hash identifiers.
3895
3895
3896 Returns 0 if successful.
3896 Returns 0 if successful.
3897 """
3897 """
3898
3898
3899 opts = pycompat.byteskwargs(opts)
3899 opts = pycompat.byteskwargs(opts)
3900 if not repo and not source:
3900 if not repo and not source:
3901 raise error.Abort(
3901 raise error.Abort(
3902 _(b"there is no Mercurial repository here (.hg not found)")
3902 _(b"there is no Mercurial repository here (.hg not found)")
3903 )
3903 )
3904
3904
3905 default = not (num or id or branch or tags or bookmarks)
3905 default = not (num or id or branch or tags or bookmarks)
3906 output = []
3906 output = []
3907 revs = []
3907 revs = []
3908
3908
3909 if source:
3909 if source:
3910 source, branches = hg.parseurl(ui.expandpath(source))
3910 source, branches = hg.parseurl(ui.expandpath(source))
3911 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3911 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3912 repo = peer.local()
3912 repo = peer.local()
3913 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3913 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3914
3914
3915 fm = ui.formatter(b'identify', opts)
3915 fm = ui.formatter(b'identify', opts)
3916 fm.startitem()
3916 fm.startitem()
3917
3917
3918 if not repo:
3918 if not repo:
3919 if num or branch or tags:
3919 if num or branch or tags:
3920 raise error.Abort(
3920 raise error.Abort(
3921 _(b"can't query remote revision number, branch, or tags")
3921 _(b"can't query remote revision number, branch, or tags")
3922 )
3922 )
3923 if not rev and revs:
3923 if not rev and revs:
3924 rev = revs[0]
3924 rev = revs[0]
3925 if not rev:
3925 if not rev:
3926 rev = b"tip"
3926 rev = b"tip"
3927
3927
3928 remoterev = peer.lookup(rev)
3928 remoterev = peer.lookup(rev)
3929 hexrev = fm.hexfunc(remoterev)
3929 hexrev = fm.hexfunc(remoterev)
3930 if default or id:
3930 if default or id:
3931 output = [hexrev]
3931 output = [hexrev]
3932 fm.data(id=hexrev)
3932 fm.data(id=hexrev)
3933
3933
3934 @util.cachefunc
3934 @util.cachefunc
3935 def getbms():
3935 def getbms():
3936 bms = []
3936 bms = []
3937
3937
3938 if b'bookmarks' in peer.listkeys(b'namespaces'):
3938 if b'bookmarks' in peer.listkeys(b'namespaces'):
3939 hexremoterev = hex(remoterev)
3939 hexremoterev = hex(remoterev)
3940 bms = [
3940 bms = [
3941 bm
3941 bm
3942 for bm, bmr in pycompat.iteritems(
3942 for bm, bmr in pycompat.iteritems(
3943 peer.listkeys(b'bookmarks')
3943 peer.listkeys(b'bookmarks')
3944 )
3944 )
3945 if bmr == hexremoterev
3945 if bmr == hexremoterev
3946 ]
3946 ]
3947
3947
3948 return sorted(bms)
3948 return sorted(bms)
3949
3949
3950 if fm.isplain():
3950 if fm.isplain():
3951 if bookmarks:
3951 if bookmarks:
3952 output.extend(getbms())
3952 output.extend(getbms())
3953 elif default and not ui.quiet:
3953 elif default and not ui.quiet:
3954 # multiple bookmarks for a single parent separated by '/'
3954 # multiple bookmarks for a single parent separated by '/'
3955 bm = b'/'.join(getbms())
3955 bm = b'/'.join(getbms())
3956 if bm:
3956 if bm:
3957 output.append(bm)
3957 output.append(bm)
3958 else:
3958 else:
3959 fm.data(node=hex(remoterev))
3959 fm.data(node=hex(remoterev))
3960 if bookmarks or b'bookmarks' in fm.datahint():
3960 if bookmarks or b'bookmarks' in fm.datahint():
3961 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3961 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3962 else:
3962 else:
3963 if rev:
3963 if rev:
3964 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3964 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3965 ctx = scmutil.revsingle(repo, rev, None)
3965 ctx = scmutil.revsingle(repo, rev, None)
3966
3966
3967 if ctx.rev() is None:
3967 if ctx.rev() is None:
3968 ctx = repo[None]
3968 ctx = repo[None]
3969 parents = ctx.parents()
3969 parents = ctx.parents()
3970 taglist = []
3970 taglist = []
3971 for p in parents:
3971 for p in parents:
3972 taglist.extend(p.tags())
3972 taglist.extend(p.tags())
3973
3973
3974 dirty = b""
3974 dirty = b""
3975 if ctx.dirty(missing=True, merge=False, branch=False):
3975 if ctx.dirty(missing=True, merge=False, branch=False):
3976 dirty = b'+'
3976 dirty = b'+'
3977 fm.data(dirty=dirty)
3977 fm.data(dirty=dirty)
3978
3978
3979 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3979 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3980 if default or id:
3980 if default or id:
3981 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3981 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3982 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3982 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3983
3983
3984 if num:
3984 if num:
3985 numoutput = [b"%d" % p.rev() for p in parents]
3985 numoutput = [b"%d" % p.rev() for p in parents]
3986 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3986 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3987
3987
3988 fm.data(
3988 fm.data(
3989 parents=fm.formatlist(
3989 parents=fm.formatlist(
3990 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3990 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3991 )
3991 )
3992 )
3992 )
3993 else:
3993 else:
3994 hexoutput = fm.hexfunc(ctx.node())
3994 hexoutput = fm.hexfunc(ctx.node())
3995 if default or id:
3995 if default or id:
3996 output = [hexoutput]
3996 output = [hexoutput]
3997 fm.data(id=hexoutput)
3997 fm.data(id=hexoutput)
3998
3998
3999 if num:
3999 if num:
4000 output.append(pycompat.bytestr(ctx.rev()))
4000 output.append(pycompat.bytestr(ctx.rev()))
4001 taglist = ctx.tags()
4001 taglist = ctx.tags()
4002
4002
4003 if default and not ui.quiet:
4003 if default and not ui.quiet:
4004 b = ctx.branch()
4004 b = ctx.branch()
4005 if b != b'default':
4005 if b != b'default':
4006 output.append(b"(%s)" % b)
4006 output.append(b"(%s)" % b)
4007
4007
4008 # multiple tags for a single parent separated by '/'
4008 # multiple tags for a single parent separated by '/'
4009 t = b'/'.join(taglist)
4009 t = b'/'.join(taglist)
4010 if t:
4010 if t:
4011 output.append(t)
4011 output.append(t)
4012
4012
4013 # multiple bookmarks for a single parent separated by '/'
4013 # multiple bookmarks for a single parent separated by '/'
4014 bm = b'/'.join(ctx.bookmarks())
4014 bm = b'/'.join(ctx.bookmarks())
4015 if bm:
4015 if bm:
4016 output.append(bm)
4016 output.append(bm)
4017 else:
4017 else:
4018 if branch:
4018 if branch:
4019 output.append(ctx.branch())
4019 output.append(ctx.branch())
4020
4020
4021 if tags:
4021 if tags:
4022 output.extend(taglist)
4022 output.extend(taglist)
4023
4023
4024 if bookmarks:
4024 if bookmarks:
4025 output.extend(ctx.bookmarks())
4025 output.extend(ctx.bookmarks())
4026
4026
4027 fm.data(node=ctx.hex())
4027 fm.data(node=ctx.hex())
4028 fm.data(branch=ctx.branch())
4028 fm.data(branch=ctx.branch())
4029 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4029 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4030 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4030 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4031 fm.context(ctx=ctx)
4031 fm.context(ctx=ctx)
4032
4032
4033 fm.plain(b"%s\n" % b' '.join(output))
4033 fm.plain(b"%s\n" % b' '.join(output))
4034 fm.end()
4034 fm.end()
4035
4035
4036
4036
4037 @command(
4037 @command(
4038 b'import|patch',
4038 b'import|patch',
4039 [
4039 [
4040 (
4040 (
4041 b'p',
4041 b'p',
4042 b'strip',
4042 b'strip',
4043 1,
4043 1,
4044 _(
4044 _(
4045 b'directory strip option for patch. This has the same '
4045 b'directory strip option for patch. This has the same '
4046 b'meaning as the corresponding patch option'
4046 b'meaning as the corresponding patch option'
4047 ),
4047 ),
4048 _(b'NUM'),
4048 _(b'NUM'),
4049 ),
4049 ),
4050 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4050 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4051 (b'', b'secret', None, _(b'use the secret phase for committing')),
4051 (b'', b'secret', None, _(b'use the secret phase for committing')),
4052 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4052 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4053 (
4053 (
4054 b'f',
4054 b'f',
4055 b'force',
4055 b'force',
4056 None,
4056 None,
4057 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4057 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4058 ),
4058 ),
4059 (
4059 (
4060 b'',
4060 b'',
4061 b'no-commit',
4061 b'no-commit',
4062 None,
4062 None,
4063 _(b"don't commit, just update the working directory"),
4063 _(b"don't commit, just update the working directory"),
4064 ),
4064 ),
4065 (
4065 (
4066 b'',
4066 b'',
4067 b'bypass',
4067 b'bypass',
4068 None,
4068 None,
4069 _(b"apply patch without touching the working directory"),
4069 _(b"apply patch without touching the working directory"),
4070 ),
4070 ),
4071 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4071 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4072 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4072 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4073 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4073 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4074 (
4074 (
4075 b'',
4075 b'',
4076 b'import-branch',
4076 b'import-branch',
4077 None,
4077 None,
4078 _(b'use any branch information in patch (implied by --exact)'),
4078 _(b'use any branch information in patch (implied by --exact)'),
4079 ),
4079 ),
4080 ]
4080 ]
4081 + commitopts
4081 + commitopts
4082 + commitopts2
4082 + commitopts2
4083 + similarityopts,
4083 + similarityopts,
4084 _(b'[OPTION]... PATCH...'),
4084 _(b'[OPTION]... PATCH...'),
4085 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4085 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4086 )
4086 )
4087 def import_(ui, repo, patch1=None, *patches, **opts):
4087 def import_(ui, repo, patch1=None, *patches, **opts):
4088 """import an ordered set of patches
4088 """import an ordered set of patches
4089
4089
4090 Import a list of patches and commit them individually (unless
4090 Import a list of patches and commit them individually (unless
4091 --no-commit is specified).
4091 --no-commit is specified).
4092
4092
4093 To read a patch from standard input (stdin), use "-" as the patch
4093 To read a patch from standard input (stdin), use "-" as the patch
4094 name. If a URL is specified, the patch will be downloaded from
4094 name. If a URL is specified, the patch will be downloaded from
4095 there.
4095 there.
4096
4096
4097 Import first applies changes to the working directory (unless
4097 Import first applies changes to the working directory (unless
4098 --bypass is specified), import will abort if there are outstanding
4098 --bypass is specified), import will abort if there are outstanding
4099 changes.
4099 changes.
4100
4100
4101 Use --bypass to apply and commit patches directly to the
4101 Use --bypass to apply and commit patches directly to the
4102 repository, without affecting the working directory. Without
4102 repository, without affecting the working directory. Without
4103 --exact, patches will be applied on top of the working directory
4103 --exact, patches will be applied on top of the working directory
4104 parent revision.
4104 parent revision.
4105
4105
4106 You can import a patch straight from a mail message. Even patches
4106 You can import a patch straight from a mail message. Even patches
4107 as attachments work (to use the body part, it must have type
4107 as attachments work (to use the body part, it must have type
4108 text/plain or text/x-patch). From and Subject headers of email
4108 text/plain or text/x-patch). From and Subject headers of email
4109 message are used as default committer and commit message. All
4109 message are used as default committer and commit message. All
4110 text/plain body parts before first diff are added to the commit
4110 text/plain body parts before first diff are added to the commit
4111 message.
4111 message.
4112
4112
4113 If the imported patch was generated by :hg:`export`, user and
4113 If the imported patch was generated by :hg:`export`, user and
4114 description from patch override values from message headers and
4114 description from patch override values from message headers and
4115 body. Values given on command line with -m/--message and -u/--user
4115 body. Values given on command line with -m/--message and -u/--user
4116 override these.
4116 override these.
4117
4117
4118 If --exact is specified, import will set the working directory to
4118 If --exact is specified, import will set the working directory to
4119 the parent of each patch before applying it, and will abort if the
4119 the parent of each patch before applying it, and will abort if the
4120 resulting changeset has a different ID than the one recorded in
4120 resulting changeset has a different ID than the one recorded in
4121 the patch. This will guard against various ways that portable
4121 the patch. This will guard against various ways that portable
4122 patch formats and mail systems might fail to transfer Mercurial
4122 patch formats and mail systems might fail to transfer Mercurial
4123 data or metadata. See :hg:`bundle` for lossless transmission.
4123 data or metadata. See :hg:`bundle` for lossless transmission.
4124
4124
4125 Use --partial to ensure a changeset will be created from the patch
4125 Use --partial to ensure a changeset will be created from the patch
4126 even if some hunks fail to apply. Hunks that fail to apply will be
4126 even if some hunks fail to apply. Hunks that fail to apply will be
4127 written to a <target-file>.rej file. Conflicts can then be resolved
4127 written to a <target-file>.rej file. Conflicts can then be resolved
4128 by hand before :hg:`commit --amend` is run to update the created
4128 by hand before :hg:`commit --amend` is run to update the created
4129 changeset. This flag exists to let people import patches that
4129 changeset. This flag exists to let people import patches that
4130 partially apply without losing the associated metadata (author,
4130 partially apply without losing the associated metadata (author,
4131 date, description, ...).
4131 date, description, ...).
4132
4132
4133 .. note::
4133 .. note::
4134
4134
4135 When no hunks apply cleanly, :hg:`import --partial` will create
4135 When no hunks apply cleanly, :hg:`import --partial` will create
4136 an empty changeset, importing only the patch metadata.
4136 an empty changeset, importing only the patch metadata.
4137
4137
4138 With -s/--similarity, hg will attempt to discover renames and
4138 With -s/--similarity, hg will attempt to discover renames and
4139 copies in the patch in the same way as :hg:`addremove`.
4139 copies in the patch in the same way as :hg:`addremove`.
4140
4140
4141 It is possible to use external patch programs to perform the patch
4141 It is possible to use external patch programs to perform the patch
4142 by setting the ``ui.patch`` configuration option. For the default
4142 by setting the ``ui.patch`` configuration option. For the default
4143 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4143 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4144 See :hg:`help config` for more information about configuration
4144 See :hg:`help config` for more information about configuration
4145 files and how to use these options.
4145 files and how to use these options.
4146
4146
4147 See :hg:`help dates` for a list of formats valid for -d/--date.
4147 See :hg:`help dates` for a list of formats valid for -d/--date.
4148
4148
4149 .. container:: verbose
4149 .. container:: verbose
4150
4150
4151 Examples:
4151 Examples:
4152
4152
4153 - import a traditional patch from a website and detect renames::
4153 - import a traditional patch from a website and detect renames::
4154
4154
4155 hg import -s 80 http://example.com/bugfix.patch
4155 hg import -s 80 http://example.com/bugfix.patch
4156
4156
4157 - import a changeset from an hgweb server::
4157 - import a changeset from an hgweb server::
4158
4158
4159 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4159 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4160
4160
4161 - import all the patches in an Unix-style mbox::
4161 - import all the patches in an Unix-style mbox::
4162
4162
4163 hg import incoming-patches.mbox
4163 hg import incoming-patches.mbox
4164
4164
4165 - import patches from stdin::
4165 - import patches from stdin::
4166
4166
4167 hg import -
4167 hg import -
4168
4168
4169 - attempt to exactly restore an exported changeset (not always
4169 - attempt to exactly restore an exported changeset (not always
4170 possible)::
4170 possible)::
4171
4171
4172 hg import --exact proposed-fix.patch
4172 hg import --exact proposed-fix.patch
4173
4173
4174 - use an external tool to apply a patch which is too fuzzy for
4174 - use an external tool to apply a patch which is too fuzzy for
4175 the default internal tool.
4175 the default internal tool.
4176
4176
4177 hg import --config ui.patch="patch --merge" fuzzy.patch
4177 hg import --config ui.patch="patch --merge" fuzzy.patch
4178
4178
4179 - change the default fuzzing from 2 to a less strict 7
4179 - change the default fuzzing from 2 to a less strict 7
4180
4180
4181 hg import --config ui.fuzz=7 fuzz.patch
4181 hg import --config ui.fuzz=7 fuzz.patch
4182
4182
4183 Returns 0 on success, 1 on partial success (see --partial).
4183 Returns 0 on success, 1 on partial success (see --partial).
4184 """
4184 """
4185
4185
4186 opts = pycompat.byteskwargs(opts)
4186 opts = pycompat.byteskwargs(opts)
4187 if not patch1:
4187 if not patch1:
4188 raise error.Abort(_(b'need at least one patch to import'))
4188 raise error.Abort(_(b'need at least one patch to import'))
4189
4189
4190 patches = (patch1,) + patches
4190 patches = (patch1,) + patches
4191
4191
4192 date = opts.get(b'date')
4192 date = opts.get(b'date')
4193 if date:
4193 if date:
4194 opts[b'date'] = dateutil.parsedate(date)
4194 opts[b'date'] = dateutil.parsedate(date)
4195
4195
4196 exact = opts.get(b'exact')
4196 exact = opts.get(b'exact')
4197 update = not opts.get(b'bypass')
4197 update = not opts.get(b'bypass')
4198 if not update and opts.get(b'no_commit'):
4198 if not update and opts.get(b'no_commit'):
4199 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4199 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4200 if opts.get(b'secret') and opts.get(b'no_commit'):
4200 if opts.get(b'secret') and opts.get(b'no_commit'):
4201 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4201 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4202 try:
4202 try:
4203 sim = float(opts.get(b'similarity') or 0)
4203 sim = float(opts.get(b'similarity') or 0)
4204 except ValueError:
4204 except ValueError:
4205 raise error.Abort(_(b'similarity must be a number'))
4205 raise error.Abort(_(b'similarity must be a number'))
4206 if sim < 0 or sim > 100:
4206 if sim < 0 or sim > 100:
4207 raise error.Abort(_(b'similarity must be between 0 and 100'))
4207 raise error.Abort(_(b'similarity must be between 0 and 100'))
4208 if sim and not update:
4208 if sim and not update:
4209 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4209 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4210 if exact:
4210 if exact:
4211 if opts.get(b'edit'):
4211 if opts.get(b'edit'):
4212 raise error.Abort(_(b'cannot use --exact with --edit'))
4212 raise error.Abort(_(b'cannot use --exact with --edit'))
4213 if opts.get(b'prefix'):
4213 if opts.get(b'prefix'):
4214 raise error.Abort(_(b'cannot use --exact with --prefix'))
4214 raise error.Abort(_(b'cannot use --exact with --prefix'))
4215
4215
4216 base = opts[b"base"]
4216 base = opts[b"base"]
4217 msgs = []
4217 msgs = []
4218 ret = 0
4218 ret = 0
4219
4219
4220 with repo.wlock():
4220 with repo.wlock():
4221 if update:
4221 if update:
4222 cmdutil.checkunfinished(repo)
4222 cmdutil.checkunfinished(repo)
4223 if exact or not opts.get(b'force'):
4223 if exact or not opts.get(b'force'):
4224 cmdutil.bailifchanged(repo)
4224 cmdutil.bailifchanged(repo)
4225
4225
4226 if not opts.get(b'no_commit'):
4226 if not opts.get(b'no_commit'):
4227 lock = repo.lock
4227 lock = repo.lock
4228 tr = lambda: repo.transaction(b'import')
4228 tr = lambda: repo.transaction(b'import')
4229 dsguard = util.nullcontextmanager
4229 dsguard = util.nullcontextmanager
4230 else:
4230 else:
4231 lock = util.nullcontextmanager
4231 lock = util.nullcontextmanager
4232 tr = util.nullcontextmanager
4232 tr = util.nullcontextmanager
4233 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4233 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4234 with lock(), tr(), dsguard():
4234 with lock(), tr(), dsguard():
4235 parents = repo[None].parents()
4235 parents = repo[None].parents()
4236 for patchurl in patches:
4236 for patchurl in patches:
4237 if patchurl == b'-':
4237 if patchurl == b'-':
4238 ui.status(_(b'applying patch from stdin\n'))
4238 ui.status(_(b'applying patch from stdin\n'))
4239 patchfile = ui.fin
4239 patchfile = ui.fin
4240 patchurl = b'stdin' # for error message
4240 patchurl = b'stdin' # for error message
4241 else:
4241 else:
4242 patchurl = os.path.join(base, patchurl)
4242 patchurl = os.path.join(base, patchurl)
4243 ui.status(_(b'applying %s\n') % patchurl)
4243 ui.status(_(b'applying %s\n') % patchurl)
4244 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4244 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4245
4245
4246 haspatch = False
4246 haspatch = False
4247 for hunk in patch.split(patchfile):
4247 for hunk in patch.split(patchfile):
4248 with patch.extract(ui, hunk) as patchdata:
4248 with patch.extract(ui, hunk) as patchdata:
4249 msg, node, rej = cmdutil.tryimportone(
4249 msg, node, rej = cmdutil.tryimportone(
4250 ui, repo, patchdata, parents, opts, msgs, hg.clean
4250 ui, repo, patchdata, parents, opts, msgs, hg.clean
4251 )
4251 )
4252 if msg:
4252 if msg:
4253 haspatch = True
4253 haspatch = True
4254 ui.note(msg + b'\n')
4254 ui.note(msg + b'\n')
4255 if update or exact:
4255 if update or exact:
4256 parents = repo[None].parents()
4256 parents = repo[None].parents()
4257 else:
4257 else:
4258 parents = [repo[node]]
4258 parents = [repo[node]]
4259 if rej:
4259 if rej:
4260 ui.write_err(_(b"patch applied partially\n"))
4260 ui.write_err(_(b"patch applied partially\n"))
4261 ui.write_err(
4261 ui.write_err(
4262 _(
4262 _(
4263 b"(fix the .rej files and run "
4263 b"(fix the .rej files and run "
4264 b"`hg commit --amend`)\n"
4264 b"`hg commit --amend`)\n"
4265 )
4265 )
4266 )
4266 )
4267 ret = 1
4267 ret = 1
4268 break
4268 break
4269
4269
4270 if not haspatch:
4270 if not haspatch:
4271 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4271 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4272
4272
4273 if msgs:
4273 if msgs:
4274 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4274 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4275 return ret
4275 return ret
4276
4276
4277
4277
4278 @command(
4278 @command(
4279 b'incoming|in',
4279 b'incoming|in',
4280 [
4280 [
4281 (
4281 (
4282 b'f',
4282 b'f',
4283 b'force',
4283 b'force',
4284 None,
4284 None,
4285 _(b'run even if remote repository is unrelated'),
4285 _(b'run even if remote repository is unrelated'),
4286 ),
4286 ),
4287 (b'n', b'newest-first', None, _(b'show newest record first')),
4287 (b'n', b'newest-first', None, _(b'show newest record first')),
4288 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4288 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4289 (
4289 (
4290 b'r',
4290 b'r',
4291 b'rev',
4291 b'rev',
4292 [],
4292 [],
4293 _(b'a remote changeset intended to be added'),
4293 _(b'a remote changeset intended to be added'),
4294 _(b'REV'),
4294 _(b'REV'),
4295 ),
4295 ),
4296 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4296 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4297 (
4297 (
4298 b'b',
4298 b'b',
4299 b'branch',
4299 b'branch',
4300 [],
4300 [],
4301 _(b'a specific branch you would like to pull'),
4301 _(b'a specific branch you would like to pull'),
4302 _(b'BRANCH'),
4302 _(b'BRANCH'),
4303 ),
4303 ),
4304 ]
4304 ]
4305 + logopts
4305 + logopts
4306 + remoteopts
4306 + remoteopts
4307 + subrepoopts,
4307 + subrepoopts,
4308 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4308 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4309 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4309 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4310 )
4310 )
4311 def incoming(ui, repo, source=b"default", **opts):
4311 def incoming(ui, repo, source=b"default", **opts):
4312 """show new changesets found in source
4312 """show new changesets found in source
4313
4313
4314 Show new changesets found in the specified path/URL or the default
4314 Show new changesets found in the specified path/URL or the default
4315 pull location. These are the changesets that would have been pulled
4315 pull location. These are the changesets that would have been pulled
4316 by :hg:`pull` at the time you issued this command.
4316 by :hg:`pull` at the time you issued this command.
4317
4317
4318 See pull for valid source format details.
4318 See pull for valid source format details.
4319
4319
4320 .. container:: verbose
4320 .. container:: verbose
4321
4321
4322 With -B/--bookmarks, the result of bookmark comparison between
4322 With -B/--bookmarks, the result of bookmark comparison between
4323 local and remote repositories is displayed. With -v/--verbose,
4323 local and remote repositories is displayed. With -v/--verbose,
4324 status is also displayed for each bookmark like below::
4324 status is also displayed for each bookmark like below::
4325
4325
4326 BM1 01234567890a added
4326 BM1 01234567890a added
4327 BM2 1234567890ab advanced
4327 BM2 1234567890ab advanced
4328 BM3 234567890abc diverged
4328 BM3 234567890abc diverged
4329 BM4 34567890abcd changed
4329 BM4 34567890abcd changed
4330
4330
4331 The action taken locally when pulling depends on the
4331 The action taken locally when pulling depends on the
4332 status of each bookmark:
4332 status of each bookmark:
4333
4333
4334 :``added``: pull will create it
4334 :``added``: pull will create it
4335 :``advanced``: pull will update it
4335 :``advanced``: pull will update it
4336 :``diverged``: pull will create a divergent bookmark
4336 :``diverged``: pull will create a divergent bookmark
4337 :``changed``: result depends on remote changesets
4337 :``changed``: result depends on remote changesets
4338
4338
4339 From the point of view of pulling behavior, bookmark
4339 From the point of view of pulling behavior, bookmark
4340 existing only in the remote repository are treated as ``added``,
4340 existing only in the remote repository are treated as ``added``,
4341 even if it is in fact locally deleted.
4341 even if it is in fact locally deleted.
4342
4342
4343 .. container:: verbose
4343 .. container:: verbose
4344
4344
4345 For remote repository, using --bundle avoids downloading the
4345 For remote repository, using --bundle avoids downloading the
4346 changesets twice if the incoming is followed by a pull.
4346 changesets twice if the incoming is followed by a pull.
4347
4347
4348 Examples:
4348 Examples:
4349
4349
4350 - show incoming changes with patches and full description::
4350 - show incoming changes with patches and full description::
4351
4351
4352 hg incoming -vp
4352 hg incoming -vp
4353
4353
4354 - show incoming changes excluding merges, store a bundle::
4354 - show incoming changes excluding merges, store a bundle::
4355
4355
4356 hg in -vpM --bundle incoming.hg
4356 hg in -vpM --bundle incoming.hg
4357 hg pull incoming.hg
4357 hg pull incoming.hg
4358
4358
4359 - briefly list changes inside a bundle::
4359 - briefly list changes inside a bundle::
4360
4360
4361 hg in changes.hg -T "{desc|firstline}\\n"
4361 hg in changes.hg -T "{desc|firstline}\\n"
4362
4362
4363 Returns 0 if there are incoming changes, 1 otherwise.
4363 Returns 0 if there are incoming changes, 1 otherwise.
4364 """
4364 """
4365 opts = pycompat.byteskwargs(opts)
4365 opts = pycompat.byteskwargs(opts)
4366 if opts.get(b'graph'):
4366 if opts.get(b'graph'):
4367 logcmdutil.checkunsupportedgraphflags([], opts)
4367 logcmdutil.checkunsupportedgraphflags([], opts)
4368
4368
4369 def display(other, chlist, displayer):
4369 def display(other, chlist, displayer):
4370 revdag = logcmdutil.graphrevs(other, chlist, opts)
4370 revdag = logcmdutil.graphrevs(other, chlist, opts)
4371 logcmdutil.displaygraph(
4371 logcmdutil.displaygraph(
4372 ui, repo, revdag, displayer, graphmod.asciiedges
4372 ui, repo, revdag, displayer, graphmod.asciiedges
4373 )
4373 )
4374
4374
4375 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4375 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4376 return 0
4376 return 0
4377
4377
4378 if opts.get(b'bundle') and opts.get(b'subrepos'):
4378 if opts.get(b'bundle') and opts.get(b'subrepos'):
4379 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4379 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4380
4380
4381 if opts.get(b'bookmarks'):
4381 if opts.get(b'bookmarks'):
4382 source, branches = hg.parseurl(
4382 source, branches = hg.parseurl(
4383 ui.expandpath(source), opts.get(b'branch')
4383 ui.expandpath(source), opts.get(b'branch')
4384 )
4384 )
4385 other = hg.peer(repo, opts, source)
4385 other = hg.peer(repo, opts, source)
4386 if b'bookmarks' not in other.listkeys(b'namespaces'):
4386 if b'bookmarks' not in other.listkeys(b'namespaces'):
4387 ui.warn(_(b"remote doesn't support bookmarks\n"))
4387 ui.warn(_(b"remote doesn't support bookmarks\n"))
4388 return 0
4388 return 0
4389 ui.pager(b'incoming')
4389 ui.pager(b'incoming')
4390 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4390 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4391 return bookmarks.incoming(ui, repo, other)
4391 return bookmarks.incoming(ui, repo, other)
4392
4392
4393 repo._subtoppath = ui.expandpath(source)
4393 repo._subtoppath = ui.expandpath(source)
4394 try:
4394 try:
4395 return hg.incoming(ui, repo, source, opts)
4395 return hg.incoming(ui, repo, source, opts)
4396 finally:
4396 finally:
4397 del repo._subtoppath
4397 del repo._subtoppath
4398
4398
4399
4399
4400 @command(
4400 @command(
4401 b'init',
4401 b'init',
4402 remoteopts,
4402 remoteopts,
4403 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4403 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4404 helpcategory=command.CATEGORY_REPO_CREATION,
4404 helpcategory=command.CATEGORY_REPO_CREATION,
4405 helpbasic=True,
4405 helpbasic=True,
4406 norepo=True,
4406 norepo=True,
4407 )
4407 )
4408 def init(ui, dest=b".", **opts):
4408 def init(ui, dest=b".", **opts):
4409 """create a new repository in the given directory
4409 """create a new repository in the given directory
4410
4410
4411 Initialize a new repository in the given directory. If the given
4411 Initialize a new repository in the given directory. If the given
4412 directory does not exist, it will be created.
4412 directory does not exist, it will be created.
4413
4413
4414 If no directory is given, the current directory is used.
4414 If no directory is given, the current directory is used.
4415
4415
4416 It is possible to specify an ``ssh://`` URL as the destination.
4416 It is possible to specify an ``ssh://`` URL as the destination.
4417 See :hg:`help urls` for more information.
4417 See :hg:`help urls` for more information.
4418
4418
4419 Returns 0 on success.
4419 Returns 0 on success.
4420 """
4420 """
4421 opts = pycompat.byteskwargs(opts)
4421 opts = pycompat.byteskwargs(opts)
4422 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4422 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4423
4423
4424
4424
4425 @command(
4425 @command(
4426 b'locate',
4426 b'locate',
4427 [
4427 [
4428 (
4428 (
4429 b'r',
4429 b'r',
4430 b'rev',
4430 b'rev',
4431 b'',
4431 b'',
4432 _(b'search the repository as it is in REV'),
4432 _(b'search the repository as it is in REV'),
4433 _(b'REV'),
4433 _(b'REV'),
4434 ),
4434 ),
4435 (
4435 (
4436 b'0',
4436 b'0',
4437 b'print0',
4437 b'print0',
4438 None,
4438 None,
4439 _(b'end filenames with NUL, for use with xargs'),
4439 _(b'end filenames with NUL, for use with xargs'),
4440 ),
4440 ),
4441 (
4441 (
4442 b'f',
4442 b'f',
4443 b'fullpath',
4443 b'fullpath',
4444 None,
4444 None,
4445 _(b'print complete paths from the filesystem root'),
4445 _(b'print complete paths from the filesystem root'),
4446 ),
4446 ),
4447 ]
4447 ]
4448 + walkopts,
4448 + walkopts,
4449 _(b'[OPTION]... [PATTERN]...'),
4449 _(b'[OPTION]... [PATTERN]...'),
4450 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4450 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4451 )
4451 )
4452 def locate(ui, repo, *pats, **opts):
4452 def locate(ui, repo, *pats, **opts):
4453 """locate files matching specific patterns (DEPRECATED)
4453 """locate files matching specific patterns (DEPRECATED)
4454
4454
4455 Print files under Mercurial control in the working directory whose
4455 Print files under Mercurial control in the working directory whose
4456 names match the given patterns.
4456 names match the given patterns.
4457
4457
4458 By default, this command searches all directories in the working
4458 By default, this command searches all directories in the working
4459 directory. To search just the current directory and its
4459 directory. To search just the current directory and its
4460 subdirectories, use "--include .".
4460 subdirectories, use "--include .".
4461
4461
4462 If no patterns are given to match, this command prints the names
4462 If no patterns are given to match, this command prints the names
4463 of all files under Mercurial control in the working directory.
4463 of all files under Mercurial control in the working directory.
4464
4464
4465 If you want to feed the output of this command into the "xargs"
4465 If you want to feed the output of this command into the "xargs"
4466 command, use the -0 option to both this command and "xargs". This
4466 command, use the -0 option to both this command and "xargs". This
4467 will avoid the problem of "xargs" treating single filenames that
4467 will avoid the problem of "xargs" treating single filenames that
4468 contain whitespace as multiple filenames.
4468 contain whitespace as multiple filenames.
4469
4469
4470 See :hg:`help files` for a more versatile command.
4470 See :hg:`help files` for a more versatile command.
4471
4471
4472 Returns 0 if a match is found, 1 otherwise.
4472 Returns 0 if a match is found, 1 otherwise.
4473 """
4473 """
4474 opts = pycompat.byteskwargs(opts)
4474 opts = pycompat.byteskwargs(opts)
4475 if opts.get(b'print0'):
4475 if opts.get(b'print0'):
4476 end = b'\0'
4476 end = b'\0'
4477 else:
4477 else:
4478 end = b'\n'
4478 end = b'\n'
4479 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4479 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4480
4480
4481 ret = 1
4481 ret = 1
4482 m = scmutil.match(
4482 m = scmutil.match(
4483 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4483 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4484 )
4484 )
4485
4485
4486 ui.pager(b'locate')
4486 ui.pager(b'locate')
4487 if ctx.rev() is None:
4487 if ctx.rev() is None:
4488 # When run on the working copy, "locate" includes removed files, so
4488 # When run on the working copy, "locate" includes removed files, so
4489 # we get the list of files from the dirstate.
4489 # we get the list of files from the dirstate.
4490 filesgen = sorted(repo.dirstate.matches(m))
4490 filesgen = sorted(repo.dirstate.matches(m))
4491 else:
4491 else:
4492 filesgen = ctx.matches(m)
4492 filesgen = ctx.matches(m)
4493 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4493 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4494 for abs in filesgen:
4494 for abs in filesgen:
4495 if opts.get(b'fullpath'):
4495 if opts.get(b'fullpath'):
4496 ui.write(repo.wjoin(abs), end)
4496 ui.write(repo.wjoin(abs), end)
4497 else:
4497 else:
4498 ui.write(uipathfn(abs), end)
4498 ui.write(uipathfn(abs), end)
4499 ret = 0
4499 ret = 0
4500
4500
4501 return ret
4501 return ret
4502
4502
4503
4503
4504 @command(
4504 @command(
4505 b'log|history',
4505 b'log|history',
4506 [
4506 [
4507 (
4507 (
4508 b'f',
4508 b'f',
4509 b'follow',
4509 b'follow',
4510 None,
4510 None,
4511 _(
4511 _(
4512 b'follow changeset history, or file history across copies and renames'
4512 b'follow changeset history, or file history across copies and renames'
4513 ),
4513 ),
4514 ),
4514 ),
4515 (
4515 (
4516 b'',
4516 b'',
4517 b'follow-first',
4517 b'follow-first',
4518 None,
4518 None,
4519 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4519 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4520 ),
4520 ),
4521 (
4521 (
4522 b'd',
4522 b'd',
4523 b'date',
4523 b'date',
4524 b'',
4524 b'',
4525 _(b'show revisions matching date spec'),
4525 _(b'show revisions matching date spec'),
4526 _(b'DATE'),
4526 _(b'DATE'),
4527 ),
4527 ),
4528 (b'C', b'copies', None, _(b'show copied files')),
4528 (b'C', b'copies', None, _(b'show copied files')),
4529 (
4529 (
4530 b'k',
4530 b'k',
4531 b'keyword',
4531 b'keyword',
4532 [],
4532 [],
4533 _(b'do case-insensitive search for a given text'),
4533 _(b'do case-insensitive search for a given text'),
4534 _(b'TEXT'),
4534 _(b'TEXT'),
4535 ),
4535 ),
4536 (
4536 (
4537 b'r',
4537 b'r',
4538 b'rev',
4538 b'rev',
4539 [],
4539 [],
4540 _(b'show the specified revision or revset'),
4540 _(b'show the specified revision or revset'),
4541 _(b'REV'),
4541 _(b'REV'),
4542 ),
4542 ),
4543 (
4543 (
4544 b'L',
4544 b'L',
4545 b'line-range',
4545 b'line-range',
4546 [],
4546 [],
4547 _(b'follow line range of specified file (EXPERIMENTAL)'),
4547 _(b'follow line range of specified file (EXPERIMENTAL)'),
4548 _(b'FILE,RANGE'),
4548 _(b'FILE,RANGE'),
4549 ),
4549 ),
4550 (
4550 (
4551 b'',
4551 b'',
4552 b'removed',
4552 b'removed',
4553 None,
4553 None,
4554 _(b'include revisions where files were removed'),
4554 _(b'include revisions where files were removed'),
4555 ),
4555 ),
4556 (
4556 (
4557 b'm',
4557 b'm',
4558 b'only-merges',
4558 b'only-merges',
4559 None,
4559 None,
4560 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4560 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4561 ),
4561 ),
4562 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4562 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4563 (
4563 (
4564 b'',
4564 b'',
4565 b'only-branch',
4565 b'only-branch',
4566 [],
4566 [],
4567 _(
4567 _(
4568 b'show only changesets within the given named branch (DEPRECATED)'
4568 b'show only changesets within the given named branch (DEPRECATED)'
4569 ),
4569 ),
4570 _(b'BRANCH'),
4570 _(b'BRANCH'),
4571 ),
4571 ),
4572 (
4572 (
4573 b'b',
4573 b'b',
4574 b'branch',
4574 b'branch',
4575 [],
4575 [],
4576 _(b'show changesets within the given named branch'),
4576 _(b'show changesets within the given named branch'),
4577 _(b'BRANCH'),
4577 _(b'BRANCH'),
4578 ),
4578 ),
4579 (
4579 (
4580 b'P',
4580 b'P',
4581 b'prune',
4581 b'prune',
4582 [],
4582 [],
4583 _(b'do not display revision or any of its ancestors'),
4583 _(b'do not display revision or any of its ancestors'),
4584 _(b'REV'),
4584 _(b'REV'),
4585 ),
4585 ),
4586 ]
4586 ]
4587 + logopts
4587 + logopts
4588 + walkopts,
4588 + walkopts,
4589 _(b'[OPTION]... [FILE]'),
4589 _(b'[OPTION]... [FILE]'),
4590 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4590 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4591 helpbasic=True,
4591 helpbasic=True,
4592 inferrepo=True,
4592 inferrepo=True,
4593 intents={INTENT_READONLY},
4593 intents={INTENT_READONLY},
4594 )
4594 )
4595 def log(ui, repo, *pats, **opts):
4595 def log(ui, repo, *pats, **opts):
4596 """show revision history of entire repository or files
4596 """show revision history of entire repository or files
4597
4597
4598 Print the revision history of the specified files or the entire
4598 Print the revision history of the specified files or the entire
4599 project.
4599 project.
4600
4600
4601 If no revision range is specified, the default is ``tip:0`` unless
4601 If no revision range is specified, the default is ``tip:0`` unless
4602 --follow is set, in which case the working directory parent is
4602 --follow is set, in which case the working directory parent is
4603 used as the starting revision.
4603 used as the starting revision.
4604
4604
4605 File history is shown without following rename or copy history of
4605 File history is shown without following rename or copy history of
4606 files. Use -f/--follow with a filename to follow history across
4606 files. Use -f/--follow with a filename to follow history across
4607 renames and copies. --follow without a filename will only show
4607 renames and copies. --follow without a filename will only show
4608 ancestors of the starting revision.
4608 ancestors of the starting revision.
4609
4609
4610 By default this command prints revision number and changeset id,
4610 By default this command prints revision number and changeset id,
4611 tags, non-trivial parents, user, date and time, and a summary for
4611 tags, non-trivial parents, user, date and time, and a summary for
4612 each commit. When the -v/--verbose switch is used, the list of
4612 each commit. When the -v/--verbose switch is used, the list of
4613 changed files and full commit message are shown.
4613 changed files and full commit message are shown.
4614
4614
4615 With --graph the revisions are shown as an ASCII art DAG with the most
4615 With --graph the revisions are shown as an ASCII art DAG with the most
4616 recent changeset at the top.
4616 recent changeset at the top.
4617 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4617 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4618 involved in an unresolved merge conflict, '_' closes a branch,
4618 involved in an unresolved merge conflict, '_' closes a branch,
4619 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4619 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4620 changeset from the lines below is a parent of the 'o' merge on the same
4620 changeset from the lines below is a parent of the 'o' merge on the same
4621 line.
4621 line.
4622 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4622 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4623 of a '|' indicates one or more revisions in a path are omitted.
4623 of a '|' indicates one or more revisions in a path are omitted.
4624
4624
4625 .. container:: verbose
4625 .. container:: verbose
4626
4626
4627 Use -L/--line-range FILE,M:N options to follow the history of lines
4627 Use -L/--line-range FILE,M:N options to follow the history of lines
4628 from M to N in FILE. With -p/--patch only diff hunks affecting
4628 from M to N in FILE. With -p/--patch only diff hunks affecting
4629 specified line range will be shown. This option requires --follow;
4629 specified line range will be shown. This option requires --follow;
4630 it can be specified multiple times. Currently, this option is not
4630 it can be specified multiple times. Currently, this option is not
4631 compatible with --graph. This option is experimental.
4631 compatible with --graph. This option is experimental.
4632
4632
4633 .. note::
4633 .. note::
4634
4634
4635 :hg:`log --patch` may generate unexpected diff output for merge
4635 :hg:`log --patch` may generate unexpected diff output for merge
4636 changesets, as it will only compare the merge changeset against
4636 changesets, as it will only compare the merge changeset against
4637 its first parent. Also, only files different from BOTH parents
4637 its first parent. Also, only files different from BOTH parents
4638 will appear in files:.
4638 will appear in files:.
4639
4639
4640 .. note::
4640 .. note::
4641
4641
4642 For performance reasons, :hg:`log FILE` may omit duplicate changes
4642 For performance reasons, :hg:`log FILE` may omit duplicate changes
4643 made on branches and will not show removals or mode changes. To
4643 made on branches and will not show removals or mode changes. To
4644 see all such changes, use the --removed switch.
4644 see all such changes, use the --removed switch.
4645
4645
4646 .. container:: verbose
4646 .. container:: verbose
4647
4647
4648 .. note::
4648 .. note::
4649
4649
4650 The history resulting from -L/--line-range options depends on diff
4650 The history resulting from -L/--line-range options depends on diff
4651 options; for instance if white-spaces are ignored, respective changes
4651 options; for instance if white-spaces are ignored, respective changes
4652 with only white-spaces in specified line range will not be listed.
4652 with only white-spaces in specified line range will not be listed.
4653
4653
4654 .. container:: verbose
4654 .. container:: verbose
4655
4655
4656 Some examples:
4656 Some examples:
4657
4657
4658 - changesets with full descriptions and file lists::
4658 - changesets with full descriptions and file lists::
4659
4659
4660 hg log -v
4660 hg log -v
4661
4661
4662 - changesets ancestral to the working directory::
4662 - changesets ancestral to the working directory::
4663
4663
4664 hg log -f
4664 hg log -f
4665
4665
4666 - last 10 commits on the current branch::
4666 - last 10 commits on the current branch::
4667
4667
4668 hg log -l 10 -b .
4668 hg log -l 10 -b .
4669
4669
4670 - changesets showing all modifications of a file, including removals::
4670 - changesets showing all modifications of a file, including removals::
4671
4671
4672 hg log --removed file.c
4672 hg log --removed file.c
4673
4673
4674 - all changesets that touch a directory, with diffs, excluding merges::
4674 - all changesets that touch a directory, with diffs, excluding merges::
4675
4675
4676 hg log -Mp lib/
4676 hg log -Mp lib/
4677
4677
4678 - all revision numbers that match a keyword::
4678 - all revision numbers that match a keyword::
4679
4679
4680 hg log -k bug --template "{rev}\\n"
4680 hg log -k bug --template "{rev}\\n"
4681
4681
4682 - the full hash identifier of the working directory parent::
4682 - the full hash identifier of the working directory parent::
4683
4683
4684 hg log -r . --template "{node}\\n"
4684 hg log -r . --template "{node}\\n"
4685
4685
4686 - list available log templates::
4686 - list available log templates::
4687
4687
4688 hg log -T list
4688 hg log -T list
4689
4689
4690 - check if a given changeset is included in a tagged release::
4690 - check if a given changeset is included in a tagged release::
4691
4691
4692 hg log -r "a21ccf and ancestor(1.9)"
4692 hg log -r "a21ccf and ancestor(1.9)"
4693
4693
4694 - find all changesets by some user in a date range::
4694 - find all changesets by some user in a date range::
4695
4695
4696 hg log -k alice -d "may 2008 to jul 2008"
4696 hg log -k alice -d "may 2008 to jul 2008"
4697
4697
4698 - summary of all changesets after the last tag::
4698 - summary of all changesets after the last tag::
4699
4699
4700 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4700 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4701
4701
4702 - changesets touching lines 13 to 23 for file.c::
4702 - changesets touching lines 13 to 23 for file.c::
4703
4703
4704 hg log -L file.c,13:23
4704 hg log -L file.c,13:23
4705
4705
4706 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4706 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4707 main.c with patch::
4707 main.c with patch::
4708
4708
4709 hg log -L file.c,13:23 -L main.c,2:6 -p
4709 hg log -L file.c,13:23 -L main.c,2:6 -p
4710
4710
4711 See :hg:`help dates` for a list of formats valid for -d/--date.
4711 See :hg:`help dates` for a list of formats valid for -d/--date.
4712
4712
4713 See :hg:`help revisions` for more about specifying and ordering
4713 See :hg:`help revisions` for more about specifying and ordering
4714 revisions.
4714 revisions.
4715
4715
4716 See :hg:`help templates` for more about pre-packaged styles and
4716 See :hg:`help templates` for more about pre-packaged styles and
4717 specifying custom templates. The default template used by the log
4717 specifying custom templates. The default template used by the log
4718 command can be customized via the ``ui.logtemplate`` configuration
4718 command can be customized via the ``ui.logtemplate`` configuration
4719 setting.
4719 setting.
4720
4720
4721 Returns 0 on success.
4721 Returns 0 on success.
4722
4722
4723 """
4723 """
4724 opts = pycompat.byteskwargs(opts)
4724 opts = pycompat.byteskwargs(opts)
4725 linerange = opts.get(b'line_range')
4725 linerange = opts.get(b'line_range')
4726
4726
4727 if linerange and not opts.get(b'follow'):
4727 if linerange and not opts.get(b'follow'):
4728 raise error.Abort(_(b'--line-range requires --follow'))
4728 raise error.Abort(_(b'--line-range requires --follow'))
4729
4729
4730 if linerange and pats:
4730 if linerange and pats:
4731 # TODO: take pats as patterns with no line-range filter
4731 # TODO: take pats as patterns with no line-range filter
4732 raise error.Abort(
4732 raise error.Abort(
4733 _(b'FILE arguments are not compatible with --line-range option')
4733 _(b'FILE arguments are not compatible with --line-range option')
4734 )
4734 )
4735
4735
4736 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4736 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4737 revs, differ = logcmdutil.getrevs(repo, pats, opts)
4737 revs, differ = logcmdutil.getrevs(
4738 repo, logcmdutil.parseopts(ui, pats, opts)
4739 )
4738 if linerange:
4740 if linerange:
4739 # TODO: should follow file history from logcmdutil._initialrevs(),
4741 # TODO: should follow file history from logcmdutil._initialrevs(),
4740 # then filter the result by logcmdutil._makerevset() and --limit
4742 # then filter the result by logcmdutil._makerevset() and --limit
4741 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4743 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4742
4744
4743 getcopies = None
4745 getcopies = None
4744 if opts.get(b'copies'):
4746 if opts.get(b'copies'):
4745 endrev = None
4747 endrev = None
4746 if revs:
4748 if revs:
4747 endrev = revs.max() + 1
4749 endrev = revs.max() + 1
4748 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4750 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4749
4751
4750 ui.pager(b'log')
4752 ui.pager(b'log')
4751 displayer = logcmdutil.changesetdisplayer(
4753 displayer = logcmdutil.changesetdisplayer(
4752 ui, repo, opts, differ, buffered=True
4754 ui, repo, opts, differ, buffered=True
4753 )
4755 )
4754 if opts.get(b'graph'):
4756 if opts.get(b'graph'):
4755 displayfn = logcmdutil.displaygraphrevs
4757 displayfn = logcmdutil.displaygraphrevs
4756 else:
4758 else:
4757 displayfn = logcmdutil.displayrevs
4759 displayfn = logcmdutil.displayrevs
4758 displayfn(ui, repo, revs, displayer, getcopies)
4760 displayfn(ui, repo, revs, displayer, getcopies)
4759
4761
4760
4762
4761 @command(
4763 @command(
4762 b'manifest',
4764 b'manifest',
4763 [
4765 [
4764 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4766 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4765 (b'', b'all', False, _(b"list files from all revisions")),
4767 (b'', b'all', False, _(b"list files from all revisions")),
4766 ]
4768 ]
4767 + formatteropts,
4769 + formatteropts,
4768 _(b'[-r REV]'),
4770 _(b'[-r REV]'),
4769 helpcategory=command.CATEGORY_MAINTENANCE,
4771 helpcategory=command.CATEGORY_MAINTENANCE,
4770 intents={INTENT_READONLY},
4772 intents={INTENT_READONLY},
4771 )
4773 )
4772 def manifest(ui, repo, node=None, rev=None, **opts):
4774 def manifest(ui, repo, node=None, rev=None, **opts):
4773 """output the current or given revision of the project manifest
4775 """output the current or given revision of the project manifest
4774
4776
4775 Print a list of version controlled files for the given revision.
4777 Print a list of version controlled files for the given revision.
4776 If no revision is given, the first parent of the working directory
4778 If no revision is given, the first parent of the working directory
4777 is used, or the null revision if no revision is checked out.
4779 is used, or the null revision if no revision is checked out.
4778
4780
4779 With -v, print file permissions, symlink and executable bits.
4781 With -v, print file permissions, symlink and executable bits.
4780 With --debug, print file revision hashes.
4782 With --debug, print file revision hashes.
4781
4783
4782 If option --all is specified, the list of all files from all revisions
4784 If option --all is specified, the list of all files from all revisions
4783 is printed. This includes deleted and renamed files.
4785 is printed. This includes deleted and renamed files.
4784
4786
4785 Returns 0 on success.
4787 Returns 0 on success.
4786 """
4788 """
4787 opts = pycompat.byteskwargs(opts)
4789 opts = pycompat.byteskwargs(opts)
4788 fm = ui.formatter(b'manifest', opts)
4790 fm = ui.formatter(b'manifest', opts)
4789
4791
4790 if opts.get(b'all'):
4792 if opts.get(b'all'):
4791 if rev or node:
4793 if rev or node:
4792 raise error.Abort(_(b"can't specify a revision with --all"))
4794 raise error.Abort(_(b"can't specify a revision with --all"))
4793
4795
4794 res = set()
4796 res = set()
4795 for rev in repo:
4797 for rev in repo:
4796 ctx = repo[rev]
4798 ctx = repo[rev]
4797 res |= set(ctx.files())
4799 res |= set(ctx.files())
4798
4800
4799 ui.pager(b'manifest')
4801 ui.pager(b'manifest')
4800 for f in sorted(res):
4802 for f in sorted(res):
4801 fm.startitem()
4803 fm.startitem()
4802 fm.write(b"path", b'%s\n', f)
4804 fm.write(b"path", b'%s\n', f)
4803 fm.end()
4805 fm.end()
4804 return
4806 return
4805
4807
4806 if rev and node:
4808 if rev and node:
4807 raise error.Abort(_(b"please specify just one revision"))
4809 raise error.Abort(_(b"please specify just one revision"))
4808
4810
4809 if not node:
4811 if not node:
4810 node = rev
4812 node = rev
4811
4813
4812 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4814 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4813 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4815 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4814 if node:
4816 if node:
4815 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4817 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4816 ctx = scmutil.revsingle(repo, node)
4818 ctx = scmutil.revsingle(repo, node)
4817 mf = ctx.manifest()
4819 mf = ctx.manifest()
4818 ui.pager(b'manifest')
4820 ui.pager(b'manifest')
4819 for f in ctx:
4821 for f in ctx:
4820 fm.startitem()
4822 fm.startitem()
4821 fm.context(ctx=ctx)
4823 fm.context(ctx=ctx)
4822 fl = ctx[f].flags()
4824 fl = ctx[f].flags()
4823 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4825 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4824 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4826 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4825 fm.write(b'path', b'%s\n', f)
4827 fm.write(b'path', b'%s\n', f)
4826 fm.end()
4828 fm.end()
4827
4829
4828
4830
4829 @command(
4831 @command(
4830 b'merge',
4832 b'merge',
4831 [
4833 [
4832 (
4834 (
4833 b'f',
4835 b'f',
4834 b'force',
4836 b'force',
4835 None,
4837 None,
4836 _(b'force a merge including outstanding changes (DEPRECATED)'),
4838 _(b'force a merge including outstanding changes (DEPRECATED)'),
4837 ),
4839 ),
4838 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4840 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4839 (
4841 (
4840 b'P',
4842 b'P',
4841 b'preview',
4843 b'preview',
4842 None,
4844 None,
4843 _(b'review revisions to merge (no merge is performed)'),
4845 _(b'review revisions to merge (no merge is performed)'),
4844 ),
4846 ),
4845 (b'', b'abort', None, _(b'abort the ongoing merge')),
4847 (b'', b'abort', None, _(b'abort the ongoing merge')),
4846 ]
4848 ]
4847 + mergetoolopts,
4849 + mergetoolopts,
4848 _(b'[-P] [[-r] REV]'),
4850 _(b'[-P] [[-r] REV]'),
4849 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4851 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4850 helpbasic=True,
4852 helpbasic=True,
4851 )
4853 )
4852 def merge(ui, repo, node=None, **opts):
4854 def merge(ui, repo, node=None, **opts):
4853 """merge another revision into working directory
4855 """merge another revision into working directory
4854
4856
4855 The current working directory is updated with all changes made in
4857 The current working directory is updated with all changes made in
4856 the requested revision since the last common predecessor revision.
4858 the requested revision since the last common predecessor revision.
4857
4859
4858 Files that changed between either parent are marked as changed for
4860 Files that changed between either parent are marked as changed for
4859 the next commit and a commit must be performed before any further
4861 the next commit and a commit must be performed before any further
4860 updates to the repository are allowed. The next commit will have
4862 updates to the repository are allowed. The next commit will have
4861 two parents.
4863 two parents.
4862
4864
4863 ``--tool`` can be used to specify the merge tool used for file
4865 ``--tool`` can be used to specify the merge tool used for file
4864 merges. It overrides the HGMERGE environment variable and your
4866 merges. It overrides the HGMERGE environment variable and your
4865 configuration files. See :hg:`help merge-tools` for options.
4867 configuration files. See :hg:`help merge-tools` for options.
4866
4868
4867 If no revision is specified, the working directory's parent is a
4869 If no revision is specified, the working directory's parent is a
4868 head revision, and the current branch contains exactly one other
4870 head revision, and the current branch contains exactly one other
4869 head, the other head is merged with by default. Otherwise, an
4871 head, the other head is merged with by default. Otherwise, an
4870 explicit revision with which to merge must be provided.
4872 explicit revision with which to merge must be provided.
4871
4873
4872 See :hg:`help resolve` for information on handling file conflicts.
4874 See :hg:`help resolve` for information on handling file conflicts.
4873
4875
4874 To undo an uncommitted merge, use :hg:`merge --abort` which
4876 To undo an uncommitted merge, use :hg:`merge --abort` which
4875 will check out a clean copy of the original merge parent, losing
4877 will check out a clean copy of the original merge parent, losing
4876 all changes.
4878 all changes.
4877
4879
4878 Returns 0 on success, 1 if there are unresolved files.
4880 Returns 0 on success, 1 if there are unresolved files.
4879 """
4881 """
4880
4882
4881 opts = pycompat.byteskwargs(opts)
4883 opts = pycompat.byteskwargs(opts)
4882 abort = opts.get(b'abort')
4884 abort = opts.get(b'abort')
4883 if abort and repo.dirstate.p2() == nullid:
4885 if abort and repo.dirstate.p2() == nullid:
4884 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4886 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4885 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4887 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4886 if abort:
4888 if abort:
4887 state = cmdutil.getunfinishedstate(repo)
4889 state = cmdutil.getunfinishedstate(repo)
4888 if state and state._opname != b'merge':
4890 if state and state._opname != b'merge':
4889 raise error.Abort(
4891 raise error.Abort(
4890 _(b'cannot abort merge with %s in progress') % (state._opname),
4892 _(b'cannot abort merge with %s in progress') % (state._opname),
4891 hint=state.hint(),
4893 hint=state.hint(),
4892 )
4894 )
4893 if node:
4895 if node:
4894 raise error.Abort(_(b"cannot specify a node with --abort"))
4896 raise error.Abort(_(b"cannot specify a node with --abort"))
4895 return hg.abortmerge(repo.ui, repo)
4897 return hg.abortmerge(repo.ui, repo)
4896
4898
4897 if opts.get(b'rev') and node:
4899 if opts.get(b'rev') and node:
4898 raise error.Abort(_(b"please specify just one revision"))
4900 raise error.Abort(_(b"please specify just one revision"))
4899 if not node:
4901 if not node:
4900 node = opts.get(b'rev')
4902 node = opts.get(b'rev')
4901
4903
4902 if node:
4904 if node:
4903 ctx = scmutil.revsingle(repo, node)
4905 ctx = scmutil.revsingle(repo, node)
4904 else:
4906 else:
4905 if ui.configbool(b'commands', b'merge.require-rev'):
4907 if ui.configbool(b'commands', b'merge.require-rev'):
4906 raise error.Abort(
4908 raise error.Abort(
4907 _(
4909 _(
4908 b'configuration requires specifying revision to merge '
4910 b'configuration requires specifying revision to merge '
4909 b'with'
4911 b'with'
4910 )
4912 )
4911 )
4913 )
4912 ctx = repo[destutil.destmerge(repo)]
4914 ctx = repo[destutil.destmerge(repo)]
4913
4915
4914 if ctx.node() is None:
4916 if ctx.node() is None:
4915 raise error.Abort(_(b'merging with the working copy has no effect'))
4917 raise error.Abort(_(b'merging with the working copy has no effect'))
4916
4918
4917 if opts.get(b'preview'):
4919 if opts.get(b'preview'):
4918 # find nodes that are ancestors of p2 but not of p1
4920 # find nodes that are ancestors of p2 but not of p1
4919 p1 = repo[b'.'].node()
4921 p1 = repo[b'.'].node()
4920 p2 = ctx.node()
4922 p2 = ctx.node()
4921 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4923 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4922
4924
4923 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4925 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4924 for node in nodes:
4926 for node in nodes:
4925 displayer.show(repo[node])
4927 displayer.show(repo[node])
4926 displayer.close()
4928 displayer.close()
4927 return 0
4929 return 0
4928
4930
4929 # ui.forcemerge is an internal variable, do not document
4931 # ui.forcemerge is an internal variable, do not document
4930 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4932 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4931 with ui.configoverride(overrides, b'merge'):
4933 with ui.configoverride(overrides, b'merge'):
4932 force = opts.get(b'force')
4934 force = opts.get(b'force')
4933 labels = [b'working copy', b'merge rev']
4935 labels = [b'working copy', b'merge rev']
4934 return hg.merge(ctx, force=force, labels=labels)
4936 return hg.merge(ctx, force=force, labels=labels)
4935
4937
4936
4938
4937 statemod.addunfinished(
4939 statemod.addunfinished(
4938 b'merge',
4940 b'merge',
4939 fname=None,
4941 fname=None,
4940 clearable=True,
4942 clearable=True,
4941 allowcommit=True,
4943 allowcommit=True,
4942 cmdmsg=_(b'outstanding uncommitted merge'),
4944 cmdmsg=_(b'outstanding uncommitted merge'),
4943 abortfunc=hg.abortmerge,
4945 abortfunc=hg.abortmerge,
4944 statushint=_(
4946 statushint=_(
4945 b'To continue: hg commit\nTo abort: hg merge --abort'
4947 b'To continue: hg commit\nTo abort: hg merge --abort'
4946 ),
4948 ),
4947 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4949 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4948 )
4950 )
4949
4951
4950
4952
4951 @command(
4953 @command(
4952 b'outgoing|out',
4954 b'outgoing|out',
4953 [
4955 [
4954 (
4956 (
4955 b'f',
4957 b'f',
4956 b'force',
4958 b'force',
4957 None,
4959 None,
4958 _(b'run even when the destination is unrelated'),
4960 _(b'run even when the destination is unrelated'),
4959 ),
4961 ),
4960 (
4962 (
4961 b'r',
4963 b'r',
4962 b'rev',
4964 b'rev',
4963 [],
4965 [],
4964 _(b'a changeset intended to be included in the destination'),
4966 _(b'a changeset intended to be included in the destination'),
4965 _(b'REV'),
4967 _(b'REV'),
4966 ),
4968 ),
4967 (b'n', b'newest-first', None, _(b'show newest record first')),
4969 (b'n', b'newest-first', None, _(b'show newest record first')),
4968 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4970 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4969 (
4971 (
4970 b'b',
4972 b'b',
4971 b'branch',
4973 b'branch',
4972 [],
4974 [],
4973 _(b'a specific branch you would like to push'),
4975 _(b'a specific branch you would like to push'),
4974 _(b'BRANCH'),
4976 _(b'BRANCH'),
4975 ),
4977 ),
4976 ]
4978 ]
4977 + logopts
4979 + logopts
4978 + remoteopts
4980 + remoteopts
4979 + subrepoopts,
4981 + subrepoopts,
4980 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4982 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4981 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4983 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4982 )
4984 )
4983 def outgoing(ui, repo, dest=None, **opts):
4985 def outgoing(ui, repo, dest=None, **opts):
4984 """show changesets not found in the destination
4986 """show changesets not found in the destination
4985
4987
4986 Show changesets not found in the specified destination repository
4988 Show changesets not found in the specified destination repository
4987 or the default push location. These are the changesets that would
4989 or the default push location. These are the changesets that would
4988 be pushed if a push was requested.
4990 be pushed if a push was requested.
4989
4991
4990 See pull for details of valid destination formats.
4992 See pull for details of valid destination formats.
4991
4993
4992 .. container:: verbose
4994 .. container:: verbose
4993
4995
4994 With -B/--bookmarks, the result of bookmark comparison between
4996 With -B/--bookmarks, the result of bookmark comparison between
4995 local and remote repositories is displayed. With -v/--verbose,
4997 local and remote repositories is displayed. With -v/--verbose,
4996 status is also displayed for each bookmark like below::
4998 status is also displayed for each bookmark like below::
4997
4999
4998 BM1 01234567890a added
5000 BM1 01234567890a added
4999 BM2 deleted
5001 BM2 deleted
5000 BM3 234567890abc advanced
5002 BM3 234567890abc advanced
5001 BM4 34567890abcd diverged
5003 BM4 34567890abcd diverged
5002 BM5 4567890abcde changed
5004 BM5 4567890abcde changed
5003
5005
5004 The action taken when pushing depends on the
5006 The action taken when pushing depends on the
5005 status of each bookmark:
5007 status of each bookmark:
5006
5008
5007 :``added``: push with ``-B`` will create it
5009 :``added``: push with ``-B`` will create it
5008 :``deleted``: push with ``-B`` will delete it
5010 :``deleted``: push with ``-B`` will delete it
5009 :``advanced``: push will update it
5011 :``advanced``: push will update it
5010 :``diverged``: push with ``-B`` will update it
5012 :``diverged``: push with ``-B`` will update it
5011 :``changed``: push with ``-B`` will update it
5013 :``changed``: push with ``-B`` will update it
5012
5014
5013 From the point of view of pushing behavior, bookmarks
5015 From the point of view of pushing behavior, bookmarks
5014 existing only in the remote repository are treated as
5016 existing only in the remote repository are treated as
5015 ``deleted``, even if it is in fact added remotely.
5017 ``deleted``, even if it is in fact added remotely.
5016
5018
5017 Returns 0 if there are outgoing changes, 1 otherwise.
5019 Returns 0 if there are outgoing changes, 1 otherwise.
5018 """
5020 """
5019 # hg._outgoing() needs to re-resolve the path in order to handle #branch
5021 # hg._outgoing() needs to re-resolve the path in order to handle #branch
5020 # style URLs, so don't overwrite dest.
5022 # style URLs, so don't overwrite dest.
5021 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5023 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5022 if not path:
5024 if not path:
5023 raise error.Abort(
5025 raise error.Abort(
5024 _(b'default repository not configured!'),
5026 _(b'default repository not configured!'),
5025 hint=_(b"see 'hg help config.paths'"),
5027 hint=_(b"see 'hg help config.paths'"),
5026 )
5028 )
5027
5029
5028 opts = pycompat.byteskwargs(opts)
5030 opts = pycompat.byteskwargs(opts)
5029 if opts.get(b'graph'):
5031 if opts.get(b'graph'):
5030 logcmdutil.checkunsupportedgraphflags([], opts)
5032 logcmdutil.checkunsupportedgraphflags([], opts)
5031 o, other = hg._outgoing(ui, repo, dest, opts)
5033 o, other = hg._outgoing(ui, repo, dest, opts)
5032 if not o:
5034 if not o:
5033 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5035 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5034 return
5036 return
5035
5037
5036 revdag = logcmdutil.graphrevs(repo, o, opts)
5038 revdag = logcmdutil.graphrevs(repo, o, opts)
5037 ui.pager(b'outgoing')
5039 ui.pager(b'outgoing')
5038 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5040 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5039 logcmdutil.displaygraph(
5041 logcmdutil.displaygraph(
5040 ui, repo, revdag, displayer, graphmod.asciiedges
5042 ui, repo, revdag, displayer, graphmod.asciiedges
5041 )
5043 )
5042 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5044 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5043 return 0
5045 return 0
5044
5046
5045 if opts.get(b'bookmarks'):
5047 if opts.get(b'bookmarks'):
5046 dest = path.pushloc or path.loc
5048 dest = path.pushloc or path.loc
5047 other = hg.peer(repo, opts, dest)
5049 other = hg.peer(repo, opts, dest)
5048 if b'bookmarks' not in other.listkeys(b'namespaces'):
5050 if b'bookmarks' not in other.listkeys(b'namespaces'):
5049 ui.warn(_(b"remote doesn't support bookmarks\n"))
5051 ui.warn(_(b"remote doesn't support bookmarks\n"))
5050 return 0
5052 return 0
5051 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5053 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5052 ui.pager(b'outgoing')
5054 ui.pager(b'outgoing')
5053 return bookmarks.outgoing(ui, repo, other)
5055 return bookmarks.outgoing(ui, repo, other)
5054
5056
5055 repo._subtoppath = path.pushloc or path.loc
5057 repo._subtoppath = path.pushloc or path.loc
5056 try:
5058 try:
5057 return hg.outgoing(ui, repo, dest, opts)
5059 return hg.outgoing(ui, repo, dest, opts)
5058 finally:
5060 finally:
5059 del repo._subtoppath
5061 del repo._subtoppath
5060
5062
5061
5063
5062 @command(
5064 @command(
5063 b'parents',
5065 b'parents',
5064 [
5066 [
5065 (
5067 (
5066 b'r',
5068 b'r',
5067 b'rev',
5069 b'rev',
5068 b'',
5070 b'',
5069 _(b'show parents of the specified revision'),
5071 _(b'show parents of the specified revision'),
5070 _(b'REV'),
5072 _(b'REV'),
5071 ),
5073 ),
5072 ]
5074 ]
5073 + templateopts,
5075 + templateopts,
5074 _(b'[-r REV] [FILE]'),
5076 _(b'[-r REV] [FILE]'),
5075 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5077 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5076 inferrepo=True,
5078 inferrepo=True,
5077 )
5079 )
5078 def parents(ui, repo, file_=None, **opts):
5080 def parents(ui, repo, file_=None, **opts):
5079 """show the parents of the working directory or revision (DEPRECATED)
5081 """show the parents of the working directory or revision (DEPRECATED)
5080
5082
5081 Print the working directory's parent revisions. If a revision is
5083 Print the working directory's parent revisions. If a revision is
5082 given via -r/--rev, the parent of that revision will be printed.
5084 given via -r/--rev, the parent of that revision will be printed.
5083 If a file argument is given, the revision in which the file was
5085 If a file argument is given, the revision in which the file was
5084 last changed (before the working directory revision or the
5086 last changed (before the working directory revision or the
5085 argument to --rev if given) is printed.
5087 argument to --rev if given) is printed.
5086
5088
5087 This command is equivalent to::
5089 This command is equivalent to::
5088
5090
5089 hg log -r "p1()+p2()" or
5091 hg log -r "p1()+p2()" or
5090 hg log -r "p1(REV)+p2(REV)" or
5092 hg log -r "p1(REV)+p2(REV)" or
5091 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5093 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5092 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5094 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5093
5095
5094 See :hg:`summary` and :hg:`help revsets` for related information.
5096 See :hg:`summary` and :hg:`help revsets` for related information.
5095
5097
5096 Returns 0 on success.
5098 Returns 0 on success.
5097 """
5099 """
5098
5100
5099 opts = pycompat.byteskwargs(opts)
5101 opts = pycompat.byteskwargs(opts)
5100 rev = opts.get(b'rev')
5102 rev = opts.get(b'rev')
5101 if rev:
5103 if rev:
5102 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5104 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5103 ctx = scmutil.revsingle(repo, rev, None)
5105 ctx = scmutil.revsingle(repo, rev, None)
5104
5106
5105 if file_:
5107 if file_:
5106 m = scmutil.match(ctx, (file_,), opts)
5108 m = scmutil.match(ctx, (file_,), opts)
5107 if m.anypats() or len(m.files()) != 1:
5109 if m.anypats() or len(m.files()) != 1:
5108 raise error.Abort(_(b'can only specify an explicit filename'))
5110 raise error.Abort(_(b'can only specify an explicit filename'))
5109 file_ = m.files()[0]
5111 file_ = m.files()[0]
5110 filenodes = []
5112 filenodes = []
5111 for cp in ctx.parents():
5113 for cp in ctx.parents():
5112 if not cp:
5114 if not cp:
5113 continue
5115 continue
5114 try:
5116 try:
5115 filenodes.append(cp.filenode(file_))
5117 filenodes.append(cp.filenode(file_))
5116 except error.LookupError:
5118 except error.LookupError:
5117 pass
5119 pass
5118 if not filenodes:
5120 if not filenodes:
5119 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5121 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5120 p = []
5122 p = []
5121 for fn in filenodes:
5123 for fn in filenodes:
5122 fctx = repo.filectx(file_, fileid=fn)
5124 fctx = repo.filectx(file_, fileid=fn)
5123 p.append(fctx.node())
5125 p.append(fctx.node())
5124 else:
5126 else:
5125 p = [cp.node() for cp in ctx.parents()]
5127 p = [cp.node() for cp in ctx.parents()]
5126
5128
5127 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5129 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5128 for n in p:
5130 for n in p:
5129 if n != nullid:
5131 if n != nullid:
5130 displayer.show(repo[n])
5132 displayer.show(repo[n])
5131 displayer.close()
5133 displayer.close()
5132
5134
5133
5135
5134 @command(
5136 @command(
5135 b'paths',
5137 b'paths',
5136 formatteropts,
5138 formatteropts,
5137 _(b'[NAME]'),
5139 _(b'[NAME]'),
5138 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5140 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5139 optionalrepo=True,
5141 optionalrepo=True,
5140 intents={INTENT_READONLY},
5142 intents={INTENT_READONLY},
5141 )
5143 )
5142 def paths(ui, repo, search=None, **opts):
5144 def paths(ui, repo, search=None, **opts):
5143 """show aliases for remote repositories
5145 """show aliases for remote repositories
5144
5146
5145 Show definition of symbolic path name NAME. If no name is given,
5147 Show definition of symbolic path name NAME. If no name is given,
5146 show definition of all available names.
5148 show definition of all available names.
5147
5149
5148 Option -q/--quiet suppresses all output when searching for NAME
5150 Option -q/--quiet suppresses all output when searching for NAME
5149 and shows only the path names when listing all definitions.
5151 and shows only the path names when listing all definitions.
5150
5152
5151 Path names are defined in the [paths] section of your
5153 Path names are defined in the [paths] section of your
5152 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5154 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5153 repository, ``.hg/hgrc`` is used, too.
5155 repository, ``.hg/hgrc`` is used, too.
5154
5156
5155 The path names ``default`` and ``default-push`` have a special
5157 The path names ``default`` and ``default-push`` have a special
5156 meaning. When performing a push or pull operation, they are used
5158 meaning. When performing a push or pull operation, they are used
5157 as fallbacks if no location is specified on the command-line.
5159 as fallbacks if no location is specified on the command-line.
5158 When ``default-push`` is set, it will be used for push and
5160 When ``default-push`` is set, it will be used for push and
5159 ``default`` will be used for pull; otherwise ``default`` is used
5161 ``default`` will be used for pull; otherwise ``default`` is used
5160 as the fallback for both. When cloning a repository, the clone
5162 as the fallback for both. When cloning a repository, the clone
5161 source is written as ``default`` in ``.hg/hgrc``.
5163 source is written as ``default`` in ``.hg/hgrc``.
5162
5164
5163 .. note::
5165 .. note::
5164
5166
5165 ``default`` and ``default-push`` apply to all inbound (e.g.
5167 ``default`` and ``default-push`` apply to all inbound (e.g.
5166 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5168 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5167 and :hg:`bundle`) operations.
5169 and :hg:`bundle`) operations.
5168
5170
5169 See :hg:`help urls` for more information.
5171 See :hg:`help urls` for more information.
5170
5172
5171 .. container:: verbose
5173 .. container:: verbose
5172
5174
5173 Template:
5175 Template:
5174
5176
5175 The following keywords are supported. See also :hg:`help templates`.
5177 The following keywords are supported. See also :hg:`help templates`.
5176
5178
5177 :name: String. Symbolic name of the path alias.
5179 :name: String. Symbolic name of the path alias.
5178 :pushurl: String. URL for push operations.
5180 :pushurl: String. URL for push operations.
5179 :url: String. URL or directory path for the other operations.
5181 :url: String. URL or directory path for the other operations.
5180
5182
5181 Returns 0 on success.
5183 Returns 0 on success.
5182 """
5184 """
5183
5185
5184 opts = pycompat.byteskwargs(opts)
5186 opts = pycompat.byteskwargs(opts)
5185 ui.pager(b'paths')
5187 ui.pager(b'paths')
5186 if search:
5188 if search:
5187 pathitems = [
5189 pathitems = [
5188 (name, path)
5190 (name, path)
5189 for name, path in pycompat.iteritems(ui.paths)
5191 for name, path in pycompat.iteritems(ui.paths)
5190 if name == search
5192 if name == search
5191 ]
5193 ]
5192 else:
5194 else:
5193 pathitems = sorted(pycompat.iteritems(ui.paths))
5195 pathitems = sorted(pycompat.iteritems(ui.paths))
5194
5196
5195 fm = ui.formatter(b'paths', opts)
5197 fm = ui.formatter(b'paths', opts)
5196 if fm.isplain():
5198 if fm.isplain():
5197 hidepassword = util.hidepassword
5199 hidepassword = util.hidepassword
5198 else:
5200 else:
5199 hidepassword = bytes
5201 hidepassword = bytes
5200 if ui.quiet:
5202 if ui.quiet:
5201 namefmt = b'%s\n'
5203 namefmt = b'%s\n'
5202 else:
5204 else:
5203 namefmt = b'%s = '
5205 namefmt = b'%s = '
5204 showsubopts = not search and not ui.quiet
5206 showsubopts = not search and not ui.quiet
5205
5207
5206 for name, path in pathitems:
5208 for name, path in pathitems:
5207 fm.startitem()
5209 fm.startitem()
5208 fm.condwrite(not search, b'name', namefmt, name)
5210 fm.condwrite(not search, b'name', namefmt, name)
5209 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5211 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5210 for subopt, value in sorted(path.suboptions.items()):
5212 for subopt, value in sorted(path.suboptions.items()):
5211 assert subopt not in (b'name', b'url')
5213 assert subopt not in (b'name', b'url')
5212 if showsubopts:
5214 if showsubopts:
5213 fm.plain(b'%s:%s = ' % (name, subopt))
5215 fm.plain(b'%s:%s = ' % (name, subopt))
5214 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5216 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5215
5217
5216 fm.end()
5218 fm.end()
5217
5219
5218 if search and not pathitems:
5220 if search and not pathitems:
5219 if not ui.quiet:
5221 if not ui.quiet:
5220 ui.warn(_(b"not found!\n"))
5222 ui.warn(_(b"not found!\n"))
5221 return 1
5223 return 1
5222 else:
5224 else:
5223 return 0
5225 return 0
5224
5226
5225
5227
5226 @command(
5228 @command(
5227 b'phase',
5229 b'phase',
5228 [
5230 [
5229 (b'p', b'public', False, _(b'set changeset phase to public')),
5231 (b'p', b'public', False, _(b'set changeset phase to public')),
5230 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5232 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5231 (b's', b'secret', False, _(b'set changeset phase to secret')),
5233 (b's', b'secret', False, _(b'set changeset phase to secret')),
5232 (b'f', b'force', False, _(b'allow to move boundary backward')),
5234 (b'f', b'force', False, _(b'allow to move boundary backward')),
5233 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5235 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5234 ],
5236 ],
5235 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5237 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5236 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5238 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5237 )
5239 )
5238 def phase(ui, repo, *revs, **opts):
5240 def phase(ui, repo, *revs, **opts):
5239 """set or show the current phase name
5241 """set or show the current phase name
5240
5242
5241 With no argument, show the phase name of the current revision(s).
5243 With no argument, show the phase name of the current revision(s).
5242
5244
5243 With one of -p/--public, -d/--draft or -s/--secret, change the
5245 With one of -p/--public, -d/--draft or -s/--secret, change the
5244 phase value of the specified revisions.
5246 phase value of the specified revisions.
5245
5247
5246 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5248 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5247 lower phase to a higher phase. Phases are ordered as follows::
5249 lower phase to a higher phase. Phases are ordered as follows::
5248
5250
5249 public < draft < secret
5251 public < draft < secret
5250
5252
5251 Returns 0 on success, 1 if some phases could not be changed.
5253 Returns 0 on success, 1 if some phases could not be changed.
5252
5254
5253 (For more information about the phases concept, see :hg:`help phases`.)
5255 (For more information about the phases concept, see :hg:`help phases`.)
5254 """
5256 """
5255 opts = pycompat.byteskwargs(opts)
5257 opts = pycompat.byteskwargs(opts)
5256 # search for a unique phase argument
5258 # search for a unique phase argument
5257 targetphase = None
5259 targetphase = None
5258 for idx, name in enumerate(phases.cmdphasenames):
5260 for idx, name in enumerate(phases.cmdphasenames):
5259 if opts[name]:
5261 if opts[name]:
5260 if targetphase is not None:
5262 if targetphase is not None:
5261 raise error.Abort(_(b'only one phase can be specified'))
5263 raise error.Abort(_(b'only one phase can be specified'))
5262 targetphase = idx
5264 targetphase = idx
5263
5265
5264 # look for specified revision
5266 # look for specified revision
5265 revs = list(revs)
5267 revs = list(revs)
5266 revs.extend(opts[b'rev'])
5268 revs.extend(opts[b'rev'])
5267 if not revs:
5269 if not revs:
5268 # display both parents as the second parent phase can influence
5270 # display both parents as the second parent phase can influence
5269 # the phase of a merge commit
5271 # the phase of a merge commit
5270 revs = [c.rev() for c in repo[None].parents()]
5272 revs = [c.rev() for c in repo[None].parents()]
5271
5273
5272 revs = scmutil.revrange(repo, revs)
5274 revs = scmutil.revrange(repo, revs)
5273
5275
5274 ret = 0
5276 ret = 0
5275 if targetphase is None:
5277 if targetphase is None:
5276 # display
5278 # display
5277 for r in revs:
5279 for r in revs:
5278 ctx = repo[r]
5280 ctx = repo[r]
5279 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5281 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5280 else:
5282 else:
5281 with repo.lock(), repo.transaction(b"phase") as tr:
5283 with repo.lock(), repo.transaction(b"phase") as tr:
5282 # set phase
5284 # set phase
5283 if not revs:
5285 if not revs:
5284 raise error.Abort(_(b'empty revision set'))
5286 raise error.Abort(_(b'empty revision set'))
5285 nodes = [repo[r].node() for r in revs]
5287 nodes = [repo[r].node() for r in revs]
5286 # moving revision from public to draft may hide them
5288 # moving revision from public to draft may hide them
5287 # We have to check result on an unfiltered repository
5289 # We have to check result on an unfiltered repository
5288 unfi = repo.unfiltered()
5290 unfi = repo.unfiltered()
5289 getphase = unfi._phasecache.phase
5291 getphase = unfi._phasecache.phase
5290 olddata = [getphase(unfi, r) for r in unfi]
5292 olddata = [getphase(unfi, r) for r in unfi]
5291 phases.advanceboundary(repo, tr, targetphase, nodes)
5293 phases.advanceboundary(repo, tr, targetphase, nodes)
5292 if opts[b'force']:
5294 if opts[b'force']:
5293 phases.retractboundary(repo, tr, targetphase, nodes)
5295 phases.retractboundary(repo, tr, targetphase, nodes)
5294 getphase = unfi._phasecache.phase
5296 getphase = unfi._phasecache.phase
5295 newdata = [getphase(unfi, r) for r in unfi]
5297 newdata = [getphase(unfi, r) for r in unfi]
5296 changes = sum(newdata[r] != olddata[r] for r in unfi)
5298 changes = sum(newdata[r] != olddata[r] for r in unfi)
5297 cl = unfi.changelog
5299 cl = unfi.changelog
5298 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5300 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5299 if rejected:
5301 if rejected:
5300 ui.warn(
5302 ui.warn(
5301 _(
5303 _(
5302 b'cannot move %i changesets to a higher '
5304 b'cannot move %i changesets to a higher '
5303 b'phase, use --force\n'
5305 b'phase, use --force\n'
5304 )
5306 )
5305 % len(rejected)
5307 % len(rejected)
5306 )
5308 )
5307 ret = 1
5309 ret = 1
5308 if changes:
5310 if changes:
5309 msg = _(b'phase changed for %i changesets\n') % changes
5311 msg = _(b'phase changed for %i changesets\n') % changes
5310 if ret:
5312 if ret:
5311 ui.status(msg)
5313 ui.status(msg)
5312 else:
5314 else:
5313 ui.note(msg)
5315 ui.note(msg)
5314 else:
5316 else:
5315 ui.warn(_(b'no phases changed\n'))
5317 ui.warn(_(b'no phases changed\n'))
5316 return ret
5318 return ret
5317
5319
5318
5320
5319 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5321 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5320 """Run after a changegroup has been added via pull/unbundle
5322 """Run after a changegroup has been added via pull/unbundle
5321
5323
5322 This takes arguments below:
5324 This takes arguments below:
5323
5325
5324 :modheads: change of heads by pull/unbundle
5326 :modheads: change of heads by pull/unbundle
5325 :optupdate: updating working directory is needed or not
5327 :optupdate: updating working directory is needed or not
5326 :checkout: update destination revision (or None to default destination)
5328 :checkout: update destination revision (or None to default destination)
5327 :brev: a name, which might be a bookmark to be activated after updating
5329 :brev: a name, which might be a bookmark to be activated after updating
5328 """
5330 """
5329 if modheads == 0:
5331 if modheads == 0:
5330 return
5332 return
5331 if optupdate:
5333 if optupdate:
5332 try:
5334 try:
5333 return hg.updatetotally(ui, repo, checkout, brev)
5335 return hg.updatetotally(ui, repo, checkout, brev)
5334 except error.UpdateAbort as inst:
5336 except error.UpdateAbort as inst:
5335 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5337 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5336 hint = inst.hint
5338 hint = inst.hint
5337 raise error.UpdateAbort(msg, hint=hint)
5339 raise error.UpdateAbort(msg, hint=hint)
5338 if modheads is not None and modheads > 1:
5340 if modheads is not None and modheads > 1:
5339 currentbranchheads = len(repo.branchheads())
5341 currentbranchheads = len(repo.branchheads())
5340 if currentbranchheads == modheads:
5342 if currentbranchheads == modheads:
5341 ui.status(
5343 ui.status(
5342 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5344 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5343 )
5345 )
5344 elif currentbranchheads > 1:
5346 elif currentbranchheads > 1:
5345 ui.status(
5347 ui.status(
5346 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5348 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5347 )
5349 )
5348 else:
5350 else:
5349 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5351 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5350 elif not ui.configbool(b'commands', b'update.requiredest'):
5352 elif not ui.configbool(b'commands', b'update.requiredest'):
5351 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5353 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5352
5354
5353
5355
5354 @command(
5356 @command(
5355 b'pull',
5357 b'pull',
5356 [
5358 [
5357 (
5359 (
5358 b'u',
5360 b'u',
5359 b'update',
5361 b'update',
5360 None,
5362 None,
5361 _(b'update to new branch head if new descendants were pulled'),
5363 _(b'update to new branch head if new descendants were pulled'),
5362 ),
5364 ),
5363 (
5365 (
5364 b'f',
5366 b'f',
5365 b'force',
5367 b'force',
5366 None,
5368 None,
5367 _(b'run even when remote repository is unrelated'),
5369 _(b'run even when remote repository is unrelated'),
5368 ),
5370 ),
5369 (b'', b'confirm', None, _(b'confirm pull before applying changes'),),
5371 (b'', b'confirm', None, _(b'confirm pull before applying changes'),),
5370 (
5372 (
5371 b'r',
5373 b'r',
5372 b'rev',
5374 b'rev',
5373 [],
5375 [],
5374 _(b'a remote changeset intended to be added'),
5376 _(b'a remote changeset intended to be added'),
5375 _(b'REV'),
5377 _(b'REV'),
5376 ),
5378 ),
5377 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5379 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5378 (
5380 (
5379 b'b',
5381 b'b',
5380 b'branch',
5382 b'branch',
5381 [],
5383 [],
5382 _(b'a specific branch you would like to pull'),
5384 _(b'a specific branch you would like to pull'),
5383 _(b'BRANCH'),
5385 _(b'BRANCH'),
5384 ),
5386 ),
5385 ]
5387 ]
5386 + remoteopts,
5388 + remoteopts,
5387 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5389 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5388 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5390 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5389 helpbasic=True,
5391 helpbasic=True,
5390 )
5392 )
5391 def pull(ui, repo, source=b"default", **opts):
5393 def pull(ui, repo, source=b"default", **opts):
5392 """pull changes from the specified source
5394 """pull changes from the specified source
5393
5395
5394 Pull changes from a remote repository to a local one.
5396 Pull changes from a remote repository to a local one.
5395
5397
5396 This finds all changes from the repository at the specified path
5398 This finds all changes from the repository at the specified path
5397 or URL and adds them to a local repository (the current one unless
5399 or URL and adds them to a local repository (the current one unless
5398 -R is specified). By default, this does not update the copy of the
5400 -R is specified). By default, this does not update the copy of the
5399 project in the working directory.
5401 project in the working directory.
5400
5402
5401 When cloning from servers that support it, Mercurial may fetch
5403 When cloning from servers that support it, Mercurial may fetch
5402 pre-generated data. When this is done, hooks operating on incoming
5404 pre-generated data. When this is done, hooks operating on incoming
5403 changesets and changegroups may fire more than once, once for each
5405 changesets and changegroups may fire more than once, once for each
5404 pre-generated bundle and as well as for any additional remaining
5406 pre-generated bundle and as well as for any additional remaining
5405 data. See :hg:`help -e clonebundles` for more.
5407 data. See :hg:`help -e clonebundles` for more.
5406
5408
5407 Use :hg:`incoming` if you want to see what would have been added
5409 Use :hg:`incoming` if you want to see what would have been added
5408 by a pull at the time you issued this command. If you then decide
5410 by a pull at the time you issued this command. If you then decide
5409 to add those changes to the repository, you should use :hg:`pull
5411 to add those changes to the repository, you should use :hg:`pull
5410 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5412 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5411
5413
5412 If SOURCE is omitted, the 'default' path will be used.
5414 If SOURCE is omitted, the 'default' path will be used.
5413 See :hg:`help urls` for more information.
5415 See :hg:`help urls` for more information.
5414
5416
5415 Specifying bookmark as ``.`` is equivalent to specifying the active
5417 Specifying bookmark as ``.`` is equivalent to specifying the active
5416 bookmark's name.
5418 bookmark's name.
5417
5419
5418 Returns 0 on success, 1 if an update had unresolved files.
5420 Returns 0 on success, 1 if an update had unresolved files.
5419 """
5421 """
5420
5422
5421 opts = pycompat.byteskwargs(opts)
5423 opts = pycompat.byteskwargs(opts)
5422 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5424 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5423 b'update'
5425 b'update'
5424 ):
5426 ):
5425 msg = _(b'update destination required by configuration')
5427 msg = _(b'update destination required by configuration')
5426 hint = _(b'use hg pull followed by hg update DEST')
5428 hint = _(b'use hg pull followed by hg update DEST')
5427 raise error.Abort(msg, hint=hint)
5429 raise error.Abort(msg, hint=hint)
5428
5430
5429 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5431 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5430 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5432 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5431 other = hg.peer(repo, opts, source)
5433 other = hg.peer(repo, opts, source)
5432 try:
5434 try:
5433 revs, checkout = hg.addbranchrevs(
5435 revs, checkout = hg.addbranchrevs(
5434 repo, other, branches, opts.get(b'rev')
5436 repo, other, branches, opts.get(b'rev')
5435 )
5437 )
5436
5438
5437 pullopargs = {}
5439 pullopargs = {}
5438
5440
5439 nodes = None
5441 nodes = None
5440 if opts.get(b'bookmark') or revs:
5442 if opts.get(b'bookmark') or revs:
5441 # The list of bookmark used here is the same used to actually update
5443 # The list of bookmark used here is the same used to actually update
5442 # the bookmark names, to avoid the race from issue 4689 and we do
5444 # the bookmark names, to avoid the race from issue 4689 and we do
5443 # all lookup and bookmark queries in one go so they see the same
5445 # all lookup and bookmark queries in one go so they see the same
5444 # version of the server state (issue 4700).
5446 # version of the server state (issue 4700).
5445 nodes = []
5447 nodes = []
5446 fnodes = []
5448 fnodes = []
5447 revs = revs or []
5449 revs = revs or []
5448 if revs and not other.capable(b'lookup'):
5450 if revs and not other.capable(b'lookup'):
5449 err = _(
5451 err = _(
5450 b"other repository doesn't support revision lookup, "
5452 b"other repository doesn't support revision lookup, "
5451 b"so a rev cannot be specified."
5453 b"so a rev cannot be specified."
5452 )
5454 )
5453 raise error.Abort(err)
5455 raise error.Abort(err)
5454 with other.commandexecutor() as e:
5456 with other.commandexecutor() as e:
5455 fremotebookmarks = e.callcommand(
5457 fremotebookmarks = e.callcommand(
5456 b'listkeys', {b'namespace': b'bookmarks'}
5458 b'listkeys', {b'namespace': b'bookmarks'}
5457 )
5459 )
5458 for r in revs:
5460 for r in revs:
5459 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5461 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5460 remotebookmarks = fremotebookmarks.result()
5462 remotebookmarks = fremotebookmarks.result()
5461 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5463 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5462 pullopargs[b'remotebookmarks'] = remotebookmarks
5464 pullopargs[b'remotebookmarks'] = remotebookmarks
5463 for b in opts.get(b'bookmark', []):
5465 for b in opts.get(b'bookmark', []):
5464 b = repo._bookmarks.expandname(b)
5466 b = repo._bookmarks.expandname(b)
5465 if b not in remotebookmarks:
5467 if b not in remotebookmarks:
5466 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5468 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5467 nodes.append(remotebookmarks[b])
5469 nodes.append(remotebookmarks[b])
5468 for i, rev in enumerate(revs):
5470 for i, rev in enumerate(revs):
5469 node = fnodes[i].result()
5471 node = fnodes[i].result()
5470 nodes.append(node)
5472 nodes.append(node)
5471 if rev == checkout:
5473 if rev == checkout:
5472 checkout = node
5474 checkout = node
5473
5475
5474 wlock = util.nullcontextmanager()
5476 wlock = util.nullcontextmanager()
5475 if opts.get(b'update'):
5477 if opts.get(b'update'):
5476 wlock = repo.wlock()
5478 wlock = repo.wlock()
5477 with wlock:
5479 with wlock:
5478 pullopargs.update(opts.get(b'opargs', {}))
5480 pullopargs.update(opts.get(b'opargs', {}))
5479 modheads = exchange.pull(
5481 modheads = exchange.pull(
5480 repo,
5482 repo,
5481 other,
5483 other,
5482 heads=nodes,
5484 heads=nodes,
5483 force=opts.get(b'force'),
5485 force=opts.get(b'force'),
5484 bookmarks=opts.get(b'bookmark', ()),
5486 bookmarks=opts.get(b'bookmark', ()),
5485 opargs=pullopargs,
5487 opargs=pullopargs,
5486 confirm=opts.get(b'confirm'),
5488 confirm=opts.get(b'confirm'),
5487 ).cgresult
5489 ).cgresult
5488
5490
5489 # brev is a name, which might be a bookmark to be activated at
5491 # brev is a name, which might be a bookmark to be activated at
5490 # the end of the update. In other words, it is an explicit
5492 # the end of the update. In other words, it is an explicit
5491 # destination of the update
5493 # destination of the update
5492 brev = None
5494 brev = None
5493
5495
5494 if checkout:
5496 if checkout:
5495 checkout = repo.unfiltered().changelog.rev(checkout)
5497 checkout = repo.unfiltered().changelog.rev(checkout)
5496
5498
5497 # order below depends on implementation of
5499 # order below depends on implementation of
5498 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5500 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5499 # because 'checkout' is determined without it.
5501 # because 'checkout' is determined without it.
5500 if opts.get(b'rev'):
5502 if opts.get(b'rev'):
5501 brev = opts[b'rev'][0]
5503 brev = opts[b'rev'][0]
5502 elif opts.get(b'branch'):
5504 elif opts.get(b'branch'):
5503 brev = opts[b'branch'][0]
5505 brev = opts[b'branch'][0]
5504 else:
5506 else:
5505 brev = branches[0]
5507 brev = branches[0]
5506 repo._subtoppath = source
5508 repo._subtoppath = source
5507 try:
5509 try:
5508 ret = postincoming(
5510 ret = postincoming(
5509 ui, repo, modheads, opts.get(b'update'), checkout, brev
5511 ui, repo, modheads, opts.get(b'update'), checkout, brev
5510 )
5512 )
5511 except error.FilteredRepoLookupError as exc:
5513 except error.FilteredRepoLookupError as exc:
5512 msg = _(b'cannot update to target: %s') % exc.args[0]
5514 msg = _(b'cannot update to target: %s') % exc.args[0]
5513 exc.args = (msg,) + exc.args[1:]
5515 exc.args = (msg,) + exc.args[1:]
5514 raise
5516 raise
5515 finally:
5517 finally:
5516 del repo._subtoppath
5518 del repo._subtoppath
5517
5519
5518 finally:
5520 finally:
5519 other.close()
5521 other.close()
5520 return ret
5522 return ret
5521
5523
5522
5524
5523 @command(
5525 @command(
5524 b'push',
5526 b'push',
5525 [
5527 [
5526 (b'f', b'force', None, _(b'force push')),
5528 (b'f', b'force', None, _(b'force push')),
5527 (
5529 (
5528 b'r',
5530 b'r',
5529 b'rev',
5531 b'rev',
5530 [],
5532 [],
5531 _(b'a changeset intended to be included in the destination'),
5533 _(b'a changeset intended to be included in the destination'),
5532 _(b'REV'),
5534 _(b'REV'),
5533 ),
5535 ),
5534 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5536 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5535 (
5537 (
5536 b'b',
5538 b'b',
5537 b'branch',
5539 b'branch',
5538 [],
5540 [],
5539 _(b'a specific branch you would like to push'),
5541 _(b'a specific branch you would like to push'),
5540 _(b'BRANCH'),
5542 _(b'BRANCH'),
5541 ),
5543 ),
5542 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5544 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5543 (
5545 (
5544 b'',
5546 b'',
5545 b'pushvars',
5547 b'pushvars',
5546 [],
5548 [],
5547 _(b'variables that can be sent to server (ADVANCED)'),
5549 _(b'variables that can be sent to server (ADVANCED)'),
5548 ),
5550 ),
5549 (
5551 (
5550 b'',
5552 b'',
5551 b'publish',
5553 b'publish',
5552 False,
5554 False,
5553 _(b'push the changeset as public (EXPERIMENTAL)'),
5555 _(b'push the changeset as public (EXPERIMENTAL)'),
5554 ),
5556 ),
5555 ]
5557 ]
5556 + remoteopts,
5558 + remoteopts,
5557 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5559 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5558 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5560 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5559 helpbasic=True,
5561 helpbasic=True,
5560 )
5562 )
5561 def push(ui, repo, dest=None, **opts):
5563 def push(ui, repo, dest=None, **opts):
5562 """push changes to the specified destination
5564 """push changes to the specified destination
5563
5565
5564 Push changesets from the local repository to the specified
5566 Push changesets from the local repository to the specified
5565 destination.
5567 destination.
5566
5568
5567 This operation is symmetrical to pull: it is identical to a pull
5569 This operation is symmetrical to pull: it is identical to a pull
5568 in the destination repository from the current one.
5570 in the destination repository from the current one.
5569
5571
5570 By default, push will not allow creation of new heads at the
5572 By default, push will not allow creation of new heads at the
5571 destination, since multiple heads would make it unclear which head
5573 destination, since multiple heads would make it unclear which head
5572 to use. In this situation, it is recommended to pull and merge
5574 to use. In this situation, it is recommended to pull and merge
5573 before pushing.
5575 before pushing.
5574
5576
5575 Use --new-branch if you want to allow push to create a new named
5577 Use --new-branch if you want to allow push to create a new named
5576 branch that is not present at the destination. This allows you to
5578 branch that is not present at the destination. This allows you to
5577 only create a new branch without forcing other changes.
5579 only create a new branch without forcing other changes.
5578
5580
5579 .. note::
5581 .. note::
5580
5582
5581 Extra care should be taken with the -f/--force option,
5583 Extra care should be taken with the -f/--force option,
5582 which will push all new heads on all branches, an action which will
5584 which will push all new heads on all branches, an action which will
5583 almost always cause confusion for collaborators.
5585 almost always cause confusion for collaborators.
5584
5586
5585 If -r/--rev is used, the specified revision and all its ancestors
5587 If -r/--rev is used, the specified revision and all its ancestors
5586 will be pushed to the remote repository.
5588 will be pushed to the remote repository.
5587
5589
5588 If -B/--bookmark is used, the specified bookmarked revision, its
5590 If -B/--bookmark is used, the specified bookmarked revision, its
5589 ancestors, and the bookmark will be pushed to the remote
5591 ancestors, and the bookmark will be pushed to the remote
5590 repository. Specifying ``.`` is equivalent to specifying the active
5592 repository. Specifying ``.`` is equivalent to specifying the active
5591 bookmark's name.
5593 bookmark's name.
5592
5594
5593 Please see :hg:`help urls` for important details about ``ssh://``
5595 Please see :hg:`help urls` for important details about ``ssh://``
5594 URLs. If DESTINATION is omitted, a default path will be used.
5596 URLs. If DESTINATION is omitted, a default path will be used.
5595
5597
5596 .. container:: verbose
5598 .. container:: verbose
5597
5599
5598 The --pushvars option sends strings to the server that become
5600 The --pushvars option sends strings to the server that become
5599 environment variables prepended with ``HG_USERVAR_``. For example,
5601 environment variables prepended with ``HG_USERVAR_``. For example,
5600 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5602 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5601 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5603 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5602
5604
5603 pushvars can provide for user-overridable hooks as well as set debug
5605 pushvars can provide for user-overridable hooks as well as set debug
5604 levels. One example is having a hook that blocks commits containing
5606 levels. One example is having a hook that blocks commits containing
5605 conflict markers, but enables the user to override the hook if the file
5607 conflict markers, but enables the user to override the hook if the file
5606 is using conflict markers for testing purposes or the file format has
5608 is using conflict markers for testing purposes or the file format has
5607 strings that look like conflict markers.
5609 strings that look like conflict markers.
5608
5610
5609 By default, servers will ignore `--pushvars`. To enable it add the
5611 By default, servers will ignore `--pushvars`. To enable it add the
5610 following to your configuration file::
5612 following to your configuration file::
5611
5613
5612 [push]
5614 [push]
5613 pushvars.server = true
5615 pushvars.server = true
5614
5616
5615 Returns 0 if push was successful, 1 if nothing to push.
5617 Returns 0 if push was successful, 1 if nothing to push.
5616 """
5618 """
5617
5619
5618 opts = pycompat.byteskwargs(opts)
5620 opts = pycompat.byteskwargs(opts)
5619 if opts.get(b'bookmark'):
5621 if opts.get(b'bookmark'):
5620 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5622 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5621 for b in opts[b'bookmark']:
5623 for b in opts[b'bookmark']:
5622 # translate -B options to -r so changesets get pushed
5624 # translate -B options to -r so changesets get pushed
5623 b = repo._bookmarks.expandname(b)
5625 b = repo._bookmarks.expandname(b)
5624 if b in repo._bookmarks:
5626 if b in repo._bookmarks:
5625 opts.setdefault(b'rev', []).append(b)
5627 opts.setdefault(b'rev', []).append(b)
5626 else:
5628 else:
5627 # if we try to push a deleted bookmark, translate it to null
5629 # if we try to push a deleted bookmark, translate it to null
5628 # this lets simultaneous -r, -b options continue working
5630 # this lets simultaneous -r, -b options continue working
5629 opts.setdefault(b'rev', []).append(b"null")
5631 opts.setdefault(b'rev', []).append(b"null")
5630
5632
5631 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5633 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5632 if not path:
5634 if not path:
5633 raise error.Abort(
5635 raise error.Abort(
5634 _(b'default repository not configured!'),
5636 _(b'default repository not configured!'),
5635 hint=_(b"see 'hg help config.paths'"),
5637 hint=_(b"see 'hg help config.paths'"),
5636 )
5638 )
5637 dest = path.pushloc or path.loc
5639 dest = path.pushloc or path.loc
5638 branches = (path.branch, opts.get(b'branch') or [])
5640 branches = (path.branch, opts.get(b'branch') or [])
5639 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5641 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5640 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5642 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5641 other = hg.peer(repo, opts, dest)
5643 other = hg.peer(repo, opts, dest)
5642
5644
5643 if revs:
5645 if revs:
5644 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5646 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5645 if not revs:
5647 if not revs:
5646 raise error.Abort(
5648 raise error.Abort(
5647 _(b"specified revisions evaluate to an empty set"),
5649 _(b"specified revisions evaluate to an empty set"),
5648 hint=_(b"use different revision arguments"),
5650 hint=_(b"use different revision arguments"),
5649 )
5651 )
5650 elif path.pushrev:
5652 elif path.pushrev:
5651 # It doesn't make any sense to specify ancestor revisions. So limit
5653 # It doesn't make any sense to specify ancestor revisions. So limit
5652 # to DAG heads to make discovery simpler.
5654 # to DAG heads to make discovery simpler.
5653 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5655 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5654 revs = scmutil.revrange(repo, [expr])
5656 revs = scmutil.revrange(repo, [expr])
5655 revs = [repo[rev].node() for rev in revs]
5657 revs = [repo[rev].node() for rev in revs]
5656 if not revs:
5658 if not revs:
5657 raise error.Abort(
5659 raise error.Abort(
5658 _(b'default push revset for path evaluates to an empty set')
5660 _(b'default push revset for path evaluates to an empty set')
5659 )
5661 )
5660 elif ui.configbool(b'commands', b'push.require-revs'):
5662 elif ui.configbool(b'commands', b'push.require-revs'):
5661 raise error.Abort(
5663 raise error.Abort(
5662 _(b'no revisions specified to push'),
5664 _(b'no revisions specified to push'),
5663 hint=_(b'did you mean "hg push -r ."?'),
5665 hint=_(b'did you mean "hg push -r ."?'),
5664 )
5666 )
5665
5667
5666 repo._subtoppath = dest
5668 repo._subtoppath = dest
5667 try:
5669 try:
5668 # push subrepos depth-first for coherent ordering
5670 # push subrepos depth-first for coherent ordering
5669 c = repo[b'.']
5671 c = repo[b'.']
5670 subs = c.substate # only repos that are committed
5672 subs = c.substate # only repos that are committed
5671 for s in sorted(subs):
5673 for s in sorted(subs):
5672 result = c.sub(s).push(opts)
5674 result = c.sub(s).push(opts)
5673 if result == 0:
5675 if result == 0:
5674 return not result
5676 return not result
5675 finally:
5677 finally:
5676 del repo._subtoppath
5678 del repo._subtoppath
5677
5679
5678 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5680 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5679 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5681 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5680
5682
5681 pushop = exchange.push(
5683 pushop = exchange.push(
5682 repo,
5684 repo,
5683 other,
5685 other,
5684 opts.get(b'force'),
5686 opts.get(b'force'),
5685 revs=revs,
5687 revs=revs,
5686 newbranch=opts.get(b'new_branch'),
5688 newbranch=opts.get(b'new_branch'),
5687 bookmarks=opts.get(b'bookmark', ()),
5689 bookmarks=opts.get(b'bookmark', ()),
5688 publish=opts.get(b'publish'),
5690 publish=opts.get(b'publish'),
5689 opargs=opargs,
5691 opargs=opargs,
5690 )
5692 )
5691
5693
5692 result = not pushop.cgresult
5694 result = not pushop.cgresult
5693
5695
5694 if pushop.bkresult is not None:
5696 if pushop.bkresult is not None:
5695 if pushop.bkresult == 2:
5697 if pushop.bkresult == 2:
5696 result = 2
5698 result = 2
5697 elif not result and pushop.bkresult:
5699 elif not result and pushop.bkresult:
5698 result = 2
5700 result = 2
5699
5701
5700 return result
5702 return result
5701
5703
5702
5704
5703 @command(
5705 @command(
5704 b'recover',
5706 b'recover',
5705 [(b'', b'verify', False, b"run `hg verify` after successful recover"),],
5707 [(b'', b'verify', False, b"run `hg verify` after successful recover"),],
5706 helpcategory=command.CATEGORY_MAINTENANCE,
5708 helpcategory=command.CATEGORY_MAINTENANCE,
5707 )
5709 )
5708 def recover(ui, repo, **opts):
5710 def recover(ui, repo, **opts):
5709 """roll back an interrupted transaction
5711 """roll back an interrupted transaction
5710
5712
5711 Recover from an interrupted commit or pull.
5713 Recover from an interrupted commit or pull.
5712
5714
5713 This command tries to fix the repository status after an
5715 This command tries to fix the repository status after an
5714 interrupted operation. It should only be necessary when Mercurial
5716 interrupted operation. It should only be necessary when Mercurial
5715 suggests it.
5717 suggests it.
5716
5718
5717 Returns 0 if successful, 1 if nothing to recover or verify fails.
5719 Returns 0 if successful, 1 if nothing to recover or verify fails.
5718 """
5720 """
5719 ret = repo.recover()
5721 ret = repo.recover()
5720 if ret:
5722 if ret:
5721 if opts['verify']:
5723 if opts['verify']:
5722 return hg.verify(repo)
5724 return hg.verify(repo)
5723 else:
5725 else:
5724 msg = _(
5726 msg = _(
5725 b"(verify step skipped, run `hg verify` to check your "
5727 b"(verify step skipped, run `hg verify` to check your "
5726 b"repository content)\n"
5728 b"repository content)\n"
5727 )
5729 )
5728 ui.warn(msg)
5730 ui.warn(msg)
5729 return 0
5731 return 0
5730 return 1
5732 return 1
5731
5733
5732
5734
5733 @command(
5735 @command(
5734 b'remove|rm',
5736 b'remove|rm',
5735 [
5737 [
5736 (b'A', b'after', None, _(b'record delete for missing files')),
5738 (b'A', b'after', None, _(b'record delete for missing files')),
5737 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5739 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5738 ]
5740 ]
5739 + subrepoopts
5741 + subrepoopts
5740 + walkopts
5742 + walkopts
5741 + dryrunopts,
5743 + dryrunopts,
5742 _(b'[OPTION]... FILE...'),
5744 _(b'[OPTION]... FILE...'),
5743 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5745 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5744 helpbasic=True,
5746 helpbasic=True,
5745 inferrepo=True,
5747 inferrepo=True,
5746 )
5748 )
5747 def remove(ui, repo, *pats, **opts):
5749 def remove(ui, repo, *pats, **opts):
5748 """remove the specified files on the next commit
5750 """remove the specified files on the next commit
5749
5751
5750 Schedule the indicated files for removal from the current branch.
5752 Schedule the indicated files for removal from the current branch.
5751
5753
5752 This command schedules the files to be removed at the next commit.
5754 This command schedules the files to be removed at the next commit.
5753 To undo a remove before that, see :hg:`revert`. To undo added
5755 To undo a remove before that, see :hg:`revert`. To undo added
5754 files, see :hg:`forget`.
5756 files, see :hg:`forget`.
5755
5757
5756 .. container:: verbose
5758 .. container:: verbose
5757
5759
5758 -A/--after can be used to remove only files that have already
5760 -A/--after can be used to remove only files that have already
5759 been deleted, -f/--force can be used to force deletion, and -Af
5761 been deleted, -f/--force can be used to force deletion, and -Af
5760 can be used to remove files from the next revision without
5762 can be used to remove files from the next revision without
5761 deleting them from the working directory.
5763 deleting them from the working directory.
5762
5764
5763 The following table details the behavior of remove for different
5765 The following table details the behavior of remove for different
5764 file states (columns) and option combinations (rows). The file
5766 file states (columns) and option combinations (rows). The file
5765 states are Added [A], Clean [C], Modified [M] and Missing [!]
5767 states are Added [A], Clean [C], Modified [M] and Missing [!]
5766 (as reported by :hg:`status`). The actions are Warn, Remove
5768 (as reported by :hg:`status`). The actions are Warn, Remove
5767 (from branch) and Delete (from disk):
5769 (from branch) and Delete (from disk):
5768
5770
5769 ========= == == == ==
5771 ========= == == == ==
5770 opt/state A C M !
5772 opt/state A C M !
5771 ========= == == == ==
5773 ========= == == == ==
5772 none W RD W R
5774 none W RD W R
5773 -f R RD RD R
5775 -f R RD RD R
5774 -A W W W R
5776 -A W W W R
5775 -Af R R R R
5777 -Af R R R R
5776 ========= == == == ==
5778 ========= == == == ==
5777
5779
5778 .. note::
5780 .. note::
5779
5781
5780 :hg:`remove` never deletes files in Added [A] state from the
5782 :hg:`remove` never deletes files in Added [A] state from the
5781 working directory, not even if ``--force`` is specified.
5783 working directory, not even if ``--force`` is specified.
5782
5784
5783 Returns 0 on success, 1 if any warnings encountered.
5785 Returns 0 on success, 1 if any warnings encountered.
5784 """
5786 """
5785
5787
5786 opts = pycompat.byteskwargs(opts)
5788 opts = pycompat.byteskwargs(opts)
5787 after, force = opts.get(b'after'), opts.get(b'force')
5789 after, force = opts.get(b'after'), opts.get(b'force')
5788 dryrun = opts.get(b'dry_run')
5790 dryrun = opts.get(b'dry_run')
5789 if not pats and not after:
5791 if not pats and not after:
5790 raise error.Abort(_(b'no files specified'))
5792 raise error.Abort(_(b'no files specified'))
5791
5793
5792 m = scmutil.match(repo[None], pats, opts)
5794 m = scmutil.match(repo[None], pats, opts)
5793 subrepos = opts.get(b'subrepos')
5795 subrepos = opts.get(b'subrepos')
5794 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5796 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5795 return cmdutil.remove(
5797 return cmdutil.remove(
5796 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5798 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5797 )
5799 )
5798
5800
5799
5801
5800 @command(
5802 @command(
5801 b'rename|move|mv',
5803 b'rename|move|mv',
5802 [
5804 [
5803 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5805 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5804 (
5806 (
5805 b'',
5807 b'',
5806 b'at-rev',
5808 b'at-rev',
5807 b'',
5809 b'',
5808 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5810 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5809 _(b'REV'),
5811 _(b'REV'),
5810 ),
5812 ),
5811 (
5813 (
5812 b'f',
5814 b'f',
5813 b'force',
5815 b'force',
5814 None,
5816 None,
5815 _(b'forcibly move over an existing managed file'),
5817 _(b'forcibly move over an existing managed file'),
5816 ),
5818 ),
5817 ]
5819 ]
5818 + walkopts
5820 + walkopts
5819 + dryrunopts,
5821 + dryrunopts,
5820 _(b'[OPTION]... SOURCE... DEST'),
5822 _(b'[OPTION]... SOURCE... DEST'),
5821 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5823 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5822 )
5824 )
5823 def rename(ui, repo, *pats, **opts):
5825 def rename(ui, repo, *pats, **opts):
5824 """rename files; equivalent of copy + remove
5826 """rename files; equivalent of copy + remove
5825
5827
5826 Mark dest as copies of sources; mark sources for deletion. If dest
5828 Mark dest as copies of sources; mark sources for deletion. If dest
5827 is a directory, copies are put in that directory. If dest is a
5829 is a directory, copies are put in that directory. If dest is a
5828 file, there can only be one source.
5830 file, there can only be one source.
5829
5831
5830 By default, this command copies the contents of files as they
5832 By default, this command copies the contents of files as they
5831 exist in the working directory. If invoked with -A/--after, the
5833 exist in the working directory. If invoked with -A/--after, the
5832 operation is recorded, but no copying is performed.
5834 operation is recorded, but no copying is performed.
5833
5835
5834 This command takes effect at the next commit. To undo a rename
5836 This command takes effect at the next commit. To undo a rename
5835 before that, see :hg:`revert`.
5837 before that, see :hg:`revert`.
5836
5838
5837 Returns 0 on success, 1 if errors are encountered.
5839 Returns 0 on success, 1 if errors are encountered.
5838 """
5840 """
5839 opts = pycompat.byteskwargs(opts)
5841 opts = pycompat.byteskwargs(opts)
5840 with repo.wlock():
5842 with repo.wlock():
5841 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5843 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5842
5844
5843
5845
5844 @command(
5846 @command(
5845 b'resolve',
5847 b'resolve',
5846 [
5848 [
5847 (b'a', b'all', None, _(b'select all unresolved files')),
5849 (b'a', b'all', None, _(b'select all unresolved files')),
5848 (b'l', b'list', None, _(b'list state of files needing merge')),
5850 (b'l', b'list', None, _(b'list state of files needing merge')),
5849 (b'm', b'mark', None, _(b'mark files as resolved')),
5851 (b'm', b'mark', None, _(b'mark files as resolved')),
5850 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5852 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5851 (b'n', b'no-status', None, _(b'hide status prefix')),
5853 (b'n', b'no-status', None, _(b'hide status prefix')),
5852 (b'', b're-merge', None, _(b're-merge files')),
5854 (b'', b're-merge', None, _(b're-merge files')),
5853 ]
5855 ]
5854 + mergetoolopts
5856 + mergetoolopts
5855 + walkopts
5857 + walkopts
5856 + formatteropts,
5858 + formatteropts,
5857 _(b'[OPTION]... [FILE]...'),
5859 _(b'[OPTION]... [FILE]...'),
5858 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5860 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5859 inferrepo=True,
5861 inferrepo=True,
5860 )
5862 )
5861 def resolve(ui, repo, *pats, **opts):
5863 def resolve(ui, repo, *pats, **opts):
5862 """redo merges or set/view the merge status of files
5864 """redo merges or set/view the merge status of files
5863
5865
5864 Merges with unresolved conflicts are often the result of
5866 Merges with unresolved conflicts are often the result of
5865 non-interactive merging using the ``internal:merge`` configuration
5867 non-interactive merging using the ``internal:merge`` configuration
5866 setting, or a command-line merge tool like ``diff3``. The resolve
5868 setting, or a command-line merge tool like ``diff3``. The resolve
5867 command is used to manage the files involved in a merge, after
5869 command is used to manage the files involved in a merge, after
5868 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5870 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5869 working directory must have two parents). See :hg:`help
5871 working directory must have two parents). See :hg:`help
5870 merge-tools` for information on configuring merge tools.
5872 merge-tools` for information on configuring merge tools.
5871
5873
5872 The resolve command can be used in the following ways:
5874 The resolve command can be used in the following ways:
5873
5875
5874 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5876 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5875 the specified files, discarding any previous merge attempts. Re-merging
5877 the specified files, discarding any previous merge attempts. Re-merging
5876 is not performed for files already marked as resolved. Use ``--all/-a``
5878 is not performed for files already marked as resolved. Use ``--all/-a``
5877 to select all unresolved files. ``--tool`` can be used to specify
5879 to select all unresolved files. ``--tool`` can be used to specify
5878 the merge tool used for the given files. It overrides the HGMERGE
5880 the merge tool used for the given files. It overrides the HGMERGE
5879 environment variable and your configuration files. Previous file
5881 environment variable and your configuration files. Previous file
5880 contents are saved with a ``.orig`` suffix.
5882 contents are saved with a ``.orig`` suffix.
5881
5883
5882 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5884 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5883 (e.g. after having manually fixed-up the files). The default is
5885 (e.g. after having manually fixed-up the files). The default is
5884 to mark all unresolved files.
5886 to mark all unresolved files.
5885
5887
5886 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5888 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5887 default is to mark all resolved files.
5889 default is to mark all resolved files.
5888
5890
5889 - :hg:`resolve -l`: list files which had or still have conflicts.
5891 - :hg:`resolve -l`: list files which had or still have conflicts.
5890 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5892 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5891 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5893 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5892 the list. See :hg:`help filesets` for details.
5894 the list. See :hg:`help filesets` for details.
5893
5895
5894 .. note::
5896 .. note::
5895
5897
5896 Mercurial will not let you commit files with unresolved merge
5898 Mercurial will not let you commit files with unresolved merge
5897 conflicts. You must use :hg:`resolve -m ...` before you can
5899 conflicts. You must use :hg:`resolve -m ...` before you can
5898 commit after a conflicting merge.
5900 commit after a conflicting merge.
5899
5901
5900 .. container:: verbose
5902 .. container:: verbose
5901
5903
5902 Template:
5904 Template:
5903
5905
5904 The following keywords are supported in addition to the common template
5906 The following keywords are supported in addition to the common template
5905 keywords and functions. See also :hg:`help templates`.
5907 keywords and functions. See also :hg:`help templates`.
5906
5908
5907 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5909 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5908 :path: String. Repository-absolute path of the file.
5910 :path: String. Repository-absolute path of the file.
5909
5911
5910 Returns 0 on success, 1 if any files fail a resolve attempt.
5912 Returns 0 on success, 1 if any files fail a resolve attempt.
5911 """
5913 """
5912
5914
5913 opts = pycompat.byteskwargs(opts)
5915 opts = pycompat.byteskwargs(opts)
5914 confirm = ui.configbool(b'commands', b'resolve.confirm')
5916 confirm = ui.configbool(b'commands', b'resolve.confirm')
5915 flaglist = b'all mark unmark list no_status re_merge'.split()
5917 flaglist = b'all mark unmark list no_status re_merge'.split()
5916 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5918 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5917
5919
5918 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5920 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5919 if actioncount > 1:
5921 if actioncount > 1:
5920 raise error.Abort(_(b"too many actions specified"))
5922 raise error.Abort(_(b"too many actions specified"))
5921 elif actioncount == 0 and ui.configbool(
5923 elif actioncount == 0 and ui.configbool(
5922 b'commands', b'resolve.explicit-re-merge'
5924 b'commands', b'resolve.explicit-re-merge'
5923 ):
5925 ):
5924 hint = _(b'use --mark, --unmark, --list or --re-merge')
5926 hint = _(b'use --mark, --unmark, --list or --re-merge')
5925 raise error.Abort(_(b'no action specified'), hint=hint)
5927 raise error.Abort(_(b'no action specified'), hint=hint)
5926 if pats and all:
5928 if pats and all:
5927 raise error.Abort(_(b"can't specify --all and patterns"))
5929 raise error.Abort(_(b"can't specify --all and patterns"))
5928 if not (all or pats or show or mark or unmark):
5930 if not (all or pats or show or mark or unmark):
5929 raise error.Abort(
5931 raise error.Abort(
5930 _(b'no files or directories specified'),
5932 _(b'no files or directories specified'),
5931 hint=b'use --all to re-merge all unresolved files',
5933 hint=b'use --all to re-merge all unresolved files',
5932 )
5934 )
5933
5935
5934 if confirm:
5936 if confirm:
5935 if all:
5937 if all:
5936 if ui.promptchoice(
5938 if ui.promptchoice(
5937 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5939 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5938 ):
5940 ):
5939 raise error.Abort(_(b'user quit'))
5941 raise error.Abort(_(b'user quit'))
5940 if mark and not pats:
5942 if mark and not pats:
5941 if ui.promptchoice(
5943 if ui.promptchoice(
5942 _(
5944 _(
5943 b'mark all unresolved files as resolved (yn)?'
5945 b'mark all unresolved files as resolved (yn)?'
5944 b'$$ &Yes $$ &No'
5946 b'$$ &Yes $$ &No'
5945 )
5947 )
5946 ):
5948 ):
5947 raise error.Abort(_(b'user quit'))
5949 raise error.Abort(_(b'user quit'))
5948 if unmark and not pats:
5950 if unmark and not pats:
5949 if ui.promptchoice(
5951 if ui.promptchoice(
5950 _(
5952 _(
5951 b'mark all resolved files as unresolved (yn)?'
5953 b'mark all resolved files as unresolved (yn)?'
5952 b'$$ &Yes $$ &No'
5954 b'$$ &Yes $$ &No'
5953 )
5955 )
5954 ):
5956 ):
5955 raise error.Abort(_(b'user quit'))
5957 raise error.Abort(_(b'user quit'))
5956
5958
5957 uipathfn = scmutil.getuipathfn(repo)
5959 uipathfn = scmutil.getuipathfn(repo)
5958
5960
5959 if show:
5961 if show:
5960 ui.pager(b'resolve')
5962 ui.pager(b'resolve')
5961 fm = ui.formatter(b'resolve', opts)
5963 fm = ui.formatter(b'resolve', opts)
5962 ms = mergestatemod.mergestate.read(repo)
5964 ms = mergestatemod.mergestate.read(repo)
5963 wctx = repo[None]
5965 wctx = repo[None]
5964 m = scmutil.match(wctx, pats, opts)
5966 m = scmutil.match(wctx, pats, opts)
5965
5967
5966 # Labels and keys based on merge state. Unresolved path conflicts show
5968 # Labels and keys based on merge state. Unresolved path conflicts show
5967 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5969 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5968 # resolved conflicts.
5970 # resolved conflicts.
5969 mergestateinfo = {
5971 mergestateinfo = {
5970 mergestatemod.MERGE_RECORD_UNRESOLVED: (
5972 mergestatemod.MERGE_RECORD_UNRESOLVED: (
5971 b'resolve.unresolved',
5973 b'resolve.unresolved',
5972 b'U',
5974 b'U',
5973 ),
5975 ),
5974 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5976 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5975 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
5977 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
5976 b'resolve.unresolved',
5978 b'resolve.unresolved',
5977 b'P',
5979 b'P',
5978 ),
5980 ),
5979 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
5981 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
5980 b'resolve.resolved',
5982 b'resolve.resolved',
5981 b'R',
5983 b'R',
5982 ),
5984 ),
5983 }
5985 }
5984
5986
5985 for f in ms:
5987 for f in ms:
5986 if not m(f):
5988 if not m(f):
5987 continue
5989 continue
5988
5990
5989 label, key = mergestateinfo[ms[f]]
5991 label, key = mergestateinfo[ms[f]]
5990 fm.startitem()
5992 fm.startitem()
5991 fm.context(ctx=wctx)
5993 fm.context(ctx=wctx)
5992 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5994 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5993 fm.data(path=f)
5995 fm.data(path=f)
5994 fm.plain(b'%s\n' % uipathfn(f), label=label)
5996 fm.plain(b'%s\n' % uipathfn(f), label=label)
5995 fm.end()
5997 fm.end()
5996 return 0
5998 return 0
5997
5999
5998 with repo.wlock():
6000 with repo.wlock():
5999 ms = mergestatemod.mergestate.read(repo)
6001 ms = mergestatemod.mergestate.read(repo)
6000
6002
6001 if not (ms.active() or repo.dirstate.p2() != nullid):
6003 if not (ms.active() or repo.dirstate.p2() != nullid):
6002 raise error.Abort(
6004 raise error.Abort(
6003 _(b'resolve command not applicable when not merging')
6005 _(b'resolve command not applicable when not merging')
6004 )
6006 )
6005
6007
6006 wctx = repo[None]
6008 wctx = repo[None]
6007 m = scmutil.match(wctx, pats, opts)
6009 m = scmutil.match(wctx, pats, opts)
6008 ret = 0
6010 ret = 0
6009 didwork = False
6011 didwork = False
6010
6012
6011 tocomplete = []
6013 tocomplete = []
6012 hasconflictmarkers = []
6014 hasconflictmarkers = []
6013 if mark:
6015 if mark:
6014 markcheck = ui.config(b'commands', b'resolve.mark-check')
6016 markcheck = ui.config(b'commands', b'resolve.mark-check')
6015 if markcheck not in [b'warn', b'abort']:
6017 if markcheck not in [b'warn', b'abort']:
6016 # Treat all invalid / unrecognized values as 'none'.
6018 # Treat all invalid / unrecognized values as 'none'.
6017 markcheck = False
6019 markcheck = False
6018 for f in ms:
6020 for f in ms:
6019 if not m(f):
6021 if not m(f):
6020 continue
6022 continue
6021
6023
6022 didwork = True
6024 didwork = True
6023
6025
6024 # path conflicts must be resolved manually
6026 # path conflicts must be resolved manually
6025 if ms[f] in (
6027 if ms[f] in (
6026 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6028 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6027 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6029 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6028 ):
6030 ):
6029 if mark:
6031 if mark:
6030 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6032 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6031 elif unmark:
6033 elif unmark:
6032 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6034 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6033 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6035 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6034 ui.warn(
6036 ui.warn(
6035 _(b'%s: path conflict must be resolved manually\n')
6037 _(b'%s: path conflict must be resolved manually\n')
6036 % uipathfn(f)
6038 % uipathfn(f)
6037 )
6039 )
6038 continue
6040 continue
6039
6041
6040 if mark:
6042 if mark:
6041 if markcheck:
6043 if markcheck:
6042 fdata = repo.wvfs.tryread(f)
6044 fdata = repo.wvfs.tryread(f)
6043 if (
6045 if (
6044 filemerge.hasconflictmarkers(fdata)
6046 filemerge.hasconflictmarkers(fdata)
6045 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6047 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6046 ):
6048 ):
6047 hasconflictmarkers.append(f)
6049 hasconflictmarkers.append(f)
6048 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6050 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6049 elif unmark:
6051 elif unmark:
6050 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6052 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6051 else:
6053 else:
6052 # backup pre-resolve (merge uses .orig for its own purposes)
6054 # backup pre-resolve (merge uses .orig for its own purposes)
6053 a = repo.wjoin(f)
6055 a = repo.wjoin(f)
6054 try:
6056 try:
6055 util.copyfile(a, a + b".resolve")
6057 util.copyfile(a, a + b".resolve")
6056 except (IOError, OSError) as inst:
6058 except (IOError, OSError) as inst:
6057 if inst.errno != errno.ENOENT:
6059 if inst.errno != errno.ENOENT:
6058 raise
6060 raise
6059
6061
6060 try:
6062 try:
6061 # preresolve file
6063 # preresolve file
6062 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6064 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6063 with ui.configoverride(overrides, b'resolve'):
6065 with ui.configoverride(overrides, b'resolve'):
6064 complete, r = ms.preresolve(f, wctx)
6066 complete, r = ms.preresolve(f, wctx)
6065 if not complete:
6067 if not complete:
6066 tocomplete.append(f)
6068 tocomplete.append(f)
6067 elif r:
6069 elif r:
6068 ret = 1
6070 ret = 1
6069 finally:
6071 finally:
6070 ms.commit()
6072 ms.commit()
6071
6073
6072 # replace filemerge's .orig file with our resolve file, but only
6074 # replace filemerge's .orig file with our resolve file, but only
6073 # for merges that are complete
6075 # for merges that are complete
6074 if complete:
6076 if complete:
6075 try:
6077 try:
6076 util.rename(
6078 util.rename(
6077 a + b".resolve", scmutil.backuppath(ui, repo, f)
6079 a + b".resolve", scmutil.backuppath(ui, repo, f)
6078 )
6080 )
6079 except OSError as inst:
6081 except OSError as inst:
6080 if inst.errno != errno.ENOENT:
6082 if inst.errno != errno.ENOENT:
6081 raise
6083 raise
6082
6084
6083 if hasconflictmarkers:
6085 if hasconflictmarkers:
6084 ui.warn(
6086 ui.warn(
6085 _(
6087 _(
6086 b'warning: the following files still have conflict '
6088 b'warning: the following files still have conflict '
6087 b'markers:\n'
6089 b'markers:\n'
6088 )
6090 )
6089 + b''.join(
6091 + b''.join(
6090 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6092 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6091 )
6093 )
6092 )
6094 )
6093 if markcheck == b'abort' and not all and not pats:
6095 if markcheck == b'abort' and not all and not pats:
6094 raise error.Abort(
6096 raise error.Abort(
6095 _(b'conflict markers detected'),
6097 _(b'conflict markers detected'),
6096 hint=_(b'use --all to mark anyway'),
6098 hint=_(b'use --all to mark anyway'),
6097 )
6099 )
6098
6100
6099 for f in tocomplete:
6101 for f in tocomplete:
6100 try:
6102 try:
6101 # resolve file
6103 # resolve file
6102 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6104 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6103 with ui.configoverride(overrides, b'resolve'):
6105 with ui.configoverride(overrides, b'resolve'):
6104 r = ms.resolve(f, wctx)
6106 r = ms.resolve(f, wctx)
6105 if r:
6107 if r:
6106 ret = 1
6108 ret = 1
6107 finally:
6109 finally:
6108 ms.commit()
6110 ms.commit()
6109
6111
6110 # replace filemerge's .orig file with our resolve file
6112 # replace filemerge's .orig file with our resolve file
6111 a = repo.wjoin(f)
6113 a = repo.wjoin(f)
6112 try:
6114 try:
6113 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6115 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6114 except OSError as inst:
6116 except OSError as inst:
6115 if inst.errno != errno.ENOENT:
6117 if inst.errno != errno.ENOENT:
6116 raise
6118 raise
6117
6119
6118 ms.commit()
6120 ms.commit()
6119 branchmerge = repo.dirstate.p2() != nullid
6121 branchmerge = repo.dirstate.p2() != nullid
6120 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6122 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6121
6123
6122 if not didwork and pats:
6124 if not didwork and pats:
6123 hint = None
6125 hint = None
6124 if not any([p for p in pats if p.find(b':') >= 0]):
6126 if not any([p for p in pats if p.find(b':') >= 0]):
6125 pats = [b'path:%s' % p for p in pats]
6127 pats = [b'path:%s' % p for p in pats]
6126 m = scmutil.match(wctx, pats, opts)
6128 m = scmutil.match(wctx, pats, opts)
6127 for f in ms:
6129 for f in ms:
6128 if not m(f):
6130 if not m(f):
6129 continue
6131 continue
6130
6132
6131 def flag(o):
6133 def flag(o):
6132 if o == b're_merge':
6134 if o == b're_merge':
6133 return b'--re-merge '
6135 return b'--re-merge '
6134 return b'-%s ' % o[0:1]
6136 return b'-%s ' % o[0:1]
6135
6137
6136 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6138 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6137 hint = _(b"(try: hg resolve %s%s)\n") % (
6139 hint = _(b"(try: hg resolve %s%s)\n") % (
6138 flags,
6140 flags,
6139 b' '.join(pats),
6141 b' '.join(pats),
6140 )
6142 )
6141 break
6143 break
6142 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6144 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6143 if hint:
6145 if hint:
6144 ui.warn(hint)
6146 ui.warn(hint)
6145
6147
6146 unresolvedf = list(ms.unresolved())
6148 unresolvedf = list(ms.unresolved())
6147 if not unresolvedf:
6149 if not unresolvedf:
6148 ui.status(_(b'(no more unresolved files)\n'))
6150 ui.status(_(b'(no more unresolved files)\n'))
6149 cmdutil.checkafterresolved(repo)
6151 cmdutil.checkafterresolved(repo)
6150
6152
6151 return ret
6153 return ret
6152
6154
6153
6155
6154 @command(
6156 @command(
6155 b'revert',
6157 b'revert',
6156 [
6158 [
6157 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6159 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6158 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6160 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6159 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6161 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6160 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6162 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6161 (b'i', b'interactive', None, _(b'interactively select the changes')),
6163 (b'i', b'interactive', None, _(b'interactively select the changes')),
6162 ]
6164 ]
6163 + walkopts
6165 + walkopts
6164 + dryrunopts,
6166 + dryrunopts,
6165 _(b'[OPTION]... [-r REV] [NAME]...'),
6167 _(b'[OPTION]... [-r REV] [NAME]...'),
6166 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6168 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6167 )
6169 )
6168 def revert(ui, repo, *pats, **opts):
6170 def revert(ui, repo, *pats, **opts):
6169 """restore files to their checkout state
6171 """restore files to their checkout state
6170
6172
6171 .. note::
6173 .. note::
6172
6174
6173 To check out earlier revisions, you should use :hg:`update REV`.
6175 To check out earlier revisions, you should use :hg:`update REV`.
6174 To cancel an uncommitted merge (and lose your changes),
6176 To cancel an uncommitted merge (and lose your changes),
6175 use :hg:`merge --abort`.
6177 use :hg:`merge --abort`.
6176
6178
6177 With no revision specified, revert the specified files or directories
6179 With no revision specified, revert the specified files or directories
6178 to the contents they had in the parent of the working directory.
6180 to the contents they had in the parent of the working directory.
6179 This restores the contents of files to an unmodified
6181 This restores the contents of files to an unmodified
6180 state and unschedules adds, removes, copies, and renames. If the
6182 state and unschedules adds, removes, copies, and renames. If the
6181 working directory has two parents, you must explicitly specify a
6183 working directory has two parents, you must explicitly specify a
6182 revision.
6184 revision.
6183
6185
6184 Using the -r/--rev or -d/--date options, revert the given files or
6186 Using the -r/--rev or -d/--date options, revert the given files or
6185 directories to their states as of a specific revision. Because
6187 directories to their states as of a specific revision. Because
6186 revert does not change the working directory parents, this will
6188 revert does not change the working directory parents, this will
6187 cause these files to appear modified. This can be helpful to "back
6189 cause these files to appear modified. This can be helpful to "back
6188 out" some or all of an earlier change. See :hg:`backout` for a
6190 out" some or all of an earlier change. See :hg:`backout` for a
6189 related method.
6191 related method.
6190
6192
6191 Modified files are saved with a .orig suffix before reverting.
6193 Modified files are saved with a .orig suffix before reverting.
6192 To disable these backups, use --no-backup. It is possible to store
6194 To disable these backups, use --no-backup. It is possible to store
6193 the backup files in a custom directory relative to the root of the
6195 the backup files in a custom directory relative to the root of the
6194 repository by setting the ``ui.origbackuppath`` configuration
6196 repository by setting the ``ui.origbackuppath`` configuration
6195 option.
6197 option.
6196
6198
6197 See :hg:`help dates` for a list of formats valid for -d/--date.
6199 See :hg:`help dates` for a list of formats valid for -d/--date.
6198
6200
6199 See :hg:`help backout` for a way to reverse the effect of an
6201 See :hg:`help backout` for a way to reverse the effect of an
6200 earlier changeset.
6202 earlier changeset.
6201
6203
6202 Returns 0 on success.
6204 Returns 0 on success.
6203 """
6205 """
6204
6206
6205 opts = pycompat.byteskwargs(opts)
6207 opts = pycompat.byteskwargs(opts)
6206 if opts.get(b"date"):
6208 if opts.get(b"date"):
6207 if opts.get(b"rev"):
6209 if opts.get(b"rev"):
6208 raise error.Abort(_(b"you can't specify a revision and a date"))
6210 raise error.Abort(_(b"you can't specify a revision and a date"))
6209 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6211 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6210
6212
6211 parent, p2 = repo.dirstate.parents()
6213 parent, p2 = repo.dirstate.parents()
6212 if not opts.get(b'rev') and p2 != nullid:
6214 if not opts.get(b'rev') and p2 != nullid:
6213 # revert after merge is a trap for new users (issue2915)
6215 # revert after merge is a trap for new users (issue2915)
6214 raise error.Abort(
6216 raise error.Abort(
6215 _(b'uncommitted merge with no revision specified'),
6217 _(b'uncommitted merge with no revision specified'),
6216 hint=_(b"use 'hg update' or see 'hg help revert'"),
6218 hint=_(b"use 'hg update' or see 'hg help revert'"),
6217 )
6219 )
6218
6220
6219 rev = opts.get(b'rev')
6221 rev = opts.get(b'rev')
6220 if rev:
6222 if rev:
6221 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6223 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6222 ctx = scmutil.revsingle(repo, rev)
6224 ctx = scmutil.revsingle(repo, rev)
6223
6225
6224 if not (
6226 if not (
6225 pats
6227 pats
6226 or opts.get(b'include')
6228 or opts.get(b'include')
6227 or opts.get(b'exclude')
6229 or opts.get(b'exclude')
6228 or opts.get(b'all')
6230 or opts.get(b'all')
6229 or opts.get(b'interactive')
6231 or opts.get(b'interactive')
6230 ):
6232 ):
6231 msg = _(b"no files or directories specified")
6233 msg = _(b"no files or directories specified")
6232 if p2 != nullid:
6234 if p2 != nullid:
6233 hint = _(
6235 hint = _(
6234 b"uncommitted merge, use --all to discard all changes,"
6236 b"uncommitted merge, use --all to discard all changes,"
6235 b" or 'hg update -C .' to abort the merge"
6237 b" or 'hg update -C .' to abort the merge"
6236 )
6238 )
6237 raise error.Abort(msg, hint=hint)
6239 raise error.Abort(msg, hint=hint)
6238 dirty = any(repo.status())
6240 dirty = any(repo.status())
6239 node = ctx.node()
6241 node = ctx.node()
6240 if node != parent:
6242 if node != parent:
6241 if dirty:
6243 if dirty:
6242 hint = (
6244 hint = (
6243 _(
6245 _(
6244 b"uncommitted changes, use --all to discard all"
6246 b"uncommitted changes, use --all to discard all"
6245 b" changes, or 'hg update %d' to update"
6247 b" changes, or 'hg update %d' to update"
6246 )
6248 )
6247 % ctx.rev()
6249 % ctx.rev()
6248 )
6250 )
6249 else:
6251 else:
6250 hint = (
6252 hint = (
6251 _(
6253 _(
6252 b"use --all to revert all files,"
6254 b"use --all to revert all files,"
6253 b" or 'hg update %d' to update"
6255 b" or 'hg update %d' to update"
6254 )
6256 )
6255 % ctx.rev()
6257 % ctx.rev()
6256 )
6258 )
6257 elif dirty:
6259 elif dirty:
6258 hint = _(b"uncommitted changes, use --all to discard all changes")
6260 hint = _(b"uncommitted changes, use --all to discard all changes")
6259 else:
6261 else:
6260 hint = _(b"use --all to revert all files")
6262 hint = _(b"use --all to revert all files")
6261 raise error.Abort(msg, hint=hint)
6263 raise error.Abort(msg, hint=hint)
6262
6264
6263 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6265 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6264
6266
6265
6267
6266 @command(
6268 @command(
6267 b'rollback',
6269 b'rollback',
6268 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6270 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6269 helpcategory=command.CATEGORY_MAINTENANCE,
6271 helpcategory=command.CATEGORY_MAINTENANCE,
6270 )
6272 )
6271 def rollback(ui, repo, **opts):
6273 def rollback(ui, repo, **opts):
6272 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6274 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6273
6275
6274 Please use :hg:`commit --amend` instead of rollback to correct
6276 Please use :hg:`commit --amend` instead of rollback to correct
6275 mistakes in the last commit.
6277 mistakes in the last commit.
6276
6278
6277 This command should be used with care. There is only one level of
6279 This command should be used with care. There is only one level of
6278 rollback, and there is no way to undo a rollback. It will also
6280 rollback, and there is no way to undo a rollback. It will also
6279 restore the dirstate at the time of the last transaction, losing
6281 restore the dirstate at the time of the last transaction, losing
6280 any dirstate changes since that time. This command does not alter
6282 any dirstate changes since that time. This command does not alter
6281 the working directory.
6283 the working directory.
6282
6284
6283 Transactions are used to encapsulate the effects of all commands
6285 Transactions are used to encapsulate the effects of all commands
6284 that create new changesets or propagate existing changesets into a
6286 that create new changesets or propagate existing changesets into a
6285 repository.
6287 repository.
6286
6288
6287 .. container:: verbose
6289 .. container:: verbose
6288
6290
6289 For example, the following commands are transactional, and their
6291 For example, the following commands are transactional, and their
6290 effects can be rolled back:
6292 effects can be rolled back:
6291
6293
6292 - commit
6294 - commit
6293 - import
6295 - import
6294 - pull
6296 - pull
6295 - push (with this repository as the destination)
6297 - push (with this repository as the destination)
6296 - unbundle
6298 - unbundle
6297
6299
6298 To avoid permanent data loss, rollback will refuse to rollback a
6300 To avoid permanent data loss, rollback will refuse to rollback a
6299 commit transaction if it isn't checked out. Use --force to
6301 commit transaction if it isn't checked out. Use --force to
6300 override this protection.
6302 override this protection.
6301
6303
6302 The rollback command can be entirely disabled by setting the
6304 The rollback command can be entirely disabled by setting the
6303 ``ui.rollback`` configuration setting to false. If you're here
6305 ``ui.rollback`` configuration setting to false. If you're here
6304 because you want to use rollback and it's disabled, you can
6306 because you want to use rollback and it's disabled, you can
6305 re-enable the command by setting ``ui.rollback`` to true.
6307 re-enable the command by setting ``ui.rollback`` to true.
6306
6308
6307 This command is not intended for use on public repositories. Once
6309 This command is not intended for use on public repositories. Once
6308 changes are visible for pull by other users, rolling a transaction
6310 changes are visible for pull by other users, rolling a transaction
6309 back locally is ineffective (someone else may already have pulled
6311 back locally is ineffective (someone else may already have pulled
6310 the changes). Furthermore, a race is possible with readers of the
6312 the changes). Furthermore, a race is possible with readers of the
6311 repository; for example an in-progress pull from the repository
6313 repository; for example an in-progress pull from the repository
6312 may fail if a rollback is performed.
6314 may fail if a rollback is performed.
6313
6315
6314 Returns 0 on success, 1 if no rollback data is available.
6316 Returns 0 on success, 1 if no rollback data is available.
6315 """
6317 """
6316 if not ui.configbool(b'ui', b'rollback'):
6318 if not ui.configbool(b'ui', b'rollback'):
6317 raise error.Abort(
6319 raise error.Abort(
6318 _(b'rollback is disabled because it is unsafe'),
6320 _(b'rollback is disabled because it is unsafe'),
6319 hint=b'see `hg help -v rollback` for information',
6321 hint=b'see `hg help -v rollback` for information',
6320 )
6322 )
6321 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6323 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6322
6324
6323
6325
6324 @command(
6326 @command(
6325 b'root',
6327 b'root',
6326 [] + formatteropts,
6328 [] + formatteropts,
6327 intents={INTENT_READONLY},
6329 intents={INTENT_READONLY},
6328 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6330 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6329 )
6331 )
6330 def root(ui, repo, **opts):
6332 def root(ui, repo, **opts):
6331 """print the root (top) of the current working directory
6333 """print the root (top) of the current working directory
6332
6334
6333 Print the root directory of the current repository.
6335 Print the root directory of the current repository.
6334
6336
6335 .. container:: verbose
6337 .. container:: verbose
6336
6338
6337 Template:
6339 Template:
6338
6340
6339 The following keywords are supported in addition to the common template
6341 The following keywords are supported in addition to the common template
6340 keywords and functions. See also :hg:`help templates`.
6342 keywords and functions. See also :hg:`help templates`.
6341
6343
6342 :hgpath: String. Path to the .hg directory.
6344 :hgpath: String. Path to the .hg directory.
6343 :storepath: String. Path to the directory holding versioned data.
6345 :storepath: String. Path to the directory holding versioned data.
6344
6346
6345 Returns 0 on success.
6347 Returns 0 on success.
6346 """
6348 """
6347 opts = pycompat.byteskwargs(opts)
6349 opts = pycompat.byteskwargs(opts)
6348 with ui.formatter(b'root', opts) as fm:
6350 with ui.formatter(b'root', opts) as fm:
6349 fm.startitem()
6351 fm.startitem()
6350 fm.write(b'reporoot', b'%s\n', repo.root)
6352 fm.write(b'reporoot', b'%s\n', repo.root)
6351 fm.data(hgpath=repo.path, storepath=repo.spath)
6353 fm.data(hgpath=repo.path, storepath=repo.spath)
6352
6354
6353
6355
6354 @command(
6356 @command(
6355 b'serve',
6357 b'serve',
6356 [
6358 [
6357 (
6359 (
6358 b'A',
6360 b'A',
6359 b'accesslog',
6361 b'accesslog',
6360 b'',
6362 b'',
6361 _(b'name of access log file to write to'),
6363 _(b'name of access log file to write to'),
6362 _(b'FILE'),
6364 _(b'FILE'),
6363 ),
6365 ),
6364 (b'd', b'daemon', None, _(b'run server in background')),
6366 (b'd', b'daemon', None, _(b'run server in background')),
6365 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6367 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6366 (
6368 (
6367 b'E',
6369 b'E',
6368 b'errorlog',
6370 b'errorlog',
6369 b'',
6371 b'',
6370 _(b'name of error log file to write to'),
6372 _(b'name of error log file to write to'),
6371 _(b'FILE'),
6373 _(b'FILE'),
6372 ),
6374 ),
6373 # use string type, then we can check if something was passed
6375 # use string type, then we can check if something was passed
6374 (
6376 (
6375 b'p',
6377 b'p',
6376 b'port',
6378 b'port',
6377 b'',
6379 b'',
6378 _(b'port to listen on (default: 8000)'),
6380 _(b'port to listen on (default: 8000)'),
6379 _(b'PORT'),
6381 _(b'PORT'),
6380 ),
6382 ),
6381 (
6383 (
6382 b'a',
6384 b'a',
6383 b'address',
6385 b'address',
6384 b'',
6386 b'',
6385 _(b'address to listen on (default: all interfaces)'),
6387 _(b'address to listen on (default: all interfaces)'),
6386 _(b'ADDR'),
6388 _(b'ADDR'),
6387 ),
6389 ),
6388 (
6390 (
6389 b'',
6391 b'',
6390 b'prefix',
6392 b'prefix',
6391 b'',
6393 b'',
6392 _(b'prefix path to serve from (default: server root)'),
6394 _(b'prefix path to serve from (default: server root)'),
6393 _(b'PREFIX'),
6395 _(b'PREFIX'),
6394 ),
6396 ),
6395 (
6397 (
6396 b'n',
6398 b'n',
6397 b'name',
6399 b'name',
6398 b'',
6400 b'',
6399 _(b'name to show in web pages (default: working directory)'),
6401 _(b'name to show in web pages (default: working directory)'),
6400 _(b'NAME'),
6402 _(b'NAME'),
6401 ),
6403 ),
6402 (
6404 (
6403 b'',
6405 b'',
6404 b'web-conf',
6406 b'web-conf',
6405 b'',
6407 b'',
6406 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6408 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6407 _(b'FILE'),
6409 _(b'FILE'),
6408 ),
6410 ),
6409 (
6411 (
6410 b'',
6412 b'',
6411 b'webdir-conf',
6413 b'webdir-conf',
6412 b'',
6414 b'',
6413 _(b'name of the hgweb config file (DEPRECATED)'),
6415 _(b'name of the hgweb config file (DEPRECATED)'),
6414 _(b'FILE'),
6416 _(b'FILE'),
6415 ),
6417 ),
6416 (
6418 (
6417 b'',
6419 b'',
6418 b'pid-file',
6420 b'pid-file',
6419 b'',
6421 b'',
6420 _(b'name of file to write process ID to'),
6422 _(b'name of file to write process ID to'),
6421 _(b'FILE'),
6423 _(b'FILE'),
6422 ),
6424 ),
6423 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6425 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6424 (
6426 (
6425 b'',
6427 b'',
6426 b'cmdserver',
6428 b'cmdserver',
6427 b'',
6429 b'',
6428 _(b'for remote clients (ADVANCED)'),
6430 _(b'for remote clients (ADVANCED)'),
6429 _(b'MODE'),
6431 _(b'MODE'),
6430 ),
6432 ),
6431 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6433 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6432 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6434 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6433 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6435 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6434 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6436 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6435 (b'', b'print-url', None, _(b'start and print only the URL')),
6437 (b'', b'print-url', None, _(b'start and print only the URL')),
6436 ]
6438 ]
6437 + subrepoopts,
6439 + subrepoopts,
6438 _(b'[OPTION]...'),
6440 _(b'[OPTION]...'),
6439 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6441 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6440 helpbasic=True,
6442 helpbasic=True,
6441 optionalrepo=True,
6443 optionalrepo=True,
6442 )
6444 )
6443 def serve(ui, repo, **opts):
6445 def serve(ui, repo, **opts):
6444 """start stand-alone webserver
6446 """start stand-alone webserver
6445
6447
6446 Start a local HTTP repository browser and pull server. You can use
6448 Start a local HTTP repository browser and pull server. You can use
6447 this for ad-hoc sharing and browsing of repositories. It is
6449 this for ad-hoc sharing and browsing of repositories. It is
6448 recommended to use a real web server to serve a repository for
6450 recommended to use a real web server to serve a repository for
6449 longer periods of time.
6451 longer periods of time.
6450
6452
6451 Please note that the server does not implement access control.
6453 Please note that the server does not implement access control.
6452 This means that, by default, anybody can read from the server and
6454 This means that, by default, anybody can read from the server and
6453 nobody can write to it by default. Set the ``web.allow-push``
6455 nobody can write to it by default. Set the ``web.allow-push``
6454 option to ``*`` to allow everybody to push to the server. You
6456 option to ``*`` to allow everybody to push to the server. You
6455 should use a real web server if you need to authenticate users.
6457 should use a real web server if you need to authenticate users.
6456
6458
6457 By default, the server logs accesses to stdout and errors to
6459 By default, the server logs accesses to stdout and errors to
6458 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6460 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6459 files.
6461 files.
6460
6462
6461 To have the server choose a free port number to listen on, specify
6463 To have the server choose a free port number to listen on, specify
6462 a port number of 0; in this case, the server will print the port
6464 a port number of 0; in this case, the server will print the port
6463 number it uses.
6465 number it uses.
6464
6466
6465 Returns 0 on success.
6467 Returns 0 on success.
6466 """
6468 """
6467
6469
6468 opts = pycompat.byteskwargs(opts)
6470 opts = pycompat.byteskwargs(opts)
6469 if opts[b"stdio"] and opts[b"cmdserver"]:
6471 if opts[b"stdio"] and opts[b"cmdserver"]:
6470 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6472 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6471 if opts[b"print_url"] and ui.verbose:
6473 if opts[b"print_url"] and ui.verbose:
6472 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6474 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6473
6475
6474 if opts[b"stdio"]:
6476 if opts[b"stdio"]:
6475 if repo is None:
6477 if repo is None:
6476 raise error.RepoError(
6478 raise error.RepoError(
6477 _(b"there is no Mercurial repository here (.hg not found)")
6479 _(b"there is no Mercurial repository here (.hg not found)")
6478 )
6480 )
6479 s = wireprotoserver.sshserver(ui, repo)
6481 s = wireprotoserver.sshserver(ui, repo)
6480 s.serve_forever()
6482 s.serve_forever()
6481
6483
6482 service = server.createservice(ui, repo, opts)
6484 service = server.createservice(ui, repo, opts)
6483 return server.runservice(opts, initfn=service.init, runfn=service.run)
6485 return server.runservice(opts, initfn=service.init, runfn=service.run)
6484
6486
6485
6487
6486 @command(
6488 @command(
6487 b'shelve',
6489 b'shelve',
6488 [
6490 [
6489 (
6491 (
6490 b'A',
6492 b'A',
6491 b'addremove',
6493 b'addremove',
6492 None,
6494 None,
6493 _(b'mark new/missing files as added/removed before shelving'),
6495 _(b'mark new/missing files as added/removed before shelving'),
6494 ),
6496 ),
6495 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6497 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6496 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6498 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6497 (
6499 (
6498 b'',
6500 b'',
6499 b'date',
6501 b'date',
6500 b'',
6502 b'',
6501 _(b'shelve with the specified commit date'),
6503 _(b'shelve with the specified commit date'),
6502 _(b'DATE'),
6504 _(b'DATE'),
6503 ),
6505 ),
6504 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6506 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6505 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6507 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6506 (
6508 (
6507 b'k',
6509 b'k',
6508 b'keep',
6510 b'keep',
6509 False,
6511 False,
6510 _(b'shelve, but keep changes in the working directory'),
6512 _(b'shelve, but keep changes in the working directory'),
6511 ),
6513 ),
6512 (b'l', b'list', None, _(b'list current shelves')),
6514 (b'l', b'list', None, _(b'list current shelves')),
6513 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6515 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6514 (
6516 (
6515 b'n',
6517 b'n',
6516 b'name',
6518 b'name',
6517 b'',
6519 b'',
6518 _(b'use the given name for the shelved commit'),
6520 _(b'use the given name for the shelved commit'),
6519 _(b'NAME'),
6521 _(b'NAME'),
6520 ),
6522 ),
6521 (
6523 (
6522 b'p',
6524 b'p',
6523 b'patch',
6525 b'patch',
6524 None,
6526 None,
6525 _(
6527 _(
6526 b'output patches for changes (provide the names of the shelved '
6528 b'output patches for changes (provide the names of the shelved '
6527 b'changes as positional arguments)'
6529 b'changes as positional arguments)'
6528 ),
6530 ),
6529 ),
6531 ),
6530 (b'i', b'interactive', None, _(b'interactive mode')),
6532 (b'i', b'interactive', None, _(b'interactive mode')),
6531 (
6533 (
6532 b'',
6534 b'',
6533 b'stat',
6535 b'stat',
6534 None,
6536 None,
6535 _(
6537 _(
6536 b'output diffstat-style summary of changes (provide the names of '
6538 b'output diffstat-style summary of changes (provide the names of '
6537 b'the shelved changes as positional arguments)'
6539 b'the shelved changes as positional arguments)'
6538 ),
6540 ),
6539 ),
6541 ),
6540 ]
6542 ]
6541 + cmdutil.walkopts,
6543 + cmdutil.walkopts,
6542 _(b'hg shelve [OPTION]... [FILE]...'),
6544 _(b'hg shelve [OPTION]... [FILE]...'),
6543 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6545 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6544 )
6546 )
6545 def shelve(ui, repo, *pats, **opts):
6547 def shelve(ui, repo, *pats, **opts):
6546 '''save and set aside changes from the working directory
6548 '''save and set aside changes from the working directory
6547
6549
6548 Shelving takes files that "hg status" reports as not clean, saves
6550 Shelving takes files that "hg status" reports as not clean, saves
6549 the modifications to a bundle (a shelved change), and reverts the
6551 the modifications to a bundle (a shelved change), and reverts the
6550 files so that their state in the working directory becomes clean.
6552 files so that their state in the working directory becomes clean.
6551
6553
6552 To restore these changes to the working directory, using "hg
6554 To restore these changes to the working directory, using "hg
6553 unshelve"; this will work even if you switch to a different
6555 unshelve"; this will work even if you switch to a different
6554 commit.
6556 commit.
6555
6557
6556 When no files are specified, "hg shelve" saves all not-clean
6558 When no files are specified, "hg shelve" saves all not-clean
6557 files. If specific files or directories are named, only changes to
6559 files. If specific files or directories are named, only changes to
6558 those files are shelved.
6560 those files are shelved.
6559
6561
6560 In bare shelve (when no files are specified, without interactive,
6562 In bare shelve (when no files are specified, without interactive,
6561 include and exclude option), shelving remembers information if the
6563 include and exclude option), shelving remembers information if the
6562 working directory was on newly created branch, in other words working
6564 working directory was on newly created branch, in other words working
6563 directory was on different branch than its first parent. In this
6565 directory was on different branch than its first parent. In this
6564 situation unshelving restores branch information to the working directory.
6566 situation unshelving restores branch information to the working directory.
6565
6567
6566 Each shelved change has a name that makes it easier to find later.
6568 Each shelved change has a name that makes it easier to find later.
6567 The name of a shelved change defaults to being based on the active
6569 The name of a shelved change defaults to being based on the active
6568 bookmark, or if there is no active bookmark, the current named
6570 bookmark, or if there is no active bookmark, the current named
6569 branch. To specify a different name, use ``--name``.
6571 branch. To specify a different name, use ``--name``.
6570
6572
6571 To see a list of existing shelved changes, use the ``--list``
6573 To see a list of existing shelved changes, use the ``--list``
6572 option. For each shelved change, this will print its name, age,
6574 option. For each shelved change, this will print its name, age,
6573 and description; use ``--patch`` or ``--stat`` for more details.
6575 and description; use ``--patch`` or ``--stat`` for more details.
6574
6576
6575 To delete specific shelved changes, use ``--delete``. To delete
6577 To delete specific shelved changes, use ``--delete``. To delete
6576 all shelved changes, use ``--cleanup``.
6578 all shelved changes, use ``--cleanup``.
6577 '''
6579 '''
6578 opts = pycompat.byteskwargs(opts)
6580 opts = pycompat.byteskwargs(opts)
6579 allowables = [
6581 allowables = [
6580 (b'addremove', {b'create'}), # 'create' is pseudo action
6582 (b'addremove', {b'create'}), # 'create' is pseudo action
6581 (b'unknown', {b'create'}),
6583 (b'unknown', {b'create'}),
6582 (b'cleanup', {b'cleanup'}),
6584 (b'cleanup', {b'cleanup'}),
6583 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6585 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6584 (b'delete', {b'delete'}),
6586 (b'delete', {b'delete'}),
6585 (b'edit', {b'create'}),
6587 (b'edit', {b'create'}),
6586 (b'keep', {b'create'}),
6588 (b'keep', {b'create'}),
6587 (b'list', {b'list'}),
6589 (b'list', {b'list'}),
6588 (b'message', {b'create'}),
6590 (b'message', {b'create'}),
6589 (b'name', {b'create'}),
6591 (b'name', {b'create'}),
6590 (b'patch', {b'patch', b'list'}),
6592 (b'patch', {b'patch', b'list'}),
6591 (b'stat', {b'stat', b'list'}),
6593 (b'stat', {b'stat', b'list'}),
6592 ]
6594 ]
6593
6595
6594 def checkopt(opt):
6596 def checkopt(opt):
6595 if opts.get(opt):
6597 if opts.get(opt):
6596 for i, allowable in allowables:
6598 for i, allowable in allowables:
6597 if opts[i] and opt not in allowable:
6599 if opts[i] and opt not in allowable:
6598 raise error.Abort(
6600 raise error.Abort(
6599 _(
6601 _(
6600 b"options '--%s' and '--%s' may not be "
6602 b"options '--%s' and '--%s' may not be "
6601 b"used together"
6603 b"used together"
6602 )
6604 )
6603 % (opt, i)
6605 % (opt, i)
6604 )
6606 )
6605 return True
6607 return True
6606
6608
6607 if checkopt(b'cleanup'):
6609 if checkopt(b'cleanup'):
6608 if pats:
6610 if pats:
6609 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6611 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6610 return shelvemod.cleanupcmd(ui, repo)
6612 return shelvemod.cleanupcmd(ui, repo)
6611 elif checkopt(b'delete'):
6613 elif checkopt(b'delete'):
6612 return shelvemod.deletecmd(ui, repo, pats)
6614 return shelvemod.deletecmd(ui, repo, pats)
6613 elif checkopt(b'list'):
6615 elif checkopt(b'list'):
6614 return shelvemod.listcmd(ui, repo, pats, opts)
6616 return shelvemod.listcmd(ui, repo, pats, opts)
6615 elif checkopt(b'patch') or checkopt(b'stat'):
6617 elif checkopt(b'patch') or checkopt(b'stat'):
6616 return shelvemod.patchcmds(ui, repo, pats, opts)
6618 return shelvemod.patchcmds(ui, repo, pats, opts)
6617 else:
6619 else:
6618 return shelvemod.createcmd(ui, repo, pats, opts)
6620 return shelvemod.createcmd(ui, repo, pats, opts)
6619
6621
6620
6622
6621 _NOTTERSE = b'nothing'
6623 _NOTTERSE = b'nothing'
6622
6624
6623
6625
6624 @command(
6626 @command(
6625 b'status|st',
6627 b'status|st',
6626 [
6628 [
6627 (b'A', b'all', None, _(b'show status of all files')),
6629 (b'A', b'all', None, _(b'show status of all files')),
6628 (b'm', b'modified', None, _(b'show only modified files')),
6630 (b'm', b'modified', None, _(b'show only modified files')),
6629 (b'a', b'added', None, _(b'show only added files')),
6631 (b'a', b'added', None, _(b'show only added files')),
6630 (b'r', b'removed', None, _(b'show only removed files')),
6632 (b'r', b'removed', None, _(b'show only removed files')),
6631 (b'd', b'deleted', None, _(b'show only missing files')),
6633 (b'd', b'deleted', None, _(b'show only missing files')),
6632 (b'c', b'clean', None, _(b'show only files without changes')),
6634 (b'c', b'clean', None, _(b'show only files without changes')),
6633 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6635 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6634 (b'i', b'ignored', None, _(b'show only ignored files')),
6636 (b'i', b'ignored', None, _(b'show only ignored files')),
6635 (b'n', b'no-status', None, _(b'hide status prefix')),
6637 (b'n', b'no-status', None, _(b'hide status prefix')),
6636 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6638 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6637 (
6639 (
6638 b'C',
6640 b'C',
6639 b'copies',
6641 b'copies',
6640 None,
6642 None,
6641 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6643 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6642 ),
6644 ),
6643 (
6645 (
6644 b'0',
6646 b'0',
6645 b'print0',
6647 b'print0',
6646 None,
6648 None,
6647 _(b'end filenames with NUL, for use with xargs'),
6649 _(b'end filenames with NUL, for use with xargs'),
6648 ),
6650 ),
6649 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6651 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6650 (
6652 (
6651 b'',
6653 b'',
6652 b'change',
6654 b'change',
6653 b'',
6655 b'',
6654 _(b'list the changed files of a revision'),
6656 _(b'list the changed files of a revision'),
6655 _(b'REV'),
6657 _(b'REV'),
6656 ),
6658 ),
6657 ]
6659 ]
6658 + walkopts
6660 + walkopts
6659 + subrepoopts
6661 + subrepoopts
6660 + formatteropts,
6662 + formatteropts,
6661 _(b'[OPTION]... [FILE]...'),
6663 _(b'[OPTION]... [FILE]...'),
6662 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6664 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6663 helpbasic=True,
6665 helpbasic=True,
6664 inferrepo=True,
6666 inferrepo=True,
6665 intents={INTENT_READONLY},
6667 intents={INTENT_READONLY},
6666 )
6668 )
6667 def status(ui, repo, *pats, **opts):
6669 def status(ui, repo, *pats, **opts):
6668 """show changed files in the working directory
6670 """show changed files in the working directory
6669
6671
6670 Show status of files in the repository. If names are given, only
6672 Show status of files in the repository. If names are given, only
6671 files that match are shown. Files that are clean or ignored or
6673 files that match are shown. Files that are clean or ignored or
6672 the source of a copy/move operation, are not listed unless
6674 the source of a copy/move operation, are not listed unless
6673 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6675 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6674 Unless options described with "show only ..." are given, the
6676 Unless options described with "show only ..." are given, the
6675 options -mardu are used.
6677 options -mardu are used.
6676
6678
6677 Option -q/--quiet hides untracked (unknown and ignored) files
6679 Option -q/--quiet hides untracked (unknown and ignored) files
6678 unless explicitly requested with -u/--unknown or -i/--ignored.
6680 unless explicitly requested with -u/--unknown or -i/--ignored.
6679
6681
6680 .. note::
6682 .. note::
6681
6683
6682 :hg:`status` may appear to disagree with diff if permissions have
6684 :hg:`status` may appear to disagree with diff if permissions have
6683 changed or a merge has occurred. The standard diff format does
6685 changed or a merge has occurred. The standard diff format does
6684 not report permission changes and diff only reports changes
6686 not report permission changes and diff only reports changes
6685 relative to one merge parent.
6687 relative to one merge parent.
6686
6688
6687 If one revision is given, it is used as the base revision.
6689 If one revision is given, it is used as the base revision.
6688 If two revisions are given, the differences between them are
6690 If two revisions are given, the differences between them are
6689 shown. The --change option can also be used as a shortcut to list
6691 shown. The --change option can also be used as a shortcut to list
6690 the changed files of a revision from its first parent.
6692 the changed files of a revision from its first parent.
6691
6693
6692 The codes used to show the status of files are::
6694 The codes used to show the status of files are::
6693
6695
6694 M = modified
6696 M = modified
6695 A = added
6697 A = added
6696 R = removed
6698 R = removed
6697 C = clean
6699 C = clean
6698 ! = missing (deleted by non-hg command, but still tracked)
6700 ! = missing (deleted by non-hg command, but still tracked)
6699 ? = not tracked
6701 ? = not tracked
6700 I = ignored
6702 I = ignored
6701 = origin of the previous file (with --copies)
6703 = origin of the previous file (with --copies)
6702
6704
6703 .. container:: verbose
6705 .. container:: verbose
6704
6706
6705 The -t/--terse option abbreviates the output by showing only the directory
6707 The -t/--terse option abbreviates the output by showing only the directory
6706 name if all the files in it share the same status. The option takes an
6708 name if all the files in it share the same status. The option takes an
6707 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6709 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6708 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6710 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6709 for 'ignored' and 'c' for clean.
6711 for 'ignored' and 'c' for clean.
6710
6712
6711 It abbreviates only those statuses which are passed. Note that clean and
6713 It abbreviates only those statuses which are passed. Note that clean and
6712 ignored files are not displayed with '--terse ic' unless the -c/--clean
6714 ignored files are not displayed with '--terse ic' unless the -c/--clean
6713 and -i/--ignored options are also used.
6715 and -i/--ignored options are also used.
6714
6716
6715 The -v/--verbose option shows information when the repository is in an
6717 The -v/--verbose option shows information when the repository is in an
6716 unfinished merge, shelve, rebase state etc. You can have this behavior
6718 unfinished merge, shelve, rebase state etc. You can have this behavior
6717 turned on by default by enabling the ``commands.status.verbose`` option.
6719 turned on by default by enabling the ``commands.status.verbose`` option.
6718
6720
6719 You can skip displaying some of these states by setting
6721 You can skip displaying some of these states by setting
6720 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6722 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6721 'histedit', 'merge', 'rebase', or 'unshelve'.
6723 'histedit', 'merge', 'rebase', or 'unshelve'.
6722
6724
6723 Template:
6725 Template:
6724
6726
6725 The following keywords are supported in addition to the common template
6727 The following keywords are supported in addition to the common template
6726 keywords and functions. See also :hg:`help templates`.
6728 keywords and functions. See also :hg:`help templates`.
6727
6729
6728 :path: String. Repository-absolute path of the file.
6730 :path: String. Repository-absolute path of the file.
6729 :source: String. Repository-absolute path of the file originated from.
6731 :source: String. Repository-absolute path of the file originated from.
6730 Available if ``--copies`` is specified.
6732 Available if ``--copies`` is specified.
6731 :status: String. Character denoting file's status.
6733 :status: String. Character denoting file's status.
6732
6734
6733 Examples:
6735 Examples:
6734
6736
6735 - show changes in the working directory relative to a
6737 - show changes in the working directory relative to a
6736 changeset::
6738 changeset::
6737
6739
6738 hg status --rev 9353
6740 hg status --rev 9353
6739
6741
6740 - show changes in the working directory relative to the
6742 - show changes in the working directory relative to the
6741 current directory (see :hg:`help patterns` for more information)::
6743 current directory (see :hg:`help patterns` for more information)::
6742
6744
6743 hg status re:
6745 hg status re:
6744
6746
6745 - show all changes including copies in an existing changeset::
6747 - show all changes including copies in an existing changeset::
6746
6748
6747 hg status --copies --change 9353
6749 hg status --copies --change 9353
6748
6750
6749 - get a NUL separated list of added files, suitable for xargs::
6751 - get a NUL separated list of added files, suitable for xargs::
6750
6752
6751 hg status -an0
6753 hg status -an0
6752
6754
6753 - show more information about the repository status, abbreviating
6755 - show more information about the repository status, abbreviating
6754 added, removed, modified, deleted, and untracked paths::
6756 added, removed, modified, deleted, and untracked paths::
6755
6757
6756 hg status -v -t mardu
6758 hg status -v -t mardu
6757
6759
6758 Returns 0 on success.
6760 Returns 0 on success.
6759
6761
6760 """
6762 """
6761
6763
6762 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6764 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6763 opts = pycompat.byteskwargs(opts)
6765 opts = pycompat.byteskwargs(opts)
6764 revs = opts.get(b'rev')
6766 revs = opts.get(b'rev')
6765 change = opts.get(b'change')
6767 change = opts.get(b'change')
6766 terse = opts.get(b'terse')
6768 terse = opts.get(b'terse')
6767 if terse is _NOTTERSE:
6769 if terse is _NOTTERSE:
6768 if revs:
6770 if revs:
6769 terse = b''
6771 terse = b''
6770 else:
6772 else:
6771 terse = ui.config(b'commands', b'status.terse')
6773 terse = ui.config(b'commands', b'status.terse')
6772
6774
6773 if revs and terse:
6775 if revs and terse:
6774 msg = _(b'cannot use --terse with --rev')
6776 msg = _(b'cannot use --terse with --rev')
6775 raise error.Abort(msg)
6777 raise error.Abort(msg)
6776 elif change:
6778 elif change:
6777 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6779 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6778 ctx2 = scmutil.revsingle(repo, change, None)
6780 ctx2 = scmutil.revsingle(repo, change, None)
6779 ctx1 = ctx2.p1()
6781 ctx1 = ctx2.p1()
6780 else:
6782 else:
6781 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6783 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6782 ctx1, ctx2 = scmutil.revpair(repo, revs)
6784 ctx1, ctx2 = scmutil.revpair(repo, revs)
6783
6785
6784 forcerelativevalue = None
6786 forcerelativevalue = None
6785 if ui.hasconfig(b'commands', b'status.relative'):
6787 if ui.hasconfig(b'commands', b'status.relative'):
6786 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6788 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6787 uipathfn = scmutil.getuipathfn(
6789 uipathfn = scmutil.getuipathfn(
6788 repo,
6790 repo,
6789 legacyrelativevalue=bool(pats),
6791 legacyrelativevalue=bool(pats),
6790 forcerelativevalue=forcerelativevalue,
6792 forcerelativevalue=forcerelativevalue,
6791 )
6793 )
6792
6794
6793 if opts.get(b'print0'):
6795 if opts.get(b'print0'):
6794 end = b'\0'
6796 end = b'\0'
6795 else:
6797 else:
6796 end = b'\n'
6798 end = b'\n'
6797 states = b'modified added removed deleted unknown ignored clean'.split()
6799 states = b'modified added removed deleted unknown ignored clean'.split()
6798 show = [k for k in states if opts.get(k)]
6800 show = [k for k in states if opts.get(k)]
6799 if opts.get(b'all'):
6801 if opts.get(b'all'):
6800 show += ui.quiet and (states[:4] + [b'clean']) or states
6802 show += ui.quiet and (states[:4] + [b'clean']) or states
6801
6803
6802 if not show:
6804 if not show:
6803 if ui.quiet:
6805 if ui.quiet:
6804 show = states[:4]
6806 show = states[:4]
6805 else:
6807 else:
6806 show = states[:5]
6808 show = states[:5]
6807
6809
6808 m = scmutil.match(ctx2, pats, opts)
6810 m = scmutil.match(ctx2, pats, opts)
6809 if terse:
6811 if terse:
6810 # we need to compute clean and unknown to terse
6812 # we need to compute clean and unknown to terse
6811 stat = repo.status(
6813 stat = repo.status(
6812 ctx1.node(),
6814 ctx1.node(),
6813 ctx2.node(),
6815 ctx2.node(),
6814 m,
6816 m,
6815 b'ignored' in show or b'i' in terse,
6817 b'ignored' in show or b'i' in terse,
6816 clean=True,
6818 clean=True,
6817 unknown=True,
6819 unknown=True,
6818 listsubrepos=opts.get(b'subrepos'),
6820 listsubrepos=opts.get(b'subrepos'),
6819 )
6821 )
6820
6822
6821 stat = cmdutil.tersedir(stat, terse)
6823 stat = cmdutil.tersedir(stat, terse)
6822 else:
6824 else:
6823 stat = repo.status(
6825 stat = repo.status(
6824 ctx1.node(),
6826 ctx1.node(),
6825 ctx2.node(),
6827 ctx2.node(),
6826 m,
6828 m,
6827 b'ignored' in show,
6829 b'ignored' in show,
6828 b'clean' in show,
6830 b'clean' in show,
6829 b'unknown' in show,
6831 b'unknown' in show,
6830 opts.get(b'subrepos'),
6832 opts.get(b'subrepos'),
6831 )
6833 )
6832
6834
6833 changestates = zip(
6835 changestates = zip(
6834 states,
6836 states,
6835 pycompat.iterbytestr(b'MAR!?IC'),
6837 pycompat.iterbytestr(b'MAR!?IC'),
6836 [getattr(stat, s.decode('utf8')) for s in states],
6838 [getattr(stat, s.decode('utf8')) for s in states],
6837 )
6839 )
6838
6840
6839 copy = {}
6841 copy = {}
6840 if (
6842 if (
6841 opts.get(b'all')
6843 opts.get(b'all')
6842 or opts.get(b'copies')
6844 or opts.get(b'copies')
6843 or ui.configbool(b'ui', b'statuscopies')
6845 or ui.configbool(b'ui', b'statuscopies')
6844 ) and not opts.get(b'no_status'):
6846 ) and not opts.get(b'no_status'):
6845 copy = copies.pathcopies(ctx1, ctx2, m)
6847 copy = copies.pathcopies(ctx1, ctx2, m)
6846
6848
6847 morestatus = None
6849 morestatus = None
6848 if (
6850 if (
6849 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6851 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6850 ) and not ui.plain():
6852 ) and not ui.plain():
6851 morestatus = cmdutil.readmorestatus(repo)
6853 morestatus = cmdutil.readmorestatus(repo)
6852
6854
6853 ui.pager(b'status')
6855 ui.pager(b'status')
6854 fm = ui.formatter(b'status', opts)
6856 fm = ui.formatter(b'status', opts)
6855 fmt = b'%s' + end
6857 fmt = b'%s' + end
6856 showchar = not opts.get(b'no_status')
6858 showchar = not opts.get(b'no_status')
6857
6859
6858 for state, char, files in changestates:
6860 for state, char, files in changestates:
6859 if state in show:
6861 if state in show:
6860 label = b'status.' + state
6862 label = b'status.' + state
6861 for f in files:
6863 for f in files:
6862 fm.startitem()
6864 fm.startitem()
6863 fm.context(ctx=ctx2)
6865 fm.context(ctx=ctx2)
6864 fm.data(itemtype=b'file', path=f)
6866 fm.data(itemtype=b'file', path=f)
6865 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6867 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6866 fm.plain(fmt % uipathfn(f), label=label)
6868 fm.plain(fmt % uipathfn(f), label=label)
6867 if f in copy:
6869 if f in copy:
6868 fm.data(source=copy[f])
6870 fm.data(source=copy[f])
6869 fm.plain(
6871 fm.plain(
6870 (b' %s' + end) % uipathfn(copy[f]),
6872 (b' %s' + end) % uipathfn(copy[f]),
6871 label=b'status.copied',
6873 label=b'status.copied',
6872 )
6874 )
6873 if morestatus:
6875 if morestatus:
6874 morestatus.formatfile(f, fm)
6876 morestatus.formatfile(f, fm)
6875
6877
6876 if morestatus:
6878 if morestatus:
6877 morestatus.formatfooter(fm)
6879 morestatus.formatfooter(fm)
6878 fm.end()
6880 fm.end()
6879
6881
6880
6882
6881 @command(
6883 @command(
6882 b'summary|sum',
6884 b'summary|sum',
6883 [(b'', b'remote', None, _(b'check for push and pull'))],
6885 [(b'', b'remote', None, _(b'check for push and pull'))],
6884 b'[--remote]',
6886 b'[--remote]',
6885 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6887 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6886 helpbasic=True,
6888 helpbasic=True,
6887 intents={INTENT_READONLY},
6889 intents={INTENT_READONLY},
6888 )
6890 )
6889 def summary(ui, repo, **opts):
6891 def summary(ui, repo, **opts):
6890 """summarize working directory state
6892 """summarize working directory state
6891
6893
6892 This generates a brief summary of the working directory state,
6894 This generates a brief summary of the working directory state,
6893 including parents, branch, commit status, phase and available updates.
6895 including parents, branch, commit status, phase and available updates.
6894
6896
6895 With the --remote option, this will check the default paths for
6897 With the --remote option, this will check the default paths for
6896 incoming and outgoing changes. This can be time-consuming.
6898 incoming and outgoing changes. This can be time-consuming.
6897
6899
6898 Returns 0 on success.
6900 Returns 0 on success.
6899 """
6901 """
6900
6902
6901 opts = pycompat.byteskwargs(opts)
6903 opts = pycompat.byteskwargs(opts)
6902 ui.pager(b'summary')
6904 ui.pager(b'summary')
6903 ctx = repo[None]
6905 ctx = repo[None]
6904 parents = ctx.parents()
6906 parents = ctx.parents()
6905 pnode = parents[0].node()
6907 pnode = parents[0].node()
6906 marks = []
6908 marks = []
6907
6909
6908 try:
6910 try:
6909 ms = mergestatemod.mergestate.read(repo)
6911 ms = mergestatemod.mergestate.read(repo)
6910 except error.UnsupportedMergeRecords as e:
6912 except error.UnsupportedMergeRecords as e:
6911 s = b' '.join(e.recordtypes)
6913 s = b' '.join(e.recordtypes)
6912 ui.warn(
6914 ui.warn(
6913 _(b'warning: merge state has unsupported record types: %s\n') % s
6915 _(b'warning: merge state has unsupported record types: %s\n') % s
6914 )
6916 )
6915 unresolved = []
6917 unresolved = []
6916 else:
6918 else:
6917 unresolved = list(ms.unresolved())
6919 unresolved = list(ms.unresolved())
6918
6920
6919 for p in parents:
6921 for p in parents:
6920 # label with log.changeset (instead of log.parent) since this
6922 # label with log.changeset (instead of log.parent) since this
6921 # shows a working directory parent *changeset*:
6923 # shows a working directory parent *changeset*:
6922 # i18n: column positioning for "hg summary"
6924 # i18n: column positioning for "hg summary"
6923 ui.write(
6925 ui.write(
6924 _(b'parent: %d:%s ') % (p.rev(), p),
6926 _(b'parent: %d:%s ') % (p.rev(), p),
6925 label=logcmdutil.changesetlabels(p),
6927 label=logcmdutil.changesetlabels(p),
6926 )
6928 )
6927 ui.write(b' '.join(p.tags()), label=b'log.tag')
6929 ui.write(b' '.join(p.tags()), label=b'log.tag')
6928 if p.bookmarks():
6930 if p.bookmarks():
6929 marks.extend(p.bookmarks())
6931 marks.extend(p.bookmarks())
6930 if p.rev() == -1:
6932 if p.rev() == -1:
6931 if not len(repo):
6933 if not len(repo):
6932 ui.write(_(b' (empty repository)'))
6934 ui.write(_(b' (empty repository)'))
6933 else:
6935 else:
6934 ui.write(_(b' (no revision checked out)'))
6936 ui.write(_(b' (no revision checked out)'))
6935 if p.obsolete():
6937 if p.obsolete():
6936 ui.write(_(b' (obsolete)'))
6938 ui.write(_(b' (obsolete)'))
6937 if p.isunstable():
6939 if p.isunstable():
6938 instabilities = (
6940 instabilities = (
6939 ui.label(instability, b'trouble.%s' % instability)
6941 ui.label(instability, b'trouble.%s' % instability)
6940 for instability in p.instabilities()
6942 for instability in p.instabilities()
6941 )
6943 )
6942 ui.write(b' (' + b', '.join(instabilities) + b')')
6944 ui.write(b' (' + b', '.join(instabilities) + b')')
6943 ui.write(b'\n')
6945 ui.write(b'\n')
6944 if p.description():
6946 if p.description():
6945 ui.status(
6947 ui.status(
6946 b' ' + p.description().splitlines()[0].strip() + b'\n',
6948 b' ' + p.description().splitlines()[0].strip() + b'\n',
6947 label=b'log.summary',
6949 label=b'log.summary',
6948 )
6950 )
6949
6951
6950 branch = ctx.branch()
6952 branch = ctx.branch()
6951 bheads = repo.branchheads(branch)
6953 bheads = repo.branchheads(branch)
6952 # i18n: column positioning for "hg summary"
6954 # i18n: column positioning for "hg summary"
6953 m = _(b'branch: %s\n') % branch
6955 m = _(b'branch: %s\n') % branch
6954 if branch != b'default':
6956 if branch != b'default':
6955 ui.write(m, label=b'log.branch')
6957 ui.write(m, label=b'log.branch')
6956 else:
6958 else:
6957 ui.status(m, label=b'log.branch')
6959 ui.status(m, label=b'log.branch')
6958
6960
6959 if marks:
6961 if marks:
6960 active = repo._activebookmark
6962 active = repo._activebookmark
6961 # i18n: column positioning for "hg summary"
6963 # i18n: column positioning for "hg summary"
6962 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6964 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6963 if active is not None:
6965 if active is not None:
6964 if active in marks:
6966 if active in marks:
6965 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6967 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6966 marks.remove(active)
6968 marks.remove(active)
6967 else:
6969 else:
6968 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6970 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6969 for m in marks:
6971 for m in marks:
6970 ui.write(b' ' + m, label=b'log.bookmark')
6972 ui.write(b' ' + m, label=b'log.bookmark')
6971 ui.write(b'\n', label=b'log.bookmark')
6973 ui.write(b'\n', label=b'log.bookmark')
6972
6974
6973 status = repo.status(unknown=True)
6975 status = repo.status(unknown=True)
6974
6976
6975 c = repo.dirstate.copies()
6977 c = repo.dirstate.copies()
6976 copied, renamed = [], []
6978 copied, renamed = [], []
6977 for d, s in pycompat.iteritems(c):
6979 for d, s in pycompat.iteritems(c):
6978 if s in status.removed:
6980 if s in status.removed:
6979 status.removed.remove(s)
6981 status.removed.remove(s)
6980 renamed.append(d)
6982 renamed.append(d)
6981 else:
6983 else:
6982 copied.append(d)
6984 copied.append(d)
6983 if d in status.added:
6985 if d in status.added:
6984 status.added.remove(d)
6986 status.added.remove(d)
6985
6987
6986 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
6988 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
6987
6989
6988 labels = [
6990 labels = [
6989 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
6991 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
6990 (ui.label(_(b'%d added'), b'status.added'), status.added),
6992 (ui.label(_(b'%d added'), b'status.added'), status.added),
6991 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
6993 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
6992 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
6994 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
6993 (ui.label(_(b'%d copied'), b'status.copied'), copied),
6995 (ui.label(_(b'%d copied'), b'status.copied'), copied),
6994 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
6996 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
6995 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
6997 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
6996 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
6998 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
6997 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
6999 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
6998 ]
7000 ]
6999 t = []
7001 t = []
7000 for l, s in labels:
7002 for l, s in labels:
7001 if s:
7003 if s:
7002 t.append(l % len(s))
7004 t.append(l % len(s))
7003
7005
7004 t = b', '.join(t)
7006 t = b', '.join(t)
7005 cleanworkdir = False
7007 cleanworkdir = False
7006
7008
7007 if repo.vfs.exists(b'graftstate'):
7009 if repo.vfs.exists(b'graftstate'):
7008 t += _(b' (graft in progress)')
7010 t += _(b' (graft in progress)')
7009 if repo.vfs.exists(b'updatestate'):
7011 if repo.vfs.exists(b'updatestate'):
7010 t += _(b' (interrupted update)')
7012 t += _(b' (interrupted update)')
7011 elif len(parents) > 1:
7013 elif len(parents) > 1:
7012 t += _(b' (merge)')
7014 t += _(b' (merge)')
7013 elif branch != parents[0].branch():
7015 elif branch != parents[0].branch():
7014 t += _(b' (new branch)')
7016 t += _(b' (new branch)')
7015 elif parents[0].closesbranch() and pnode in repo.branchheads(
7017 elif parents[0].closesbranch() and pnode in repo.branchheads(
7016 branch, closed=True
7018 branch, closed=True
7017 ):
7019 ):
7018 t += _(b' (head closed)')
7020 t += _(b' (head closed)')
7019 elif not (
7021 elif not (
7020 status.modified
7022 status.modified
7021 or status.added
7023 or status.added
7022 or status.removed
7024 or status.removed
7023 or renamed
7025 or renamed
7024 or copied
7026 or copied
7025 or subs
7027 or subs
7026 ):
7028 ):
7027 t += _(b' (clean)')
7029 t += _(b' (clean)')
7028 cleanworkdir = True
7030 cleanworkdir = True
7029 elif pnode not in bheads:
7031 elif pnode not in bheads:
7030 t += _(b' (new branch head)')
7032 t += _(b' (new branch head)')
7031
7033
7032 if parents:
7034 if parents:
7033 pendingphase = max(p.phase() for p in parents)
7035 pendingphase = max(p.phase() for p in parents)
7034 else:
7036 else:
7035 pendingphase = phases.public
7037 pendingphase = phases.public
7036
7038
7037 if pendingphase > phases.newcommitphase(ui):
7039 if pendingphase > phases.newcommitphase(ui):
7038 t += b' (%s)' % phases.phasenames[pendingphase]
7040 t += b' (%s)' % phases.phasenames[pendingphase]
7039
7041
7040 if cleanworkdir:
7042 if cleanworkdir:
7041 # i18n: column positioning for "hg summary"
7043 # i18n: column positioning for "hg summary"
7042 ui.status(_(b'commit: %s\n') % t.strip())
7044 ui.status(_(b'commit: %s\n') % t.strip())
7043 else:
7045 else:
7044 # i18n: column positioning for "hg summary"
7046 # i18n: column positioning for "hg summary"
7045 ui.write(_(b'commit: %s\n') % t.strip())
7047 ui.write(_(b'commit: %s\n') % t.strip())
7046
7048
7047 # all ancestors of branch heads - all ancestors of parent = new csets
7049 # all ancestors of branch heads - all ancestors of parent = new csets
7048 new = len(
7050 new = len(
7049 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7051 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7050 )
7052 )
7051
7053
7052 if new == 0:
7054 if new == 0:
7053 # i18n: column positioning for "hg summary"
7055 # i18n: column positioning for "hg summary"
7054 ui.status(_(b'update: (current)\n'))
7056 ui.status(_(b'update: (current)\n'))
7055 elif pnode not in bheads:
7057 elif pnode not in bheads:
7056 # i18n: column positioning for "hg summary"
7058 # i18n: column positioning for "hg summary"
7057 ui.write(_(b'update: %d new changesets (update)\n') % new)
7059 ui.write(_(b'update: %d new changesets (update)\n') % new)
7058 else:
7060 else:
7059 # i18n: column positioning for "hg summary"
7061 # i18n: column positioning for "hg summary"
7060 ui.write(
7062 ui.write(
7061 _(b'update: %d new changesets, %d branch heads (merge)\n')
7063 _(b'update: %d new changesets, %d branch heads (merge)\n')
7062 % (new, len(bheads))
7064 % (new, len(bheads))
7063 )
7065 )
7064
7066
7065 t = []
7067 t = []
7066 draft = len(repo.revs(b'draft()'))
7068 draft = len(repo.revs(b'draft()'))
7067 if draft:
7069 if draft:
7068 t.append(_(b'%d draft') % draft)
7070 t.append(_(b'%d draft') % draft)
7069 secret = len(repo.revs(b'secret()'))
7071 secret = len(repo.revs(b'secret()'))
7070 if secret:
7072 if secret:
7071 t.append(_(b'%d secret') % secret)
7073 t.append(_(b'%d secret') % secret)
7072
7074
7073 if draft or secret:
7075 if draft or secret:
7074 ui.status(_(b'phases: %s\n') % b', '.join(t))
7076 ui.status(_(b'phases: %s\n') % b', '.join(t))
7075
7077
7076 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7078 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7077 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7079 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7078 numtrouble = len(repo.revs(trouble + b"()"))
7080 numtrouble = len(repo.revs(trouble + b"()"))
7079 # We write all the possibilities to ease translation
7081 # We write all the possibilities to ease translation
7080 troublemsg = {
7082 troublemsg = {
7081 b"orphan": _(b"orphan: %d changesets"),
7083 b"orphan": _(b"orphan: %d changesets"),
7082 b"contentdivergent": _(b"content-divergent: %d changesets"),
7084 b"contentdivergent": _(b"content-divergent: %d changesets"),
7083 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7085 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7084 }
7086 }
7085 if numtrouble > 0:
7087 if numtrouble > 0:
7086 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7088 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7087
7089
7088 cmdutil.summaryhooks(ui, repo)
7090 cmdutil.summaryhooks(ui, repo)
7089
7091
7090 if opts.get(b'remote'):
7092 if opts.get(b'remote'):
7091 needsincoming, needsoutgoing = True, True
7093 needsincoming, needsoutgoing = True, True
7092 else:
7094 else:
7093 needsincoming, needsoutgoing = False, False
7095 needsincoming, needsoutgoing = False, False
7094 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7096 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7095 if i:
7097 if i:
7096 needsincoming = True
7098 needsincoming = True
7097 if o:
7099 if o:
7098 needsoutgoing = True
7100 needsoutgoing = True
7099 if not needsincoming and not needsoutgoing:
7101 if not needsincoming and not needsoutgoing:
7100 return
7102 return
7101
7103
7102 def getincoming():
7104 def getincoming():
7103 source, branches = hg.parseurl(ui.expandpath(b'default'))
7105 source, branches = hg.parseurl(ui.expandpath(b'default'))
7104 sbranch = branches[0]
7106 sbranch = branches[0]
7105 try:
7107 try:
7106 other = hg.peer(repo, {}, source)
7108 other = hg.peer(repo, {}, source)
7107 except error.RepoError:
7109 except error.RepoError:
7108 if opts.get(b'remote'):
7110 if opts.get(b'remote'):
7109 raise
7111 raise
7110 return source, sbranch, None, None, None
7112 return source, sbranch, None, None, None
7111 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7113 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7112 if revs:
7114 if revs:
7113 revs = [other.lookup(rev) for rev in revs]
7115 revs = [other.lookup(rev) for rev in revs]
7114 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7116 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7115 repo.ui.pushbuffer()
7117 repo.ui.pushbuffer()
7116 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7118 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7117 repo.ui.popbuffer()
7119 repo.ui.popbuffer()
7118 return source, sbranch, other, commoninc, commoninc[1]
7120 return source, sbranch, other, commoninc, commoninc[1]
7119
7121
7120 if needsincoming:
7122 if needsincoming:
7121 source, sbranch, sother, commoninc, incoming = getincoming()
7123 source, sbranch, sother, commoninc, incoming = getincoming()
7122 else:
7124 else:
7123 source = sbranch = sother = commoninc = incoming = None
7125 source = sbranch = sother = commoninc = incoming = None
7124
7126
7125 def getoutgoing():
7127 def getoutgoing():
7126 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7128 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7127 dbranch = branches[0]
7129 dbranch = branches[0]
7128 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7130 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7129 if source != dest:
7131 if source != dest:
7130 try:
7132 try:
7131 dother = hg.peer(repo, {}, dest)
7133 dother = hg.peer(repo, {}, dest)
7132 except error.RepoError:
7134 except error.RepoError:
7133 if opts.get(b'remote'):
7135 if opts.get(b'remote'):
7134 raise
7136 raise
7135 return dest, dbranch, None, None
7137 return dest, dbranch, None, None
7136 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7138 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7137 elif sother is None:
7139 elif sother is None:
7138 # there is no explicit destination peer, but source one is invalid
7140 # there is no explicit destination peer, but source one is invalid
7139 return dest, dbranch, None, None
7141 return dest, dbranch, None, None
7140 else:
7142 else:
7141 dother = sother
7143 dother = sother
7142 if source != dest or (sbranch is not None and sbranch != dbranch):
7144 if source != dest or (sbranch is not None and sbranch != dbranch):
7143 common = None
7145 common = None
7144 else:
7146 else:
7145 common = commoninc
7147 common = commoninc
7146 if revs:
7148 if revs:
7147 revs = [repo.lookup(rev) for rev in revs]
7149 revs = [repo.lookup(rev) for rev in revs]
7148 repo.ui.pushbuffer()
7150 repo.ui.pushbuffer()
7149 outgoing = discovery.findcommonoutgoing(
7151 outgoing = discovery.findcommonoutgoing(
7150 repo, dother, onlyheads=revs, commoninc=common
7152 repo, dother, onlyheads=revs, commoninc=common
7151 )
7153 )
7152 repo.ui.popbuffer()
7154 repo.ui.popbuffer()
7153 return dest, dbranch, dother, outgoing
7155 return dest, dbranch, dother, outgoing
7154
7156
7155 if needsoutgoing:
7157 if needsoutgoing:
7156 dest, dbranch, dother, outgoing = getoutgoing()
7158 dest, dbranch, dother, outgoing = getoutgoing()
7157 else:
7159 else:
7158 dest = dbranch = dother = outgoing = None
7160 dest = dbranch = dother = outgoing = None
7159
7161
7160 if opts.get(b'remote'):
7162 if opts.get(b'remote'):
7161 t = []
7163 t = []
7162 if incoming:
7164 if incoming:
7163 t.append(_(b'1 or more incoming'))
7165 t.append(_(b'1 or more incoming'))
7164 o = outgoing.missing
7166 o = outgoing.missing
7165 if o:
7167 if o:
7166 t.append(_(b'%d outgoing') % len(o))
7168 t.append(_(b'%d outgoing') % len(o))
7167 other = dother or sother
7169 other = dother or sother
7168 if b'bookmarks' in other.listkeys(b'namespaces'):
7170 if b'bookmarks' in other.listkeys(b'namespaces'):
7169 counts = bookmarks.summary(repo, other)
7171 counts = bookmarks.summary(repo, other)
7170 if counts[0] > 0:
7172 if counts[0] > 0:
7171 t.append(_(b'%d incoming bookmarks') % counts[0])
7173 t.append(_(b'%d incoming bookmarks') % counts[0])
7172 if counts[1] > 0:
7174 if counts[1] > 0:
7173 t.append(_(b'%d outgoing bookmarks') % counts[1])
7175 t.append(_(b'%d outgoing bookmarks') % counts[1])
7174
7176
7175 if t:
7177 if t:
7176 # i18n: column positioning for "hg summary"
7178 # i18n: column positioning for "hg summary"
7177 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7179 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7178 else:
7180 else:
7179 # i18n: column positioning for "hg summary"
7181 # i18n: column positioning for "hg summary"
7180 ui.status(_(b'remote: (synced)\n'))
7182 ui.status(_(b'remote: (synced)\n'))
7181
7183
7182 cmdutil.summaryremotehooks(
7184 cmdutil.summaryremotehooks(
7183 ui,
7185 ui,
7184 repo,
7186 repo,
7185 opts,
7187 opts,
7186 (
7188 (
7187 (source, sbranch, sother, commoninc),
7189 (source, sbranch, sother, commoninc),
7188 (dest, dbranch, dother, outgoing),
7190 (dest, dbranch, dother, outgoing),
7189 ),
7191 ),
7190 )
7192 )
7191
7193
7192
7194
7193 @command(
7195 @command(
7194 b'tag',
7196 b'tag',
7195 [
7197 [
7196 (b'f', b'force', None, _(b'force tag')),
7198 (b'f', b'force', None, _(b'force tag')),
7197 (b'l', b'local', None, _(b'make the tag local')),
7199 (b'l', b'local', None, _(b'make the tag local')),
7198 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7200 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7199 (b'', b'remove', None, _(b'remove a tag')),
7201 (b'', b'remove', None, _(b'remove a tag')),
7200 # -l/--local is already there, commitopts cannot be used
7202 # -l/--local is already there, commitopts cannot be used
7201 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7203 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7202 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7204 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7203 ]
7205 ]
7204 + commitopts2,
7206 + commitopts2,
7205 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7207 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7206 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7208 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7207 )
7209 )
7208 def tag(ui, repo, name1, *names, **opts):
7210 def tag(ui, repo, name1, *names, **opts):
7209 """add one or more tags for the current or given revision
7211 """add one or more tags for the current or given revision
7210
7212
7211 Name a particular revision using <name>.
7213 Name a particular revision using <name>.
7212
7214
7213 Tags are used to name particular revisions of the repository and are
7215 Tags are used to name particular revisions of the repository and are
7214 very useful to compare different revisions, to go back to significant
7216 very useful to compare different revisions, to go back to significant
7215 earlier versions or to mark branch points as releases, etc. Changing
7217 earlier versions or to mark branch points as releases, etc. Changing
7216 an existing tag is normally disallowed; use -f/--force to override.
7218 an existing tag is normally disallowed; use -f/--force to override.
7217
7219
7218 If no revision is given, the parent of the working directory is
7220 If no revision is given, the parent of the working directory is
7219 used.
7221 used.
7220
7222
7221 To facilitate version control, distribution, and merging of tags,
7223 To facilitate version control, distribution, and merging of tags,
7222 they are stored as a file named ".hgtags" which is managed similarly
7224 they are stored as a file named ".hgtags" which is managed similarly
7223 to other project files and can be hand-edited if necessary. This
7225 to other project files and can be hand-edited if necessary. This
7224 also means that tagging creates a new commit. The file
7226 also means that tagging creates a new commit. The file
7225 ".hg/localtags" is used for local tags (not shared among
7227 ".hg/localtags" is used for local tags (not shared among
7226 repositories).
7228 repositories).
7227
7229
7228 Tag commits are usually made at the head of a branch. If the parent
7230 Tag commits are usually made at the head of a branch. If the parent
7229 of the working directory is not a branch head, :hg:`tag` aborts; use
7231 of the working directory is not a branch head, :hg:`tag` aborts; use
7230 -f/--force to force the tag commit to be based on a non-head
7232 -f/--force to force the tag commit to be based on a non-head
7231 changeset.
7233 changeset.
7232
7234
7233 See :hg:`help dates` for a list of formats valid for -d/--date.
7235 See :hg:`help dates` for a list of formats valid for -d/--date.
7234
7236
7235 Since tag names have priority over branch names during revision
7237 Since tag names have priority over branch names during revision
7236 lookup, using an existing branch name as a tag name is discouraged.
7238 lookup, using an existing branch name as a tag name is discouraged.
7237
7239
7238 Returns 0 on success.
7240 Returns 0 on success.
7239 """
7241 """
7240 opts = pycompat.byteskwargs(opts)
7242 opts = pycompat.byteskwargs(opts)
7241 with repo.wlock(), repo.lock():
7243 with repo.wlock(), repo.lock():
7242 rev_ = b"."
7244 rev_ = b"."
7243 names = [t.strip() for t in (name1,) + names]
7245 names = [t.strip() for t in (name1,) + names]
7244 if len(names) != len(set(names)):
7246 if len(names) != len(set(names)):
7245 raise error.Abort(_(b'tag names must be unique'))
7247 raise error.Abort(_(b'tag names must be unique'))
7246 for n in names:
7248 for n in names:
7247 scmutil.checknewlabel(repo, n, b'tag')
7249 scmutil.checknewlabel(repo, n, b'tag')
7248 if not n:
7250 if not n:
7249 raise error.Abort(
7251 raise error.Abort(
7250 _(b'tag names cannot consist entirely of whitespace')
7252 _(b'tag names cannot consist entirely of whitespace')
7251 )
7253 )
7252 if opts.get(b'rev') and opts.get(b'remove'):
7254 if opts.get(b'rev') and opts.get(b'remove'):
7253 raise error.Abort(_(b"--rev and --remove are incompatible"))
7255 raise error.Abort(_(b"--rev and --remove are incompatible"))
7254 if opts.get(b'rev'):
7256 if opts.get(b'rev'):
7255 rev_ = opts[b'rev']
7257 rev_ = opts[b'rev']
7256 message = opts.get(b'message')
7258 message = opts.get(b'message')
7257 if opts.get(b'remove'):
7259 if opts.get(b'remove'):
7258 if opts.get(b'local'):
7260 if opts.get(b'local'):
7259 expectedtype = b'local'
7261 expectedtype = b'local'
7260 else:
7262 else:
7261 expectedtype = b'global'
7263 expectedtype = b'global'
7262
7264
7263 for n in names:
7265 for n in names:
7264 if repo.tagtype(n) == b'global':
7266 if repo.tagtype(n) == b'global':
7265 alltags = tagsmod.findglobaltags(ui, repo)
7267 alltags = tagsmod.findglobaltags(ui, repo)
7266 if alltags[n][0] == nullid:
7268 if alltags[n][0] == nullid:
7267 raise error.Abort(_(b"tag '%s' is already removed") % n)
7269 raise error.Abort(_(b"tag '%s' is already removed") % n)
7268 if not repo.tagtype(n):
7270 if not repo.tagtype(n):
7269 raise error.Abort(_(b"tag '%s' does not exist") % n)
7271 raise error.Abort(_(b"tag '%s' does not exist") % n)
7270 if repo.tagtype(n) != expectedtype:
7272 if repo.tagtype(n) != expectedtype:
7271 if expectedtype == b'global':
7273 if expectedtype == b'global':
7272 raise error.Abort(
7274 raise error.Abort(
7273 _(b"tag '%s' is not a global tag") % n
7275 _(b"tag '%s' is not a global tag") % n
7274 )
7276 )
7275 else:
7277 else:
7276 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7278 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7277 rev_ = b'null'
7279 rev_ = b'null'
7278 if not message:
7280 if not message:
7279 # we don't translate commit messages
7281 # we don't translate commit messages
7280 message = b'Removed tag %s' % b', '.join(names)
7282 message = b'Removed tag %s' % b', '.join(names)
7281 elif not opts.get(b'force'):
7283 elif not opts.get(b'force'):
7282 for n in names:
7284 for n in names:
7283 if n in repo.tags():
7285 if n in repo.tags():
7284 raise error.Abort(
7286 raise error.Abort(
7285 _(b"tag '%s' already exists (use -f to force)") % n
7287 _(b"tag '%s' already exists (use -f to force)") % n
7286 )
7288 )
7287 if not opts.get(b'local'):
7289 if not opts.get(b'local'):
7288 p1, p2 = repo.dirstate.parents()
7290 p1, p2 = repo.dirstate.parents()
7289 if p2 != nullid:
7291 if p2 != nullid:
7290 raise error.Abort(_(b'uncommitted merge'))
7292 raise error.Abort(_(b'uncommitted merge'))
7291 bheads = repo.branchheads()
7293 bheads = repo.branchheads()
7292 if not opts.get(b'force') and bheads and p1 not in bheads:
7294 if not opts.get(b'force') and bheads and p1 not in bheads:
7293 raise error.Abort(
7295 raise error.Abort(
7294 _(
7296 _(
7295 b'working directory is not at a branch head '
7297 b'working directory is not at a branch head '
7296 b'(use -f to force)'
7298 b'(use -f to force)'
7297 )
7299 )
7298 )
7300 )
7299 node = scmutil.revsingle(repo, rev_).node()
7301 node = scmutil.revsingle(repo, rev_).node()
7300
7302
7301 if not message:
7303 if not message:
7302 # we don't translate commit messages
7304 # we don't translate commit messages
7303 message = b'Added tag %s for changeset %s' % (
7305 message = b'Added tag %s for changeset %s' % (
7304 b', '.join(names),
7306 b', '.join(names),
7305 short(node),
7307 short(node),
7306 )
7308 )
7307
7309
7308 date = opts.get(b'date')
7310 date = opts.get(b'date')
7309 if date:
7311 if date:
7310 date = dateutil.parsedate(date)
7312 date = dateutil.parsedate(date)
7311
7313
7312 if opts.get(b'remove'):
7314 if opts.get(b'remove'):
7313 editform = b'tag.remove'
7315 editform = b'tag.remove'
7314 else:
7316 else:
7315 editform = b'tag.add'
7317 editform = b'tag.add'
7316 editor = cmdutil.getcommiteditor(
7318 editor = cmdutil.getcommiteditor(
7317 editform=editform, **pycompat.strkwargs(opts)
7319 editform=editform, **pycompat.strkwargs(opts)
7318 )
7320 )
7319
7321
7320 # don't allow tagging the null rev
7322 # don't allow tagging the null rev
7321 if (
7323 if (
7322 not opts.get(b'remove')
7324 not opts.get(b'remove')
7323 and scmutil.revsingle(repo, rev_).rev() == nullrev
7325 and scmutil.revsingle(repo, rev_).rev() == nullrev
7324 ):
7326 ):
7325 raise error.Abort(_(b"cannot tag null revision"))
7327 raise error.Abort(_(b"cannot tag null revision"))
7326
7328
7327 tagsmod.tag(
7329 tagsmod.tag(
7328 repo,
7330 repo,
7329 names,
7331 names,
7330 node,
7332 node,
7331 message,
7333 message,
7332 opts.get(b'local'),
7334 opts.get(b'local'),
7333 opts.get(b'user'),
7335 opts.get(b'user'),
7334 date,
7336 date,
7335 editor=editor,
7337 editor=editor,
7336 )
7338 )
7337
7339
7338
7340
7339 @command(
7341 @command(
7340 b'tags',
7342 b'tags',
7341 formatteropts,
7343 formatteropts,
7342 b'',
7344 b'',
7343 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7345 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7344 intents={INTENT_READONLY},
7346 intents={INTENT_READONLY},
7345 )
7347 )
7346 def tags(ui, repo, **opts):
7348 def tags(ui, repo, **opts):
7347 """list repository tags
7349 """list repository tags
7348
7350
7349 This lists both regular and local tags. When the -v/--verbose
7351 This lists both regular and local tags. When the -v/--verbose
7350 switch is used, a third column "local" is printed for local tags.
7352 switch is used, a third column "local" is printed for local tags.
7351 When the -q/--quiet switch is used, only the tag name is printed.
7353 When the -q/--quiet switch is used, only the tag name is printed.
7352
7354
7353 .. container:: verbose
7355 .. container:: verbose
7354
7356
7355 Template:
7357 Template:
7356
7358
7357 The following keywords are supported in addition to the common template
7359 The following keywords are supported in addition to the common template
7358 keywords and functions such as ``{tag}``. See also
7360 keywords and functions such as ``{tag}``. See also
7359 :hg:`help templates`.
7361 :hg:`help templates`.
7360
7362
7361 :type: String. ``local`` for local tags.
7363 :type: String. ``local`` for local tags.
7362
7364
7363 Returns 0 on success.
7365 Returns 0 on success.
7364 """
7366 """
7365
7367
7366 opts = pycompat.byteskwargs(opts)
7368 opts = pycompat.byteskwargs(opts)
7367 ui.pager(b'tags')
7369 ui.pager(b'tags')
7368 fm = ui.formatter(b'tags', opts)
7370 fm = ui.formatter(b'tags', opts)
7369 hexfunc = fm.hexfunc
7371 hexfunc = fm.hexfunc
7370
7372
7371 for t, n in reversed(repo.tagslist()):
7373 for t, n in reversed(repo.tagslist()):
7372 hn = hexfunc(n)
7374 hn = hexfunc(n)
7373 label = b'tags.normal'
7375 label = b'tags.normal'
7374 tagtype = b''
7376 tagtype = b''
7375 if repo.tagtype(t) == b'local':
7377 if repo.tagtype(t) == b'local':
7376 label = b'tags.local'
7378 label = b'tags.local'
7377 tagtype = b'local'
7379 tagtype = b'local'
7378
7380
7379 fm.startitem()
7381 fm.startitem()
7380 fm.context(repo=repo)
7382 fm.context(repo=repo)
7381 fm.write(b'tag', b'%s', t, label=label)
7383 fm.write(b'tag', b'%s', t, label=label)
7382 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7384 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7383 fm.condwrite(
7385 fm.condwrite(
7384 not ui.quiet,
7386 not ui.quiet,
7385 b'rev node',
7387 b'rev node',
7386 fmt,
7388 fmt,
7387 repo.changelog.rev(n),
7389 repo.changelog.rev(n),
7388 hn,
7390 hn,
7389 label=label,
7391 label=label,
7390 )
7392 )
7391 fm.condwrite(
7393 fm.condwrite(
7392 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7394 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7393 )
7395 )
7394 fm.plain(b'\n')
7396 fm.plain(b'\n')
7395 fm.end()
7397 fm.end()
7396
7398
7397
7399
7398 @command(
7400 @command(
7399 b'tip',
7401 b'tip',
7400 [
7402 [
7401 (b'p', b'patch', None, _(b'show patch')),
7403 (b'p', b'patch', None, _(b'show patch')),
7402 (b'g', b'git', None, _(b'use git extended diff format')),
7404 (b'g', b'git', None, _(b'use git extended diff format')),
7403 ]
7405 ]
7404 + templateopts,
7406 + templateopts,
7405 _(b'[-p] [-g]'),
7407 _(b'[-p] [-g]'),
7406 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7408 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7407 )
7409 )
7408 def tip(ui, repo, **opts):
7410 def tip(ui, repo, **opts):
7409 """show the tip revision (DEPRECATED)
7411 """show the tip revision (DEPRECATED)
7410
7412
7411 The tip revision (usually just called the tip) is the changeset
7413 The tip revision (usually just called the tip) is the changeset
7412 most recently added to the repository (and therefore the most
7414 most recently added to the repository (and therefore the most
7413 recently changed head).
7415 recently changed head).
7414
7416
7415 If you have just made a commit, that commit will be the tip. If
7417 If you have just made a commit, that commit will be the tip. If
7416 you have just pulled changes from another repository, the tip of
7418 you have just pulled changes from another repository, the tip of
7417 that repository becomes the current tip. The "tip" tag is special
7419 that repository becomes the current tip. The "tip" tag is special
7418 and cannot be renamed or assigned to a different changeset.
7420 and cannot be renamed or assigned to a different changeset.
7419
7421
7420 This command is deprecated, please use :hg:`heads` instead.
7422 This command is deprecated, please use :hg:`heads` instead.
7421
7423
7422 Returns 0 on success.
7424 Returns 0 on success.
7423 """
7425 """
7424 opts = pycompat.byteskwargs(opts)
7426 opts = pycompat.byteskwargs(opts)
7425 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7427 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7426 displayer.show(repo[b'tip'])
7428 displayer.show(repo[b'tip'])
7427 displayer.close()
7429 displayer.close()
7428
7430
7429
7431
7430 @command(
7432 @command(
7431 b'unbundle',
7433 b'unbundle',
7432 [
7434 [
7433 (
7435 (
7434 b'u',
7436 b'u',
7435 b'update',
7437 b'update',
7436 None,
7438 None,
7437 _(b'update to new branch head if changesets were unbundled'),
7439 _(b'update to new branch head if changesets were unbundled'),
7438 )
7440 )
7439 ],
7441 ],
7440 _(b'[-u] FILE...'),
7442 _(b'[-u] FILE...'),
7441 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7443 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7442 )
7444 )
7443 def unbundle(ui, repo, fname1, *fnames, **opts):
7445 def unbundle(ui, repo, fname1, *fnames, **opts):
7444 """apply one or more bundle files
7446 """apply one or more bundle files
7445
7447
7446 Apply one or more bundle files generated by :hg:`bundle`.
7448 Apply one or more bundle files generated by :hg:`bundle`.
7447
7449
7448 Returns 0 on success, 1 if an update has unresolved files.
7450 Returns 0 on success, 1 if an update has unresolved files.
7449 """
7451 """
7450 fnames = (fname1,) + fnames
7452 fnames = (fname1,) + fnames
7451
7453
7452 with repo.lock():
7454 with repo.lock():
7453 for fname in fnames:
7455 for fname in fnames:
7454 f = hg.openpath(ui, fname)
7456 f = hg.openpath(ui, fname)
7455 gen = exchange.readbundle(ui, f, fname)
7457 gen = exchange.readbundle(ui, f, fname)
7456 if isinstance(gen, streamclone.streamcloneapplier):
7458 if isinstance(gen, streamclone.streamcloneapplier):
7457 raise error.Abort(
7459 raise error.Abort(
7458 _(
7460 _(
7459 b'packed bundles cannot be applied with '
7461 b'packed bundles cannot be applied with '
7460 b'"hg unbundle"'
7462 b'"hg unbundle"'
7461 ),
7463 ),
7462 hint=_(b'use "hg debugapplystreamclonebundle"'),
7464 hint=_(b'use "hg debugapplystreamclonebundle"'),
7463 )
7465 )
7464 url = b'bundle:' + fname
7466 url = b'bundle:' + fname
7465 try:
7467 try:
7466 txnname = b'unbundle'
7468 txnname = b'unbundle'
7467 if not isinstance(gen, bundle2.unbundle20):
7469 if not isinstance(gen, bundle2.unbundle20):
7468 txnname = b'unbundle\n%s' % util.hidepassword(url)
7470 txnname = b'unbundle\n%s' % util.hidepassword(url)
7469 with repo.transaction(txnname) as tr:
7471 with repo.transaction(txnname) as tr:
7470 op = bundle2.applybundle(
7472 op = bundle2.applybundle(
7471 repo, gen, tr, source=b'unbundle', url=url
7473 repo, gen, tr, source=b'unbundle', url=url
7472 )
7474 )
7473 except error.BundleUnknownFeatureError as exc:
7475 except error.BundleUnknownFeatureError as exc:
7474 raise error.Abort(
7476 raise error.Abort(
7475 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7477 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7476 hint=_(
7478 hint=_(
7477 b"see https://mercurial-scm.org/"
7479 b"see https://mercurial-scm.org/"
7478 b"wiki/BundleFeature for more "
7480 b"wiki/BundleFeature for more "
7479 b"information"
7481 b"information"
7480 ),
7482 ),
7481 )
7483 )
7482 modheads = bundle2.combinechangegroupresults(op)
7484 modheads = bundle2.combinechangegroupresults(op)
7483
7485
7484 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7486 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7485
7487
7486
7488
7487 @command(
7489 @command(
7488 b'unshelve',
7490 b'unshelve',
7489 [
7491 [
7490 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7492 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7491 (
7493 (
7492 b'c',
7494 b'c',
7493 b'continue',
7495 b'continue',
7494 None,
7496 None,
7495 _(b'continue an incomplete unshelve operation'),
7497 _(b'continue an incomplete unshelve operation'),
7496 ),
7498 ),
7497 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7499 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7498 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7500 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7499 (
7501 (
7500 b'n',
7502 b'n',
7501 b'name',
7503 b'name',
7502 b'',
7504 b'',
7503 _(b'restore shelved change with given name'),
7505 _(b'restore shelved change with given name'),
7504 _(b'NAME'),
7506 _(b'NAME'),
7505 ),
7507 ),
7506 (b't', b'tool', b'', _(b'specify merge tool')),
7508 (b't', b'tool', b'', _(b'specify merge tool')),
7507 (
7509 (
7508 b'',
7510 b'',
7509 b'date',
7511 b'date',
7510 b'',
7512 b'',
7511 _(b'set date for temporary commits (DEPRECATED)'),
7513 _(b'set date for temporary commits (DEPRECATED)'),
7512 _(b'DATE'),
7514 _(b'DATE'),
7513 ),
7515 ),
7514 ],
7516 ],
7515 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7517 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7516 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7518 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7517 )
7519 )
7518 def unshelve(ui, repo, *shelved, **opts):
7520 def unshelve(ui, repo, *shelved, **opts):
7519 """restore a shelved change to the working directory
7521 """restore a shelved change to the working directory
7520
7522
7521 This command accepts an optional name of a shelved change to
7523 This command accepts an optional name of a shelved change to
7522 restore. If none is given, the most recent shelved change is used.
7524 restore. If none is given, the most recent shelved change is used.
7523
7525
7524 If a shelved change is applied successfully, the bundle that
7526 If a shelved change is applied successfully, the bundle that
7525 contains the shelved changes is moved to a backup location
7527 contains the shelved changes is moved to a backup location
7526 (.hg/shelve-backup).
7528 (.hg/shelve-backup).
7527
7529
7528 Since you can restore a shelved change on top of an arbitrary
7530 Since you can restore a shelved change on top of an arbitrary
7529 commit, it is possible that unshelving will result in a conflict
7531 commit, it is possible that unshelving will result in a conflict
7530 between your changes and the commits you are unshelving onto. If
7532 between your changes and the commits you are unshelving onto. If
7531 this occurs, you must resolve the conflict, then use
7533 this occurs, you must resolve the conflict, then use
7532 ``--continue`` to complete the unshelve operation. (The bundle
7534 ``--continue`` to complete the unshelve operation. (The bundle
7533 will not be moved until you successfully complete the unshelve.)
7535 will not be moved until you successfully complete the unshelve.)
7534
7536
7535 (Alternatively, you can use ``--abort`` to abandon an unshelve
7537 (Alternatively, you can use ``--abort`` to abandon an unshelve
7536 that causes a conflict. This reverts the unshelved changes, and
7538 that causes a conflict. This reverts the unshelved changes, and
7537 leaves the bundle in place.)
7539 leaves the bundle in place.)
7538
7540
7539 If bare shelved change (without interactive, include and exclude
7541 If bare shelved change (without interactive, include and exclude
7540 option) was done on newly created branch it would restore branch
7542 option) was done on newly created branch it would restore branch
7541 information to the working directory.
7543 information to the working directory.
7542
7544
7543 After a successful unshelve, the shelved changes are stored in a
7545 After a successful unshelve, the shelved changes are stored in a
7544 backup directory. Only the N most recent backups are kept. N
7546 backup directory. Only the N most recent backups are kept. N
7545 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7547 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7546 configuration option.
7548 configuration option.
7547
7549
7548 .. container:: verbose
7550 .. container:: verbose
7549
7551
7550 Timestamp in seconds is used to decide order of backups. More
7552 Timestamp in seconds is used to decide order of backups. More
7551 than ``maxbackups`` backups are kept, if same timestamp
7553 than ``maxbackups`` backups are kept, if same timestamp
7552 prevents from deciding exact order of them, for safety.
7554 prevents from deciding exact order of them, for safety.
7553
7555
7554 Selected changes can be unshelved with ``--interactive`` flag.
7556 Selected changes can be unshelved with ``--interactive`` flag.
7555 The working directory is updated with the selected changes, and
7557 The working directory is updated with the selected changes, and
7556 only the unselected changes remain shelved.
7558 only the unselected changes remain shelved.
7557 Note: The whole shelve is applied to working directory first before
7559 Note: The whole shelve is applied to working directory first before
7558 running interactively. So, this will bring up all the conflicts between
7560 running interactively. So, this will bring up all the conflicts between
7559 working directory and the shelve, irrespective of which changes will be
7561 working directory and the shelve, irrespective of which changes will be
7560 unshelved.
7562 unshelved.
7561 """
7563 """
7562 with repo.wlock():
7564 with repo.wlock():
7563 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7565 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7564
7566
7565
7567
7566 statemod.addunfinished(
7568 statemod.addunfinished(
7567 b'unshelve',
7569 b'unshelve',
7568 fname=b'shelvedstate',
7570 fname=b'shelvedstate',
7569 continueflag=True,
7571 continueflag=True,
7570 abortfunc=shelvemod.hgabortunshelve,
7572 abortfunc=shelvemod.hgabortunshelve,
7571 continuefunc=shelvemod.hgcontinueunshelve,
7573 continuefunc=shelvemod.hgcontinueunshelve,
7572 cmdmsg=_(b'unshelve already in progress'),
7574 cmdmsg=_(b'unshelve already in progress'),
7573 )
7575 )
7574
7576
7575
7577
7576 @command(
7578 @command(
7577 b'update|up|checkout|co',
7579 b'update|up|checkout|co',
7578 [
7580 [
7579 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7581 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7580 (b'c', b'check', None, _(b'require clean working directory')),
7582 (b'c', b'check', None, _(b'require clean working directory')),
7581 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7583 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7582 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7584 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7583 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7585 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7584 ]
7586 ]
7585 + mergetoolopts,
7587 + mergetoolopts,
7586 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7588 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7587 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7589 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7588 helpbasic=True,
7590 helpbasic=True,
7589 )
7591 )
7590 def update(ui, repo, node=None, **opts):
7592 def update(ui, repo, node=None, **opts):
7591 """update working directory (or switch revisions)
7593 """update working directory (or switch revisions)
7592
7594
7593 Update the repository's working directory to the specified
7595 Update the repository's working directory to the specified
7594 changeset. If no changeset is specified, update to the tip of the
7596 changeset. If no changeset is specified, update to the tip of the
7595 current named branch and move the active bookmark (see :hg:`help
7597 current named branch and move the active bookmark (see :hg:`help
7596 bookmarks`).
7598 bookmarks`).
7597
7599
7598 Update sets the working directory's parent revision to the specified
7600 Update sets the working directory's parent revision to the specified
7599 changeset (see :hg:`help parents`).
7601 changeset (see :hg:`help parents`).
7600
7602
7601 If the changeset is not a descendant or ancestor of the working
7603 If the changeset is not a descendant or ancestor of the working
7602 directory's parent and there are uncommitted changes, the update is
7604 directory's parent and there are uncommitted changes, the update is
7603 aborted. With the -c/--check option, the working directory is checked
7605 aborted. With the -c/--check option, the working directory is checked
7604 for uncommitted changes; if none are found, the working directory is
7606 for uncommitted changes; if none are found, the working directory is
7605 updated to the specified changeset.
7607 updated to the specified changeset.
7606
7608
7607 .. container:: verbose
7609 .. container:: verbose
7608
7610
7609 The -C/--clean, -c/--check, and -m/--merge options control what
7611 The -C/--clean, -c/--check, and -m/--merge options control what
7610 happens if the working directory contains uncommitted changes.
7612 happens if the working directory contains uncommitted changes.
7611 At most of one of them can be specified.
7613 At most of one of them can be specified.
7612
7614
7613 1. If no option is specified, and if
7615 1. If no option is specified, and if
7614 the requested changeset is an ancestor or descendant of
7616 the requested changeset is an ancestor or descendant of
7615 the working directory's parent, the uncommitted changes
7617 the working directory's parent, the uncommitted changes
7616 are merged into the requested changeset and the merged
7618 are merged into the requested changeset and the merged
7617 result is left uncommitted. If the requested changeset is
7619 result is left uncommitted. If the requested changeset is
7618 not an ancestor or descendant (that is, it is on another
7620 not an ancestor or descendant (that is, it is on another
7619 branch), the update is aborted and the uncommitted changes
7621 branch), the update is aborted and the uncommitted changes
7620 are preserved.
7622 are preserved.
7621
7623
7622 2. With the -m/--merge option, the update is allowed even if the
7624 2. With the -m/--merge option, the update is allowed even if the
7623 requested changeset is not an ancestor or descendant of
7625 requested changeset is not an ancestor or descendant of
7624 the working directory's parent.
7626 the working directory's parent.
7625
7627
7626 3. With the -c/--check option, the update is aborted and the
7628 3. With the -c/--check option, the update is aborted and the
7627 uncommitted changes are preserved.
7629 uncommitted changes are preserved.
7628
7630
7629 4. With the -C/--clean option, uncommitted changes are discarded and
7631 4. With the -C/--clean option, uncommitted changes are discarded and
7630 the working directory is updated to the requested changeset.
7632 the working directory is updated to the requested changeset.
7631
7633
7632 To cancel an uncommitted merge (and lose your changes), use
7634 To cancel an uncommitted merge (and lose your changes), use
7633 :hg:`merge --abort`.
7635 :hg:`merge --abort`.
7634
7636
7635 Use null as the changeset to remove the working directory (like
7637 Use null as the changeset to remove the working directory (like
7636 :hg:`clone -U`).
7638 :hg:`clone -U`).
7637
7639
7638 If you want to revert just one file to an older revision, use
7640 If you want to revert just one file to an older revision, use
7639 :hg:`revert [-r REV] NAME`.
7641 :hg:`revert [-r REV] NAME`.
7640
7642
7641 See :hg:`help dates` for a list of formats valid for -d/--date.
7643 See :hg:`help dates` for a list of formats valid for -d/--date.
7642
7644
7643 Returns 0 on success, 1 if there are unresolved files.
7645 Returns 0 on success, 1 if there are unresolved files.
7644 """
7646 """
7645 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7647 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7646 rev = opts.get('rev')
7648 rev = opts.get('rev')
7647 date = opts.get('date')
7649 date = opts.get('date')
7648 clean = opts.get('clean')
7650 clean = opts.get('clean')
7649 check = opts.get('check')
7651 check = opts.get('check')
7650 merge = opts.get('merge')
7652 merge = opts.get('merge')
7651 if rev and node:
7653 if rev and node:
7652 raise error.Abort(_(b"please specify just one revision"))
7654 raise error.Abort(_(b"please specify just one revision"))
7653
7655
7654 if ui.configbool(b'commands', b'update.requiredest'):
7656 if ui.configbool(b'commands', b'update.requiredest'):
7655 if not node and not rev and not date:
7657 if not node and not rev and not date:
7656 raise error.Abort(
7658 raise error.Abort(
7657 _(b'you must specify a destination'),
7659 _(b'you must specify a destination'),
7658 hint=_(b'for example: hg update ".::"'),
7660 hint=_(b'for example: hg update ".::"'),
7659 )
7661 )
7660
7662
7661 if rev is None or rev == b'':
7663 if rev is None or rev == b'':
7662 rev = node
7664 rev = node
7663
7665
7664 if date and rev is not None:
7666 if date and rev is not None:
7665 raise error.Abort(_(b"you can't specify a revision and a date"))
7667 raise error.Abort(_(b"you can't specify a revision and a date"))
7666
7668
7667 updatecheck = None
7669 updatecheck = None
7668 if check:
7670 if check:
7669 updatecheck = b'abort'
7671 updatecheck = b'abort'
7670 elif merge:
7672 elif merge:
7671 updatecheck = b'none'
7673 updatecheck = b'none'
7672
7674
7673 with repo.wlock():
7675 with repo.wlock():
7674 cmdutil.clearunfinished(repo)
7676 cmdutil.clearunfinished(repo)
7675 if date:
7677 if date:
7676 rev = cmdutil.finddate(ui, repo, date)
7678 rev = cmdutil.finddate(ui, repo, date)
7677
7679
7678 # if we defined a bookmark, we have to remember the original name
7680 # if we defined a bookmark, we have to remember the original name
7679 brev = rev
7681 brev = rev
7680 if rev:
7682 if rev:
7681 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7683 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7682 ctx = scmutil.revsingle(repo, rev, default=None)
7684 ctx = scmutil.revsingle(repo, rev, default=None)
7683 rev = ctx.rev()
7685 rev = ctx.rev()
7684 hidden = ctx.hidden()
7686 hidden = ctx.hidden()
7685 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7687 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7686 with ui.configoverride(overrides, b'update'):
7688 with ui.configoverride(overrides, b'update'):
7687 ret = hg.updatetotally(
7689 ret = hg.updatetotally(
7688 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7690 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7689 )
7691 )
7690 if hidden:
7692 if hidden:
7691 ctxstr = ctx.hex()[:12]
7693 ctxstr = ctx.hex()[:12]
7692 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7694 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7693
7695
7694 if ctx.obsolete():
7696 if ctx.obsolete():
7695 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7697 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7696 ui.warn(b"(%s)\n" % obsfatemsg)
7698 ui.warn(b"(%s)\n" % obsfatemsg)
7697 return ret
7699 return ret
7698
7700
7699
7701
7700 @command(
7702 @command(
7701 b'verify',
7703 b'verify',
7702 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7704 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7703 helpcategory=command.CATEGORY_MAINTENANCE,
7705 helpcategory=command.CATEGORY_MAINTENANCE,
7704 )
7706 )
7705 def verify(ui, repo, **opts):
7707 def verify(ui, repo, **opts):
7706 """verify the integrity of the repository
7708 """verify the integrity of the repository
7707
7709
7708 Verify the integrity of the current repository.
7710 Verify the integrity of the current repository.
7709
7711
7710 This will perform an extensive check of the repository's
7712 This will perform an extensive check of the repository's
7711 integrity, validating the hashes and checksums of each entry in
7713 integrity, validating the hashes and checksums of each entry in
7712 the changelog, manifest, and tracked files, as well as the
7714 the changelog, manifest, and tracked files, as well as the
7713 integrity of their crosslinks and indices.
7715 integrity of their crosslinks and indices.
7714
7716
7715 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7717 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7716 for more information about recovery from corruption of the
7718 for more information about recovery from corruption of the
7717 repository.
7719 repository.
7718
7720
7719 Returns 0 on success, 1 if errors are encountered.
7721 Returns 0 on success, 1 if errors are encountered.
7720 """
7722 """
7721 opts = pycompat.byteskwargs(opts)
7723 opts = pycompat.byteskwargs(opts)
7722
7724
7723 level = None
7725 level = None
7724 if opts[b'full']:
7726 if opts[b'full']:
7725 level = verifymod.VERIFY_FULL
7727 level = verifymod.VERIFY_FULL
7726 return hg.verify(repo, level)
7728 return hg.verify(repo, level)
7727
7729
7728
7730
7729 @command(
7731 @command(
7730 b'version',
7732 b'version',
7731 [] + formatteropts,
7733 [] + formatteropts,
7732 helpcategory=command.CATEGORY_HELP,
7734 helpcategory=command.CATEGORY_HELP,
7733 norepo=True,
7735 norepo=True,
7734 intents={INTENT_READONLY},
7736 intents={INTENT_READONLY},
7735 )
7737 )
7736 def version_(ui, **opts):
7738 def version_(ui, **opts):
7737 """output version and copyright information
7739 """output version and copyright information
7738
7740
7739 .. container:: verbose
7741 .. container:: verbose
7740
7742
7741 Template:
7743 Template:
7742
7744
7743 The following keywords are supported. See also :hg:`help templates`.
7745 The following keywords are supported. See also :hg:`help templates`.
7744
7746
7745 :extensions: List of extensions.
7747 :extensions: List of extensions.
7746 :ver: String. Version number.
7748 :ver: String. Version number.
7747
7749
7748 And each entry of ``{extensions}`` provides the following sub-keywords
7750 And each entry of ``{extensions}`` provides the following sub-keywords
7749 in addition to ``{ver}``.
7751 in addition to ``{ver}``.
7750
7752
7751 :bundled: Boolean. True if included in the release.
7753 :bundled: Boolean. True if included in the release.
7752 :name: String. Extension name.
7754 :name: String. Extension name.
7753 """
7755 """
7754 opts = pycompat.byteskwargs(opts)
7756 opts = pycompat.byteskwargs(opts)
7755 if ui.verbose:
7757 if ui.verbose:
7756 ui.pager(b'version')
7758 ui.pager(b'version')
7757 fm = ui.formatter(b"version", opts)
7759 fm = ui.formatter(b"version", opts)
7758 fm.startitem()
7760 fm.startitem()
7759 fm.write(
7761 fm.write(
7760 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7762 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7761 )
7763 )
7762 license = _(
7764 license = _(
7763 b"(see https://mercurial-scm.org for more information)\n"
7765 b"(see https://mercurial-scm.org for more information)\n"
7764 b"\nCopyright (C) 2005-2020 Matt Mackall and others\n"
7766 b"\nCopyright (C) 2005-2020 Matt Mackall and others\n"
7765 b"This is free software; see the source for copying conditions. "
7767 b"This is free software; see the source for copying conditions. "
7766 b"There is NO\nwarranty; "
7768 b"There is NO\nwarranty; "
7767 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7769 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7768 )
7770 )
7769 if not ui.quiet:
7771 if not ui.quiet:
7770 fm.plain(license)
7772 fm.plain(license)
7771
7773
7772 if ui.verbose:
7774 if ui.verbose:
7773 fm.plain(_(b"\nEnabled extensions:\n\n"))
7775 fm.plain(_(b"\nEnabled extensions:\n\n"))
7774 # format names and versions into columns
7776 # format names and versions into columns
7775 names = []
7777 names = []
7776 vers = []
7778 vers = []
7777 isinternals = []
7779 isinternals = []
7778 for name, module in sorted(extensions.extensions()):
7780 for name, module in sorted(extensions.extensions()):
7779 names.append(name)
7781 names.append(name)
7780 vers.append(extensions.moduleversion(module) or None)
7782 vers.append(extensions.moduleversion(module) or None)
7781 isinternals.append(extensions.ismoduleinternal(module))
7783 isinternals.append(extensions.ismoduleinternal(module))
7782 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7784 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7783 if names:
7785 if names:
7784 namefmt = b" %%-%ds " % max(len(n) for n in names)
7786 namefmt = b" %%-%ds " % max(len(n) for n in names)
7785 places = [_(b"external"), _(b"internal")]
7787 places = [_(b"external"), _(b"internal")]
7786 for n, v, p in zip(names, vers, isinternals):
7788 for n, v, p in zip(names, vers, isinternals):
7787 fn.startitem()
7789 fn.startitem()
7788 fn.condwrite(ui.verbose, b"name", namefmt, n)
7790 fn.condwrite(ui.verbose, b"name", namefmt, n)
7789 if ui.verbose:
7791 if ui.verbose:
7790 fn.plain(b"%s " % places[p])
7792 fn.plain(b"%s " % places[p])
7791 fn.data(bundled=p)
7793 fn.data(bundled=p)
7792 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7794 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7793 if ui.verbose:
7795 if ui.verbose:
7794 fn.plain(b"\n")
7796 fn.plain(b"\n")
7795 fn.end()
7797 fn.end()
7796 fm.end()
7798 fm.end()
7797
7799
7798
7800
7799 def loadcmdtable(ui, name, cmdtable):
7801 def loadcmdtable(ui, name, cmdtable):
7800 """Load command functions from specified cmdtable
7802 """Load command functions from specified cmdtable
7801 """
7803 """
7802 overrides = [cmd for cmd in cmdtable if cmd in table]
7804 overrides = [cmd for cmd in cmdtable if cmd in table]
7803 if overrides:
7805 if overrides:
7804 ui.warn(
7806 ui.warn(
7805 _(b"extension '%s' overrides commands: %s\n")
7807 _(b"extension '%s' overrides commands: %s\n")
7806 % (name, b" ".join(overrides))
7808 % (name, b" ".join(overrides))
7807 )
7809 )
7808 table.update(cmdtable)
7810 table.update(cmdtable)
@@ -1,1103 +1,1131 b''
1 # logcmdutil.py - utility for log-like commands
1 # logcmdutil.py - utility for log-like commands
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 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import itertools
10 import itertools
11 import os
11 import os
12 import posixpath
12 import posixpath
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 nullid,
16 nullid,
17 wdirid,
17 wdirid,
18 wdirrev,
18 wdirrev,
19 )
19 )
20
20
21 from .thirdparty import attr
22
21 from . import (
23 from . import (
22 dagop,
24 dagop,
23 error,
25 error,
24 formatter,
26 formatter,
25 graphmod,
27 graphmod,
26 match as matchmod,
28 match as matchmod,
27 mdiff,
29 mdiff,
28 patch,
30 patch,
29 pathutil,
31 pathutil,
30 pycompat,
32 pycompat,
31 revset,
33 revset,
32 revsetlang,
34 revsetlang,
33 scmutil,
35 scmutil,
34 smartset,
36 smartset,
35 templatekw,
37 templatekw,
36 templater,
38 templater,
37 util,
39 util,
38 )
40 )
39 from .utils import (
41 from .utils import (
40 dateutil,
42 dateutil,
41 stringutil,
43 stringutil,
42 )
44 )
43
45
44
46
45 if pycompat.TYPE_CHECKING:
47 if pycompat.TYPE_CHECKING:
46 from typing import (
48 from typing import (
47 Any,
49 Any,
50 Dict,
51 List,
48 Optional,
52 Optional,
49 Tuple,
53 Tuple,
50 )
54 )
51
55
52 for t in (Any, Optional, Tuple):
56 for t in (Any, Dict, List, Optional, Tuple):
53 assert t
57 assert t
54
58
55
59
56 def getlimit(opts):
60 def getlimit(opts):
57 """get the log limit according to option -l/--limit"""
61 """get the log limit according to option -l/--limit"""
58 limit = opts.get(b'limit')
62 limit = opts.get(b'limit')
59 if limit:
63 if limit:
60 try:
64 try:
61 limit = int(limit)
65 limit = int(limit)
62 except ValueError:
66 except ValueError:
63 raise error.Abort(_(b'limit must be a positive integer'))
67 raise error.Abort(_(b'limit must be a positive integer'))
64 if limit <= 0:
68 if limit <= 0:
65 raise error.Abort(_(b'limit must be positive'))
69 raise error.Abort(_(b'limit must be positive'))
66 else:
70 else:
67 limit = None
71 limit = None
68 return limit
72 return limit
69
73
70
74
71 def diffordiffstat(
75 def diffordiffstat(
72 ui,
76 ui,
73 repo,
77 repo,
74 diffopts,
78 diffopts,
75 ctx1,
79 ctx1,
76 ctx2,
80 ctx2,
77 match,
81 match,
78 changes=None,
82 changes=None,
79 stat=False,
83 stat=False,
80 fp=None,
84 fp=None,
81 graphwidth=0,
85 graphwidth=0,
82 prefix=b'',
86 prefix=b'',
83 root=b'',
87 root=b'',
84 listsubrepos=False,
88 listsubrepos=False,
85 hunksfilterfn=None,
89 hunksfilterfn=None,
86 ):
90 ):
87 '''show diff or diffstat.'''
91 '''show diff or diffstat.'''
88 if root:
92 if root:
89 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
93 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
90 else:
94 else:
91 relroot = b''
95 relroot = b''
92 copysourcematch = None
96 copysourcematch = None
93
97
94 def compose(f, g):
98 def compose(f, g):
95 return lambda x: f(g(x))
99 return lambda x: f(g(x))
96
100
97 def pathfn(f):
101 def pathfn(f):
98 return posixpath.join(prefix, f)
102 return posixpath.join(prefix, f)
99
103
100 if relroot != b'':
104 if relroot != b'':
101 # XXX relative roots currently don't work if the root is within a
105 # XXX relative roots currently don't work if the root is within a
102 # subrepo
106 # subrepo
103 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
107 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
104 uirelroot = uipathfn(pathfn(relroot))
108 uirelroot = uipathfn(pathfn(relroot))
105 relroot += b'/'
109 relroot += b'/'
106 for matchroot in match.files():
110 for matchroot in match.files():
107 if not matchroot.startswith(relroot):
111 if not matchroot.startswith(relroot):
108 ui.warn(
112 ui.warn(
109 _(b'warning: %s not inside relative root %s\n')
113 _(b'warning: %s not inside relative root %s\n')
110 % (uipathfn(pathfn(matchroot)), uirelroot)
114 % (uipathfn(pathfn(matchroot)), uirelroot)
111 )
115 )
112
116
113 relrootmatch = scmutil.match(ctx2, pats=[relroot], default=b'path')
117 relrootmatch = scmutil.match(ctx2, pats=[relroot], default=b'path')
114 match = matchmod.intersectmatchers(match, relrootmatch)
118 match = matchmod.intersectmatchers(match, relrootmatch)
115 copysourcematch = relrootmatch
119 copysourcematch = relrootmatch
116
120
117 checkroot = repo.ui.configbool(
121 checkroot = repo.ui.configbool(
118 b'devel', b'all-warnings'
122 b'devel', b'all-warnings'
119 ) or repo.ui.configbool(b'devel', b'check-relroot')
123 ) or repo.ui.configbool(b'devel', b'check-relroot')
120
124
121 def relrootpathfn(f):
125 def relrootpathfn(f):
122 if checkroot and not f.startswith(relroot):
126 if checkroot and not f.startswith(relroot):
123 raise AssertionError(
127 raise AssertionError(
124 b"file %s doesn't start with relroot %s" % (f, relroot)
128 b"file %s doesn't start with relroot %s" % (f, relroot)
125 )
129 )
126 return f[len(relroot) :]
130 return f[len(relroot) :]
127
131
128 pathfn = compose(relrootpathfn, pathfn)
132 pathfn = compose(relrootpathfn, pathfn)
129
133
130 if stat:
134 if stat:
131 diffopts = diffopts.copy(context=0, noprefix=False)
135 diffopts = diffopts.copy(context=0, noprefix=False)
132 width = 80
136 width = 80
133 if not ui.plain():
137 if not ui.plain():
134 width = ui.termwidth() - graphwidth
138 width = ui.termwidth() - graphwidth
135 # If an explicit --root was given, don't respect ui.relative-paths
139 # If an explicit --root was given, don't respect ui.relative-paths
136 if not relroot:
140 if not relroot:
137 pathfn = compose(scmutil.getuipathfn(repo), pathfn)
141 pathfn = compose(scmutil.getuipathfn(repo), pathfn)
138
142
139 chunks = ctx2.diff(
143 chunks = ctx2.diff(
140 ctx1,
144 ctx1,
141 match,
145 match,
142 changes,
146 changes,
143 opts=diffopts,
147 opts=diffopts,
144 pathfn=pathfn,
148 pathfn=pathfn,
145 copysourcematch=copysourcematch,
149 copysourcematch=copysourcematch,
146 hunksfilterfn=hunksfilterfn,
150 hunksfilterfn=hunksfilterfn,
147 )
151 )
148
152
149 if fp is not None or ui.canwritewithoutlabels():
153 if fp is not None or ui.canwritewithoutlabels():
150 out = fp or ui
154 out = fp or ui
151 if stat:
155 if stat:
152 chunks = [patch.diffstat(util.iterlines(chunks), width=width)]
156 chunks = [patch.diffstat(util.iterlines(chunks), width=width)]
153 for chunk in util.filechunkiter(util.chunkbuffer(chunks)):
157 for chunk in util.filechunkiter(util.chunkbuffer(chunks)):
154 out.write(chunk)
158 out.write(chunk)
155 else:
159 else:
156 if stat:
160 if stat:
157 chunks = patch.diffstatui(util.iterlines(chunks), width=width)
161 chunks = patch.diffstatui(util.iterlines(chunks), width=width)
158 else:
162 else:
159 chunks = patch.difflabel(
163 chunks = patch.difflabel(
160 lambda chunks, **kwargs: chunks, chunks, opts=diffopts
164 lambda chunks, **kwargs: chunks, chunks, opts=diffopts
161 )
165 )
162 if ui.canbatchlabeledwrites():
166 if ui.canbatchlabeledwrites():
163
167
164 def gen():
168 def gen():
165 for chunk, label in chunks:
169 for chunk, label in chunks:
166 yield ui.label(chunk, label=label)
170 yield ui.label(chunk, label=label)
167
171
168 for chunk in util.filechunkiter(util.chunkbuffer(gen())):
172 for chunk in util.filechunkiter(util.chunkbuffer(gen())):
169 ui.write(chunk)
173 ui.write(chunk)
170 else:
174 else:
171 for chunk, label in chunks:
175 for chunk, label in chunks:
172 ui.write(chunk, label=label)
176 ui.write(chunk, label=label)
173
177
174 node2 = ctx2.node()
178 node2 = ctx2.node()
175 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
179 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
176 tempnode2 = node2
180 tempnode2 = node2
177 try:
181 try:
178 if node2 is not None:
182 if node2 is not None:
179 tempnode2 = ctx2.substate[subpath][1]
183 tempnode2 = ctx2.substate[subpath][1]
180 except KeyError:
184 except KeyError:
181 # A subrepo that existed in node1 was deleted between node1 and
185 # A subrepo that existed in node1 was deleted between node1 and
182 # node2 (inclusive). Thus, ctx2's substate won't contain that
186 # node2 (inclusive). Thus, ctx2's substate won't contain that
183 # subpath. The best we can do is to ignore it.
187 # subpath. The best we can do is to ignore it.
184 tempnode2 = None
188 tempnode2 = None
185 submatch = matchmod.subdirmatcher(subpath, match)
189 submatch = matchmod.subdirmatcher(subpath, match)
186 subprefix = repo.wvfs.reljoin(prefix, subpath)
190 subprefix = repo.wvfs.reljoin(prefix, subpath)
187 if listsubrepos or match.exact(subpath) or any(submatch.files()):
191 if listsubrepos or match.exact(subpath) or any(submatch.files()):
188 sub.diff(
192 sub.diff(
189 ui,
193 ui,
190 diffopts,
194 diffopts,
191 tempnode2,
195 tempnode2,
192 submatch,
196 submatch,
193 changes=changes,
197 changes=changes,
194 stat=stat,
198 stat=stat,
195 fp=fp,
199 fp=fp,
196 prefix=subprefix,
200 prefix=subprefix,
197 )
201 )
198
202
199
203
200 class changesetdiffer(object):
204 class changesetdiffer(object):
201 """Generate diff of changeset with pre-configured filtering functions"""
205 """Generate diff of changeset with pre-configured filtering functions"""
202
206
203 def _makefilematcher(self, ctx):
207 def _makefilematcher(self, ctx):
204 return scmutil.matchall(ctx.repo())
208 return scmutil.matchall(ctx.repo())
205
209
206 def _makehunksfilter(self, ctx):
210 def _makehunksfilter(self, ctx):
207 return None
211 return None
208
212
209 def showdiff(self, ui, ctx, diffopts, graphwidth=0, stat=False):
213 def showdiff(self, ui, ctx, diffopts, graphwidth=0, stat=False):
210 diffordiffstat(
214 diffordiffstat(
211 ui,
215 ui,
212 ctx.repo(),
216 ctx.repo(),
213 diffopts,
217 diffopts,
214 ctx.p1(),
218 ctx.p1(),
215 ctx,
219 ctx,
216 match=self._makefilematcher(ctx),
220 match=self._makefilematcher(ctx),
217 stat=stat,
221 stat=stat,
218 graphwidth=graphwidth,
222 graphwidth=graphwidth,
219 hunksfilterfn=self._makehunksfilter(ctx),
223 hunksfilterfn=self._makehunksfilter(ctx),
220 )
224 )
221
225
222
226
223 def changesetlabels(ctx):
227 def changesetlabels(ctx):
224 labels = [b'log.changeset', b'changeset.%s' % ctx.phasestr()]
228 labels = [b'log.changeset', b'changeset.%s' % ctx.phasestr()]
225 if ctx.obsolete():
229 if ctx.obsolete():
226 labels.append(b'changeset.obsolete')
230 labels.append(b'changeset.obsolete')
227 if ctx.isunstable():
231 if ctx.isunstable():
228 labels.append(b'changeset.unstable')
232 labels.append(b'changeset.unstable')
229 for instability in ctx.instabilities():
233 for instability in ctx.instabilities():
230 labels.append(b'instability.%s' % instability)
234 labels.append(b'instability.%s' % instability)
231 return b' '.join(labels)
235 return b' '.join(labels)
232
236
233
237
234 class changesetprinter(object):
238 class changesetprinter(object):
235 '''show changeset information when templating not requested.'''
239 '''show changeset information when templating not requested.'''
236
240
237 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
241 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
238 self.ui = ui
242 self.ui = ui
239 self.repo = repo
243 self.repo = repo
240 self.buffered = buffered
244 self.buffered = buffered
241 self._differ = differ or changesetdiffer()
245 self._differ = differ or changesetdiffer()
242 self._diffopts = patch.diffallopts(ui, diffopts)
246 self._diffopts = patch.diffallopts(ui, diffopts)
243 self._includestat = diffopts and diffopts.get(b'stat')
247 self._includestat = diffopts and diffopts.get(b'stat')
244 self._includediff = diffopts and diffopts.get(b'patch')
248 self._includediff = diffopts and diffopts.get(b'patch')
245 self.header = {}
249 self.header = {}
246 self.hunk = {}
250 self.hunk = {}
247 self.lastheader = None
251 self.lastheader = None
248 self.footer = None
252 self.footer = None
249 self._columns = templatekw.getlogcolumns()
253 self._columns = templatekw.getlogcolumns()
250
254
251 def flush(self, ctx):
255 def flush(self, ctx):
252 rev = ctx.rev()
256 rev = ctx.rev()
253 if rev in self.header:
257 if rev in self.header:
254 h = self.header[rev]
258 h = self.header[rev]
255 if h != self.lastheader:
259 if h != self.lastheader:
256 self.lastheader = h
260 self.lastheader = h
257 self.ui.write(h)
261 self.ui.write(h)
258 del self.header[rev]
262 del self.header[rev]
259 if rev in self.hunk:
263 if rev in self.hunk:
260 self.ui.write(self.hunk[rev])
264 self.ui.write(self.hunk[rev])
261 del self.hunk[rev]
265 del self.hunk[rev]
262
266
263 def close(self):
267 def close(self):
264 if self.footer:
268 if self.footer:
265 self.ui.write(self.footer)
269 self.ui.write(self.footer)
266
270
267 def show(self, ctx, copies=None, **props):
271 def show(self, ctx, copies=None, **props):
268 props = pycompat.byteskwargs(props)
272 props = pycompat.byteskwargs(props)
269 if self.buffered:
273 if self.buffered:
270 self.ui.pushbuffer(labeled=True)
274 self.ui.pushbuffer(labeled=True)
271 self._show(ctx, copies, props)
275 self._show(ctx, copies, props)
272 self.hunk[ctx.rev()] = self.ui.popbuffer()
276 self.hunk[ctx.rev()] = self.ui.popbuffer()
273 else:
277 else:
274 self._show(ctx, copies, props)
278 self._show(ctx, copies, props)
275
279
276 def _show(self, ctx, copies, props):
280 def _show(self, ctx, copies, props):
277 '''show a single changeset or file revision'''
281 '''show a single changeset or file revision'''
278 changenode = ctx.node()
282 changenode = ctx.node()
279 graphwidth = props.get(b'graphwidth', 0)
283 graphwidth = props.get(b'graphwidth', 0)
280
284
281 if self.ui.quiet:
285 if self.ui.quiet:
282 self.ui.write(
286 self.ui.write(
283 b"%s\n" % scmutil.formatchangeid(ctx), label=b'log.node'
287 b"%s\n" % scmutil.formatchangeid(ctx), label=b'log.node'
284 )
288 )
285 return
289 return
286
290
287 columns = self._columns
291 columns = self._columns
288 self.ui.write(
292 self.ui.write(
289 columns[b'changeset'] % scmutil.formatchangeid(ctx),
293 columns[b'changeset'] % scmutil.formatchangeid(ctx),
290 label=changesetlabels(ctx),
294 label=changesetlabels(ctx),
291 )
295 )
292
296
293 # branches are shown first before any other names due to backwards
297 # branches are shown first before any other names due to backwards
294 # compatibility
298 # compatibility
295 branch = ctx.branch()
299 branch = ctx.branch()
296 # don't show the default branch name
300 # don't show the default branch name
297 if branch != b'default':
301 if branch != b'default':
298 self.ui.write(columns[b'branch'] % branch, label=b'log.branch')
302 self.ui.write(columns[b'branch'] % branch, label=b'log.branch')
299
303
300 for nsname, ns in pycompat.iteritems(self.repo.names):
304 for nsname, ns in pycompat.iteritems(self.repo.names):
301 # branches has special logic already handled above, so here we just
305 # branches has special logic already handled above, so here we just
302 # skip it
306 # skip it
303 if nsname == b'branches':
307 if nsname == b'branches':
304 continue
308 continue
305 # we will use the templatename as the color name since those two
309 # we will use the templatename as the color name since those two
306 # should be the same
310 # should be the same
307 for name in ns.names(self.repo, changenode):
311 for name in ns.names(self.repo, changenode):
308 self.ui.write(ns.logfmt % name, label=b'log.%s' % ns.colorname)
312 self.ui.write(ns.logfmt % name, label=b'log.%s' % ns.colorname)
309 if self.ui.debugflag:
313 if self.ui.debugflag:
310 self.ui.write(
314 self.ui.write(
311 columns[b'phase'] % ctx.phasestr(), label=b'log.phase'
315 columns[b'phase'] % ctx.phasestr(), label=b'log.phase'
312 )
316 )
313 for pctx in scmutil.meaningfulparents(self.repo, ctx):
317 for pctx in scmutil.meaningfulparents(self.repo, ctx):
314 label = b'log.parent changeset.%s' % pctx.phasestr()
318 label = b'log.parent changeset.%s' % pctx.phasestr()
315 self.ui.write(
319 self.ui.write(
316 columns[b'parent'] % scmutil.formatchangeid(pctx), label=label
320 columns[b'parent'] % scmutil.formatchangeid(pctx), label=label
317 )
321 )
318
322
319 if self.ui.debugflag:
323 if self.ui.debugflag:
320 mnode = ctx.manifestnode()
324 mnode = ctx.manifestnode()
321 if mnode is None:
325 if mnode is None:
322 mnode = wdirid
326 mnode = wdirid
323 mrev = wdirrev
327 mrev = wdirrev
324 else:
328 else:
325 mrev = self.repo.manifestlog.rev(mnode)
329 mrev = self.repo.manifestlog.rev(mnode)
326 self.ui.write(
330 self.ui.write(
327 columns[b'manifest']
331 columns[b'manifest']
328 % scmutil.formatrevnode(self.ui, mrev, mnode),
332 % scmutil.formatrevnode(self.ui, mrev, mnode),
329 label=b'ui.debug log.manifest',
333 label=b'ui.debug log.manifest',
330 )
334 )
331 self.ui.write(columns[b'user'] % ctx.user(), label=b'log.user')
335 self.ui.write(columns[b'user'] % ctx.user(), label=b'log.user')
332 self.ui.write(
336 self.ui.write(
333 columns[b'date'] % dateutil.datestr(ctx.date()), label=b'log.date'
337 columns[b'date'] % dateutil.datestr(ctx.date()), label=b'log.date'
334 )
338 )
335
339
336 if ctx.isunstable():
340 if ctx.isunstable():
337 instabilities = ctx.instabilities()
341 instabilities = ctx.instabilities()
338 self.ui.write(
342 self.ui.write(
339 columns[b'instability'] % b', '.join(instabilities),
343 columns[b'instability'] % b', '.join(instabilities),
340 label=b'log.instability',
344 label=b'log.instability',
341 )
345 )
342
346
343 elif ctx.obsolete():
347 elif ctx.obsolete():
344 self._showobsfate(ctx)
348 self._showobsfate(ctx)
345
349
346 self._exthook(ctx)
350 self._exthook(ctx)
347
351
348 if self.ui.debugflag:
352 if self.ui.debugflag:
349 files = ctx.p1().status(ctx)
353 files = ctx.p1().status(ctx)
350 for key, value in zip(
354 for key, value in zip(
351 [b'files', b'files+', b'files-'],
355 [b'files', b'files+', b'files-'],
352 [files.modified, files.added, files.removed],
356 [files.modified, files.added, files.removed],
353 ):
357 ):
354 if value:
358 if value:
355 self.ui.write(
359 self.ui.write(
356 columns[key] % b" ".join(value),
360 columns[key] % b" ".join(value),
357 label=b'ui.debug log.files',
361 label=b'ui.debug log.files',
358 )
362 )
359 elif ctx.files() and self.ui.verbose:
363 elif ctx.files() and self.ui.verbose:
360 self.ui.write(
364 self.ui.write(
361 columns[b'files'] % b" ".join(ctx.files()),
365 columns[b'files'] % b" ".join(ctx.files()),
362 label=b'ui.note log.files',
366 label=b'ui.note log.files',
363 )
367 )
364 if copies and self.ui.verbose:
368 if copies and self.ui.verbose:
365 copies = [b'%s (%s)' % c for c in copies]
369 copies = [b'%s (%s)' % c for c in copies]
366 self.ui.write(
370 self.ui.write(
367 columns[b'copies'] % b' '.join(copies),
371 columns[b'copies'] % b' '.join(copies),
368 label=b'ui.note log.copies',
372 label=b'ui.note log.copies',
369 )
373 )
370
374
371 extra = ctx.extra()
375 extra = ctx.extra()
372 if extra and self.ui.debugflag:
376 if extra and self.ui.debugflag:
373 for key, value in sorted(extra.items()):
377 for key, value in sorted(extra.items()):
374 self.ui.write(
378 self.ui.write(
375 columns[b'extra'] % (key, stringutil.escapestr(value)),
379 columns[b'extra'] % (key, stringutil.escapestr(value)),
376 label=b'ui.debug log.extra',
380 label=b'ui.debug log.extra',
377 )
381 )
378
382
379 description = ctx.description().strip()
383 description = ctx.description().strip()
380 if description:
384 if description:
381 if self.ui.verbose:
385 if self.ui.verbose:
382 self.ui.write(
386 self.ui.write(
383 _(b"description:\n"), label=b'ui.note log.description'
387 _(b"description:\n"), label=b'ui.note log.description'
384 )
388 )
385 self.ui.write(description, label=b'ui.note log.description')
389 self.ui.write(description, label=b'ui.note log.description')
386 self.ui.write(b"\n\n")
390 self.ui.write(b"\n\n")
387 else:
391 else:
388 self.ui.write(
392 self.ui.write(
389 columns[b'summary'] % description.splitlines()[0],
393 columns[b'summary'] % description.splitlines()[0],
390 label=b'log.summary',
394 label=b'log.summary',
391 )
395 )
392 self.ui.write(b"\n")
396 self.ui.write(b"\n")
393
397
394 self._showpatch(ctx, graphwidth)
398 self._showpatch(ctx, graphwidth)
395
399
396 def _showobsfate(self, ctx):
400 def _showobsfate(self, ctx):
397 # TODO: do not depend on templater
401 # TODO: do not depend on templater
398 tres = formatter.templateresources(self.repo.ui, self.repo)
402 tres = formatter.templateresources(self.repo.ui, self.repo)
399 t = formatter.maketemplater(
403 t = formatter.maketemplater(
400 self.repo.ui,
404 self.repo.ui,
401 b'{join(obsfate, "\n")}',
405 b'{join(obsfate, "\n")}',
402 defaults=templatekw.keywords,
406 defaults=templatekw.keywords,
403 resources=tres,
407 resources=tres,
404 )
408 )
405 obsfate = t.renderdefault({b'ctx': ctx}).splitlines()
409 obsfate = t.renderdefault({b'ctx': ctx}).splitlines()
406
410
407 if obsfate:
411 if obsfate:
408 for obsfateline in obsfate:
412 for obsfateline in obsfate:
409 self.ui.write(
413 self.ui.write(
410 self._columns[b'obsolete'] % obsfateline,
414 self._columns[b'obsolete'] % obsfateline,
411 label=b'log.obsfate',
415 label=b'log.obsfate',
412 )
416 )
413
417
414 def _exthook(self, ctx):
418 def _exthook(self, ctx):
415 '''empty method used by extension as a hook point
419 '''empty method used by extension as a hook point
416 '''
420 '''
417
421
418 def _showpatch(self, ctx, graphwidth=0):
422 def _showpatch(self, ctx, graphwidth=0):
419 if self._includestat:
423 if self._includestat:
420 self._differ.showdiff(
424 self._differ.showdiff(
421 self.ui, ctx, self._diffopts, graphwidth, stat=True
425 self.ui, ctx, self._diffopts, graphwidth, stat=True
422 )
426 )
423 if self._includestat and self._includediff:
427 if self._includestat and self._includediff:
424 self.ui.write(b"\n")
428 self.ui.write(b"\n")
425 if self._includediff:
429 if self._includediff:
426 self._differ.showdiff(
430 self._differ.showdiff(
427 self.ui, ctx, self._diffopts, graphwidth, stat=False
431 self.ui, ctx, self._diffopts, graphwidth, stat=False
428 )
432 )
429 if self._includestat or self._includediff:
433 if self._includestat or self._includediff:
430 self.ui.write(b"\n")
434 self.ui.write(b"\n")
431
435
432
436
433 class changesetformatter(changesetprinter):
437 class changesetformatter(changesetprinter):
434 """Format changeset information by generic formatter"""
438 """Format changeset information by generic formatter"""
435
439
436 def __init__(
440 def __init__(
437 self, ui, repo, fm, differ=None, diffopts=None, buffered=False
441 self, ui, repo, fm, differ=None, diffopts=None, buffered=False
438 ):
442 ):
439 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
443 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
440 self._diffopts = patch.difffeatureopts(ui, diffopts, git=True)
444 self._diffopts = patch.difffeatureopts(ui, diffopts, git=True)
441 self._fm = fm
445 self._fm = fm
442
446
443 def close(self):
447 def close(self):
444 self._fm.end()
448 self._fm.end()
445
449
446 def _show(self, ctx, copies, props):
450 def _show(self, ctx, copies, props):
447 '''show a single changeset or file revision'''
451 '''show a single changeset or file revision'''
448 fm = self._fm
452 fm = self._fm
449 fm.startitem()
453 fm.startitem()
450 fm.context(ctx=ctx)
454 fm.context(ctx=ctx)
451 fm.data(rev=scmutil.intrev(ctx), node=fm.hexfunc(scmutil.binnode(ctx)))
455 fm.data(rev=scmutil.intrev(ctx), node=fm.hexfunc(scmutil.binnode(ctx)))
452
456
453 datahint = fm.datahint()
457 datahint = fm.datahint()
454 if self.ui.quiet and not datahint:
458 if self.ui.quiet and not datahint:
455 return
459 return
456
460
457 fm.data(
461 fm.data(
458 branch=ctx.branch(),
462 branch=ctx.branch(),
459 phase=ctx.phasestr(),
463 phase=ctx.phasestr(),
460 user=ctx.user(),
464 user=ctx.user(),
461 date=fm.formatdate(ctx.date()),
465 date=fm.formatdate(ctx.date()),
462 desc=ctx.description(),
466 desc=ctx.description(),
463 bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'),
467 bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'),
464 tags=fm.formatlist(ctx.tags(), name=b'tag'),
468 tags=fm.formatlist(ctx.tags(), name=b'tag'),
465 parents=fm.formatlist(
469 parents=fm.formatlist(
466 [fm.hexfunc(c.node()) for c in ctx.parents()], name=b'node'
470 [fm.hexfunc(c.node()) for c in ctx.parents()], name=b'node'
467 ),
471 ),
468 )
472 )
469
473
470 if self.ui.debugflag or b'manifest' in datahint:
474 if self.ui.debugflag or b'manifest' in datahint:
471 fm.data(manifest=fm.hexfunc(ctx.manifestnode() or wdirid))
475 fm.data(manifest=fm.hexfunc(ctx.manifestnode() or wdirid))
472 if self.ui.debugflag or b'extra' in datahint:
476 if self.ui.debugflag or b'extra' in datahint:
473 fm.data(extra=fm.formatdict(ctx.extra()))
477 fm.data(extra=fm.formatdict(ctx.extra()))
474
478
475 if (
479 if (
476 self.ui.debugflag
480 self.ui.debugflag
477 or b'modified' in datahint
481 or b'modified' in datahint
478 or b'added' in datahint
482 or b'added' in datahint
479 or b'removed' in datahint
483 or b'removed' in datahint
480 ):
484 ):
481 files = ctx.p1().status(ctx)
485 files = ctx.p1().status(ctx)
482 fm.data(
486 fm.data(
483 modified=fm.formatlist(files.modified, name=b'file'),
487 modified=fm.formatlist(files.modified, name=b'file'),
484 added=fm.formatlist(files.added, name=b'file'),
488 added=fm.formatlist(files.added, name=b'file'),
485 removed=fm.formatlist(files.removed, name=b'file'),
489 removed=fm.formatlist(files.removed, name=b'file'),
486 )
490 )
487
491
488 verbose = not self.ui.debugflag and self.ui.verbose
492 verbose = not self.ui.debugflag and self.ui.verbose
489 if verbose or b'files' in datahint:
493 if verbose or b'files' in datahint:
490 fm.data(files=fm.formatlist(ctx.files(), name=b'file'))
494 fm.data(files=fm.formatlist(ctx.files(), name=b'file'))
491 if verbose and copies or b'copies' in datahint:
495 if verbose and copies or b'copies' in datahint:
492 fm.data(
496 fm.data(
493 copies=fm.formatdict(copies or {}, key=b'name', value=b'source')
497 copies=fm.formatdict(copies or {}, key=b'name', value=b'source')
494 )
498 )
495
499
496 if self._includestat or b'diffstat' in datahint:
500 if self._includestat or b'diffstat' in datahint:
497 self.ui.pushbuffer()
501 self.ui.pushbuffer()
498 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
502 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
499 fm.data(diffstat=self.ui.popbuffer())
503 fm.data(diffstat=self.ui.popbuffer())
500 if self._includediff or b'diff' in datahint:
504 if self._includediff or b'diff' in datahint:
501 self.ui.pushbuffer()
505 self.ui.pushbuffer()
502 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
506 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
503 fm.data(diff=self.ui.popbuffer())
507 fm.data(diff=self.ui.popbuffer())
504
508
505
509
506 class changesettemplater(changesetprinter):
510 class changesettemplater(changesetprinter):
507 '''format changeset information.
511 '''format changeset information.
508
512
509 Note: there are a variety of convenience functions to build a
513 Note: there are a variety of convenience functions to build a
510 changesettemplater for common cases. See functions such as:
514 changesettemplater for common cases. See functions such as:
511 maketemplater, changesetdisplayer, buildcommittemplate, or other
515 maketemplater, changesetdisplayer, buildcommittemplate, or other
512 functions that use changesest_templater.
516 functions that use changesest_templater.
513 '''
517 '''
514
518
515 # Arguments before "buffered" used to be positional. Consider not
519 # Arguments before "buffered" used to be positional. Consider not
516 # adding/removing arguments before "buffered" to not break callers.
520 # adding/removing arguments before "buffered" to not break callers.
517 def __init__(
521 def __init__(
518 self, ui, repo, tmplspec, differ=None, diffopts=None, buffered=False
522 self, ui, repo, tmplspec, differ=None, diffopts=None, buffered=False
519 ):
523 ):
520 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
524 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
521 # tres is shared with _graphnodeformatter()
525 # tres is shared with _graphnodeformatter()
522 self._tresources = tres = formatter.templateresources(ui, repo)
526 self._tresources = tres = formatter.templateresources(ui, repo)
523 self.t = formatter.loadtemplater(
527 self.t = formatter.loadtemplater(
524 ui,
528 ui,
525 tmplspec,
529 tmplspec,
526 defaults=templatekw.keywords,
530 defaults=templatekw.keywords,
527 resources=tres,
531 resources=tres,
528 cache=templatekw.defaulttempl,
532 cache=templatekw.defaulttempl,
529 )
533 )
530 self._counter = itertools.count()
534 self._counter = itertools.count()
531
535
532 self._tref = tmplspec.ref
536 self._tref = tmplspec.ref
533 self._parts = {
537 self._parts = {
534 b'header': b'',
538 b'header': b'',
535 b'footer': b'',
539 b'footer': b'',
536 tmplspec.ref: tmplspec.ref,
540 tmplspec.ref: tmplspec.ref,
537 b'docheader': b'',
541 b'docheader': b'',
538 b'docfooter': b'',
542 b'docfooter': b'',
539 b'separator': b'',
543 b'separator': b'',
540 }
544 }
541 if tmplspec.mapfile:
545 if tmplspec.mapfile:
542 # find correct templates for current mode, for backward
546 # find correct templates for current mode, for backward
543 # compatibility with 'log -v/-q/--debug' using a mapfile
547 # compatibility with 'log -v/-q/--debug' using a mapfile
544 tmplmodes = [
548 tmplmodes = [
545 (True, b''),
549 (True, b''),
546 (self.ui.verbose, b'_verbose'),
550 (self.ui.verbose, b'_verbose'),
547 (self.ui.quiet, b'_quiet'),
551 (self.ui.quiet, b'_quiet'),
548 (self.ui.debugflag, b'_debug'),
552 (self.ui.debugflag, b'_debug'),
549 ]
553 ]
550 for mode, postfix in tmplmodes:
554 for mode, postfix in tmplmodes:
551 for t in self._parts:
555 for t in self._parts:
552 cur = t + postfix
556 cur = t + postfix
553 if mode and cur in self.t:
557 if mode and cur in self.t:
554 self._parts[t] = cur
558 self._parts[t] = cur
555 else:
559 else:
556 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
560 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
557 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
561 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
558 self._parts.update(m)
562 self._parts.update(m)
559
563
560 if self._parts[b'docheader']:
564 if self._parts[b'docheader']:
561 self.ui.write(self.t.render(self._parts[b'docheader'], {}))
565 self.ui.write(self.t.render(self._parts[b'docheader'], {}))
562
566
563 def close(self):
567 def close(self):
564 if self._parts[b'docfooter']:
568 if self._parts[b'docfooter']:
565 if not self.footer:
569 if not self.footer:
566 self.footer = b""
570 self.footer = b""
567 self.footer += self.t.render(self._parts[b'docfooter'], {})
571 self.footer += self.t.render(self._parts[b'docfooter'], {})
568 return super(changesettemplater, self).close()
572 return super(changesettemplater, self).close()
569
573
570 def _show(self, ctx, copies, props):
574 def _show(self, ctx, copies, props):
571 '''show a single changeset or file revision'''
575 '''show a single changeset or file revision'''
572 props = props.copy()
576 props = props.copy()
573 props[b'ctx'] = ctx
577 props[b'ctx'] = ctx
574 props[b'index'] = index = next(self._counter)
578 props[b'index'] = index = next(self._counter)
575 props[b'revcache'] = {b'copies': copies}
579 props[b'revcache'] = {b'copies': copies}
576 graphwidth = props.get(b'graphwidth', 0)
580 graphwidth = props.get(b'graphwidth', 0)
577
581
578 # write separator, which wouldn't work well with the header part below
582 # write separator, which wouldn't work well with the header part below
579 # since there's inherently a conflict between header (across items) and
583 # since there's inherently a conflict between header (across items) and
580 # separator (per item)
584 # separator (per item)
581 if self._parts[b'separator'] and index > 0:
585 if self._parts[b'separator'] and index > 0:
582 self.ui.write(self.t.render(self._parts[b'separator'], {}))
586 self.ui.write(self.t.render(self._parts[b'separator'], {}))
583
587
584 # write header
588 # write header
585 if self._parts[b'header']:
589 if self._parts[b'header']:
586 h = self.t.render(self._parts[b'header'], props)
590 h = self.t.render(self._parts[b'header'], props)
587 if self.buffered:
591 if self.buffered:
588 self.header[ctx.rev()] = h
592 self.header[ctx.rev()] = h
589 else:
593 else:
590 if self.lastheader != h:
594 if self.lastheader != h:
591 self.lastheader = h
595 self.lastheader = h
592 self.ui.write(h)
596 self.ui.write(h)
593
597
594 # write changeset metadata, then patch if requested
598 # write changeset metadata, then patch if requested
595 key = self._parts[self._tref]
599 key = self._parts[self._tref]
596 self.ui.write(self.t.render(key, props))
600 self.ui.write(self.t.render(key, props))
597 self._exthook(ctx)
601 self._exthook(ctx)
598 self._showpatch(ctx, graphwidth)
602 self._showpatch(ctx, graphwidth)
599
603
600 if self._parts[b'footer']:
604 if self._parts[b'footer']:
601 if not self.footer:
605 if not self.footer:
602 self.footer = self.t.render(self._parts[b'footer'], props)
606 self.footer = self.t.render(self._parts[b'footer'], props)
603
607
604
608
605 def templatespec(tmpl, mapfile):
609 def templatespec(tmpl, mapfile):
606 assert not (tmpl and mapfile)
610 assert not (tmpl and mapfile)
607 if mapfile:
611 if mapfile:
608 return formatter.mapfile_templatespec(b'changeset', mapfile)
612 return formatter.mapfile_templatespec(b'changeset', mapfile)
609 else:
613 else:
610 return formatter.literal_templatespec(tmpl)
614 return formatter.literal_templatespec(tmpl)
611
615
612
616
613 def _lookuptemplate(ui, tmpl, style):
617 def _lookuptemplate(ui, tmpl, style):
614 """Find the template matching the given template spec or style
618 """Find the template matching the given template spec or style
615
619
616 See formatter.lookuptemplate() for details.
620 See formatter.lookuptemplate() for details.
617 """
621 """
618
622
619 # ui settings
623 # ui settings
620 if not tmpl and not style: # template are stronger than style
624 if not tmpl and not style: # template are stronger than style
621 tmpl = ui.config(b'ui', b'logtemplate')
625 tmpl = ui.config(b'ui', b'logtemplate')
622 if tmpl:
626 if tmpl:
623 return formatter.literal_templatespec(templater.unquotestring(tmpl))
627 return formatter.literal_templatespec(templater.unquotestring(tmpl))
624 else:
628 else:
625 style = util.expandpath(ui.config(b'ui', b'style'))
629 style = util.expandpath(ui.config(b'ui', b'style'))
626
630
627 if not tmpl and style:
631 if not tmpl and style:
628 mapfile = style
632 mapfile = style
629 fp = None
633 fp = None
630 if not os.path.split(mapfile)[0]:
634 if not os.path.split(mapfile)[0]:
631 (mapname, fp) = templater.try_open_template(
635 (mapname, fp) = templater.try_open_template(
632 b'map-cmdline.' + mapfile
636 b'map-cmdline.' + mapfile
633 ) or templater.try_open_template(mapfile)
637 ) or templater.try_open_template(mapfile)
634 if mapname:
638 if mapname:
635 mapfile = mapname
639 mapfile = mapname
636 return formatter.mapfile_templatespec(b'changeset', mapfile, fp)
640 return formatter.mapfile_templatespec(b'changeset', mapfile, fp)
637
641
638 return formatter.lookuptemplate(ui, b'changeset', tmpl)
642 return formatter.lookuptemplate(ui, b'changeset', tmpl)
639
643
640
644
641 def maketemplater(ui, repo, tmpl, buffered=False):
645 def maketemplater(ui, repo, tmpl, buffered=False):
642 """Create a changesettemplater from a literal template 'tmpl'
646 """Create a changesettemplater from a literal template 'tmpl'
643 byte-string."""
647 byte-string."""
644 spec = formatter.literal_templatespec(tmpl)
648 spec = formatter.literal_templatespec(tmpl)
645 return changesettemplater(ui, repo, spec, buffered=buffered)
649 return changesettemplater(ui, repo, spec, buffered=buffered)
646
650
647
651
648 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
652 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
649 """show one changeset using template or regular display.
653 """show one changeset using template or regular display.
650
654
651 Display format will be the first non-empty hit of:
655 Display format will be the first non-empty hit of:
652 1. option 'template'
656 1. option 'template'
653 2. option 'style'
657 2. option 'style'
654 3. [ui] setting 'logtemplate'
658 3. [ui] setting 'logtemplate'
655 4. [ui] setting 'style'
659 4. [ui] setting 'style'
656 If all of these values are either the unset or the empty string,
660 If all of these values are either the unset or the empty string,
657 regular display via changesetprinter() is done.
661 regular display via changesetprinter() is done.
658 """
662 """
659 postargs = (differ, opts, buffered)
663 postargs = (differ, opts, buffered)
660 spec = _lookuptemplate(ui, opts.get(b'template'), opts.get(b'style'))
664 spec = _lookuptemplate(ui, opts.get(b'template'), opts.get(b'style'))
661
665
662 # machine-readable formats have slightly different keyword set than
666 # machine-readable formats have slightly different keyword set than
663 # plain templates, which are handled by changesetformatter.
667 # plain templates, which are handled by changesetformatter.
664 # note that {b'pickle', b'debug'} can also be added to the list if needed.
668 # note that {b'pickle', b'debug'} can also be added to the list if needed.
665 if spec.ref in {b'cbor', b'json'}:
669 if spec.ref in {b'cbor', b'json'}:
666 fm = ui.formatter(b'log', opts)
670 fm = ui.formatter(b'log', opts)
667 return changesetformatter(ui, repo, fm, *postargs)
671 return changesetformatter(ui, repo, fm, *postargs)
668
672
669 if not spec.ref and not spec.tmpl and not spec.mapfile:
673 if not spec.ref and not spec.tmpl and not spec.mapfile:
670 return changesetprinter(ui, repo, *postargs)
674 return changesetprinter(ui, repo, *postargs)
671
675
672 return changesettemplater(ui, repo, spec, *postargs)
676 return changesettemplater(ui, repo, spec, *postargs)
673
677
674
678
675 def _makematcher(repo, revs, pats, opts):
679 @attr.s
680 class walkopts(object):
681 """Options to configure a set of revisions and file matcher factory
682 to scan revision/file history
683 """
684
685 # raw command-line parameters, which a matcher will be built from
686 pats = attr.ib() # type: List[bytes]
687 opts = attr.ib() # type: Dict[bytes, Any]
688
689
690 def parseopts(ui, pats, opts):
691 # type: (Any, List[bytes], Dict[bytes, Any]) -> walkopts
692 """Parse log command options into walkopts
693
694 The returned walkopts will be passed in to getrevs().
695 """
696 return walkopts(pats=pats, opts=opts)
697
698
699 def _makematcher(repo, revs, wopts):
676 """Build matcher and expanded patterns from log options
700 """Build matcher and expanded patterns from log options
677
701
678 If --follow, revs are the revisions to follow from.
702 If --follow, revs are the revisions to follow from.
679
703
680 Returns (match, pats, slowpath) where
704 Returns (match, pats, slowpath) where
681 - match: a matcher built from the given pats and -I/-X opts
705 - match: a matcher built from the given pats and -I/-X opts
682 - pats: patterns used (globs are expanded on Windows)
706 - pats: patterns used (globs are expanded on Windows)
683 - slowpath: True if patterns aren't as simple as scanning filelogs
707 - slowpath: True if patterns aren't as simple as scanning filelogs
684 """
708 """
685 # pats/include/exclude are passed to match.match() directly in
709 # pats/include/exclude are passed to match.match() directly in
686 # _matchfiles() revset but walkchangerevs() builds its matcher with
710 # _matchfiles() revset but walkchangerevs() builds its matcher with
687 # scmutil.match(). The difference is input pats are globbed on
711 # scmutil.match(). The difference is input pats are globbed on
688 # platforms without shell expansion (windows).
712 # platforms without shell expansion (windows).
689 wctx = repo[None]
713 wctx = repo[None]
690 match, pats = scmutil.matchandpats(wctx, pats, opts)
714 match, pats = scmutil.matchandpats(wctx, wopts.pats, wopts.opts)
691 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
715 slowpath = match.anypats() or (
716 not match.always() and wopts.opts.get(b'removed')
717 )
692 if not slowpath:
718 if not slowpath:
693 follow = opts.get(b'follow') or opts.get(b'follow_first')
719 follow = wopts.opts.get(b'follow') or wopts.opts.get(b'follow_first')
694 if follow and opts.get(b'rev'):
720 if follow and wopts.opts.get(b'rev'):
695 # There may be the case that a path doesn't exist in some (but
721 # There may be the case that a path doesn't exist in some (but
696 # not all) of the specified start revisions, but let's consider
722 # not all) of the specified start revisions, but let's consider
697 # the path is valid. Missing files will be warned by the matcher.
723 # the path is valid. Missing files will be warned by the matcher.
698 startctxs = [repo[r] for r in revs]
724 startctxs = [repo[r] for r in revs]
699 for f in match.files():
725 for f in match.files():
700 found = False
726 found = False
701 for c in startctxs:
727 for c in startctxs:
702 if f in c:
728 if f in c:
703 found = True
729 found = True
704 elif c.hasdir(f):
730 elif c.hasdir(f):
705 # If a directory exists in any of the start revisions,
731 # If a directory exists in any of the start revisions,
706 # take the slow path.
732 # take the slow path.
707 found = slowpath = True
733 found = slowpath = True
708 if not found:
734 if not found:
709 raise error.Abort(
735 raise error.Abort(
710 _(
736 _(
711 b'cannot follow file not in any of the specified '
737 b'cannot follow file not in any of the specified '
712 b'revisions: "%s"'
738 b'revisions: "%s"'
713 )
739 )
714 % f
740 % f
715 )
741 )
716 elif follow:
742 elif follow:
717 for f in match.files():
743 for f in match.files():
718 if f not in wctx:
744 if f not in wctx:
719 # If the file exists, it may be a directory, so let it
745 # If the file exists, it may be a directory, so let it
720 # take the slow path.
746 # take the slow path.
721 if os.path.exists(repo.wjoin(f)):
747 if os.path.exists(repo.wjoin(f)):
722 slowpath = True
748 slowpath = True
723 continue
749 continue
724 else:
750 else:
725 raise error.Abort(
751 raise error.Abort(
726 _(
752 _(
727 b'cannot follow file not in parent '
753 b'cannot follow file not in parent '
728 b'revision: "%s"'
754 b'revision: "%s"'
729 )
755 )
730 % f
756 % f
731 )
757 )
732 filelog = repo.file(f)
758 filelog = repo.file(f)
733 if not filelog:
759 if not filelog:
734 # A file exists in wdir but not in history, which means
760 # A file exists in wdir but not in history, which means
735 # the file isn't committed yet.
761 # the file isn't committed yet.
736 raise error.Abort(
762 raise error.Abort(
737 _(b'cannot follow nonexistent file: "%s"') % f
763 _(b'cannot follow nonexistent file: "%s"') % f
738 )
764 )
739 else:
765 else:
740 for f in match.files():
766 for f in match.files():
741 filelog = repo.file(f)
767 filelog = repo.file(f)
742 if not filelog:
768 if not filelog:
743 # A zero count may be a directory or deleted file, so
769 # A zero count may be a directory or deleted file, so
744 # try to find matching entries on the slow path.
770 # try to find matching entries on the slow path.
745 slowpath = True
771 slowpath = True
746
772
747 # We decided to fall back to the slowpath because at least one
773 # We decided to fall back to the slowpath because at least one
748 # of the paths was not a file. Check to see if at least one of them
774 # of the paths was not a file. Check to see if at least one of them
749 # existed in history - in that case, we'll continue down the
775 # existed in history - in that case, we'll continue down the
750 # slowpath; otherwise, we can turn off the slowpath
776 # slowpath; otherwise, we can turn off the slowpath
751 if slowpath:
777 if slowpath:
752 for path in match.files():
778 for path in match.files():
753 if path == b'.' or path in repo.store:
779 if path == b'.' or path in repo.store:
754 break
780 break
755 else:
781 else:
756 slowpath = False
782 slowpath = False
757
783
758 return match, pats, slowpath
784 return match, pats, slowpath
759
785
760
786
761 def _fileancestors(repo, revs, match, followfirst):
787 def _fileancestors(repo, revs, match, followfirst):
762 fctxs = []
788 fctxs = []
763 for r in revs:
789 for r in revs:
764 ctx = repo[r]
790 ctx = repo[r]
765 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
791 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
766
792
767 # When displaying a revision with --patch --follow FILE, we have
793 # When displaying a revision with --patch --follow FILE, we have
768 # to know which file of the revision must be diffed. With
794 # to know which file of the revision must be diffed. With
769 # --follow, we want the names of the ancestors of FILE in the
795 # --follow, we want the names of the ancestors of FILE in the
770 # revision, stored in "fcache". "fcache" is populated as a side effect
796 # revision, stored in "fcache". "fcache" is populated as a side effect
771 # of the graph traversal.
797 # of the graph traversal.
772 fcache = {}
798 fcache = {}
773
799
774 def filematcher(ctx):
800 def filematcher(ctx):
775 return scmutil.matchfiles(repo, fcache.get(scmutil.intrev(ctx), []))
801 return scmutil.matchfiles(repo, fcache.get(scmutil.intrev(ctx), []))
776
802
777 def revgen():
803 def revgen():
778 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
804 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
779 fcache[rev] = [c.path() for c in cs]
805 fcache[rev] = [c.path() for c in cs]
780 yield rev
806 yield rev
781
807
782 return smartset.generatorset(revgen(), iterasc=False), filematcher
808 return smartset.generatorset(revgen(), iterasc=False), filematcher
783
809
784
810
785 def _makenofollowfilematcher(repo, pats, opts):
811 def _makenofollowfilematcher(repo, pats, opts):
786 '''hook for extensions to override the filematcher for non-follow cases'''
812 '''hook for extensions to override the filematcher for non-follow cases'''
787 return None
813 return None
788
814
789
815
790 _opt2logrevset = {
816 _opt2logrevset = {
791 b'no_merges': (b'not merge()', None),
817 b'no_merges': (b'not merge()', None),
792 b'only_merges': (b'merge()', None),
818 b'only_merges': (b'merge()', None),
793 b'_matchfiles': (None, b'_matchfiles(%ps)'),
819 b'_matchfiles': (None, b'_matchfiles(%ps)'),
794 b'date': (b'date(%s)', None),
820 b'date': (b'date(%s)', None),
795 b'branch': (b'branch(%s)', b'%lr'),
821 b'branch': (b'branch(%s)', b'%lr'),
796 b'_patslog': (b'filelog(%s)', b'%lr'),
822 b'_patslog': (b'filelog(%s)', b'%lr'),
797 b'keyword': (b'keyword(%s)', b'%lr'),
823 b'keyword': (b'keyword(%s)', b'%lr'),
798 b'prune': (b'ancestors(%s)', b'not %lr'),
824 b'prune': (b'ancestors(%s)', b'not %lr'),
799 b'user': (b'user(%s)', b'%lr'),
825 b'user': (b'user(%s)', b'%lr'),
800 }
826 }
801
827
802
828
803 def _makerevset(repo, pats, slowpath, opts):
829 def _makerevset(repo, wopts, slowpath):
804 """Return a revset string built from log options and file patterns"""
830 """Return a revset string built from log options and file patterns"""
805 opts = dict(opts)
831 opts = dict(wopts.opts)
806 # follow or not follow?
832 # follow or not follow?
807 follow = opts.get(b'follow') or opts.get(b'follow_first')
833 follow = opts.get(b'follow') or opts.get(b'follow_first')
808
834
809 # branch and only_branch are really aliases and must be handled at
835 # branch and only_branch are really aliases and must be handled at
810 # the same time
836 # the same time
811 opts[b'branch'] = opts.get(b'branch', []) + opts.get(b'only_branch', [])
837 opts[b'branch'] = opts.get(b'branch', []) + opts.get(b'only_branch', [])
812 opts[b'branch'] = [repo.lookupbranch(b) for b in opts[b'branch']]
838 opts[b'branch'] = [repo.lookupbranch(b) for b in opts[b'branch']]
813
839
814 if slowpath:
840 if slowpath:
815 # See walkchangerevs() slow path.
841 # See walkchangerevs() slow path.
816 #
842 #
817 # pats/include/exclude cannot be represented as separate
843 # pats/include/exclude cannot be represented as separate
818 # revset expressions as their filtering logic applies at file
844 # revset expressions as their filtering logic applies at file
819 # level. For instance "-I a -X b" matches a revision touching
845 # level. For instance "-I a -X b" matches a revision touching
820 # "a" and "b" while "file(a) and not file(b)" does
846 # "a" and "b" while "file(a) and not file(b)" does
821 # not. Besides, filesets are evaluated against the working
847 # not. Besides, filesets are evaluated against the working
822 # directory.
848 # directory.
823 matchargs = [b'r:', b'd:relpath']
849 matchargs = [b'r:', b'd:relpath']
824 for p in pats:
850 for p in wopts.pats:
825 matchargs.append(b'p:' + p)
851 matchargs.append(b'p:' + p)
826 for p in opts.get(b'include', []):
852 for p in opts.get(b'include', []):
827 matchargs.append(b'i:' + p)
853 matchargs.append(b'i:' + p)
828 for p in opts.get(b'exclude', []):
854 for p in opts.get(b'exclude', []):
829 matchargs.append(b'x:' + p)
855 matchargs.append(b'x:' + p)
830 opts[b'_matchfiles'] = matchargs
856 opts[b'_matchfiles'] = matchargs
831 elif not follow:
857 elif not follow:
832 opts[b'_patslog'] = list(pats)
858 opts[b'_patslog'] = list(wopts.pats)
833
859
834 expr = []
860 expr = []
835 for op, val in sorted(pycompat.iteritems(opts)):
861 for op, val in sorted(pycompat.iteritems(opts)):
836 if not val:
862 if not val:
837 continue
863 continue
838 if op not in _opt2logrevset:
864 if op not in _opt2logrevset:
839 continue
865 continue
840 revop, listop = _opt2logrevset[op]
866 revop, listop = _opt2logrevset[op]
841 if revop and b'%' not in revop:
867 if revop and b'%' not in revop:
842 expr.append(revop)
868 expr.append(revop)
843 elif not listop:
869 elif not listop:
844 expr.append(revsetlang.formatspec(revop, val))
870 expr.append(revsetlang.formatspec(revop, val))
845 else:
871 else:
846 if revop:
872 if revop:
847 val = [revsetlang.formatspec(revop, v) for v in val]
873 val = [revsetlang.formatspec(revop, v) for v in val]
848 expr.append(revsetlang.formatspec(listop, val))
874 expr.append(revsetlang.formatspec(listop, val))
849
875
850 if expr:
876 if expr:
851 expr = b'(' + b' and '.join(expr) + b')'
877 expr = b'(' + b' and '.join(expr) + b')'
852 else:
878 else:
853 expr = None
879 expr = None
854 return expr
880 return expr
855
881
856
882
857 def _initialrevs(repo, opts):
883 def _initialrevs(repo, wopts):
858 """Return the initial set of revisions to be filtered or followed"""
884 """Return the initial set of revisions to be filtered or followed"""
859 follow = opts.get(b'follow') or opts.get(b'follow_first')
885 follow = wopts.opts.get(b'follow') or wopts.opts.get(b'follow_first')
860 if opts.get(b'rev'):
886 if wopts.opts.get(b'rev'):
861 revs = scmutil.revrange(repo, opts[b'rev'])
887 revs = scmutil.revrange(repo, wopts.opts[b'rev'])
862 elif follow and repo.dirstate.p1() == nullid:
888 elif follow and repo.dirstate.p1() == nullid:
863 revs = smartset.baseset()
889 revs = smartset.baseset()
864 elif follow:
890 elif follow:
865 revs = repo.revs(b'.')
891 revs = repo.revs(b'.')
866 else:
892 else:
867 revs = smartset.spanset(repo)
893 revs = smartset.spanset(repo)
868 revs.reverse()
894 revs.reverse()
869 return revs
895 return revs
870
896
871
897
872 def getrevs(repo, pats, opts):
898 def getrevs(repo, wopts):
873 # type: (Any, Any, Any) -> Tuple[smartset.abstractsmartset, Optional[changesetdiffer]]
899 # type: (Any, walkopts) -> Tuple[smartset.abstractsmartset, Optional[changesetdiffer]]
874 """Return (revs, differ) where revs is a smartset
900 """Return (revs, differ) where revs is a smartset
875
901
876 differ is a changesetdiffer with pre-configured file matcher.
902 differ is a changesetdiffer with pre-configured file matcher.
877 """
903 """
878 follow = opts.get(b'follow') or opts.get(b'follow_first')
904 follow = wopts.opts.get(b'follow') or wopts.opts.get(b'follow_first')
879 followfirst = opts.get(b'follow_first')
905 followfirst = wopts.opts.get(b'follow_first')
880 limit = getlimit(opts)
906 limit = getlimit(wopts.opts)
881 revs = _initialrevs(repo, opts)
907 revs = _initialrevs(repo, wopts)
882 if not revs:
908 if not revs:
883 return smartset.baseset(), None
909 return smartset.baseset(), None
884 match, pats, slowpath = _makematcher(repo, revs, pats, opts)
910 match, pats, slowpath = _makematcher(repo, revs, wopts)
911 wopts = attr.evolve(wopts, pats=pats)
912
885 filematcher = None
913 filematcher = None
886 if follow:
914 if follow:
887 if slowpath or match.always():
915 if slowpath or match.always():
888 revs = dagop.revancestors(repo, revs, followfirst=followfirst)
916 revs = dagop.revancestors(repo, revs, followfirst=followfirst)
889 else:
917 else:
890 revs, filematcher = _fileancestors(repo, revs, match, followfirst)
918 revs, filematcher = _fileancestors(repo, revs, match, followfirst)
891 revs.reverse()
919 revs.reverse()
892 if filematcher is None:
920 if filematcher is None:
893 filematcher = _makenofollowfilematcher(repo, pats, opts)
921 filematcher = _makenofollowfilematcher(repo, wopts.pats, wopts.opts)
894 if filematcher is None:
922 if filematcher is None:
895
923
896 def filematcher(ctx):
924 def filematcher(ctx):
897 return match
925 return match
898
926
899 expr = _makerevset(repo, pats, slowpath, opts)
927 expr = _makerevset(repo, wopts, slowpath)
900 if opts.get(b'graph'):
928 if wopts.opts.get(b'graph'):
901 if repo.ui.configbool(b'experimental', b'log.topo'):
929 if repo.ui.configbool(b'experimental', b'log.topo'):
902 if not revs.istopo():
930 if not revs.istopo():
903 revs = dagop.toposort(revs, repo.changelog.parentrevs)
931 revs = dagop.toposort(revs, repo.changelog.parentrevs)
904 # TODO: try to iterate the set lazily
932 # TODO: try to iterate the set lazily
905 revs = revset.baseset(list(revs), istopo=True)
933 revs = revset.baseset(list(revs), istopo=True)
906 elif not (revs.isdescending() or revs.istopo()):
934 elif not (revs.isdescending() or revs.istopo()):
907 # User-specified revs might be unsorted
935 # User-specified revs might be unsorted
908 revs.sort(reverse=True)
936 revs.sort(reverse=True)
909 if expr:
937 if expr:
910 matcher = revset.match(None, expr)
938 matcher = revset.match(None, expr)
911 revs = matcher(repo, revs)
939 revs = matcher(repo, revs)
912 if limit is not None:
940 if limit is not None:
913 revs = revs.slice(0, limit)
941 revs = revs.slice(0, limit)
914
942
915 differ = changesetdiffer()
943 differ = changesetdiffer()
916 differ._makefilematcher = filematcher
944 differ._makefilematcher = filematcher
917 return revs, differ
945 return revs, differ
918
946
919
947
920 def _parselinerangeopt(repo, opts):
948 def _parselinerangeopt(repo, opts):
921 """Parse --line-range log option and return a list of tuples (filename,
949 """Parse --line-range log option and return a list of tuples (filename,
922 (fromline, toline)).
950 (fromline, toline)).
923 """
951 """
924 linerangebyfname = []
952 linerangebyfname = []
925 for pat in opts.get(b'line_range', []):
953 for pat in opts.get(b'line_range', []):
926 try:
954 try:
927 pat, linerange = pat.rsplit(b',', 1)
955 pat, linerange = pat.rsplit(b',', 1)
928 except ValueError:
956 except ValueError:
929 raise error.Abort(_(b'malformatted line-range pattern %s') % pat)
957 raise error.Abort(_(b'malformatted line-range pattern %s') % pat)
930 try:
958 try:
931 fromline, toline = map(int, linerange.split(b':'))
959 fromline, toline = map(int, linerange.split(b':'))
932 except ValueError:
960 except ValueError:
933 raise error.Abort(_(b"invalid line range for %s") % pat)
961 raise error.Abort(_(b"invalid line range for %s") % pat)
934 msg = _(b"line range pattern '%s' must match exactly one file") % pat
962 msg = _(b"line range pattern '%s' must match exactly one file") % pat
935 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
963 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
936 linerangebyfname.append(
964 linerangebyfname.append(
937 (fname, util.processlinerange(fromline, toline))
965 (fname, util.processlinerange(fromline, toline))
938 )
966 )
939 return linerangebyfname
967 return linerangebyfname
940
968
941
969
942 def getlinerangerevs(repo, userrevs, opts):
970 def getlinerangerevs(repo, userrevs, opts):
943 """Return (revs, differ).
971 """Return (revs, differ).
944
972
945 "revs" are revisions obtained by processing "line-range" log options and
973 "revs" are revisions obtained by processing "line-range" log options and
946 walking block ancestors of each specified file/line-range.
974 walking block ancestors of each specified file/line-range.
947
975
948 "differ" is a changesetdiffer with pre-configured file matcher and hunks
976 "differ" is a changesetdiffer with pre-configured file matcher and hunks
949 filter.
977 filter.
950 """
978 """
951 wctx = repo[None]
979 wctx = repo[None]
952
980
953 # Two-levels map of "rev -> file ctx -> [line range]".
981 # Two-levels map of "rev -> file ctx -> [line range]".
954 linerangesbyrev = {}
982 linerangesbyrev = {}
955 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
983 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
956 if fname not in wctx:
984 if fname not in wctx:
957 raise error.Abort(
985 raise error.Abort(
958 _(b'cannot follow file not in parent revision: "%s"') % fname
986 _(b'cannot follow file not in parent revision: "%s"') % fname
959 )
987 )
960 fctx = wctx.filectx(fname)
988 fctx = wctx.filectx(fname)
961 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
989 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
962 rev = fctx.introrev()
990 rev = fctx.introrev()
963 if rev is None:
991 if rev is None:
964 rev = wdirrev
992 rev = wdirrev
965 if rev not in userrevs:
993 if rev not in userrevs:
966 continue
994 continue
967 linerangesbyrev.setdefault(rev, {}).setdefault(
995 linerangesbyrev.setdefault(rev, {}).setdefault(
968 fctx.path(), []
996 fctx.path(), []
969 ).append(linerange)
997 ).append(linerange)
970
998
971 def nofilterhunksfn(fctx, hunks):
999 def nofilterhunksfn(fctx, hunks):
972 return hunks
1000 return hunks
973
1001
974 def hunksfilter(ctx):
1002 def hunksfilter(ctx):
975 fctxlineranges = linerangesbyrev.get(scmutil.intrev(ctx))
1003 fctxlineranges = linerangesbyrev.get(scmutil.intrev(ctx))
976 if fctxlineranges is None:
1004 if fctxlineranges is None:
977 return nofilterhunksfn
1005 return nofilterhunksfn
978
1006
979 def filterfn(fctx, hunks):
1007 def filterfn(fctx, hunks):
980 lineranges = fctxlineranges.get(fctx.path())
1008 lineranges = fctxlineranges.get(fctx.path())
981 if lineranges is not None:
1009 if lineranges is not None:
982 for hr, lines in hunks:
1010 for hr, lines in hunks:
983 if hr is None: # binary
1011 if hr is None: # binary
984 yield hr, lines
1012 yield hr, lines
985 continue
1013 continue
986 if any(mdiff.hunkinrange(hr[2:], lr) for lr in lineranges):
1014 if any(mdiff.hunkinrange(hr[2:], lr) for lr in lineranges):
987 yield hr, lines
1015 yield hr, lines
988 else:
1016 else:
989 for hunk in hunks:
1017 for hunk in hunks:
990 yield hunk
1018 yield hunk
991
1019
992 return filterfn
1020 return filterfn
993
1021
994 def filematcher(ctx):
1022 def filematcher(ctx):
995 files = list(linerangesbyrev.get(scmutil.intrev(ctx), []))
1023 files = list(linerangesbyrev.get(scmutil.intrev(ctx), []))
996 return scmutil.matchfiles(repo, files)
1024 return scmutil.matchfiles(repo, files)
997
1025
998 revs = sorted(linerangesbyrev, reverse=True)
1026 revs = sorted(linerangesbyrev, reverse=True)
999
1027
1000 differ = changesetdiffer()
1028 differ = changesetdiffer()
1001 differ._makefilematcher = filematcher
1029 differ._makefilematcher = filematcher
1002 differ._makehunksfilter = hunksfilter
1030 differ._makehunksfilter = hunksfilter
1003 return smartset.baseset(revs), differ
1031 return smartset.baseset(revs), differ
1004
1032
1005
1033
1006 def _graphnodeformatter(ui, displayer):
1034 def _graphnodeformatter(ui, displayer):
1007 spec = ui.config(b'ui', b'graphnodetemplate')
1035 spec = ui.config(b'ui', b'graphnodetemplate')
1008 if not spec:
1036 if not spec:
1009 return templatekw.getgraphnode # fast path for "{graphnode}"
1037 return templatekw.getgraphnode # fast path for "{graphnode}"
1010
1038
1011 spec = templater.unquotestring(spec)
1039 spec = templater.unquotestring(spec)
1012 if isinstance(displayer, changesettemplater):
1040 if isinstance(displayer, changesettemplater):
1013 # reuse cache of slow templates
1041 # reuse cache of slow templates
1014 tres = displayer._tresources
1042 tres = displayer._tresources
1015 else:
1043 else:
1016 tres = formatter.templateresources(ui)
1044 tres = formatter.templateresources(ui)
1017 templ = formatter.maketemplater(
1045 templ = formatter.maketemplater(
1018 ui, spec, defaults=templatekw.keywords, resources=tres
1046 ui, spec, defaults=templatekw.keywords, resources=tres
1019 )
1047 )
1020
1048
1021 def formatnode(repo, ctx, cache):
1049 def formatnode(repo, ctx, cache):
1022 props = {b'ctx': ctx, b'repo': repo}
1050 props = {b'ctx': ctx, b'repo': repo}
1023 return templ.renderdefault(props)
1051 return templ.renderdefault(props)
1024
1052
1025 return formatnode
1053 return formatnode
1026
1054
1027
1055
1028 def displaygraph(ui, repo, dag, displayer, edgefn, getcopies=None, props=None):
1056 def displaygraph(ui, repo, dag, displayer, edgefn, getcopies=None, props=None):
1029 props = props or {}
1057 props = props or {}
1030 formatnode = _graphnodeformatter(ui, displayer)
1058 formatnode = _graphnodeformatter(ui, displayer)
1031 state = graphmod.asciistate()
1059 state = graphmod.asciistate()
1032 styles = state.styles
1060 styles = state.styles
1033
1061
1034 # only set graph styling if HGPLAIN is not set.
1062 # only set graph styling if HGPLAIN is not set.
1035 if ui.plain(b'graph'):
1063 if ui.plain(b'graph'):
1036 # set all edge styles to |, the default pre-3.8 behaviour
1064 # set all edge styles to |, the default pre-3.8 behaviour
1037 styles.update(dict.fromkeys(styles, b'|'))
1065 styles.update(dict.fromkeys(styles, b'|'))
1038 else:
1066 else:
1039 edgetypes = {
1067 edgetypes = {
1040 b'parent': graphmod.PARENT,
1068 b'parent': graphmod.PARENT,
1041 b'grandparent': graphmod.GRANDPARENT,
1069 b'grandparent': graphmod.GRANDPARENT,
1042 b'missing': graphmod.MISSINGPARENT,
1070 b'missing': graphmod.MISSINGPARENT,
1043 }
1071 }
1044 for name, key in edgetypes.items():
1072 for name, key in edgetypes.items():
1045 # experimental config: experimental.graphstyle.*
1073 # experimental config: experimental.graphstyle.*
1046 styles[key] = ui.config(
1074 styles[key] = ui.config(
1047 b'experimental', b'graphstyle.%s' % name, styles[key]
1075 b'experimental', b'graphstyle.%s' % name, styles[key]
1048 )
1076 )
1049 if not styles[key]:
1077 if not styles[key]:
1050 styles[key] = None
1078 styles[key] = None
1051
1079
1052 # experimental config: experimental.graphshorten
1080 # experimental config: experimental.graphshorten
1053 state.graphshorten = ui.configbool(b'experimental', b'graphshorten')
1081 state.graphshorten = ui.configbool(b'experimental', b'graphshorten')
1054
1082
1055 formatnode_cache = {}
1083 formatnode_cache = {}
1056 for rev, type, ctx, parents in dag:
1084 for rev, type, ctx, parents in dag:
1057 char = formatnode(repo, ctx, formatnode_cache)
1085 char = formatnode(repo, ctx, formatnode_cache)
1058 copies = getcopies(ctx) if getcopies else None
1086 copies = getcopies(ctx) if getcopies else None
1059 edges = edgefn(type, char, state, rev, parents)
1087 edges = edgefn(type, char, state, rev, parents)
1060 firstedge = next(edges)
1088 firstedge = next(edges)
1061 width = firstedge[2]
1089 width = firstedge[2]
1062 displayer.show(
1090 displayer.show(
1063 ctx, copies=copies, graphwidth=width, **pycompat.strkwargs(props)
1091 ctx, copies=copies, graphwidth=width, **pycompat.strkwargs(props)
1064 )
1092 )
1065 lines = displayer.hunk.pop(rev).split(b'\n')
1093 lines = displayer.hunk.pop(rev).split(b'\n')
1066 if not lines[-1]:
1094 if not lines[-1]:
1067 del lines[-1]
1095 del lines[-1]
1068 displayer.flush(ctx)
1096 displayer.flush(ctx)
1069 for type, char, width, coldata in itertools.chain([firstedge], edges):
1097 for type, char, width, coldata in itertools.chain([firstedge], edges):
1070 graphmod.ascii(ui, state, type, char, lines, coldata)
1098 graphmod.ascii(ui, state, type, char, lines, coldata)
1071 lines = []
1099 lines = []
1072 displayer.close()
1100 displayer.close()
1073
1101
1074
1102
1075 def displaygraphrevs(ui, repo, revs, displayer, getrenamed):
1103 def displaygraphrevs(ui, repo, revs, displayer, getrenamed):
1076 revdag = graphmod.dagwalker(repo, revs)
1104 revdag = graphmod.dagwalker(repo, revs)
1077 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
1105 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
1078
1106
1079
1107
1080 def displayrevs(ui, repo, revs, displayer, getcopies):
1108 def displayrevs(ui, repo, revs, displayer, getcopies):
1081 for rev in revs:
1109 for rev in revs:
1082 ctx = repo[rev]
1110 ctx = repo[rev]
1083 copies = getcopies(ctx) if getcopies else None
1111 copies = getcopies(ctx) if getcopies else None
1084 displayer.show(ctx, copies=copies)
1112 displayer.show(ctx, copies=copies)
1085 displayer.flush(ctx)
1113 displayer.flush(ctx)
1086 displayer.close()
1114 displayer.close()
1087
1115
1088
1116
1089 def checkunsupportedgraphflags(pats, opts):
1117 def checkunsupportedgraphflags(pats, opts):
1090 for op in [b"newest_first"]:
1118 for op in [b"newest_first"]:
1091 if op in opts and opts[op]:
1119 if op in opts and opts[op]:
1092 raise error.Abort(
1120 raise error.Abort(
1093 _(b"-G/--graph option is incompatible with --%s")
1121 _(b"-G/--graph option is incompatible with --%s")
1094 % op.replace(b"_", b"-")
1122 % op.replace(b"_", b"-")
1095 )
1123 )
1096
1124
1097
1125
1098 def graphrevs(repo, nodes, opts):
1126 def graphrevs(repo, nodes, opts):
1099 limit = getlimit(opts)
1127 limit = getlimit(opts)
1100 nodes.reverse()
1128 nodes.reverse()
1101 if limit is not None:
1129 if limit is not None:
1102 nodes = nodes[:limit]
1130 nodes = nodes[:limit]
1103 return graphmod.nodes(repo, nodes)
1131 return graphmod.nodes(repo, nodes)
@@ -1,48 +1,50 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2 from mercurial.thirdparty import attr
2 from mercurial import (
3 from mercurial import (
3 cmdutil,
4 cmdutil,
4 commands,
5 commands,
5 extensions,
6 extensions,
6 logcmdutil,
7 logcmdutil,
7 revsetlang,
8 revsetlang,
8 smartset,
9 smartset,
9 )
10 )
10
11
11 from mercurial.utils import stringutil
12 from mercurial.utils import stringutil
12
13
13
14
14 def logrevset(repo, pats, opts):
15 def logrevset(repo, wopts):
15 revs = logcmdutil._initialrevs(repo, opts)
16 revs = logcmdutil._initialrevs(repo, wopts)
16 if not revs:
17 if not revs:
17 return None
18 return None
18 match, pats, slowpath = logcmdutil._makematcher(repo, revs, pats, opts)
19 match, pats, slowpath = logcmdutil._makematcher(repo, revs, wopts)
19 return logcmdutil._makerevset(repo, pats, slowpath, opts)
20 wopts = attr.evolve(wopts, pats=pats)
21 return logcmdutil._makerevset(repo, wopts, slowpath)
20
22
21
23
22 def uisetup(ui):
24 def uisetup(ui):
23 def printrevset(orig, repo, pats, opts):
25 def printrevset(orig, repo, wopts):
24 revs, filematcher = orig(repo, pats, opts)
26 revs, filematcher = orig(repo, wopts)
25 if opts.get(b'print_revset'):
27 if wopts.opts.get(b'print_revset'):
26 expr = logrevset(repo, pats, opts)
28 expr = logrevset(repo, wopts)
27 if expr:
29 if expr:
28 tree = revsetlang.parse(expr)
30 tree = revsetlang.parse(expr)
29 tree = revsetlang.analyze(tree)
31 tree = revsetlang.analyze(tree)
30 else:
32 else:
31 tree = []
33 tree = []
32 ui = repo.ui
34 ui = repo.ui
33 ui.write(b'%s\n' % stringutil.pprint(opts.get(b'rev', [])))
35 ui.write(b'%s\n' % stringutil.pprint(wopts.opts.get(b'rev', [])))
34 ui.write(revsetlang.prettyformat(tree) + b'\n')
36 ui.write(revsetlang.prettyformat(tree) + b'\n')
35 ui.write(stringutil.prettyrepr(revs) + b'\n')
37 ui.write(stringutil.prettyrepr(revs) + b'\n')
36 revs = smartset.baseset() # display no revisions
38 revs = smartset.baseset() # display no revisions
37 return revs, filematcher
39 return revs, filematcher
38
40
39 extensions.wrapfunction(logcmdutil, 'getrevs', printrevset)
41 extensions.wrapfunction(logcmdutil, 'getrevs', printrevset)
40 aliases, entry = cmdutil.findcmd(b'log', commands.table)
42 aliases, entry = cmdutil.findcmd(b'log', commands.table)
41 entry[1].append(
43 entry[1].append(
42 (
44 (
43 b'',
45 b'',
44 b'print-revset',
46 b'print-revset',
45 False,
47 False,
46 b'print generated revset and exit (DEPRECATED)',
48 b'print generated revset and exit (DEPRECATED)',
47 )
49 )
48 )
50 )
General Comments 0
You need to be logged in to leave comments. Login now