##// 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 1 # sparse.py - allow sparse checkouts of the working directory
2 2 #
3 3 # Copyright 2014 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """allow sparse checkouts of the working directory (EXPERIMENTAL)
9 9
10 10 (This extension is not yet protected by backwards compatibility
11 11 guarantees. Any aspect may break in future releases until this
12 12 notice is removed.)
13 13
14 14 This extension allows the working directory to only consist of a
15 15 subset of files for the revision. This allows specific files or
16 16 directories to be explicitly included or excluded. Many repository
17 17 operations have performance proportional to the number of files in
18 18 the working directory. So only realizing a subset of files in the
19 19 working directory can improve performance.
20 20
21 21 Sparse Config Files
22 22 -------------------
23 23
24 24 The set of files that are part of a sparse checkout are defined by
25 25 a sparse config file. The file defines 3 things: includes (files to
26 26 include in the sparse checkout), excludes (files to exclude from the
27 27 sparse checkout), and profiles (links to other config files).
28 28
29 29 The file format is newline delimited. Empty lines and lines beginning
30 30 with ``#`` are ignored.
31 31
32 32 Lines beginning with ``%include `` denote another sparse config file
33 33 to include. e.g. ``%include tests.sparse``. The filename is relative
34 34 to the repository root.
35 35
36 36 The special lines ``[include]`` and ``[exclude]`` denote the section
37 37 for includes and excludes that follow, respectively. It is illegal to
38 38 have ``[include]`` after ``[exclude]``.
39 39
40 40 Non-special lines resemble file patterns to be added to either includes
41 41 or excludes. The syntax of these lines is documented by :hg:`help patterns`.
42 42 Patterns are interpreted as ``glob:`` by default and match against the
43 43 root of the repository.
44 44
45 45 Exclusion patterns take precedence over inclusion patterns. So even
46 46 if a file is explicitly included, an ``[exclude]`` entry can remove it.
47 47
48 48 For example, say you have a repository with 3 directories, ``frontend/``,
49 49 ``backend/``, and ``tools/``. ``frontend/`` and ``backend/`` correspond
50 50 to different projects and it is uncommon for someone working on one
51 51 to need the files for the other. But ``tools/`` contains files shared
52 52 between both projects. Your sparse config files may resemble::
53 53
54 54 # frontend.sparse
55 55 frontend/**
56 56 tools/**
57 57
58 58 # backend.sparse
59 59 backend/**
60 60 tools/**
61 61
62 62 Say the backend grows in size. Or there's a directory with thousands
63 63 of files you wish to exclude. You can modify the profile to exclude
64 64 certain files::
65 65
66 66 [include]
67 67 backend/**
68 68 tools/**
69 69
70 70 [exclude]
71 71 tools/tests/**
72 72 """
73 73
74 74 from __future__ import absolute_import
75 75
76 76 from mercurial.i18n import _
77 77 from mercurial.pycompat import setattr
78 78 from mercurial import (
79 79 commands,
80 80 dirstate,
81 81 error,
82 82 extensions,
83 83 hg,
84 84 logcmdutil,
85 85 match as matchmod,
86 86 pycompat,
87 87 registrar,
88 88 sparse,
89 89 util,
90 90 )
91 91
92 92 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
93 93 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
94 94 # be specifying the version(s) of Mercurial they are tested with, or
95 95 # leave the attribute unspecified.
96 96 testedwith = b'ships-with-hg-core'
97 97
98 98 cmdtable = {}
99 99 command = registrar.command(cmdtable)
100 100
101 101
102 102 def extsetup(ui):
103 103 sparse.enabled = True
104 104
105 105 _setupclone(ui)
106 106 _setuplog(ui)
107 107 _setupadd(ui)
108 108 _setupdirstate(ui)
109 109
110 110
111 111 def replacefilecache(cls, propname, replacement):
112 112 """Replace a filecache property with a new class. This allows changing the
113 113 cache invalidation condition."""
114 114 origcls = cls
115 115 assert callable(replacement)
116 116 while cls is not object:
117 117 if propname in cls.__dict__:
118 118 orig = cls.__dict__[propname]
119 119 setattr(cls, propname, replacement(orig))
120 120 break
121 121 cls = cls.__bases__[0]
122 122
123 123 if cls is object:
124 124 raise AttributeError(
125 125 _(b"type '%s' has no property '%s'") % (origcls, propname)
126 126 )
127 127
128 128
129 129 def _setuplog(ui):
130 130 entry = commands.table[b'log|history']
131 131 entry[1].append(
132 132 (
133 133 b'',
134 134 b'sparse',
135 135 None,
136 136 b"limit to changesets affecting the sparse checkout",
137 137 )
138 138 )
139 139
140 def _initialrevs(orig, repo, opts):
141 revs = orig(repo, opts)
142 if opts.get(b'sparse'):
140 def _initialrevs(orig, repo, wopts):
141 revs = orig(repo, wopts)
142 if wopts.opts.get(b'sparse'):
143 143 sparsematch = sparse.matcher(repo)
144 144
145 145 def ctxmatch(rev):
146 146 ctx = repo[rev]
147 147 return any(f for f in ctx.files() if sparsematch(f))
148 148
149 149 revs = revs.filter(ctxmatch)
150 150 return revs
151 151
152 152 extensions.wrapfunction(logcmdutil, b'_initialrevs', _initialrevs)
153 153
154 154
155 155 def _clonesparsecmd(orig, ui, repo, *args, **opts):
156 156 include_pat = opts.get('include')
157 157 exclude_pat = opts.get('exclude')
158 158 enableprofile_pat = opts.get('enable_profile')
159 159 narrow_pat = opts.get('narrow')
160 160 include = exclude = enableprofile = False
161 161 if include_pat:
162 162 pat = include_pat
163 163 include = True
164 164 if exclude_pat:
165 165 pat = exclude_pat
166 166 exclude = True
167 167 if enableprofile_pat:
168 168 pat = enableprofile_pat
169 169 enableprofile = True
170 170 if sum([include, exclude, enableprofile]) > 1:
171 171 raise error.Abort(_(b"too many flags specified."))
172 172 # if --narrow is passed, it means they are includes and excludes for narrow
173 173 # clone
174 174 if not narrow_pat and (include or exclude or enableprofile):
175 175
176 176 def clonesparse(orig, self, node, overwrite, *args, **kwargs):
177 177 sparse.updateconfig(
178 178 self.unfiltered(),
179 179 pat,
180 180 {},
181 181 include=include,
182 182 exclude=exclude,
183 183 enableprofile=enableprofile,
184 184 usereporootpaths=True,
185 185 )
186 186 return orig(self, node, overwrite, *args, **kwargs)
187 187
188 188 extensions.wrapfunction(hg, b'updaterepo', clonesparse)
189 189 return orig(ui, repo, *args, **opts)
190 190
191 191
192 192 def _setupclone(ui):
193 193 entry = commands.table[b'clone']
194 194 entry[1].append((b'', b'enable-profile', [], b'enable a sparse profile'))
195 195 entry[1].append((b'', b'include', [], b'include sparse pattern'))
196 196 entry[1].append((b'', b'exclude', [], b'exclude sparse pattern'))
197 197 extensions.wrapcommand(commands.table, b'clone', _clonesparsecmd)
198 198
199 199
200 200 def _setupadd(ui):
201 201 entry = commands.table[b'add']
202 202 entry[1].append(
203 203 (
204 204 b's',
205 205 b'sparse',
206 206 None,
207 207 b'also include directories of added files in sparse config',
208 208 )
209 209 )
210 210
211 211 def _add(orig, ui, repo, *pats, **opts):
212 212 if opts.get('sparse'):
213 213 dirs = set()
214 214 for pat in pats:
215 215 dirname, basename = util.split(pat)
216 216 dirs.add(dirname)
217 217 sparse.updateconfig(repo, list(dirs), opts, include=True)
218 218 return orig(ui, repo, *pats, **opts)
219 219
220 220 extensions.wrapcommand(commands.table, b'add', _add)
221 221
222 222
223 223 def _setupdirstate(ui):
224 224 """Modify the dirstate to prevent stat'ing excluded files,
225 225 and to prevent modifications to files outside the checkout.
226 226 """
227 227
228 228 def walk(orig, self, match, subrepos, unknown, ignored, full=True):
229 229 # hack to not exclude explicitly-specified paths so that they can
230 230 # be warned later on e.g. dirstate.add()
231 231 em = matchmod.exact(match.files())
232 232 sm = matchmod.unionmatcher([self._sparsematcher, em])
233 233 match = matchmod.intersectmatchers(match, sm)
234 234 return orig(self, match, subrepos, unknown, ignored, full)
235 235
236 236 extensions.wrapfunction(dirstate.dirstate, b'walk', walk)
237 237
238 238 # dirstate.rebuild should not add non-matching files
239 239 def _rebuild(orig, self, parent, allfiles, changedfiles=None):
240 240 matcher = self._sparsematcher
241 241 if not matcher.always():
242 242 allfiles = [f for f in allfiles if matcher(f)]
243 243 if changedfiles:
244 244 changedfiles = [f for f in changedfiles if matcher(f)]
245 245
246 246 if changedfiles is not None:
247 247 # In _rebuild, these files will be deleted from the dirstate
248 248 # when they are not found to be in allfiles
249 249 dirstatefilestoremove = {f for f in self if not matcher(f)}
250 250 changedfiles = dirstatefilestoremove.union(changedfiles)
251 251
252 252 return orig(self, parent, allfiles, changedfiles)
253 253
254 254 extensions.wrapfunction(dirstate.dirstate, b'rebuild', _rebuild)
255 255
256 256 # Prevent adding files that are outside the sparse checkout
257 257 editfuncs = [
258 258 b'normal',
259 259 b'add',
260 260 b'normallookup',
261 261 b'copy',
262 262 b'remove',
263 263 b'merge',
264 264 ]
265 265 hint = _(
266 266 b'include file with `hg debugsparse --include <pattern>` or use '
267 267 + b'`hg add -s <file>` to include file directory while adding'
268 268 )
269 269 for func in editfuncs:
270 270
271 271 def _wrapper(orig, self, *args, **kwargs):
272 272 sparsematch = self._sparsematcher
273 273 if not sparsematch.always():
274 274 for f in args:
275 275 if f is not None and not sparsematch(f) and f not in self:
276 276 raise error.Abort(
277 277 _(
278 278 b"cannot add '%s' - it is outside "
279 279 b"the sparse checkout"
280 280 )
281 281 % f,
282 282 hint=hint,
283 283 )
284 284 return orig(self, *args, **kwargs)
285 285
286 286 extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
287 287
288 288
289 289 @command(
290 290 b'debugsparse',
291 291 [
292 292 (b'I', b'include', False, _(b'include files in the sparse checkout')),
293 293 (b'X', b'exclude', False, _(b'exclude files in the sparse checkout')),
294 294 (b'd', b'delete', False, _(b'delete an include/exclude rule')),
295 295 (
296 296 b'f',
297 297 b'force',
298 298 False,
299 299 _(b'allow changing rules even with pending changes'),
300 300 ),
301 301 (b'', b'enable-profile', False, _(b'enables the specified profile')),
302 302 (b'', b'disable-profile', False, _(b'disables the specified profile')),
303 303 (b'', b'import-rules', False, _(b'imports rules from a file')),
304 304 (b'', b'clear-rules', False, _(b'clears local include/exclude rules')),
305 305 (
306 306 b'',
307 307 b'refresh',
308 308 False,
309 309 _(b'updates the working after sparseness changes'),
310 310 ),
311 311 (b'', b'reset', False, _(b'makes the repo full again')),
312 312 ]
313 313 + commands.templateopts,
314 314 _(b'[--OPTION] PATTERN...'),
315 315 helpbasic=True,
316 316 )
317 317 def debugsparse(ui, repo, *pats, **opts):
318 318 """make the current checkout sparse, or edit the existing checkout
319 319
320 320 The sparse command is used to make the current checkout sparse.
321 321 This means files that don't meet the sparse condition will not be
322 322 written to disk, or show up in any working copy operations. It does
323 323 not affect files in history in any way.
324 324
325 325 Passing no arguments prints the currently applied sparse rules.
326 326
327 327 --include and --exclude are used to add and remove files from the sparse
328 328 checkout. The effects of adding an include or exclude rule are applied
329 329 immediately. If applying the new rule would cause a file with pending
330 330 changes to be added or removed, the command will fail. Pass --force to
331 331 force a rule change even with pending changes (the changes on disk will
332 332 be preserved).
333 333
334 334 --delete removes an existing include/exclude rule. The effects are
335 335 immediate.
336 336
337 337 --refresh refreshes the files on disk based on the sparse rules. This is
338 338 only necessary if .hg/sparse was changed by hand.
339 339
340 340 --enable-profile and --disable-profile accept a path to a .hgsparse file.
341 341 This allows defining sparse checkouts and tracking them inside the
342 342 repository. This is useful for defining commonly used sparse checkouts for
343 343 many people to use. As the profile definition changes over time, the sparse
344 344 checkout will automatically be updated appropriately, depending on which
345 345 changeset is checked out. Changes to .hgsparse are not applied until they
346 346 have been committed.
347 347
348 348 --import-rules accepts a path to a file containing rules in the .hgsparse
349 349 format, allowing you to add --include, --exclude and --enable-profile rules
350 350 in bulk. Like the --include, --exclude and --enable-profile switches, the
351 351 changes are applied immediately.
352 352
353 353 --clear-rules removes all local include and exclude rules, while leaving
354 354 any enabled profiles in place.
355 355
356 356 Returns 0 if editing the sparse checkout succeeds.
357 357 """
358 358 opts = pycompat.byteskwargs(opts)
359 359 include = opts.get(b'include')
360 360 exclude = opts.get(b'exclude')
361 361 force = opts.get(b'force')
362 362 enableprofile = opts.get(b'enable_profile')
363 363 disableprofile = opts.get(b'disable_profile')
364 364 importrules = opts.get(b'import_rules')
365 365 clearrules = opts.get(b'clear_rules')
366 366 delete = opts.get(b'delete')
367 367 refresh = opts.get(b'refresh')
368 368 reset = opts.get(b'reset')
369 369 count = sum(
370 370 [
371 371 include,
372 372 exclude,
373 373 enableprofile,
374 374 disableprofile,
375 375 delete,
376 376 importrules,
377 377 refresh,
378 378 clearrules,
379 379 reset,
380 380 ]
381 381 )
382 382 if count > 1:
383 383 raise error.Abort(_(b"too many flags specified"))
384 384
385 385 if count == 0:
386 386 if repo.vfs.exists(b'sparse'):
387 387 ui.status(repo.vfs.read(b"sparse") + b"\n")
388 388 temporaryincludes = sparse.readtemporaryincludes(repo)
389 389 if temporaryincludes:
390 390 ui.status(
391 391 _(b"Temporarily Included Files (for merge/rebase):\n")
392 392 )
393 393 ui.status((b"\n".join(temporaryincludes) + b"\n"))
394 394 return
395 395 else:
396 396 raise error.Abort(
397 397 _(
398 398 b'the debugsparse command is only supported on'
399 399 b' sparse repositories'
400 400 )
401 401 )
402 402
403 403 if include or exclude or delete or reset or enableprofile or disableprofile:
404 404 sparse.updateconfig(
405 405 repo,
406 406 pats,
407 407 opts,
408 408 include=include,
409 409 exclude=exclude,
410 410 reset=reset,
411 411 delete=delete,
412 412 enableprofile=enableprofile,
413 413 disableprofile=disableprofile,
414 414 force=force,
415 415 )
416 416
417 417 if importrules:
418 418 sparse.importfromfiles(repo, opts, pats, force=force)
419 419
420 420 if clearrules:
421 421 sparse.clearrules(repo, force=force)
422 422
423 423 if refresh:
424 424 try:
425 425 wlock = repo.wlock()
426 426 fcounts = map(
427 427 len,
428 428 sparse.refreshwdir(
429 429 repo, repo.status(), sparse.matcher(repo), force=force
430 430 ),
431 431 )
432 432 sparse.printchanges(
433 433 ui,
434 434 opts,
435 435 added=fcounts[0],
436 436 dropped=fcounts[1],
437 437 conflicting=fcounts[2],
438 438 )
439 439 finally:
440 440 wlock.release()
@@ -1,7808 +1,7810 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import difflib
11 11 import errno
12 12 import os
13 13 import re
14 14 import sys
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 hex,
19 19 nullid,
20 20 nullrev,
21 21 short,
22 22 wdirhex,
23 23 wdirrev,
24 24 )
25 25 from .pycompat import open
26 26 from . import (
27 27 archival,
28 28 bookmarks,
29 29 bundle2,
30 30 changegroup,
31 31 cmdutil,
32 32 copies,
33 33 debugcommands as debugcommandsmod,
34 34 destutil,
35 35 dirstateguard,
36 36 discovery,
37 37 encoding,
38 38 error,
39 39 exchange,
40 40 extensions,
41 41 filemerge,
42 42 formatter,
43 43 graphmod,
44 44 hbisect,
45 45 help,
46 46 hg,
47 47 logcmdutil,
48 48 merge as mergemod,
49 49 mergestate as mergestatemod,
50 50 narrowspec,
51 51 obsolete,
52 52 obsutil,
53 53 patch,
54 54 phases,
55 55 pycompat,
56 56 rcutil,
57 57 registrar,
58 58 requirements,
59 59 revsetlang,
60 60 rewriteutil,
61 61 scmutil,
62 62 server,
63 63 shelve as shelvemod,
64 64 state as statemod,
65 65 streamclone,
66 66 tags as tagsmod,
67 67 ui as uimod,
68 68 util,
69 69 verify as verifymod,
70 70 vfs as vfsmod,
71 71 wireprotoserver,
72 72 )
73 73 from .utils import (
74 74 dateutil,
75 75 stringutil,
76 76 )
77 77
78 78 table = {}
79 79 table.update(debugcommandsmod.command._table)
80 80
81 81 command = registrar.command(table)
82 82 INTENT_READONLY = registrar.INTENT_READONLY
83 83
84 84 # common command options
85 85
86 86 globalopts = [
87 87 (
88 88 b'R',
89 89 b'repository',
90 90 b'',
91 91 _(b'repository root directory or name of overlay bundle file'),
92 92 _(b'REPO'),
93 93 ),
94 94 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
95 95 (
96 96 b'y',
97 97 b'noninteractive',
98 98 None,
99 99 _(
100 100 b'do not prompt, automatically pick the first choice for all prompts'
101 101 ),
102 102 ),
103 103 (b'q', b'quiet', None, _(b'suppress output')),
104 104 (b'v', b'verbose', None, _(b'enable additional output')),
105 105 (
106 106 b'',
107 107 b'color',
108 108 b'',
109 109 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
110 110 # and should not be translated
111 111 _(b"when to colorize (boolean, always, auto, never, or debug)"),
112 112 _(b'TYPE'),
113 113 ),
114 114 (
115 115 b'',
116 116 b'config',
117 117 [],
118 118 _(b'set/override config option (use \'section.name=value\')'),
119 119 _(b'CONFIG'),
120 120 ),
121 121 (b'', b'debug', None, _(b'enable debugging output')),
122 122 (b'', b'debugger', None, _(b'start debugger')),
123 123 (
124 124 b'',
125 125 b'encoding',
126 126 encoding.encoding,
127 127 _(b'set the charset encoding'),
128 128 _(b'ENCODE'),
129 129 ),
130 130 (
131 131 b'',
132 132 b'encodingmode',
133 133 encoding.encodingmode,
134 134 _(b'set the charset encoding mode'),
135 135 _(b'MODE'),
136 136 ),
137 137 (b'', b'traceback', None, _(b'always print a traceback on exception')),
138 138 (b'', b'time', None, _(b'time how long the command takes')),
139 139 (b'', b'profile', None, _(b'print command execution profile')),
140 140 (b'', b'version', None, _(b'output version information and exit')),
141 141 (b'h', b'help', None, _(b'display help and exit')),
142 142 (b'', b'hidden', False, _(b'consider hidden changesets')),
143 143 (
144 144 b'',
145 145 b'pager',
146 146 b'auto',
147 147 _(b"when to paginate (boolean, always, auto, or never)"),
148 148 _(b'TYPE'),
149 149 ),
150 150 ]
151 151
152 152 dryrunopts = cmdutil.dryrunopts
153 153 remoteopts = cmdutil.remoteopts
154 154 walkopts = cmdutil.walkopts
155 155 commitopts = cmdutil.commitopts
156 156 commitopts2 = cmdutil.commitopts2
157 157 commitopts3 = cmdutil.commitopts3
158 158 formatteropts = cmdutil.formatteropts
159 159 templateopts = cmdutil.templateopts
160 160 logopts = cmdutil.logopts
161 161 diffopts = cmdutil.diffopts
162 162 diffwsopts = cmdutil.diffwsopts
163 163 diffopts2 = cmdutil.diffopts2
164 164 mergetoolopts = cmdutil.mergetoolopts
165 165 similarityopts = cmdutil.similarityopts
166 166 subrepoopts = cmdutil.subrepoopts
167 167 debugrevlogopts = cmdutil.debugrevlogopts
168 168
169 169 # Commands start here, listed alphabetically
170 170
171 171
172 172 @command(
173 173 b'abort',
174 174 dryrunopts,
175 175 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
176 176 helpbasic=True,
177 177 )
178 178 def abort(ui, repo, **opts):
179 179 """abort an unfinished operation (EXPERIMENTAL)
180 180
181 181 Aborts a multistep operation like graft, histedit, rebase, merge,
182 182 and unshelve if they are in an unfinished state.
183 183
184 184 use --dry-run/-n to dry run the command.
185 185 """
186 186 dryrun = opts.get('dry_run')
187 187 abortstate = cmdutil.getunfinishedstate(repo)
188 188 if not abortstate:
189 189 raise error.Abort(_(b'no operation in progress'))
190 190 if not abortstate.abortfunc:
191 191 raise error.Abort(
192 192 (
193 193 _(b"%s in progress but does not support 'hg abort'")
194 194 % (abortstate._opname)
195 195 ),
196 196 hint=abortstate.hint(),
197 197 )
198 198 if dryrun:
199 199 ui.status(
200 200 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
201 201 )
202 202 return
203 203 return abortstate.abortfunc(ui, repo)
204 204
205 205
206 206 @command(
207 207 b'add',
208 208 walkopts + subrepoopts + dryrunopts,
209 209 _(b'[OPTION]... [FILE]...'),
210 210 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
211 211 helpbasic=True,
212 212 inferrepo=True,
213 213 )
214 214 def add(ui, repo, *pats, **opts):
215 215 """add the specified files on the next commit
216 216
217 217 Schedule files to be version controlled and added to the
218 218 repository.
219 219
220 220 The files will be added to the repository at the next commit. To
221 221 undo an add before that, see :hg:`forget`.
222 222
223 223 If no names are given, add all files to the repository (except
224 224 files matching ``.hgignore``).
225 225
226 226 .. container:: verbose
227 227
228 228 Examples:
229 229
230 230 - New (unknown) files are added
231 231 automatically by :hg:`add`::
232 232
233 233 $ ls
234 234 foo.c
235 235 $ hg status
236 236 ? foo.c
237 237 $ hg add
238 238 adding foo.c
239 239 $ hg status
240 240 A foo.c
241 241
242 242 - Specific files to be added can be specified::
243 243
244 244 $ ls
245 245 bar.c foo.c
246 246 $ hg status
247 247 ? bar.c
248 248 ? foo.c
249 249 $ hg add bar.c
250 250 $ hg status
251 251 A bar.c
252 252 ? foo.c
253 253
254 254 Returns 0 if all files are successfully added.
255 255 """
256 256
257 257 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
258 258 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
259 259 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
260 260 return rejected and 1 or 0
261 261
262 262
263 263 @command(
264 264 b'addremove',
265 265 similarityopts + subrepoopts + walkopts + dryrunopts,
266 266 _(b'[OPTION]... [FILE]...'),
267 267 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
268 268 inferrepo=True,
269 269 )
270 270 def addremove(ui, repo, *pats, **opts):
271 271 """add all new files, delete all missing files
272 272
273 273 Add all new files and remove all missing files from the
274 274 repository.
275 275
276 276 Unless names are given, new files are ignored if they match any of
277 277 the patterns in ``.hgignore``. As with add, these changes take
278 278 effect at the next commit.
279 279
280 280 Use the -s/--similarity option to detect renamed files. This
281 281 option takes a percentage between 0 (disabled) and 100 (files must
282 282 be identical) as its parameter. With a parameter greater than 0,
283 283 this compares every removed file with every added file and records
284 284 those similar enough as renames. Detecting renamed files this way
285 285 can be expensive. After using this option, :hg:`status -C` can be
286 286 used to check which files were identified as moved or renamed. If
287 287 not specified, -s/--similarity defaults to 100 and only renames of
288 288 identical files are detected.
289 289
290 290 .. container:: verbose
291 291
292 292 Examples:
293 293
294 294 - A number of files (bar.c and foo.c) are new,
295 295 while foobar.c has been removed (without using :hg:`remove`)
296 296 from the repository::
297 297
298 298 $ ls
299 299 bar.c foo.c
300 300 $ hg status
301 301 ! foobar.c
302 302 ? bar.c
303 303 ? foo.c
304 304 $ hg addremove
305 305 adding bar.c
306 306 adding foo.c
307 307 removing foobar.c
308 308 $ hg status
309 309 A bar.c
310 310 A foo.c
311 311 R foobar.c
312 312
313 313 - A file foobar.c was moved to foo.c without using :hg:`rename`.
314 314 Afterwards, it was edited slightly::
315 315
316 316 $ ls
317 317 foo.c
318 318 $ hg status
319 319 ! foobar.c
320 320 ? foo.c
321 321 $ hg addremove --similarity 90
322 322 removing foobar.c
323 323 adding foo.c
324 324 recording removal of foobar.c as rename to foo.c (94% similar)
325 325 $ hg status -C
326 326 A foo.c
327 327 foobar.c
328 328 R foobar.c
329 329
330 330 Returns 0 if all files are successfully added.
331 331 """
332 332 opts = pycompat.byteskwargs(opts)
333 333 if not opts.get(b'similarity'):
334 334 opts[b'similarity'] = b'100'
335 335 matcher = scmutil.match(repo[None], pats, opts)
336 336 relative = scmutil.anypats(pats, opts)
337 337 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
338 338 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
339 339
340 340
341 341 @command(
342 342 b'annotate|blame',
343 343 [
344 344 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
345 345 (
346 346 b'',
347 347 b'follow',
348 348 None,
349 349 _(b'follow copies/renames and list the filename (DEPRECATED)'),
350 350 ),
351 351 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
352 352 (b'a', b'text', None, _(b'treat all files as text')),
353 353 (b'u', b'user', None, _(b'list the author (long with -v)')),
354 354 (b'f', b'file', None, _(b'list the filename')),
355 355 (b'd', b'date', None, _(b'list the date (short with -q)')),
356 356 (b'n', b'number', None, _(b'list the revision number (default)')),
357 357 (b'c', b'changeset', None, _(b'list the changeset')),
358 358 (
359 359 b'l',
360 360 b'line-number',
361 361 None,
362 362 _(b'show line number at the first appearance'),
363 363 ),
364 364 (
365 365 b'',
366 366 b'skip',
367 367 [],
368 368 _(b'revset to not display (EXPERIMENTAL)'),
369 369 _(b'REV'),
370 370 ),
371 371 ]
372 372 + diffwsopts
373 373 + walkopts
374 374 + formatteropts,
375 375 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
376 376 helpcategory=command.CATEGORY_FILE_CONTENTS,
377 377 helpbasic=True,
378 378 inferrepo=True,
379 379 )
380 380 def annotate(ui, repo, *pats, **opts):
381 381 """show changeset information by line for each file
382 382
383 383 List changes in files, showing the revision id responsible for
384 384 each line.
385 385
386 386 This command is useful for discovering when a change was made and
387 387 by whom.
388 388
389 389 If you include --file, --user, or --date, the revision number is
390 390 suppressed unless you also include --number.
391 391
392 392 Without the -a/--text option, annotate will avoid processing files
393 393 it detects as binary. With -a, annotate will annotate the file
394 394 anyway, although the results will probably be neither useful
395 395 nor desirable.
396 396
397 397 .. container:: verbose
398 398
399 399 Template:
400 400
401 401 The following keywords are supported in addition to the common template
402 402 keywords and functions. See also :hg:`help templates`.
403 403
404 404 :lines: List of lines with annotation data.
405 405 :path: String. Repository-absolute path of the specified file.
406 406
407 407 And each entry of ``{lines}`` provides the following sub-keywords in
408 408 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
409 409
410 410 :line: String. Line content.
411 411 :lineno: Integer. Line number at that revision.
412 412 :path: String. Repository-absolute path of the file at that revision.
413 413
414 414 See :hg:`help templates.operators` for the list expansion syntax.
415 415
416 416 Returns 0 on success.
417 417 """
418 418 opts = pycompat.byteskwargs(opts)
419 419 if not pats:
420 420 raise error.Abort(_(b'at least one filename or pattern is required'))
421 421
422 422 if opts.get(b'follow'):
423 423 # --follow is deprecated and now just an alias for -f/--file
424 424 # to mimic the behavior of Mercurial before version 1.5
425 425 opts[b'file'] = True
426 426
427 427 if (
428 428 not opts.get(b'user')
429 429 and not opts.get(b'changeset')
430 430 and not opts.get(b'date')
431 431 and not opts.get(b'file')
432 432 ):
433 433 opts[b'number'] = True
434 434
435 435 linenumber = opts.get(b'line_number') is not None
436 436 if (
437 437 linenumber
438 438 and (not opts.get(b'changeset'))
439 439 and (not opts.get(b'number'))
440 440 ):
441 441 raise error.Abort(_(b'at least one of -n/-c is required for -l'))
442 442
443 443 rev = opts.get(b'rev')
444 444 if rev:
445 445 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
446 446 ctx = scmutil.revsingle(repo, rev)
447 447
448 448 ui.pager(b'annotate')
449 449 rootfm = ui.formatter(b'annotate', opts)
450 450 if ui.debugflag:
451 451 shorthex = pycompat.identity
452 452 else:
453 453
454 454 def shorthex(h):
455 455 return h[:12]
456 456
457 457 if ui.quiet:
458 458 datefunc = dateutil.shortdate
459 459 else:
460 460 datefunc = dateutil.datestr
461 461 if ctx.rev() is None:
462 462 if opts.get(b'changeset'):
463 463 # omit "+" suffix which is appended to node hex
464 464 def formatrev(rev):
465 465 if rev == wdirrev:
466 466 return b'%d' % ctx.p1().rev()
467 467 else:
468 468 return b'%d' % rev
469 469
470 470 else:
471 471
472 472 def formatrev(rev):
473 473 if rev == wdirrev:
474 474 return b'%d+' % ctx.p1().rev()
475 475 else:
476 476 return b'%d ' % rev
477 477
478 478 def formathex(h):
479 479 if h == wdirhex:
480 480 return b'%s+' % shorthex(hex(ctx.p1().node()))
481 481 else:
482 482 return b'%s ' % shorthex(h)
483 483
484 484 else:
485 485 formatrev = b'%d'.__mod__
486 486 formathex = shorthex
487 487
488 488 opmap = [
489 489 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
490 490 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
491 491 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
492 492 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
493 493 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
494 494 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
495 495 ]
496 496 opnamemap = {
497 497 b'rev': b'number',
498 498 b'node': b'changeset',
499 499 b'path': b'file',
500 500 b'lineno': b'line_number',
501 501 }
502 502
503 503 if rootfm.isplain():
504 504
505 505 def makefunc(get, fmt):
506 506 return lambda x: fmt(get(x))
507 507
508 508 else:
509 509
510 510 def makefunc(get, fmt):
511 511 return get
512 512
513 513 datahint = rootfm.datahint()
514 514 funcmap = [
515 515 (makefunc(get, fmt), sep)
516 516 for fn, sep, get, fmt in opmap
517 517 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
518 518 ]
519 519 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
520 520 fields = b' '.join(
521 521 fn
522 522 for fn, sep, get, fmt in opmap
523 523 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
524 524 )
525 525
526 526 def bad(x, y):
527 527 raise error.Abort(b"%s: %s" % (x, y))
528 528
529 529 m = scmutil.match(ctx, pats, opts, badfn=bad)
530 530
531 531 follow = not opts.get(b'no_follow')
532 532 diffopts = patch.difffeatureopts(
533 533 ui, opts, section=b'annotate', whitespace=True
534 534 )
535 535 skiprevs = opts.get(b'skip')
536 536 if skiprevs:
537 537 skiprevs = scmutil.revrange(repo, skiprevs)
538 538
539 539 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
540 540 for abs in ctx.walk(m):
541 541 fctx = ctx[abs]
542 542 rootfm.startitem()
543 543 rootfm.data(path=abs)
544 544 if not opts.get(b'text') and fctx.isbinary():
545 545 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
546 546 continue
547 547
548 548 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
549 549 lines = fctx.annotate(
550 550 follow=follow, skiprevs=skiprevs, diffopts=diffopts
551 551 )
552 552 if not lines:
553 553 fm.end()
554 554 continue
555 555 formats = []
556 556 pieces = []
557 557
558 558 for f, sep in funcmap:
559 559 l = [f(n) for n in lines]
560 560 if fm.isplain():
561 561 sizes = [encoding.colwidth(x) for x in l]
562 562 ml = max(sizes)
563 563 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
564 564 else:
565 565 formats.append([b'%s'] * len(l))
566 566 pieces.append(l)
567 567
568 568 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
569 569 fm.startitem()
570 570 fm.context(fctx=n.fctx)
571 571 fm.write(fields, b"".join(f), *p)
572 572 if n.skip:
573 573 fmt = b"* %s"
574 574 else:
575 575 fmt = b": %s"
576 576 fm.write(b'line', fmt, n.text)
577 577
578 578 if not lines[-1].text.endswith(b'\n'):
579 579 fm.plain(b'\n')
580 580 fm.end()
581 581
582 582 rootfm.end()
583 583
584 584
585 585 @command(
586 586 b'archive',
587 587 [
588 588 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
589 589 (
590 590 b'p',
591 591 b'prefix',
592 592 b'',
593 593 _(b'directory prefix for files in archive'),
594 594 _(b'PREFIX'),
595 595 ),
596 596 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
597 597 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
598 598 ]
599 599 + subrepoopts
600 600 + walkopts,
601 601 _(b'[OPTION]... DEST'),
602 602 helpcategory=command.CATEGORY_IMPORT_EXPORT,
603 603 )
604 604 def archive(ui, repo, dest, **opts):
605 605 '''create an unversioned archive of a repository revision
606 606
607 607 By default, the revision used is the parent of the working
608 608 directory; use -r/--rev to specify a different revision.
609 609
610 610 The archive type is automatically detected based on file
611 611 extension (to override, use -t/--type).
612 612
613 613 .. container:: verbose
614 614
615 615 Examples:
616 616
617 617 - create a zip file containing the 1.0 release::
618 618
619 619 hg archive -r 1.0 project-1.0.zip
620 620
621 621 - create a tarball excluding .hg files::
622 622
623 623 hg archive project.tar.gz -X ".hg*"
624 624
625 625 Valid types are:
626 626
627 627 :``files``: a directory full of files (default)
628 628 :``tar``: tar archive, uncompressed
629 629 :``tbz2``: tar archive, compressed using bzip2
630 630 :``tgz``: tar archive, compressed using gzip
631 631 :``txz``: tar archive, compressed using lzma (only in Python 3)
632 632 :``uzip``: zip archive, uncompressed
633 633 :``zip``: zip archive, compressed using deflate
634 634
635 635 The exact name of the destination archive or directory is given
636 636 using a format string; see :hg:`help export` for details.
637 637
638 638 Each member added to an archive file has a directory prefix
639 639 prepended. Use -p/--prefix to specify a format string for the
640 640 prefix. The default is the basename of the archive, with suffixes
641 641 removed.
642 642
643 643 Returns 0 on success.
644 644 '''
645 645
646 646 opts = pycompat.byteskwargs(opts)
647 647 rev = opts.get(b'rev')
648 648 if rev:
649 649 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
650 650 ctx = scmutil.revsingle(repo, rev)
651 651 if not ctx:
652 652 raise error.Abort(_(b'no working directory: please specify a revision'))
653 653 node = ctx.node()
654 654 dest = cmdutil.makefilename(ctx, dest)
655 655 if os.path.realpath(dest) == repo.root:
656 656 raise error.Abort(_(b'repository root cannot be destination'))
657 657
658 658 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
659 659 prefix = opts.get(b'prefix')
660 660
661 661 if dest == b'-':
662 662 if kind == b'files':
663 663 raise error.Abort(_(b'cannot archive plain files to stdout'))
664 664 dest = cmdutil.makefileobj(ctx, dest)
665 665 if not prefix:
666 666 prefix = os.path.basename(repo.root) + b'-%h'
667 667
668 668 prefix = cmdutil.makefilename(ctx, prefix)
669 669 match = scmutil.match(ctx, [], opts)
670 670 archival.archive(
671 671 repo,
672 672 dest,
673 673 node,
674 674 kind,
675 675 not opts.get(b'no_decode'),
676 676 match,
677 677 prefix,
678 678 subrepos=opts.get(b'subrepos'),
679 679 )
680 680
681 681
682 682 @command(
683 683 b'backout',
684 684 [
685 685 (
686 686 b'',
687 687 b'merge',
688 688 None,
689 689 _(b'merge with old dirstate parent after backout'),
690 690 ),
691 691 (
692 692 b'',
693 693 b'commit',
694 694 None,
695 695 _(b'commit if no conflicts were encountered (DEPRECATED)'),
696 696 ),
697 697 (b'', b'no-commit', None, _(b'do not commit')),
698 698 (
699 699 b'',
700 700 b'parent',
701 701 b'',
702 702 _(b'parent to choose when backing out merge (DEPRECATED)'),
703 703 _(b'REV'),
704 704 ),
705 705 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
706 706 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
707 707 ]
708 708 + mergetoolopts
709 709 + walkopts
710 710 + commitopts
711 711 + commitopts2,
712 712 _(b'[OPTION]... [-r] REV'),
713 713 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
714 714 )
715 715 def backout(ui, repo, node=None, rev=None, **opts):
716 716 '''reverse effect of earlier changeset
717 717
718 718 Prepare a new changeset with the effect of REV undone in the
719 719 current working directory. If no conflicts were encountered,
720 720 it will be committed immediately.
721 721
722 722 If REV is the parent of the working directory, then this new changeset
723 723 is committed automatically (unless --no-commit is specified).
724 724
725 725 .. note::
726 726
727 727 :hg:`backout` cannot be used to fix either an unwanted or
728 728 incorrect merge.
729 729
730 730 .. container:: verbose
731 731
732 732 Examples:
733 733
734 734 - Reverse the effect of the parent of the working directory.
735 735 This backout will be committed immediately::
736 736
737 737 hg backout -r .
738 738
739 739 - Reverse the effect of previous bad revision 23::
740 740
741 741 hg backout -r 23
742 742
743 743 - Reverse the effect of previous bad revision 23 and
744 744 leave changes uncommitted::
745 745
746 746 hg backout -r 23 --no-commit
747 747 hg commit -m "Backout revision 23"
748 748
749 749 By default, the pending changeset will have one parent,
750 750 maintaining a linear history. With --merge, the pending
751 751 changeset will instead have two parents: the old parent of the
752 752 working directory and a new child of REV that simply undoes REV.
753 753
754 754 Before version 1.7, the behavior without --merge was equivalent
755 755 to specifying --merge followed by :hg:`update --clean .` to
756 756 cancel the merge and leave the child of REV as a head to be
757 757 merged separately.
758 758
759 759 See :hg:`help dates` for a list of formats valid for -d/--date.
760 760
761 761 See :hg:`help revert` for a way to restore files to the state
762 762 of another revision.
763 763
764 764 Returns 0 on success, 1 if nothing to backout or there are unresolved
765 765 files.
766 766 '''
767 767 with repo.wlock(), repo.lock():
768 768 return _dobackout(ui, repo, node, rev, **opts)
769 769
770 770
771 771 def _dobackout(ui, repo, node=None, rev=None, **opts):
772 772 opts = pycompat.byteskwargs(opts)
773 773 if opts.get(b'commit') and opts.get(b'no_commit'):
774 774 raise error.Abort(_(b"cannot use --commit with --no-commit"))
775 775 if opts.get(b'merge') and opts.get(b'no_commit'):
776 776 raise error.Abort(_(b"cannot use --merge with --no-commit"))
777 777
778 778 if rev and node:
779 779 raise error.Abort(_(b"please specify just one revision"))
780 780
781 781 if not rev:
782 782 rev = node
783 783
784 784 if not rev:
785 785 raise error.Abort(_(b"please specify a revision to backout"))
786 786
787 787 date = opts.get(b'date')
788 788 if date:
789 789 opts[b'date'] = dateutil.parsedate(date)
790 790
791 791 cmdutil.checkunfinished(repo)
792 792 cmdutil.bailifchanged(repo)
793 793 ctx = scmutil.revsingle(repo, rev)
794 794 node = ctx.node()
795 795
796 796 op1, op2 = repo.dirstate.parents()
797 797 if not repo.changelog.isancestor(node, op1):
798 798 raise error.Abort(_(b'cannot backout change that is not an ancestor'))
799 799
800 800 p1, p2 = repo.changelog.parents(node)
801 801 if p1 == nullid:
802 802 raise error.Abort(_(b'cannot backout a change with no parents'))
803 803 if p2 != nullid:
804 804 if not opts.get(b'parent'):
805 805 raise error.Abort(_(b'cannot backout a merge changeset'))
806 806 p = repo.lookup(opts[b'parent'])
807 807 if p not in (p1, p2):
808 808 raise error.Abort(
809 809 _(b'%s is not a parent of %s') % (short(p), short(node))
810 810 )
811 811 parent = p
812 812 else:
813 813 if opts.get(b'parent'):
814 814 raise error.Abort(_(b'cannot use --parent on non-merge changeset'))
815 815 parent = p1
816 816
817 817 # the backout should appear on the same branch
818 818 branch = repo.dirstate.branch()
819 819 bheads = repo.branchheads(branch)
820 820 rctx = scmutil.revsingle(repo, hex(parent))
821 821 if not opts.get(b'merge') and op1 != node:
822 822 with dirstateguard.dirstateguard(repo, b'backout'):
823 823 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
824 824 with ui.configoverride(overrides, b'backout'):
825 825 stats = mergemod.back_out(ctx, parent=repo[parent])
826 826 repo.setparents(op1, op2)
827 827 hg._showstats(repo, stats)
828 828 if stats.unresolvedcount:
829 829 repo.ui.status(
830 830 _(b"use 'hg resolve' to retry unresolved file merges\n")
831 831 )
832 832 return 1
833 833 else:
834 834 hg.clean(repo, node, show_stats=False)
835 835 repo.dirstate.setbranch(branch)
836 836 cmdutil.revert(ui, repo, rctx)
837 837
838 838 if opts.get(b'no_commit'):
839 839 msg = _(b"changeset %s backed out, don't forget to commit.\n")
840 840 ui.status(msg % short(node))
841 841 return 0
842 842
843 843 def commitfunc(ui, repo, message, match, opts):
844 844 editform = b'backout'
845 845 e = cmdutil.getcommiteditor(
846 846 editform=editform, **pycompat.strkwargs(opts)
847 847 )
848 848 if not message:
849 849 # we don't translate commit messages
850 850 message = b"Backed out changeset %s" % short(node)
851 851 e = cmdutil.getcommiteditor(edit=True, editform=editform)
852 852 return repo.commit(
853 853 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
854 854 )
855 855
856 856 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
857 857 if not newnode:
858 858 ui.status(_(b"nothing changed\n"))
859 859 return 1
860 860 cmdutil.commitstatus(repo, newnode, branch, bheads)
861 861
862 862 def nice(node):
863 863 return b'%d:%s' % (repo.changelog.rev(node), short(node))
864 864
865 865 ui.status(
866 866 _(b'changeset %s backs out changeset %s\n')
867 867 % (nice(repo.changelog.tip()), nice(node))
868 868 )
869 869 if opts.get(b'merge') and op1 != node:
870 870 hg.clean(repo, op1, show_stats=False)
871 871 ui.status(
872 872 _(b'merging with changeset %s\n') % nice(repo.changelog.tip())
873 873 )
874 874 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
875 875 with ui.configoverride(overrides, b'backout'):
876 876 return hg.merge(repo[b'tip'])
877 877 return 0
878 878
879 879
880 880 @command(
881 881 b'bisect',
882 882 [
883 883 (b'r', b'reset', False, _(b'reset bisect state')),
884 884 (b'g', b'good', False, _(b'mark changeset good')),
885 885 (b'b', b'bad', False, _(b'mark changeset bad')),
886 886 (b's', b'skip', False, _(b'skip testing changeset')),
887 887 (b'e', b'extend', False, _(b'extend the bisect range')),
888 888 (
889 889 b'c',
890 890 b'command',
891 891 b'',
892 892 _(b'use command to check changeset state'),
893 893 _(b'CMD'),
894 894 ),
895 895 (b'U', b'noupdate', False, _(b'do not update to target')),
896 896 ],
897 897 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
898 898 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
899 899 )
900 900 def bisect(
901 901 ui,
902 902 repo,
903 903 rev=None,
904 904 extra=None,
905 905 command=None,
906 906 reset=None,
907 907 good=None,
908 908 bad=None,
909 909 skip=None,
910 910 extend=None,
911 911 noupdate=None,
912 912 ):
913 913 """subdivision search of changesets
914 914
915 915 This command helps to find changesets which introduce problems. To
916 916 use, mark the earliest changeset you know exhibits the problem as
917 917 bad, then mark the latest changeset which is free from the problem
918 918 as good. Bisect will update your working directory to a revision
919 919 for testing (unless the -U/--noupdate option is specified). Once
920 920 you have performed tests, mark the working directory as good or
921 921 bad, and bisect will either update to another candidate changeset
922 922 or announce that it has found the bad revision.
923 923
924 924 As a shortcut, you can also use the revision argument to mark a
925 925 revision as good or bad without checking it out first.
926 926
927 927 If you supply a command, it will be used for automatic bisection.
928 928 The environment variable HG_NODE will contain the ID of the
929 929 changeset being tested. The exit status of the command will be
930 930 used to mark revisions as good or bad: status 0 means good, 125
931 931 means to skip the revision, 127 (command not found) will abort the
932 932 bisection, and any other non-zero exit status means the revision
933 933 is bad.
934 934
935 935 .. container:: verbose
936 936
937 937 Some examples:
938 938
939 939 - start a bisection with known bad revision 34, and good revision 12::
940 940
941 941 hg bisect --bad 34
942 942 hg bisect --good 12
943 943
944 944 - advance the current bisection by marking current revision as good or
945 945 bad::
946 946
947 947 hg bisect --good
948 948 hg bisect --bad
949 949
950 950 - mark the current revision, or a known revision, to be skipped (e.g. if
951 951 that revision is not usable because of another issue)::
952 952
953 953 hg bisect --skip
954 954 hg bisect --skip 23
955 955
956 956 - skip all revisions that do not touch directories ``foo`` or ``bar``::
957 957
958 958 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
959 959
960 960 - forget the current bisection::
961 961
962 962 hg bisect --reset
963 963
964 964 - use 'make && make tests' to automatically find the first broken
965 965 revision::
966 966
967 967 hg bisect --reset
968 968 hg bisect --bad 34
969 969 hg bisect --good 12
970 970 hg bisect --command "make && make tests"
971 971
972 972 - see all changesets whose states are already known in the current
973 973 bisection::
974 974
975 975 hg log -r "bisect(pruned)"
976 976
977 977 - see the changeset currently being bisected (especially useful
978 978 if running with -U/--noupdate)::
979 979
980 980 hg log -r "bisect(current)"
981 981
982 982 - see all changesets that took part in the current bisection::
983 983
984 984 hg log -r "bisect(range)"
985 985
986 986 - you can even get a nice graph::
987 987
988 988 hg log --graph -r "bisect(range)"
989 989
990 990 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
991 991
992 992 Returns 0 on success.
993 993 """
994 994 # backward compatibility
995 995 if rev in b"good bad reset init".split():
996 996 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
997 997 cmd, rev, extra = rev, extra, None
998 998 if cmd == b"good":
999 999 good = True
1000 1000 elif cmd == b"bad":
1001 1001 bad = True
1002 1002 else:
1003 1003 reset = True
1004 1004 elif extra:
1005 1005 raise error.Abort(_(b'incompatible arguments'))
1006 1006
1007 1007 incompatibles = {
1008 1008 b'--bad': bad,
1009 1009 b'--command': bool(command),
1010 1010 b'--extend': extend,
1011 1011 b'--good': good,
1012 1012 b'--reset': reset,
1013 1013 b'--skip': skip,
1014 1014 }
1015 1015
1016 1016 enabled = [x for x in incompatibles if incompatibles[x]]
1017 1017
1018 1018 if len(enabled) > 1:
1019 1019 raise error.Abort(
1020 1020 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1021 1021 )
1022 1022
1023 1023 if reset:
1024 1024 hbisect.resetstate(repo)
1025 1025 return
1026 1026
1027 1027 state = hbisect.load_state(repo)
1028 1028
1029 1029 # update state
1030 1030 if good or bad or skip:
1031 1031 if rev:
1032 1032 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
1033 1033 else:
1034 1034 nodes = [repo.lookup(b'.')]
1035 1035 if good:
1036 1036 state[b'good'] += nodes
1037 1037 elif bad:
1038 1038 state[b'bad'] += nodes
1039 1039 elif skip:
1040 1040 state[b'skip'] += nodes
1041 1041 hbisect.save_state(repo, state)
1042 1042 if not (state[b'good'] and state[b'bad']):
1043 1043 return
1044 1044
1045 1045 def mayupdate(repo, node, show_stats=True):
1046 1046 """common used update sequence"""
1047 1047 if noupdate:
1048 1048 return
1049 1049 cmdutil.checkunfinished(repo)
1050 1050 cmdutil.bailifchanged(repo)
1051 1051 return hg.clean(repo, node, show_stats=show_stats)
1052 1052
1053 1053 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1054 1054
1055 1055 if command:
1056 1056 changesets = 1
1057 1057 if noupdate:
1058 1058 try:
1059 1059 node = state[b'current'][0]
1060 1060 except LookupError:
1061 1061 raise error.Abort(
1062 1062 _(
1063 1063 b'current bisect revision is unknown - '
1064 1064 b'start a new bisect to fix'
1065 1065 )
1066 1066 )
1067 1067 else:
1068 1068 node, p2 = repo.dirstate.parents()
1069 1069 if p2 != nullid:
1070 1070 raise error.Abort(_(b'current bisect revision is a merge'))
1071 1071 if rev:
1072 1072 node = repo[scmutil.revsingle(repo, rev, node)].node()
1073 1073 with hbisect.restore_state(repo, state, node):
1074 1074 while changesets:
1075 1075 # update state
1076 1076 state[b'current'] = [node]
1077 1077 hbisect.save_state(repo, state)
1078 1078 status = ui.system(
1079 1079 command,
1080 1080 environ={b'HG_NODE': hex(node)},
1081 1081 blockedtag=b'bisect_check',
1082 1082 )
1083 1083 if status == 125:
1084 1084 transition = b"skip"
1085 1085 elif status == 0:
1086 1086 transition = b"good"
1087 1087 # status < 0 means process was killed
1088 1088 elif status == 127:
1089 1089 raise error.Abort(_(b"failed to execute %s") % command)
1090 1090 elif status < 0:
1091 1091 raise error.Abort(_(b"%s killed") % command)
1092 1092 else:
1093 1093 transition = b"bad"
1094 1094 state[transition].append(node)
1095 1095 ctx = repo[node]
1096 1096 ui.status(
1097 1097 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1098 1098 )
1099 1099 hbisect.checkstate(state)
1100 1100 # bisect
1101 1101 nodes, changesets, bgood = hbisect.bisect(repo, state)
1102 1102 # update to next check
1103 1103 node = nodes[0]
1104 1104 mayupdate(repo, node, show_stats=False)
1105 1105 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1106 1106 return
1107 1107
1108 1108 hbisect.checkstate(state)
1109 1109
1110 1110 # actually bisect
1111 1111 nodes, changesets, good = hbisect.bisect(repo, state)
1112 1112 if extend:
1113 1113 if not changesets:
1114 1114 extendnode = hbisect.extendrange(repo, state, nodes, good)
1115 1115 if extendnode is not None:
1116 1116 ui.write(
1117 1117 _(b"Extending search to changeset %d:%s\n")
1118 1118 % (extendnode.rev(), extendnode)
1119 1119 )
1120 1120 state[b'current'] = [extendnode.node()]
1121 1121 hbisect.save_state(repo, state)
1122 1122 return mayupdate(repo, extendnode.node())
1123 1123 raise error.Abort(_(b"nothing to extend"))
1124 1124
1125 1125 if changesets == 0:
1126 1126 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1127 1127 else:
1128 1128 assert len(nodes) == 1 # only a single node can be tested next
1129 1129 node = nodes[0]
1130 1130 # compute the approximate number of remaining tests
1131 1131 tests, size = 0, 2
1132 1132 while size <= changesets:
1133 1133 tests, size = tests + 1, size * 2
1134 1134 rev = repo.changelog.rev(node)
1135 1135 ui.write(
1136 1136 _(
1137 1137 b"Testing changeset %d:%s "
1138 1138 b"(%d changesets remaining, ~%d tests)\n"
1139 1139 )
1140 1140 % (rev, short(node), changesets, tests)
1141 1141 )
1142 1142 state[b'current'] = [node]
1143 1143 hbisect.save_state(repo, state)
1144 1144 return mayupdate(repo, node)
1145 1145
1146 1146
1147 1147 @command(
1148 1148 b'bookmarks|bookmark',
1149 1149 [
1150 1150 (b'f', b'force', False, _(b'force')),
1151 1151 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1152 1152 (b'd', b'delete', False, _(b'delete a given bookmark')),
1153 1153 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1154 1154 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1155 1155 (b'l', b'list', False, _(b'list existing bookmarks')),
1156 1156 ]
1157 1157 + formatteropts,
1158 1158 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1159 1159 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1160 1160 )
1161 1161 def bookmark(ui, repo, *names, **opts):
1162 1162 '''create a new bookmark or list existing bookmarks
1163 1163
1164 1164 Bookmarks are labels on changesets to help track lines of development.
1165 1165 Bookmarks are unversioned and can be moved, renamed and deleted.
1166 1166 Deleting or moving a bookmark has no effect on the associated changesets.
1167 1167
1168 1168 Creating or updating to a bookmark causes it to be marked as 'active'.
1169 1169 The active bookmark is indicated with a '*'.
1170 1170 When a commit is made, the active bookmark will advance to the new commit.
1171 1171 A plain :hg:`update` will also advance an active bookmark, if possible.
1172 1172 Updating away from a bookmark will cause it to be deactivated.
1173 1173
1174 1174 Bookmarks can be pushed and pulled between repositories (see
1175 1175 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1176 1176 diverged, a new 'divergent bookmark' of the form 'name@path' will
1177 1177 be created. Using :hg:`merge` will resolve the divergence.
1178 1178
1179 1179 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1180 1180 the active bookmark's name.
1181 1181
1182 1182 A bookmark named '@' has the special property that :hg:`clone` will
1183 1183 check it out by default if it exists.
1184 1184
1185 1185 .. container:: verbose
1186 1186
1187 1187 Template:
1188 1188
1189 1189 The following keywords are supported in addition to the common template
1190 1190 keywords and functions such as ``{bookmark}``. See also
1191 1191 :hg:`help templates`.
1192 1192
1193 1193 :active: Boolean. True if the bookmark is active.
1194 1194
1195 1195 Examples:
1196 1196
1197 1197 - create an active bookmark for a new line of development::
1198 1198
1199 1199 hg book new-feature
1200 1200
1201 1201 - create an inactive bookmark as a place marker::
1202 1202
1203 1203 hg book -i reviewed
1204 1204
1205 1205 - create an inactive bookmark on another changeset::
1206 1206
1207 1207 hg book -r .^ tested
1208 1208
1209 1209 - rename bookmark turkey to dinner::
1210 1210
1211 1211 hg book -m turkey dinner
1212 1212
1213 1213 - move the '@' bookmark from another branch::
1214 1214
1215 1215 hg book -f @
1216 1216
1217 1217 - print only the active bookmark name::
1218 1218
1219 1219 hg book -ql .
1220 1220 '''
1221 1221 opts = pycompat.byteskwargs(opts)
1222 1222 force = opts.get(b'force')
1223 1223 rev = opts.get(b'rev')
1224 1224 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1225 1225
1226 1226 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1227 1227 if action:
1228 1228 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1229 1229 elif names or rev:
1230 1230 action = b'add'
1231 1231 elif inactive:
1232 1232 action = b'inactive' # meaning deactivate
1233 1233 else:
1234 1234 action = b'list'
1235 1235
1236 1236 cmdutil.check_incompatible_arguments(
1237 1237 opts, b'inactive', [b'delete', b'list']
1238 1238 )
1239 1239 if not names and action in {b'add', b'delete'}:
1240 1240 raise error.Abort(_(b"bookmark name required"))
1241 1241
1242 1242 if action in {b'add', b'delete', b'rename', b'inactive'}:
1243 1243 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1244 1244 if action == b'delete':
1245 1245 names = pycompat.maplist(repo._bookmarks.expandname, names)
1246 1246 bookmarks.delete(repo, tr, names)
1247 1247 elif action == b'rename':
1248 1248 if not names:
1249 1249 raise error.Abort(_(b"new bookmark name required"))
1250 1250 elif len(names) > 1:
1251 1251 raise error.Abort(_(b"only one new bookmark name allowed"))
1252 1252 oldname = repo._bookmarks.expandname(opts[b'rename'])
1253 1253 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1254 1254 elif action == b'add':
1255 1255 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1256 1256 elif action == b'inactive':
1257 1257 if len(repo._bookmarks) == 0:
1258 1258 ui.status(_(b"no bookmarks set\n"))
1259 1259 elif not repo._activebookmark:
1260 1260 ui.status(_(b"no active bookmark\n"))
1261 1261 else:
1262 1262 bookmarks.deactivate(repo)
1263 1263 elif action == b'list':
1264 1264 names = pycompat.maplist(repo._bookmarks.expandname, names)
1265 1265 with ui.formatter(b'bookmarks', opts) as fm:
1266 1266 bookmarks.printbookmarks(ui, repo, fm, names)
1267 1267 else:
1268 1268 raise error.ProgrammingError(b'invalid action: %s' % action)
1269 1269
1270 1270
1271 1271 @command(
1272 1272 b'branch',
1273 1273 [
1274 1274 (
1275 1275 b'f',
1276 1276 b'force',
1277 1277 None,
1278 1278 _(b'set branch name even if it shadows an existing branch'),
1279 1279 ),
1280 1280 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1281 1281 (
1282 1282 b'r',
1283 1283 b'rev',
1284 1284 [],
1285 1285 _(b'change branches of the given revs (EXPERIMENTAL)'),
1286 1286 ),
1287 1287 ],
1288 1288 _(b'[-fC] [NAME]'),
1289 1289 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1290 1290 )
1291 1291 def branch(ui, repo, label=None, **opts):
1292 1292 """set or show the current branch name
1293 1293
1294 1294 .. note::
1295 1295
1296 1296 Branch names are permanent and global. Use :hg:`bookmark` to create a
1297 1297 light-weight bookmark instead. See :hg:`help glossary` for more
1298 1298 information about named branches and bookmarks.
1299 1299
1300 1300 With no argument, show the current branch name. With one argument,
1301 1301 set the working directory branch name (the branch will not exist
1302 1302 in the repository until the next commit). Standard practice
1303 1303 recommends that primary development take place on the 'default'
1304 1304 branch.
1305 1305
1306 1306 Unless -f/--force is specified, branch will not let you set a
1307 1307 branch name that already exists.
1308 1308
1309 1309 Use -C/--clean to reset the working directory branch to that of
1310 1310 the parent of the working directory, negating a previous branch
1311 1311 change.
1312 1312
1313 1313 Use the command :hg:`update` to switch to an existing branch. Use
1314 1314 :hg:`commit --close-branch` to mark this branch head as closed.
1315 1315 When all heads of a branch are closed, the branch will be
1316 1316 considered closed.
1317 1317
1318 1318 Returns 0 on success.
1319 1319 """
1320 1320 opts = pycompat.byteskwargs(opts)
1321 1321 revs = opts.get(b'rev')
1322 1322 if label:
1323 1323 label = label.strip()
1324 1324
1325 1325 if not opts.get(b'clean') and not label:
1326 1326 if revs:
1327 1327 raise error.Abort(_(b"no branch name specified for the revisions"))
1328 1328 ui.write(b"%s\n" % repo.dirstate.branch())
1329 1329 return
1330 1330
1331 1331 with repo.wlock():
1332 1332 if opts.get(b'clean'):
1333 1333 label = repo[b'.'].branch()
1334 1334 repo.dirstate.setbranch(label)
1335 1335 ui.status(_(b'reset working directory to branch %s\n') % label)
1336 1336 elif label:
1337 1337
1338 1338 scmutil.checknewlabel(repo, label, b'branch')
1339 1339 if revs:
1340 1340 return cmdutil.changebranch(ui, repo, revs, label, opts)
1341 1341
1342 1342 if not opts.get(b'force') and label in repo.branchmap():
1343 1343 if label not in [p.branch() for p in repo[None].parents()]:
1344 1344 raise error.Abort(
1345 1345 _(b'a branch of the same name already exists'),
1346 1346 # i18n: "it" refers to an existing branch
1347 1347 hint=_(b"use 'hg update' to switch to it"),
1348 1348 )
1349 1349
1350 1350 repo.dirstate.setbranch(label)
1351 1351 ui.status(_(b'marked working directory as branch %s\n') % label)
1352 1352
1353 1353 # find any open named branches aside from default
1354 1354 for n, h, t, c in repo.branchmap().iterbranches():
1355 1355 if n != b"default" and not c:
1356 1356 return 0
1357 1357 ui.status(
1358 1358 _(
1359 1359 b'(branches are permanent and global, '
1360 1360 b'did you want a bookmark?)\n'
1361 1361 )
1362 1362 )
1363 1363
1364 1364
1365 1365 @command(
1366 1366 b'branches',
1367 1367 [
1368 1368 (
1369 1369 b'a',
1370 1370 b'active',
1371 1371 False,
1372 1372 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1373 1373 ),
1374 1374 (b'c', b'closed', False, _(b'show normal and closed branches')),
1375 1375 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1376 1376 ]
1377 1377 + formatteropts,
1378 1378 _(b'[-c]'),
1379 1379 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1380 1380 intents={INTENT_READONLY},
1381 1381 )
1382 1382 def branches(ui, repo, active=False, closed=False, **opts):
1383 1383 """list repository named branches
1384 1384
1385 1385 List the repository's named branches, indicating which ones are
1386 1386 inactive. If -c/--closed is specified, also list branches which have
1387 1387 been marked closed (see :hg:`commit --close-branch`).
1388 1388
1389 1389 Use the command :hg:`update` to switch to an existing branch.
1390 1390
1391 1391 .. container:: verbose
1392 1392
1393 1393 Template:
1394 1394
1395 1395 The following keywords are supported in addition to the common template
1396 1396 keywords and functions such as ``{branch}``. See also
1397 1397 :hg:`help templates`.
1398 1398
1399 1399 :active: Boolean. True if the branch is active.
1400 1400 :closed: Boolean. True if the branch is closed.
1401 1401 :current: Boolean. True if it is the current branch.
1402 1402
1403 1403 Returns 0.
1404 1404 """
1405 1405
1406 1406 opts = pycompat.byteskwargs(opts)
1407 1407 revs = opts.get(b'rev')
1408 1408 selectedbranches = None
1409 1409 if revs:
1410 1410 revs = scmutil.revrange(repo, revs)
1411 1411 getbi = repo.revbranchcache().branchinfo
1412 1412 selectedbranches = {getbi(r)[0] for r in revs}
1413 1413
1414 1414 ui.pager(b'branches')
1415 1415 fm = ui.formatter(b'branches', opts)
1416 1416 hexfunc = fm.hexfunc
1417 1417
1418 1418 allheads = set(repo.heads())
1419 1419 branches = []
1420 1420 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1421 1421 if selectedbranches is not None and tag not in selectedbranches:
1422 1422 continue
1423 1423 isactive = False
1424 1424 if not isclosed:
1425 1425 openheads = set(repo.branchmap().iteropen(heads))
1426 1426 isactive = bool(openheads & allheads)
1427 1427 branches.append((tag, repo[tip], isactive, not isclosed))
1428 1428 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1429 1429
1430 1430 for tag, ctx, isactive, isopen in branches:
1431 1431 if active and not isactive:
1432 1432 continue
1433 1433 if isactive:
1434 1434 label = b'branches.active'
1435 1435 notice = b''
1436 1436 elif not isopen:
1437 1437 if not closed:
1438 1438 continue
1439 1439 label = b'branches.closed'
1440 1440 notice = _(b' (closed)')
1441 1441 else:
1442 1442 label = b'branches.inactive'
1443 1443 notice = _(b' (inactive)')
1444 1444 current = tag == repo.dirstate.branch()
1445 1445 if current:
1446 1446 label = b'branches.current'
1447 1447
1448 1448 fm.startitem()
1449 1449 fm.write(b'branch', b'%s', tag, label=label)
1450 1450 rev = ctx.rev()
1451 1451 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1452 1452 fmt = b' ' * padsize + b' %d:%s'
1453 1453 fm.condwrite(
1454 1454 not ui.quiet,
1455 1455 b'rev node',
1456 1456 fmt,
1457 1457 rev,
1458 1458 hexfunc(ctx.node()),
1459 1459 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1460 1460 )
1461 1461 fm.context(ctx=ctx)
1462 1462 fm.data(active=isactive, closed=not isopen, current=current)
1463 1463 if not ui.quiet:
1464 1464 fm.plain(notice)
1465 1465 fm.plain(b'\n')
1466 1466 fm.end()
1467 1467
1468 1468
1469 1469 @command(
1470 1470 b'bundle',
1471 1471 [
1472 1472 (
1473 1473 b'f',
1474 1474 b'force',
1475 1475 None,
1476 1476 _(b'run even when the destination is unrelated'),
1477 1477 ),
1478 1478 (
1479 1479 b'r',
1480 1480 b'rev',
1481 1481 [],
1482 1482 _(b'a changeset intended to be added to the destination'),
1483 1483 _(b'REV'),
1484 1484 ),
1485 1485 (
1486 1486 b'b',
1487 1487 b'branch',
1488 1488 [],
1489 1489 _(b'a specific branch you would like to bundle'),
1490 1490 _(b'BRANCH'),
1491 1491 ),
1492 1492 (
1493 1493 b'',
1494 1494 b'base',
1495 1495 [],
1496 1496 _(b'a base changeset assumed to be available at the destination'),
1497 1497 _(b'REV'),
1498 1498 ),
1499 1499 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1500 1500 (
1501 1501 b't',
1502 1502 b'type',
1503 1503 b'bzip2',
1504 1504 _(b'bundle compression type to use'),
1505 1505 _(b'TYPE'),
1506 1506 ),
1507 1507 ]
1508 1508 + remoteopts,
1509 1509 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1510 1510 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1511 1511 )
1512 1512 def bundle(ui, repo, fname, dest=None, **opts):
1513 1513 """create a bundle file
1514 1514
1515 1515 Generate a bundle file containing data to be transferred to another
1516 1516 repository.
1517 1517
1518 1518 To create a bundle containing all changesets, use -a/--all
1519 1519 (or --base null). Otherwise, hg assumes the destination will have
1520 1520 all the nodes you specify with --base parameters. Otherwise, hg
1521 1521 will assume the repository has all the nodes in destination, or
1522 1522 default-push/default if no destination is specified, where destination
1523 1523 is the repository you provide through DEST option.
1524 1524
1525 1525 You can change bundle format with the -t/--type option. See
1526 1526 :hg:`help bundlespec` for documentation on this format. By default,
1527 1527 the most appropriate format is used and compression defaults to
1528 1528 bzip2.
1529 1529
1530 1530 The bundle file can then be transferred using conventional means
1531 1531 and applied to another repository with the unbundle or pull
1532 1532 command. This is useful when direct push and pull are not
1533 1533 available or when exporting an entire repository is undesirable.
1534 1534
1535 1535 Applying bundles preserves all changeset contents including
1536 1536 permissions, copy/rename information, and revision history.
1537 1537
1538 1538 Returns 0 on success, 1 if no changes found.
1539 1539 """
1540 1540 opts = pycompat.byteskwargs(opts)
1541 1541 revs = None
1542 1542 if b'rev' in opts:
1543 1543 revstrings = opts[b'rev']
1544 1544 revs = scmutil.revrange(repo, revstrings)
1545 1545 if revstrings and not revs:
1546 1546 raise error.Abort(_(b'no commits to bundle'))
1547 1547
1548 1548 bundletype = opts.get(b'type', b'bzip2').lower()
1549 1549 try:
1550 1550 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1551 1551 except error.UnsupportedBundleSpecification as e:
1552 1552 raise error.Abort(
1553 1553 pycompat.bytestr(e),
1554 1554 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1555 1555 )
1556 1556 cgversion = bundlespec.contentopts[b"cg.version"]
1557 1557
1558 1558 # Packed bundles are a pseudo bundle format for now.
1559 1559 if cgversion == b's1':
1560 1560 raise error.Abort(
1561 1561 _(b'packed bundles cannot be produced by "hg bundle"'),
1562 1562 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1563 1563 )
1564 1564
1565 1565 if opts.get(b'all'):
1566 1566 if dest:
1567 1567 raise error.Abort(
1568 1568 _(b"--all is incompatible with specifying a destination")
1569 1569 )
1570 1570 if opts.get(b'base'):
1571 1571 ui.warn(_(b"ignoring --base because --all was specified\n"))
1572 1572 base = [nullrev]
1573 1573 else:
1574 1574 base = scmutil.revrange(repo, opts.get(b'base'))
1575 1575 if cgversion not in changegroup.supportedoutgoingversions(repo):
1576 1576 raise error.Abort(
1577 1577 _(b"repository does not support bundle version %s") % cgversion
1578 1578 )
1579 1579
1580 1580 if base:
1581 1581 if dest:
1582 1582 raise error.Abort(
1583 1583 _(b"--base is incompatible with specifying a destination")
1584 1584 )
1585 1585 common = [repo[rev].node() for rev in base]
1586 1586 heads = [repo[r].node() for r in revs] if revs else None
1587 1587 outgoing = discovery.outgoing(repo, common, heads)
1588 1588 else:
1589 1589 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1590 1590 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1591 1591 other = hg.peer(repo, opts, dest)
1592 1592 revs = [repo[r].hex() for r in revs]
1593 1593 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1594 1594 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1595 1595 outgoing = discovery.findcommonoutgoing(
1596 1596 repo,
1597 1597 other,
1598 1598 onlyheads=heads,
1599 1599 force=opts.get(b'force'),
1600 1600 portable=True,
1601 1601 )
1602 1602
1603 1603 if not outgoing.missing:
1604 1604 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1605 1605 return 1
1606 1606
1607 1607 if cgversion == b'01': # bundle1
1608 1608 bversion = b'HG10' + bundlespec.wirecompression
1609 1609 bcompression = None
1610 1610 elif cgversion in (b'02', b'03'):
1611 1611 bversion = b'HG20'
1612 1612 bcompression = bundlespec.wirecompression
1613 1613 else:
1614 1614 raise error.ProgrammingError(
1615 1615 b'bundle: unexpected changegroup version %s' % cgversion
1616 1616 )
1617 1617
1618 1618 # TODO compression options should be derived from bundlespec parsing.
1619 1619 # This is a temporary hack to allow adjusting bundle compression
1620 1620 # level without a) formalizing the bundlespec changes to declare it
1621 1621 # b) introducing a command flag.
1622 1622 compopts = {}
1623 1623 complevel = ui.configint(
1624 1624 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1625 1625 )
1626 1626 if complevel is None:
1627 1627 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1628 1628 if complevel is not None:
1629 1629 compopts[b'level'] = complevel
1630 1630
1631 1631 # Allow overriding the bundling of obsmarker in phases through
1632 1632 # configuration while we don't have a bundle version that include them
1633 1633 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1634 1634 bundlespec.contentopts[b'obsolescence'] = True
1635 1635 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1636 1636 bundlespec.contentopts[b'phases'] = True
1637 1637
1638 1638 bundle2.writenewbundle(
1639 1639 ui,
1640 1640 repo,
1641 1641 b'bundle',
1642 1642 fname,
1643 1643 bversion,
1644 1644 outgoing,
1645 1645 bundlespec.contentopts,
1646 1646 compression=bcompression,
1647 1647 compopts=compopts,
1648 1648 )
1649 1649
1650 1650
1651 1651 @command(
1652 1652 b'cat',
1653 1653 [
1654 1654 (
1655 1655 b'o',
1656 1656 b'output',
1657 1657 b'',
1658 1658 _(b'print output to file with formatted name'),
1659 1659 _(b'FORMAT'),
1660 1660 ),
1661 1661 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1662 1662 (b'', b'decode', None, _(b'apply any matching decode filter')),
1663 1663 ]
1664 1664 + walkopts
1665 1665 + formatteropts,
1666 1666 _(b'[OPTION]... FILE...'),
1667 1667 helpcategory=command.CATEGORY_FILE_CONTENTS,
1668 1668 inferrepo=True,
1669 1669 intents={INTENT_READONLY},
1670 1670 )
1671 1671 def cat(ui, repo, file1, *pats, **opts):
1672 1672 """output the current or given revision of files
1673 1673
1674 1674 Print the specified files as they were at the given revision. If
1675 1675 no revision is given, the parent of the working directory is used.
1676 1676
1677 1677 Output may be to a file, in which case the name of the file is
1678 1678 given using a template string. See :hg:`help templates`. In addition
1679 1679 to the common template keywords, the following formatting rules are
1680 1680 supported:
1681 1681
1682 1682 :``%%``: literal "%" character
1683 1683 :``%s``: basename of file being printed
1684 1684 :``%d``: dirname of file being printed, or '.' if in repository root
1685 1685 :``%p``: root-relative path name of file being printed
1686 1686 :``%H``: changeset hash (40 hexadecimal digits)
1687 1687 :``%R``: changeset revision number
1688 1688 :``%h``: short-form changeset hash (12 hexadecimal digits)
1689 1689 :``%r``: zero-padded changeset revision number
1690 1690 :``%b``: basename of the exporting repository
1691 1691 :``\\``: literal "\\" character
1692 1692
1693 1693 .. container:: verbose
1694 1694
1695 1695 Template:
1696 1696
1697 1697 The following keywords are supported in addition to the common template
1698 1698 keywords and functions. See also :hg:`help templates`.
1699 1699
1700 1700 :data: String. File content.
1701 1701 :path: String. Repository-absolute path of the file.
1702 1702
1703 1703 Returns 0 on success.
1704 1704 """
1705 1705 opts = pycompat.byteskwargs(opts)
1706 1706 rev = opts.get(b'rev')
1707 1707 if rev:
1708 1708 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1709 1709 ctx = scmutil.revsingle(repo, rev)
1710 1710 m = scmutil.match(ctx, (file1,) + pats, opts)
1711 1711 fntemplate = opts.pop(b'output', b'')
1712 1712 if cmdutil.isstdiofilename(fntemplate):
1713 1713 fntemplate = b''
1714 1714
1715 1715 if fntemplate:
1716 1716 fm = formatter.nullformatter(ui, b'cat', opts)
1717 1717 else:
1718 1718 ui.pager(b'cat')
1719 1719 fm = ui.formatter(b'cat', opts)
1720 1720 with fm:
1721 1721 return cmdutil.cat(
1722 1722 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1723 1723 )
1724 1724
1725 1725
1726 1726 @command(
1727 1727 b'clone',
1728 1728 [
1729 1729 (
1730 1730 b'U',
1731 1731 b'noupdate',
1732 1732 None,
1733 1733 _(
1734 1734 b'the clone will include an empty working '
1735 1735 b'directory (only a repository)'
1736 1736 ),
1737 1737 ),
1738 1738 (
1739 1739 b'u',
1740 1740 b'updaterev',
1741 1741 b'',
1742 1742 _(b'revision, tag, or branch to check out'),
1743 1743 _(b'REV'),
1744 1744 ),
1745 1745 (
1746 1746 b'r',
1747 1747 b'rev',
1748 1748 [],
1749 1749 _(
1750 1750 b'do not clone everything, but include this changeset'
1751 1751 b' and its ancestors'
1752 1752 ),
1753 1753 _(b'REV'),
1754 1754 ),
1755 1755 (
1756 1756 b'b',
1757 1757 b'branch',
1758 1758 [],
1759 1759 _(
1760 1760 b'do not clone everything, but include this branch\'s'
1761 1761 b' changesets and their ancestors'
1762 1762 ),
1763 1763 _(b'BRANCH'),
1764 1764 ),
1765 1765 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1766 1766 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1767 1767 (b'', b'stream', None, _(b'clone with minimal data processing')),
1768 1768 ]
1769 1769 + remoteopts,
1770 1770 _(b'[OPTION]... SOURCE [DEST]'),
1771 1771 helpcategory=command.CATEGORY_REPO_CREATION,
1772 1772 helpbasic=True,
1773 1773 norepo=True,
1774 1774 )
1775 1775 def clone(ui, source, dest=None, **opts):
1776 1776 """make a copy of an existing repository
1777 1777
1778 1778 Create a copy of an existing repository in a new directory.
1779 1779
1780 1780 If no destination directory name is specified, it defaults to the
1781 1781 basename of the source.
1782 1782
1783 1783 The location of the source is added to the new repository's
1784 1784 ``.hg/hgrc`` file, as the default to be used for future pulls.
1785 1785
1786 1786 Only local paths and ``ssh://`` URLs are supported as
1787 1787 destinations. For ``ssh://`` destinations, no working directory or
1788 1788 ``.hg/hgrc`` will be created on the remote side.
1789 1789
1790 1790 If the source repository has a bookmark called '@' set, that
1791 1791 revision will be checked out in the new repository by default.
1792 1792
1793 1793 To check out a particular version, use -u/--update, or
1794 1794 -U/--noupdate to create a clone with no working directory.
1795 1795
1796 1796 To pull only a subset of changesets, specify one or more revisions
1797 1797 identifiers with -r/--rev or branches with -b/--branch. The
1798 1798 resulting clone will contain only the specified changesets and
1799 1799 their ancestors. These options (or 'clone src#rev dest') imply
1800 1800 --pull, even for local source repositories.
1801 1801
1802 1802 In normal clone mode, the remote normalizes repository data into a common
1803 1803 exchange format and the receiving end translates this data into its local
1804 1804 storage format. --stream activates a different clone mode that essentially
1805 1805 copies repository files from the remote with minimal data processing. This
1806 1806 significantly reduces the CPU cost of a clone both remotely and locally.
1807 1807 However, it often increases the transferred data size by 30-40%. This can
1808 1808 result in substantially faster clones where I/O throughput is plentiful,
1809 1809 especially for larger repositories. A side-effect of --stream clones is
1810 1810 that storage settings and requirements on the remote are applied locally:
1811 1811 a modern client may inherit legacy or inefficient storage used by the
1812 1812 remote or a legacy Mercurial client may not be able to clone from a
1813 1813 modern Mercurial remote.
1814 1814
1815 1815 .. note::
1816 1816
1817 1817 Specifying a tag will include the tagged changeset but not the
1818 1818 changeset containing the tag.
1819 1819
1820 1820 .. container:: verbose
1821 1821
1822 1822 For efficiency, hardlinks are used for cloning whenever the
1823 1823 source and destination are on the same filesystem (note this
1824 1824 applies only to the repository data, not to the working
1825 1825 directory). Some filesystems, such as AFS, implement hardlinking
1826 1826 incorrectly, but do not report errors. In these cases, use the
1827 1827 --pull option to avoid hardlinking.
1828 1828
1829 1829 Mercurial will update the working directory to the first applicable
1830 1830 revision from this list:
1831 1831
1832 1832 a) null if -U or the source repository has no changesets
1833 1833 b) if -u . and the source repository is local, the first parent of
1834 1834 the source repository's working directory
1835 1835 c) the changeset specified with -u (if a branch name, this means the
1836 1836 latest head of that branch)
1837 1837 d) the changeset specified with -r
1838 1838 e) the tipmost head specified with -b
1839 1839 f) the tipmost head specified with the url#branch source syntax
1840 1840 g) the revision marked with the '@' bookmark, if present
1841 1841 h) the tipmost head of the default branch
1842 1842 i) tip
1843 1843
1844 1844 When cloning from servers that support it, Mercurial may fetch
1845 1845 pre-generated data from a server-advertised URL or inline from the
1846 1846 same stream. When this is done, hooks operating on incoming changesets
1847 1847 and changegroups may fire more than once, once for each pre-generated
1848 1848 bundle and as well as for any additional remaining data. In addition,
1849 1849 if an error occurs, the repository may be rolled back to a partial
1850 1850 clone. This behavior may change in future releases.
1851 1851 See :hg:`help -e clonebundles` for more.
1852 1852
1853 1853 Examples:
1854 1854
1855 1855 - clone a remote repository to a new directory named hg/::
1856 1856
1857 1857 hg clone https://www.mercurial-scm.org/repo/hg/
1858 1858
1859 1859 - create a lightweight local clone::
1860 1860
1861 1861 hg clone project/ project-feature/
1862 1862
1863 1863 - clone from an absolute path on an ssh server (note double-slash)::
1864 1864
1865 1865 hg clone ssh://user@server//home/projects/alpha/
1866 1866
1867 1867 - do a streaming clone while checking out a specified version::
1868 1868
1869 1869 hg clone --stream http://server/repo -u 1.5
1870 1870
1871 1871 - create a repository without changesets after a particular revision::
1872 1872
1873 1873 hg clone -r 04e544 experimental/ good/
1874 1874
1875 1875 - clone (and track) a particular named branch::
1876 1876
1877 1877 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1878 1878
1879 1879 See :hg:`help urls` for details on specifying URLs.
1880 1880
1881 1881 Returns 0 on success.
1882 1882 """
1883 1883 opts = pycompat.byteskwargs(opts)
1884 1884 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1885 1885
1886 1886 # --include/--exclude can come from narrow or sparse.
1887 1887 includepats, excludepats = None, None
1888 1888
1889 1889 # hg.clone() differentiates between None and an empty set. So make sure
1890 1890 # patterns are sets if narrow is requested without patterns.
1891 1891 if opts.get(b'narrow'):
1892 1892 includepats = set()
1893 1893 excludepats = set()
1894 1894
1895 1895 if opts.get(b'include'):
1896 1896 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1897 1897 if opts.get(b'exclude'):
1898 1898 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1899 1899
1900 1900 r = hg.clone(
1901 1901 ui,
1902 1902 opts,
1903 1903 source,
1904 1904 dest,
1905 1905 pull=opts.get(b'pull'),
1906 1906 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1907 1907 revs=opts.get(b'rev'),
1908 1908 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1909 1909 branch=opts.get(b'branch'),
1910 1910 shareopts=opts.get(b'shareopts'),
1911 1911 storeincludepats=includepats,
1912 1912 storeexcludepats=excludepats,
1913 1913 depth=opts.get(b'depth') or None,
1914 1914 )
1915 1915
1916 1916 return r is None
1917 1917
1918 1918
1919 1919 @command(
1920 1920 b'commit|ci',
1921 1921 [
1922 1922 (
1923 1923 b'A',
1924 1924 b'addremove',
1925 1925 None,
1926 1926 _(b'mark new/missing files as added/removed before committing'),
1927 1927 ),
1928 1928 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1929 1929 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1930 1930 (b's', b'secret', None, _(b'use the secret phase for committing')),
1931 1931 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1932 1932 (
1933 1933 b'',
1934 1934 b'force-close-branch',
1935 1935 None,
1936 1936 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1937 1937 ),
1938 1938 (b'i', b'interactive', None, _(b'use interactive mode')),
1939 1939 ]
1940 1940 + walkopts
1941 1941 + commitopts
1942 1942 + commitopts2
1943 1943 + subrepoopts,
1944 1944 _(b'[OPTION]... [FILE]...'),
1945 1945 helpcategory=command.CATEGORY_COMMITTING,
1946 1946 helpbasic=True,
1947 1947 inferrepo=True,
1948 1948 )
1949 1949 def commit(ui, repo, *pats, **opts):
1950 1950 """commit the specified files or all outstanding changes
1951 1951
1952 1952 Commit changes to the given files into the repository. Unlike a
1953 1953 centralized SCM, this operation is a local operation. See
1954 1954 :hg:`push` for a way to actively distribute your changes.
1955 1955
1956 1956 If a list of files is omitted, all changes reported by :hg:`status`
1957 1957 will be committed.
1958 1958
1959 1959 If you are committing the result of a merge, do not provide any
1960 1960 filenames or -I/-X filters.
1961 1961
1962 1962 If no commit message is specified, Mercurial starts your
1963 1963 configured editor where you can enter a message. In case your
1964 1964 commit fails, you will find a backup of your message in
1965 1965 ``.hg/last-message.txt``.
1966 1966
1967 1967 The --close-branch flag can be used to mark the current branch
1968 1968 head closed. When all heads of a branch are closed, the branch
1969 1969 will be considered closed and no longer listed.
1970 1970
1971 1971 The --amend flag can be used to amend the parent of the
1972 1972 working directory with a new commit that contains the changes
1973 1973 in the parent in addition to those currently reported by :hg:`status`,
1974 1974 if there are any. The old commit is stored in a backup bundle in
1975 1975 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1976 1976 on how to restore it).
1977 1977
1978 1978 Message, user and date are taken from the amended commit unless
1979 1979 specified. When a message isn't specified on the command line,
1980 1980 the editor will open with the message of the amended commit.
1981 1981
1982 1982 It is not possible to amend public changesets (see :hg:`help phases`)
1983 1983 or changesets that have children.
1984 1984
1985 1985 See :hg:`help dates` for a list of formats valid for -d/--date.
1986 1986
1987 1987 Returns 0 on success, 1 if nothing changed.
1988 1988
1989 1989 .. container:: verbose
1990 1990
1991 1991 Examples:
1992 1992
1993 1993 - commit all files ending in .py::
1994 1994
1995 1995 hg commit --include "set:**.py"
1996 1996
1997 1997 - commit all non-binary files::
1998 1998
1999 1999 hg commit --exclude "set:binary()"
2000 2000
2001 2001 - amend the current commit and set the date to now::
2002 2002
2003 2003 hg commit --amend --date now
2004 2004 """
2005 2005 with repo.wlock(), repo.lock():
2006 2006 return _docommit(ui, repo, *pats, **opts)
2007 2007
2008 2008
2009 2009 def _docommit(ui, repo, *pats, **opts):
2010 2010 if opts.get('interactive'):
2011 2011 opts.pop('interactive')
2012 2012 ret = cmdutil.dorecord(
2013 2013 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2014 2014 )
2015 2015 # ret can be 0 (no changes to record) or the value returned by
2016 2016 # commit(), 1 if nothing changed or None on success.
2017 2017 return 1 if ret == 0 else ret
2018 2018
2019 2019 opts = pycompat.byteskwargs(opts)
2020 2020 if opts.get(b'subrepos'):
2021 2021 if opts.get(b'amend'):
2022 2022 raise error.Abort(_(b'cannot amend with --subrepos'))
2023 2023 # Let --subrepos on the command line override config setting.
2024 2024 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2025 2025
2026 2026 cmdutil.checkunfinished(repo, commit=True)
2027 2027
2028 2028 branch = repo[None].branch()
2029 2029 bheads = repo.branchheads(branch)
2030 2030
2031 2031 extra = {}
2032 2032 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2033 2033 extra[b'close'] = b'1'
2034 2034
2035 2035 if repo[b'.'].closesbranch():
2036 2036 raise error.Abort(
2037 2037 _(b'current revision is already a branch closing head')
2038 2038 )
2039 2039 elif not bheads:
2040 2040 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2041 2041 elif (
2042 2042 branch == repo[b'.'].branch()
2043 2043 and repo[b'.'].node() not in bheads
2044 2044 and not opts.get(b'force_close_branch')
2045 2045 ):
2046 2046 hint = _(
2047 2047 b'use --force-close-branch to close branch from a non-head'
2048 2048 b' changeset'
2049 2049 )
2050 2050 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2051 2051 elif opts.get(b'amend'):
2052 2052 if (
2053 2053 repo[b'.'].p1().branch() != branch
2054 2054 and repo[b'.'].p2().branch() != branch
2055 2055 ):
2056 2056 raise error.Abort(_(b'can only close branch heads'))
2057 2057
2058 2058 if opts.get(b'amend'):
2059 2059 if ui.configbool(b'ui', b'commitsubrepos'):
2060 2060 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2061 2061
2062 2062 old = repo[b'.']
2063 2063 rewriteutil.precheck(repo, [old.rev()], b'amend')
2064 2064
2065 2065 # Currently histedit gets confused if an amend happens while histedit
2066 2066 # is in progress. Since we have a checkunfinished command, we are
2067 2067 # temporarily honoring it.
2068 2068 #
2069 2069 # Note: eventually this guard will be removed. Please do not expect
2070 2070 # this behavior to remain.
2071 2071 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2072 2072 cmdutil.checkunfinished(repo)
2073 2073
2074 2074 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2075 2075 if node == old.node():
2076 2076 ui.status(_(b"nothing changed\n"))
2077 2077 return 1
2078 2078 else:
2079 2079
2080 2080 def commitfunc(ui, repo, message, match, opts):
2081 2081 overrides = {}
2082 2082 if opts.get(b'secret'):
2083 2083 overrides[(b'phases', b'new-commit')] = b'secret'
2084 2084
2085 2085 baseui = repo.baseui
2086 2086 with baseui.configoverride(overrides, b'commit'):
2087 2087 with ui.configoverride(overrides, b'commit'):
2088 2088 editform = cmdutil.mergeeditform(
2089 2089 repo[None], b'commit.normal'
2090 2090 )
2091 2091 editor = cmdutil.getcommiteditor(
2092 2092 editform=editform, **pycompat.strkwargs(opts)
2093 2093 )
2094 2094 return repo.commit(
2095 2095 message,
2096 2096 opts.get(b'user'),
2097 2097 opts.get(b'date'),
2098 2098 match,
2099 2099 editor=editor,
2100 2100 extra=extra,
2101 2101 )
2102 2102
2103 2103 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2104 2104
2105 2105 if not node:
2106 2106 stat = cmdutil.postcommitstatus(repo, pats, opts)
2107 2107 if stat.deleted:
2108 2108 ui.status(
2109 2109 _(
2110 2110 b"nothing changed (%d missing files, see "
2111 2111 b"'hg status')\n"
2112 2112 )
2113 2113 % len(stat.deleted)
2114 2114 )
2115 2115 else:
2116 2116 ui.status(_(b"nothing changed\n"))
2117 2117 return 1
2118 2118
2119 2119 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2120 2120
2121 2121 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2122 2122 status(
2123 2123 ui,
2124 2124 repo,
2125 2125 modified=True,
2126 2126 added=True,
2127 2127 removed=True,
2128 2128 deleted=True,
2129 2129 unknown=True,
2130 2130 subrepos=opts.get(b'subrepos'),
2131 2131 )
2132 2132
2133 2133
2134 2134 @command(
2135 2135 b'config|showconfig|debugconfig',
2136 2136 [
2137 2137 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2138 2138 (b'e', b'edit', None, _(b'edit user config')),
2139 2139 (b'l', b'local', None, _(b'edit repository config')),
2140 2140 (
2141 2141 b'',
2142 2142 b'shared',
2143 2143 None,
2144 2144 _(b'edit shared source repository config (EXPERIMENTAL)'),
2145 2145 ),
2146 2146 (b'g', b'global', None, _(b'edit global config')),
2147 2147 ]
2148 2148 + formatteropts,
2149 2149 _(b'[-u] [NAME]...'),
2150 2150 helpcategory=command.CATEGORY_HELP,
2151 2151 optionalrepo=True,
2152 2152 intents={INTENT_READONLY},
2153 2153 )
2154 2154 def config(ui, repo, *values, **opts):
2155 2155 """show combined config settings from all hgrc files
2156 2156
2157 2157 With no arguments, print names and values of all config items.
2158 2158
2159 2159 With one argument of the form section.name, print just the value
2160 2160 of that config item.
2161 2161
2162 2162 With multiple arguments, print names and values of all config
2163 2163 items with matching section names or section.names.
2164 2164
2165 2165 With --edit, start an editor on the user-level config file. With
2166 2166 --global, edit the system-wide config file. With --local, edit the
2167 2167 repository-level config file.
2168 2168
2169 2169 With --debug, the source (filename and line number) is printed
2170 2170 for each config item.
2171 2171
2172 2172 See :hg:`help config` for more information about config files.
2173 2173
2174 2174 .. container:: verbose
2175 2175
2176 2176 Template:
2177 2177
2178 2178 The following keywords are supported. See also :hg:`help templates`.
2179 2179
2180 2180 :name: String. Config name.
2181 2181 :source: String. Filename and line number where the item is defined.
2182 2182 :value: String. Config value.
2183 2183
2184 2184 The --shared flag can be used to edit the config file of shared source
2185 2185 repository. It only works when you have shared using the experimental
2186 2186 share safe feature.
2187 2187
2188 2188 Returns 0 on success, 1 if NAME does not exist.
2189 2189
2190 2190 """
2191 2191
2192 2192 opts = pycompat.byteskwargs(opts)
2193 2193 editopts = (b'edit', b'local', b'global', b'shared')
2194 2194 if any(opts.get(o) for o in editopts):
2195 2195 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2196 2196 if opts.get(b'local'):
2197 2197 if not repo:
2198 2198 raise error.Abort(_(b"can't use --local outside a repository"))
2199 2199 paths = [repo.vfs.join(b'hgrc')]
2200 2200 elif opts.get(b'global'):
2201 2201 paths = rcutil.systemrcpath()
2202 2202 elif opts.get(b'shared'):
2203 2203 if not repo.shared():
2204 2204 raise error.Abort(
2205 2205 _(b"repository is not shared; can't use --shared")
2206 2206 )
2207 2207 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2208 2208 raise error.Abort(
2209 2209 _(
2210 2210 b"share safe feature not unabled; "
2211 2211 b"unable to edit shared source repository config"
2212 2212 )
2213 2213 )
2214 2214 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2215 2215 else:
2216 2216 paths = rcutil.userrcpath()
2217 2217
2218 2218 for f in paths:
2219 2219 if os.path.exists(f):
2220 2220 break
2221 2221 else:
2222 2222 if opts.get(b'global'):
2223 2223 samplehgrc = uimod.samplehgrcs[b'global']
2224 2224 elif opts.get(b'local'):
2225 2225 samplehgrc = uimod.samplehgrcs[b'local']
2226 2226 else:
2227 2227 samplehgrc = uimod.samplehgrcs[b'user']
2228 2228
2229 2229 f = paths[0]
2230 2230 fp = open(f, b"wb")
2231 2231 fp.write(util.tonativeeol(samplehgrc))
2232 2232 fp.close()
2233 2233
2234 2234 editor = ui.geteditor()
2235 2235 ui.system(
2236 2236 b"%s \"%s\"" % (editor, f),
2237 2237 onerr=error.Abort,
2238 2238 errprefix=_(b"edit failed"),
2239 2239 blockedtag=b'config_edit',
2240 2240 )
2241 2241 return
2242 2242 ui.pager(b'config')
2243 2243 fm = ui.formatter(b'config', opts)
2244 2244 for t, f in rcutil.rccomponents():
2245 2245 if t == b'path':
2246 2246 ui.debug(b'read config from: %s\n' % f)
2247 2247 elif t == b'resource':
2248 2248 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2249 2249 elif t == b'items':
2250 2250 # Don't print anything for 'items'.
2251 2251 pass
2252 2252 else:
2253 2253 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2254 2254 untrusted = bool(opts.get(b'untrusted'))
2255 2255
2256 2256 selsections = selentries = []
2257 2257 if values:
2258 2258 selsections = [v for v in values if b'.' not in v]
2259 2259 selentries = [v for v in values if b'.' in v]
2260 2260 uniquesel = len(selentries) == 1 and not selsections
2261 2261 selsections = set(selsections)
2262 2262 selentries = set(selentries)
2263 2263
2264 2264 matched = False
2265 2265 for section, name, value in ui.walkconfig(untrusted=untrusted):
2266 2266 source = ui.configsource(section, name, untrusted)
2267 2267 value = pycompat.bytestr(value)
2268 2268 defaultvalue = ui.configdefault(section, name)
2269 2269 if fm.isplain():
2270 2270 source = source or b'none'
2271 2271 value = value.replace(b'\n', b'\\n')
2272 2272 entryname = section + b'.' + name
2273 2273 if values and not (section in selsections or entryname in selentries):
2274 2274 continue
2275 2275 fm.startitem()
2276 2276 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2277 2277 if uniquesel:
2278 2278 fm.data(name=entryname)
2279 2279 fm.write(b'value', b'%s\n', value)
2280 2280 else:
2281 2281 fm.write(b'name value', b'%s=%s\n', entryname, value)
2282 2282 if formatter.isprintable(defaultvalue):
2283 2283 fm.data(defaultvalue=defaultvalue)
2284 2284 elif isinstance(defaultvalue, list) and all(
2285 2285 formatter.isprintable(e) for e in defaultvalue
2286 2286 ):
2287 2287 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2288 2288 # TODO: no idea how to process unsupported defaultvalue types
2289 2289 matched = True
2290 2290 fm.end()
2291 2291 if matched:
2292 2292 return 0
2293 2293 return 1
2294 2294
2295 2295
2296 2296 @command(
2297 2297 b'continue',
2298 2298 dryrunopts,
2299 2299 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2300 2300 helpbasic=True,
2301 2301 )
2302 2302 def continuecmd(ui, repo, **opts):
2303 2303 """resumes an interrupted operation (EXPERIMENTAL)
2304 2304
2305 2305 Finishes a multistep operation like graft, histedit, rebase, merge,
2306 2306 and unshelve if they are in an interrupted state.
2307 2307
2308 2308 use --dry-run/-n to dry run the command.
2309 2309 """
2310 2310 dryrun = opts.get('dry_run')
2311 2311 contstate = cmdutil.getunfinishedstate(repo)
2312 2312 if not contstate:
2313 2313 raise error.Abort(_(b'no operation in progress'))
2314 2314 if not contstate.continuefunc:
2315 2315 raise error.Abort(
2316 2316 (
2317 2317 _(b"%s in progress but does not support 'hg continue'")
2318 2318 % (contstate._opname)
2319 2319 ),
2320 2320 hint=contstate.continuemsg(),
2321 2321 )
2322 2322 if dryrun:
2323 2323 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2324 2324 return
2325 2325 return contstate.continuefunc(ui, repo)
2326 2326
2327 2327
2328 2328 @command(
2329 2329 b'copy|cp',
2330 2330 [
2331 2331 (b'', b'forget', None, _(b'unmark a file as copied')),
2332 2332 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2333 2333 (
2334 2334 b'',
2335 2335 b'at-rev',
2336 2336 b'',
2337 2337 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2338 2338 _(b'REV'),
2339 2339 ),
2340 2340 (
2341 2341 b'f',
2342 2342 b'force',
2343 2343 None,
2344 2344 _(b'forcibly copy over an existing managed file'),
2345 2345 ),
2346 2346 ]
2347 2347 + walkopts
2348 2348 + dryrunopts,
2349 2349 _(b'[OPTION]... SOURCE... DEST'),
2350 2350 helpcategory=command.CATEGORY_FILE_CONTENTS,
2351 2351 )
2352 2352 def copy(ui, repo, *pats, **opts):
2353 2353 """mark files as copied for the next commit
2354 2354
2355 2355 Mark dest as having copies of source files. If dest is a
2356 2356 directory, copies are put in that directory. If dest is a file,
2357 2357 the source must be a single file.
2358 2358
2359 2359 By default, this command copies the contents of files as they
2360 2360 exist in the working directory. If invoked with -A/--after, the
2361 2361 operation is recorded, but no copying is performed.
2362 2362
2363 2363 To undo marking a file as copied, use --forget. With that option,
2364 2364 all given (positional) arguments are unmarked as copies. The destination
2365 2365 file(s) will be left in place (still tracked).
2366 2366
2367 2367 This command takes effect with the next commit by default.
2368 2368
2369 2369 Returns 0 on success, 1 if errors are encountered.
2370 2370 """
2371 2371 opts = pycompat.byteskwargs(opts)
2372 2372 with repo.wlock():
2373 2373 return cmdutil.copy(ui, repo, pats, opts)
2374 2374
2375 2375
2376 2376 @command(
2377 2377 b'debugcommands',
2378 2378 [],
2379 2379 _(b'[COMMAND]'),
2380 2380 helpcategory=command.CATEGORY_HELP,
2381 2381 norepo=True,
2382 2382 )
2383 2383 def debugcommands(ui, cmd=b'', *args):
2384 2384 """list all available commands and options"""
2385 2385 for cmd, vals in sorted(pycompat.iteritems(table)):
2386 2386 cmd = cmd.split(b'|')[0]
2387 2387 opts = b', '.join([i[1] for i in vals[1]])
2388 2388 ui.write(b'%s: %s\n' % (cmd, opts))
2389 2389
2390 2390
2391 2391 @command(
2392 2392 b'debugcomplete',
2393 2393 [(b'o', b'options', None, _(b'show the command options'))],
2394 2394 _(b'[-o] CMD'),
2395 2395 helpcategory=command.CATEGORY_HELP,
2396 2396 norepo=True,
2397 2397 )
2398 2398 def debugcomplete(ui, cmd=b'', **opts):
2399 2399 """returns the completion list associated with the given command"""
2400 2400
2401 2401 if opts.get('options'):
2402 2402 options = []
2403 2403 otables = [globalopts]
2404 2404 if cmd:
2405 2405 aliases, entry = cmdutil.findcmd(cmd, table, False)
2406 2406 otables.append(entry[1])
2407 2407 for t in otables:
2408 2408 for o in t:
2409 2409 if b"(DEPRECATED)" in o[3]:
2410 2410 continue
2411 2411 if o[0]:
2412 2412 options.append(b'-%s' % o[0])
2413 2413 options.append(b'--%s' % o[1])
2414 2414 ui.write(b"%s\n" % b"\n".join(options))
2415 2415 return
2416 2416
2417 2417 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2418 2418 if ui.verbose:
2419 2419 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2420 2420 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2421 2421
2422 2422
2423 2423 @command(
2424 2424 b'diff',
2425 2425 [
2426 2426 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2427 2427 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2428 2428 ]
2429 2429 + diffopts
2430 2430 + diffopts2
2431 2431 + walkopts
2432 2432 + subrepoopts,
2433 2433 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2434 2434 helpcategory=command.CATEGORY_FILE_CONTENTS,
2435 2435 helpbasic=True,
2436 2436 inferrepo=True,
2437 2437 intents={INTENT_READONLY},
2438 2438 )
2439 2439 def diff(ui, repo, *pats, **opts):
2440 2440 """diff repository (or selected files)
2441 2441
2442 2442 Show differences between revisions for the specified files.
2443 2443
2444 2444 Differences between files are shown using the unified diff format.
2445 2445
2446 2446 .. note::
2447 2447
2448 2448 :hg:`diff` may generate unexpected results for merges, as it will
2449 2449 default to comparing against the working directory's first
2450 2450 parent changeset if no revisions are specified.
2451 2451
2452 2452 When two revision arguments are given, then changes are shown
2453 2453 between those revisions. If only one revision is specified then
2454 2454 that revision is compared to the working directory, and, when no
2455 2455 revisions are specified, the working directory files are compared
2456 2456 to its first parent.
2457 2457
2458 2458 Alternatively you can specify -c/--change with a revision to see
2459 2459 the changes in that changeset relative to its first parent.
2460 2460
2461 2461 Without the -a/--text option, diff will avoid generating diffs of
2462 2462 files it detects as binary. With -a, diff will generate a diff
2463 2463 anyway, probably with undesirable results.
2464 2464
2465 2465 Use the -g/--git option to generate diffs in the git extended diff
2466 2466 format. For more information, read :hg:`help diffs`.
2467 2467
2468 2468 .. container:: verbose
2469 2469
2470 2470 Examples:
2471 2471
2472 2472 - compare a file in the current working directory to its parent::
2473 2473
2474 2474 hg diff foo.c
2475 2475
2476 2476 - compare two historical versions of a directory, with rename info::
2477 2477
2478 2478 hg diff --git -r 1.0:1.2 lib/
2479 2479
2480 2480 - get change stats relative to the last change on some date::
2481 2481
2482 2482 hg diff --stat -r "date('may 2')"
2483 2483
2484 2484 - diff all newly-added files that contain a keyword::
2485 2485
2486 2486 hg diff "set:added() and grep(GNU)"
2487 2487
2488 2488 - compare a revision and its parents::
2489 2489
2490 2490 hg diff -c 9353 # compare against first parent
2491 2491 hg diff -r 9353^:9353 # same using revset syntax
2492 2492 hg diff -r 9353^2:9353 # compare against the second parent
2493 2493
2494 2494 Returns 0 on success.
2495 2495 """
2496 2496
2497 2497 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2498 2498 opts = pycompat.byteskwargs(opts)
2499 2499 revs = opts.get(b'rev')
2500 2500 change = opts.get(b'change')
2501 2501 stat = opts.get(b'stat')
2502 2502 reverse = opts.get(b'reverse')
2503 2503
2504 2504 if change:
2505 2505 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2506 2506 ctx2 = scmutil.revsingle(repo, change, None)
2507 2507 ctx1 = ctx2.p1()
2508 2508 else:
2509 2509 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2510 2510 ctx1, ctx2 = scmutil.revpair(repo, revs)
2511 2511
2512 2512 if reverse:
2513 2513 ctxleft = ctx2
2514 2514 ctxright = ctx1
2515 2515 else:
2516 2516 ctxleft = ctx1
2517 2517 ctxright = ctx2
2518 2518
2519 2519 diffopts = patch.diffallopts(ui, opts)
2520 2520 m = scmutil.match(ctx2, pats, opts)
2521 2521 m = repo.narrowmatch(m)
2522 2522 ui.pager(b'diff')
2523 2523 logcmdutil.diffordiffstat(
2524 2524 ui,
2525 2525 repo,
2526 2526 diffopts,
2527 2527 ctxleft,
2528 2528 ctxright,
2529 2529 m,
2530 2530 stat=stat,
2531 2531 listsubrepos=opts.get(b'subrepos'),
2532 2532 root=opts.get(b'root'),
2533 2533 )
2534 2534
2535 2535
2536 2536 @command(
2537 2537 b'export',
2538 2538 [
2539 2539 (
2540 2540 b'B',
2541 2541 b'bookmark',
2542 2542 b'',
2543 2543 _(b'export changes only reachable by given bookmark'),
2544 2544 _(b'BOOKMARK'),
2545 2545 ),
2546 2546 (
2547 2547 b'o',
2548 2548 b'output',
2549 2549 b'',
2550 2550 _(b'print output to file with formatted name'),
2551 2551 _(b'FORMAT'),
2552 2552 ),
2553 2553 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2554 2554 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2555 2555 ]
2556 2556 + diffopts
2557 2557 + formatteropts,
2558 2558 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2559 2559 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2560 2560 helpbasic=True,
2561 2561 intents={INTENT_READONLY},
2562 2562 )
2563 2563 def export(ui, repo, *changesets, **opts):
2564 2564 """dump the header and diffs for one or more changesets
2565 2565
2566 2566 Print the changeset header and diffs for one or more revisions.
2567 2567 If no revision is given, the parent of the working directory is used.
2568 2568
2569 2569 The information shown in the changeset header is: author, date,
2570 2570 branch name (if non-default), changeset hash, parent(s) and commit
2571 2571 comment.
2572 2572
2573 2573 .. note::
2574 2574
2575 2575 :hg:`export` may generate unexpected diff output for merge
2576 2576 changesets, as it will compare the merge changeset against its
2577 2577 first parent only.
2578 2578
2579 2579 Output may be to a file, in which case the name of the file is
2580 2580 given using a template string. See :hg:`help templates`. In addition
2581 2581 to the common template keywords, the following formatting rules are
2582 2582 supported:
2583 2583
2584 2584 :``%%``: literal "%" character
2585 2585 :``%H``: changeset hash (40 hexadecimal digits)
2586 2586 :``%N``: number of patches being generated
2587 2587 :``%R``: changeset revision number
2588 2588 :``%b``: basename of the exporting repository
2589 2589 :``%h``: short-form changeset hash (12 hexadecimal digits)
2590 2590 :``%m``: first line of the commit message (only alphanumeric characters)
2591 2591 :``%n``: zero-padded sequence number, starting at 1
2592 2592 :``%r``: zero-padded changeset revision number
2593 2593 :``\\``: literal "\\" character
2594 2594
2595 2595 Without the -a/--text option, export will avoid generating diffs
2596 2596 of files it detects as binary. With -a, export will generate a
2597 2597 diff anyway, probably with undesirable results.
2598 2598
2599 2599 With -B/--bookmark changesets reachable by the given bookmark are
2600 2600 selected.
2601 2601
2602 2602 Use the -g/--git option to generate diffs in the git extended diff
2603 2603 format. See :hg:`help diffs` for more information.
2604 2604
2605 2605 With the --switch-parent option, the diff will be against the
2606 2606 second parent. It can be useful to review a merge.
2607 2607
2608 2608 .. container:: verbose
2609 2609
2610 2610 Template:
2611 2611
2612 2612 The following keywords are supported in addition to the common template
2613 2613 keywords and functions. See also :hg:`help templates`.
2614 2614
2615 2615 :diff: String. Diff content.
2616 2616 :parents: List of strings. Parent nodes of the changeset.
2617 2617
2618 2618 Examples:
2619 2619
2620 2620 - use export and import to transplant a bugfix to the current
2621 2621 branch::
2622 2622
2623 2623 hg export -r 9353 | hg import -
2624 2624
2625 2625 - export all the changesets between two revisions to a file with
2626 2626 rename information::
2627 2627
2628 2628 hg export --git -r 123:150 > changes.txt
2629 2629
2630 2630 - split outgoing changes into a series of patches with
2631 2631 descriptive names::
2632 2632
2633 2633 hg export -r "outgoing()" -o "%n-%m.patch"
2634 2634
2635 2635 Returns 0 on success.
2636 2636 """
2637 2637 opts = pycompat.byteskwargs(opts)
2638 2638 bookmark = opts.get(b'bookmark')
2639 2639 changesets += tuple(opts.get(b'rev', []))
2640 2640
2641 2641 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2642 2642
2643 2643 if bookmark:
2644 2644 if bookmark not in repo._bookmarks:
2645 2645 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2646 2646
2647 2647 revs = scmutil.bookmarkrevs(repo, bookmark)
2648 2648 else:
2649 2649 if not changesets:
2650 2650 changesets = [b'.']
2651 2651
2652 2652 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2653 2653 revs = scmutil.revrange(repo, changesets)
2654 2654
2655 2655 if not revs:
2656 2656 raise error.Abort(_(b"export requires at least one changeset"))
2657 2657 if len(revs) > 1:
2658 2658 ui.note(_(b'exporting patches:\n'))
2659 2659 else:
2660 2660 ui.note(_(b'exporting patch:\n'))
2661 2661
2662 2662 fntemplate = opts.get(b'output')
2663 2663 if cmdutil.isstdiofilename(fntemplate):
2664 2664 fntemplate = b''
2665 2665
2666 2666 if fntemplate:
2667 2667 fm = formatter.nullformatter(ui, b'export', opts)
2668 2668 else:
2669 2669 ui.pager(b'export')
2670 2670 fm = ui.formatter(b'export', opts)
2671 2671 with fm:
2672 2672 cmdutil.export(
2673 2673 repo,
2674 2674 revs,
2675 2675 fm,
2676 2676 fntemplate=fntemplate,
2677 2677 switch_parent=opts.get(b'switch_parent'),
2678 2678 opts=patch.diffallopts(ui, opts),
2679 2679 )
2680 2680
2681 2681
2682 2682 @command(
2683 2683 b'files',
2684 2684 [
2685 2685 (
2686 2686 b'r',
2687 2687 b'rev',
2688 2688 b'',
2689 2689 _(b'search the repository as it is in REV'),
2690 2690 _(b'REV'),
2691 2691 ),
2692 2692 (
2693 2693 b'0',
2694 2694 b'print0',
2695 2695 None,
2696 2696 _(b'end filenames with NUL, for use with xargs'),
2697 2697 ),
2698 2698 ]
2699 2699 + walkopts
2700 2700 + formatteropts
2701 2701 + subrepoopts,
2702 2702 _(b'[OPTION]... [FILE]...'),
2703 2703 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2704 2704 intents={INTENT_READONLY},
2705 2705 )
2706 2706 def files(ui, repo, *pats, **opts):
2707 2707 """list tracked files
2708 2708
2709 2709 Print files under Mercurial control in the working directory or
2710 2710 specified revision for given files (excluding removed files).
2711 2711 Files can be specified as filenames or filesets.
2712 2712
2713 2713 If no files are given to match, this command prints the names
2714 2714 of all files under Mercurial control.
2715 2715
2716 2716 .. container:: verbose
2717 2717
2718 2718 Template:
2719 2719
2720 2720 The following keywords are supported in addition to the common template
2721 2721 keywords and functions. See also :hg:`help templates`.
2722 2722
2723 2723 :flags: String. Character denoting file's symlink and executable bits.
2724 2724 :path: String. Repository-absolute path of the file.
2725 2725 :size: Integer. Size of the file in bytes.
2726 2726
2727 2727 Examples:
2728 2728
2729 2729 - list all files under the current directory::
2730 2730
2731 2731 hg files .
2732 2732
2733 2733 - shows sizes and flags for current revision::
2734 2734
2735 2735 hg files -vr .
2736 2736
2737 2737 - list all files named README::
2738 2738
2739 2739 hg files -I "**/README"
2740 2740
2741 2741 - list all binary files::
2742 2742
2743 2743 hg files "set:binary()"
2744 2744
2745 2745 - find files containing a regular expression::
2746 2746
2747 2747 hg files "set:grep('bob')"
2748 2748
2749 2749 - search tracked file contents with xargs and grep::
2750 2750
2751 2751 hg files -0 | xargs -0 grep foo
2752 2752
2753 2753 See :hg:`help patterns` and :hg:`help filesets` for more information
2754 2754 on specifying file patterns.
2755 2755
2756 2756 Returns 0 if a match is found, 1 otherwise.
2757 2757
2758 2758 """
2759 2759
2760 2760 opts = pycompat.byteskwargs(opts)
2761 2761 rev = opts.get(b'rev')
2762 2762 if rev:
2763 2763 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2764 2764 ctx = scmutil.revsingle(repo, rev, None)
2765 2765
2766 2766 end = b'\n'
2767 2767 if opts.get(b'print0'):
2768 2768 end = b'\0'
2769 2769 fmt = b'%s' + end
2770 2770
2771 2771 m = scmutil.match(ctx, pats, opts)
2772 2772 ui.pager(b'files')
2773 2773 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2774 2774 with ui.formatter(b'files', opts) as fm:
2775 2775 return cmdutil.files(
2776 2776 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2777 2777 )
2778 2778
2779 2779
2780 2780 @command(
2781 2781 b'forget',
2782 2782 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2783 2783 + walkopts
2784 2784 + dryrunopts,
2785 2785 _(b'[OPTION]... FILE...'),
2786 2786 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2787 2787 helpbasic=True,
2788 2788 inferrepo=True,
2789 2789 )
2790 2790 def forget(ui, repo, *pats, **opts):
2791 2791 """forget the specified files on the next commit
2792 2792
2793 2793 Mark the specified files so they will no longer be tracked
2794 2794 after the next commit.
2795 2795
2796 2796 This only removes files from the current branch, not from the
2797 2797 entire project history, and it does not delete them from the
2798 2798 working directory.
2799 2799
2800 2800 To delete the file from the working directory, see :hg:`remove`.
2801 2801
2802 2802 To undo a forget before the next commit, see :hg:`add`.
2803 2803
2804 2804 .. container:: verbose
2805 2805
2806 2806 Examples:
2807 2807
2808 2808 - forget newly-added binary files::
2809 2809
2810 2810 hg forget "set:added() and binary()"
2811 2811
2812 2812 - forget files that would be excluded by .hgignore::
2813 2813
2814 2814 hg forget "set:hgignore()"
2815 2815
2816 2816 Returns 0 on success.
2817 2817 """
2818 2818
2819 2819 opts = pycompat.byteskwargs(opts)
2820 2820 if not pats:
2821 2821 raise error.Abort(_(b'no files specified'))
2822 2822
2823 2823 m = scmutil.match(repo[None], pats, opts)
2824 2824 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2825 2825 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2826 2826 rejected = cmdutil.forget(
2827 2827 ui,
2828 2828 repo,
2829 2829 m,
2830 2830 prefix=b"",
2831 2831 uipathfn=uipathfn,
2832 2832 explicitonly=False,
2833 2833 dryrun=dryrun,
2834 2834 interactive=interactive,
2835 2835 )[0]
2836 2836 return rejected and 1 or 0
2837 2837
2838 2838
2839 2839 @command(
2840 2840 b'graft',
2841 2841 [
2842 2842 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2843 2843 (
2844 2844 b'',
2845 2845 b'base',
2846 2846 b'',
2847 2847 _(b'base revision when doing the graft merge (ADVANCED)'),
2848 2848 _(b'REV'),
2849 2849 ),
2850 2850 (b'c', b'continue', False, _(b'resume interrupted graft')),
2851 2851 (b'', b'stop', False, _(b'stop interrupted graft')),
2852 2852 (b'', b'abort', False, _(b'abort interrupted graft')),
2853 2853 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2854 2854 (b'', b'log', None, _(b'append graft info to log message')),
2855 2855 (
2856 2856 b'',
2857 2857 b'no-commit',
2858 2858 None,
2859 2859 _(b"don't commit, just apply the changes in working directory"),
2860 2860 ),
2861 2861 (b'f', b'force', False, _(b'force graft')),
2862 2862 (
2863 2863 b'D',
2864 2864 b'currentdate',
2865 2865 False,
2866 2866 _(b'record the current date as commit date'),
2867 2867 ),
2868 2868 (
2869 2869 b'U',
2870 2870 b'currentuser',
2871 2871 False,
2872 2872 _(b'record the current user as committer'),
2873 2873 ),
2874 2874 ]
2875 2875 + commitopts2
2876 2876 + mergetoolopts
2877 2877 + dryrunopts,
2878 2878 _(b'[OPTION]... [-r REV]... REV...'),
2879 2879 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2880 2880 )
2881 2881 def graft(ui, repo, *revs, **opts):
2882 2882 '''copy changes from other branches onto the current branch
2883 2883
2884 2884 This command uses Mercurial's merge logic to copy individual
2885 2885 changes from other branches without merging branches in the
2886 2886 history graph. This is sometimes known as 'backporting' or
2887 2887 'cherry-picking'. By default, graft will copy user, date, and
2888 2888 description from the source changesets.
2889 2889
2890 2890 Changesets that are ancestors of the current revision, that have
2891 2891 already been grafted, or that are merges will be skipped.
2892 2892
2893 2893 If --log is specified, log messages will have a comment appended
2894 2894 of the form::
2895 2895
2896 2896 (grafted from CHANGESETHASH)
2897 2897
2898 2898 If --force is specified, revisions will be grafted even if they
2899 2899 are already ancestors of, or have been grafted to, the destination.
2900 2900 This is useful when the revisions have since been backed out.
2901 2901
2902 2902 If a graft merge results in conflicts, the graft process is
2903 2903 interrupted so that the current merge can be manually resolved.
2904 2904 Once all conflicts are addressed, the graft process can be
2905 2905 continued with the -c/--continue option.
2906 2906
2907 2907 The -c/--continue option reapplies all the earlier options.
2908 2908
2909 2909 .. container:: verbose
2910 2910
2911 2911 The --base option exposes more of how graft internally uses merge with a
2912 2912 custom base revision. --base can be used to specify another ancestor than
2913 2913 the first and only parent.
2914 2914
2915 2915 The command::
2916 2916
2917 2917 hg graft -r 345 --base 234
2918 2918
2919 2919 is thus pretty much the same as::
2920 2920
2921 2921 hg diff -r 234 -r 345 | hg import
2922 2922
2923 2923 but using merge to resolve conflicts and track moved files.
2924 2924
2925 2925 The result of a merge can thus be backported as a single commit by
2926 2926 specifying one of the merge parents as base, and thus effectively
2927 2927 grafting the changes from the other side.
2928 2928
2929 2929 It is also possible to collapse multiple changesets and clean up history
2930 2930 by specifying another ancestor as base, much like rebase --collapse
2931 2931 --keep.
2932 2932
2933 2933 The commit message can be tweaked after the fact using commit --amend .
2934 2934
2935 2935 For using non-ancestors as the base to backout changes, see the backout
2936 2936 command and the hidden --parent option.
2937 2937
2938 2938 .. container:: verbose
2939 2939
2940 2940 Examples:
2941 2941
2942 2942 - copy a single change to the stable branch and edit its description::
2943 2943
2944 2944 hg update stable
2945 2945 hg graft --edit 9393
2946 2946
2947 2947 - graft a range of changesets with one exception, updating dates::
2948 2948
2949 2949 hg graft -D "2085::2093 and not 2091"
2950 2950
2951 2951 - continue a graft after resolving conflicts::
2952 2952
2953 2953 hg graft -c
2954 2954
2955 2955 - show the source of a grafted changeset::
2956 2956
2957 2957 hg log --debug -r .
2958 2958
2959 2959 - show revisions sorted by date::
2960 2960
2961 2961 hg log -r "sort(all(), date)"
2962 2962
2963 2963 - backport the result of a merge as a single commit::
2964 2964
2965 2965 hg graft -r 123 --base 123^
2966 2966
2967 2967 - land a feature branch as one changeset::
2968 2968
2969 2969 hg up -cr default
2970 2970 hg graft -r featureX --base "ancestor('featureX', 'default')"
2971 2971
2972 2972 See :hg:`help revisions` for more about specifying revisions.
2973 2973
2974 2974 Returns 0 on successful completion, 1 if there are unresolved files.
2975 2975 '''
2976 2976 with repo.wlock():
2977 2977 return _dograft(ui, repo, *revs, **opts)
2978 2978
2979 2979
2980 2980 def _dograft(ui, repo, *revs, **opts):
2981 2981 opts = pycompat.byteskwargs(opts)
2982 2982 if revs and opts.get(b'rev'):
2983 2983 ui.warn(
2984 2984 _(
2985 2985 b'warning: inconsistent use of --rev might give unexpected '
2986 2986 b'revision ordering!\n'
2987 2987 )
2988 2988 )
2989 2989
2990 2990 revs = list(revs)
2991 2991 revs.extend(opts.get(b'rev'))
2992 2992 # a dict of data to be stored in state file
2993 2993 statedata = {}
2994 2994 # list of new nodes created by ongoing graft
2995 2995 statedata[b'newnodes'] = []
2996 2996
2997 2997 cmdutil.resolvecommitoptions(ui, opts)
2998 2998
2999 2999 editor = cmdutil.getcommiteditor(
3000 3000 editform=b'graft', **pycompat.strkwargs(opts)
3001 3001 )
3002 3002
3003 3003 cmdutil.check_at_most_one_arg(opts, b'abort', b'stop', b'continue')
3004 3004
3005 3005 cont = False
3006 3006 if opts.get(b'no_commit'):
3007 3007 cmdutil.check_incompatible_arguments(
3008 3008 opts,
3009 3009 b'no_commit',
3010 3010 [b'edit', b'currentuser', b'currentdate', b'log'],
3011 3011 )
3012 3012
3013 3013 graftstate = statemod.cmdstate(repo, b'graftstate')
3014 3014
3015 3015 if opts.get(b'stop'):
3016 3016 cmdutil.check_incompatible_arguments(
3017 3017 opts,
3018 3018 b'stop',
3019 3019 [
3020 3020 b'edit',
3021 3021 b'log',
3022 3022 b'user',
3023 3023 b'date',
3024 3024 b'currentdate',
3025 3025 b'currentuser',
3026 3026 b'rev',
3027 3027 ],
3028 3028 )
3029 3029 return _stopgraft(ui, repo, graftstate)
3030 3030 elif opts.get(b'abort'):
3031 3031 cmdutil.check_incompatible_arguments(
3032 3032 opts,
3033 3033 b'abort',
3034 3034 [
3035 3035 b'edit',
3036 3036 b'log',
3037 3037 b'user',
3038 3038 b'date',
3039 3039 b'currentdate',
3040 3040 b'currentuser',
3041 3041 b'rev',
3042 3042 ],
3043 3043 )
3044 3044 return cmdutil.abortgraft(ui, repo, graftstate)
3045 3045 elif opts.get(b'continue'):
3046 3046 cont = True
3047 3047 if revs:
3048 3048 raise error.Abort(_(b"can't specify --continue and revisions"))
3049 3049 # read in unfinished revisions
3050 3050 if graftstate.exists():
3051 3051 statedata = cmdutil.readgraftstate(repo, graftstate)
3052 3052 if statedata.get(b'date'):
3053 3053 opts[b'date'] = statedata[b'date']
3054 3054 if statedata.get(b'user'):
3055 3055 opts[b'user'] = statedata[b'user']
3056 3056 if statedata.get(b'log'):
3057 3057 opts[b'log'] = True
3058 3058 if statedata.get(b'no_commit'):
3059 3059 opts[b'no_commit'] = statedata.get(b'no_commit')
3060 3060 if statedata.get(b'base'):
3061 3061 opts[b'base'] = statedata.get(b'base')
3062 3062 nodes = statedata[b'nodes']
3063 3063 revs = [repo[node].rev() for node in nodes]
3064 3064 else:
3065 3065 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3066 3066 else:
3067 3067 if not revs:
3068 3068 raise error.Abort(_(b'no revisions specified'))
3069 3069 cmdutil.checkunfinished(repo)
3070 3070 cmdutil.bailifchanged(repo)
3071 3071 revs = scmutil.revrange(repo, revs)
3072 3072
3073 3073 skipped = set()
3074 3074 basectx = None
3075 3075 if opts.get(b'base'):
3076 3076 basectx = scmutil.revsingle(repo, opts[b'base'], None)
3077 3077 if basectx is None:
3078 3078 # check for merges
3079 3079 for rev in repo.revs(b'%ld and merge()', revs):
3080 3080 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3081 3081 skipped.add(rev)
3082 3082 revs = [r for r in revs if r not in skipped]
3083 3083 if not revs:
3084 3084 return -1
3085 3085 if basectx is not None and len(revs) != 1:
3086 3086 raise error.Abort(_(b'only one revision allowed with --base '))
3087 3087
3088 3088 # Don't check in the --continue case, in effect retaining --force across
3089 3089 # --continues. That's because without --force, any revisions we decided to
3090 3090 # skip would have been filtered out here, so they wouldn't have made their
3091 3091 # way to the graftstate. With --force, any revisions we would have otherwise
3092 3092 # skipped would not have been filtered out, and if they hadn't been applied
3093 3093 # already, they'd have been in the graftstate.
3094 3094 if not (cont or opts.get(b'force')) and basectx is None:
3095 3095 # check for ancestors of dest branch
3096 3096 ancestors = repo.revs(b'%ld & (::.)', revs)
3097 3097 for rev in ancestors:
3098 3098 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3099 3099
3100 3100 revs = [r for r in revs if r not in ancestors]
3101 3101
3102 3102 if not revs:
3103 3103 return -1
3104 3104
3105 3105 # analyze revs for earlier grafts
3106 3106 ids = {}
3107 3107 for ctx in repo.set(b"%ld", revs):
3108 3108 ids[ctx.hex()] = ctx.rev()
3109 3109 n = ctx.extra().get(b'source')
3110 3110 if n:
3111 3111 ids[n] = ctx.rev()
3112 3112
3113 3113 # check ancestors for earlier grafts
3114 3114 ui.debug(b'scanning for duplicate grafts\n')
3115 3115
3116 3116 # The only changesets we can be sure doesn't contain grafts of any
3117 3117 # revs, are the ones that are common ancestors of *all* revs:
3118 3118 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3119 3119 ctx = repo[rev]
3120 3120 n = ctx.extra().get(b'source')
3121 3121 if n in ids:
3122 3122 try:
3123 3123 r = repo[n].rev()
3124 3124 except error.RepoLookupError:
3125 3125 r = None
3126 3126 if r in revs:
3127 3127 ui.warn(
3128 3128 _(
3129 3129 b'skipping revision %d:%s '
3130 3130 b'(already grafted to %d:%s)\n'
3131 3131 )
3132 3132 % (r, repo[r], rev, ctx)
3133 3133 )
3134 3134 revs.remove(r)
3135 3135 elif ids[n] in revs:
3136 3136 if r is None:
3137 3137 ui.warn(
3138 3138 _(
3139 3139 b'skipping already grafted revision %d:%s '
3140 3140 b'(%d:%s also has unknown origin %s)\n'
3141 3141 )
3142 3142 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3143 3143 )
3144 3144 else:
3145 3145 ui.warn(
3146 3146 _(
3147 3147 b'skipping already grafted revision %d:%s '
3148 3148 b'(%d:%s also has origin %d:%s)\n'
3149 3149 )
3150 3150 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3151 3151 )
3152 3152 revs.remove(ids[n])
3153 3153 elif ctx.hex() in ids:
3154 3154 r = ids[ctx.hex()]
3155 3155 if r in revs:
3156 3156 ui.warn(
3157 3157 _(
3158 3158 b'skipping already grafted revision %d:%s '
3159 3159 b'(was grafted from %d:%s)\n'
3160 3160 )
3161 3161 % (r, repo[r], rev, ctx)
3162 3162 )
3163 3163 revs.remove(r)
3164 3164 if not revs:
3165 3165 return -1
3166 3166
3167 3167 if opts.get(b'no_commit'):
3168 3168 statedata[b'no_commit'] = True
3169 3169 if opts.get(b'base'):
3170 3170 statedata[b'base'] = opts[b'base']
3171 3171 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3172 3172 desc = b'%d:%s "%s"' % (
3173 3173 ctx.rev(),
3174 3174 ctx,
3175 3175 ctx.description().split(b'\n', 1)[0],
3176 3176 )
3177 3177 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3178 3178 if names:
3179 3179 desc += b' (%s)' % b' '.join(names)
3180 3180 ui.status(_(b'grafting %s\n') % desc)
3181 3181 if opts.get(b'dry_run'):
3182 3182 continue
3183 3183
3184 3184 source = ctx.extra().get(b'source')
3185 3185 extra = {}
3186 3186 if source:
3187 3187 extra[b'source'] = source
3188 3188 extra[b'intermediate-source'] = ctx.hex()
3189 3189 else:
3190 3190 extra[b'source'] = ctx.hex()
3191 3191 user = ctx.user()
3192 3192 if opts.get(b'user'):
3193 3193 user = opts[b'user']
3194 3194 statedata[b'user'] = user
3195 3195 date = ctx.date()
3196 3196 if opts.get(b'date'):
3197 3197 date = opts[b'date']
3198 3198 statedata[b'date'] = date
3199 3199 message = ctx.description()
3200 3200 if opts.get(b'log'):
3201 3201 message += b'\n(grafted from %s)' % ctx.hex()
3202 3202 statedata[b'log'] = True
3203 3203
3204 3204 # we don't merge the first commit when continuing
3205 3205 if not cont:
3206 3206 # perform the graft merge with p1(rev) as 'ancestor'
3207 3207 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3208 3208 base = ctx.p1() if basectx is None else basectx
3209 3209 with ui.configoverride(overrides, b'graft'):
3210 3210 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3211 3211 # report any conflicts
3212 3212 if stats.unresolvedcount > 0:
3213 3213 # write out state for --continue
3214 3214 nodes = [repo[rev].hex() for rev in revs[pos:]]
3215 3215 statedata[b'nodes'] = nodes
3216 3216 stateversion = 1
3217 3217 graftstate.save(stateversion, statedata)
3218 3218 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3219 3219 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3220 3220 return 1
3221 3221 else:
3222 3222 cont = False
3223 3223
3224 3224 # commit if --no-commit is false
3225 3225 if not opts.get(b'no_commit'):
3226 3226 node = repo.commit(
3227 3227 text=message, user=user, date=date, extra=extra, editor=editor
3228 3228 )
3229 3229 if node is None:
3230 3230 ui.warn(
3231 3231 _(b'note: graft of %d:%s created no changes to commit\n')
3232 3232 % (ctx.rev(), ctx)
3233 3233 )
3234 3234 # checking that newnodes exist because old state files won't have it
3235 3235 elif statedata.get(b'newnodes') is not None:
3236 3236 statedata[b'newnodes'].append(node)
3237 3237
3238 3238 # remove state when we complete successfully
3239 3239 if not opts.get(b'dry_run'):
3240 3240 graftstate.delete()
3241 3241
3242 3242 return 0
3243 3243
3244 3244
3245 3245 def _stopgraft(ui, repo, graftstate):
3246 3246 """stop the interrupted graft"""
3247 3247 if not graftstate.exists():
3248 3248 raise error.Abort(_(b"no interrupted graft found"))
3249 3249 pctx = repo[b'.']
3250 3250 mergemod.clean_update(pctx)
3251 3251 graftstate.delete()
3252 3252 ui.status(_(b"stopped the interrupted graft\n"))
3253 3253 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3254 3254 return 0
3255 3255
3256 3256
3257 3257 statemod.addunfinished(
3258 3258 b'graft',
3259 3259 fname=b'graftstate',
3260 3260 clearable=True,
3261 3261 stopflag=True,
3262 3262 continueflag=True,
3263 3263 abortfunc=cmdutil.hgabortgraft,
3264 3264 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3265 3265 )
3266 3266
3267 3267
3268 3268 @command(
3269 3269 b'grep',
3270 3270 [
3271 3271 (b'0', b'print0', None, _(b'end fields with NUL')),
3272 3272 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3273 3273 (
3274 3274 b'',
3275 3275 b'diff',
3276 3276 None,
3277 3277 _(
3278 3278 b'search revision differences for when the pattern was added '
3279 3279 b'or removed'
3280 3280 ),
3281 3281 ),
3282 3282 (b'a', b'text', None, _(b'treat all files as text')),
3283 3283 (
3284 3284 b'f',
3285 3285 b'follow',
3286 3286 None,
3287 3287 _(
3288 3288 b'follow changeset history,'
3289 3289 b' or file history across copies and renames'
3290 3290 ),
3291 3291 ),
3292 3292 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3293 3293 (
3294 3294 b'l',
3295 3295 b'files-with-matches',
3296 3296 None,
3297 3297 _(b'print only filenames and revisions that match'),
3298 3298 ),
3299 3299 (b'n', b'line-number', None, _(b'print matching line numbers')),
3300 3300 (
3301 3301 b'r',
3302 3302 b'rev',
3303 3303 [],
3304 3304 _(b'search files changed within revision range'),
3305 3305 _(b'REV'),
3306 3306 ),
3307 3307 (
3308 3308 b'',
3309 3309 b'all-files',
3310 3310 None,
3311 3311 _(
3312 3312 b'include all files in the changeset while grepping (DEPRECATED)'
3313 3313 ),
3314 3314 ),
3315 3315 (b'u', b'user', None, _(b'list the author (long with -v)')),
3316 3316 (b'd', b'date', None, _(b'list the date (short with -q)')),
3317 3317 ]
3318 3318 + formatteropts
3319 3319 + walkopts,
3320 3320 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3321 3321 helpcategory=command.CATEGORY_FILE_CONTENTS,
3322 3322 inferrepo=True,
3323 3323 intents={INTENT_READONLY},
3324 3324 )
3325 3325 def grep(ui, repo, pattern, *pats, **opts):
3326 3326 """search for a pattern in specified files
3327 3327
3328 3328 Search the working directory or revision history for a regular
3329 3329 expression in the specified files for the entire repository.
3330 3330
3331 3331 By default, grep searches the repository files in the working
3332 3332 directory and prints the files where it finds a match. To specify
3333 3333 historical revisions instead of the working directory, use the
3334 3334 --rev flag.
3335 3335
3336 3336 To search instead historical revision differences that contains a
3337 3337 change in match status ("-" for a match that becomes a non-match,
3338 3338 or "+" for a non-match that becomes a match), use the --diff flag.
3339 3339
3340 3340 PATTERN can be any Python (roughly Perl-compatible) regular
3341 3341 expression.
3342 3342
3343 3343 If no FILEs are specified and the --rev flag isn't supplied, all
3344 3344 files in the working directory are searched. When using the --rev
3345 3345 flag and specifying FILEs, use the --follow argument to also
3346 3346 follow the specified FILEs across renames and copies.
3347 3347
3348 3348 .. container:: verbose
3349 3349
3350 3350 Template:
3351 3351
3352 3352 The following keywords are supported in addition to the common template
3353 3353 keywords and functions. See also :hg:`help templates`.
3354 3354
3355 3355 :change: String. Character denoting insertion ``+`` or removal ``-``.
3356 3356 Available if ``--diff`` is specified.
3357 3357 :lineno: Integer. Line number of the match.
3358 3358 :path: String. Repository-absolute path of the file.
3359 3359 :texts: List of text chunks.
3360 3360
3361 3361 And each entry of ``{texts}`` provides the following sub-keywords.
3362 3362
3363 3363 :matched: Boolean. True if the chunk matches the specified pattern.
3364 3364 :text: String. Chunk content.
3365 3365
3366 3366 See :hg:`help templates.operators` for the list expansion syntax.
3367 3367
3368 3368 Returns 0 if a match is found, 1 otherwise.
3369 3369
3370 3370 """
3371 3371 opts = pycompat.byteskwargs(opts)
3372 3372 diff = opts.get(b'all') or opts.get(b'diff')
3373 3373 if diff and opts.get(b'all_files'):
3374 3374 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3375 3375 if opts.get(b'all_files') is None and not diff:
3376 3376 opts[b'all_files'] = True
3377 3377 plaingrep = (
3378 3378 opts.get(b'all_files')
3379 3379 and not opts.get(b'rev')
3380 3380 and not opts.get(b'follow')
3381 3381 )
3382 3382 all_files = opts.get(b'all_files')
3383 3383 if plaingrep:
3384 3384 opts[b'rev'] = [b'wdir()']
3385 3385
3386 3386 reflags = re.M
3387 3387 if opts.get(b'ignore_case'):
3388 3388 reflags |= re.I
3389 3389 try:
3390 3390 regexp = util.re.compile(pattern, reflags)
3391 3391 except re.error as inst:
3392 3392 ui.warn(
3393 3393 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3394 3394 )
3395 3395 return 1
3396 3396 sep, eol = b':', b'\n'
3397 3397 if opts.get(b'print0'):
3398 3398 sep = eol = b'\0'
3399 3399
3400 3400 getfile = util.lrucachefunc(repo.file)
3401 3401
3402 3402 def matchlines(body):
3403 3403 begin = 0
3404 3404 linenum = 0
3405 3405 while begin < len(body):
3406 3406 match = regexp.search(body, begin)
3407 3407 if not match:
3408 3408 break
3409 3409 mstart, mend = match.span()
3410 3410 linenum += body.count(b'\n', begin, mstart) + 1
3411 3411 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3412 3412 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3413 3413 lend = begin - 1
3414 3414 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3415 3415
3416 3416 class linestate(object):
3417 3417 def __init__(self, line, linenum, colstart, colend):
3418 3418 self.line = line
3419 3419 self.linenum = linenum
3420 3420 self.colstart = colstart
3421 3421 self.colend = colend
3422 3422
3423 3423 def __hash__(self):
3424 3424 return hash(self.line)
3425 3425
3426 3426 def __eq__(self, other):
3427 3427 return self.line == other.line
3428 3428
3429 3429 def findpos(self):
3430 3430 """Iterate all (start, end) indices of matches"""
3431 3431 yield self.colstart, self.colend
3432 3432 p = self.colend
3433 3433 while p < len(self.line):
3434 3434 m = regexp.search(self.line, p)
3435 3435 if not m:
3436 3436 break
3437 3437 if m.end() == p:
3438 3438 p += 1
3439 3439 else:
3440 3440 yield m.span()
3441 3441 p = m.end()
3442 3442
3443 3443 matches = {}
3444 3444 copies = {}
3445 3445
3446 3446 def grepbody(fn, rev, body):
3447 3447 matches[rev].setdefault(fn, [])
3448 3448 m = matches[rev][fn]
3449 3449 if body is None:
3450 3450 return
3451 3451
3452 3452 for lnum, cstart, cend, line in matchlines(body):
3453 3453 s = linestate(line, lnum, cstart, cend)
3454 3454 m.append(s)
3455 3455
3456 3456 def difflinestates(a, b):
3457 3457 sm = difflib.SequenceMatcher(None, a, b)
3458 3458 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3459 3459 if tag == 'insert':
3460 3460 for i in pycompat.xrange(blo, bhi):
3461 3461 yield (b'+', b[i])
3462 3462 elif tag == 'delete':
3463 3463 for i in pycompat.xrange(alo, ahi):
3464 3464 yield (b'-', a[i])
3465 3465 elif tag == 'replace':
3466 3466 for i in pycompat.xrange(alo, ahi):
3467 3467 yield (b'-', a[i])
3468 3468 for i in pycompat.xrange(blo, bhi):
3469 3469 yield (b'+', b[i])
3470 3470
3471 3471 uipathfn = scmutil.getuipathfn(repo)
3472 3472
3473 3473 def display(fm, fn, ctx, pstates, states):
3474 3474 rev = scmutil.intrev(ctx)
3475 3475 if fm.isplain():
3476 3476 formatuser = ui.shortuser
3477 3477 else:
3478 3478 formatuser = pycompat.bytestr
3479 3479 if ui.quiet:
3480 3480 datefmt = b'%Y-%m-%d'
3481 3481 else:
3482 3482 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3483 3483 found = False
3484 3484
3485 3485 @util.cachefunc
3486 3486 def binary():
3487 3487 flog = getfile(fn)
3488 3488 try:
3489 3489 return stringutil.binary(flog.read(ctx.filenode(fn)))
3490 3490 except error.WdirUnsupported:
3491 3491 return ctx[fn].isbinary()
3492 3492
3493 3493 fieldnamemap = {b'linenumber': b'lineno'}
3494 3494 if diff:
3495 3495 iter = difflinestates(pstates, states)
3496 3496 else:
3497 3497 iter = [(b'', l) for l in states]
3498 3498 for change, l in iter:
3499 3499 fm.startitem()
3500 3500 fm.context(ctx=ctx)
3501 3501 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3502 3502 fm.plain(uipathfn(fn), label=b'grep.filename')
3503 3503
3504 3504 cols = [
3505 3505 (b'rev', b'%d', rev, not plaingrep, b''),
3506 3506 (
3507 3507 b'linenumber',
3508 3508 b'%d',
3509 3509 l.linenum,
3510 3510 opts.get(b'line_number'),
3511 3511 b'',
3512 3512 ),
3513 3513 ]
3514 3514 if diff:
3515 3515 cols.append(
3516 3516 (
3517 3517 b'change',
3518 3518 b'%s',
3519 3519 change,
3520 3520 True,
3521 3521 b'grep.inserted '
3522 3522 if change == b'+'
3523 3523 else b'grep.deleted ',
3524 3524 )
3525 3525 )
3526 3526 cols.extend(
3527 3527 [
3528 3528 (
3529 3529 b'user',
3530 3530 b'%s',
3531 3531 formatuser(ctx.user()),
3532 3532 opts.get(b'user'),
3533 3533 b'',
3534 3534 ),
3535 3535 (
3536 3536 b'date',
3537 3537 b'%s',
3538 3538 fm.formatdate(ctx.date(), datefmt),
3539 3539 opts.get(b'date'),
3540 3540 b'',
3541 3541 ),
3542 3542 ]
3543 3543 )
3544 3544 for name, fmt, data, cond, extra_label in cols:
3545 3545 if cond:
3546 3546 fm.plain(sep, label=b'grep.sep')
3547 3547 field = fieldnamemap.get(name, name)
3548 3548 label = extra_label + (b'grep.%s' % name)
3549 3549 fm.condwrite(cond, field, fmt, data, label=label)
3550 3550 if not opts.get(b'files_with_matches'):
3551 3551 fm.plain(sep, label=b'grep.sep')
3552 3552 if not opts.get(b'text') and binary():
3553 3553 fm.plain(_(b" Binary file matches"))
3554 3554 else:
3555 3555 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3556 3556 fm.plain(eol)
3557 3557 found = True
3558 3558 if opts.get(b'files_with_matches'):
3559 3559 break
3560 3560 return found
3561 3561
3562 3562 def displaymatches(fm, l):
3563 3563 p = 0
3564 3564 for s, e in l.findpos():
3565 3565 if p < s:
3566 3566 fm.startitem()
3567 3567 fm.write(b'text', b'%s', l.line[p:s])
3568 3568 fm.data(matched=False)
3569 3569 fm.startitem()
3570 3570 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3571 3571 fm.data(matched=True)
3572 3572 p = e
3573 3573 if p < len(l.line):
3574 3574 fm.startitem()
3575 3575 fm.write(b'text', b'%s', l.line[p:])
3576 3576 fm.data(matched=False)
3577 3577 fm.end()
3578 3578
3579 3579 skip = set()
3580 3580 revfiles = {}
3581 3581 match = scmutil.match(repo[None], pats, opts)
3582 3582 found = False
3583 3583 follow = opts.get(b'follow')
3584 3584
3585 3585 getrenamed = scmutil.getrenamedfn(repo)
3586 3586
3587 3587 def readfile(ctx, fn):
3588 3588 rev = ctx.rev()
3589 3589 if rev is None:
3590 3590 fctx = ctx[fn]
3591 3591 try:
3592 3592 return fctx.data()
3593 3593 except IOError as e:
3594 3594 if e.errno != errno.ENOENT:
3595 3595 raise
3596 3596 else:
3597 3597 flog = getfile(fn)
3598 3598 fnode = ctx.filenode(fn)
3599 3599 try:
3600 3600 return flog.read(fnode)
3601 3601 except error.CensoredNodeError:
3602 3602 ui.warn(
3603 3603 _(
3604 3604 b'cannot search in censored file: %(filename)s:%(revnum)s\n'
3605 3605 )
3606 3606 % {b'filename': fn, b'revnum': pycompat.bytestr(rev),}
3607 3607 )
3608 3608
3609 3609 def prep(ctx, fns):
3610 3610 rev = ctx.rev()
3611 3611 pctx = ctx.p1()
3612 3612 matches.setdefault(rev, {})
3613 3613 if diff:
3614 3614 parent = pctx.rev()
3615 3615 matches.setdefault(parent, {})
3616 3616 files = revfiles.setdefault(rev, [])
3617 3617 if rev is None:
3618 3618 # in `hg grep pattern`, 2/3 of the time is spent is spent in
3619 3619 # pathauditor checks without this in mozilla-central
3620 3620 contextmanager = repo.wvfs.audit.cached
3621 3621 else:
3622 3622 contextmanager = util.nullcontextmanager
3623 3623 with contextmanager():
3624 3624 for fn in fns:
3625 3625 # fn might not exist in the revision (could be a file removed by
3626 3626 # the revision). We could check `fn not in ctx` even when rev is
3627 3627 # None, but it's less racy to protect againt that in readfile.
3628 3628 if rev is not None and fn not in ctx:
3629 3629 continue
3630 3630
3631 3631 copy = None
3632 3632 if follow:
3633 3633 copy = getrenamed(fn, rev)
3634 3634 if copy:
3635 3635 copies.setdefault(rev, {})[fn] = copy
3636 3636 if fn in skip:
3637 3637 skip.add(copy)
3638 3638 if fn in skip:
3639 3639 continue
3640 3640 files.append(fn)
3641 3641
3642 3642 if fn not in matches[rev]:
3643 3643 grepbody(fn, rev, readfile(ctx, fn))
3644 3644
3645 3645 if diff:
3646 3646 pfn = copy or fn
3647 3647 if pfn not in matches[parent] and pfn in pctx:
3648 3648 grepbody(pfn, parent, readfile(pctx, pfn))
3649 3649
3650 3650 ui.pager(b'grep')
3651 3651 fm = ui.formatter(b'grep', opts)
3652 3652 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3653 3653 rev = ctx.rev()
3654 3654 parent = ctx.p1().rev()
3655 3655 for fn in sorted(revfiles.get(rev, [])):
3656 3656 states = matches[rev][fn]
3657 3657 copy = copies.get(rev, {}).get(fn)
3658 3658 if fn in skip:
3659 3659 if copy:
3660 3660 skip.add(copy)
3661 3661 continue
3662 3662 pstates = matches.get(parent, {}).get(copy or fn, [])
3663 3663 if pstates or states:
3664 3664 r = display(fm, fn, ctx, pstates, states)
3665 3665 found = found or r
3666 3666 if r and not diff and not all_files:
3667 3667 skip.add(fn)
3668 3668 if copy:
3669 3669 skip.add(copy)
3670 3670 del revfiles[rev]
3671 3671 # We will keep the matches dict for the duration of the window
3672 3672 # clear the matches dict once the window is over
3673 3673 if not revfiles:
3674 3674 matches.clear()
3675 3675 fm.end()
3676 3676
3677 3677 return not found
3678 3678
3679 3679
3680 3680 @command(
3681 3681 b'heads',
3682 3682 [
3683 3683 (
3684 3684 b'r',
3685 3685 b'rev',
3686 3686 b'',
3687 3687 _(b'show only heads which are descendants of STARTREV'),
3688 3688 _(b'STARTREV'),
3689 3689 ),
3690 3690 (b't', b'topo', False, _(b'show topological heads only')),
3691 3691 (
3692 3692 b'a',
3693 3693 b'active',
3694 3694 False,
3695 3695 _(b'show active branchheads only (DEPRECATED)'),
3696 3696 ),
3697 3697 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3698 3698 ]
3699 3699 + templateopts,
3700 3700 _(b'[-ct] [-r STARTREV] [REV]...'),
3701 3701 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3702 3702 intents={INTENT_READONLY},
3703 3703 )
3704 3704 def heads(ui, repo, *branchrevs, **opts):
3705 3705 """show branch heads
3706 3706
3707 3707 With no arguments, show all open branch heads in the repository.
3708 3708 Branch heads are changesets that have no descendants on the
3709 3709 same branch. They are where development generally takes place and
3710 3710 are the usual targets for update and merge operations.
3711 3711
3712 3712 If one or more REVs are given, only open branch heads on the
3713 3713 branches associated with the specified changesets are shown. This
3714 3714 means that you can use :hg:`heads .` to see the heads on the
3715 3715 currently checked-out branch.
3716 3716
3717 3717 If -c/--closed is specified, also show branch heads marked closed
3718 3718 (see :hg:`commit --close-branch`).
3719 3719
3720 3720 If STARTREV is specified, only those heads that are descendants of
3721 3721 STARTREV will be displayed.
3722 3722
3723 3723 If -t/--topo is specified, named branch mechanics will be ignored and only
3724 3724 topological heads (changesets with no children) will be shown.
3725 3725
3726 3726 Returns 0 if matching heads are found, 1 if not.
3727 3727 """
3728 3728
3729 3729 opts = pycompat.byteskwargs(opts)
3730 3730 start = None
3731 3731 rev = opts.get(b'rev')
3732 3732 if rev:
3733 3733 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3734 3734 start = scmutil.revsingle(repo, rev, None).node()
3735 3735
3736 3736 if opts.get(b'topo'):
3737 3737 heads = [repo[h] for h in repo.heads(start)]
3738 3738 else:
3739 3739 heads = []
3740 3740 for branch in repo.branchmap():
3741 3741 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3742 3742 heads = [repo[h] for h in heads]
3743 3743
3744 3744 if branchrevs:
3745 3745 branches = {
3746 3746 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3747 3747 }
3748 3748 heads = [h for h in heads if h.branch() in branches]
3749 3749
3750 3750 if opts.get(b'active') and branchrevs:
3751 3751 dagheads = repo.heads(start)
3752 3752 heads = [h for h in heads if h.node() in dagheads]
3753 3753
3754 3754 if branchrevs:
3755 3755 haveheads = {h.branch() for h in heads}
3756 3756 if branches - haveheads:
3757 3757 headless = b', '.join(b for b in branches - haveheads)
3758 3758 msg = _(b'no open branch heads found on branches %s')
3759 3759 if opts.get(b'rev'):
3760 3760 msg += _(b' (started at %s)') % opts[b'rev']
3761 3761 ui.warn((msg + b'\n') % headless)
3762 3762
3763 3763 if not heads:
3764 3764 return 1
3765 3765
3766 3766 ui.pager(b'heads')
3767 3767 heads = sorted(heads, key=lambda x: -(x.rev()))
3768 3768 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3769 3769 for ctx in heads:
3770 3770 displayer.show(ctx)
3771 3771 displayer.close()
3772 3772
3773 3773
3774 3774 @command(
3775 3775 b'help',
3776 3776 [
3777 3777 (b'e', b'extension', None, _(b'show only help for extensions')),
3778 3778 (b'c', b'command', None, _(b'show only help for commands')),
3779 3779 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3780 3780 (
3781 3781 b's',
3782 3782 b'system',
3783 3783 [],
3784 3784 _(b'show help for specific platform(s)'),
3785 3785 _(b'PLATFORM'),
3786 3786 ),
3787 3787 ],
3788 3788 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3789 3789 helpcategory=command.CATEGORY_HELP,
3790 3790 norepo=True,
3791 3791 intents={INTENT_READONLY},
3792 3792 )
3793 3793 def help_(ui, name=None, **opts):
3794 3794 """show help for a given topic or a help overview
3795 3795
3796 3796 With no arguments, print a list of commands with short help messages.
3797 3797
3798 3798 Given a topic, extension, or command name, print help for that
3799 3799 topic.
3800 3800
3801 3801 Returns 0 if successful.
3802 3802 """
3803 3803
3804 3804 keep = opts.get('system') or []
3805 3805 if len(keep) == 0:
3806 3806 if pycompat.sysplatform.startswith(b'win'):
3807 3807 keep.append(b'windows')
3808 3808 elif pycompat.sysplatform == b'OpenVMS':
3809 3809 keep.append(b'vms')
3810 3810 elif pycompat.sysplatform == b'plan9':
3811 3811 keep.append(b'plan9')
3812 3812 else:
3813 3813 keep.append(b'unix')
3814 3814 keep.append(pycompat.sysplatform.lower())
3815 3815 if ui.verbose:
3816 3816 keep.append(b'verbose')
3817 3817
3818 3818 commands = sys.modules[__name__]
3819 3819 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3820 3820 ui.pager(b'help')
3821 3821 ui.write(formatted)
3822 3822
3823 3823
3824 3824 @command(
3825 3825 b'identify|id',
3826 3826 [
3827 3827 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3828 3828 (b'n', b'num', None, _(b'show local revision number')),
3829 3829 (b'i', b'id', None, _(b'show global revision id')),
3830 3830 (b'b', b'branch', None, _(b'show branch')),
3831 3831 (b't', b'tags', None, _(b'show tags')),
3832 3832 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3833 3833 ]
3834 3834 + remoteopts
3835 3835 + formatteropts,
3836 3836 _(b'[-nibtB] [-r REV] [SOURCE]'),
3837 3837 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3838 3838 optionalrepo=True,
3839 3839 intents={INTENT_READONLY},
3840 3840 )
3841 3841 def identify(
3842 3842 ui,
3843 3843 repo,
3844 3844 source=None,
3845 3845 rev=None,
3846 3846 num=None,
3847 3847 id=None,
3848 3848 branch=None,
3849 3849 tags=None,
3850 3850 bookmarks=None,
3851 3851 **opts
3852 3852 ):
3853 3853 """identify the working directory or specified revision
3854 3854
3855 3855 Print a summary identifying the repository state at REV using one or
3856 3856 two parent hash identifiers, followed by a "+" if the working
3857 3857 directory has uncommitted changes, the branch name (if not default),
3858 3858 a list of tags, and a list of bookmarks.
3859 3859
3860 3860 When REV is not given, print a summary of the current state of the
3861 3861 repository including the working directory. Specify -r. to get information
3862 3862 of the working directory parent without scanning uncommitted changes.
3863 3863
3864 3864 Specifying a path to a repository root or Mercurial bundle will
3865 3865 cause lookup to operate on that repository/bundle.
3866 3866
3867 3867 .. container:: verbose
3868 3868
3869 3869 Template:
3870 3870
3871 3871 The following keywords are supported in addition to the common template
3872 3872 keywords and functions. See also :hg:`help templates`.
3873 3873
3874 3874 :dirty: String. Character ``+`` denoting if the working directory has
3875 3875 uncommitted changes.
3876 3876 :id: String. One or two nodes, optionally followed by ``+``.
3877 3877 :parents: List of strings. Parent nodes of the changeset.
3878 3878
3879 3879 Examples:
3880 3880
3881 3881 - generate a build identifier for the working directory::
3882 3882
3883 3883 hg id --id > build-id.dat
3884 3884
3885 3885 - find the revision corresponding to a tag::
3886 3886
3887 3887 hg id -n -r 1.3
3888 3888
3889 3889 - check the most recent revision of a remote repository::
3890 3890
3891 3891 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3892 3892
3893 3893 See :hg:`log` for generating more information about specific revisions,
3894 3894 including full hash identifiers.
3895 3895
3896 3896 Returns 0 if successful.
3897 3897 """
3898 3898
3899 3899 opts = pycompat.byteskwargs(opts)
3900 3900 if not repo and not source:
3901 3901 raise error.Abort(
3902 3902 _(b"there is no Mercurial repository here (.hg not found)")
3903 3903 )
3904 3904
3905 3905 default = not (num or id or branch or tags or bookmarks)
3906 3906 output = []
3907 3907 revs = []
3908 3908
3909 3909 if source:
3910 3910 source, branches = hg.parseurl(ui.expandpath(source))
3911 3911 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3912 3912 repo = peer.local()
3913 3913 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3914 3914
3915 3915 fm = ui.formatter(b'identify', opts)
3916 3916 fm.startitem()
3917 3917
3918 3918 if not repo:
3919 3919 if num or branch or tags:
3920 3920 raise error.Abort(
3921 3921 _(b"can't query remote revision number, branch, or tags")
3922 3922 )
3923 3923 if not rev and revs:
3924 3924 rev = revs[0]
3925 3925 if not rev:
3926 3926 rev = b"tip"
3927 3927
3928 3928 remoterev = peer.lookup(rev)
3929 3929 hexrev = fm.hexfunc(remoterev)
3930 3930 if default or id:
3931 3931 output = [hexrev]
3932 3932 fm.data(id=hexrev)
3933 3933
3934 3934 @util.cachefunc
3935 3935 def getbms():
3936 3936 bms = []
3937 3937
3938 3938 if b'bookmarks' in peer.listkeys(b'namespaces'):
3939 3939 hexremoterev = hex(remoterev)
3940 3940 bms = [
3941 3941 bm
3942 3942 for bm, bmr in pycompat.iteritems(
3943 3943 peer.listkeys(b'bookmarks')
3944 3944 )
3945 3945 if bmr == hexremoterev
3946 3946 ]
3947 3947
3948 3948 return sorted(bms)
3949 3949
3950 3950 if fm.isplain():
3951 3951 if bookmarks:
3952 3952 output.extend(getbms())
3953 3953 elif default and not ui.quiet:
3954 3954 # multiple bookmarks for a single parent separated by '/'
3955 3955 bm = b'/'.join(getbms())
3956 3956 if bm:
3957 3957 output.append(bm)
3958 3958 else:
3959 3959 fm.data(node=hex(remoterev))
3960 3960 if bookmarks or b'bookmarks' in fm.datahint():
3961 3961 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3962 3962 else:
3963 3963 if rev:
3964 3964 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3965 3965 ctx = scmutil.revsingle(repo, rev, None)
3966 3966
3967 3967 if ctx.rev() is None:
3968 3968 ctx = repo[None]
3969 3969 parents = ctx.parents()
3970 3970 taglist = []
3971 3971 for p in parents:
3972 3972 taglist.extend(p.tags())
3973 3973
3974 3974 dirty = b""
3975 3975 if ctx.dirty(missing=True, merge=False, branch=False):
3976 3976 dirty = b'+'
3977 3977 fm.data(dirty=dirty)
3978 3978
3979 3979 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3980 3980 if default or id:
3981 3981 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3982 3982 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3983 3983
3984 3984 if num:
3985 3985 numoutput = [b"%d" % p.rev() for p in parents]
3986 3986 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3987 3987
3988 3988 fm.data(
3989 3989 parents=fm.formatlist(
3990 3990 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3991 3991 )
3992 3992 )
3993 3993 else:
3994 3994 hexoutput = fm.hexfunc(ctx.node())
3995 3995 if default or id:
3996 3996 output = [hexoutput]
3997 3997 fm.data(id=hexoutput)
3998 3998
3999 3999 if num:
4000 4000 output.append(pycompat.bytestr(ctx.rev()))
4001 4001 taglist = ctx.tags()
4002 4002
4003 4003 if default and not ui.quiet:
4004 4004 b = ctx.branch()
4005 4005 if b != b'default':
4006 4006 output.append(b"(%s)" % b)
4007 4007
4008 4008 # multiple tags for a single parent separated by '/'
4009 4009 t = b'/'.join(taglist)
4010 4010 if t:
4011 4011 output.append(t)
4012 4012
4013 4013 # multiple bookmarks for a single parent separated by '/'
4014 4014 bm = b'/'.join(ctx.bookmarks())
4015 4015 if bm:
4016 4016 output.append(bm)
4017 4017 else:
4018 4018 if branch:
4019 4019 output.append(ctx.branch())
4020 4020
4021 4021 if tags:
4022 4022 output.extend(taglist)
4023 4023
4024 4024 if bookmarks:
4025 4025 output.extend(ctx.bookmarks())
4026 4026
4027 4027 fm.data(node=ctx.hex())
4028 4028 fm.data(branch=ctx.branch())
4029 4029 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4030 4030 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4031 4031 fm.context(ctx=ctx)
4032 4032
4033 4033 fm.plain(b"%s\n" % b' '.join(output))
4034 4034 fm.end()
4035 4035
4036 4036
4037 4037 @command(
4038 4038 b'import|patch',
4039 4039 [
4040 4040 (
4041 4041 b'p',
4042 4042 b'strip',
4043 4043 1,
4044 4044 _(
4045 4045 b'directory strip option for patch. This has the same '
4046 4046 b'meaning as the corresponding patch option'
4047 4047 ),
4048 4048 _(b'NUM'),
4049 4049 ),
4050 4050 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4051 4051 (b'', b'secret', None, _(b'use the secret phase for committing')),
4052 4052 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4053 4053 (
4054 4054 b'f',
4055 4055 b'force',
4056 4056 None,
4057 4057 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4058 4058 ),
4059 4059 (
4060 4060 b'',
4061 4061 b'no-commit',
4062 4062 None,
4063 4063 _(b"don't commit, just update the working directory"),
4064 4064 ),
4065 4065 (
4066 4066 b'',
4067 4067 b'bypass',
4068 4068 None,
4069 4069 _(b"apply patch without touching the working directory"),
4070 4070 ),
4071 4071 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4072 4072 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4073 4073 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4074 4074 (
4075 4075 b'',
4076 4076 b'import-branch',
4077 4077 None,
4078 4078 _(b'use any branch information in patch (implied by --exact)'),
4079 4079 ),
4080 4080 ]
4081 4081 + commitopts
4082 4082 + commitopts2
4083 4083 + similarityopts,
4084 4084 _(b'[OPTION]... PATCH...'),
4085 4085 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4086 4086 )
4087 4087 def import_(ui, repo, patch1=None, *patches, **opts):
4088 4088 """import an ordered set of patches
4089 4089
4090 4090 Import a list of patches and commit them individually (unless
4091 4091 --no-commit is specified).
4092 4092
4093 4093 To read a patch from standard input (stdin), use "-" as the patch
4094 4094 name. If a URL is specified, the patch will be downloaded from
4095 4095 there.
4096 4096
4097 4097 Import first applies changes to the working directory (unless
4098 4098 --bypass is specified), import will abort if there are outstanding
4099 4099 changes.
4100 4100
4101 4101 Use --bypass to apply and commit patches directly to the
4102 4102 repository, without affecting the working directory. Without
4103 4103 --exact, patches will be applied on top of the working directory
4104 4104 parent revision.
4105 4105
4106 4106 You can import a patch straight from a mail message. Even patches
4107 4107 as attachments work (to use the body part, it must have type
4108 4108 text/plain or text/x-patch). From and Subject headers of email
4109 4109 message are used as default committer and commit message. All
4110 4110 text/plain body parts before first diff are added to the commit
4111 4111 message.
4112 4112
4113 4113 If the imported patch was generated by :hg:`export`, user and
4114 4114 description from patch override values from message headers and
4115 4115 body. Values given on command line with -m/--message and -u/--user
4116 4116 override these.
4117 4117
4118 4118 If --exact is specified, import will set the working directory to
4119 4119 the parent of each patch before applying it, and will abort if the
4120 4120 resulting changeset has a different ID than the one recorded in
4121 4121 the patch. This will guard against various ways that portable
4122 4122 patch formats and mail systems might fail to transfer Mercurial
4123 4123 data or metadata. See :hg:`bundle` for lossless transmission.
4124 4124
4125 4125 Use --partial to ensure a changeset will be created from the patch
4126 4126 even if some hunks fail to apply. Hunks that fail to apply will be
4127 4127 written to a <target-file>.rej file. Conflicts can then be resolved
4128 4128 by hand before :hg:`commit --amend` is run to update the created
4129 4129 changeset. This flag exists to let people import patches that
4130 4130 partially apply without losing the associated metadata (author,
4131 4131 date, description, ...).
4132 4132
4133 4133 .. note::
4134 4134
4135 4135 When no hunks apply cleanly, :hg:`import --partial` will create
4136 4136 an empty changeset, importing only the patch metadata.
4137 4137
4138 4138 With -s/--similarity, hg will attempt to discover renames and
4139 4139 copies in the patch in the same way as :hg:`addremove`.
4140 4140
4141 4141 It is possible to use external patch programs to perform the patch
4142 4142 by setting the ``ui.patch`` configuration option. For the default
4143 4143 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4144 4144 See :hg:`help config` for more information about configuration
4145 4145 files and how to use these options.
4146 4146
4147 4147 See :hg:`help dates` for a list of formats valid for -d/--date.
4148 4148
4149 4149 .. container:: verbose
4150 4150
4151 4151 Examples:
4152 4152
4153 4153 - import a traditional patch from a website and detect renames::
4154 4154
4155 4155 hg import -s 80 http://example.com/bugfix.patch
4156 4156
4157 4157 - import a changeset from an hgweb server::
4158 4158
4159 4159 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4160 4160
4161 4161 - import all the patches in an Unix-style mbox::
4162 4162
4163 4163 hg import incoming-patches.mbox
4164 4164
4165 4165 - import patches from stdin::
4166 4166
4167 4167 hg import -
4168 4168
4169 4169 - attempt to exactly restore an exported changeset (not always
4170 4170 possible)::
4171 4171
4172 4172 hg import --exact proposed-fix.patch
4173 4173
4174 4174 - use an external tool to apply a patch which is too fuzzy for
4175 4175 the default internal tool.
4176 4176
4177 4177 hg import --config ui.patch="patch --merge" fuzzy.patch
4178 4178
4179 4179 - change the default fuzzing from 2 to a less strict 7
4180 4180
4181 4181 hg import --config ui.fuzz=7 fuzz.patch
4182 4182
4183 4183 Returns 0 on success, 1 on partial success (see --partial).
4184 4184 """
4185 4185
4186 4186 opts = pycompat.byteskwargs(opts)
4187 4187 if not patch1:
4188 4188 raise error.Abort(_(b'need at least one patch to import'))
4189 4189
4190 4190 patches = (patch1,) + patches
4191 4191
4192 4192 date = opts.get(b'date')
4193 4193 if date:
4194 4194 opts[b'date'] = dateutil.parsedate(date)
4195 4195
4196 4196 exact = opts.get(b'exact')
4197 4197 update = not opts.get(b'bypass')
4198 4198 if not update and opts.get(b'no_commit'):
4199 4199 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4200 4200 if opts.get(b'secret') and opts.get(b'no_commit'):
4201 4201 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4202 4202 try:
4203 4203 sim = float(opts.get(b'similarity') or 0)
4204 4204 except ValueError:
4205 4205 raise error.Abort(_(b'similarity must be a number'))
4206 4206 if sim < 0 or sim > 100:
4207 4207 raise error.Abort(_(b'similarity must be between 0 and 100'))
4208 4208 if sim and not update:
4209 4209 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4210 4210 if exact:
4211 4211 if opts.get(b'edit'):
4212 4212 raise error.Abort(_(b'cannot use --exact with --edit'))
4213 4213 if opts.get(b'prefix'):
4214 4214 raise error.Abort(_(b'cannot use --exact with --prefix'))
4215 4215
4216 4216 base = opts[b"base"]
4217 4217 msgs = []
4218 4218 ret = 0
4219 4219
4220 4220 with repo.wlock():
4221 4221 if update:
4222 4222 cmdutil.checkunfinished(repo)
4223 4223 if exact or not opts.get(b'force'):
4224 4224 cmdutil.bailifchanged(repo)
4225 4225
4226 4226 if not opts.get(b'no_commit'):
4227 4227 lock = repo.lock
4228 4228 tr = lambda: repo.transaction(b'import')
4229 4229 dsguard = util.nullcontextmanager
4230 4230 else:
4231 4231 lock = util.nullcontextmanager
4232 4232 tr = util.nullcontextmanager
4233 4233 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4234 4234 with lock(), tr(), dsguard():
4235 4235 parents = repo[None].parents()
4236 4236 for patchurl in patches:
4237 4237 if patchurl == b'-':
4238 4238 ui.status(_(b'applying patch from stdin\n'))
4239 4239 patchfile = ui.fin
4240 4240 patchurl = b'stdin' # for error message
4241 4241 else:
4242 4242 patchurl = os.path.join(base, patchurl)
4243 4243 ui.status(_(b'applying %s\n') % patchurl)
4244 4244 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4245 4245
4246 4246 haspatch = False
4247 4247 for hunk in patch.split(patchfile):
4248 4248 with patch.extract(ui, hunk) as patchdata:
4249 4249 msg, node, rej = cmdutil.tryimportone(
4250 4250 ui, repo, patchdata, parents, opts, msgs, hg.clean
4251 4251 )
4252 4252 if msg:
4253 4253 haspatch = True
4254 4254 ui.note(msg + b'\n')
4255 4255 if update or exact:
4256 4256 parents = repo[None].parents()
4257 4257 else:
4258 4258 parents = [repo[node]]
4259 4259 if rej:
4260 4260 ui.write_err(_(b"patch applied partially\n"))
4261 4261 ui.write_err(
4262 4262 _(
4263 4263 b"(fix the .rej files and run "
4264 4264 b"`hg commit --amend`)\n"
4265 4265 )
4266 4266 )
4267 4267 ret = 1
4268 4268 break
4269 4269
4270 4270 if not haspatch:
4271 4271 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4272 4272
4273 4273 if msgs:
4274 4274 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4275 4275 return ret
4276 4276
4277 4277
4278 4278 @command(
4279 4279 b'incoming|in',
4280 4280 [
4281 4281 (
4282 4282 b'f',
4283 4283 b'force',
4284 4284 None,
4285 4285 _(b'run even if remote repository is unrelated'),
4286 4286 ),
4287 4287 (b'n', b'newest-first', None, _(b'show newest record first')),
4288 4288 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4289 4289 (
4290 4290 b'r',
4291 4291 b'rev',
4292 4292 [],
4293 4293 _(b'a remote changeset intended to be added'),
4294 4294 _(b'REV'),
4295 4295 ),
4296 4296 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4297 4297 (
4298 4298 b'b',
4299 4299 b'branch',
4300 4300 [],
4301 4301 _(b'a specific branch you would like to pull'),
4302 4302 _(b'BRANCH'),
4303 4303 ),
4304 4304 ]
4305 4305 + logopts
4306 4306 + remoteopts
4307 4307 + subrepoopts,
4308 4308 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4309 4309 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4310 4310 )
4311 4311 def incoming(ui, repo, source=b"default", **opts):
4312 4312 """show new changesets found in source
4313 4313
4314 4314 Show new changesets found in the specified path/URL or the default
4315 4315 pull location. These are the changesets that would have been pulled
4316 4316 by :hg:`pull` at the time you issued this command.
4317 4317
4318 4318 See pull for valid source format details.
4319 4319
4320 4320 .. container:: verbose
4321 4321
4322 4322 With -B/--bookmarks, the result of bookmark comparison between
4323 4323 local and remote repositories is displayed. With -v/--verbose,
4324 4324 status is also displayed for each bookmark like below::
4325 4325
4326 4326 BM1 01234567890a added
4327 4327 BM2 1234567890ab advanced
4328 4328 BM3 234567890abc diverged
4329 4329 BM4 34567890abcd changed
4330 4330
4331 4331 The action taken locally when pulling depends on the
4332 4332 status of each bookmark:
4333 4333
4334 4334 :``added``: pull will create it
4335 4335 :``advanced``: pull will update it
4336 4336 :``diverged``: pull will create a divergent bookmark
4337 4337 :``changed``: result depends on remote changesets
4338 4338
4339 4339 From the point of view of pulling behavior, bookmark
4340 4340 existing only in the remote repository are treated as ``added``,
4341 4341 even if it is in fact locally deleted.
4342 4342
4343 4343 .. container:: verbose
4344 4344
4345 4345 For remote repository, using --bundle avoids downloading the
4346 4346 changesets twice if the incoming is followed by a pull.
4347 4347
4348 4348 Examples:
4349 4349
4350 4350 - show incoming changes with patches and full description::
4351 4351
4352 4352 hg incoming -vp
4353 4353
4354 4354 - show incoming changes excluding merges, store a bundle::
4355 4355
4356 4356 hg in -vpM --bundle incoming.hg
4357 4357 hg pull incoming.hg
4358 4358
4359 4359 - briefly list changes inside a bundle::
4360 4360
4361 4361 hg in changes.hg -T "{desc|firstline}\\n"
4362 4362
4363 4363 Returns 0 if there are incoming changes, 1 otherwise.
4364 4364 """
4365 4365 opts = pycompat.byteskwargs(opts)
4366 4366 if opts.get(b'graph'):
4367 4367 logcmdutil.checkunsupportedgraphflags([], opts)
4368 4368
4369 4369 def display(other, chlist, displayer):
4370 4370 revdag = logcmdutil.graphrevs(other, chlist, opts)
4371 4371 logcmdutil.displaygraph(
4372 4372 ui, repo, revdag, displayer, graphmod.asciiedges
4373 4373 )
4374 4374
4375 4375 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4376 4376 return 0
4377 4377
4378 4378 if opts.get(b'bundle') and opts.get(b'subrepos'):
4379 4379 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4380 4380
4381 4381 if opts.get(b'bookmarks'):
4382 4382 source, branches = hg.parseurl(
4383 4383 ui.expandpath(source), opts.get(b'branch')
4384 4384 )
4385 4385 other = hg.peer(repo, opts, source)
4386 4386 if b'bookmarks' not in other.listkeys(b'namespaces'):
4387 4387 ui.warn(_(b"remote doesn't support bookmarks\n"))
4388 4388 return 0
4389 4389 ui.pager(b'incoming')
4390 4390 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4391 4391 return bookmarks.incoming(ui, repo, other)
4392 4392
4393 4393 repo._subtoppath = ui.expandpath(source)
4394 4394 try:
4395 4395 return hg.incoming(ui, repo, source, opts)
4396 4396 finally:
4397 4397 del repo._subtoppath
4398 4398
4399 4399
4400 4400 @command(
4401 4401 b'init',
4402 4402 remoteopts,
4403 4403 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4404 4404 helpcategory=command.CATEGORY_REPO_CREATION,
4405 4405 helpbasic=True,
4406 4406 norepo=True,
4407 4407 )
4408 4408 def init(ui, dest=b".", **opts):
4409 4409 """create a new repository in the given directory
4410 4410
4411 4411 Initialize a new repository in the given directory. If the given
4412 4412 directory does not exist, it will be created.
4413 4413
4414 4414 If no directory is given, the current directory is used.
4415 4415
4416 4416 It is possible to specify an ``ssh://`` URL as the destination.
4417 4417 See :hg:`help urls` for more information.
4418 4418
4419 4419 Returns 0 on success.
4420 4420 """
4421 4421 opts = pycompat.byteskwargs(opts)
4422 4422 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4423 4423
4424 4424
4425 4425 @command(
4426 4426 b'locate',
4427 4427 [
4428 4428 (
4429 4429 b'r',
4430 4430 b'rev',
4431 4431 b'',
4432 4432 _(b'search the repository as it is in REV'),
4433 4433 _(b'REV'),
4434 4434 ),
4435 4435 (
4436 4436 b'0',
4437 4437 b'print0',
4438 4438 None,
4439 4439 _(b'end filenames with NUL, for use with xargs'),
4440 4440 ),
4441 4441 (
4442 4442 b'f',
4443 4443 b'fullpath',
4444 4444 None,
4445 4445 _(b'print complete paths from the filesystem root'),
4446 4446 ),
4447 4447 ]
4448 4448 + walkopts,
4449 4449 _(b'[OPTION]... [PATTERN]...'),
4450 4450 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4451 4451 )
4452 4452 def locate(ui, repo, *pats, **opts):
4453 4453 """locate files matching specific patterns (DEPRECATED)
4454 4454
4455 4455 Print files under Mercurial control in the working directory whose
4456 4456 names match the given patterns.
4457 4457
4458 4458 By default, this command searches all directories in the working
4459 4459 directory. To search just the current directory and its
4460 4460 subdirectories, use "--include .".
4461 4461
4462 4462 If no patterns are given to match, this command prints the names
4463 4463 of all files under Mercurial control in the working directory.
4464 4464
4465 4465 If you want to feed the output of this command into the "xargs"
4466 4466 command, use the -0 option to both this command and "xargs". This
4467 4467 will avoid the problem of "xargs" treating single filenames that
4468 4468 contain whitespace as multiple filenames.
4469 4469
4470 4470 See :hg:`help files` for a more versatile command.
4471 4471
4472 4472 Returns 0 if a match is found, 1 otherwise.
4473 4473 """
4474 4474 opts = pycompat.byteskwargs(opts)
4475 4475 if opts.get(b'print0'):
4476 4476 end = b'\0'
4477 4477 else:
4478 4478 end = b'\n'
4479 4479 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4480 4480
4481 4481 ret = 1
4482 4482 m = scmutil.match(
4483 4483 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4484 4484 )
4485 4485
4486 4486 ui.pager(b'locate')
4487 4487 if ctx.rev() is None:
4488 4488 # When run on the working copy, "locate" includes removed files, so
4489 4489 # we get the list of files from the dirstate.
4490 4490 filesgen = sorted(repo.dirstate.matches(m))
4491 4491 else:
4492 4492 filesgen = ctx.matches(m)
4493 4493 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4494 4494 for abs in filesgen:
4495 4495 if opts.get(b'fullpath'):
4496 4496 ui.write(repo.wjoin(abs), end)
4497 4497 else:
4498 4498 ui.write(uipathfn(abs), end)
4499 4499 ret = 0
4500 4500
4501 4501 return ret
4502 4502
4503 4503
4504 4504 @command(
4505 4505 b'log|history',
4506 4506 [
4507 4507 (
4508 4508 b'f',
4509 4509 b'follow',
4510 4510 None,
4511 4511 _(
4512 4512 b'follow changeset history, or file history across copies and renames'
4513 4513 ),
4514 4514 ),
4515 4515 (
4516 4516 b'',
4517 4517 b'follow-first',
4518 4518 None,
4519 4519 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4520 4520 ),
4521 4521 (
4522 4522 b'd',
4523 4523 b'date',
4524 4524 b'',
4525 4525 _(b'show revisions matching date spec'),
4526 4526 _(b'DATE'),
4527 4527 ),
4528 4528 (b'C', b'copies', None, _(b'show copied files')),
4529 4529 (
4530 4530 b'k',
4531 4531 b'keyword',
4532 4532 [],
4533 4533 _(b'do case-insensitive search for a given text'),
4534 4534 _(b'TEXT'),
4535 4535 ),
4536 4536 (
4537 4537 b'r',
4538 4538 b'rev',
4539 4539 [],
4540 4540 _(b'show the specified revision or revset'),
4541 4541 _(b'REV'),
4542 4542 ),
4543 4543 (
4544 4544 b'L',
4545 4545 b'line-range',
4546 4546 [],
4547 4547 _(b'follow line range of specified file (EXPERIMENTAL)'),
4548 4548 _(b'FILE,RANGE'),
4549 4549 ),
4550 4550 (
4551 4551 b'',
4552 4552 b'removed',
4553 4553 None,
4554 4554 _(b'include revisions where files were removed'),
4555 4555 ),
4556 4556 (
4557 4557 b'm',
4558 4558 b'only-merges',
4559 4559 None,
4560 4560 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4561 4561 ),
4562 4562 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4563 4563 (
4564 4564 b'',
4565 4565 b'only-branch',
4566 4566 [],
4567 4567 _(
4568 4568 b'show only changesets within the given named branch (DEPRECATED)'
4569 4569 ),
4570 4570 _(b'BRANCH'),
4571 4571 ),
4572 4572 (
4573 4573 b'b',
4574 4574 b'branch',
4575 4575 [],
4576 4576 _(b'show changesets within the given named branch'),
4577 4577 _(b'BRANCH'),
4578 4578 ),
4579 4579 (
4580 4580 b'P',
4581 4581 b'prune',
4582 4582 [],
4583 4583 _(b'do not display revision or any of its ancestors'),
4584 4584 _(b'REV'),
4585 4585 ),
4586 4586 ]
4587 4587 + logopts
4588 4588 + walkopts,
4589 4589 _(b'[OPTION]... [FILE]'),
4590 4590 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4591 4591 helpbasic=True,
4592 4592 inferrepo=True,
4593 4593 intents={INTENT_READONLY},
4594 4594 )
4595 4595 def log(ui, repo, *pats, **opts):
4596 4596 """show revision history of entire repository or files
4597 4597
4598 4598 Print the revision history of the specified files or the entire
4599 4599 project.
4600 4600
4601 4601 If no revision range is specified, the default is ``tip:0`` unless
4602 4602 --follow is set, in which case the working directory parent is
4603 4603 used as the starting revision.
4604 4604
4605 4605 File history is shown without following rename or copy history of
4606 4606 files. Use -f/--follow with a filename to follow history across
4607 4607 renames and copies. --follow without a filename will only show
4608 4608 ancestors of the starting revision.
4609 4609
4610 4610 By default this command prints revision number and changeset id,
4611 4611 tags, non-trivial parents, user, date and time, and a summary for
4612 4612 each commit. When the -v/--verbose switch is used, the list of
4613 4613 changed files and full commit message are shown.
4614 4614
4615 4615 With --graph the revisions are shown as an ASCII art DAG with the most
4616 4616 recent changeset at the top.
4617 4617 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4618 4618 involved in an unresolved merge conflict, '_' closes a branch,
4619 4619 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4620 4620 changeset from the lines below is a parent of the 'o' merge on the same
4621 4621 line.
4622 4622 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4623 4623 of a '|' indicates one or more revisions in a path are omitted.
4624 4624
4625 4625 .. container:: verbose
4626 4626
4627 4627 Use -L/--line-range FILE,M:N options to follow the history of lines
4628 4628 from M to N in FILE. With -p/--patch only diff hunks affecting
4629 4629 specified line range will be shown. This option requires --follow;
4630 4630 it can be specified multiple times. Currently, this option is not
4631 4631 compatible with --graph. This option is experimental.
4632 4632
4633 4633 .. note::
4634 4634
4635 4635 :hg:`log --patch` may generate unexpected diff output for merge
4636 4636 changesets, as it will only compare the merge changeset against
4637 4637 its first parent. Also, only files different from BOTH parents
4638 4638 will appear in files:.
4639 4639
4640 4640 .. note::
4641 4641
4642 4642 For performance reasons, :hg:`log FILE` may omit duplicate changes
4643 4643 made on branches and will not show removals or mode changes. To
4644 4644 see all such changes, use the --removed switch.
4645 4645
4646 4646 .. container:: verbose
4647 4647
4648 4648 .. note::
4649 4649
4650 4650 The history resulting from -L/--line-range options depends on diff
4651 4651 options; for instance if white-spaces are ignored, respective changes
4652 4652 with only white-spaces in specified line range will not be listed.
4653 4653
4654 4654 .. container:: verbose
4655 4655
4656 4656 Some examples:
4657 4657
4658 4658 - changesets with full descriptions and file lists::
4659 4659
4660 4660 hg log -v
4661 4661
4662 4662 - changesets ancestral to the working directory::
4663 4663
4664 4664 hg log -f
4665 4665
4666 4666 - last 10 commits on the current branch::
4667 4667
4668 4668 hg log -l 10 -b .
4669 4669
4670 4670 - changesets showing all modifications of a file, including removals::
4671 4671
4672 4672 hg log --removed file.c
4673 4673
4674 4674 - all changesets that touch a directory, with diffs, excluding merges::
4675 4675
4676 4676 hg log -Mp lib/
4677 4677
4678 4678 - all revision numbers that match a keyword::
4679 4679
4680 4680 hg log -k bug --template "{rev}\\n"
4681 4681
4682 4682 - the full hash identifier of the working directory parent::
4683 4683
4684 4684 hg log -r . --template "{node}\\n"
4685 4685
4686 4686 - list available log templates::
4687 4687
4688 4688 hg log -T list
4689 4689
4690 4690 - check if a given changeset is included in a tagged release::
4691 4691
4692 4692 hg log -r "a21ccf and ancestor(1.9)"
4693 4693
4694 4694 - find all changesets by some user in a date range::
4695 4695
4696 4696 hg log -k alice -d "may 2008 to jul 2008"
4697 4697
4698 4698 - summary of all changesets after the last tag::
4699 4699
4700 4700 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4701 4701
4702 4702 - changesets touching lines 13 to 23 for file.c::
4703 4703
4704 4704 hg log -L file.c,13:23
4705 4705
4706 4706 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4707 4707 main.c with patch::
4708 4708
4709 4709 hg log -L file.c,13:23 -L main.c,2:6 -p
4710 4710
4711 4711 See :hg:`help dates` for a list of formats valid for -d/--date.
4712 4712
4713 4713 See :hg:`help revisions` for more about specifying and ordering
4714 4714 revisions.
4715 4715
4716 4716 See :hg:`help templates` for more about pre-packaged styles and
4717 4717 specifying custom templates. The default template used by the log
4718 4718 command can be customized via the ``ui.logtemplate`` configuration
4719 4719 setting.
4720 4720
4721 4721 Returns 0 on success.
4722 4722
4723 4723 """
4724 4724 opts = pycompat.byteskwargs(opts)
4725 4725 linerange = opts.get(b'line_range')
4726 4726
4727 4727 if linerange and not opts.get(b'follow'):
4728 4728 raise error.Abort(_(b'--line-range requires --follow'))
4729 4729
4730 4730 if linerange and pats:
4731 4731 # TODO: take pats as patterns with no line-range filter
4732 4732 raise error.Abort(
4733 4733 _(b'FILE arguments are not compatible with --line-range option')
4734 4734 )
4735 4735
4736 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 4740 if linerange:
4739 4741 # TODO: should follow file history from logcmdutil._initialrevs(),
4740 4742 # then filter the result by logcmdutil._makerevset() and --limit
4741 4743 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4742 4744
4743 4745 getcopies = None
4744 4746 if opts.get(b'copies'):
4745 4747 endrev = None
4746 4748 if revs:
4747 4749 endrev = revs.max() + 1
4748 4750 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4749 4751
4750 4752 ui.pager(b'log')
4751 4753 displayer = logcmdutil.changesetdisplayer(
4752 4754 ui, repo, opts, differ, buffered=True
4753 4755 )
4754 4756 if opts.get(b'graph'):
4755 4757 displayfn = logcmdutil.displaygraphrevs
4756 4758 else:
4757 4759 displayfn = logcmdutil.displayrevs
4758 4760 displayfn(ui, repo, revs, displayer, getcopies)
4759 4761
4760 4762
4761 4763 @command(
4762 4764 b'manifest',
4763 4765 [
4764 4766 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4765 4767 (b'', b'all', False, _(b"list files from all revisions")),
4766 4768 ]
4767 4769 + formatteropts,
4768 4770 _(b'[-r REV]'),
4769 4771 helpcategory=command.CATEGORY_MAINTENANCE,
4770 4772 intents={INTENT_READONLY},
4771 4773 )
4772 4774 def manifest(ui, repo, node=None, rev=None, **opts):
4773 4775 """output the current or given revision of the project manifest
4774 4776
4775 4777 Print a list of version controlled files for the given revision.
4776 4778 If no revision is given, the first parent of the working directory
4777 4779 is used, or the null revision if no revision is checked out.
4778 4780
4779 4781 With -v, print file permissions, symlink and executable bits.
4780 4782 With --debug, print file revision hashes.
4781 4783
4782 4784 If option --all is specified, the list of all files from all revisions
4783 4785 is printed. This includes deleted and renamed files.
4784 4786
4785 4787 Returns 0 on success.
4786 4788 """
4787 4789 opts = pycompat.byteskwargs(opts)
4788 4790 fm = ui.formatter(b'manifest', opts)
4789 4791
4790 4792 if opts.get(b'all'):
4791 4793 if rev or node:
4792 4794 raise error.Abort(_(b"can't specify a revision with --all"))
4793 4795
4794 4796 res = set()
4795 4797 for rev in repo:
4796 4798 ctx = repo[rev]
4797 4799 res |= set(ctx.files())
4798 4800
4799 4801 ui.pager(b'manifest')
4800 4802 for f in sorted(res):
4801 4803 fm.startitem()
4802 4804 fm.write(b"path", b'%s\n', f)
4803 4805 fm.end()
4804 4806 return
4805 4807
4806 4808 if rev and node:
4807 4809 raise error.Abort(_(b"please specify just one revision"))
4808 4810
4809 4811 if not node:
4810 4812 node = rev
4811 4813
4812 4814 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4813 4815 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4814 4816 if node:
4815 4817 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4816 4818 ctx = scmutil.revsingle(repo, node)
4817 4819 mf = ctx.manifest()
4818 4820 ui.pager(b'manifest')
4819 4821 for f in ctx:
4820 4822 fm.startitem()
4821 4823 fm.context(ctx=ctx)
4822 4824 fl = ctx[f].flags()
4823 4825 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4824 4826 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4825 4827 fm.write(b'path', b'%s\n', f)
4826 4828 fm.end()
4827 4829
4828 4830
4829 4831 @command(
4830 4832 b'merge',
4831 4833 [
4832 4834 (
4833 4835 b'f',
4834 4836 b'force',
4835 4837 None,
4836 4838 _(b'force a merge including outstanding changes (DEPRECATED)'),
4837 4839 ),
4838 4840 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4839 4841 (
4840 4842 b'P',
4841 4843 b'preview',
4842 4844 None,
4843 4845 _(b'review revisions to merge (no merge is performed)'),
4844 4846 ),
4845 4847 (b'', b'abort', None, _(b'abort the ongoing merge')),
4846 4848 ]
4847 4849 + mergetoolopts,
4848 4850 _(b'[-P] [[-r] REV]'),
4849 4851 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4850 4852 helpbasic=True,
4851 4853 )
4852 4854 def merge(ui, repo, node=None, **opts):
4853 4855 """merge another revision into working directory
4854 4856
4855 4857 The current working directory is updated with all changes made in
4856 4858 the requested revision since the last common predecessor revision.
4857 4859
4858 4860 Files that changed between either parent are marked as changed for
4859 4861 the next commit and a commit must be performed before any further
4860 4862 updates to the repository are allowed. The next commit will have
4861 4863 two parents.
4862 4864
4863 4865 ``--tool`` can be used to specify the merge tool used for file
4864 4866 merges. It overrides the HGMERGE environment variable and your
4865 4867 configuration files. See :hg:`help merge-tools` for options.
4866 4868
4867 4869 If no revision is specified, the working directory's parent is a
4868 4870 head revision, and the current branch contains exactly one other
4869 4871 head, the other head is merged with by default. Otherwise, an
4870 4872 explicit revision with which to merge must be provided.
4871 4873
4872 4874 See :hg:`help resolve` for information on handling file conflicts.
4873 4875
4874 4876 To undo an uncommitted merge, use :hg:`merge --abort` which
4875 4877 will check out a clean copy of the original merge parent, losing
4876 4878 all changes.
4877 4879
4878 4880 Returns 0 on success, 1 if there are unresolved files.
4879 4881 """
4880 4882
4881 4883 opts = pycompat.byteskwargs(opts)
4882 4884 abort = opts.get(b'abort')
4883 4885 if abort and repo.dirstate.p2() == nullid:
4884 4886 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4885 4887 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4886 4888 if abort:
4887 4889 state = cmdutil.getunfinishedstate(repo)
4888 4890 if state and state._opname != b'merge':
4889 4891 raise error.Abort(
4890 4892 _(b'cannot abort merge with %s in progress') % (state._opname),
4891 4893 hint=state.hint(),
4892 4894 )
4893 4895 if node:
4894 4896 raise error.Abort(_(b"cannot specify a node with --abort"))
4895 4897 return hg.abortmerge(repo.ui, repo)
4896 4898
4897 4899 if opts.get(b'rev') and node:
4898 4900 raise error.Abort(_(b"please specify just one revision"))
4899 4901 if not node:
4900 4902 node = opts.get(b'rev')
4901 4903
4902 4904 if node:
4903 4905 ctx = scmutil.revsingle(repo, node)
4904 4906 else:
4905 4907 if ui.configbool(b'commands', b'merge.require-rev'):
4906 4908 raise error.Abort(
4907 4909 _(
4908 4910 b'configuration requires specifying revision to merge '
4909 4911 b'with'
4910 4912 )
4911 4913 )
4912 4914 ctx = repo[destutil.destmerge(repo)]
4913 4915
4914 4916 if ctx.node() is None:
4915 4917 raise error.Abort(_(b'merging with the working copy has no effect'))
4916 4918
4917 4919 if opts.get(b'preview'):
4918 4920 # find nodes that are ancestors of p2 but not of p1
4919 4921 p1 = repo[b'.'].node()
4920 4922 p2 = ctx.node()
4921 4923 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4922 4924
4923 4925 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4924 4926 for node in nodes:
4925 4927 displayer.show(repo[node])
4926 4928 displayer.close()
4927 4929 return 0
4928 4930
4929 4931 # ui.forcemerge is an internal variable, do not document
4930 4932 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4931 4933 with ui.configoverride(overrides, b'merge'):
4932 4934 force = opts.get(b'force')
4933 4935 labels = [b'working copy', b'merge rev']
4934 4936 return hg.merge(ctx, force=force, labels=labels)
4935 4937
4936 4938
4937 4939 statemod.addunfinished(
4938 4940 b'merge',
4939 4941 fname=None,
4940 4942 clearable=True,
4941 4943 allowcommit=True,
4942 4944 cmdmsg=_(b'outstanding uncommitted merge'),
4943 4945 abortfunc=hg.abortmerge,
4944 4946 statushint=_(
4945 4947 b'To continue: hg commit\nTo abort: hg merge --abort'
4946 4948 ),
4947 4949 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4948 4950 )
4949 4951
4950 4952
4951 4953 @command(
4952 4954 b'outgoing|out',
4953 4955 [
4954 4956 (
4955 4957 b'f',
4956 4958 b'force',
4957 4959 None,
4958 4960 _(b'run even when the destination is unrelated'),
4959 4961 ),
4960 4962 (
4961 4963 b'r',
4962 4964 b'rev',
4963 4965 [],
4964 4966 _(b'a changeset intended to be included in the destination'),
4965 4967 _(b'REV'),
4966 4968 ),
4967 4969 (b'n', b'newest-first', None, _(b'show newest record first')),
4968 4970 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4969 4971 (
4970 4972 b'b',
4971 4973 b'branch',
4972 4974 [],
4973 4975 _(b'a specific branch you would like to push'),
4974 4976 _(b'BRANCH'),
4975 4977 ),
4976 4978 ]
4977 4979 + logopts
4978 4980 + remoteopts
4979 4981 + subrepoopts,
4980 4982 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4981 4983 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4982 4984 )
4983 4985 def outgoing(ui, repo, dest=None, **opts):
4984 4986 """show changesets not found in the destination
4985 4987
4986 4988 Show changesets not found in the specified destination repository
4987 4989 or the default push location. These are the changesets that would
4988 4990 be pushed if a push was requested.
4989 4991
4990 4992 See pull for details of valid destination formats.
4991 4993
4992 4994 .. container:: verbose
4993 4995
4994 4996 With -B/--bookmarks, the result of bookmark comparison between
4995 4997 local and remote repositories is displayed. With -v/--verbose,
4996 4998 status is also displayed for each bookmark like below::
4997 4999
4998 5000 BM1 01234567890a added
4999 5001 BM2 deleted
5000 5002 BM3 234567890abc advanced
5001 5003 BM4 34567890abcd diverged
5002 5004 BM5 4567890abcde changed
5003 5005
5004 5006 The action taken when pushing depends on the
5005 5007 status of each bookmark:
5006 5008
5007 5009 :``added``: push with ``-B`` will create it
5008 5010 :``deleted``: push with ``-B`` will delete it
5009 5011 :``advanced``: push will update it
5010 5012 :``diverged``: push with ``-B`` will update it
5011 5013 :``changed``: push with ``-B`` will update it
5012 5014
5013 5015 From the point of view of pushing behavior, bookmarks
5014 5016 existing only in the remote repository are treated as
5015 5017 ``deleted``, even if it is in fact added remotely.
5016 5018
5017 5019 Returns 0 if there are outgoing changes, 1 otherwise.
5018 5020 """
5019 5021 # hg._outgoing() needs to re-resolve the path in order to handle #branch
5020 5022 # style URLs, so don't overwrite dest.
5021 5023 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5022 5024 if not path:
5023 5025 raise error.Abort(
5024 5026 _(b'default repository not configured!'),
5025 5027 hint=_(b"see 'hg help config.paths'"),
5026 5028 )
5027 5029
5028 5030 opts = pycompat.byteskwargs(opts)
5029 5031 if opts.get(b'graph'):
5030 5032 logcmdutil.checkunsupportedgraphflags([], opts)
5031 5033 o, other = hg._outgoing(ui, repo, dest, opts)
5032 5034 if not o:
5033 5035 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5034 5036 return
5035 5037
5036 5038 revdag = logcmdutil.graphrevs(repo, o, opts)
5037 5039 ui.pager(b'outgoing')
5038 5040 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5039 5041 logcmdutil.displaygraph(
5040 5042 ui, repo, revdag, displayer, graphmod.asciiedges
5041 5043 )
5042 5044 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5043 5045 return 0
5044 5046
5045 5047 if opts.get(b'bookmarks'):
5046 5048 dest = path.pushloc or path.loc
5047 5049 other = hg.peer(repo, opts, dest)
5048 5050 if b'bookmarks' not in other.listkeys(b'namespaces'):
5049 5051 ui.warn(_(b"remote doesn't support bookmarks\n"))
5050 5052 return 0
5051 5053 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5052 5054 ui.pager(b'outgoing')
5053 5055 return bookmarks.outgoing(ui, repo, other)
5054 5056
5055 5057 repo._subtoppath = path.pushloc or path.loc
5056 5058 try:
5057 5059 return hg.outgoing(ui, repo, dest, opts)
5058 5060 finally:
5059 5061 del repo._subtoppath
5060 5062
5061 5063
5062 5064 @command(
5063 5065 b'parents',
5064 5066 [
5065 5067 (
5066 5068 b'r',
5067 5069 b'rev',
5068 5070 b'',
5069 5071 _(b'show parents of the specified revision'),
5070 5072 _(b'REV'),
5071 5073 ),
5072 5074 ]
5073 5075 + templateopts,
5074 5076 _(b'[-r REV] [FILE]'),
5075 5077 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5076 5078 inferrepo=True,
5077 5079 )
5078 5080 def parents(ui, repo, file_=None, **opts):
5079 5081 """show the parents of the working directory or revision (DEPRECATED)
5080 5082
5081 5083 Print the working directory's parent revisions. If a revision is
5082 5084 given via -r/--rev, the parent of that revision will be printed.
5083 5085 If a file argument is given, the revision in which the file was
5084 5086 last changed (before the working directory revision or the
5085 5087 argument to --rev if given) is printed.
5086 5088
5087 5089 This command is equivalent to::
5088 5090
5089 5091 hg log -r "p1()+p2()" or
5090 5092 hg log -r "p1(REV)+p2(REV)" or
5091 5093 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5092 5094 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5093 5095
5094 5096 See :hg:`summary` and :hg:`help revsets` for related information.
5095 5097
5096 5098 Returns 0 on success.
5097 5099 """
5098 5100
5099 5101 opts = pycompat.byteskwargs(opts)
5100 5102 rev = opts.get(b'rev')
5101 5103 if rev:
5102 5104 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5103 5105 ctx = scmutil.revsingle(repo, rev, None)
5104 5106
5105 5107 if file_:
5106 5108 m = scmutil.match(ctx, (file_,), opts)
5107 5109 if m.anypats() or len(m.files()) != 1:
5108 5110 raise error.Abort(_(b'can only specify an explicit filename'))
5109 5111 file_ = m.files()[0]
5110 5112 filenodes = []
5111 5113 for cp in ctx.parents():
5112 5114 if not cp:
5113 5115 continue
5114 5116 try:
5115 5117 filenodes.append(cp.filenode(file_))
5116 5118 except error.LookupError:
5117 5119 pass
5118 5120 if not filenodes:
5119 5121 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5120 5122 p = []
5121 5123 for fn in filenodes:
5122 5124 fctx = repo.filectx(file_, fileid=fn)
5123 5125 p.append(fctx.node())
5124 5126 else:
5125 5127 p = [cp.node() for cp in ctx.parents()]
5126 5128
5127 5129 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5128 5130 for n in p:
5129 5131 if n != nullid:
5130 5132 displayer.show(repo[n])
5131 5133 displayer.close()
5132 5134
5133 5135
5134 5136 @command(
5135 5137 b'paths',
5136 5138 formatteropts,
5137 5139 _(b'[NAME]'),
5138 5140 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5139 5141 optionalrepo=True,
5140 5142 intents={INTENT_READONLY},
5141 5143 )
5142 5144 def paths(ui, repo, search=None, **opts):
5143 5145 """show aliases for remote repositories
5144 5146
5145 5147 Show definition of symbolic path name NAME. If no name is given,
5146 5148 show definition of all available names.
5147 5149
5148 5150 Option -q/--quiet suppresses all output when searching for NAME
5149 5151 and shows only the path names when listing all definitions.
5150 5152
5151 5153 Path names are defined in the [paths] section of your
5152 5154 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5153 5155 repository, ``.hg/hgrc`` is used, too.
5154 5156
5155 5157 The path names ``default`` and ``default-push`` have a special
5156 5158 meaning. When performing a push or pull operation, they are used
5157 5159 as fallbacks if no location is specified on the command-line.
5158 5160 When ``default-push`` is set, it will be used for push and
5159 5161 ``default`` will be used for pull; otherwise ``default`` is used
5160 5162 as the fallback for both. When cloning a repository, the clone
5161 5163 source is written as ``default`` in ``.hg/hgrc``.
5162 5164
5163 5165 .. note::
5164 5166
5165 5167 ``default`` and ``default-push`` apply to all inbound (e.g.
5166 5168 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5167 5169 and :hg:`bundle`) operations.
5168 5170
5169 5171 See :hg:`help urls` for more information.
5170 5172
5171 5173 .. container:: verbose
5172 5174
5173 5175 Template:
5174 5176
5175 5177 The following keywords are supported. See also :hg:`help templates`.
5176 5178
5177 5179 :name: String. Symbolic name of the path alias.
5178 5180 :pushurl: String. URL for push operations.
5179 5181 :url: String. URL or directory path for the other operations.
5180 5182
5181 5183 Returns 0 on success.
5182 5184 """
5183 5185
5184 5186 opts = pycompat.byteskwargs(opts)
5185 5187 ui.pager(b'paths')
5186 5188 if search:
5187 5189 pathitems = [
5188 5190 (name, path)
5189 5191 for name, path in pycompat.iteritems(ui.paths)
5190 5192 if name == search
5191 5193 ]
5192 5194 else:
5193 5195 pathitems = sorted(pycompat.iteritems(ui.paths))
5194 5196
5195 5197 fm = ui.formatter(b'paths', opts)
5196 5198 if fm.isplain():
5197 5199 hidepassword = util.hidepassword
5198 5200 else:
5199 5201 hidepassword = bytes
5200 5202 if ui.quiet:
5201 5203 namefmt = b'%s\n'
5202 5204 else:
5203 5205 namefmt = b'%s = '
5204 5206 showsubopts = not search and not ui.quiet
5205 5207
5206 5208 for name, path in pathitems:
5207 5209 fm.startitem()
5208 5210 fm.condwrite(not search, b'name', namefmt, name)
5209 5211 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5210 5212 for subopt, value in sorted(path.suboptions.items()):
5211 5213 assert subopt not in (b'name', b'url')
5212 5214 if showsubopts:
5213 5215 fm.plain(b'%s:%s = ' % (name, subopt))
5214 5216 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5215 5217
5216 5218 fm.end()
5217 5219
5218 5220 if search and not pathitems:
5219 5221 if not ui.quiet:
5220 5222 ui.warn(_(b"not found!\n"))
5221 5223 return 1
5222 5224 else:
5223 5225 return 0
5224 5226
5225 5227
5226 5228 @command(
5227 5229 b'phase',
5228 5230 [
5229 5231 (b'p', b'public', False, _(b'set changeset phase to public')),
5230 5232 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5231 5233 (b's', b'secret', False, _(b'set changeset phase to secret')),
5232 5234 (b'f', b'force', False, _(b'allow to move boundary backward')),
5233 5235 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5234 5236 ],
5235 5237 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5236 5238 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5237 5239 )
5238 5240 def phase(ui, repo, *revs, **opts):
5239 5241 """set or show the current phase name
5240 5242
5241 5243 With no argument, show the phase name of the current revision(s).
5242 5244
5243 5245 With one of -p/--public, -d/--draft or -s/--secret, change the
5244 5246 phase value of the specified revisions.
5245 5247
5246 5248 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5247 5249 lower phase to a higher phase. Phases are ordered as follows::
5248 5250
5249 5251 public < draft < secret
5250 5252
5251 5253 Returns 0 on success, 1 if some phases could not be changed.
5252 5254
5253 5255 (For more information about the phases concept, see :hg:`help phases`.)
5254 5256 """
5255 5257 opts = pycompat.byteskwargs(opts)
5256 5258 # search for a unique phase argument
5257 5259 targetphase = None
5258 5260 for idx, name in enumerate(phases.cmdphasenames):
5259 5261 if opts[name]:
5260 5262 if targetphase is not None:
5261 5263 raise error.Abort(_(b'only one phase can be specified'))
5262 5264 targetphase = idx
5263 5265
5264 5266 # look for specified revision
5265 5267 revs = list(revs)
5266 5268 revs.extend(opts[b'rev'])
5267 5269 if not revs:
5268 5270 # display both parents as the second parent phase can influence
5269 5271 # the phase of a merge commit
5270 5272 revs = [c.rev() for c in repo[None].parents()]
5271 5273
5272 5274 revs = scmutil.revrange(repo, revs)
5273 5275
5274 5276 ret = 0
5275 5277 if targetphase is None:
5276 5278 # display
5277 5279 for r in revs:
5278 5280 ctx = repo[r]
5279 5281 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5280 5282 else:
5281 5283 with repo.lock(), repo.transaction(b"phase") as tr:
5282 5284 # set phase
5283 5285 if not revs:
5284 5286 raise error.Abort(_(b'empty revision set'))
5285 5287 nodes = [repo[r].node() for r in revs]
5286 5288 # moving revision from public to draft may hide them
5287 5289 # We have to check result on an unfiltered repository
5288 5290 unfi = repo.unfiltered()
5289 5291 getphase = unfi._phasecache.phase
5290 5292 olddata = [getphase(unfi, r) for r in unfi]
5291 5293 phases.advanceboundary(repo, tr, targetphase, nodes)
5292 5294 if opts[b'force']:
5293 5295 phases.retractboundary(repo, tr, targetphase, nodes)
5294 5296 getphase = unfi._phasecache.phase
5295 5297 newdata = [getphase(unfi, r) for r in unfi]
5296 5298 changes = sum(newdata[r] != olddata[r] for r in unfi)
5297 5299 cl = unfi.changelog
5298 5300 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5299 5301 if rejected:
5300 5302 ui.warn(
5301 5303 _(
5302 5304 b'cannot move %i changesets to a higher '
5303 5305 b'phase, use --force\n'
5304 5306 )
5305 5307 % len(rejected)
5306 5308 )
5307 5309 ret = 1
5308 5310 if changes:
5309 5311 msg = _(b'phase changed for %i changesets\n') % changes
5310 5312 if ret:
5311 5313 ui.status(msg)
5312 5314 else:
5313 5315 ui.note(msg)
5314 5316 else:
5315 5317 ui.warn(_(b'no phases changed\n'))
5316 5318 return ret
5317 5319
5318 5320
5319 5321 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5320 5322 """Run after a changegroup has been added via pull/unbundle
5321 5323
5322 5324 This takes arguments below:
5323 5325
5324 5326 :modheads: change of heads by pull/unbundle
5325 5327 :optupdate: updating working directory is needed or not
5326 5328 :checkout: update destination revision (or None to default destination)
5327 5329 :brev: a name, which might be a bookmark to be activated after updating
5328 5330 """
5329 5331 if modheads == 0:
5330 5332 return
5331 5333 if optupdate:
5332 5334 try:
5333 5335 return hg.updatetotally(ui, repo, checkout, brev)
5334 5336 except error.UpdateAbort as inst:
5335 5337 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5336 5338 hint = inst.hint
5337 5339 raise error.UpdateAbort(msg, hint=hint)
5338 5340 if modheads is not None and modheads > 1:
5339 5341 currentbranchheads = len(repo.branchheads())
5340 5342 if currentbranchheads == modheads:
5341 5343 ui.status(
5342 5344 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5343 5345 )
5344 5346 elif currentbranchheads > 1:
5345 5347 ui.status(
5346 5348 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5347 5349 )
5348 5350 else:
5349 5351 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5350 5352 elif not ui.configbool(b'commands', b'update.requiredest'):
5351 5353 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5352 5354
5353 5355
5354 5356 @command(
5355 5357 b'pull',
5356 5358 [
5357 5359 (
5358 5360 b'u',
5359 5361 b'update',
5360 5362 None,
5361 5363 _(b'update to new branch head if new descendants were pulled'),
5362 5364 ),
5363 5365 (
5364 5366 b'f',
5365 5367 b'force',
5366 5368 None,
5367 5369 _(b'run even when remote repository is unrelated'),
5368 5370 ),
5369 5371 (b'', b'confirm', None, _(b'confirm pull before applying changes'),),
5370 5372 (
5371 5373 b'r',
5372 5374 b'rev',
5373 5375 [],
5374 5376 _(b'a remote changeset intended to be added'),
5375 5377 _(b'REV'),
5376 5378 ),
5377 5379 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5378 5380 (
5379 5381 b'b',
5380 5382 b'branch',
5381 5383 [],
5382 5384 _(b'a specific branch you would like to pull'),
5383 5385 _(b'BRANCH'),
5384 5386 ),
5385 5387 ]
5386 5388 + remoteopts,
5387 5389 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5388 5390 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5389 5391 helpbasic=True,
5390 5392 )
5391 5393 def pull(ui, repo, source=b"default", **opts):
5392 5394 """pull changes from the specified source
5393 5395
5394 5396 Pull changes from a remote repository to a local one.
5395 5397
5396 5398 This finds all changes from the repository at the specified path
5397 5399 or URL and adds them to a local repository (the current one unless
5398 5400 -R is specified). By default, this does not update the copy of the
5399 5401 project in the working directory.
5400 5402
5401 5403 When cloning from servers that support it, Mercurial may fetch
5402 5404 pre-generated data. When this is done, hooks operating on incoming
5403 5405 changesets and changegroups may fire more than once, once for each
5404 5406 pre-generated bundle and as well as for any additional remaining
5405 5407 data. See :hg:`help -e clonebundles` for more.
5406 5408
5407 5409 Use :hg:`incoming` if you want to see what would have been added
5408 5410 by a pull at the time you issued this command. If you then decide
5409 5411 to add those changes to the repository, you should use :hg:`pull
5410 5412 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5411 5413
5412 5414 If SOURCE is omitted, the 'default' path will be used.
5413 5415 See :hg:`help urls` for more information.
5414 5416
5415 5417 Specifying bookmark as ``.`` is equivalent to specifying the active
5416 5418 bookmark's name.
5417 5419
5418 5420 Returns 0 on success, 1 if an update had unresolved files.
5419 5421 """
5420 5422
5421 5423 opts = pycompat.byteskwargs(opts)
5422 5424 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5423 5425 b'update'
5424 5426 ):
5425 5427 msg = _(b'update destination required by configuration')
5426 5428 hint = _(b'use hg pull followed by hg update DEST')
5427 5429 raise error.Abort(msg, hint=hint)
5428 5430
5429 5431 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5430 5432 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5431 5433 other = hg.peer(repo, opts, source)
5432 5434 try:
5433 5435 revs, checkout = hg.addbranchrevs(
5434 5436 repo, other, branches, opts.get(b'rev')
5435 5437 )
5436 5438
5437 5439 pullopargs = {}
5438 5440
5439 5441 nodes = None
5440 5442 if opts.get(b'bookmark') or revs:
5441 5443 # The list of bookmark used here is the same used to actually update
5442 5444 # the bookmark names, to avoid the race from issue 4689 and we do
5443 5445 # all lookup and bookmark queries in one go so they see the same
5444 5446 # version of the server state (issue 4700).
5445 5447 nodes = []
5446 5448 fnodes = []
5447 5449 revs = revs or []
5448 5450 if revs and not other.capable(b'lookup'):
5449 5451 err = _(
5450 5452 b"other repository doesn't support revision lookup, "
5451 5453 b"so a rev cannot be specified."
5452 5454 )
5453 5455 raise error.Abort(err)
5454 5456 with other.commandexecutor() as e:
5455 5457 fremotebookmarks = e.callcommand(
5456 5458 b'listkeys', {b'namespace': b'bookmarks'}
5457 5459 )
5458 5460 for r in revs:
5459 5461 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5460 5462 remotebookmarks = fremotebookmarks.result()
5461 5463 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5462 5464 pullopargs[b'remotebookmarks'] = remotebookmarks
5463 5465 for b in opts.get(b'bookmark', []):
5464 5466 b = repo._bookmarks.expandname(b)
5465 5467 if b not in remotebookmarks:
5466 5468 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5467 5469 nodes.append(remotebookmarks[b])
5468 5470 for i, rev in enumerate(revs):
5469 5471 node = fnodes[i].result()
5470 5472 nodes.append(node)
5471 5473 if rev == checkout:
5472 5474 checkout = node
5473 5475
5474 5476 wlock = util.nullcontextmanager()
5475 5477 if opts.get(b'update'):
5476 5478 wlock = repo.wlock()
5477 5479 with wlock:
5478 5480 pullopargs.update(opts.get(b'opargs', {}))
5479 5481 modheads = exchange.pull(
5480 5482 repo,
5481 5483 other,
5482 5484 heads=nodes,
5483 5485 force=opts.get(b'force'),
5484 5486 bookmarks=opts.get(b'bookmark', ()),
5485 5487 opargs=pullopargs,
5486 5488 confirm=opts.get(b'confirm'),
5487 5489 ).cgresult
5488 5490
5489 5491 # brev is a name, which might be a bookmark to be activated at
5490 5492 # the end of the update. In other words, it is an explicit
5491 5493 # destination of the update
5492 5494 brev = None
5493 5495
5494 5496 if checkout:
5495 5497 checkout = repo.unfiltered().changelog.rev(checkout)
5496 5498
5497 5499 # order below depends on implementation of
5498 5500 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5499 5501 # because 'checkout' is determined without it.
5500 5502 if opts.get(b'rev'):
5501 5503 brev = opts[b'rev'][0]
5502 5504 elif opts.get(b'branch'):
5503 5505 brev = opts[b'branch'][0]
5504 5506 else:
5505 5507 brev = branches[0]
5506 5508 repo._subtoppath = source
5507 5509 try:
5508 5510 ret = postincoming(
5509 5511 ui, repo, modheads, opts.get(b'update'), checkout, brev
5510 5512 )
5511 5513 except error.FilteredRepoLookupError as exc:
5512 5514 msg = _(b'cannot update to target: %s') % exc.args[0]
5513 5515 exc.args = (msg,) + exc.args[1:]
5514 5516 raise
5515 5517 finally:
5516 5518 del repo._subtoppath
5517 5519
5518 5520 finally:
5519 5521 other.close()
5520 5522 return ret
5521 5523
5522 5524
5523 5525 @command(
5524 5526 b'push',
5525 5527 [
5526 5528 (b'f', b'force', None, _(b'force push')),
5527 5529 (
5528 5530 b'r',
5529 5531 b'rev',
5530 5532 [],
5531 5533 _(b'a changeset intended to be included in the destination'),
5532 5534 _(b'REV'),
5533 5535 ),
5534 5536 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5535 5537 (
5536 5538 b'b',
5537 5539 b'branch',
5538 5540 [],
5539 5541 _(b'a specific branch you would like to push'),
5540 5542 _(b'BRANCH'),
5541 5543 ),
5542 5544 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5543 5545 (
5544 5546 b'',
5545 5547 b'pushvars',
5546 5548 [],
5547 5549 _(b'variables that can be sent to server (ADVANCED)'),
5548 5550 ),
5549 5551 (
5550 5552 b'',
5551 5553 b'publish',
5552 5554 False,
5553 5555 _(b'push the changeset as public (EXPERIMENTAL)'),
5554 5556 ),
5555 5557 ]
5556 5558 + remoteopts,
5557 5559 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5558 5560 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5559 5561 helpbasic=True,
5560 5562 )
5561 5563 def push(ui, repo, dest=None, **opts):
5562 5564 """push changes to the specified destination
5563 5565
5564 5566 Push changesets from the local repository to the specified
5565 5567 destination.
5566 5568
5567 5569 This operation is symmetrical to pull: it is identical to a pull
5568 5570 in the destination repository from the current one.
5569 5571
5570 5572 By default, push will not allow creation of new heads at the
5571 5573 destination, since multiple heads would make it unclear which head
5572 5574 to use. In this situation, it is recommended to pull and merge
5573 5575 before pushing.
5574 5576
5575 5577 Use --new-branch if you want to allow push to create a new named
5576 5578 branch that is not present at the destination. This allows you to
5577 5579 only create a new branch without forcing other changes.
5578 5580
5579 5581 .. note::
5580 5582
5581 5583 Extra care should be taken with the -f/--force option,
5582 5584 which will push all new heads on all branches, an action which will
5583 5585 almost always cause confusion for collaborators.
5584 5586
5585 5587 If -r/--rev is used, the specified revision and all its ancestors
5586 5588 will be pushed to the remote repository.
5587 5589
5588 5590 If -B/--bookmark is used, the specified bookmarked revision, its
5589 5591 ancestors, and the bookmark will be pushed to the remote
5590 5592 repository. Specifying ``.`` is equivalent to specifying the active
5591 5593 bookmark's name.
5592 5594
5593 5595 Please see :hg:`help urls` for important details about ``ssh://``
5594 5596 URLs. If DESTINATION is omitted, a default path will be used.
5595 5597
5596 5598 .. container:: verbose
5597 5599
5598 5600 The --pushvars option sends strings to the server that become
5599 5601 environment variables prepended with ``HG_USERVAR_``. For example,
5600 5602 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5601 5603 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5602 5604
5603 5605 pushvars can provide for user-overridable hooks as well as set debug
5604 5606 levels. One example is having a hook that blocks commits containing
5605 5607 conflict markers, but enables the user to override the hook if the file
5606 5608 is using conflict markers for testing purposes or the file format has
5607 5609 strings that look like conflict markers.
5608 5610
5609 5611 By default, servers will ignore `--pushvars`. To enable it add the
5610 5612 following to your configuration file::
5611 5613
5612 5614 [push]
5613 5615 pushvars.server = true
5614 5616
5615 5617 Returns 0 if push was successful, 1 if nothing to push.
5616 5618 """
5617 5619
5618 5620 opts = pycompat.byteskwargs(opts)
5619 5621 if opts.get(b'bookmark'):
5620 5622 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5621 5623 for b in opts[b'bookmark']:
5622 5624 # translate -B options to -r so changesets get pushed
5623 5625 b = repo._bookmarks.expandname(b)
5624 5626 if b in repo._bookmarks:
5625 5627 opts.setdefault(b'rev', []).append(b)
5626 5628 else:
5627 5629 # if we try to push a deleted bookmark, translate it to null
5628 5630 # this lets simultaneous -r, -b options continue working
5629 5631 opts.setdefault(b'rev', []).append(b"null")
5630 5632
5631 5633 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5632 5634 if not path:
5633 5635 raise error.Abort(
5634 5636 _(b'default repository not configured!'),
5635 5637 hint=_(b"see 'hg help config.paths'"),
5636 5638 )
5637 5639 dest = path.pushloc or path.loc
5638 5640 branches = (path.branch, opts.get(b'branch') or [])
5639 5641 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5640 5642 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5641 5643 other = hg.peer(repo, opts, dest)
5642 5644
5643 5645 if revs:
5644 5646 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5645 5647 if not revs:
5646 5648 raise error.Abort(
5647 5649 _(b"specified revisions evaluate to an empty set"),
5648 5650 hint=_(b"use different revision arguments"),
5649 5651 )
5650 5652 elif path.pushrev:
5651 5653 # It doesn't make any sense to specify ancestor revisions. So limit
5652 5654 # to DAG heads to make discovery simpler.
5653 5655 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5654 5656 revs = scmutil.revrange(repo, [expr])
5655 5657 revs = [repo[rev].node() for rev in revs]
5656 5658 if not revs:
5657 5659 raise error.Abort(
5658 5660 _(b'default push revset for path evaluates to an empty set')
5659 5661 )
5660 5662 elif ui.configbool(b'commands', b'push.require-revs'):
5661 5663 raise error.Abort(
5662 5664 _(b'no revisions specified to push'),
5663 5665 hint=_(b'did you mean "hg push -r ."?'),
5664 5666 )
5665 5667
5666 5668 repo._subtoppath = dest
5667 5669 try:
5668 5670 # push subrepos depth-first for coherent ordering
5669 5671 c = repo[b'.']
5670 5672 subs = c.substate # only repos that are committed
5671 5673 for s in sorted(subs):
5672 5674 result = c.sub(s).push(opts)
5673 5675 if result == 0:
5674 5676 return not result
5675 5677 finally:
5676 5678 del repo._subtoppath
5677 5679
5678 5680 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5679 5681 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5680 5682
5681 5683 pushop = exchange.push(
5682 5684 repo,
5683 5685 other,
5684 5686 opts.get(b'force'),
5685 5687 revs=revs,
5686 5688 newbranch=opts.get(b'new_branch'),
5687 5689 bookmarks=opts.get(b'bookmark', ()),
5688 5690 publish=opts.get(b'publish'),
5689 5691 opargs=opargs,
5690 5692 )
5691 5693
5692 5694 result = not pushop.cgresult
5693 5695
5694 5696 if pushop.bkresult is not None:
5695 5697 if pushop.bkresult == 2:
5696 5698 result = 2
5697 5699 elif not result and pushop.bkresult:
5698 5700 result = 2
5699 5701
5700 5702 return result
5701 5703
5702 5704
5703 5705 @command(
5704 5706 b'recover',
5705 5707 [(b'', b'verify', False, b"run `hg verify` after successful recover"),],
5706 5708 helpcategory=command.CATEGORY_MAINTENANCE,
5707 5709 )
5708 5710 def recover(ui, repo, **opts):
5709 5711 """roll back an interrupted transaction
5710 5712
5711 5713 Recover from an interrupted commit or pull.
5712 5714
5713 5715 This command tries to fix the repository status after an
5714 5716 interrupted operation. It should only be necessary when Mercurial
5715 5717 suggests it.
5716 5718
5717 5719 Returns 0 if successful, 1 if nothing to recover or verify fails.
5718 5720 """
5719 5721 ret = repo.recover()
5720 5722 if ret:
5721 5723 if opts['verify']:
5722 5724 return hg.verify(repo)
5723 5725 else:
5724 5726 msg = _(
5725 5727 b"(verify step skipped, run `hg verify` to check your "
5726 5728 b"repository content)\n"
5727 5729 )
5728 5730 ui.warn(msg)
5729 5731 return 0
5730 5732 return 1
5731 5733
5732 5734
5733 5735 @command(
5734 5736 b'remove|rm',
5735 5737 [
5736 5738 (b'A', b'after', None, _(b'record delete for missing files')),
5737 5739 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5738 5740 ]
5739 5741 + subrepoopts
5740 5742 + walkopts
5741 5743 + dryrunopts,
5742 5744 _(b'[OPTION]... FILE...'),
5743 5745 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5744 5746 helpbasic=True,
5745 5747 inferrepo=True,
5746 5748 )
5747 5749 def remove(ui, repo, *pats, **opts):
5748 5750 """remove the specified files on the next commit
5749 5751
5750 5752 Schedule the indicated files for removal from the current branch.
5751 5753
5752 5754 This command schedules the files to be removed at the next commit.
5753 5755 To undo a remove before that, see :hg:`revert`. To undo added
5754 5756 files, see :hg:`forget`.
5755 5757
5756 5758 .. container:: verbose
5757 5759
5758 5760 -A/--after can be used to remove only files that have already
5759 5761 been deleted, -f/--force can be used to force deletion, and -Af
5760 5762 can be used to remove files from the next revision without
5761 5763 deleting them from the working directory.
5762 5764
5763 5765 The following table details the behavior of remove for different
5764 5766 file states (columns) and option combinations (rows). The file
5765 5767 states are Added [A], Clean [C], Modified [M] and Missing [!]
5766 5768 (as reported by :hg:`status`). The actions are Warn, Remove
5767 5769 (from branch) and Delete (from disk):
5768 5770
5769 5771 ========= == == == ==
5770 5772 opt/state A C M !
5771 5773 ========= == == == ==
5772 5774 none W RD W R
5773 5775 -f R RD RD R
5774 5776 -A W W W R
5775 5777 -Af R R R R
5776 5778 ========= == == == ==
5777 5779
5778 5780 .. note::
5779 5781
5780 5782 :hg:`remove` never deletes files in Added [A] state from the
5781 5783 working directory, not even if ``--force`` is specified.
5782 5784
5783 5785 Returns 0 on success, 1 if any warnings encountered.
5784 5786 """
5785 5787
5786 5788 opts = pycompat.byteskwargs(opts)
5787 5789 after, force = opts.get(b'after'), opts.get(b'force')
5788 5790 dryrun = opts.get(b'dry_run')
5789 5791 if not pats and not after:
5790 5792 raise error.Abort(_(b'no files specified'))
5791 5793
5792 5794 m = scmutil.match(repo[None], pats, opts)
5793 5795 subrepos = opts.get(b'subrepos')
5794 5796 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5795 5797 return cmdutil.remove(
5796 5798 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5797 5799 )
5798 5800
5799 5801
5800 5802 @command(
5801 5803 b'rename|move|mv',
5802 5804 [
5803 5805 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5804 5806 (
5805 5807 b'',
5806 5808 b'at-rev',
5807 5809 b'',
5808 5810 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5809 5811 _(b'REV'),
5810 5812 ),
5811 5813 (
5812 5814 b'f',
5813 5815 b'force',
5814 5816 None,
5815 5817 _(b'forcibly move over an existing managed file'),
5816 5818 ),
5817 5819 ]
5818 5820 + walkopts
5819 5821 + dryrunopts,
5820 5822 _(b'[OPTION]... SOURCE... DEST'),
5821 5823 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5822 5824 )
5823 5825 def rename(ui, repo, *pats, **opts):
5824 5826 """rename files; equivalent of copy + remove
5825 5827
5826 5828 Mark dest as copies of sources; mark sources for deletion. If dest
5827 5829 is a directory, copies are put in that directory. If dest is a
5828 5830 file, there can only be one source.
5829 5831
5830 5832 By default, this command copies the contents of files as they
5831 5833 exist in the working directory. If invoked with -A/--after, the
5832 5834 operation is recorded, but no copying is performed.
5833 5835
5834 5836 This command takes effect at the next commit. To undo a rename
5835 5837 before that, see :hg:`revert`.
5836 5838
5837 5839 Returns 0 on success, 1 if errors are encountered.
5838 5840 """
5839 5841 opts = pycompat.byteskwargs(opts)
5840 5842 with repo.wlock():
5841 5843 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5842 5844
5843 5845
5844 5846 @command(
5845 5847 b'resolve',
5846 5848 [
5847 5849 (b'a', b'all', None, _(b'select all unresolved files')),
5848 5850 (b'l', b'list', None, _(b'list state of files needing merge')),
5849 5851 (b'm', b'mark', None, _(b'mark files as resolved')),
5850 5852 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5851 5853 (b'n', b'no-status', None, _(b'hide status prefix')),
5852 5854 (b'', b're-merge', None, _(b're-merge files')),
5853 5855 ]
5854 5856 + mergetoolopts
5855 5857 + walkopts
5856 5858 + formatteropts,
5857 5859 _(b'[OPTION]... [FILE]...'),
5858 5860 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5859 5861 inferrepo=True,
5860 5862 )
5861 5863 def resolve(ui, repo, *pats, **opts):
5862 5864 """redo merges or set/view the merge status of files
5863 5865
5864 5866 Merges with unresolved conflicts are often the result of
5865 5867 non-interactive merging using the ``internal:merge`` configuration
5866 5868 setting, or a command-line merge tool like ``diff3``. The resolve
5867 5869 command is used to manage the files involved in a merge, after
5868 5870 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5869 5871 working directory must have two parents). See :hg:`help
5870 5872 merge-tools` for information on configuring merge tools.
5871 5873
5872 5874 The resolve command can be used in the following ways:
5873 5875
5874 5876 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5875 5877 the specified files, discarding any previous merge attempts. Re-merging
5876 5878 is not performed for files already marked as resolved. Use ``--all/-a``
5877 5879 to select all unresolved files. ``--tool`` can be used to specify
5878 5880 the merge tool used for the given files. It overrides the HGMERGE
5879 5881 environment variable and your configuration files. Previous file
5880 5882 contents are saved with a ``.orig`` suffix.
5881 5883
5882 5884 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5883 5885 (e.g. after having manually fixed-up the files). The default is
5884 5886 to mark all unresolved files.
5885 5887
5886 5888 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5887 5889 default is to mark all resolved files.
5888 5890
5889 5891 - :hg:`resolve -l`: list files which had or still have conflicts.
5890 5892 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5891 5893 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5892 5894 the list. See :hg:`help filesets` for details.
5893 5895
5894 5896 .. note::
5895 5897
5896 5898 Mercurial will not let you commit files with unresolved merge
5897 5899 conflicts. You must use :hg:`resolve -m ...` before you can
5898 5900 commit after a conflicting merge.
5899 5901
5900 5902 .. container:: verbose
5901 5903
5902 5904 Template:
5903 5905
5904 5906 The following keywords are supported in addition to the common template
5905 5907 keywords and functions. See also :hg:`help templates`.
5906 5908
5907 5909 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5908 5910 :path: String. Repository-absolute path of the file.
5909 5911
5910 5912 Returns 0 on success, 1 if any files fail a resolve attempt.
5911 5913 """
5912 5914
5913 5915 opts = pycompat.byteskwargs(opts)
5914 5916 confirm = ui.configbool(b'commands', b'resolve.confirm')
5915 5917 flaglist = b'all mark unmark list no_status re_merge'.split()
5916 5918 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5917 5919
5918 5920 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5919 5921 if actioncount > 1:
5920 5922 raise error.Abort(_(b"too many actions specified"))
5921 5923 elif actioncount == 0 and ui.configbool(
5922 5924 b'commands', b'resolve.explicit-re-merge'
5923 5925 ):
5924 5926 hint = _(b'use --mark, --unmark, --list or --re-merge')
5925 5927 raise error.Abort(_(b'no action specified'), hint=hint)
5926 5928 if pats and all:
5927 5929 raise error.Abort(_(b"can't specify --all and patterns"))
5928 5930 if not (all or pats or show or mark or unmark):
5929 5931 raise error.Abort(
5930 5932 _(b'no files or directories specified'),
5931 5933 hint=b'use --all to re-merge all unresolved files',
5932 5934 )
5933 5935
5934 5936 if confirm:
5935 5937 if all:
5936 5938 if ui.promptchoice(
5937 5939 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5938 5940 ):
5939 5941 raise error.Abort(_(b'user quit'))
5940 5942 if mark and not pats:
5941 5943 if ui.promptchoice(
5942 5944 _(
5943 5945 b'mark all unresolved files as resolved (yn)?'
5944 5946 b'$$ &Yes $$ &No'
5945 5947 )
5946 5948 ):
5947 5949 raise error.Abort(_(b'user quit'))
5948 5950 if unmark and not pats:
5949 5951 if ui.promptchoice(
5950 5952 _(
5951 5953 b'mark all resolved files as unresolved (yn)?'
5952 5954 b'$$ &Yes $$ &No'
5953 5955 )
5954 5956 ):
5955 5957 raise error.Abort(_(b'user quit'))
5956 5958
5957 5959 uipathfn = scmutil.getuipathfn(repo)
5958 5960
5959 5961 if show:
5960 5962 ui.pager(b'resolve')
5961 5963 fm = ui.formatter(b'resolve', opts)
5962 5964 ms = mergestatemod.mergestate.read(repo)
5963 5965 wctx = repo[None]
5964 5966 m = scmutil.match(wctx, pats, opts)
5965 5967
5966 5968 # Labels and keys based on merge state. Unresolved path conflicts show
5967 5969 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5968 5970 # resolved conflicts.
5969 5971 mergestateinfo = {
5970 5972 mergestatemod.MERGE_RECORD_UNRESOLVED: (
5971 5973 b'resolve.unresolved',
5972 5974 b'U',
5973 5975 ),
5974 5976 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5975 5977 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
5976 5978 b'resolve.unresolved',
5977 5979 b'P',
5978 5980 ),
5979 5981 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
5980 5982 b'resolve.resolved',
5981 5983 b'R',
5982 5984 ),
5983 5985 }
5984 5986
5985 5987 for f in ms:
5986 5988 if not m(f):
5987 5989 continue
5988 5990
5989 5991 label, key = mergestateinfo[ms[f]]
5990 5992 fm.startitem()
5991 5993 fm.context(ctx=wctx)
5992 5994 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5993 5995 fm.data(path=f)
5994 5996 fm.plain(b'%s\n' % uipathfn(f), label=label)
5995 5997 fm.end()
5996 5998 return 0
5997 5999
5998 6000 with repo.wlock():
5999 6001 ms = mergestatemod.mergestate.read(repo)
6000 6002
6001 6003 if not (ms.active() or repo.dirstate.p2() != nullid):
6002 6004 raise error.Abort(
6003 6005 _(b'resolve command not applicable when not merging')
6004 6006 )
6005 6007
6006 6008 wctx = repo[None]
6007 6009 m = scmutil.match(wctx, pats, opts)
6008 6010 ret = 0
6009 6011 didwork = False
6010 6012
6011 6013 tocomplete = []
6012 6014 hasconflictmarkers = []
6013 6015 if mark:
6014 6016 markcheck = ui.config(b'commands', b'resolve.mark-check')
6015 6017 if markcheck not in [b'warn', b'abort']:
6016 6018 # Treat all invalid / unrecognized values as 'none'.
6017 6019 markcheck = False
6018 6020 for f in ms:
6019 6021 if not m(f):
6020 6022 continue
6021 6023
6022 6024 didwork = True
6023 6025
6024 6026 # path conflicts must be resolved manually
6025 6027 if ms[f] in (
6026 6028 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6027 6029 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6028 6030 ):
6029 6031 if mark:
6030 6032 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6031 6033 elif unmark:
6032 6034 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6033 6035 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6034 6036 ui.warn(
6035 6037 _(b'%s: path conflict must be resolved manually\n')
6036 6038 % uipathfn(f)
6037 6039 )
6038 6040 continue
6039 6041
6040 6042 if mark:
6041 6043 if markcheck:
6042 6044 fdata = repo.wvfs.tryread(f)
6043 6045 if (
6044 6046 filemerge.hasconflictmarkers(fdata)
6045 6047 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6046 6048 ):
6047 6049 hasconflictmarkers.append(f)
6048 6050 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6049 6051 elif unmark:
6050 6052 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6051 6053 else:
6052 6054 # backup pre-resolve (merge uses .orig for its own purposes)
6053 6055 a = repo.wjoin(f)
6054 6056 try:
6055 6057 util.copyfile(a, a + b".resolve")
6056 6058 except (IOError, OSError) as inst:
6057 6059 if inst.errno != errno.ENOENT:
6058 6060 raise
6059 6061
6060 6062 try:
6061 6063 # preresolve file
6062 6064 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6063 6065 with ui.configoverride(overrides, b'resolve'):
6064 6066 complete, r = ms.preresolve(f, wctx)
6065 6067 if not complete:
6066 6068 tocomplete.append(f)
6067 6069 elif r:
6068 6070 ret = 1
6069 6071 finally:
6070 6072 ms.commit()
6071 6073
6072 6074 # replace filemerge's .orig file with our resolve file, but only
6073 6075 # for merges that are complete
6074 6076 if complete:
6075 6077 try:
6076 6078 util.rename(
6077 6079 a + b".resolve", scmutil.backuppath(ui, repo, f)
6078 6080 )
6079 6081 except OSError as inst:
6080 6082 if inst.errno != errno.ENOENT:
6081 6083 raise
6082 6084
6083 6085 if hasconflictmarkers:
6084 6086 ui.warn(
6085 6087 _(
6086 6088 b'warning: the following files still have conflict '
6087 6089 b'markers:\n'
6088 6090 )
6089 6091 + b''.join(
6090 6092 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6091 6093 )
6092 6094 )
6093 6095 if markcheck == b'abort' and not all and not pats:
6094 6096 raise error.Abort(
6095 6097 _(b'conflict markers detected'),
6096 6098 hint=_(b'use --all to mark anyway'),
6097 6099 )
6098 6100
6099 6101 for f in tocomplete:
6100 6102 try:
6101 6103 # resolve file
6102 6104 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6103 6105 with ui.configoverride(overrides, b'resolve'):
6104 6106 r = ms.resolve(f, wctx)
6105 6107 if r:
6106 6108 ret = 1
6107 6109 finally:
6108 6110 ms.commit()
6109 6111
6110 6112 # replace filemerge's .orig file with our resolve file
6111 6113 a = repo.wjoin(f)
6112 6114 try:
6113 6115 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6114 6116 except OSError as inst:
6115 6117 if inst.errno != errno.ENOENT:
6116 6118 raise
6117 6119
6118 6120 ms.commit()
6119 6121 branchmerge = repo.dirstate.p2() != nullid
6120 6122 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6121 6123
6122 6124 if not didwork and pats:
6123 6125 hint = None
6124 6126 if not any([p for p in pats if p.find(b':') >= 0]):
6125 6127 pats = [b'path:%s' % p for p in pats]
6126 6128 m = scmutil.match(wctx, pats, opts)
6127 6129 for f in ms:
6128 6130 if not m(f):
6129 6131 continue
6130 6132
6131 6133 def flag(o):
6132 6134 if o == b're_merge':
6133 6135 return b'--re-merge '
6134 6136 return b'-%s ' % o[0:1]
6135 6137
6136 6138 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6137 6139 hint = _(b"(try: hg resolve %s%s)\n") % (
6138 6140 flags,
6139 6141 b' '.join(pats),
6140 6142 )
6141 6143 break
6142 6144 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6143 6145 if hint:
6144 6146 ui.warn(hint)
6145 6147
6146 6148 unresolvedf = list(ms.unresolved())
6147 6149 if not unresolvedf:
6148 6150 ui.status(_(b'(no more unresolved files)\n'))
6149 6151 cmdutil.checkafterresolved(repo)
6150 6152
6151 6153 return ret
6152 6154
6153 6155
6154 6156 @command(
6155 6157 b'revert',
6156 6158 [
6157 6159 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6158 6160 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6159 6161 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6160 6162 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6161 6163 (b'i', b'interactive', None, _(b'interactively select the changes')),
6162 6164 ]
6163 6165 + walkopts
6164 6166 + dryrunopts,
6165 6167 _(b'[OPTION]... [-r REV] [NAME]...'),
6166 6168 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6167 6169 )
6168 6170 def revert(ui, repo, *pats, **opts):
6169 6171 """restore files to their checkout state
6170 6172
6171 6173 .. note::
6172 6174
6173 6175 To check out earlier revisions, you should use :hg:`update REV`.
6174 6176 To cancel an uncommitted merge (and lose your changes),
6175 6177 use :hg:`merge --abort`.
6176 6178
6177 6179 With no revision specified, revert the specified files or directories
6178 6180 to the contents they had in the parent of the working directory.
6179 6181 This restores the contents of files to an unmodified
6180 6182 state and unschedules adds, removes, copies, and renames. If the
6181 6183 working directory has two parents, you must explicitly specify a
6182 6184 revision.
6183 6185
6184 6186 Using the -r/--rev or -d/--date options, revert the given files or
6185 6187 directories to their states as of a specific revision. Because
6186 6188 revert does not change the working directory parents, this will
6187 6189 cause these files to appear modified. This can be helpful to "back
6188 6190 out" some or all of an earlier change. See :hg:`backout` for a
6189 6191 related method.
6190 6192
6191 6193 Modified files are saved with a .orig suffix before reverting.
6192 6194 To disable these backups, use --no-backup. It is possible to store
6193 6195 the backup files in a custom directory relative to the root of the
6194 6196 repository by setting the ``ui.origbackuppath`` configuration
6195 6197 option.
6196 6198
6197 6199 See :hg:`help dates` for a list of formats valid for -d/--date.
6198 6200
6199 6201 See :hg:`help backout` for a way to reverse the effect of an
6200 6202 earlier changeset.
6201 6203
6202 6204 Returns 0 on success.
6203 6205 """
6204 6206
6205 6207 opts = pycompat.byteskwargs(opts)
6206 6208 if opts.get(b"date"):
6207 6209 if opts.get(b"rev"):
6208 6210 raise error.Abort(_(b"you can't specify a revision and a date"))
6209 6211 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6210 6212
6211 6213 parent, p2 = repo.dirstate.parents()
6212 6214 if not opts.get(b'rev') and p2 != nullid:
6213 6215 # revert after merge is a trap for new users (issue2915)
6214 6216 raise error.Abort(
6215 6217 _(b'uncommitted merge with no revision specified'),
6216 6218 hint=_(b"use 'hg update' or see 'hg help revert'"),
6217 6219 )
6218 6220
6219 6221 rev = opts.get(b'rev')
6220 6222 if rev:
6221 6223 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6222 6224 ctx = scmutil.revsingle(repo, rev)
6223 6225
6224 6226 if not (
6225 6227 pats
6226 6228 or opts.get(b'include')
6227 6229 or opts.get(b'exclude')
6228 6230 or opts.get(b'all')
6229 6231 or opts.get(b'interactive')
6230 6232 ):
6231 6233 msg = _(b"no files or directories specified")
6232 6234 if p2 != nullid:
6233 6235 hint = _(
6234 6236 b"uncommitted merge, use --all to discard all changes,"
6235 6237 b" or 'hg update -C .' to abort the merge"
6236 6238 )
6237 6239 raise error.Abort(msg, hint=hint)
6238 6240 dirty = any(repo.status())
6239 6241 node = ctx.node()
6240 6242 if node != parent:
6241 6243 if dirty:
6242 6244 hint = (
6243 6245 _(
6244 6246 b"uncommitted changes, use --all to discard all"
6245 6247 b" changes, or 'hg update %d' to update"
6246 6248 )
6247 6249 % ctx.rev()
6248 6250 )
6249 6251 else:
6250 6252 hint = (
6251 6253 _(
6252 6254 b"use --all to revert all files,"
6253 6255 b" or 'hg update %d' to update"
6254 6256 )
6255 6257 % ctx.rev()
6256 6258 )
6257 6259 elif dirty:
6258 6260 hint = _(b"uncommitted changes, use --all to discard all changes")
6259 6261 else:
6260 6262 hint = _(b"use --all to revert all files")
6261 6263 raise error.Abort(msg, hint=hint)
6262 6264
6263 6265 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6264 6266
6265 6267
6266 6268 @command(
6267 6269 b'rollback',
6268 6270 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6269 6271 helpcategory=command.CATEGORY_MAINTENANCE,
6270 6272 )
6271 6273 def rollback(ui, repo, **opts):
6272 6274 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6273 6275
6274 6276 Please use :hg:`commit --amend` instead of rollback to correct
6275 6277 mistakes in the last commit.
6276 6278
6277 6279 This command should be used with care. There is only one level of
6278 6280 rollback, and there is no way to undo a rollback. It will also
6279 6281 restore the dirstate at the time of the last transaction, losing
6280 6282 any dirstate changes since that time. This command does not alter
6281 6283 the working directory.
6282 6284
6283 6285 Transactions are used to encapsulate the effects of all commands
6284 6286 that create new changesets or propagate existing changesets into a
6285 6287 repository.
6286 6288
6287 6289 .. container:: verbose
6288 6290
6289 6291 For example, the following commands are transactional, and their
6290 6292 effects can be rolled back:
6291 6293
6292 6294 - commit
6293 6295 - import
6294 6296 - pull
6295 6297 - push (with this repository as the destination)
6296 6298 - unbundle
6297 6299
6298 6300 To avoid permanent data loss, rollback will refuse to rollback a
6299 6301 commit transaction if it isn't checked out. Use --force to
6300 6302 override this protection.
6301 6303
6302 6304 The rollback command can be entirely disabled by setting the
6303 6305 ``ui.rollback`` configuration setting to false. If you're here
6304 6306 because you want to use rollback and it's disabled, you can
6305 6307 re-enable the command by setting ``ui.rollback`` to true.
6306 6308
6307 6309 This command is not intended for use on public repositories. Once
6308 6310 changes are visible for pull by other users, rolling a transaction
6309 6311 back locally is ineffective (someone else may already have pulled
6310 6312 the changes). Furthermore, a race is possible with readers of the
6311 6313 repository; for example an in-progress pull from the repository
6312 6314 may fail if a rollback is performed.
6313 6315
6314 6316 Returns 0 on success, 1 if no rollback data is available.
6315 6317 """
6316 6318 if not ui.configbool(b'ui', b'rollback'):
6317 6319 raise error.Abort(
6318 6320 _(b'rollback is disabled because it is unsafe'),
6319 6321 hint=b'see `hg help -v rollback` for information',
6320 6322 )
6321 6323 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6322 6324
6323 6325
6324 6326 @command(
6325 6327 b'root',
6326 6328 [] + formatteropts,
6327 6329 intents={INTENT_READONLY},
6328 6330 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6329 6331 )
6330 6332 def root(ui, repo, **opts):
6331 6333 """print the root (top) of the current working directory
6332 6334
6333 6335 Print the root directory of the current repository.
6334 6336
6335 6337 .. container:: verbose
6336 6338
6337 6339 Template:
6338 6340
6339 6341 The following keywords are supported in addition to the common template
6340 6342 keywords and functions. See also :hg:`help templates`.
6341 6343
6342 6344 :hgpath: String. Path to the .hg directory.
6343 6345 :storepath: String. Path to the directory holding versioned data.
6344 6346
6345 6347 Returns 0 on success.
6346 6348 """
6347 6349 opts = pycompat.byteskwargs(opts)
6348 6350 with ui.formatter(b'root', opts) as fm:
6349 6351 fm.startitem()
6350 6352 fm.write(b'reporoot', b'%s\n', repo.root)
6351 6353 fm.data(hgpath=repo.path, storepath=repo.spath)
6352 6354
6353 6355
6354 6356 @command(
6355 6357 b'serve',
6356 6358 [
6357 6359 (
6358 6360 b'A',
6359 6361 b'accesslog',
6360 6362 b'',
6361 6363 _(b'name of access log file to write to'),
6362 6364 _(b'FILE'),
6363 6365 ),
6364 6366 (b'd', b'daemon', None, _(b'run server in background')),
6365 6367 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6366 6368 (
6367 6369 b'E',
6368 6370 b'errorlog',
6369 6371 b'',
6370 6372 _(b'name of error log file to write to'),
6371 6373 _(b'FILE'),
6372 6374 ),
6373 6375 # use string type, then we can check if something was passed
6374 6376 (
6375 6377 b'p',
6376 6378 b'port',
6377 6379 b'',
6378 6380 _(b'port to listen on (default: 8000)'),
6379 6381 _(b'PORT'),
6380 6382 ),
6381 6383 (
6382 6384 b'a',
6383 6385 b'address',
6384 6386 b'',
6385 6387 _(b'address to listen on (default: all interfaces)'),
6386 6388 _(b'ADDR'),
6387 6389 ),
6388 6390 (
6389 6391 b'',
6390 6392 b'prefix',
6391 6393 b'',
6392 6394 _(b'prefix path to serve from (default: server root)'),
6393 6395 _(b'PREFIX'),
6394 6396 ),
6395 6397 (
6396 6398 b'n',
6397 6399 b'name',
6398 6400 b'',
6399 6401 _(b'name to show in web pages (default: working directory)'),
6400 6402 _(b'NAME'),
6401 6403 ),
6402 6404 (
6403 6405 b'',
6404 6406 b'web-conf',
6405 6407 b'',
6406 6408 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6407 6409 _(b'FILE'),
6408 6410 ),
6409 6411 (
6410 6412 b'',
6411 6413 b'webdir-conf',
6412 6414 b'',
6413 6415 _(b'name of the hgweb config file (DEPRECATED)'),
6414 6416 _(b'FILE'),
6415 6417 ),
6416 6418 (
6417 6419 b'',
6418 6420 b'pid-file',
6419 6421 b'',
6420 6422 _(b'name of file to write process ID to'),
6421 6423 _(b'FILE'),
6422 6424 ),
6423 6425 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6424 6426 (
6425 6427 b'',
6426 6428 b'cmdserver',
6427 6429 b'',
6428 6430 _(b'for remote clients (ADVANCED)'),
6429 6431 _(b'MODE'),
6430 6432 ),
6431 6433 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6432 6434 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6433 6435 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6434 6436 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6435 6437 (b'', b'print-url', None, _(b'start and print only the URL')),
6436 6438 ]
6437 6439 + subrepoopts,
6438 6440 _(b'[OPTION]...'),
6439 6441 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6440 6442 helpbasic=True,
6441 6443 optionalrepo=True,
6442 6444 )
6443 6445 def serve(ui, repo, **opts):
6444 6446 """start stand-alone webserver
6445 6447
6446 6448 Start a local HTTP repository browser and pull server. You can use
6447 6449 this for ad-hoc sharing and browsing of repositories. It is
6448 6450 recommended to use a real web server to serve a repository for
6449 6451 longer periods of time.
6450 6452
6451 6453 Please note that the server does not implement access control.
6452 6454 This means that, by default, anybody can read from the server and
6453 6455 nobody can write to it by default. Set the ``web.allow-push``
6454 6456 option to ``*`` to allow everybody to push to the server. You
6455 6457 should use a real web server if you need to authenticate users.
6456 6458
6457 6459 By default, the server logs accesses to stdout and errors to
6458 6460 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6459 6461 files.
6460 6462
6461 6463 To have the server choose a free port number to listen on, specify
6462 6464 a port number of 0; in this case, the server will print the port
6463 6465 number it uses.
6464 6466
6465 6467 Returns 0 on success.
6466 6468 """
6467 6469
6468 6470 opts = pycompat.byteskwargs(opts)
6469 6471 if opts[b"stdio"] and opts[b"cmdserver"]:
6470 6472 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6471 6473 if opts[b"print_url"] and ui.verbose:
6472 6474 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6473 6475
6474 6476 if opts[b"stdio"]:
6475 6477 if repo is None:
6476 6478 raise error.RepoError(
6477 6479 _(b"there is no Mercurial repository here (.hg not found)")
6478 6480 )
6479 6481 s = wireprotoserver.sshserver(ui, repo)
6480 6482 s.serve_forever()
6481 6483
6482 6484 service = server.createservice(ui, repo, opts)
6483 6485 return server.runservice(opts, initfn=service.init, runfn=service.run)
6484 6486
6485 6487
6486 6488 @command(
6487 6489 b'shelve',
6488 6490 [
6489 6491 (
6490 6492 b'A',
6491 6493 b'addremove',
6492 6494 None,
6493 6495 _(b'mark new/missing files as added/removed before shelving'),
6494 6496 ),
6495 6497 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6496 6498 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6497 6499 (
6498 6500 b'',
6499 6501 b'date',
6500 6502 b'',
6501 6503 _(b'shelve with the specified commit date'),
6502 6504 _(b'DATE'),
6503 6505 ),
6504 6506 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6505 6507 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6506 6508 (
6507 6509 b'k',
6508 6510 b'keep',
6509 6511 False,
6510 6512 _(b'shelve, but keep changes in the working directory'),
6511 6513 ),
6512 6514 (b'l', b'list', None, _(b'list current shelves')),
6513 6515 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6514 6516 (
6515 6517 b'n',
6516 6518 b'name',
6517 6519 b'',
6518 6520 _(b'use the given name for the shelved commit'),
6519 6521 _(b'NAME'),
6520 6522 ),
6521 6523 (
6522 6524 b'p',
6523 6525 b'patch',
6524 6526 None,
6525 6527 _(
6526 6528 b'output patches for changes (provide the names of the shelved '
6527 6529 b'changes as positional arguments)'
6528 6530 ),
6529 6531 ),
6530 6532 (b'i', b'interactive', None, _(b'interactive mode')),
6531 6533 (
6532 6534 b'',
6533 6535 b'stat',
6534 6536 None,
6535 6537 _(
6536 6538 b'output diffstat-style summary of changes (provide the names of '
6537 6539 b'the shelved changes as positional arguments)'
6538 6540 ),
6539 6541 ),
6540 6542 ]
6541 6543 + cmdutil.walkopts,
6542 6544 _(b'hg shelve [OPTION]... [FILE]...'),
6543 6545 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6544 6546 )
6545 6547 def shelve(ui, repo, *pats, **opts):
6546 6548 '''save and set aside changes from the working directory
6547 6549
6548 6550 Shelving takes files that "hg status" reports as not clean, saves
6549 6551 the modifications to a bundle (a shelved change), and reverts the
6550 6552 files so that their state in the working directory becomes clean.
6551 6553
6552 6554 To restore these changes to the working directory, using "hg
6553 6555 unshelve"; this will work even if you switch to a different
6554 6556 commit.
6555 6557
6556 6558 When no files are specified, "hg shelve" saves all not-clean
6557 6559 files. If specific files or directories are named, only changes to
6558 6560 those files are shelved.
6559 6561
6560 6562 In bare shelve (when no files are specified, without interactive,
6561 6563 include and exclude option), shelving remembers information if the
6562 6564 working directory was on newly created branch, in other words working
6563 6565 directory was on different branch than its first parent. In this
6564 6566 situation unshelving restores branch information to the working directory.
6565 6567
6566 6568 Each shelved change has a name that makes it easier to find later.
6567 6569 The name of a shelved change defaults to being based on the active
6568 6570 bookmark, or if there is no active bookmark, the current named
6569 6571 branch. To specify a different name, use ``--name``.
6570 6572
6571 6573 To see a list of existing shelved changes, use the ``--list``
6572 6574 option. For each shelved change, this will print its name, age,
6573 6575 and description; use ``--patch`` or ``--stat`` for more details.
6574 6576
6575 6577 To delete specific shelved changes, use ``--delete``. To delete
6576 6578 all shelved changes, use ``--cleanup``.
6577 6579 '''
6578 6580 opts = pycompat.byteskwargs(opts)
6579 6581 allowables = [
6580 6582 (b'addremove', {b'create'}), # 'create' is pseudo action
6581 6583 (b'unknown', {b'create'}),
6582 6584 (b'cleanup', {b'cleanup'}),
6583 6585 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6584 6586 (b'delete', {b'delete'}),
6585 6587 (b'edit', {b'create'}),
6586 6588 (b'keep', {b'create'}),
6587 6589 (b'list', {b'list'}),
6588 6590 (b'message', {b'create'}),
6589 6591 (b'name', {b'create'}),
6590 6592 (b'patch', {b'patch', b'list'}),
6591 6593 (b'stat', {b'stat', b'list'}),
6592 6594 ]
6593 6595
6594 6596 def checkopt(opt):
6595 6597 if opts.get(opt):
6596 6598 for i, allowable in allowables:
6597 6599 if opts[i] and opt not in allowable:
6598 6600 raise error.Abort(
6599 6601 _(
6600 6602 b"options '--%s' and '--%s' may not be "
6601 6603 b"used together"
6602 6604 )
6603 6605 % (opt, i)
6604 6606 )
6605 6607 return True
6606 6608
6607 6609 if checkopt(b'cleanup'):
6608 6610 if pats:
6609 6611 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6610 6612 return shelvemod.cleanupcmd(ui, repo)
6611 6613 elif checkopt(b'delete'):
6612 6614 return shelvemod.deletecmd(ui, repo, pats)
6613 6615 elif checkopt(b'list'):
6614 6616 return shelvemod.listcmd(ui, repo, pats, opts)
6615 6617 elif checkopt(b'patch') or checkopt(b'stat'):
6616 6618 return shelvemod.patchcmds(ui, repo, pats, opts)
6617 6619 else:
6618 6620 return shelvemod.createcmd(ui, repo, pats, opts)
6619 6621
6620 6622
6621 6623 _NOTTERSE = b'nothing'
6622 6624
6623 6625
6624 6626 @command(
6625 6627 b'status|st',
6626 6628 [
6627 6629 (b'A', b'all', None, _(b'show status of all files')),
6628 6630 (b'm', b'modified', None, _(b'show only modified files')),
6629 6631 (b'a', b'added', None, _(b'show only added files')),
6630 6632 (b'r', b'removed', None, _(b'show only removed files')),
6631 6633 (b'd', b'deleted', None, _(b'show only missing files')),
6632 6634 (b'c', b'clean', None, _(b'show only files without changes')),
6633 6635 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6634 6636 (b'i', b'ignored', None, _(b'show only ignored files')),
6635 6637 (b'n', b'no-status', None, _(b'hide status prefix')),
6636 6638 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6637 6639 (
6638 6640 b'C',
6639 6641 b'copies',
6640 6642 None,
6641 6643 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6642 6644 ),
6643 6645 (
6644 6646 b'0',
6645 6647 b'print0',
6646 6648 None,
6647 6649 _(b'end filenames with NUL, for use with xargs'),
6648 6650 ),
6649 6651 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6650 6652 (
6651 6653 b'',
6652 6654 b'change',
6653 6655 b'',
6654 6656 _(b'list the changed files of a revision'),
6655 6657 _(b'REV'),
6656 6658 ),
6657 6659 ]
6658 6660 + walkopts
6659 6661 + subrepoopts
6660 6662 + formatteropts,
6661 6663 _(b'[OPTION]... [FILE]...'),
6662 6664 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6663 6665 helpbasic=True,
6664 6666 inferrepo=True,
6665 6667 intents={INTENT_READONLY},
6666 6668 )
6667 6669 def status(ui, repo, *pats, **opts):
6668 6670 """show changed files in the working directory
6669 6671
6670 6672 Show status of files in the repository. If names are given, only
6671 6673 files that match are shown. Files that are clean or ignored or
6672 6674 the source of a copy/move operation, are not listed unless
6673 6675 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6674 6676 Unless options described with "show only ..." are given, the
6675 6677 options -mardu are used.
6676 6678
6677 6679 Option -q/--quiet hides untracked (unknown and ignored) files
6678 6680 unless explicitly requested with -u/--unknown or -i/--ignored.
6679 6681
6680 6682 .. note::
6681 6683
6682 6684 :hg:`status` may appear to disagree with diff if permissions have
6683 6685 changed or a merge has occurred. The standard diff format does
6684 6686 not report permission changes and diff only reports changes
6685 6687 relative to one merge parent.
6686 6688
6687 6689 If one revision is given, it is used as the base revision.
6688 6690 If two revisions are given, the differences between them are
6689 6691 shown. The --change option can also be used as a shortcut to list
6690 6692 the changed files of a revision from its first parent.
6691 6693
6692 6694 The codes used to show the status of files are::
6693 6695
6694 6696 M = modified
6695 6697 A = added
6696 6698 R = removed
6697 6699 C = clean
6698 6700 ! = missing (deleted by non-hg command, but still tracked)
6699 6701 ? = not tracked
6700 6702 I = ignored
6701 6703 = origin of the previous file (with --copies)
6702 6704
6703 6705 .. container:: verbose
6704 6706
6705 6707 The -t/--terse option abbreviates the output by showing only the directory
6706 6708 name if all the files in it share the same status. The option takes an
6707 6709 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6708 6710 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6709 6711 for 'ignored' and 'c' for clean.
6710 6712
6711 6713 It abbreviates only those statuses which are passed. Note that clean and
6712 6714 ignored files are not displayed with '--terse ic' unless the -c/--clean
6713 6715 and -i/--ignored options are also used.
6714 6716
6715 6717 The -v/--verbose option shows information when the repository is in an
6716 6718 unfinished merge, shelve, rebase state etc. You can have this behavior
6717 6719 turned on by default by enabling the ``commands.status.verbose`` option.
6718 6720
6719 6721 You can skip displaying some of these states by setting
6720 6722 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6721 6723 'histedit', 'merge', 'rebase', or 'unshelve'.
6722 6724
6723 6725 Template:
6724 6726
6725 6727 The following keywords are supported in addition to the common template
6726 6728 keywords and functions. See also :hg:`help templates`.
6727 6729
6728 6730 :path: String. Repository-absolute path of the file.
6729 6731 :source: String. Repository-absolute path of the file originated from.
6730 6732 Available if ``--copies`` is specified.
6731 6733 :status: String. Character denoting file's status.
6732 6734
6733 6735 Examples:
6734 6736
6735 6737 - show changes in the working directory relative to a
6736 6738 changeset::
6737 6739
6738 6740 hg status --rev 9353
6739 6741
6740 6742 - show changes in the working directory relative to the
6741 6743 current directory (see :hg:`help patterns` for more information)::
6742 6744
6743 6745 hg status re:
6744 6746
6745 6747 - show all changes including copies in an existing changeset::
6746 6748
6747 6749 hg status --copies --change 9353
6748 6750
6749 6751 - get a NUL separated list of added files, suitable for xargs::
6750 6752
6751 6753 hg status -an0
6752 6754
6753 6755 - show more information about the repository status, abbreviating
6754 6756 added, removed, modified, deleted, and untracked paths::
6755 6757
6756 6758 hg status -v -t mardu
6757 6759
6758 6760 Returns 0 on success.
6759 6761
6760 6762 """
6761 6763
6762 6764 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6763 6765 opts = pycompat.byteskwargs(opts)
6764 6766 revs = opts.get(b'rev')
6765 6767 change = opts.get(b'change')
6766 6768 terse = opts.get(b'terse')
6767 6769 if terse is _NOTTERSE:
6768 6770 if revs:
6769 6771 terse = b''
6770 6772 else:
6771 6773 terse = ui.config(b'commands', b'status.terse')
6772 6774
6773 6775 if revs and terse:
6774 6776 msg = _(b'cannot use --terse with --rev')
6775 6777 raise error.Abort(msg)
6776 6778 elif change:
6777 6779 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6778 6780 ctx2 = scmutil.revsingle(repo, change, None)
6779 6781 ctx1 = ctx2.p1()
6780 6782 else:
6781 6783 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6782 6784 ctx1, ctx2 = scmutil.revpair(repo, revs)
6783 6785
6784 6786 forcerelativevalue = None
6785 6787 if ui.hasconfig(b'commands', b'status.relative'):
6786 6788 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6787 6789 uipathfn = scmutil.getuipathfn(
6788 6790 repo,
6789 6791 legacyrelativevalue=bool(pats),
6790 6792 forcerelativevalue=forcerelativevalue,
6791 6793 )
6792 6794
6793 6795 if opts.get(b'print0'):
6794 6796 end = b'\0'
6795 6797 else:
6796 6798 end = b'\n'
6797 6799 states = b'modified added removed deleted unknown ignored clean'.split()
6798 6800 show = [k for k in states if opts.get(k)]
6799 6801 if opts.get(b'all'):
6800 6802 show += ui.quiet and (states[:4] + [b'clean']) or states
6801 6803
6802 6804 if not show:
6803 6805 if ui.quiet:
6804 6806 show = states[:4]
6805 6807 else:
6806 6808 show = states[:5]
6807 6809
6808 6810 m = scmutil.match(ctx2, pats, opts)
6809 6811 if terse:
6810 6812 # we need to compute clean and unknown to terse
6811 6813 stat = repo.status(
6812 6814 ctx1.node(),
6813 6815 ctx2.node(),
6814 6816 m,
6815 6817 b'ignored' in show or b'i' in terse,
6816 6818 clean=True,
6817 6819 unknown=True,
6818 6820 listsubrepos=opts.get(b'subrepos'),
6819 6821 )
6820 6822
6821 6823 stat = cmdutil.tersedir(stat, terse)
6822 6824 else:
6823 6825 stat = repo.status(
6824 6826 ctx1.node(),
6825 6827 ctx2.node(),
6826 6828 m,
6827 6829 b'ignored' in show,
6828 6830 b'clean' in show,
6829 6831 b'unknown' in show,
6830 6832 opts.get(b'subrepos'),
6831 6833 )
6832 6834
6833 6835 changestates = zip(
6834 6836 states,
6835 6837 pycompat.iterbytestr(b'MAR!?IC'),
6836 6838 [getattr(stat, s.decode('utf8')) for s in states],
6837 6839 )
6838 6840
6839 6841 copy = {}
6840 6842 if (
6841 6843 opts.get(b'all')
6842 6844 or opts.get(b'copies')
6843 6845 or ui.configbool(b'ui', b'statuscopies')
6844 6846 ) and not opts.get(b'no_status'):
6845 6847 copy = copies.pathcopies(ctx1, ctx2, m)
6846 6848
6847 6849 morestatus = None
6848 6850 if (
6849 6851 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6850 6852 ) and not ui.plain():
6851 6853 morestatus = cmdutil.readmorestatus(repo)
6852 6854
6853 6855 ui.pager(b'status')
6854 6856 fm = ui.formatter(b'status', opts)
6855 6857 fmt = b'%s' + end
6856 6858 showchar = not opts.get(b'no_status')
6857 6859
6858 6860 for state, char, files in changestates:
6859 6861 if state in show:
6860 6862 label = b'status.' + state
6861 6863 for f in files:
6862 6864 fm.startitem()
6863 6865 fm.context(ctx=ctx2)
6864 6866 fm.data(itemtype=b'file', path=f)
6865 6867 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6866 6868 fm.plain(fmt % uipathfn(f), label=label)
6867 6869 if f in copy:
6868 6870 fm.data(source=copy[f])
6869 6871 fm.plain(
6870 6872 (b' %s' + end) % uipathfn(copy[f]),
6871 6873 label=b'status.copied',
6872 6874 )
6873 6875 if morestatus:
6874 6876 morestatus.formatfile(f, fm)
6875 6877
6876 6878 if morestatus:
6877 6879 morestatus.formatfooter(fm)
6878 6880 fm.end()
6879 6881
6880 6882
6881 6883 @command(
6882 6884 b'summary|sum',
6883 6885 [(b'', b'remote', None, _(b'check for push and pull'))],
6884 6886 b'[--remote]',
6885 6887 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6886 6888 helpbasic=True,
6887 6889 intents={INTENT_READONLY},
6888 6890 )
6889 6891 def summary(ui, repo, **opts):
6890 6892 """summarize working directory state
6891 6893
6892 6894 This generates a brief summary of the working directory state,
6893 6895 including parents, branch, commit status, phase and available updates.
6894 6896
6895 6897 With the --remote option, this will check the default paths for
6896 6898 incoming and outgoing changes. This can be time-consuming.
6897 6899
6898 6900 Returns 0 on success.
6899 6901 """
6900 6902
6901 6903 opts = pycompat.byteskwargs(opts)
6902 6904 ui.pager(b'summary')
6903 6905 ctx = repo[None]
6904 6906 parents = ctx.parents()
6905 6907 pnode = parents[0].node()
6906 6908 marks = []
6907 6909
6908 6910 try:
6909 6911 ms = mergestatemod.mergestate.read(repo)
6910 6912 except error.UnsupportedMergeRecords as e:
6911 6913 s = b' '.join(e.recordtypes)
6912 6914 ui.warn(
6913 6915 _(b'warning: merge state has unsupported record types: %s\n') % s
6914 6916 )
6915 6917 unresolved = []
6916 6918 else:
6917 6919 unresolved = list(ms.unresolved())
6918 6920
6919 6921 for p in parents:
6920 6922 # label with log.changeset (instead of log.parent) since this
6921 6923 # shows a working directory parent *changeset*:
6922 6924 # i18n: column positioning for "hg summary"
6923 6925 ui.write(
6924 6926 _(b'parent: %d:%s ') % (p.rev(), p),
6925 6927 label=logcmdutil.changesetlabels(p),
6926 6928 )
6927 6929 ui.write(b' '.join(p.tags()), label=b'log.tag')
6928 6930 if p.bookmarks():
6929 6931 marks.extend(p.bookmarks())
6930 6932 if p.rev() == -1:
6931 6933 if not len(repo):
6932 6934 ui.write(_(b' (empty repository)'))
6933 6935 else:
6934 6936 ui.write(_(b' (no revision checked out)'))
6935 6937 if p.obsolete():
6936 6938 ui.write(_(b' (obsolete)'))
6937 6939 if p.isunstable():
6938 6940 instabilities = (
6939 6941 ui.label(instability, b'trouble.%s' % instability)
6940 6942 for instability in p.instabilities()
6941 6943 )
6942 6944 ui.write(b' (' + b', '.join(instabilities) + b')')
6943 6945 ui.write(b'\n')
6944 6946 if p.description():
6945 6947 ui.status(
6946 6948 b' ' + p.description().splitlines()[0].strip() + b'\n',
6947 6949 label=b'log.summary',
6948 6950 )
6949 6951
6950 6952 branch = ctx.branch()
6951 6953 bheads = repo.branchheads(branch)
6952 6954 # i18n: column positioning for "hg summary"
6953 6955 m = _(b'branch: %s\n') % branch
6954 6956 if branch != b'default':
6955 6957 ui.write(m, label=b'log.branch')
6956 6958 else:
6957 6959 ui.status(m, label=b'log.branch')
6958 6960
6959 6961 if marks:
6960 6962 active = repo._activebookmark
6961 6963 # i18n: column positioning for "hg summary"
6962 6964 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6963 6965 if active is not None:
6964 6966 if active in marks:
6965 6967 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6966 6968 marks.remove(active)
6967 6969 else:
6968 6970 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6969 6971 for m in marks:
6970 6972 ui.write(b' ' + m, label=b'log.bookmark')
6971 6973 ui.write(b'\n', label=b'log.bookmark')
6972 6974
6973 6975 status = repo.status(unknown=True)
6974 6976
6975 6977 c = repo.dirstate.copies()
6976 6978 copied, renamed = [], []
6977 6979 for d, s in pycompat.iteritems(c):
6978 6980 if s in status.removed:
6979 6981 status.removed.remove(s)
6980 6982 renamed.append(d)
6981 6983 else:
6982 6984 copied.append(d)
6983 6985 if d in status.added:
6984 6986 status.added.remove(d)
6985 6987
6986 6988 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
6987 6989
6988 6990 labels = [
6989 6991 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
6990 6992 (ui.label(_(b'%d added'), b'status.added'), status.added),
6991 6993 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
6992 6994 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
6993 6995 (ui.label(_(b'%d copied'), b'status.copied'), copied),
6994 6996 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
6995 6997 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
6996 6998 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
6997 6999 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
6998 7000 ]
6999 7001 t = []
7000 7002 for l, s in labels:
7001 7003 if s:
7002 7004 t.append(l % len(s))
7003 7005
7004 7006 t = b', '.join(t)
7005 7007 cleanworkdir = False
7006 7008
7007 7009 if repo.vfs.exists(b'graftstate'):
7008 7010 t += _(b' (graft in progress)')
7009 7011 if repo.vfs.exists(b'updatestate'):
7010 7012 t += _(b' (interrupted update)')
7011 7013 elif len(parents) > 1:
7012 7014 t += _(b' (merge)')
7013 7015 elif branch != parents[0].branch():
7014 7016 t += _(b' (new branch)')
7015 7017 elif parents[0].closesbranch() and pnode in repo.branchheads(
7016 7018 branch, closed=True
7017 7019 ):
7018 7020 t += _(b' (head closed)')
7019 7021 elif not (
7020 7022 status.modified
7021 7023 or status.added
7022 7024 or status.removed
7023 7025 or renamed
7024 7026 or copied
7025 7027 or subs
7026 7028 ):
7027 7029 t += _(b' (clean)')
7028 7030 cleanworkdir = True
7029 7031 elif pnode not in bheads:
7030 7032 t += _(b' (new branch head)')
7031 7033
7032 7034 if parents:
7033 7035 pendingphase = max(p.phase() for p in parents)
7034 7036 else:
7035 7037 pendingphase = phases.public
7036 7038
7037 7039 if pendingphase > phases.newcommitphase(ui):
7038 7040 t += b' (%s)' % phases.phasenames[pendingphase]
7039 7041
7040 7042 if cleanworkdir:
7041 7043 # i18n: column positioning for "hg summary"
7042 7044 ui.status(_(b'commit: %s\n') % t.strip())
7043 7045 else:
7044 7046 # i18n: column positioning for "hg summary"
7045 7047 ui.write(_(b'commit: %s\n') % t.strip())
7046 7048
7047 7049 # all ancestors of branch heads - all ancestors of parent = new csets
7048 7050 new = len(
7049 7051 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7050 7052 )
7051 7053
7052 7054 if new == 0:
7053 7055 # i18n: column positioning for "hg summary"
7054 7056 ui.status(_(b'update: (current)\n'))
7055 7057 elif pnode not in bheads:
7056 7058 # i18n: column positioning for "hg summary"
7057 7059 ui.write(_(b'update: %d new changesets (update)\n') % new)
7058 7060 else:
7059 7061 # i18n: column positioning for "hg summary"
7060 7062 ui.write(
7061 7063 _(b'update: %d new changesets, %d branch heads (merge)\n')
7062 7064 % (new, len(bheads))
7063 7065 )
7064 7066
7065 7067 t = []
7066 7068 draft = len(repo.revs(b'draft()'))
7067 7069 if draft:
7068 7070 t.append(_(b'%d draft') % draft)
7069 7071 secret = len(repo.revs(b'secret()'))
7070 7072 if secret:
7071 7073 t.append(_(b'%d secret') % secret)
7072 7074
7073 7075 if draft or secret:
7074 7076 ui.status(_(b'phases: %s\n') % b', '.join(t))
7075 7077
7076 7078 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7077 7079 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7078 7080 numtrouble = len(repo.revs(trouble + b"()"))
7079 7081 # We write all the possibilities to ease translation
7080 7082 troublemsg = {
7081 7083 b"orphan": _(b"orphan: %d changesets"),
7082 7084 b"contentdivergent": _(b"content-divergent: %d changesets"),
7083 7085 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7084 7086 }
7085 7087 if numtrouble > 0:
7086 7088 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7087 7089
7088 7090 cmdutil.summaryhooks(ui, repo)
7089 7091
7090 7092 if opts.get(b'remote'):
7091 7093 needsincoming, needsoutgoing = True, True
7092 7094 else:
7093 7095 needsincoming, needsoutgoing = False, False
7094 7096 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7095 7097 if i:
7096 7098 needsincoming = True
7097 7099 if o:
7098 7100 needsoutgoing = True
7099 7101 if not needsincoming and not needsoutgoing:
7100 7102 return
7101 7103
7102 7104 def getincoming():
7103 7105 source, branches = hg.parseurl(ui.expandpath(b'default'))
7104 7106 sbranch = branches[0]
7105 7107 try:
7106 7108 other = hg.peer(repo, {}, source)
7107 7109 except error.RepoError:
7108 7110 if opts.get(b'remote'):
7109 7111 raise
7110 7112 return source, sbranch, None, None, None
7111 7113 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7112 7114 if revs:
7113 7115 revs = [other.lookup(rev) for rev in revs]
7114 7116 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7115 7117 repo.ui.pushbuffer()
7116 7118 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7117 7119 repo.ui.popbuffer()
7118 7120 return source, sbranch, other, commoninc, commoninc[1]
7119 7121
7120 7122 if needsincoming:
7121 7123 source, sbranch, sother, commoninc, incoming = getincoming()
7122 7124 else:
7123 7125 source = sbranch = sother = commoninc = incoming = None
7124 7126
7125 7127 def getoutgoing():
7126 7128 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7127 7129 dbranch = branches[0]
7128 7130 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7129 7131 if source != dest:
7130 7132 try:
7131 7133 dother = hg.peer(repo, {}, dest)
7132 7134 except error.RepoError:
7133 7135 if opts.get(b'remote'):
7134 7136 raise
7135 7137 return dest, dbranch, None, None
7136 7138 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7137 7139 elif sother is None:
7138 7140 # there is no explicit destination peer, but source one is invalid
7139 7141 return dest, dbranch, None, None
7140 7142 else:
7141 7143 dother = sother
7142 7144 if source != dest or (sbranch is not None and sbranch != dbranch):
7143 7145 common = None
7144 7146 else:
7145 7147 common = commoninc
7146 7148 if revs:
7147 7149 revs = [repo.lookup(rev) for rev in revs]
7148 7150 repo.ui.pushbuffer()
7149 7151 outgoing = discovery.findcommonoutgoing(
7150 7152 repo, dother, onlyheads=revs, commoninc=common
7151 7153 )
7152 7154 repo.ui.popbuffer()
7153 7155 return dest, dbranch, dother, outgoing
7154 7156
7155 7157 if needsoutgoing:
7156 7158 dest, dbranch, dother, outgoing = getoutgoing()
7157 7159 else:
7158 7160 dest = dbranch = dother = outgoing = None
7159 7161
7160 7162 if opts.get(b'remote'):
7161 7163 t = []
7162 7164 if incoming:
7163 7165 t.append(_(b'1 or more incoming'))
7164 7166 o = outgoing.missing
7165 7167 if o:
7166 7168 t.append(_(b'%d outgoing') % len(o))
7167 7169 other = dother or sother
7168 7170 if b'bookmarks' in other.listkeys(b'namespaces'):
7169 7171 counts = bookmarks.summary(repo, other)
7170 7172 if counts[0] > 0:
7171 7173 t.append(_(b'%d incoming bookmarks') % counts[0])
7172 7174 if counts[1] > 0:
7173 7175 t.append(_(b'%d outgoing bookmarks') % counts[1])
7174 7176
7175 7177 if t:
7176 7178 # i18n: column positioning for "hg summary"
7177 7179 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7178 7180 else:
7179 7181 # i18n: column positioning for "hg summary"
7180 7182 ui.status(_(b'remote: (synced)\n'))
7181 7183
7182 7184 cmdutil.summaryremotehooks(
7183 7185 ui,
7184 7186 repo,
7185 7187 opts,
7186 7188 (
7187 7189 (source, sbranch, sother, commoninc),
7188 7190 (dest, dbranch, dother, outgoing),
7189 7191 ),
7190 7192 )
7191 7193
7192 7194
7193 7195 @command(
7194 7196 b'tag',
7195 7197 [
7196 7198 (b'f', b'force', None, _(b'force tag')),
7197 7199 (b'l', b'local', None, _(b'make the tag local')),
7198 7200 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7199 7201 (b'', b'remove', None, _(b'remove a tag')),
7200 7202 # -l/--local is already there, commitopts cannot be used
7201 7203 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7202 7204 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7203 7205 ]
7204 7206 + commitopts2,
7205 7207 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7206 7208 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7207 7209 )
7208 7210 def tag(ui, repo, name1, *names, **opts):
7209 7211 """add one or more tags for the current or given revision
7210 7212
7211 7213 Name a particular revision using <name>.
7212 7214
7213 7215 Tags are used to name particular revisions of the repository and are
7214 7216 very useful to compare different revisions, to go back to significant
7215 7217 earlier versions or to mark branch points as releases, etc. Changing
7216 7218 an existing tag is normally disallowed; use -f/--force to override.
7217 7219
7218 7220 If no revision is given, the parent of the working directory is
7219 7221 used.
7220 7222
7221 7223 To facilitate version control, distribution, and merging of tags,
7222 7224 they are stored as a file named ".hgtags" which is managed similarly
7223 7225 to other project files and can be hand-edited if necessary. This
7224 7226 also means that tagging creates a new commit. The file
7225 7227 ".hg/localtags" is used for local tags (not shared among
7226 7228 repositories).
7227 7229
7228 7230 Tag commits are usually made at the head of a branch. If the parent
7229 7231 of the working directory is not a branch head, :hg:`tag` aborts; use
7230 7232 -f/--force to force the tag commit to be based on a non-head
7231 7233 changeset.
7232 7234
7233 7235 See :hg:`help dates` for a list of formats valid for -d/--date.
7234 7236
7235 7237 Since tag names have priority over branch names during revision
7236 7238 lookup, using an existing branch name as a tag name is discouraged.
7237 7239
7238 7240 Returns 0 on success.
7239 7241 """
7240 7242 opts = pycompat.byteskwargs(opts)
7241 7243 with repo.wlock(), repo.lock():
7242 7244 rev_ = b"."
7243 7245 names = [t.strip() for t in (name1,) + names]
7244 7246 if len(names) != len(set(names)):
7245 7247 raise error.Abort(_(b'tag names must be unique'))
7246 7248 for n in names:
7247 7249 scmutil.checknewlabel(repo, n, b'tag')
7248 7250 if not n:
7249 7251 raise error.Abort(
7250 7252 _(b'tag names cannot consist entirely of whitespace')
7251 7253 )
7252 7254 if opts.get(b'rev') and opts.get(b'remove'):
7253 7255 raise error.Abort(_(b"--rev and --remove are incompatible"))
7254 7256 if opts.get(b'rev'):
7255 7257 rev_ = opts[b'rev']
7256 7258 message = opts.get(b'message')
7257 7259 if opts.get(b'remove'):
7258 7260 if opts.get(b'local'):
7259 7261 expectedtype = b'local'
7260 7262 else:
7261 7263 expectedtype = b'global'
7262 7264
7263 7265 for n in names:
7264 7266 if repo.tagtype(n) == b'global':
7265 7267 alltags = tagsmod.findglobaltags(ui, repo)
7266 7268 if alltags[n][0] == nullid:
7267 7269 raise error.Abort(_(b"tag '%s' is already removed") % n)
7268 7270 if not repo.tagtype(n):
7269 7271 raise error.Abort(_(b"tag '%s' does not exist") % n)
7270 7272 if repo.tagtype(n) != expectedtype:
7271 7273 if expectedtype == b'global':
7272 7274 raise error.Abort(
7273 7275 _(b"tag '%s' is not a global tag") % n
7274 7276 )
7275 7277 else:
7276 7278 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7277 7279 rev_ = b'null'
7278 7280 if not message:
7279 7281 # we don't translate commit messages
7280 7282 message = b'Removed tag %s' % b', '.join(names)
7281 7283 elif not opts.get(b'force'):
7282 7284 for n in names:
7283 7285 if n in repo.tags():
7284 7286 raise error.Abort(
7285 7287 _(b"tag '%s' already exists (use -f to force)") % n
7286 7288 )
7287 7289 if not opts.get(b'local'):
7288 7290 p1, p2 = repo.dirstate.parents()
7289 7291 if p2 != nullid:
7290 7292 raise error.Abort(_(b'uncommitted merge'))
7291 7293 bheads = repo.branchheads()
7292 7294 if not opts.get(b'force') and bheads and p1 not in bheads:
7293 7295 raise error.Abort(
7294 7296 _(
7295 7297 b'working directory is not at a branch head '
7296 7298 b'(use -f to force)'
7297 7299 )
7298 7300 )
7299 7301 node = scmutil.revsingle(repo, rev_).node()
7300 7302
7301 7303 if not message:
7302 7304 # we don't translate commit messages
7303 7305 message = b'Added tag %s for changeset %s' % (
7304 7306 b', '.join(names),
7305 7307 short(node),
7306 7308 )
7307 7309
7308 7310 date = opts.get(b'date')
7309 7311 if date:
7310 7312 date = dateutil.parsedate(date)
7311 7313
7312 7314 if opts.get(b'remove'):
7313 7315 editform = b'tag.remove'
7314 7316 else:
7315 7317 editform = b'tag.add'
7316 7318 editor = cmdutil.getcommiteditor(
7317 7319 editform=editform, **pycompat.strkwargs(opts)
7318 7320 )
7319 7321
7320 7322 # don't allow tagging the null rev
7321 7323 if (
7322 7324 not opts.get(b'remove')
7323 7325 and scmutil.revsingle(repo, rev_).rev() == nullrev
7324 7326 ):
7325 7327 raise error.Abort(_(b"cannot tag null revision"))
7326 7328
7327 7329 tagsmod.tag(
7328 7330 repo,
7329 7331 names,
7330 7332 node,
7331 7333 message,
7332 7334 opts.get(b'local'),
7333 7335 opts.get(b'user'),
7334 7336 date,
7335 7337 editor=editor,
7336 7338 )
7337 7339
7338 7340
7339 7341 @command(
7340 7342 b'tags',
7341 7343 formatteropts,
7342 7344 b'',
7343 7345 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7344 7346 intents={INTENT_READONLY},
7345 7347 )
7346 7348 def tags(ui, repo, **opts):
7347 7349 """list repository tags
7348 7350
7349 7351 This lists both regular and local tags. When the -v/--verbose
7350 7352 switch is used, a third column "local" is printed for local tags.
7351 7353 When the -q/--quiet switch is used, only the tag name is printed.
7352 7354
7353 7355 .. container:: verbose
7354 7356
7355 7357 Template:
7356 7358
7357 7359 The following keywords are supported in addition to the common template
7358 7360 keywords and functions such as ``{tag}``. See also
7359 7361 :hg:`help templates`.
7360 7362
7361 7363 :type: String. ``local`` for local tags.
7362 7364
7363 7365 Returns 0 on success.
7364 7366 """
7365 7367
7366 7368 opts = pycompat.byteskwargs(opts)
7367 7369 ui.pager(b'tags')
7368 7370 fm = ui.formatter(b'tags', opts)
7369 7371 hexfunc = fm.hexfunc
7370 7372
7371 7373 for t, n in reversed(repo.tagslist()):
7372 7374 hn = hexfunc(n)
7373 7375 label = b'tags.normal'
7374 7376 tagtype = b''
7375 7377 if repo.tagtype(t) == b'local':
7376 7378 label = b'tags.local'
7377 7379 tagtype = b'local'
7378 7380
7379 7381 fm.startitem()
7380 7382 fm.context(repo=repo)
7381 7383 fm.write(b'tag', b'%s', t, label=label)
7382 7384 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7383 7385 fm.condwrite(
7384 7386 not ui.quiet,
7385 7387 b'rev node',
7386 7388 fmt,
7387 7389 repo.changelog.rev(n),
7388 7390 hn,
7389 7391 label=label,
7390 7392 )
7391 7393 fm.condwrite(
7392 7394 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7393 7395 )
7394 7396 fm.plain(b'\n')
7395 7397 fm.end()
7396 7398
7397 7399
7398 7400 @command(
7399 7401 b'tip',
7400 7402 [
7401 7403 (b'p', b'patch', None, _(b'show patch')),
7402 7404 (b'g', b'git', None, _(b'use git extended diff format')),
7403 7405 ]
7404 7406 + templateopts,
7405 7407 _(b'[-p] [-g]'),
7406 7408 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7407 7409 )
7408 7410 def tip(ui, repo, **opts):
7409 7411 """show the tip revision (DEPRECATED)
7410 7412
7411 7413 The tip revision (usually just called the tip) is the changeset
7412 7414 most recently added to the repository (and therefore the most
7413 7415 recently changed head).
7414 7416
7415 7417 If you have just made a commit, that commit will be the tip. If
7416 7418 you have just pulled changes from another repository, the tip of
7417 7419 that repository becomes the current tip. The "tip" tag is special
7418 7420 and cannot be renamed or assigned to a different changeset.
7419 7421
7420 7422 This command is deprecated, please use :hg:`heads` instead.
7421 7423
7422 7424 Returns 0 on success.
7423 7425 """
7424 7426 opts = pycompat.byteskwargs(opts)
7425 7427 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7426 7428 displayer.show(repo[b'tip'])
7427 7429 displayer.close()
7428 7430
7429 7431
7430 7432 @command(
7431 7433 b'unbundle',
7432 7434 [
7433 7435 (
7434 7436 b'u',
7435 7437 b'update',
7436 7438 None,
7437 7439 _(b'update to new branch head if changesets were unbundled'),
7438 7440 )
7439 7441 ],
7440 7442 _(b'[-u] FILE...'),
7441 7443 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7442 7444 )
7443 7445 def unbundle(ui, repo, fname1, *fnames, **opts):
7444 7446 """apply one or more bundle files
7445 7447
7446 7448 Apply one or more bundle files generated by :hg:`bundle`.
7447 7449
7448 7450 Returns 0 on success, 1 if an update has unresolved files.
7449 7451 """
7450 7452 fnames = (fname1,) + fnames
7451 7453
7452 7454 with repo.lock():
7453 7455 for fname in fnames:
7454 7456 f = hg.openpath(ui, fname)
7455 7457 gen = exchange.readbundle(ui, f, fname)
7456 7458 if isinstance(gen, streamclone.streamcloneapplier):
7457 7459 raise error.Abort(
7458 7460 _(
7459 7461 b'packed bundles cannot be applied with '
7460 7462 b'"hg unbundle"'
7461 7463 ),
7462 7464 hint=_(b'use "hg debugapplystreamclonebundle"'),
7463 7465 )
7464 7466 url = b'bundle:' + fname
7465 7467 try:
7466 7468 txnname = b'unbundle'
7467 7469 if not isinstance(gen, bundle2.unbundle20):
7468 7470 txnname = b'unbundle\n%s' % util.hidepassword(url)
7469 7471 with repo.transaction(txnname) as tr:
7470 7472 op = bundle2.applybundle(
7471 7473 repo, gen, tr, source=b'unbundle', url=url
7472 7474 )
7473 7475 except error.BundleUnknownFeatureError as exc:
7474 7476 raise error.Abort(
7475 7477 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7476 7478 hint=_(
7477 7479 b"see https://mercurial-scm.org/"
7478 7480 b"wiki/BundleFeature for more "
7479 7481 b"information"
7480 7482 ),
7481 7483 )
7482 7484 modheads = bundle2.combinechangegroupresults(op)
7483 7485
7484 7486 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7485 7487
7486 7488
7487 7489 @command(
7488 7490 b'unshelve',
7489 7491 [
7490 7492 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7491 7493 (
7492 7494 b'c',
7493 7495 b'continue',
7494 7496 None,
7495 7497 _(b'continue an incomplete unshelve operation'),
7496 7498 ),
7497 7499 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7498 7500 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7499 7501 (
7500 7502 b'n',
7501 7503 b'name',
7502 7504 b'',
7503 7505 _(b'restore shelved change with given name'),
7504 7506 _(b'NAME'),
7505 7507 ),
7506 7508 (b't', b'tool', b'', _(b'specify merge tool')),
7507 7509 (
7508 7510 b'',
7509 7511 b'date',
7510 7512 b'',
7511 7513 _(b'set date for temporary commits (DEPRECATED)'),
7512 7514 _(b'DATE'),
7513 7515 ),
7514 7516 ],
7515 7517 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7516 7518 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7517 7519 )
7518 7520 def unshelve(ui, repo, *shelved, **opts):
7519 7521 """restore a shelved change to the working directory
7520 7522
7521 7523 This command accepts an optional name of a shelved change to
7522 7524 restore. If none is given, the most recent shelved change is used.
7523 7525
7524 7526 If a shelved change is applied successfully, the bundle that
7525 7527 contains the shelved changes is moved to a backup location
7526 7528 (.hg/shelve-backup).
7527 7529
7528 7530 Since you can restore a shelved change on top of an arbitrary
7529 7531 commit, it is possible that unshelving will result in a conflict
7530 7532 between your changes and the commits you are unshelving onto. If
7531 7533 this occurs, you must resolve the conflict, then use
7532 7534 ``--continue`` to complete the unshelve operation. (The bundle
7533 7535 will not be moved until you successfully complete the unshelve.)
7534 7536
7535 7537 (Alternatively, you can use ``--abort`` to abandon an unshelve
7536 7538 that causes a conflict. This reverts the unshelved changes, and
7537 7539 leaves the bundle in place.)
7538 7540
7539 7541 If bare shelved change (without interactive, include and exclude
7540 7542 option) was done on newly created branch it would restore branch
7541 7543 information to the working directory.
7542 7544
7543 7545 After a successful unshelve, the shelved changes are stored in a
7544 7546 backup directory. Only the N most recent backups are kept. N
7545 7547 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7546 7548 configuration option.
7547 7549
7548 7550 .. container:: verbose
7549 7551
7550 7552 Timestamp in seconds is used to decide order of backups. More
7551 7553 than ``maxbackups`` backups are kept, if same timestamp
7552 7554 prevents from deciding exact order of them, for safety.
7553 7555
7554 7556 Selected changes can be unshelved with ``--interactive`` flag.
7555 7557 The working directory is updated with the selected changes, and
7556 7558 only the unselected changes remain shelved.
7557 7559 Note: The whole shelve is applied to working directory first before
7558 7560 running interactively. So, this will bring up all the conflicts between
7559 7561 working directory and the shelve, irrespective of which changes will be
7560 7562 unshelved.
7561 7563 """
7562 7564 with repo.wlock():
7563 7565 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7564 7566
7565 7567
7566 7568 statemod.addunfinished(
7567 7569 b'unshelve',
7568 7570 fname=b'shelvedstate',
7569 7571 continueflag=True,
7570 7572 abortfunc=shelvemod.hgabortunshelve,
7571 7573 continuefunc=shelvemod.hgcontinueunshelve,
7572 7574 cmdmsg=_(b'unshelve already in progress'),
7573 7575 )
7574 7576
7575 7577
7576 7578 @command(
7577 7579 b'update|up|checkout|co',
7578 7580 [
7579 7581 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7580 7582 (b'c', b'check', None, _(b'require clean working directory')),
7581 7583 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7582 7584 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7583 7585 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7584 7586 ]
7585 7587 + mergetoolopts,
7586 7588 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7587 7589 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7588 7590 helpbasic=True,
7589 7591 )
7590 7592 def update(ui, repo, node=None, **opts):
7591 7593 """update working directory (or switch revisions)
7592 7594
7593 7595 Update the repository's working directory to the specified
7594 7596 changeset. If no changeset is specified, update to the tip of the
7595 7597 current named branch and move the active bookmark (see :hg:`help
7596 7598 bookmarks`).
7597 7599
7598 7600 Update sets the working directory's parent revision to the specified
7599 7601 changeset (see :hg:`help parents`).
7600 7602
7601 7603 If the changeset is not a descendant or ancestor of the working
7602 7604 directory's parent and there are uncommitted changes, the update is
7603 7605 aborted. With the -c/--check option, the working directory is checked
7604 7606 for uncommitted changes; if none are found, the working directory is
7605 7607 updated to the specified changeset.
7606 7608
7607 7609 .. container:: verbose
7608 7610
7609 7611 The -C/--clean, -c/--check, and -m/--merge options control what
7610 7612 happens if the working directory contains uncommitted changes.
7611 7613 At most of one of them can be specified.
7612 7614
7613 7615 1. If no option is specified, and if
7614 7616 the requested changeset is an ancestor or descendant of
7615 7617 the working directory's parent, the uncommitted changes
7616 7618 are merged into the requested changeset and the merged
7617 7619 result is left uncommitted. If the requested changeset is
7618 7620 not an ancestor or descendant (that is, it is on another
7619 7621 branch), the update is aborted and the uncommitted changes
7620 7622 are preserved.
7621 7623
7622 7624 2. With the -m/--merge option, the update is allowed even if the
7623 7625 requested changeset is not an ancestor or descendant of
7624 7626 the working directory's parent.
7625 7627
7626 7628 3. With the -c/--check option, the update is aborted and the
7627 7629 uncommitted changes are preserved.
7628 7630
7629 7631 4. With the -C/--clean option, uncommitted changes are discarded and
7630 7632 the working directory is updated to the requested changeset.
7631 7633
7632 7634 To cancel an uncommitted merge (and lose your changes), use
7633 7635 :hg:`merge --abort`.
7634 7636
7635 7637 Use null as the changeset to remove the working directory (like
7636 7638 :hg:`clone -U`).
7637 7639
7638 7640 If you want to revert just one file to an older revision, use
7639 7641 :hg:`revert [-r REV] NAME`.
7640 7642
7641 7643 See :hg:`help dates` for a list of formats valid for -d/--date.
7642 7644
7643 7645 Returns 0 on success, 1 if there are unresolved files.
7644 7646 """
7645 7647 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7646 7648 rev = opts.get('rev')
7647 7649 date = opts.get('date')
7648 7650 clean = opts.get('clean')
7649 7651 check = opts.get('check')
7650 7652 merge = opts.get('merge')
7651 7653 if rev and node:
7652 7654 raise error.Abort(_(b"please specify just one revision"))
7653 7655
7654 7656 if ui.configbool(b'commands', b'update.requiredest'):
7655 7657 if not node and not rev and not date:
7656 7658 raise error.Abort(
7657 7659 _(b'you must specify a destination'),
7658 7660 hint=_(b'for example: hg update ".::"'),
7659 7661 )
7660 7662
7661 7663 if rev is None or rev == b'':
7662 7664 rev = node
7663 7665
7664 7666 if date and rev is not None:
7665 7667 raise error.Abort(_(b"you can't specify a revision and a date"))
7666 7668
7667 7669 updatecheck = None
7668 7670 if check:
7669 7671 updatecheck = b'abort'
7670 7672 elif merge:
7671 7673 updatecheck = b'none'
7672 7674
7673 7675 with repo.wlock():
7674 7676 cmdutil.clearunfinished(repo)
7675 7677 if date:
7676 7678 rev = cmdutil.finddate(ui, repo, date)
7677 7679
7678 7680 # if we defined a bookmark, we have to remember the original name
7679 7681 brev = rev
7680 7682 if rev:
7681 7683 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7682 7684 ctx = scmutil.revsingle(repo, rev, default=None)
7683 7685 rev = ctx.rev()
7684 7686 hidden = ctx.hidden()
7685 7687 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7686 7688 with ui.configoverride(overrides, b'update'):
7687 7689 ret = hg.updatetotally(
7688 7690 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7689 7691 )
7690 7692 if hidden:
7691 7693 ctxstr = ctx.hex()[:12]
7692 7694 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7693 7695
7694 7696 if ctx.obsolete():
7695 7697 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7696 7698 ui.warn(b"(%s)\n" % obsfatemsg)
7697 7699 return ret
7698 7700
7699 7701
7700 7702 @command(
7701 7703 b'verify',
7702 7704 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7703 7705 helpcategory=command.CATEGORY_MAINTENANCE,
7704 7706 )
7705 7707 def verify(ui, repo, **opts):
7706 7708 """verify the integrity of the repository
7707 7709
7708 7710 Verify the integrity of the current repository.
7709 7711
7710 7712 This will perform an extensive check of the repository's
7711 7713 integrity, validating the hashes and checksums of each entry in
7712 7714 the changelog, manifest, and tracked files, as well as the
7713 7715 integrity of their crosslinks and indices.
7714 7716
7715 7717 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7716 7718 for more information about recovery from corruption of the
7717 7719 repository.
7718 7720
7719 7721 Returns 0 on success, 1 if errors are encountered.
7720 7722 """
7721 7723 opts = pycompat.byteskwargs(opts)
7722 7724
7723 7725 level = None
7724 7726 if opts[b'full']:
7725 7727 level = verifymod.VERIFY_FULL
7726 7728 return hg.verify(repo, level)
7727 7729
7728 7730
7729 7731 @command(
7730 7732 b'version',
7731 7733 [] + formatteropts,
7732 7734 helpcategory=command.CATEGORY_HELP,
7733 7735 norepo=True,
7734 7736 intents={INTENT_READONLY},
7735 7737 )
7736 7738 def version_(ui, **opts):
7737 7739 """output version and copyright information
7738 7740
7739 7741 .. container:: verbose
7740 7742
7741 7743 Template:
7742 7744
7743 7745 The following keywords are supported. See also :hg:`help templates`.
7744 7746
7745 7747 :extensions: List of extensions.
7746 7748 :ver: String. Version number.
7747 7749
7748 7750 And each entry of ``{extensions}`` provides the following sub-keywords
7749 7751 in addition to ``{ver}``.
7750 7752
7751 7753 :bundled: Boolean. True if included in the release.
7752 7754 :name: String. Extension name.
7753 7755 """
7754 7756 opts = pycompat.byteskwargs(opts)
7755 7757 if ui.verbose:
7756 7758 ui.pager(b'version')
7757 7759 fm = ui.formatter(b"version", opts)
7758 7760 fm.startitem()
7759 7761 fm.write(
7760 7762 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7761 7763 )
7762 7764 license = _(
7763 7765 b"(see https://mercurial-scm.org for more information)\n"
7764 7766 b"\nCopyright (C) 2005-2020 Matt Mackall and others\n"
7765 7767 b"This is free software; see the source for copying conditions. "
7766 7768 b"There is NO\nwarranty; "
7767 7769 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7768 7770 )
7769 7771 if not ui.quiet:
7770 7772 fm.plain(license)
7771 7773
7772 7774 if ui.verbose:
7773 7775 fm.plain(_(b"\nEnabled extensions:\n\n"))
7774 7776 # format names and versions into columns
7775 7777 names = []
7776 7778 vers = []
7777 7779 isinternals = []
7778 7780 for name, module in sorted(extensions.extensions()):
7779 7781 names.append(name)
7780 7782 vers.append(extensions.moduleversion(module) or None)
7781 7783 isinternals.append(extensions.ismoduleinternal(module))
7782 7784 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7783 7785 if names:
7784 7786 namefmt = b" %%-%ds " % max(len(n) for n in names)
7785 7787 places = [_(b"external"), _(b"internal")]
7786 7788 for n, v, p in zip(names, vers, isinternals):
7787 7789 fn.startitem()
7788 7790 fn.condwrite(ui.verbose, b"name", namefmt, n)
7789 7791 if ui.verbose:
7790 7792 fn.plain(b"%s " % places[p])
7791 7793 fn.data(bundled=p)
7792 7794 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7793 7795 if ui.verbose:
7794 7796 fn.plain(b"\n")
7795 7797 fn.end()
7796 7798 fm.end()
7797 7799
7798 7800
7799 7801 def loadcmdtable(ui, name, cmdtable):
7800 7802 """Load command functions from specified cmdtable
7801 7803 """
7802 7804 overrides = [cmd for cmd in cmdtable if cmd in table]
7803 7805 if overrides:
7804 7806 ui.warn(
7805 7807 _(b"extension '%s' overrides commands: %s\n")
7806 7808 % (name, b" ".join(overrides))
7807 7809 )
7808 7810 table.update(cmdtable)
@@ -1,1103 +1,1131 b''
1 1 # logcmdutil.py - utility for log-like commands
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import itertools
11 11 import os
12 12 import posixpath
13 13
14 14 from .i18n import _
15 15 from .node import (
16 16 nullid,
17 17 wdirid,
18 18 wdirrev,
19 19 )
20 20
21 from .thirdparty import attr
22
21 23 from . import (
22 24 dagop,
23 25 error,
24 26 formatter,
25 27 graphmod,
26 28 match as matchmod,
27 29 mdiff,
28 30 patch,
29 31 pathutil,
30 32 pycompat,
31 33 revset,
32 34 revsetlang,
33 35 scmutil,
34 36 smartset,
35 37 templatekw,
36 38 templater,
37 39 util,
38 40 )
39 41 from .utils import (
40 42 dateutil,
41 43 stringutil,
42 44 )
43 45
44 46
45 47 if pycompat.TYPE_CHECKING:
46 48 from typing import (
47 49 Any,
50 Dict,
51 List,
48 52 Optional,
49 53 Tuple,
50 54 )
51 55
52 for t in (Any, Optional, Tuple):
56 for t in (Any, Dict, List, Optional, Tuple):
53 57 assert t
54 58
55 59
56 60 def getlimit(opts):
57 61 """get the log limit according to option -l/--limit"""
58 62 limit = opts.get(b'limit')
59 63 if limit:
60 64 try:
61 65 limit = int(limit)
62 66 except ValueError:
63 67 raise error.Abort(_(b'limit must be a positive integer'))
64 68 if limit <= 0:
65 69 raise error.Abort(_(b'limit must be positive'))
66 70 else:
67 71 limit = None
68 72 return limit
69 73
70 74
71 75 def diffordiffstat(
72 76 ui,
73 77 repo,
74 78 diffopts,
75 79 ctx1,
76 80 ctx2,
77 81 match,
78 82 changes=None,
79 83 stat=False,
80 84 fp=None,
81 85 graphwidth=0,
82 86 prefix=b'',
83 87 root=b'',
84 88 listsubrepos=False,
85 89 hunksfilterfn=None,
86 90 ):
87 91 '''show diff or diffstat.'''
88 92 if root:
89 93 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
90 94 else:
91 95 relroot = b''
92 96 copysourcematch = None
93 97
94 98 def compose(f, g):
95 99 return lambda x: f(g(x))
96 100
97 101 def pathfn(f):
98 102 return posixpath.join(prefix, f)
99 103
100 104 if relroot != b'':
101 105 # XXX relative roots currently don't work if the root is within a
102 106 # subrepo
103 107 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
104 108 uirelroot = uipathfn(pathfn(relroot))
105 109 relroot += b'/'
106 110 for matchroot in match.files():
107 111 if not matchroot.startswith(relroot):
108 112 ui.warn(
109 113 _(b'warning: %s not inside relative root %s\n')
110 114 % (uipathfn(pathfn(matchroot)), uirelroot)
111 115 )
112 116
113 117 relrootmatch = scmutil.match(ctx2, pats=[relroot], default=b'path')
114 118 match = matchmod.intersectmatchers(match, relrootmatch)
115 119 copysourcematch = relrootmatch
116 120
117 121 checkroot = repo.ui.configbool(
118 122 b'devel', b'all-warnings'
119 123 ) or repo.ui.configbool(b'devel', b'check-relroot')
120 124
121 125 def relrootpathfn(f):
122 126 if checkroot and not f.startswith(relroot):
123 127 raise AssertionError(
124 128 b"file %s doesn't start with relroot %s" % (f, relroot)
125 129 )
126 130 return f[len(relroot) :]
127 131
128 132 pathfn = compose(relrootpathfn, pathfn)
129 133
130 134 if stat:
131 135 diffopts = diffopts.copy(context=0, noprefix=False)
132 136 width = 80
133 137 if not ui.plain():
134 138 width = ui.termwidth() - graphwidth
135 139 # If an explicit --root was given, don't respect ui.relative-paths
136 140 if not relroot:
137 141 pathfn = compose(scmutil.getuipathfn(repo), pathfn)
138 142
139 143 chunks = ctx2.diff(
140 144 ctx1,
141 145 match,
142 146 changes,
143 147 opts=diffopts,
144 148 pathfn=pathfn,
145 149 copysourcematch=copysourcematch,
146 150 hunksfilterfn=hunksfilterfn,
147 151 )
148 152
149 153 if fp is not None or ui.canwritewithoutlabels():
150 154 out = fp or ui
151 155 if stat:
152 156 chunks = [patch.diffstat(util.iterlines(chunks), width=width)]
153 157 for chunk in util.filechunkiter(util.chunkbuffer(chunks)):
154 158 out.write(chunk)
155 159 else:
156 160 if stat:
157 161 chunks = patch.diffstatui(util.iterlines(chunks), width=width)
158 162 else:
159 163 chunks = patch.difflabel(
160 164 lambda chunks, **kwargs: chunks, chunks, opts=diffopts
161 165 )
162 166 if ui.canbatchlabeledwrites():
163 167
164 168 def gen():
165 169 for chunk, label in chunks:
166 170 yield ui.label(chunk, label=label)
167 171
168 172 for chunk in util.filechunkiter(util.chunkbuffer(gen())):
169 173 ui.write(chunk)
170 174 else:
171 175 for chunk, label in chunks:
172 176 ui.write(chunk, label=label)
173 177
174 178 node2 = ctx2.node()
175 179 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
176 180 tempnode2 = node2
177 181 try:
178 182 if node2 is not None:
179 183 tempnode2 = ctx2.substate[subpath][1]
180 184 except KeyError:
181 185 # A subrepo that existed in node1 was deleted between node1 and
182 186 # node2 (inclusive). Thus, ctx2's substate won't contain that
183 187 # subpath. The best we can do is to ignore it.
184 188 tempnode2 = None
185 189 submatch = matchmod.subdirmatcher(subpath, match)
186 190 subprefix = repo.wvfs.reljoin(prefix, subpath)
187 191 if listsubrepos or match.exact(subpath) or any(submatch.files()):
188 192 sub.diff(
189 193 ui,
190 194 diffopts,
191 195 tempnode2,
192 196 submatch,
193 197 changes=changes,
194 198 stat=stat,
195 199 fp=fp,
196 200 prefix=subprefix,
197 201 )
198 202
199 203
200 204 class changesetdiffer(object):
201 205 """Generate diff of changeset with pre-configured filtering functions"""
202 206
203 207 def _makefilematcher(self, ctx):
204 208 return scmutil.matchall(ctx.repo())
205 209
206 210 def _makehunksfilter(self, ctx):
207 211 return None
208 212
209 213 def showdiff(self, ui, ctx, diffopts, graphwidth=0, stat=False):
210 214 diffordiffstat(
211 215 ui,
212 216 ctx.repo(),
213 217 diffopts,
214 218 ctx.p1(),
215 219 ctx,
216 220 match=self._makefilematcher(ctx),
217 221 stat=stat,
218 222 graphwidth=graphwidth,
219 223 hunksfilterfn=self._makehunksfilter(ctx),
220 224 )
221 225
222 226
223 227 def changesetlabels(ctx):
224 228 labels = [b'log.changeset', b'changeset.%s' % ctx.phasestr()]
225 229 if ctx.obsolete():
226 230 labels.append(b'changeset.obsolete')
227 231 if ctx.isunstable():
228 232 labels.append(b'changeset.unstable')
229 233 for instability in ctx.instabilities():
230 234 labels.append(b'instability.%s' % instability)
231 235 return b' '.join(labels)
232 236
233 237
234 238 class changesetprinter(object):
235 239 '''show changeset information when templating not requested.'''
236 240
237 241 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
238 242 self.ui = ui
239 243 self.repo = repo
240 244 self.buffered = buffered
241 245 self._differ = differ or changesetdiffer()
242 246 self._diffopts = patch.diffallopts(ui, diffopts)
243 247 self._includestat = diffopts and diffopts.get(b'stat')
244 248 self._includediff = diffopts and diffopts.get(b'patch')
245 249 self.header = {}
246 250 self.hunk = {}
247 251 self.lastheader = None
248 252 self.footer = None
249 253 self._columns = templatekw.getlogcolumns()
250 254
251 255 def flush(self, ctx):
252 256 rev = ctx.rev()
253 257 if rev in self.header:
254 258 h = self.header[rev]
255 259 if h != self.lastheader:
256 260 self.lastheader = h
257 261 self.ui.write(h)
258 262 del self.header[rev]
259 263 if rev in self.hunk:
260 264 self.ui.write(self.hunk[rev])
261 265 del self.hunk[rev]
262 266
263 267 def close(self):
264 268 if self.footer:
265 269 self.ui.write(self.footer)
266 270
267 271 def show(self, ctx, copies=None, **props):
268 272 props = pycompat.byteskwargs(props)
269 273 if self.buffered:
270 274 self.ui.pushbuffer(labeled=True)
271 275 self._show(ctx, copies, props)
272 276 self.hunk[ctx.rev()] = self.ui.popbuffer()
273 277 else:
274 278 self._show(ctx, copies, props)
275 279
276 280 def _show(self, ctx, copies, props):
277 281 '''show a single changeset or file revision'''
278 282 changenode = ctx.node()
279 283 graphwidth = props.get(b'graphwidth', 0)
280 284
281 285 if self.ui.quiet:
282 286 self.ui.write(
283 287 b"%s\n" % scmutil.formatchangeid(ctx), label=b'log.node'
284 288 )
285 289 return
286 290
287 291 columns = self._columns
288 292 self.ui.write(
289 293 columns[b'changeset'] % scmutil.formatchangeid(ctx),
290 294 label=changesetlabels(ctx),
291 295 )
292 296
293 297 # branches are shown first before any other names due to backwards
294 298 # compatibility
295 299 branch = ctx.branch()
296 300 # don't show the default branch name
297 301 if branch != b'default':
298 302 self.ui.write(columns[b'branch'] % branch, label=b'log.branch')
299 303
300 304 for nsname, ns in pycompat.iteritems(self.repo.names):
301 305 # branches has special logic already handled above, so here we just
302 306 # skip it
303 307 if nsname == b'branches':
304 308 continue
305 309 # we will use the templatename as the color name since those two
306 310 # should be the same
307 311 for name in ns.names(self.repo, changenode):
308 312 self.ui.write(ns.logfmt % name, label=b'log.%s' % ns.colorname)
309 313 if self.ui.debugflag:
310 314 self.ui.write(
311 315 columns[b'phase'] % ctx.phasestr(), label=b'log.phase'
312 316 )
313 317 for pctx in scmutil.meaningfulparents(self.repo, ctx):
314 318 label = b'log.parent changeset.%s' % pctx.phasestr()
315 319 self.ui.write(
316 320 columns[b'parent'] % scmutil.formatchangeid(pctx), label=label
317 321 )
318 322
319 323 if self.ui.debugflag:
320 324 mnode = ctx.manifestnode()
321 325 if mnode is None:
322 326 mnode = wdirid
323 327 mrev = wdirrev
324 328 else:
325 329 mrev = self.repo.manifestlog.rev(mnode)
326 330 self.ui.write(
327 331 columns[b'manifest']
328 332 % scmutil.formatrevnode(self.ui, mrev, mnode),
329 333 label=b'ui.debug log.manifest',
330 334 )
331 335 self.ui.write(columns[b'user'] % ctx.user(), label=b'log.user')
332 336 self.ui.write(
333 337 columns[b'date'] % dateutil.datestr(ctx.date()), label=b'log.date'
334 338 )
335 339
336 340 if ctx.isunstable():
337 341 instabilities = ctx.instabilities()
338 342 self.ui.write(
339 343 columns[b'instability'] % b', '.join(instabilities),
340 344 label=b'log.instability',
341 345 )
342 346
343 347 elif ctx.obsolete():
344 348 self._showobsfate(ctx)
345 349
346 350 self._exthook(ctx)
347 351
348 352 if self.ui.debugflag:
349 353 files = ctx.p1().status(ctx)
350 354 for key, value in zip(
351 355 [b'files', b'files+', b'files-'],
352 356 [files.modified, files.added, files.removed],
353 357 ):
354 358 if value:
355 359 self.ui.write(
356 360 columns[key] % b" ".join(value),
357 361 label=b'ui.debug log.files',
358 362 )
359 363 elif ctx.files() and self.ui.verbose:
360 364 self.ui.write(
361 365 columns[b'files'] % b" ".join(ctx.files()),
362 366 label=b'ui.note log.files',
363 367 )
364 368 if copies and self.ui.verbose:
365 369 copies = [b'%s (%s)' % c for c in copies]
366 370 self.ui.write(
367 371 columns[b'copies'] % b' '.join(copies),
368 372 label=b'ui.note log.copies',
369 373 )
370 374
371 375 extra = ctx.extra()
372 376 if extra and self.ui.debugflag:
373 377 for key, value in sorted(extra.items()):
374 378 self.ui.write(
375 379 columns[b'extra'] % (key, stringutil.escapestr(value)),
376 380 label=b'ui.debug log.extra',
377 381 )
378 382
379 383 description = ctx.description().strip()
380 384 if description:
381 385 if self.ui.verbose:
382 386 self.ui.write(
383 387 _(b"description:\n"), label=b'ui.note log.description'
384 388 )
385 389 self.ui.write(description, label=b'ui.note log.description')
386 390 self.ui.write(b"\n\n")
387 391 else:
388 392 self.ui.write(
389 393 columns[b'summary'] % description.splitlines()[0],
390 394 label=b'log.summary',
391 395 )
392 396 self.ui.write(b"\n")
393 397
394 398 self._showpatch(ctx, graphwidth)
395 399
396 400 def _showobsfate(self, ctx):
397 401 # TODO: do not depend on templater
398 402 tres = formatter.templateresources(self.repo.ui, self.repo)
399 403 t = formatter.maketemplater(
400 404 self.repo.ui,
401 405 b'{join(obsfate, "\n")}',
402 406 defaults=templatekw.keywords,
403 407 resources=tres,
404 408 )
405 409 obsfate = t.renderdefault({b'ctx': ctx}).splitlines()
406 410
407 411 if obsfate:
408 412 for obsfateline in obsfate:
409 413 self.ui.write(
410 414 self._columns[b'obsolete'] % obsfateline,
411 415 label=b'log.obsfate',
412 416 )
413 417
414 418 def _exthook(self, ctx):
415 419 '''empty method used by extension as a hook point
416 420 '''
417 421
418 422 def _showpatch(self, ctx, graphwidth=0):
419 423 if self._includestat:
420 424 self._differ.showdiff(
421 425 self.ui, ctx, self._diffopts, graphwidth, stat=True
422 426 )
423 427 if self._includestat and self._includediff:
424 428 self.ui.write(b"\n")
425 429 if self._includediff:
426 430 self._differ.showdiff(
427 431 self.ui, ctx, self._diffopts, graphwidth, stat=False
428 432 )
429 433 if self._includestat or self._includediff:
430 434 self.ui.write(b"\n")
431 435
432 436
433 437 class changesetformatter(changesetprinter):
434 438 """Format changeset information by generic formatter"""
435 439
436 440 def __init__(
437 441 self, ui, repo, fm, differ=None, diffopts=None, buffered=False
438 442 ):
439 443 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
440 444 self._diffopts = patch.difffeatureopts(ui, diffopts, git=True)
441 445 self._fm = fm
442 446
443 447 def close(self):
444 448 self._fm.end()
445 449
446 450 def _show(self, ctx, copies, props):
447 451 '''show a single changeset or file revision'''
448 452 fm = self._fm
449 453 fm.startitem()
450 454 fm.context(ctx=ctx)
451 455 fm.data(rev=scmutil.intrev(ctx), node=fm.hexfunc(scmutil.binnode(ctx)))
452 456
453 457 datahint = fm.datahint()
454 458 if self.ui.quiet and not datahint:
455 459 return
456 460
457 461 fm.data(
458 462 branch=ctx.branch(),
459 463 phase=ctx.phasestr(),
460 464 user=ctx.user(),
461 465 date=fm.formatdate(ctx.date()),
462 466 desc=ctx.description(),
463 467 bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'),
464 468 tags=fm.formatlist(ctx.tags(), name=b'tag'),
465 469 parents=fm.formatlist(
466 470 [fm.hexfunc(c.node()) for c in ctx.parents()], name=b'node'
467 471 ),
468 472 )
469 473
470 474 if self.ui.debugflag or b'manifest' in datahint:
471 475 fm.data(manifest=fm.hexfunc(ctx.manifestnode() or wdirid))
472 476 if self.ui.debugflag or b'extra' in datahint:
473 477 fm.data(extra=fm.formatdict(ctx.extra()))
474 478
475 479 if (
476 480 self.ui.debugflag
477 481 or b'modified' in datahint
478 482 or b'added' in datahint
479 483 or b'removed' in datahint
480 484 ):
481 485 files = ctx.p1().status(ctx)
482 486 fm.data(
483 487 modified=fm.formatlist(files.modified, name=b'file'),
484 488 added=fm.formatlist(files.added, name=b'file'),
485 489 removed=fm.formatlist(files.removed, name=b'file'),
486 490 )
487 491
488 492 verbose = not self.ui.debugflag and self.ui.verbose
489 493 if verbose or b'files' in datahint:
490 494 fm.data(files=fm.formatlist(ctx.files(), name=b'file'))
491 495 if verbose and copies or b'copies' in datahint:
492 496 fm.data(
493 497 copies=fm.formatdict(copies or {}, key=b'name', value=b'source')
494 498 )
495 499
496 500 if self._includestat or b'diffstat' in datahint:
497 501 self.ui.pushbuffer()
498 502 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
499 503 fm.data(diffstat=self.ui.popbuffer())
500 504 if self._includediff or b'diff' in datahint:
501 505 self.ui.pushbuffer()
502 506 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
503 507 fm.data(diff=self.ui.popbuffer())
504 508
505 509
506 510 class changesettemplater(changesetprinter):
507 511 '''format changeset information.
508 512
509 513 Note: there are a variety of convenience functions to build a
510 514 changesettemplater for common cases. See functions such as:
511 515 maketemplater, changesetdisplayer, buildcommittemplate, or other
512 516 functions that use changesest_templater.
513 517 '''
514 518
515 519 # Arguments before "buffered" used to be positional. Consider not
516 520 # adding/removing arguments before "buffered" to not break callers.
517 521 def __init__(
518 522 self, ui, repo, tmplspec, differ=None, diffopts=None, buffered=False
519 523 ):
520 524 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
521 525 # tres is shared with _graphnodeformatter()
522 526 self._tresources = tres = formatter.templateresources(ui, repo)
523 527 self.t = formatter.loadtemplater(
524 528 ui,
525 529 tmplspec,
526 530 defaults=templatekw.keywords,
527 531 resources=tres,
528 532 cache=templatekw.defaulttempl,
529 533 )
530 534 self._counter = itertools.count()
531 535
532 536 self._tref = tmplspec.ref
533 537 self._parts = {
534 538 b'header': b'',
535 539 b'footer': b'',
536 540 tmplspec.ref: tmplspec.ref,
537 541 b'docheader': b'',
538 542 b'docfooter': b'',
539 543 b'separator': b'',
540 544 }
541 545 if tmplspec.mapfile:
542 546 # find correct templates for current mode, for backward
543 547 # compatibility with 'log -v/-q/--debug' using a mapfile
544 548 tmplmodes = [
545 549 (True, b''),
546 550 (self.ui.verbose, b'_verbose'),
547 551 (self.ui.quiet, b'_quiet'),
548 552 (self.ui.debugflag, b'_debug'),
549 553 ]
550 554 for mode, postfix in tmplmodes:
551 555 for t in self._parts:
552 556 cur = t + postfix
553 557 if mode and cur in self.t:
554 558 self._parts[t] = cur
555 559 else:
556 560 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
557 561 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
558 562 self._parts.update(m)
559 563
560 564 if self._parts[b'docheader']:
561 565 self.ui.write(self.t.render(self._parts[b'docheader'], {}))
562 566
563 567 def close(self):
564 568 if self._parts[b'docfooter']:
565 569 if not self.footer:
566 570 self.footer = b""
567 571 self.footer += self.t.render(self._parts[b'docfooter'], {})
568 572 return super(changesettemplater, self).close()
569 573
570 574 def _show(self, ctx, copies, props):
571 575 '''show a single changeset or file revision'''
572 576 props = props.copy()
573 577 props[b'ctx'] = ctx
574 578 props[b'index'] = index = next(self._counter)
575 579 props[b'revcache'] = {b'copies': copies}
576 580 graphwidth = props.get(b'graphwidth', 0)
577 581
578 582 # write separator, which wouldn't work well with the header part below
579 583 # since there's inherently a conflict between header (across items) and
580 584 # separator (per item)
581 585 if self._parts[b'separator'] and index > 0:
582 586 self.ui.write(self.t.render(self._parts[b'separator'], {}))
583 587
584 588 # write header
585 589 if self._parts[b'header']:
586 590 h = self.t.render(self._parts[b'header'], props)
587 591 if self.buffered:
588 592 self.header[ctx.rev()] = h
589 593 else:
590 594 if self.lastheader != h:
591 595 self.lastheader = h
592 596 self.ui.write(h)
593 597
594 598 # write changeset metadata, then patch if requested
595 599 key = self._parts[self._tref]
596 600 self.ui.write(self.t.render(key, props))
597 601 self._exthook(ctx)
598 602 self._showpatch(ctx, graphwidth)
599 603
600 604 if self._parts[b'footer']:
601 605 if not self.footer:
602 606 self.footer = self.t.render(self._parts[b'footer'], props)
603 607
604 608
605 609 def templatespec(tmpl, mapfile):
606 610 assert not (tmpl and mapfile)
607 611 if mapfile:
608 612 return formatter.mapfile_templatespec(b'changeset', mapfile)
609 613 else:
610 614 return formatter.literal_templatespec(tmpl)
611 615
612 616
613 617 def _lookuptemplate(ui, tmpl, style):
614 618 """Find the template matching the given template spec or style
615 619
616 620 See formatter.lookuptemplate() for details.
617 621 """
618 622
619 623 # ui settings
620 624 if not tmpl and not style: # template are stronger than style
621 625 tmpl = ui.config(b'ui', b'logtemplate')
622 626 if tmpl:
623 627 return formatter.literal_templatespec(templater.unquotestring(tmpl))
624 628 else:
625 629 style = util.expandpath(ui.config(b'ui', b'style'))
626 630
627 631 if not tmpl and style:
628 632 mapfile = style
629 633 fp = None
630 634 if not os.path.split(mapfile)[0]:
631 635 (mapname, fp) = templater.try_open_template(
632 636 b'map-cmdline.' + mapfile
633 637 ) or templater.try_open_template(mapfile)
634 638 if mapname:
635 639 mapfile = mapname
636 640 return formatter.mapfile_templatespec(b'changeset', mapfile, fp)
637 641
638 642 return formatter.lookuptemplate(ui, b'changeset', tmpl)
639 643
640 644
641 645 def maketemplater(ui, repo, tmpl, buffered=False):
642 646 """Create a changesettemplater from a literal template 'tmpl'
643 647 byte-string."""
644 648 spec = formatter.literal_templatespec(tmpl)
645 649 return changesettemplater(ui, repo, spec, buffered=buffered)
646 650
647 651
648 652 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
649 653 """show one changeset using template or regular display.
650 654
651 655 Display format will be the first non-empty hit of:
652 656 1. option 'template'
653 657 2. option 'style'
654 658 3. [ui] setting 'logtemplate'
655 659 4. [ui] setting 'style'
656 660 If all of these values are either the unset or the empty string,
657 661 regular display via changesetprinter() is done.
658 662 """
659 663 postargs = (differ, opts, buffered)
660 664 spec = _lookuptemplate(ui, opts.get(b'template'), opts.get(b'style'))
661 665
662 666 # machine-readable formats have slightly different keyword set than
663 667 # plain templates, which are handled by changesetformatter.
664 668 # note that {b'pickle', b'debug'} can also be added to the list if needed.
665 669 if spec.ref in {b'cbor', b'json'}:
666 670 fm = ui.formatter(b'log', opts)
667 671 return changesetformatter(ui, repo, fm, *postargs)
668 672
669 673 if not spec.ref and not spec.tmpl and not spec.mapfile:
670 674 return changesetprinter(ui, repo, *postargs)
671 675
672 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 700 """Build matcher and expanded patterns from log options
677 701
678 702 If --follow, revs are the revisions to follow from.
679 703
680 704 Returns (match, pats, slowpath) where
681 705 - match: a matcher built from the given pats and -I/-X opts
682 706 - pats: patterns used (globs are expanded on Windows)
683 707 - slowpath: True if patterns aren't as simple as scanning filelogs
684 708 """
685 709 # pats/include/exclude are passed to match.match() directly in
686 710 # _matchfiles() revset but walkchangerevs() builds its matcher with
687 711 # scmutil.match(). The difference is input pats are globbed on
688 712 # platforms without shell expansion (windows).
689 713 wctx = repo[None]
690 match, pats = scmutil.matchandpats(wctx, pats, opts)
691 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
714 match, pats = scmutil.matchandpats(wctx, wopts.pats, wopts.opts)
715 slowpath = match.anypats() or (
716 not match.always() and wopts.opts.get(b'removed')
717 )
692 718 if not slowpath:
693 follow = opts.get(b'follow') or opts.get(b'follow_first')
694 if follow and opts.get(b'rev'):
719 follow = wopts.opts.get(b'follow') or wopts.opts.get(b'follow_first')
720 if follow and wopts.opts.get(b'rev'):
695 721 # There may be the case that a path doesn't exist in some (but
696 722 # not all) of the specified start revisions, but let's consider
697 723 # the path is valid. Missing files will be warned by the matcher.
698 724 startctxs = [repo[r] for r in revs]
699 725 for f in match.files():
700 726 found = False
701 727 for c in startctxs:
702 728 if f in c:
703 729 found = True
704 730 elif c.hasdir(f):
705 731 # If a directory exists in any of the start revisions,
706 732 # take the slow path.
707 733 found = slowpath = True
708 734 if not found:
709 735 raise error.Abort(
710 736 _(
711 737 b'cannot follow file not in any of the specified '
712 738 b'revisions: "%s"'
713 739 )
714 740 % f
715 741 )
716 742 elif follow:
717 743 for f in match.files():
718 744 if f not in wctx:
719 745 # If the file exists, it may be a directory, so let it
720 746 # take the slow path.
721 747 if os.path.exists(repo.wjoin(f)):
722 748 slowpath = True
723 749 continue
724 750 else:
725 751 raise error.Abort(
726 752 _(
727 753 b'cannot follow file not in parent '
728 754 b'revision: "%s"'
729 755 )
730 756 % f
731 757 )
732 758 filelog = repo.file(f)
733 759 if not filelog:
734 760 # A file exists in wdir but not in history, which means
735 761 # the file isn't committed yet.
736 762 raise error.Abort(
737 763 _(b'cannot follow nonexistent file: "%s"') % f
738 764 )
739 765 else:
740 766 for f in match.files():
741 767 filelog = repo.file(f)
742 768 if not filelog:
743 769 # A zero count may be a directory or deleted file, so
744 770 # try to find matching entries on the slow path.
745 771 slowpath = True
746 772
747 773 # We decided to fall back to the slowpath because at least one
748 774 # of the paths was not a file. Check to see if at least one of them
749 775 # existed in history - in that case, we'll continue down the
750 776 # slowpath; otherwise, we can turn off the slowpath
751 777 if slowpath:
752 778 for path in match.files():
753 779 if path == b'.' or path in repo.store:
754 780 break
755 781 else:
756 782 slowpath = False
757 783
758 784 return match, pats, slowpath
759 785
760 786
761 787 def _fileancestors(repo, revs, match, followfirst):
762 788 fctxs = []
763 789 for r in revs:
764 790 ctx = repo[r]
765 791 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
766 792
767 793 # When displaying a revision with --patch --follow FILE, we have
768 794 # to know which file of the revision must be diffed. With
769 795 # --follow, we want the names of the ancestors of FILE in the
770 796 # revision, stored in "fcache". "fcache" is populated as a side effect
771 797 # of the graph traversal.
772 798 fcache = {}
773 799
774 800 def filematcher(ctx):
775 801 return scmutil.matchfiles(repo, fcache.get(scmutil.intrev(ctx), []))
776 802
777 803 def revgen():
778 804 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
779 805 fcache[rev] = [c.path() for c in cs]
780 806 yield rev
781 807
782 808 return smartset.generatorset(revgen(), iterasc=False), filematcher
783 809
784 810
785 811 def _makenofollowfilematcher(repo, pats, opts):
786 812 '''hook for extensions to override the filematcher for non-follow cases'''
787 813 return None
788 814
789 815
790 816 _opt2logrevset = {
791 817 b'no_merges': (b'not merge()', None),
792 818 b'only_merges': (b'merge()', None),
793 819 b'_matchfiles': (None, b'_matchfiles(%ps)'),
794 820 b'date': (b'date(%s)', None),
795 821 b'branch': (b'branch(%s)', b'%lr'),
796 822 b'_patslog': (b'filelog(%s)', b'%lr'),
797 823 b'keyword': (b'keyword(%s)', b'%lr'),
798 824 b'prune': (b'ancestors(%s)', b'not %lr'),
799 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 830 """Return a revset string built from log options and file patterns"""
805 opts = dict(opts)
831 opts = dict(wopts.opts)
806 832 # follow or not follow?
807 833 follow = opts.get(b'follow') or opts.get(b'follow_first')
808 834
809 835 # branch and only_branch are really aliases and must be handled at
810 836 # the same time
811 837 opts[b'branch'] = opts.get(b'branch', []) + opts.get(b'only_branch', [])
812 838 opts[b'branch'] = [repo.lookupbranch(b) for b in opts[b'branch']]
813 839
814 840 if slowpath:
815 841 # See walkchangerevs() slow path.
816 842 #
817 843 # pats/include/exclude cannot be represented as separate
818 844 # revset expressions as their filtering logic applies at file
819 845 # level. For instance "-I a -X b" matches a revision touching
820 846 # "a" and "b" while "file(a) and not file(b)" does
821 847 # not. Besides, filesets are evaluated against the working
822 848 # directory.
823 849 matchargs = [b'r:', b'd:relpath']
824 for p in pats:
850 for p in wopts.pats:
825 851 matchargs.append(b'p:' + p)
826 852 for p in opts.get(b'include', []):
827 853 matchargs.append(b'i:' + p)
828 854 for p in opts.get(b'exclude', []):
829 855 matchargs.append(b'x:' + p)
830 856 opts[b'_matchfiles'] = matchargs
831 857 elif not follow:
832 opts[b'_patslog'] = list(pats)
858 opts[b'_patslog'] = list(wopts.pats)
833 859
834 860 expr = []
835 861 for op, val in sorted(pycompat.iteritems(opts)):
836 862 if not val:
837 863 continue
838 864 if op not in _opt2logrevset:
839 865 continue
840 866 revop, listop = _opt2logrevset[op]
841 867 if revop and b'%' not in revop:
842 868 expr.append(revop)
843 869 elif not listop:
844 870 expr.append(revsetlang.formatspec(revop, val))
845 871 else:
846 872 if revop:
847 873 val = [revsetlang.formatspec(revop, v) for v in val]
848 874 expr.append(revsetlang.formatspec(listop, val))
849 875
850 876 if expr:
851 877 expr = b'(' + b' and '.join(expr) + b')'
852 878 else:
853 879 expr = None
854 880 return expr
855 881
856 882
857 def _initialrevs(repo, opts):
883 def _initialrevs(repo, wopts):
858 884 """Return the initial set of revisions to be filtered or followed"""
859 follow = opts.get(b'follow') or opts.get(b'follow_first')
860 if opts.get(b'rev'):
861 revs = scmutil.revrange(repo, opts[b'rev'])
885 follow = wopts.opts.get(b'follow') or wopts.opts.get(b'follow_first')
886 if wopts.opts.get(b'rev'):
887 revs = scmutil.revrange(repo, wopts.opts[b'rev'])
862 888 elif follow and repo.dirstate.p1() == nullid:
863 889 revs = smartset.baseset()
864 890 elif follow:
865 891 revs = repo.revs(b'.')
866 892 else:
867 893 revs = smartset.spanset(repo)
868 894 revs.reverse()
869 895 return revs
870 896
871 897
872 def getrevs(repo, pats, opts):
873 # type: (Any, Any, Any) -> Tuple[smartset.abstractsmartset, Optional[changesetdiffer]]
898 def getrevs(repo, wopts):
899 # type: (Any, walkopts) -> Tuple[smartset.abstractsmartset, Optional[changesetdiffer]]
874 900 """Return (revs, differ) where revs is a smartset
875 901
876 902 differ is a changesetdiffer with pre-configured file matcher.
877 903 """
878 follow = opts.get(b'follow') or opts.get(b'follow_first')
879 followfirst = opts.get(b'follow_first')
880 limit = getlimit(opts)
881 revs = _initialrevs(repo, opts)
904 follow = wopts.opts.get(b'follow') or wopts.opts.get(b'follow_first')
905 followfirst = wopts.opts.get(b'follow_first')
906 limit = getlimit(wopts.opts)
907 revs = _initialrevs(repo, wopts)
882 908 if not revs:
883 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 913 filematcher = None
886 914 if follow:
887 915 if slowpath or match.always():
888 916 revs = dagop.revancestors(repo, revs, followfirst=followfirst)
889 917 else:
890 918 revs, filematcher = _fileancestors(repo, revs, match, followfirst)
891 919 revs.reverse()
892 920 if filematcher is None:
893 filematcher = _makenofollowfilematcher(repo, pats, opts)
921 filematcher = _makenofollowfilematcher(repo, wopts.pats, wopts.opts)
894 922 if filematcher is None:
895 923
896 924 def filematcher(ctx):
897 925 return match
898 926
899 expr = _makerevset(repo, pats, slowpath, opts)
900 if opts.get(b'graph'):
927 expr = _makerevset(repo, wopts, slowpath)
928 if wopts.opts.get(b'graph'):
901 929 if repo.ui.configbool(b'experimental', b'log.topo'):
902 930 if not revs.istopo():
903 931 revs = dagop.toposort(revs, repo.changelog.parentrevs)
904 932 # TODO: try to iterate the set lazily
905 933 revs = revset.baseset(list(revs), istopo=True)
906 934 elif not (revs.isdescending() or revs.istopo()):
907 935 # User-specified revs might be unsorted
908 936 revs.sort(reverse=True)
909 937 if expr:
910 938 matcher = revset.match(None, expr)
911 939 revs = matcher(repo, revs)
912 940 if limit is not None:
913 941 revs = revs.slice(0, limit)
914 942
915 943 differ = changesetdiffer()
916 944 differ._makefilematcher = filematcher
917 945 return revs, differ
918 946
919 947
920 948 def _parselinerangeopt(repo, opts):
921 949 """Parse --line-range log option and return a list of tuples (filename,
922 950 (fromline, toline)).
923 951 """
924 952 linerangebyfname = []
925 953 for pat in opts.get(b'line_range', []):
926 954 try:
927 955 pat, linerange = pat.rsplit(b',', 1)
928 956 except ValueError:
929 957 raise error.Abort(_(b'malformatted line-range pattern %s') % pat)
930 958 try:
931 959 fromline, toline = map(int, linerange.split(b':'))
932 960 except ValueError:
933 961 raise error.Abort(_(b"invalid line range for %s") % pat)
934 962 msg = _(b"line range pattern '%s' must match exactly one file") % pat
935 963 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
936 964 linerangebyfname.append(
937 965 (fname, util.processlinerange(fromline, toline))
938 966 )
939 967 return linerangebyfname
940 968
941 969
942 970 def getlinerangerevs(repo, userrevs, opts):
943 971 """Return (revs, differ).
944 972
945 973 "revs" are revisions obtained by processing "line-range" log options and
946 974 walking block ancestors of each specified file/line-range.
947 975
948 976 "differ" is a changesetdiffer with pre-configured file matcher and hunks
949 977 filter.
950 978 """
951 979 wctx = repo[None]
952 980
953 981 # Two-levels map of "rev -> file ctx -> [line range]".
954 982 linerangesbyrev = {}
955 983 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
956 984 if fname not in wctx:
957 985 raise error.Abort(
958 986 _(b'cannot follow file not in parent revision: "%s"') % fname
959 987 )
960 988 fctx = wctx.filectx(fname)
961 989 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
962 990 rev = fctx.introrev()
963 991 if rev is None:
964 992 rev = wdirrev
965 993 if rev not in userrevs:
966 994 continue
967 995 linerangesbyrev.setdefault(rev, {}).setdefault(
968 996 fctx.path(), []
969 997 ).append(linerange)
970 998
971 999 def nofilterhunksfn(fctx, hunks):
972 1000 return hunks
973 1001
974 1002 def hunksfilter(ctx):
975 1003 fctxlineranges = linerangesbyrev.get(scmutil.intrev(ctx))
976 1004 if fctxlineranges is None:
977 1005 return nofilterhunksfn
978 1006
979 1007 def filterfn(fctx, hunks):
980 1008 lineranges = fctxlineranges.get(fctx.path())
981 1009 if lineranges is not None:
982 1010 for hr, lines in hunks:
983 1011 if hr is None: # binary
984 1012 yield hr, lines
985 1013 continue
986 1014 if any(mdiff.hunkinrange(hr[2:], lr) for lr in lineranges):
987 1015 yield hr, lines
988 1016 else:
989 1017 for hunk in hunks:
990 1018 yield hunk
991 1019
992 1020 return filterfn
993 1021
994 1022 def filematcher(ctx):
995 1023 files = list(linerangesbyrev.get(scmutil.intrev(ctx), []))
996 1024 return scmutil.matchfiles(repo, files)
997 1025
998 1026 revs = sorted(linerangesbyrev, reverse=True)
999 1027
1000 1028 differ = changesetdiffer()
1001 1029 differ._makefilematcher = filematcher
1002 1030 differ._makehunksfilter = hunksfilter
1003 1031 return smartset.baseset(revs), differ
1004 1032
1005 1033
1006 1034 def _graphnodeformatter(ui, displayer):
1007 1035 spec = ui.config(b'ui', b'graphnodetemplate')
1008 1036 if not spec:
1009 1037 return templatekw.getgraphnode # fast path for "{graphnode}"
1010 1038
1011 1039 spec = templater.unquotestring(spec)
1012 1040 if isinstance(displayer, changesettemplater):
1013 1041 # reuse cache of slow templates
1014 1042 tres = displayer._tresources
1015 1043 else:
1016 1044 tres = formatter.templateresources(ui)
1017 1045 templ = formatter.maketemplater(
1018 1046 ui, spec, defaults=templatekw.keywords, resources=tres
1019 1047 )
1020 1048
1021 1049 def formatnode(repo, ctx, cache):
1022 1050 props = {b'ctx': ctx, b'repo': repo}
1023 1051 return templ.renderdefault(props)
1024 1052
1025 1053 return formatnode
1026 1054
1027 1055
1028 1056 def displaygraph(ui, repo, dag, displayer, edgefn, getcopies=None, props=None):
1029 1057 props = props or {}
1030 1058 formatnode = _graphnodeformatter(ui, displayer)
1031 1059 state = graphmod.asciistate()
1032 1060 styles = state.styles
1033 1061
1034 1062 # only set graph styling if HGPLAIN is not set.
1035 1063 if ui.plain(b'graph'):
1036 1064 # set all edge styles to |, the default pre-3.8 behaviour
1037 1065 styles.update(dict.fromkeys(styles, b'|'))
1038 1066 else:
1039 1067 edgetypes = {
1040 1068 b'parent': graphmod.PARENT,
1041 1069 b'grandparent': graphmod.GRANDPARENT,
1042 1070 b'missing': graphmod.MISSINGPARENT,
1043 1071 }
1044 1072 for name, key in edgetypes.items():
1045 1073 # experimental config: experimental.graphstyle.*
1046 1074 styles[key] = ui.config(
1047 1075 b'experimental', b'graphstyle.%s' % name, styles[key]
1048 1076 )
1049 1077 if not styles[key]:
1050 1078 styles[key] = None
1051 1079
1052 1080 # experimental config: experimental.graphshorten
1053 1081 state.graphshorten = ui.configbool(b'experimental', b'graphshorten')
1054 1082
1055 1083 formatnode_cache = {}
1056 1084 for rev, type, ctx, parents in dag:
1057 1085 char = formatnode(repo, ctx, formatnode_cache)
1058 1086 copies = getcopies(ctx) if getcopies else None
1059 1087 edges = edgefn(type, char, state, rev, parents)
1060 1088 firstedge = next(edges)
1061 1089 width = firstedge[2]
1062 1090 displayer.show(
1063 1091 ctx, copies=copies, graphwidth=width, **pycompat.strkwargs(props)
1064 1092 )
1065 1093 lines = displayer.hunk.pop(rev).split(b'\n')
1066 1094 if not lines[-1]:
1067 1095 del lines[-1]
1068 1096 displayer.flush(ctx)
1069 1097 for type, char, width, coldata in itertools.chain([firstedge], edges):
1070 1098 graphmod.ascii(ui, state, type, char, lines, coldata)
1071 1099 lines = []
1072 1100 displayer.close()
1073 1101
1074 1102
1075 1103 def displaygraphrevs(ui, repo, revs, displayer, getrenamed):
1076 1104 revdag = graphmod.dagwalker(repo, revs)
1077 1105 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
1078 1106
1079 1107
1080 1108 def displayrevs(ui, repo, revs, displayer, getcopies):
1081 1109 for rev in revs:
1082 1110 ctx = repo[rev]
1083 1111 copies = getcopies(ctx) if getcopies else None
1084 1112 displayer.show(ctx, copies=copies)
1085 1113 displayer.flush(ctx)
1086 1114 displayer.close()
1087 1115
1088 1116
1089 1117 def checkunsupportedgraphflags(pats, opts):
1090 1118 for op in [b"newest_first"]:
1091 1119 if op in opts and opts[op]:
1092 1120 raise error.Abort(
1093 1121 _(b"-G/--graph option is incompatible with --%s")
1094 1122 % op.replace(b"_", b"-")
1095 1123 )
1096 1124
1097 1125
1098 1126 def graphrevs(repo, nodes, opts):
1099 1127 limit = getlimit(opts)
1100 1128 nodes.reverse()
1101 1129 if limit is not None:
1102 1130 nodes = nodes[:limit]
1103 1131 return graphmod.nodes(repo, nodes)
@@ -1,48 +1,50 b''
1 1 from __future__ import absolute_import
2 from mercurial.thirdparty import attr
2 3 from mercurial import (
3 4 cmdutil,
4 5 commands,
5 6 extensions,
6 7 logcmdutil,
7 8 revsetlang,
8 9 smartset,
9 10 )
10 11
11 12 from mercurial.utils import stringutil
12 13
13 14
14 def logrevset(repo, pats, opts):
15 revs = logcmdutil._initialrevs(repo, opts)
15 def logrevset(repo, wopts):
16 revs = logcmdutil._initialrevs(repo, wopts)
16 17 if not revs:
17 18 return None
18 match, pats, slowpath = logcmdutil._makematcher(repo, revs, pats, opts)
19 return logcmdutil._makerevset(repo, pats, slowpath, opts)
19 match, pats, slowpath = logcmdutil._makematcher(repo, revs, wopts)
20 wopts = attr.evolve(wopts, pats=pats)
21 return logcmdutil._makerevset(repo, wopts, slowpath)
20 22
21 23
22 24 def uisetup(ui):
23 def printrevset(orig, repo, pats, opts):
24 revs, filematcher = orig(repo, pats, opts)
25 if opts.get(b'print_revset'):
26 expr = logrevset(repo, pats, opts)
25 def printrevset(orig, repo, wopts):
26 revs, filematcher = orig(repo, wopts)
27 if wopts.opts.get(b'print_revset'):
28 expr = logrevset(repo, wopts)
27 29 if expr:
28 30 tree = revsetlang.parse(expr)
29 31 tree = revsetlang.analyze(tree)
30 32 else:
31 33 tree = []
32 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 36 ui.write(revsetlang.prettyformat(tree) + b'\n')
35 37 ui.write(stringutil.prettyrepr(revs) + b'\n')
36 38 revs = smartset.baseset() # display no revisions
37 39 return revs, filematcher
38 40
39 41 extensions.wrapfunction(logcmdutil, 'getrevs', printrevset)
40 42 aliases, entry = cmdutil.findcmd(b'log', commands.table)
41 43 entry[1].append(
42 44 (
43 45 b'',
44 46 b'print-revset',
45 47 False,
46 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