##// END OF EJS Templates
scmutil: introduce deprecated alias for revpair()...
Martin von Zweigbergk -
r37268:7c0f40f4 default
parent child Browse files
Show More
@@ -1,427 +1,427 b''
1 1 # extdiff.py - external diff program support for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.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 '''command to allow external programs to compare revisions
9 9
10 10 The extdiff Mercurial extension allows you to use external programs
11 11 to compare revisions, or revision with working directory. The external
12 12 diff programs are called with a configurable set of options and two
13 13 non-option arguments: paths to directories containing snapshots of
14 14 files to compare.
15 15
16 16 If there is more than one file being compared and the "child" revision
17 17 is the working directory, any modifications made in the external diff
18 18 program will be copied back to the working directory from the temporary
19 19 directory.
20 20
21 21 The extdiff extension also allows you to configure new diff commands, so
22 22 you do not need to type :hg:`extdiff -p kdiff3` always. ::
23 23
24 24 [extdiff]
25 25 # add new command that runs GNU diff(1) in 'context diff' mode
26 26 cdiff = gdiff -Nprc5
27 27 ## or the old way:
28 28 #cmd.cdiff = gdiff
29 29 #opts.cdiff = -Nprc5
30 30
31 31 # add new command called meld, runs meld (no need to name twice). If
32 32 # the meld executable is not available, the meld tool in [merge-tools]
33 33 # will be used, if available
34 34 meld =
35 35
36 36 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
37 37 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
38 38 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
39 39 # your .vimrc
40 40 vimdiff = gvim -f "+next" \\
41 41 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
42 42
43 43 Tool arguments can include variables that are expanded at runtime::
44 44
45 45 $parent1, $plabel1 - filename, descriptive label of first parent
46 46 $child, $clabel - filename, descriptive label of child revision
47 47 $parent2, $plabel2 - filename, descriptive label of second parent
48 48 $root - repository root
49 49 $parent is an alias for $parent1.
50 50
51 51 The extdiff extension will look in your [diff-tools] and [merge-tools]
52 52 sections for diff tool arguments, when none are specified in [extdiff].
53 53
54 54 ::
55 55
56 56 [extdiff]
57 57 kdiff3 =
58 58
59 59 [diff-tools]
60 60 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
61 61
62 62 You can use -I/-X and list of file or directory names like normal
63 63 :hg:`diff` command. The extdiff extension makes snapshots of only
64 64 needed files, so running the external diff program will actually be
65 65 pretty fast (at least faster than having to compare the entire tree).
66 66 '''
67 67
68 68 from __future__ import absolute_import
69 69
70 70 import os
71 71 import re
72 72 import shutil
73 73 import stat
74 74 import tempfile
75 75 from mercurial.i18n import _
76 76 from mercurial.node import (
77 77 nullid,
78 78 short,
79 79 )
80 80 from mercurial import (
81 81 archival,
82 82 cmdutil,
83 83 error,
84 84 filemerge,
85 85 pycompat,
86 86 registrar,
87 87 scmutil,
88 88 util,
89 89 )
90 90 from mercurial.utils import (
91 91 procutil,
92 92 stringutil,
93 93 )
94 94
95 95 cmdtable = {}
96 96 command = registrar.command(cmdtable)
97 97
98 98 configtable = {}
99 99 configitem = registrar.configitem(configtable)
100 100
101 101 configitem('extdiff', br'opts\..*',
102 102 default='',
103 103 generic=True,
104 104 )
105 105
106 106 configitem('diff-tools', br'.*\.diffargs$',
107 107 default=None,
108 108 generic=True,
109 109 )
110 110
111 111 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
112 112 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
113 113 # be specifying the version(s) of Mercurial they are tested with, or
114 114 # leave the attribute unspecified.
115 115 testedwith = 'ships-with-hg-core'
116 116
117 117 def snapshot(ui, repo, files, node, tmproot, listsubrepos):
118 118 '''snapshot files as of some revision
119 119 if not using snapshot, -I/-X does not work and recursive diff
120 120 in tools like kdiff3 and meld displays too many files.'''
121 121 dirname = os.path.basename(repo.root)
122 122 if dirname == "":
123 123 dirname = "root"
124 124 if node is not None:
125 125 dirname = '%s.%s' % (dirname, short(node))
126 126 base = os.path.join(tmproot, dirname)
127 127 os.mkdir(base)
128 128 fnsandstat = []
129 129
130 130 if node is not None:
131 131 ui.note(_('making snapshot of %d files from rev %s\n') %
132 132 (len(files), short(node)))
133 133 else:
134 134 ui.note(_('making snapshot of %d files from working directory\n') %
135 135 (len(files)))
136 136
137 137 if files:
138 138 repo.ui.setconfig("ui", "archivemeta", False)
139 139
140 140 archival.archive(repo, base, node, 'files',
141 141 matchfn=scmutil.matchfiles(repo, files),
142 142 subrepos=listsubrepos)
143 143
144 144 for fn in sorted(files):
145 145 wfn = util.pconvert(fn)
146 146 ui.note(' %s\n' % wfn)
147 147
148 148 if node is None:
149 149 dest = os.path.join(base, wfn)
150 150
151 151 fnsandstat.append((dest, repo.wjoin(fn), os.lstat(dest)))
152 152 return dirname, fnsandstat
153 153
154 154 def dodiff(ui, repo, cmdline, pats, opts):
155 155 '''Do the actual diff:
156 156
157 157 - copy to a temp structure if diffing 2 internal revisions
158 158 - copy to a temp structure if diffing working revision with
159 159 another one and more than 1 file is changed
160 160 - just invoke the diff for a single file in the working dir
161 161 '''
162 162
163 163 revs = opts.get('rev')
164 164 change = opts.get('change')
165 165 do3way = '$parent2' in cmdline
166 166
167 167 if revs and change:
168 168 msg = _('cannot specify --rev and --change at the same time')
169 169 raise error.Abort(msg)
170 170 elif change:
171 171 node2 = scmutil.revsingle(repo, change, None).node()
172 172 node1a, node1b = repo.changelog.parents(node2)
173 173 else:
174 node1a, node2 = scmutil.revpair(repo, revs)
174 node1a, node2 = scmutil.revpairnodes(repo, revs)
175 175 if not revs:
176 176 node1b = repo.dirstate.p2()
177 177 else:
178 178 node1b = nullid
179 179
180 180 # Disable 3-way merge if there is only one parent
181 181 if do3way:
182 182 if node1b == nullid:
183 183 do3way = False
184 184
185 185 subrepos=opts.get('subrepos')
186 186
187 187 matcher = scmutil.match(repo[node2], pats, opts)
188 188
189 189 if opts.get('patch'):
190 190 if subrepos:
191 191 raise error.Abort(_('--patch cannot be used with --subrepos'))
192 192 if node2 is None:
193 193 raise error.Abort(_('--patch requires two revisions'))
194 194 else:
195 195 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher,
196 196 listsubrepos=subrepos)[:3])
197 197 if do3way:
198 198 mod_b, add_b, rem_b = map(set,
199 199 repo.status(node1b, node2, matcher,
200 200 listsubrepos=subrepos)[:3])
201 201 else:
202 202 mod_b, add_b, rem_b = set(), set(), set()
203 203 modadd = mod_a | add_a | mod_b | add_b
204 204 common = modadd | rem_a | rem_b
205 205 if not common:
206 206 return 0
207 207
208 208 tmproot = tempfile.mkdtemp(prefix='extdiff.')
209 209 try:
210 210 if not opts.get('patch'):
211 211 # Always make a copy of node1a (and node1b, if applicable)
212 212 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
213 213 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot,
214 214 subrepos)[0]
215 215 rev1a = '@%d' % repo[node1a].rev()
216 216 if do3way:
217 217 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
218 218 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot,
219 219 subrepos)[0]
220 220 rev1b = '@%d' % repo[node1b].rev()
221 221 else:
222 222 dir1b = None
223 223 rev1b = ''
224 224
225 225 fnsandstat = []
226 226
227 227 # If node2 in not the wc or there is >1 change, copy it
228 228 dir2root = ''
229 229 rev2 = ''
230 230 if node2:
231 231 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
232 232 rev2 = '@%d' % repo[node2].rev()
233 233 elif len(common) > 1:
234 234 #we only actually need to get the files to copy back to
235 235 #the working dir in this case (because the other cases
236 236 #are: diffing 2 revisions or single file -- in which case
237 237 #the file is already directly passed to the diff tool).
238 238 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot,
239 239 subrepos)
240 240 else:
241 241 # This lets the diff tool open the changed file directly
242 242 dir2 = ''
243 243 dir2root = repo.root
244 244
245 245 label1a = rev1a
246 246 label1b = rev1b
247 247 label2 = rev2
248 248
249 249 # If only one change, diff the files instead of the directories
250 250 # Handle bogus modifies correctly by checking if the files exist
251 251 if len(common) == 1:
252 252 common_file = util.localpath(common.pop())
253 253 dir1a = os.path.join(tmproot, dir1a, common_file)
254 254 label1a = common_file + rev1a
255 255 if not os.path.isfile(dir1a):
256 256 dir1a = os.devnull
257 257 if do3way:
258 258 dir1b = os.path.join(tmproot, dir1b, common_file)
259 259 label1b = common_file + rev1b
260 260 if not os.path.isfile(dir1b):
261 261 dir1b = os.devnull
262 262 dir2 = os.path.join(dir2root, dir2, common_file)
263 263 label2 = common_file + rev2
264 264 else:
265 265 template = 'hg-%h.patch'
266 266 cmdutil.export(repo, [repo[node1a].rev(), repo[node2].rev()],
267 267 fntemplate=repo.vfs.reljoin(tmproot, template),
268 268 match=matcher)
269 269 label1a = cmdutil.makefilename(repo[node1a], template)
270 270 label2 = cmdutil.makefilename(repo[node2], template)
271 271 dir1a = repo.vfs.reljoin(tmproot, label1a)
272 272 dir2 = repo.vfs.reljoin(tmproot, label2)
273 273 dir1b = None
274 274 label1b = None
275 275 fnsandstat = []
276 276
277 277 # Function to quote file/dir names in the argument string.
278 278 # When not operating in 3-way mode, an empty string is
279 279 # returned for parent2
280 280 replace = {'parent': dir1a, 'parent1': dir1a, 'parent2': dir1b,
281 281 'plabel1': label1a, 'plabel2': label1b,
282 282 'clabel': label2, 'child': dir2,
283 283 'root': repo.root}
284 284 def quote(match):
285 285 pre = match.group(2)
286 286 key = match.group(3)
287 287 if not do3way and key == 'parent2':
288 288 return pre
289 289 return pre + procutil.shellquote(replace[key])
290 290
291 291 # Match parent2 first, so 'parent1?' will match both parent1 and parent
292 292 regex = (br'''(['"]?)([^\s'"$]*)'''
293 293 br'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1')
294 294 if not do3way and not re.search(regex, cmdline):
295 295 cmdline += ' $parent1 $child'
296 296 cmdline = re.sub(regex, quote, cmdline)
297 297
298 298 ui.debug('running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
299 299 ui.system(cmdline, cwd=tmproot, blockedtag='extdiff')
300 300
301 301 for copy_fn, working_fn, st in fnsandstat:
302 302 cpstat = os.lstat(copy_fn)
303 303 # Some tools copy the file and attributes, so mtime may not detect
304 304 # all changes. A size check will detect more cases, but not all.
305 305 # The only certain way to detect every case is to diff all files,
306 306 # which could be expensive.
307 307 # copyfile() carries over the permission, so the mode check could
308 308 # be in an 'elif' branch, but for the case where the file has
309 309 # changed without affecting mtime or size.
310 310 if (cpstat[stat.ST_MTIME] != st[stat.ST_MTIME]
311 311 or cpstat.st_size != st.st_size
312 312 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)):
313 313 ui.debug('file changed while diffing. '
314 314 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
315 315 util.copyfile(copy_fn, working_fn)
316 316
317 317 return 1
318 318 finally:
319 319 ui.note(_('cleaning up temp directory\n'))
320 320 shutil.rmtree(tmproot)
321 321
322 322 extdiffopts = [
323 323 ('o', 'option', [],
324 324 _('pass option to comparison program'), _('OPT')),
325 325 ('r', 'rev', [], _('revision'), _('REV')),
326 326 ('c', 'change', '', _('change made by revision'), _('REV')),
327 327 ('', 'patch', None, _('compare patches for two revisions'))
328 328 ] + cmdutil.walkopts + cmdutil.subrepoopts
329 329
330 330 @command('extdiff',
331 331 [('p', 'program', '', _('comparison program to run'), _('CMD')),
332 332 ] + extdiffopts,
333 333 _('hg extdiff [OPT]... [FILE]...'),
334 334 inferrepo=True)
335 335 def extdiff(ui, repo, *pats, **opts):
336 336 '''use external program to diff repository (or selected files)
337 337
338 338 Show differences between revisions for the specified files, using
339 339 an external program. The default program used is diff, with
340 340 default options "-Npru".
341 341
342 342 To select a different program, use the -p/--program option. The
343 343 program will be passed the names of two directories to compare. To
344 344 pass additional options to the program, use -o/--option. These
345 345 will be passed before the names of the directories to compare.
346 346
347 347 When two revision arguments are given, then changes are shown
348 348 between those revisions. If only one revision is specified then
349 349 that revision is compared to the working directory, and, when no
350 350 revisions are specified, the working directory files are compared
351 351 to its parent.'''
352 352 opts = pycompat.byteskwargs(opts)
353 353 program = opts.get('program')
354 354 option = opts.get('option')
355 355 if not program:
356 356 program = 'diff'
357 357 option = option or ['-Npru']
358 358 cmdline = ' '.join(map(procutil.shellquote, [program] + option))
359 359 return dodiff(ui, repo, cmdline, pats, opts)
360 360
361 361 class savedcmd(object):
362 362 """use external program to diff repository (or selected files)
363 363
364 364 Show differences between revisions for the specified files, using
365 365 the following program::
366 366
367 367 %(path)s
368 368
369 369 When two revision arguments are given, then changes are shown
370 370 between those revisions. If only one revision is specified then
371 371 that revision is compared to the working directory, and, when no
372 372 revisions are specified, the working directory files are compared
373 373 to its parent.
374 374 """
375 375
376 376 def __init__(self, path, cmdline):
377 377 # We can't pass non-ASCII through docstrings (and path is
378 378 # in an unknown encoding anyway)
379 379 docpath = stringutil.escapestr(path)
380 380 self.__doc__ %= {r'path': pycompat.sysstr(stringutil.uirepr(docpath))}
381 381 self._cmdline = cmdline
382 382
383 383 def __call__(self, ui, repo, *pats, **opts):
384 384 opts = pycompat.byteskwargs(opts)
385 385 options = ' '.join(map(procutil.shellquote, opts['option']))
386 386 if options:
387 387 options = ' ' + options
388 388 return dodiff(ui, repo, self._cmdline + options, pats, opts)
389 389
390 390 def uisetup(ui):
391 391 for cmd, path in ui.configitems('extdiff'):
392 392 path = util.expandpath(path)
393 393 if cmd.startswith('cmd.'):
394 394 cmd = cmd[4:]
395 395 if not path:
396 396 path = procutil.findexe(cmd)
397 397 if path is None:
398 398 path = filemerge.findexternaltool(ui, cmd) or cmd
399 399 diffopts = ui.config('extdiff', 'opts.' + cmd)
400 400 cmdline = procutil.shellquote(path)
401 401 if diffopts:
402 402 cmdline += ' ' + diffopts
403 403 elif cmd.startswith('opts.'):
404 404 continue
405 405 else:
406 406 if path:
407 407 # case "cmd = path opts"
408 408 cmdline = path
409 409 diffopts = len(pycompat.shlexsplit(cmdline)) > 1
410 410 else:
411 411 # case "cmd ="
412 412 path = procutil.findexe(cmd)
413 413 if path is None:
414 414 path = filemerge.findexternaltool(ui, cmd) or cmd
415 415 cmdline = procutil.shellquote(path)
416 416 diffopts = False
417 417 # look for diff arguments in [diff-tools] then [merge-tools]
418 418 if not diffopts:
419 419 args = ui.config('diff-tools', cmd+'.diffargs') or \
420 420 ui.config('merge-tools', cmd+'.diffargs')
421 421 if args:
422 422 cmdline += ' ' + args
423 423 command(cmd, extdiffopts[:], _('hg %s [OPTION]... [FILE]...') % cmd,
424 424 inferrepo=True)(savedcmd(path, cmdline))
425 425
426 426 # tell hggettext to extract docstrings from these functions:
427 427 i18nfunctions = [savedcmd]
@@ -1,5639 +1,5639 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 )
23 23 from . import (
24 24 archival,
25 25 bookmarks,
26 26 bundle2,
27 27 changegroup,
28 28 cmdutil,
29 29 copies,
30 30 debugcommands as debugcommandsmod,
31 31 destutil,
32 32 dirstateguard,
33 33 discovery,
34 34 encoding,
35 35 error,
36 36 exchange,
37 37 extensions,
38 38 formatter,
39 39 graphmod,
40 40 hbisect,
41 41 help,
42 42 hg,
43 43 lock as lockmod,
44 44 logcmdutil,
45 45 merge as mergemod,
46 46 obsolete,
47 47 obsutil,
48 48 patch,
49 49 phases,
50 50 pycompat,
51 51 rcutil,
52 52 registrar,
53 53 revsetlang,
54 54 rewriteutil,
55 55 scmutil,
56 56 server,
57 57 streamclone,
58 58 tags as tagsmod,
59 59 templatekw,
60 60 ui as uimod,
61 61 util,
62 62 wireprotoserver,
63 63 )
64 64 from .utils import (
65 65 dateutil,
66 66 procutil,
67 67 stringutil,
68 68 )
69 69
70 70 release = lockmod.release
71 71
72 72 table = {}
73 73 table.update(debugcommandsmod.command._table)
74 74
75 75 command = registrar.command(table)
76 76 readonly = registrar.command.readonly
77 77
78 78 # common command options
79 79
80 80 globalopts = [
81 81 ('R', 'repository', '',
82 82 _('repository root directory or name of overlay bundle file'),
83 83 _('REPO')),
84 84 ('', 'cwd', '',
85 85 _('change working directory'), _('DIR')),
86 86 ('y', 'noninteractive', None,
87 87 _('do not prompt, automatically pick the first choice for all prompts')),
88 88 ('q', 'quiet', None, _('suppress output')),
89 89 ('v', 'verbose', None, _('enable additional output')),
90 90 ('', 'color', '',
91 91 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
92 92 # and should not be translated
93 93 _("when to colorize (boolean, always, auto, never, or debug)"),
94 94 _('TYPE')),
95 95 ('', 'config', [],
96 96 _('set/override config option (use \'section.name=value\')'),
97 97 _('CONFIG')),
98 98 ('', 'debug', None, _('enable debugging output')),
99 99 ('', 'debugger', None, _('start debugger')),
100 100 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
101 101 _('ENCODE')),
102 102 ('', 'encodingmode', encoding.encodingmode,
103 103 _('set the charset encoding mode'), _('MODE')),
104 104 ('', 'traceback', None, _('always print a traceback on exception')),
105 105 ('', 'time', None, _('time how long the command takes')),
106 106 ('', 'profile', None, _('print command execution profile')),
107 107 ('', 'version', None, _('output version information and exit')),
108 108 ('h', 'help', None, _('display help and exit')),
109 109 ('', 'hidden', False, _('consider hidden changesets')),
110 110 ('', 'pager', 'auto',
111 111 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
112 112 ]
113 113
114 114 dryrunopts = cmdutil.dryrunopts
115 115 remoteopts = cmdutil.remoteopts
116 116 walkopts = cmdutil.walkopts
117 117 commitopts = cmdutil.commitopts
118 118 commitopts2 = cmdutil.commitopts2
119 119 formatteropts = cmdutil.formatteropts
120 120 templateopts = cmdutil.templateopts
121 121 logopts = cmdutil.logopts
122 122 diffopts = cmdutil.diffopts
123 123 diffwsopts = cmdutil.diffwsopts
124 124 diffopts2 = cmdutil.diffopts2
125 125 mergetoolopts = cmdutil.mergetoolopts
126 126 similarityopts = cmdutil.similarityopts
127 127 subrepoopts = cmdutil.subrepoopts
128 128 debugrevlogopts = cmdutil.debugrevlogopts
129 129
130 130 # Commands start here, listed alphabetically
131 131
132 132 @command('^add',
133 133 walkopts + subrepoopts + dryrunopts,
134 134 _('[OPTION]... [FILE]...'),
135 135 inferrepo=True)
136 136 def add(ui, repo, *pats, **opts):
137 137 """add the specified files on the next commit
138 138
139 139 Schedule files to be version controlled and added to the
140 140 repository.
141 141
142 142 The files will be added to the repository at the next commit. To
143 143 undo an add before that, see :hg:`forget`.
144 144
145 145 If no names are given, add all files to the repository (except
146 146 files matching ``.hgignore``).
147 147
148 148 .. container:: verbose
149 149
150 150 Examples:
151 151
152 152 - New (unknown) files are added
153 153 automatically by :hg:`add`::
154 154
155 155 $ ls
156 156 foo.c
157 157 $ hg status
158 158 ? foo.c
159 159 $ hg add
160 160 adding foo.c
161 161 $ hg status
162 162 A foo.c
163 163
164 164 - Specific files to be added can be specified::
165 165
166 166 $ ls
167 167 bar.c foo.c
168 168 $ hg status
169 169 ? bar.c
170 170 ? foo.c
171 171 $ hg add bar.c
172 172 $ hg status
173 173 A bar.c
174 174 ? foo.c
175 175
176 176 Returns 0 if all files are successfully added.
177 177 """
178 178
179 179 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
180 180 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
181 181 return rejected and 1 or 0
182 182
183 183 @command('addremove',
184 184 similarityopts + subrepoopts + walkopts + dryrunopts,
185 185 _('[OPTION]... [FILE]...'),
186 186 inferrepo=True)
187 187 def addremove(ui, repo, *pats, **opts):
188 188 """add all new files, delete all missing files
189 189
190 190 Add all new files and remove all missing files from the
191 191 repository.
192 192
193 193 Unless names are given, new files are ignored if they match any of
194 194 the patterns in ``.hgignore``. As with add, these changes take
195 195 effect at the next commit.
196 196
197 197 Use the -s/--similarity option to detect renamed files. This
198 198 option takes a percentage between 0 (disabled) and 100 (files must
199 199 be identical) as its parameter. With a parameter greater than 0,
200 200 this compares every removed file with every added file and records
201 201 those similar enough as renames. Detecting renamed files this way
202 202 can be expensive. After using this option, :hg:`status -C` can be
203 203 used to check which files were identified as moved or renamed. If
204 204 not specified, -s/--similarity defaults to 100 and only renames of
205 205 identical files are detected.
206 206
207 207 .. container:: verbose
208 208
209 209 Examples:
210 210
211 211 - A number of files (bar.c and foo.c) are new,
212 212 while foobar.c has been removed (without using :hg:`remove`)
213 213 from the repository::
214 214
215 215 $ ls
216 216 bar.c foo.c
217 217 $ hg status
218 218 ! foobar.c
219 219 ? bar.c
220 220 ? foo.c
221 221 $ hg addremove
222 222 adding bar.c
223 223 adding foo.c
224 224 removing foobar.c
225 225 $ hg status
226 226 A bar.c
227 227 A foo.c
228 228 R foobar.c
229 229
230 230 - A file foobar.c was moved to foo.c without using :hg:`rename`.
231 231 Afterwards, it was edited slightly::
232 232
233 233 $ ls
234 234 foo.c
235 235 $ hg status
236 236 ! foobar.c
237 237 ? foo.c
238 238 $ hg addremove --similarity 90
239 239 removing foobar.c
240 240 adding foo.c
241 241 recording removal of foobar.c as rename to foo.c (94% similar)
242 242 $ hg status -C
243 243 A foo.c
244 244 foobar.c
245 245 R foobar.c
246 246
247 247 Returns 0 if all files are successfully added.
248 248 """
249 249 opts = pycompat.byteskwargs(opts)
250 250 try:
251 251 sim = float(opts.get('similarity') or 100)
252 252 except ValueError:
253 253 raise error.Abort(_('similarity must be a number'))
254 254 if sim < 0 or sim > 100:
255 255 raise error.Abort(_('similarity must be between 0 and 100'))
256 256 matcher = scmutil.match(repo[None], pats, opts)
257 257 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
258 258
259 259 @command('^annotate|blame',
260 260 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
261 261 ('', 'follow', None,
262 262 _('follow copies/renames and list the filename (DEPRECATED)')),
263 263 ('', 'no-follow', None, _("don't follow copies and renames")),
264 264 ('a', 'text', None, _('treat all files as text')),
265 265 ('u', 'user', None, _('list the author (long with -v)')),
266 266 ('f', 'file', None, _('list the filename')),
267 267 ('d', 'date', None, _('list the date (short with -q)')),
268 268 ('n', 'number', None, _('list the revision number (default)')),
269 269 ('c', 'changeset', None, _('list the changeset')),
270 270 ('l', 'line-number', None, _('show line number at the first appearance')),
271 271 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
272 272 ] + diffwsopts + walkopts + formatteropts,
273 273 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
274 274 inferrepo=True)
275 275 def annotate(ui, repo, *pats, **opts):
276 276 """show changeset information by line for each file
277 277
278 278 List changes in files, showing the revision id responsible for
279 279 each line.
280 280
281 281 This command is useful for discovering when a change was made and
282 282 by whom.
283 283
284 284 If you include --file, --user, or --date, the revision number is
285 285 suppressed unless you also include --number.
286 286
287 287 Without the -a/--text option, annotate will avoid processing files
288 288 it detects as binary. With -a, annotate will annotate the file
289 289 anyway, although the results will probably be neither useful
290 290 nor desirable.
291 291
292 292 Returns 0 on success.
293 293 """
294 294 opts = pycompat.byteskwargs(opts)
295 295 if not pats:
296 296 raise error.Abort(_('at least one filename or pattern is required'))
297 297
298 298 if opts.get('follow'):
299 299 # --follow is deprecated and now just an alias for -f/--file
300 300 # to mimic the behavior of Mercurial before version 1.5
301 301 opts['file'] = True
302 302
303 303 rev = opts.get('rev')
304 304 if rev:
305 305 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
306 306 ctx = scmutil.revsingle(repo, rev)
307 307
308 308 rootfm = ui.formatter('annotate', opts)
309 309 if ui.quiet:
310 310 datefunc = dateutil.shortdate
311 311 else:
312 312 datefunc = dateutil.datestr
313 313 if ctx.rev() is None:
314 314 def hexfn(node):
315 315 if node is None:
316 316 return None
317 317 else:
318 318 return rootfm.hexfunc(node)
319 319 if opts.get('changeset'):
320 320 # omit "+" suffix which is appended to node hex
321 321 def formatrev(rev):
322 322 if rev is None:
323 323 return '%d' % ctx.p1().rev()
324 324 else:
325 325 return '%d' % rev
326 326 else:
327 327 def formatrev(rev):
328 328 if rev is None:
329 329 return '%d+' % ctx.p1().rev()
330 330 else:
331 331 return '%d ' % rev
332 332 def formathex(hex):
333 333 if hex is None:
334 334 return '%s+' % rootfm.hexfunc(ctx.p1().node())
335 335 else:
336 336 return '%s ' % hex
337 337 else:
338 338 hexfn = rootfm.hexfunc
339 339 formatrev = formathex = pycompat.bytestr
340 340
341 341 opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
342 342 ('number', ' ', lambda x: x.fctx.rev(), formatrev),
343 343 ('changeset', ' ', lambda x: hexfn(x.fctx.node()), formathex),
344 344 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
345 345 ('file', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
346 346 ('line_number', ':', lambda x: x.lineno, pycompat.bytestr),
347 347 ]
348 348 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
349 349
350 350 if (not opts.get('user') and not opts.get('changeset')
351 351 and not opts.get('date') and not opts.get('file')):
352 352 opts['number'] = True
353 353
354 354 linenumber = opts.get('line_number') is not None
355 355 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
356 356 raise error.Abort(_('at least one of -n/-c is required for -l'))
357 357
358 358 ui.pager('annotate')
359 359
360 360 if rootfm.isplain():
361 361 def makefunc(get, fmt):
362 362 return lambda x: fmt(get(x))
363 363 else:
364 364 def makefunc(get, fmt):
365 365 return get
366 366 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
367 367 if opts.get(op)]
368 368 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
369 369 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
370 370 if opts.get(op))
371 371
372 372 def bad(x, y):
373 373 raise error.Abort("%s: %s" % (x, y))
374 374
375 375 m = scmutil.match(ctx, pats, opts, badfn=bad)
376 376
377 377 follow = not opts.get('no_follow')
378 378 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
379 379 whitespace=True)
380 380 skiprevs = opts.get('skip')
381 381 if skiprevs:
382 382 skiprevs = scmutil.revrange(repo, skiprevs)
383 383
384 384 for abs in ctx.walk(m):
385 385 fctx = ctx[abs]
386 386 rootfm.startitem()
387 387 rootfm.data(abspath=abs, path=m.rel(abs))
388 388 if not opts.get('text') and fctx.isbinary():
389 389 rootfm.plain(_("%s: binary file\n")
390 390 % ((pats and m.rel(abs)) or abs))
391 391 continue
392 392
393 393 fm = rootfm.nested('lines')
394 394 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
395 395 diffopts=diffopts)
396 396 if not lines:
397 397 fm.end()
398 398 continue
399 399 formats = []
400 400 pieces = []
401 401
402 402 for f, sep in funcmap:
403 403 l = [f(n) for n in lines]
404 404 if fm.isplain():
405 405 sizes = [encoding.colwidth(x) for x in l]
406 406 ml = max(sizes)
407 407 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
408 408 else:
409 409 formats.append(['%s' for x in l])
410 410 pieces.append(l)
411 411
412 412 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
413 413 fm.startitem()
414 414 fm.context(fctx=n.fctx)
415 415 fm.write(fields, "".join(f), *p)
416 416 if n.skip:
417 417 fmt = "* %s"
418 418 else:
419 419 fmt = ": %s"
420 420 fm.write('line', fmt, n.text)
421 421
422 422 if not lines[-1].text.endswith('\n'):
423 423 fm.plain('\n')
424 424 fm.end()
425 425
426 426 rootfm.end()
427 427
428 428 @command('archive',
429 429 [('', 'no-decode', None, _('do not pass files through decoders')),
430 430 ('p', 'prefix', '', _('directory prefix for files in archive'),
431 431 _('PREFIX')),
432 432 ('r', 'rev', '', _('revision to distribute'), _('REV')),
433 433 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
434 434 ] + subrepoopts + walkopts,
435 435 _('[OPTION]... DEST'))
436 436 def archive(ui, repo, dest, **opts):
437 437 '''create an unversioned archive of a repository revision
438 438
439 439 By default, the revision used is the parent of the working
440 440 directory; use -r/--rev to specify a different revision.
441 441
442 442 The archive type is automatically detected based on file
443 443 extension (to override, use -t/--type).
444 444
445 445 .. container:: verbose
446 446
447 447 Examples:
448 448
449 449 - create a zip file containing the 1.0 release::
450 450
451 451 hg archive -r 1.0 project-1.0.zip
452 452
453 453 - create a tarball excluding .hg files::
454 454
455 455 hg archive project.tar.gz -X ".hg*"
456 456
457 457 Valid types are:
458 458
459 459 :``files``: a directory full of files (default)
460 460 :``tar``: tar archive, uncompressed
461 461 :``tbz2``: tar archive, compressed using bzip2
462 462 :``tgz``: tar archive, compressed using gzip
463 463 :``uzip``: zip archive, uncompressed
464 464 :``zip``: zip archive, compressed using deflate
465 465
466 466 The exact name of the destination archive or directory is given
467 467 using a format string; see :hg:`help export` for details.
468 468
469 469 Each member added to an archive file has a directory prefix
470 470 prepended. Use -p/--prefix to specify a format string for the
471 471 prefix. The default is the basename of the archive, with suffixes
472 472 removed.
473 473
474 474 Returns 0 on success.
475 475 '''
476 476
477 477 opts = pycompat.byteskwargs(opts)
478 478 rev = opts.get('rev')
479 479 if rev:
480 480 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
481 481 ctx = scmutil.revsingle(repo, rev)
482 482 if not ctx:
483 483 raise error.Abort(_('no working directory: please specify a revision'))
484 484 node = ctx.node()
485 485 dest = cmdutil.makefilename(ctx, dest)
486 486 if os.path.realpath(dest) == repo.root:
487 487 raise error.Abort(_('repository root cannot be destination'))
488 488
489 489 kind = opts.get('type') or archival.guesskind(dest) or 'files'
490 490 prefix = opts.get('prefix')
491 491
492 492 if dest == '-':
493 493 if kind == 'files':
494 494 raise error.Abort(_('cannot archive plain files to stdout'))
495 495 dest = cmdutil.makefileobj(ctx, dest)
496 496 if not prefix:
497 497 prefix = os.path.basename(repo.root) + '-%h'
498 498
499 499 prefix = cmdutil.makefilename(ctx, prefix)
500 500 match = scmutil.match(ctx, [], opts)
501 501 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
502 502 match, prefix, subrepos=opts.get('subrepos'))
503 503
504 504 @command('backout',
505 505 [('', 'merge', None, _('merge with old dirstate parent after backout')),
506 506 ('', 'commit', None,
507 507 _('commit if no conflicts were encountered (DEPRECATED)')),
508 508 ('', 'no-commit', None, _('do not commit')),
509 509 ('', 'parent', '',
510 510 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
511 511 ('r', 'rev', '', _('revision to backout'), _('REV')),
512 512 ('e', 'edit', False, _('invoke editor on commit messages')),
513 513 ] + mergetoolopts + walkopts + commitopts + commitopts2,
514 514 _('[OPTION]... [-r] REV'))
515 515 def backout(ui, repo, node=None, rev=None, **opts):
516 516 '''reverse effect of earlier changeset
517 517
518 518 Prepare a new changeset with the effect of REV undone in the
519 519 current working directory. If no conflicts were encountered,
520 520 it will be committed immediately.
521 521
522 522 If REV is the parent of the working directory, then this new changeset
523 523 is committed automatically (unless --no-commit is specified).
524 524
525 525 .. note::
526 526
527 527 :hg:`backout` cannot be used to fix either an unwanted or
528 528 incorrect merge.
529 529
530 530 .. container:: verbose
531 531
532 532 Examples:
533 533
534 534 - Reverse the effect of the parent of the working directory.
535 535 This backout will be committed immediately::
536 536
537 537 hg backout -r .
538 538
539 539 - Reverse the effect of previous bad revision 23::
540 540
541 541 hg backout -r 23
542 542
543 543 - Reverse the effect of previous bad revision 23 and
544 544 leave changes uncommitted::
545 545
546 546 hg backout -r 23 --no-commit
547 547 hg commit -m "Backout revision 23"
548 548
549 549 By default, the pending changeset will have one parent,
550 550 maintaining a linear history. With --merge, the pending
551 551 changeset will instead have two parents: the old parent of the
552 552 working directory and a new child of REV that simply undoes REV.
553 553
554 554 Before version 1.7, the behavior without --merge was equivalent
555 555 to specifying --merge followed by :hg:`update --clean .` to
556 556 cancel the merge and leave the child of REV as a head to be
557 557 merged separately.
558 558
559 559 See :hg:`help dates` for a list of formats valid for -d/--date.
560 560
561 561 See :hg:`help revert` for a way to restore files to the state
562 562 of another revision.
563 563
564 564 Returns 0 on success, 1 if nothing to backout or there are unresolved
565 565 files.
566 566 '''
567 567 wlock = lock = None
568 568 try:
569 569 wlock = repo.wlock()
570 570 lock = repo.lock()
571 571 return _dobackout(ui, repo, node, rev, **opts)
572 572 finally:
573 573 release(lock, wlock)
574 574
575 575 def _dobackout(ui, repo, node=None, rev=None, **opts):
576 576 opts = pycompat.byteskwargs(opts)
577 577 if opts.get('commit') and opts.get('no_commit'):
578 578 raise error.Abort(_("cannot use --commit with --no-commit"))
579 579 if opts.get('merge') and opts.get('no_commit'):
580 580 raise error.Abort(_("cannot use --merge with --no-commit"))
581 581
582 582 if rev and node:
583 583 raise error.Abort(_("please specify just one revision"))
584 584
585 585 if not rev:
586 586 rev = node
587 587
588 588 if not rev:
589 589 raise error.Abort(_("please specify a revision to backout"))
590 590
591 591 date = opts.get('date')
592 592 if date:
593 593 opts['date'] = dateutil.parsedate(date)
594 594
595 595 cmdutil.checkunfinished(repo)
596 596 cmdutil.bailifchanged(repo)
597 597 node = scmutil.revsingle(repo, rev).node()
598 598
599 599 op1, op2 = repo.dirstate.parents()
600 600 if not repo.changelog.isancestor(node, op1):
601 601 raise error.Abort(_('cannot backout change that is not an ancestor'))
602 602
603 603 p1, p2 = repo.changelog.parents(node)
604 604 if p1 == nullid:
605 605 raise error.Abort(_('cannot backout a change with no parents'))
606 606 if p2 != nullid:
607 607 if not opts.get('parent'):
608 608 raise error.Abort(_('cannot backout a merge changeset'))
609 609 p = repo.lookup(opts['parent'])
610 610 if p not in (p1, p2):
611 611 raise error.Abort(_('%s is not a parent of %s') %
612 612 (short(p), short(node)))
613 613 parent = p
614 614 else:
615 615 if opts.get('parent'):
616 616 raise error.Abort(_('cannot use --parent on non-merge changeset'))
617 617 parent = p1
618 618
619 619 # the backout should appear on the same branch
620 620 branch = repo.dirstate.branch()
621 621 bheads = repo.branchheads(branch)
622 622 rctx = scmutil.revsingle(repo, hex(parent))
623 623 if not opts.get('merge') and op1 != node:
624 624 dsguard = dirstateguard.dirstateguard(repo, 'backout')
625 625 try:
626 626 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
627 627 'backout')
628 628 stats = mergemod.update(repo, parent, True, True, node, False)
629 629 repo.setparents(op1, op2)
630 630 dsguard.close()
631 631 hg._showstats(repo, stats)
632 632 if stats.unresolvedcount:
633 633 repo.ui.status(_("use 'hg resolve' to retry unresolved "
634 634 "file merges\n"))
635 635 return 1
636 636 finally:
637 637 ui.setconfig('ui', 'forcemerge', '', '')
638 638 lockmod.release(dsguard)
639 639 else:
640 640 hg.clean(repo, node, show_stats=False)
641 641 repo.dirstate.setbranch(branch)
642 642 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
643 643
644 644 if opts.get('no_commit'):
645 645 msg = _("changeset %s backed out, "
646 646 "don't forget to commit.\n")
647 647 ui.status(msg % short(node))
648 648 return 0
649 649
650 650 def commitfunc(ui, repo, message, match, opts):
651 651 editform = 'backout'
652 652 e = cmdutil.getcommiteditor(editform=editform,
653 653 **pycompat.strkwargs(opts))
654 654 if not message:
655 655 # we don't translate commit messages
656 656 message = "Backed out changeset %s" % short(node)
657 657 e = cmdutil.getcommiteditor(edit=True, editform=editform)
658 658 return repo.commit(message, opts.get('user'), opts.get('date'),
659 659 match, editor=e)
660 660 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
661 661 if not newnode:
662 662 ui.status(_("nothing changed\n"))
663 663 return 1
664 664 cmdutil.commitstatus(repo, newnode, branch, bheads)
665 665
666 666 def nice(node):
667 667 return '%d:%s' % (repo.changelog.rev(node), short(node))
668 668 ui.status(_('changeset %s backs out changeset %s\n') %
669 669 (nice(repo.changelog.tip()), nice(node)))
670 670 if opts.get('merge') and op1 != node:
671 671 hg.clean(repo, op1, show_stats=False)
672 672 ui.status(_('merging with changeset %s\n')
673 673 % nice(repo.changelog.tip()))
674 674 try:
675 675 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
676 676 'backout')
677 677 return hg.merge(repo, hex(repo.changelog.tip()))
678 678 finally:
679 679 ui.setconfig('ui', 'forcemerge', '', '')
680 680 return 0
681 681
682 682 @command('bisect',
683 683 [('r', 'reset', False, _('reset bisect state')),
684 684 ('g', 'good', False, _('mark changeset good')),
685 685 ('b', 'bad', False, _('mark changeset bad')),
686 686 ('s', 'skip', False, _('skip testing changeset')),
687 687 ('e', 'extend', False, _('extend the bisect range')),
688 688 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
689 689 ('U', 'noupdate', False, _('do not update to target'))],
690 690 _("[-gbsr] [-U] [-c CMD] [REV]"))
691 691 def bisect(ui, repo, rev=None, extra=None, command=None,
692 692 reset=None, good=None, bad=None, skip=None, extend=None,
693 693 noupdate=None):
694 694 """subdivision search of changesets
695 695
696 696 This command helps to find changesets which introduce problems. To
697 697 use, mark the earliest changeset you know exhibits the problem as
698 698 bad, then mark the latest changeset which is free from the problem
699 699 as good. Bisect will update your working directory to a revision
700 700 for testing (unless the -U/--noupdate option is specified). Once
701 701 you have performed tests, mark the working directory as good or
702 702 bad, and bisect will either update to another candidate changeset
703 703 or announce that it has found the bad revision.
704 704
705 705 As a shortcut, you can also use the revision argument to mark a
706 706 revision as good or bad without checking it out first.
707 707
708 708 If you supply a command, it will be used for automatic bisection.
709 709 The environment variable HG_NODE will contain the ID of the
710 710 changeset being tested. The exit status of the command will be
711 711 used to mark revisions as good or bad: status 0 means good, 125
712 712 means to skip the revision, 127 (command not found) will abort the
713 713 bisection, and any other non-zero exit status means the revision
714 714 is bad.
715 715
716 716 .. container:: verbose
717 717
718 718 Some examples:
719 719
720 720 - start a bisection with known bad revision 34, and good revision 12::
721 721
722 722 hg bisect --bad 34
723 723 hg bisect --good 12
724 724
725 725 - advance the current bisection by marking current revision as good or
726 726 bad::
727 727
728 728 hg bisect --good
729 729 hg bisect --bad
730 730
731 731 - mark the current revision, or a known revision, to be skipped (e.g. if
732 732 that revision is not usable because of another issue)::
733 733
734 734 hg bisect --skip
735 735 hg bisect --skip 23
736 736
737 737 - skip all revisions that do not touch directories ``foo`` or ``bar``::
738 738
739 739 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
740 740
741 741 - forget the current bisection::
742 742
743 743 hg bisect --reset
744 744
745 745 - use 'make && make tests' to automatically find the first broken
746 746 revision::
747 747
748 748 hg bisect --reset
749 749 hg bisect --bad 34
750 750 hg bisect --good 12
751 751 hg bisect --command "make && make tests"
752 752
753 753 - see all changesets whose states are already known in the current
754 754 bisection::
755 755
756 756 hg log -r "bisect(pruned)"
757 757
758 758 - see the changeset currently being bisected (especially useful
759 759 if running with -U/--noupdate)::
760 760
761 761 hg log -r "bisect(current)"
762 762
763 763 - see all changesets that took part in the current bisection::
764 764
765 765 hg log -r "bisect(range)"
766 766
767 767 - you can even get a nice graph::
768 768
769 769 hg log --graph -r "bisect(range)"
770 770
771 771 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
772 772
773 773 Returns 0 on success.
774 774 """
775 775 # backward compatibility
776 776 if rev in "good bad reset init".split():
777 777 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
778 778 cmd, rev, extra = rev, extra, None
779 779 if cmd == "good":
780 780 good = True
781 781 elif cmd == "bad":
782 782 bad = True
783 783 else:
784 784 reset = True
785 785 elif extra:
786 786 raise error.Abort(_('incompatible arguments'))
787 787
788 788 incompatibles = {
789 789 '--bad': bad,
790 790 '--command': bool(command),
791 791 '--extend': extend,
792 792 '--good': good,
793 793 '--reset': reset,
794 794 '--skip': skip,
795 795 }
796 796
797 797 enabled = [x for x in incompatibles if incompatibles[x]]
798 798
799 799 if len(enabled) > 1:
800 800 raise error.Abort(_('%s and %s are incompatible') %
801 801 tuple(sorted(enabled)[0:2]))
802 802
803 803 if reset:
804 804 hbisect.resetstate(repo)
805 805 return
806 806
807 807 state = hbisect.load_state(repo)
808 808
809 809 # update state
810 810 if good or bad or skip:
811 811 if rev:
812 812 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
813 813 else:
814 814 nodes = [repo.lookup('.')]
815 815 if good:
816 816 state['good'] += nodes
817 817 elif bad:
818 818 state['bad'] += nodes
819 819 elif skip:
820 820 state['skip'] += nodes
821 821 hbisect.save_state(repo, state)
822 822 if not (state['good'] and state['bad']):
823 823 return
824 824
825 825 def mayupdate(repo, node, show_stats=True):
826 826 """common used update sequence"""
827 827 if noupdate:
828 828 return
829 829 cmdutil.checkunfinished(repo)
830 830 cmdutil.bailifchanged(repo)
831 831 return hg.clean(repo, node, show_stats=show_stats)
832 832
833 833 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
834 834
835 835 if command:
836 836 changesets = 1
837 837 if noupdate:
838 838 try:
839 839 node = state['current'][0]
840 840 except LookupError:
841 841 raise error.Abort(_('current bisect revision is unknown - '
842 842 'start a new bisect to fix'))
843 843 else:
844 844 node, p2 = repo.dirstate.parents()
845 845 if p2 != nullid:
846 846 raise error.Abort(_('current bisect revision is a merge'))
847 847 if rev:
848 848 node = repo[scmutil.revsingle(repo, rev, node)].node()
849 849 try:
850 850 while changesets:
851 851 # update state
852 852 state['current'] = [node]
853 853 hbisect.save_state(repo, state)
854 854 status = ui.system(command, environ={'HG_NODE': hex(node)},
855 855 blockedtag='bisect_check')
856 856 if status == 125:
857 857 transition = "skip"
858 858 elif status == 0:
859 859 transition = "good"
860 860 # status < 0 means process was killed
861 861 elif status == 127:
862 862 raise error.Abort(_("failed to execute %s") % command)
863 863 elif status < 0:
864 864 raise error.Abort(_("%s killed") % command)
865 865 else:
866 866 transition = "bad"
867 867 state[transition].append(node)
868 868 ctx = repo[node]
869 869 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
870 870 transition))
871 871 hbisect.checkstate(state)
872 872 # bisect
873 873 nodes, changesets, bgood = hbisect.bisect(repo, state)
874 874 # update to next check
875 875 node = nodes[0]
876 876 mayupdate(repo, node, show_stats=False)
877 877 finally:
878 878 state['current'] = [node]
879 879 hbisect.save_state(repo, state)
880 880 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
881 881 return
882 882
883 883 hbisect.checkstate(state)
884 884
885 885 # actually bisect
886 886 nodes, changesets, good = hbisect.bisect(repo, state)
887 887 if extend:
888 888 if not changesets:
889 889 extendnode = hbisect.extendrange(repo, state, nodes, good)
890 890 if extendnode is not None:
891 891 ui.write(_("Extending search to changeset %d:%s\n")
892 892 % (extendnode.rev(), extendnode))
893 893 state['current'] = [extendnode.node()]
894 894 hbisect.save_state(repo, state)
895 895 return mayupdate(repo, extendnode.node())
896 896 raise error.Abort(_("nothing to extend"))
897 897
898 898 if changesets == 0:
899 899 hbisect.printresult(ui, repo, state, displayer, nodes, good)
900 900 else:
901 901 assert len(nodes) == 1 # only a single node can be tested next
902 902 node = nodes[0]
903 903 # compute the approximate number of remaining tests
904 904 tests, size = 0, 2
905 905 while size <= changesets:
906 906 tests, size = tests + 1, size * 2
907 907 rev = repo.changelog.rev(node)
908 908 ui.write(_("Testing changeset %d:%s "
909 909 "(%d changesets remaining, ~%d tests)\n")
910 910 % (rev, short(node), changesets, tests))
911 911 state['current'] = [node]
912 912 hbisect.save_state(repo, state)
913 913 return mayupdate(repo, node)
914 914
915 915 @command('bookmarks|bookmark',
916 916 [('f', 'force', False, _('force')),
917 917 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
918 918 ('d', 'delete', False, _('delete a given bookmark')),
919 919 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
920 920 ('i', 'inactive', False, _('mark a bookmark inactive')),
921 921 ] + formatteropts,
922 922 _('hg bookmarks [OPTIONS]... [NAME]...'))
923 923 def bookmark(ui, repo, *names, **opts):
924 924 '''create a new bookmark or list existing bookmarks
925 925
926 926 Bookmarks are labels on changesets to help track lines of development.
927 927 Bookmarks are unversioned and can be moved, renamed and deleted.
928 928 Deleting or moving a bookmark has no effect on the associated changesets.
929 929
930 930 Creating or updating to a bookmark causes it to be marked as 'active'.
931 931 The active bookmark is indicated with a '*'.
932 932 When a commit is made, the active bookmark will advance to the new commit.
933 933 A plain :hg:`update` will also advance an active bookmark, if possible.
934 934 Updating away from a bookmark will cause it to be deactivated.
935 935
936 936 Bookmarks can be pushed and pulled between repositories (see
937 937 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
938 938 diverged, a new 'divergent bookmark' of the form 'name@path' will
939 939 be created. Using :hg:`merge` will resolve the divergence.
940 940
941 941 Specifying bookmark as '.' to -m or -d options is equivalent to specifying
942 942 the active bookmark's name.
943 943
944 944 A bookmark named '@' has the special property that :hg:`clone` will
945 945 check it out by default if it exists.
946 946
947 947 .. container:: verbose
948 948
949 949 Examples:
950 950
951 951 - create an active bookmark for a new line of development::
952 952
953 953 hg book new-feature
954 954
955 955 - create an inactive bookmark as a place marker::
956 956
957 957 hg book -i reviewed
958 958
959 959 - create an inactive bookmark on another changeset::
960 960
961 961 hg book -r .^ tested
962 962
963 963 - rename bookmark turkey to dinner::
964 964
965 965 hg book -m turkey dinner
966 966
967 967 - move the '@' bookmark from another branch::
968 968
969 969 hg book -f @
970 970 '''
971 971 force = opts.get(r'force')
972 972 rev = opts.get(r'rev')
973 973 delete = opts.get(r'delete')
974 974 rename = opts.get(r'rename')
975 975 inactive = opts.get(r'inactive')
976 976
977 977 if delete and rename:
978 978 raise error.Abort(_("--delete and --rename are incompatible"))
979 979 if delete and rev:
980 980 raise error.Abort(_("--rev is incompatible with --delete"))
981 981 if rename and rev:
982 982 raise error.Abort(_("--rev is incompatible with --rename"))
983 983 if not names and (delete or rev):
984 984 raise error.Abort(_("bookmark name required"))
985 985
986 986 if delete or rename or names or inactive:
987 987 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
988 988 if delete:
989 989 names = pycompat.maplist(repo._bookmarks.expandname, names)
990 990 bookmarks.delete(repo, tr, names)
991 991 elif rename:
992 992 if not names:
993 993 raise error.Abort(_("new bookmark name required"))
994 994 elif len(names) > 1:
995 995 raise error.Abort(_("only one new bookmark name allowed"))
996 996 rename = repo._bookmarks.expandname(rename)
997 997 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
998 998 elif names:
999 999 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1000 1000 elif inactive:
1001 1001 if len(repo._bookmarks) == 0:
1002 1002 ui.status(_("no bookmarks set\n"))
1003 1003 elif not repo._activebookmark:
1004 1004 ui.status(_("no active bookmark\n"))
1005 1005 else:
1006 1006 bookmarks.deactivate(repo)
1007 1007 else: # show bookmarks
1008 1008 bookmarks.printbookmarks(ui, repo, **opts)
1009 1009
1010 1010 @command('branch',
1011 1011 [('f', 'force', None,
1012 1012 _('set branch name even if it shadows an existing branch')),
1013 1013 ('C', 'clean', None, _('reset branch name to parent branch name')),
1014 1014 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1015 1015 ],
1016 1016 _('[-fC] [NAME]'))
1017 1017 def branch(ui, repo, label=None, **opts):
1018 1018 """set or show the current branch name
1019 1019
1020 1020 .. note::
1021 1021
1022 1022 Branch names are permanent and global. Use :hg:`bookmark` to create a
1023 1023 light-weight bookmark instead. See :hg:`help glossary` for more
1024 1024 information about named branches and bookmarks.
1025 1025
1026 1026 With no argument, show the current branch name. With one argument,
1027 1027 set the working directory branch name (the branch will not exist
1028 1028 in the repository until the next commit). Standard practice
1029 1029 recommends that primary development take place on the 'default'
1030 1030 branch.
1031 1031
1032 1032 Unless -f/--force is specified, branch will not let you set a
1033 1033 branch name that already exists.
1034 1034
1035 1035 Use -C/--clean to reset the working directory branch to that of
1036 1036 the parent of the working directory, negating a previous branch
1037 1037 change.
1038 1038
1039 1039 Use the command :hg:`update` to switch to an existing branch. Use
1040 1040 :hg:`commit --close-branch` to mark this branch head as closed.
1041 1041 When all heads of a branch are closed, the branch will be
1042 1042 considered closed.
1043 1043
1044 1044 Returns 0 on success.
1045 1045 """
1046 1046 opts = pycompat.byteskwargs(opts)
1047 1047 revs = opts.get('rev')
1048 1048 if label:
1049 1049 label = label.strip()
1050 1050
1051 1051 if not opts.get('clean') and not label:
1052 1052 if revs:
1053 1053 raise error.Abort(_("no branch name specified for the revisions"))
1054 1054 ui.write("%s\n" % repo.dirstate.branch())
1055 1055 return
1056 1056
1057 1057 with repo.wlock():
1058 1058 if opts.get('clean'):
1059 1059 label = repo[None].p1().branch()
1060 1060 repo.dirstate.setbranch(label)
1061 1061 ui.status(_('reset working directory to branch %s\n') % label)
1062 1062 elif label:
1063 1063
1064 1064 scmutil.checknewlabel(repo, label, 'branch')
1065 1065 if revs:
1066 1066 return cmdutil.changebranch(ui, repo, revs, label)
1067 1067
1068 1068 if not opts.get('force') and label in repo.branchmap():
1069 1069 if label not in [p.branch() for p in repo[None].parents()]:
1070 1070 raise error.Abort(_('a branch of the same name already'
1071 1071 ' exists'),
1072 1072 # i18n: "it" refers to an existing branch
1073 1073 hint=_("use 'hg update' to switch to it"))
1074 1074
1075 1075 repo.dirstate.setbranch(label)
1076 1076 ui.status(_('marked working directory as branch %s\n') % label)
1077 1077
1078 1078 # find any open named branches aside from default
1079 1079 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1080 1080 if n != "default" and not c]
1081 1081 if not others:
1082 1082 ui.status(_('(branches are permanent and global, '
1083 1083 'did you want a bookmark?)\n'))
1084 1084
1085 1085 @command('branches',
1086 1086 [('a', 'active', False,
1087 1087 _('show only branches that have unmerged heads (DEPRECATED)')),
1088 1088 ('c', 'closed', False, _('show normal and closed branches')),
1089 1089 ] + formatteropts,
1090 1090 _('[-c]'), cmdtype=readonly)
1091 1091 def branches(ui, repo, active=False, closed=False, **opts):
1092 1092 """list repository named branches
1093 1093
1094 1094 List the repository's named branches, indicating which ones are
1095 1095 inactive. If -c/--closed is specified, also list branches which have
1096 1096 been marked closed (see :hg:`commit --close-branch`).
1097 1097
1098 1098 Use the command :hg:`update` to switch to an existing branch.
1099 1099
1100 1100 Returns 0.
1101 1101 """
1102 1102
1103 1103 opts = pycompat.byteskwargs(opts)
1104 1104 ui.pager('branches')
1105 1105 fm = ui.formatter('branches', opts)
1106 1106 hexfunc = fm.hexfunc
1107 1107
1108 1108 allheads = set(repo.heads())
1109 1109 branches = []
1110 1110 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1111 1111 isactive = False
1112 1112 if not isclosed:
1113 1113 openheads = set(repo.branchmap().iteropen(heads))
1114 1114 isactive = bool(openheads & allheads)
1115 1115 branches.append((tag, repo[tip], isactive, not isclosed))
1116 1116 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1117 1117 reverse=True)
1118 1118
1119 1119 for tag, ctx, isactive, isopen in branches:
1120 1120 if active and not isactive:
1121 1121 continue
1122 1122 if isactive:
1123 1123 label = 'branches.active'
1124 1124 notice = ''
1125 1125 elif not isopen:
1126 1126 if not closed:
1127 1127 continue
1128 1128 label = 'branches.closed'
1129 1129 notice = _(' (closed)')
1130 1130 else:
1131 1131 label = 'branches.inactive'
1132 1132 notice = _(' (inactive)')
1133 1133 current = (tag == repo.dirstate.branch())
1134 1134 if current:
1135 1135 label = 'branches.current'
1136 1136
1137 1137 fm.startitem()
1138 1138 fm.write('branch', '%s', tag, label=label)
1139 1139 rev = ctx.rev()
1140 1140 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1141 1141 fmt = ' ' * padsize + ' %d:%s'
1142 1142 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1143 1143 label='log.changeset changeset.%s' % ctx.phasestr())
1144 1144 fm.context(ctx=ctx)
1145 1145 fm.data(active=isactive, closed=not isopen, current=current)
1146 1146 if not ui.quiet:
1147 1147 fm.plain(notice)
1148 1148 fm.plain('\n')
1149 1149 fm.end()
1150 1150
1151 1151 @command('bundle',
1152 1152 [('f', 'force', None, _('run even when the destination is unrelated')),
1153 1153 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1154 1154 _('REV')),
1155 1155 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1156 1156 _('BRANCH')),
1157 1157 ('', 'base', [],
1158 1158 _('a base changeset assumed to be available at the destination'),
1159 1159 _('REV')),
1160 1160 ('a', 'all', None, _('bundle all changesets in the repository')),
1161 1161 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1162 1162 ] + remoteopts,
1163 1163 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1164 1164 def bundle(ui, repo, fname, dest=None, **opts):
1165 1165 """create a bundle file
1166 1166
1167 1167 Generate a bundle file containing data to be transferred to another
1168 1168 repository.
1169 1169
1170 1170 To create a bundle containing all changesets, use -a/--all
1171 1171 (or --base null). Otherwise, hg assumes the destination will have
1172 1172 all the nodes you specify with --base parameters. Otherwise, hg
1173 1173 will assume the repository has all the nodes in destination, or
1174 1174 default-push/default if no destination is specified, where destination
1175 1175 is the repository you provide through DEST option.
1176 1176
1177 1177 You can change bundle format with the -t/--type option. See
1178 1178 :hg:`help bundlespec` for documentation on this format. By default,
1179 1179 the most appropriate format is used and compression defaults to
1180 1180 bzip2.
1181 1181
1182 1182 The bundle file can then be transferred using conventional means
1183 1183 and applied to another repository with the unbundle or pull
1184 1184 command. This is useful when direct push and pull are not
1185 1185 available or when exporting an entire repository is undesirable.
1186 1186
1187 1187 Applying bundles preserves all changeset contents including
1188 1188 permissions, copy/rename information, and revision history.
1189 1189
1190 1190 Returns 0 on success, 1 if no changes found.
1191 1191 """
1192 1192 opts = pycompat.byteskwargs(opts)
1193 1193 revs = None
1194 1194 if 'rev' in opts:
1195 1195 revstrings = opts['rev']
1196 1196 revs = scmutil.revrange(repo, revstrings)
1197 1197 if revstrings and not revs:
1198 1198 raise error.Abort(_('no commits to bundle'))
1199 1199
1200 1200 bundletype = opts.get('type', 'bzip2').lower()
1201 1201 try:
1202 1202 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1203 1203 except error.UnsupportedBundleSpecification as e:
1204 1204 raise error.Abort(pycompat.bytestr(e),
1205 1205 hint=_("see 'hg help bundlespec' for supported "
1206 1206 "values for --type"))
1207 1207 cgversion = bundlespec.contentopts["cg.version"]
1208 1208
1209 1209 # Packed bundles are a pseudo bundle format for now.
1210 1210 if cgversion == 's1':
1211 1211 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1212 1212 hint=_("use 'hg debugcreatestreamclonebundle'"))
1213 1213
1214 1214 if opts.get('all'):
1215 1215 if dest:
1216 1216 raise error.Abort(_("--all is incompatible with specifying "
1217 1217 "a destination"))
1218 1218 if opts.get('base'):
1219 1219 ui.warn(_("ignoring --base because --all was specified\n"))
1220 1220 base = ['null']
1221 1221 else:
1222 1222 base = scmutil.revrange(repo, opts.get('base'))
1223 1223 if cgversion not in changegroup.supportedoutgoingversions(repo):
1224 1224 raise error.Abort(_("repository does not support bundle version %s") %
1225 1225 cgversion)
1226 1226
1227 1227 if base:
1228 1228 if dest:
1229 1229 raise error.Abort(_("--base is incompatible with specifying "
1230 1230 "a destination"))
1231 1231 common = [repo.lookup(rev) for rev in base]
1232 1232 heads = [repo.lookup(r) for r in revs] if revs else None
1233 1233 outgoing = discovery.outgoing(repo, common, heads)
1234 1234 else:
1235 1235 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1236 1236 dest, branches = hg.parseurl(dest, opts.get('branch'))
1237 1237 other = hg.peer(repo, opts, dest)
1238 1238 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1239 1239 heads = revs and map(repo.lookup, revs) or revs
1240 1240 outgoing = discovery.findcommonoutgoing(repo, other,
1241 1241 onlyheads=heads,
1242 1242 force=opts.get('force'),
1243 1243 portable=True)
1244 1244
1245 1245 if not outgoing.missing:
1246 1246 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1247 1247 return 1
1248 1248
1249 1249 bcompression = bundlespec.compression
1250 1250 if cgversion == '01': #bundle1
1251 1251 if bcompression is None:
1252 1252 bcompression = 'UN'
1253 1253 bversion = 'HG10' + bcompression
1254 1254 bcompression = None
1255 1255 elif cgversion in ('02', '03'):
1256 1256 bversion = 'HG20'
1257 1257 else:
1258 1258 raise error.ProgrammingError(
1259 1259 'bundle: unexpected changegroup version %s' % cgversion)
1260 1260
1261 1261 # TODO compression options should be derived from bundlespec parsing.
1262 1262 # This is a temporary hack to allow adjusting bundle compression
1263 1263 # level without a) formalizing the bundlespec changes to declare it
1264 1264 # b) introducing a command flag.
1265 1265 compopts = {}
1266 1266 complevel = ui.configint('experimental', 'bundlecomplevel')
1267 1267 if complevel is not None:
1268 1268 compopts['level'] = complevel
1269 1269
1270 1270 # Allow overriding the bundling of obsmarker in phases through
1271 1271 # configuration while we don't have a bundle version that include them
1272 1272 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1273 1273 bundlespec.contentopts['obsolescence'] = True
1274 1274 if repo.ui.configbool('experimental', 'bundle-phases'):
1275 1275 bundlespec.contentopts['phases'] = True
1276 1276
1277 1277 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1278 1278 bundlespec.contentopts, compression=bcompression,
1279 1279 compopts=compopts)
1280 1280
1281 1281 @command('cat',
1282 1282 [('o', 'output', '',
1283 1283 _('print output to file with formatted name'), _('FORMAT')),
1284 1284 ('r', 'rev', '', _('print the given revision'), _('REV')),
1285 1285 ('', 'decode', None, _('apply any matching decode filter')),
1286 1286 ] + walkopts + formatteropts,
1287 1287 _('[OPTION]... FILE...'),
1288 1288 inferrepo=True, cmdtype=readonly)
1289 1289 def cat(ui, repo, file1, *pats, **opts):
1290 1290 """output the current or given revision of files
1291 1291
1292 1292 Print the specified files as they were at the given revision. If
1293 1293 no revision is given, the parent of the working directory is used.
1294 1294
1295 1295 Output may be to a file, in which case the name of the file is
1296 1296 given using a template string. See :hg:`help templates`. In addition
1297 1297 to the common template keywords, the following formatting rules are
1298 1298 supported:
1299 1299
1300 1300 :``%%``: literal "%" character
1301 1301 :``%s``: basename of file being printed
1302 1302 :``%d``: dirname of file being printed, or '.' if in repository root
1303 1303 :``%p``: root-relative path name of file being printed
1304 1304 :``%H``: changeset hash (40 hexadecimal digits)
1305 1305 :``%R``: changeset revision number
1306 1306 :``%h``: short-form changeset hash (12 hexadecimal digits)
1307 1307 :``%r``: zero-padded changeset revision number
1308 1308 :``%b``: basename of the exporting repository
1309 1309 :``\\``: literal "\\" character
1310 1310
1311 1311 Returns 0 on success.
1312 1312 """
1313 1313 opts = pycompat.byteskwargs(opts)
1314 1314 rev = opts.get('rev')
1315 1315 if rev:
1316 1316 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1317 1317 ctx = scmutil.revsingle(repo, rev)
1318 1318 m = scmutil.match(ctx, (file1,) + pats, opts)
1319 1319 fntemplate = opts.pop('output', '')
1320 1320 if cmdutil.isstdiofilename(fntemplate):
1321 1321 fntemplate = ''
1322 1322
1323 1323 if fntemplate:
1324 1324 fm = formatter.nullformatter(ui, 'cat')
1325 1325 else:
1326 1326 ui.pager('cat')
1327 1327 fm = ui.formatter('cat', opts)
1328 1328 with fm:
1329 1329 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1330 1330 **pycompat.strkwargs(opts))
1331 1331
1332 1332 @command('^clone',
1333 1333 [('U', 'noupdate', None, _('the clone will include an empty working '
1334 1334 'directory (only a repository)')),
1335 1335 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1336 1336 _('REV')),
1337 1337 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1338 1338 ' and its ancestors'), _('REV')),
1339 1339 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1340 1340 ' changesets and their ancestors'), _('BRANCH')),
1341 1341 ('', 'pull', None, _('use pull protocol to copy metadata')),
1342 1342 ('', 'uncompressed', None,
1343 1343 _('an alias to --stream (DEPRECATED)')),
1344 1344 ('', 'stream', None,
1345 1345 _('clone with minimal data processing')),
1346 1346 ] + remoteopts,
1347 1347 _('[OPTION]... SOURCE [DEST]'),
1348 1348 norepo=True)
1349 1349 def clone(ui, source, dest=None, **opts):
1350 1350 """make a copy of an existing repository
1351 1351
1352 1352 Create a copy of an existing repository in a new directory.
1353 1353
1354 1354 If no destination directory name is specified, it defaults to the
1355 1355 basename of the source.
1356 1356
1357 1357 The location of the source is added to the new repository's
1358 1358 ``.hg/hgrc`` file, as the default to be used for future pulls.
1359 1359
1360 1360 Only local paths and ``ssh://`` URLs are supported as
1361 1361 destinations. For ``ssh://`` destinations, no working directory or
1362 1362 ``.hg/hgrc`` will be created on the remote side.
1363 1363
1364 1364 If the source repository has a bookmark called '@' set, that
1365 1365 revision will be checked out in the new repository by default.
1366 1366
1367 1367 To check out a particular version, use -u/--update, or
1368 1368 -U/--noupdate to create a clone with no working directory.
1369 1369
1370 1370 To pull only a subset of changesets, specify one or more revisions
1371 1371 identifiers with -r/--rev or branches with -b/--branch. The
1372 1372 resulting clone will contain only the specified changesets and
1373 1373 their ancestors. These options (or 'clone src#rev dest') imply
1374 1374 --pull, even for local source repositories.
1375 1375
1376 1376 In normal clone mode, the remote normalizes repository data into a common
1377 1377 exchange format and the receiving end translates this data into its local
1378 1378 storage format. --stream activates a different clone mode that essentially
1379 1379 copies repository files from the remote with minimal data processing. This
1380 1380 significantly reduces the CPU cost of a clone both remotely and locally.
1381 1381 However, it often increases the transferred data size by 30-40%. This can
1382 1382 result in substantially faster clones where I/O throughput is plentiful,
1383 1383 especially for larger repositories. A side-effect of --stream clones is
1384 1384 that storage settings and requirements on the remote are applied locally:
1385 1385 a modern client may inherit legacy or inefficient storage used by the
1386 1386 remote or a legacy Mercurial client may not be able to clone from a
1387 1387 modern Mercurial remote.
1388 1388
1389 1389 .. note::
1390 1390
1391 1391 Specifying a tag will include the tagged changeset but not the
1392 1392 changeset containing the tag.
1393 1393
1394 1394 .. container:: verbose
1395 1395
1396 1396 For efficiency, hardlinks are used for cloning whenever the
1397 1397 source and destination are on the same filesystem (note this
1398 1398 applies only to the repository data, not to the working
1399 1399 directory). Some filesystems, such as AFS, implement hardlinking
1400 1400 incorrectly, but do not report errors. In these cases, use the
1401 1401 --pull option to avoid hardlinking.
1402 1402
1403 1403 Mercurial will update the working directory to the first applicable
1404 1404 revision from this list:
1405 1405
1406 1406 a) null if -U or the source repository has no changesets
1407 1407 b) if -u . and the source repository is local, the first parent of
1408 1408 the source repository's working directory
1409 1409 c) the changeset specified with -u (if a branch name, this means the
1410 1410 latest head of that branch)
1411 1411 d) the changeset specified with -r
1412 1412 e) the tipmost head specified with -b
1413 1413 f) the tipmost head specified with the url#branch source syntax
1414 1414 g) the revision marked with the '@' bookmark, if present
1415 1415 h) the tipmost head of the default branch
1416 1416 i) tip
1417 1417
1418 1418 When cloning from servers that support it, Mercurial may fetch
1419 1419 pre-generated data from a server-advertised URL. When this is done,
1420 1420 hooks operating on incoming changesets and changegroups may fire twice,
1421 1421 once for the bundle fetched from the URL and another for any additional
1422 1422 data not fetched from this URL. In addition, if an error occurs, the
1423 1423 repository may be rolled back to a partial clone. This behavior may
1424 1424 change in future releases. See :hg:`help -e clonebundles` for more.
1425 1425
1426 1426 Examples:
1427 1427
1428 1428 - clone a remote repository to a new directory named hg/::
1429 1429
1430 1430 hg clone https://www.mercurial-scm.org/repo/hg/
1431 1431
1432 1432 - create a lightweight local clone::
1433 1433
1434 1434 hg clone project/ project-feature/
1435 1435
1436 1436 - clone from an absolute path on an ssh server (note double-slash)::
1437 1437
1438 1438 hg clone ssh://user@server//home/projects/alpha/
1439 1439
1440 1440 - do a streaming clone while checking out a specified version::
1441 1441
1442 1442 hg clone --stream http://server/repo -u 1.5
1443 1443
1444 1444 - create a repository without changesets after a particular revision::
1445 1445
1446 1446 hg clone -r 04e544 experimental/ good/
1447 1447
1448 1448 - clone (and track) a particular named branch::
1449 1449
1450 1450 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1451 1451
1452 1452 See :hg:`help urls` for details on specifying URLs.
1453 1453
1454 1454 Returns 0 on success.
1455 1455 """
1456 1456 opts = pycompat.byteskwargs(opts)
1457 1457 if opts.get('noupdate') and opts.get('updaterev'):
1458 1458 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1459 1459
1460 1460 r = hg.clone(ui, opts, source, dest,
1461 1461 pull=opts.get('pull'),
1462 1462 stream=opts.get('stream') or opts.get('uncompressed'),
1463 1463 rev=opts.get('rev'),
1464 1464 update=opts.get('updaterev') or not opts.get('noupdate'),
1465 1465 branch=opts.get('branch'),
1466 1466 shareopts=opts.get('shareopts'))
1467 1467
1468 1468 return r is None
1469 1469
1470 1470 @command('^commit|ci',
1471 1471 [('A', 'addremove', None,
1472 1472 _('mark new/missing files as added/removed before committing')),
1473 1473 ('', 'close-branch', None,
1474 1474 _('mark a branch head as closed')),
1475 1475 ('', 'amend', None, _('amend the parent of the working directory')),
1476 1476 ('s', 'secret', None, _('use the secret phase for committing')),
1477 1477 ('e', 'edit', None, _('invoke editor on commit messages')),
1478 1478 ('i', 'interactive', None, _('use interactive mode')),
1479 1479 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1480 1480 _('[OPTION]... [FILE]...'),
1481 1481 inferrepo=True)
1482 1482 def commit(ui, repo, *pats, **opts):
1483 1483 """commit the specified files or all outstanding changes
1484 1484
1485 1485 Commit changes to the given files into the repository. Unlike a
1486 1486 centralized SCM, this operation is a local operation. See
1487 1487 :hg:`push` for a way to actively distribute your changes.
1488 1488
1489 1489 If a list of files is omitted, all changes reported by :hg:`status`
1490 1490 will be committed.
1491 1491
1492 1492 If you are committing the result of a merge, do not provide any
1493 1493 filenames or -I/-X filters.
1494 1494
1495 1495 If no commit message is specified, Mercurial starts your
1496 1496 configured editor where you can enter a message. In case your
1497 1497 commit fails, you will find a backup of your message in
1498 1498 ``.hg/last-message.txt``.
1499 1499
1500 1500 The --close-branch flag can be used to mark the current branch
1501 1501 head closed. When all heads of a branch are closed, the branch
1502 1502 will be considered closed and no longer listed.
1503 1503
1504 1504 The --amend flag can be used to amend the parent of the
1505 1505 working directory with a new commit that contains the changes
1506 1506 in the parent in addition to those currently reported by :hg:`status`,
1507 1507 if there are any. The old commit is stored in a backup bundle in
1508 1508 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1509 1509 on how to restore it).
1510 1510
1511 1511 Message, user and date are taken from the amended commit unless
1512 1512 specified. When a message isn't specified on the command line,
1513 1513 the editor will open with the message of the amended commit.
1514 1514
1515 1515 It is not possible to amend public changesets (see :hg:`help phases`)
1516 1516 or changesets that have children.
1517 1517
1518 1518 See :hg:`help dates` for a list of formats valid for -d/--date.
1519 1519
1520 1520 Returns 0 on success, 1 if nothing changed.
1521 1521
1522 1522 .. container:: verbose
1523 1523
1524 1524 Examples:
1525 1525
1526 1526 - commit all files ending in .py::
1527 1527
1528 1528 hg commit --include "set:**.py"
1529 1529
1530 1530 - commit all non-binary files::
1531 1531
1532 1532 hg commit --exclude "set:binary()"
1533 1533
1534 1534 - amend the current commit and set the date to now::
1535 1535
1536 1536 hg commit --amend --date now
1537 1537 """
1538 1538 wlock = lock = None
1539 1539 try:
1540 1540 wlock = repo.wlock()
1541 1541 lock = repo.lock()
1542 1542 return _docommit(ui, repo, *pats, **opts)
1543 1543 finally:
1544 1544 release(lock, wlock)
1545 1545
1546 1546 def _docommit(ui, repo, *pats, **opts):
1547 1547 if opts.get(r'interactive'):
1548 1548 opts.pop(r'interactive')
1549 1549 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1550 1550 cmdutil.recordfilter, *pats,
1551 1551 **opts)
1552 1552 # ret can be 0 (no changes to record) or the value returned by
1553 1553 # commit(), 1 if nothing changed or None on success.
1554 1554 return 1 if ret == 0 else ret
1555 1555
1556 1556 opts = pycompat.byteskwargs(opts)
1557 1557 if opts.get('subrepos'):
1558 1558 if opts.get('amend'):
1559 1559 raise error.Abort(_('cannot amend with --subrepos'))
1560 1560 # Let --subrepos on the command line override config setting.
1561 1561 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1562 1562
1563 1563 cmdutil.checkunfinished(repo, commit=True)
1564 1564
1565 1565 branch = repo[None].branch()
1566 1566 bheads = repo.branchheads(branch)
1567 1567
1568 1568 extra = {}
1569 1569 if opts.get('close_branch'):
1570 1570 extra['close'] = '1'
1571 1571
1572 1572 if not bheads:
1573 1573 raise error.Abort(_('can only close branch heads'))
1574 1574 elif opts.get('amend'):
1575 1575 if repo[None].parents()[0].p1().branch() != branch and \
1576 1576 repo[None].parents()[0].p2().branch() != branch:
1577 1577 raise error.Abort(_('can only close branch heads'))
1578 1578
1579 1579 if opts.get('amend'):
1580 1580 if ui.configbool('ui', 'commitsubrepos'):
1581 1581 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1582 1582
1583 1583 old = repo['.']
1584 1584 rewriteutil.precheck(repo, [old.rev()], 'amend')
1585 1585
1586 1586 # Currently histedit gets confused if an amend happens while histedit
1587 1587 # is in progress. Since we have a checkunfinished command, we are
1588 1588 # temporarily honoring it.
1589 1589 #
1590 1590 # Note: eventually this guard will be removed. Please do not expect
1591 1591 # this behavior to remain.
1592 1592 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1593 1593 cmdutil.checkunfinished(repo)
1594 1594
1595 1595 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1596 1596 if node == old.node():
1597 1597 ui.status(_("nothing changed\n"))
1598 1598 return 1
1599 1599 else:
1600 1600 def commitfunc(ui, repo, message, match, opts):
1601 1601 overrides = {}
1602 1602 if opts.get('secret'):
1603 1603 overrides[('phases', 'new-commit')] = 'secret'
1604 1604
1605 1605 baseui = repo.baseui
1606 1606 with baseui.configoverride(overrides, 'commit'):
1607 1607 with ui.configoverride(overrides, 'commit'):
1608 1608 editform = cmdutil.mergeeditform(repo[None],
1609 1609 'commit.normal')
1610 1610 editor = cmdutil.getcommiteditor(
1611 1611 editform=editform, **pycompat.strkwargs(opts))
1612 1612 return repo.commit(message,
1613 1613 opts.get('user'),
1614 1614 opts.get('date'),
1615 1615 match,
1616 1616 editor=editor,
1617 1617 extra=extra)
1618 1618
1619 1619 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1620 1620
1621 1621 if not node:
1622 1622 stat = cmdutil.postcommitstatus(repo, pats, opts)
1623 1623 if stat[3]:
1624 1624 ui.status(_("nothing changed (%d missing files, see "
1625 1625 "'hg status')\n") % len(stat[3]))
1626 1626 else:
1627 1627 ui.status(_("nothing changed\n"))
1628 1628 return 1
1629 1629
1630 1630 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1631 1631
1632 1632 @command('config|showconfig|debugconfig',
1633 1633 [('u', 'untrusted', None, _('show untrusted configuration options')),
1634 1634 ('e', 'edit', None, _('edit user config')),
1635 1635 ('l', 'local', None, _('edit repository config')),
1636 1636 ('g', 'global', None, _('edit global config'))] + formatteropts,
1637 1637 _('[-u] [NAME]...'),
1638 1638 optionalrepo=True, cmdtype=readonly)
1639 1639 def config(ui, repo, *values, **opts):
1640 1640 """show combined config settings from all hgrc files
1641 1641
1642 1642 With no arguments, print names and values of all config items.
1643 1643
1644 1644 With one argument of the form section.name, print just the value
1645 1645 of that config item.
1646 1646
1647 1647 With multiple arguments, print names and values of all config
1648 1648 items with matching section names or section.names.
1649 1649
1650 1650 With --edit, start an editor on the user-level config file. With
1651 1651 --global, edit the system-wide config file. With --local, edit the
1652 1652 repository-level config file.
1653 1653
1654 1654 With --debug, the source (filename and line number) is printed
1655 1655 for each config item.
1656 1656
1657 1657 See :hg:`help config` for more information about config files.
1658 1658
1659 1659 Returns 0 on success, 1 if NAME does not exist.
1660 1660
1661 1661 """
1662 1662
1663 1663 opts = pycompat.byteskwargs(opts)
1664 1664 if opts.get('edit') or opts.get('local') or opts.get('global'):
1665 1665 if opts.get('local') and opts.get('global'):
1666 1666 raise error.Abort(_("can't use --local and --global together"))
1667 1667
1668 1668 if opts.get('local'):
1669 1669 if not repo:
1670 1670 raise error.Abort(_("can't use --local outside a repository"))
1671 1671 paths = [repo.vfs.join('hgrc')]
1672 1672 elif opts.get('global'):
1673 1673 paths = rcutil.systemrcpath()
1674 1674 else:
1675 1675 paths = rcutil.userrcpath()
1676 1676
1677 1677 for f in paths:
1678 1678 if os.path.exists(f):
1679 1679 break
1680 1680 else:
1681 1681 if opts.get('global'):
1682 1682 samplehgrc = uimod.samplehgrcs['global']
1683 1683 elif opts.get('local'):
1684 1684 samplehgrc = uimod.samplehgrcs['local']
1685 1685 else:
1686 1686 samplehgrc = uimod.samplehgrcs['user']
1687 1687
1688 1688 f = paths[0]
1689 1689 fp = open(f, "wb")
1690 1690 fp.write(util.tonativeeol(samplehgrc))
1691 1691 fp.close()
1692 1692
1693 1693 editor = ui.geteditor()
1694 1694 ui.system("%s \"%s\"" % (editor, f),
1695 1695 onerr=error.Abort, errprefix=_("edit failed"),
1696 1696 blockedtag='config_edit')
1697 1697 return
1698 1698 ui.pager('config')
1699 1699 fm = ui.formatter('config', opts)
1700 1700 for t, f in rcutil.rccomponents():
1701 1701 if t == 'path':
1702 1702 ui.debug('read config from: %s\n' % f)
1703 1703 elif t == 'items':
1704 1704 for section, name, value, source in f:
1705 1705 ui.debug('set config by: %s\n' % source)
1706 1706 else:
1707 1707 raise error.ProgrammingError('unknown rctype: %s' % t)
1708 1708 untrusted = bool(opts.get('untrusted'))
1709 1709
1710 1710 selsections = selentries = []
1711 1711 if values:
1712 1712 selsections = [v for v in values if '.' not in v]
1713 1713 selentries = [v for v in values if '.' in v]
1714 1714 uniquesel = (len(selentries) == 1 and not selsections)
1715 1715 selsections = set(selsections)
1716 1716 selentries = set(selentries)
1717 1717
1718 1718 matched = False
1719 1719 for section, name, value in ui.walkconfig(untrusted=untrusted):
1720 1720 source = ui.configsource(section, name, untrusted)
1721 1721 value = pycompat.bytestr(value)
1722 1722 if fm.isplain():
1723 1723 source = source or 'none'
1724 1724 value = value.replace('\n', '\\n')
1725 1725 entryname = section + '.' + name
1726 1726 if values and not (section in selsections or entryname in selentries):
1727 1727 continue
1728 1728 fm.startitem()
1729 1729 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1730 1730 if uniquesel:
1731 1731 fm.data(name=entryname)
1732 1732 fm.write('value', '%s\n', value)
1733 1733 else:
1734 1734 fm.write('name value', '%s=%s\n', entryname, value)
1735 1735 matched = True
1736 1736 fm.end()
1737 1737 if matched:
1738 1738 return 0
1739 1739 return 1
1740 1740
1741 1741 @command('copy|cp',
1742 1742 [('A', 'after', None, _('record a copy that has already occurred')),
1743 1743 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1744 1744 ] + walkopts + dryrunopts,
1745 1745 _('[OPTION]... [SOURCE]... DEST'))
1746 1746 def copy(ui, repo, *pats, **opts):
1747 1747 """mark files as copied for the next commit
1748 1748
1749 1749 Mark dest as having copies of source files. If dest is a
1750 1750 directory, copies are put in that directory. If dest is a file,
1751 1751 the source must be a single file.
1752 1752
1753 1753 By default, this command copies the contents of files as they
1754 1754 exist in the working directory. If invoked with -A/--after, the
1755 1755 operation is recorded, but no copying is performed.
1756 1756
1757 1757 This command takes effect with the next commit. To undo a copy
1758 1758 before that, see :hg:`revert`.
1759 1759
1760 1760 Returns 0 on success, 1 if errors are encountered.
1761 1761 """
1762 1762 opts = pycompat.byteskwargs(opts)
1763 1763 with repo.wlock(False):
1764 1764 return cmdutil.copy(ui, repo, pats, opts)
1765 1765
1766 1766 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1767 1767 def debugcommands(ui, cmd='', *args):
1768 1768 """list all available commands and options"""
1769 1769 for cmd, vals in sorted(table.iteritems()):
1770 1770 cmd = cmd.split('|')[0].strip('^')
1771 1771 opts = ', '.join([i[1] for i in vals[1]])
1772 1772 ui.write('%s: %s\n' % (cmd, opts))
1773 1773
1774 1774 @command('debugcomplete',
1775 1775 [('o', 'options', None, _('show the command options'))],
1776 1776 _('[-o] CMD'),
1777 1777 norepo=True)
1778 1778 def debugcomplete(ui, cmd='', **opts):
1779 1779 """returns the completion list associated with the given command"""
1780 1780
1781 1781 if opts.get(r'options'):
1782 1782 options = []
1783 1783 otables = [globalopts]
1784 1784 if cmd:
1785 1785 aliases, entry = cmdutil.findcmd(cmd, table, False)
1786 1786 otables.append(entry[1])
1787 1787 for t in otables:
1788 1788 for o in t:
1789 1789 if "(DEPRECATED)" in o[3]:
1790 1790 continue
1791 1791 if o[0]:
1792 1792 options.append('-%s' % o[0])
1793 1793 options.append('--%s' % o[1])
1794 1794 ui.write("%s\n" % "\n".join(options))
1795 1795 return
1796 1796
1797 1797 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1798 1798 if ui.verbose:
1799 1799 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1800 1800 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1801 1801
1802 1802 @command('^diff',
1803 1803 [('r', 'rev', [], _('revision'), _('REV')),
1804 1804 ('c', 'change', '', _('change made by revision'), _('REV'))
1805 1805 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1806 1806 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1807 1807 inferrepo=True, cmdtype=readonly)
1808 1808 def diff(ui, repo, *pats, **opts):
1809 1809 """diff repository (or selected files)
1810 1810
1811 1811 Show differences between revisions for the specified files.
1812 1812
1813 1813 Differences between files are shown using the unified diff format.
1814 1814
1815 1815 .. note::
1816 1816
1817 1817 :hg:`diff` may generate unexpected results for merges, as it will
1818 1818 default to comparing against the working directory's first
1819 1819 parent changeset if no revisions are specified.
1820 1820
1821 1821 When two revision arguments are given, then changes are shown
1822 1822 between those revisions. If only one revision is specified then
1823 1823 that revision is compared to the working directory, and, when no
1824 1824 revisions are specified, the working directory files are compared
1825 1825 to its first parent.
1826 1826
1827 1827 Alternatively you can specify -c/--change with a revision to see
1828 1828 the changes in that changeset relative to its first parent.
1829 1829
1830 1830 Without the -a/--text option, diff will avoid generating diffs of
1831 1831 files it detects as binary. With -a, diff will generate a diff
1832 1832 anyway, probably with undesirable results.
1833 1833
1834 1834 Use the -g/--git option to generate diffs in the git extended diff
1835 1835 format. For more information, read :hg:`help diffs`.
1836 1836
1837 1837 .. container:: verbose
1838 1838
1839 1839 Examples:
1840 1840
1841 1841 - compare a file in the current working directory to its parent::
1842 1842
1843 1843 hg diff foo.c
1844 1844
1845 1845 - compare two historical versions of a directory, with rename info::
1846 1846
1847 1847 hg diff --git -r 1.0:1.2 lib/
1848 1848
1849 1849 - get change stats relative to the last change on some date::
1850 1850
1851 1851 hg diff --stat -r "date('may 2')"
1852 1852
1853 1853 - diff all newly-added files that contain a keyword::
1854 1854
1855 1855 hg diff "set:added() and grep(GNU)"
1856 1856
1857 1857 - compare a revision and its parents::
1858 1858
1859 1859 hg diff -c 9353 # compare against first parent
1860 1860 hg diff -r 9353^:9353 # same using revset syntax
1861 1861 hg diff -r 9353^2:9353 # compare against the second parent
1862 1862
1863 1863 Returns 0 on success.
1864 1864 """
1865 1865
1866 1866 opts = pycompat.byteskwargs(opts)
1867 1867 revs = opts.get('rev')
1868 1868 change = opts.get('change')
1869 1869 stat = opts.get('stat')
1870 1870 reverse = opts.get('reverse')
1871 1871
1872 1872 if revs and change:
1873 1873 msg = _('cannot specify --rev and --change at the same time')
1874 1874 raise error.Abort(msg)
1875 1875 elif change:
1876 1876 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1877 1877 node2 = scmutil.revsingle(repo, change, None).node()
1878 1878 node1 = repo[node2].p1().node()
1879 1879 else:
1880 1880 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
1881 node1, node2 = scmutil.revpair(repo, revs)
1881 node1, node2 = scmutil.revpairnodes(repo, revs)
1882 1882
1883 1883 if reverse:
1884 1884 node1, node2 = node2, node1
1885 1885
1886 1886 diffopts = patch.diffallopts(ui, opts)
1887 1887 m = scmutil.match(repo[node2], pats, opts)
1888 1888 ui.pager('diff')
1889 1889 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1890 1890 listsubrepos=opts.get('subrepos'),
1891 1891 root=opts.get('root'))
1892 1892
1893 1893 @command('^export',
1894 1894 [('o', 'output', '',
1895 1895 _('print output to file with formatted name'), _('FORMAT')),
1896 1896 ('', 'switch-parent', None, _('diff against the second parent')),
1897 1897 ('r', 'rev', [], _('revisions to export'), _('REV')),
1898 1898 ] + diffopts,
1899 1899 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'), cmdtype=readonly)
1900 1900 def export(ui, repo, *changesets, **opts):
1901 1901 """dump the header and diffs for one or more changesets
1902 1902
1903 1903 Print the changeset header and diffs for one or more revisions.
1904 1904 If no revision is given, the parent of the working directory is used.
1905 1905
1906 1906 The information shown in the changeset header is: author, date,
1907 1907 branch name (if non-default), changeset hash, parent(s) and commit
1908 1908 comment.
1909 1909
1910 1910 .. note::
1911 1911
1912 1912 :hg:`export` may generate unexpected diff output for merge
1913 1913 changesets, as it will compare the merge changeset against its
1914 1914 first parent only.
1915 1915
1916 1916 Output may be to a file, in which case the name of the file is
1917 1917 given using a template string. See :hg:`help templates`. In addition
1918 1918 to the common template keywords, the following formatting rules are
1919 1919 supported:
1920 1920
1921 1921 :``%%``: literal "%" character
1922 1922 :``%H``: changeset hash (40 hexadecimal digits)
1923 1923 :``%N``: number of patches being generated
1924 1924 :``%R``: changeset revision number
1925 1925 :``%b``: basename of the exporting repository
1926 1926 :``%h``: short-form changeset hash (12 hexadecimal digits)
1927 1927 :``%m``: first line of the commit message (only alphanumeric characters)
1928 1928 :``%n``: zero-padded sequence number, starting at 1
1929 1929 :``%r``: zero-padded changeset revision number
1930 1930 :``\\``: literal "\\" character
1931 1931
1932 1932 Without the -a/--text option, export will avoid generating diffs
1933 1933 of files it detects as binary. With -a, export will generate a
1934 1934 diff anyway, probably with undesirable results.
1935 1935
1936 1936 Use the -g/--git option to generate diffs in the git extended diff
1937 1937 format. See :hg:`help diffs` for more information.
1938 1938
1939 1939 With the --switch-parent option, the diff will be against the
1940 1940 second parent. It can be useful to review a merge.
1941 1941
1942 1942 .. container:: verbose
1943 1943
1944 1944 Examples:
1945 1945
1946 1946 - use export and import to transplant a bugfix to the current
1947 1947 branch::
1948 1948
1949 1949 hg export -r 9353 | hg import -
1950 1950
1951 1951 - export all the changesets between two revisions to a file with
1952 1952 rename information::
1953 1953
1954 1954 hg export --git -r 123:150 > changes.txt
1955 1955
1956 1956 - split outgoing changes into a series of patches with
1957 1957 descriptive names::
1958 1958
1959 1959 hg export -r "outgoing()" -o "%n-%m.patch"
1960 1960
1961 1961 Returns 0 on success.
1962 1962 """
1963 1963 opts = pycompat.byteskwargs(opts)
1964 1964 changesets += tuple(opts.get('rev', []))
1965 1965 if not changesets:
1966 1966 changesets = ['.']
1967 1967 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
1968 1968 revs = scmutil.revrange(repo, changesets)
1969 1969 if not revs:
1970 1970 raise error.Abort(_("export requires at least one changeset"))
1971 1971 if len(revs) > 1:
1972 1972 ui.note(_('exporting patches:\n'))
1973 1973 else:
1974 1974 ui.note(_('exporting patch:\n'))
1975 1975 ui.pager('export')
1976 1976 cmdutil.export(repo, revs, fntemplate=opts.get('output'),
1977 1977 switch_parent=opts.get('switch_parent'),
1978 1978 opts=patch.diffallopts(ui, opts))
1979 1979
1980 1980 @command('files',
1981 1981 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
1982 1982 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
1983 1983 ] + walkopts + formatteropts + subrepoopts,
1984 1984 _('[OPTION]... [FILE]...'), cmdtype=readonly)
1985 1985 def files(ui, repo, *pats, **opts):
1986 1986 """list tracked files
1987 1987
1988 1988 Print files under Mercurial control in the working directory or
1989 1989 specified revision for given files (excluding removed files).
1990 1990 Files can be specified as filenames or filesets.
1991 1991
1992 1992 If no files are given to match, this command prints the names
1993 1993 of all files under Mercurial control.
1994 1994
1995 1995 .. container:: verbose
1996 1996
1997 1997 Examples:
1998 1998
1999 1999 - list all files under the current directory::
2000 2000
2001 2001 hg files .
2002 2002
2003 2003 - shows sizes and flags for current revision::
2004 2004
2005 2005 hg files -vr .
2006 2006
2007 2007 - list all files named README::
2008 2008
2009 2009 hg files -I "**/README"
2010 2010
2011 2011 - list all binary files::
2012 2012
2013 2013 hg files "set:binary()"
2014 2014
2015 2015 - find files containing a regular expression::
2016 2016
2017 2017 hg files "set:grep('bob')"
2018 2018
2019 2019 - search tracked file contents with xargs and grep::
2020 2020
2021 2021 hg files -0 | xargs -0 grep foo
2022 2022
2023 2023 See :hg:`help patterns` and :hg:`help filesets` for more information
2024 2024 on specifying file patterns.
2025 2025
2026 2026 Returns 0 if a match is found, 1 otherwise.
2027 2027
2028 2028 """
2029 2029
2030 2030 opts = pycompat.byteskwargs(opts)
2031 2031 rev = opts.get('rev')
2032 2032 if rev:
2033 2033 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2034 2034 ctx = scmutil.revsingle(repo, rev, None)
2035 2035
2036 2036 end = '\n'
2037 2037 if opts.get('print0'):
2038 2038 end = '\0'
2039 2039 fmt = '%s' + end
2040 2040
2041 2041 m = scmutil.match(ctx, pats, opts)
2042 2042 ui.pager('files')
2043 2043 with ui.formatter('files', opts) as fm:
2044 2044 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2045 2045
2046 2046 @command(
2047 2047 '^forget',
2048 2048 walkopts + dryrunopts,
2049 2049 _('[OPTION]... FILE...'), inferrepo=True)
2050 2050 def forget(ui, repo, *pats, **opts):
2051 2051 """forget the specified files on the next commit
2052 2052
2053 2053 Mark the specified files so they will no longer be tracked
2054 2054 after the next commit.
2055 2055
2056 2056 This only removes files from the current branch, not from the
2057 2057 entire project history, and it does not delete them from the
2058 2058 working directory.
2059 2059
2060 2060 To delete the file from the working directory, see :hg:`remove`.
2061 2061
2062 2062 To undo a forget before the next commit, see :hg:`add`.
2063 2063
2064 2064 .. container:: verbose
2065 2065
2066 2066 Examples:
2067 2067
2068 2068 - forget newly-added binary files::
2069 2069
2070 2070 hg forget "set:added() and binary()"
2071 2071
2072 2072 - forget files that would be excluded by .hgignore::
2073 2073
2074 2074 hg forget "set:hgignore()"
2075 2075
2076 2076 Returns 0 on success.
2077 2077 """
2078 2078
2079 2079 opts = pycompat.byteskwargs(opts)
2080 2080 if not pats:
2081 2081 raise error.Abort(_('no files specified'))
2082 2082
2083 2083 m = scmutil.match(repo[None], pats, opts)
2084 2084 dryrun = opts.get(r'dry_run')
2085 2085 rejected = cmdutil.forget(ui, repo, m, prefix="",
2086 2086 explicitonly=False, dryrun=dryrun)[0]
2087 2087 return rejected and 1 or 0
2088 2088
2089 2089 @command(
2090 2090 'graft',
2091 2091 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2092 2092 ('c', 'continue', False, _('resume interrupted graft')),
2093 2093 ('e', 'edit', False, _('invoke editor on commit messages')),
2094 2094 ('', 'log', None, _('append graft info to log message')),
2095 2095 ('f', 'force', False, _('force graft')),
2096 2096 ('D', 'currentdate', False,
2097 2097 _('record the current date as commit date')),
2098 2098 ('U', 'currentuser', False,
2099 2099 _('record the current user as committer'), _('DATE'))]
2100 2100 + commitopts2 + mergetoolopts + dryrunopts,
2101 2101 _('[OPTION]... [-r REV]... REV...'))
2102 2102 def graft(ui, repo, *revs, **opts):
2103 2103 '''copy changes from other branches onto the current branch
2104 2104
2105 2105 This command uses Mercurial's merge logic to copy individual
2106 2106 changes from other branches without merging branches in the
2107 2107 history graph. This is sometimes known as 'backporting' or
2108 2108 'cherry-picking'. By default, graft will copy user, date, and
2109 2109 description from the source changesets.
2110 2110
2111 2111 Changesets that are ancestors of the current revision, that have
2112 2112 already been grafted, or that are merges will be skipped.
2113 2113
2114 2114 If --log is specified, log messages will have a comment appended
2115 2115 of the form::
2116 2116
2117 2117 (grafted from CHANGESETHASH)
2118 2118
2119 2119 If --force is specified, revisions will be grafted even if they
2120 2120 are already ancestors of, or have been grafted to, the destination.
2121 2121 This is useful when the revisions have since been backed out.
2122 2122
2123 2123 If a graft merge results in conflicts, the graft process is
2124 2124 interrupted so that the current merge can be manually resolved.
2125 2125 Once all conflicts are addressed, the graft process can be
2126 2126 continued with the -c/--continue option.
2127 2127
2128 2128 .. note::
2129 2129
2130 2130 The -c/--continue option does not reapply earlier options, except
2131 2131 for --force.
2132 2132
2133 2133 .. container:: verbose
2134 2134
2135 2135 Examples:
2136 2136
2137 2137 - copy a single change to the stable branch and edit its description::
2138 2138
2139 2139 hg update stable
2140 2140 hg graft --edit 9393
2141 2141
2142 2142 - graft a range of changesets with one exception, updating dates::
2143 2143
2144 2144 hg graft -D "2085::2093 and not 2091"
2145 2145
2146 2146 - continue a graft after resolving conflicts::
2147 2147
2148 2148 hg graft -c
2149 2149
2150 2150 - show the source of a grafted changeset::
2151 2151
2152 2152 hg log --debug -r .
2153 2153
2154 2154 - show revisions sorted by date::
2155 2155
2156 2156 hg log -r "sort(all(), date)"
2157 2157
2158 2158 See :hg:`help revisions` for more about specifying revisions.
2159 2159
2160 2160 Returns 0 on successful completion.
2161 2161 '''
2162 2162 with repo.wlock():
2163 2163 return _dograft(ui, repo, *revs, **opts)
2164 2164
2165 2165 def _dograft(ui, repo, *revs, **opts):
2166 2166 opts = pycompat.byteskwargs(opts)
2167 2167 if revs and opts.get('rev'):
2168 2168 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2169 2169 'revision ordering!\n'))
2170 2170
2171 2171 revs = list(revs)
2172 2172 revs.extend(opts.get('rev'))
2173 2173
2174 2174 if not opts.get('user') and opts.get('currentuser'):
2175 2175 opts['user'] = ui.username()
2176 2176 if not opts.get('date') and opts.get('currentdate'):
2177 2177 opts['date'] = "%d %d" % dateutil.makedate()
2178 2178
2179 2179 editor = cmdutil.getcommiteditor(editform='graft',
2180 2180 **pycompat.strkwargs(opts))
2181 2181
2182 2182 cont = False
2183 2183 if opts.get('continue'):
2184 2184 cont = True
2185 2185 if revs:
2186 2186 raise error.Abort(_("can't specify --continue and revisions"))
2187 2187 # read in unfinished revisions
2188 2188 try:
2189 2189 nodes = repo.vfs.read('graftstate').splitlines()
2190 2190 revs = [repo[node].rev() for node in nodes]
2191 2191 except IOError as inst:
2192 2192 if inst.errno != errno.ENOENT:
2193 2193 raise
2194 2194 cmdutil.wrongtooltocontinue(repo, _('graft'))
2195 2195 else:
2196 2196 if not revs:
2197 2197 raise error.Abort(_('no revisions specified'))
2198 2198 cmdutil.checkunfinished(repo)
2199 2199 cmdutil.bailifchanged(repo)
2200 2200 revs = scmutil.revrange(repo, revs)
2201 2201
2202 2202 skipped = set()
2203 2203 # check for merges
2204 2204 for rev in repo.revs('%ld and merge()', revs):
2205 2205 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2206 2206 skipped.add(rev)
2207 2207 revs = [r for r in revs if r not in skipped]
2208 2208 if not revs:
2209 2209 return -1
2210 2210
2211 2211 # Don't check in the --continue case, in effect retaining --force across
2212 2212 # --continues. That's because without --force, any revisions we decided to
2213 2213 # skip would have been filtered out here, so they wouldn't have made their
2214 2214 # way to the graftstate. With --force, any revisions we would have otherwise
2215 2215 # skipped would not have been filtered out, and if they hadn't been applied
2216 2216 # already, they'd have been in the graftstate.
2217 2217 if not (cont or opts.get('force')):
2218 2218 # check for ancestors of dest branch
2219 2219 crev = repo['.'].rev()
2220 2220 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2221 2221 # XXX make this lazy in the future
2222 2222 # don't mutate while iterating, create a copy
2223 2223 for rev in list(revs):
2224 2224 if rev in ancestors:
2225 2225 ui.warn(_('skipping ancestor revision %d:%s\n') %
2226 2226 (rev, repo[rev]))
2227 2227 # XXX remove on list is slow
2228 2228 revs.remove(rev)
2229 2229 if not revs:
2230 2230 return -1
2231 2231
2232 2232 # analyze revs for earlier grafts
2233 2233 ids = {}
2234 2234 for ctx in repo.set("%ld", revs):
2235 2235 ids[ctx.hex()] = ctx.rev()
2236 2236 n = ctx.extra().get('source')
2237 2237 if n:
2238 2238 ids[n] = ctx.rev()
2239 2239
2240 2240 # check ancestors for earlier grafts
2241 2241 ui.debug('scanning for duplicate grafts\n')
2242 2242
2243 2243 # The only changesets we can be sure doesn't contain grafts of any
2244 2244 # revs, are the ones that are common ancestors of *all* revs:
2245 2245 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2246 2246 ctx = repo[rev]
2247 2247 n = ctx.extra().get('source')
2248 2248 if n in ids:
2249 2249 try:
2250 2250 r = repo[n].rev()
2251 2251 except error.RepoLookupError:
2252 2252 r = None
2253 2253 if r in revs:
2254 2254 ui.warn(_('skipping revision %d:%s '
2255 2255 '(already grafted to %d:%s)\n')
2256 2256 % (r, repo[r], rev, ctx))
2257 2257 revs.remove(r)
2258 2258 elif ids[n] in revs:
2259 2259 if r is None:
2260 2260 ui.warn(_('skipping already grafted revision %d:%s '
2261 2261 '(%d:%s also has unknown origin %s)\n')
2262 2262 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2263 2263 else:
2264 2264 ui.warn(_('skipping already grafted revision %d:%s '
2265 2265 '(%d:%s also has origin %d:%s)\n')
2266 2266 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2267 2267 revs.remove(ids[n])
2268 2268 elif ctx.hex() in ids:
2269 2269 r = ids[ctx.hex()]
2270 2270 ui.warn(_('skipping already grafted revision %d:%s '
2271 2271 '(was grafted from %d:%s)\n') %
2272 2272 (r, repo[r], rev, ctx))
2273 2273 revs.remove(r)
2274 2274 if not revs:
2275 2275 return -1
2276 2276
2277 2277 for pos, ctx in enumerate(repo.set("%ld", revs)):
2278 2278 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2279 2279 ctx.description().split('\n', 1)[0])
2280 2280 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2281 2281 if names:
2282 2282 desc += ' (%s)' % ' '.join(names)
2283 2283 ui.status(_('grafting %s\n') % desc)
2284 2284 if opts.get('dry_run'):
2285 2285 continue
2286 2286
2287 2287 source = ctx.extra().get('source')
2288 2288 extra = {}
2289 2289 if source:
2290 2290 extra['source'] = source
2291 2291 extra['intermediate-source'] = ctx.hex()
2292 2292 else:
2293 2293 extra['source'] = ctx.hex()
2294 2294 user = ctx.user()
2295 2295 if opts.get('user'):
2296 2296 user = opts['user']
2297 2297 date = ctx.date()
2298 2298 if opts.get('date'):
2299 2299 date = opts['date']
2300 2300 message = ctx.description()
2301 2301 if opts.get('log'):
2302 2302 message += '\n(grafted from %s)' % ctx.hex()
2303 2303
2304 2304 # we don't merge the first commit when continuing
2305 2305 if not cont:
2306 2306 # perform the graft merge with p1(rev) as 'ancestor'
2307 2307 try:
2308 2308 # ui.forcemerge is an internal variable, do not document
2309 2309 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2310 2310 'graft')
2311 2311 stats = mergemod.graft(repo, ctx, ctx.p1(),
2312 2312 ['local', 'graft'])
2313 2313 finally:
2314 2314 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2315 2315 # report any conflicts
2316 2316 if stats.unresolvedcount > 0:
2317 2317 # write out state for --continue
2318 2318 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2319 2319 repo.vfs.write('graftstate', ''.join(nodelines))
2320 2320 extra = ''
2321 2321 if opts.get('user'):
2322 2322 extra += ' --user %s' % procutil.shellquote(opts['user'])
2323 2323 if opts.get('date'):
2324 2324 extra += ' --date %s' % procutil.shellquote(opts['date'])
2325 2325 if opts.get('log'):
2326 2326 extra += ' --log'
2327 2327 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2328 2328 raise error.Abort(
2329 2329 _("unresolved conflicts, can't continue"),
2330 2330 hint=hint)
2331 2331 else:
2332 2332 cont = False
2333 2333
2334 2334 # commit
2335 2335 node = repo.commit(text=message, user=user,
2336 2336 date=date, extra=extra, editor=editor)
2337 2337 if node is None:
2338 2338 ui.warn(
2339 2339 _('note: graft of %d:%s created no changes to commit\n') %
2340 2340 (ctx.rev(), ctx))
2341 2341
2342 2342 # remove state when we complete successfully
2343 2343 if not opts.get('dry_run'):
2344 2344 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2345 2345
2346 2346 return 0
2347 2347
2348 2348 @command('grep',
2349 2349 [('0', 'print0', None, _('end fields with NUL')),
2350 2350 ('', 'all', None, _('print all revisions that match')),
2351 2351 ('a', 'text', None, _('treat all files as text')),
2352 2352 ('f', 'follow', None,
2353 2353 _('follow changeset history,'
2354 2354 ' or file history across copies and renames')),
2355 2355 ('i', 'ignore-case', None, _('ignore case when matching')),
2356 2356 ('l', 'files-with-matches', None,
2357 2357 _('print only filenames and revisions that match')),
2358 2358 ('n', 'line-number', None, _('print matching line numbers')),
2359 2359 ('r', 'rev', [],
2360 2360 _('only search files changed within revision range'), _('REV')),
2361 2361 ('u', 'user', None, _('list the author (long with -v)')),
2362 2362 ('d', 'date', None, _('list the date (short with -q)')),
2363 2363 ] + formatteropts + walkopts,
2364 2364 _('[OPTION]... PATTERN [FILE]...'),
2365 2365 inferrepo=True, cmdtype=readonly)
2366 2366 def grep(ui, repo, pattern, *pats, **opts):
2367 2367 """search revision history for a pattern in specified files
2368 2368
2369 2369 Search revision history for a regular expression in the specified
2370 2370 files or the entire project.
2371 2371
2372 2372 By default, grep prints the most recent revision number for each
2373 2373 file in which it finds a match. To get it to print every revision
2374 2374 that contains a change in match status ("-" for a match that becomes
2375 2375 a non-match, or "+" for a non-match that becomes a match), use the
2376 2376 --all flag.
2377 2377
2378 2378 PATTERN can be any Python (roughly Perl-compatible) regular
2379 2379 expression.
2380 2380
2381 2381 If no FILEs are specified (and -f/--follow isn't set), all files in
2382 2382 the repository are searched, including those that don't exist in the
2383 2383 current branch or have been deleted in a prior changeset.
2384 2384
2385 2385 Returns 0 if a match is found, 1 otherwise.
2386 2386 """
2387 2387 opts = pycompat.byteskwargs(opts)
2388 2388 reflags = re.M
2389 2389 if opts.get('ignore_case'):
2390 2390 reflags |= re.I
2391 2391 try:
2392 2392 regexp = util.re.compile(pattern, reflags)
2393 2393 except re.error as inst:
2394 2394 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2395 2395 return 1
2396 2396 sep, eol = ':', '\n'
2397 2397 if opts.get('print0'):
2398 2398 sep = eol = '\0'
2399 2399
2400 2400 getfile = util.lrucachefunc(repo.file)
2401 2401
2402 2402 def matchlines(body):
2403 2403 begin = 0
2404 2404 linenum = 0
2405 2405 while begin < len(body):
2406 2406 match = regexp.search(body, begin)
2407 2407 if not match:
2408 2408 break
2409 2409 mstart, mend = match.span()
2410 2410 linenum += body.count('\n', begin, mstart) + 1
2411 2411 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2412 2412 begin = body.find('\n', mend) + 1 or len(body) + 1
2413 2413 lend = begin - 1
2414 2414 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2415 2415
2416 2416 class linestate(object):
2417 2417 def __init__(self, line, linenum, colstart, colend):
2418 2418 self.line = line
2419 2419 self.linenum = linenum
2420 2420 self.colstart = colstart
2421 2421 self.colend = colend
2422 2422
2423 2423 def __hash__(self):
2424 2424 return hash((self.linenum, self.line))
2425 2425
2426 2426 def __eq__(self, other):
2427 2427 return self.line == other.line
2428 2428
2429 2429 def findpos(self):
2430 2430 """Iterate all (start, end) indices of matches"""
2431 2431 yield self.colstart, self.colend
2432 2432 p = self.colend
2433 2433 while p < len(self.line):
2434 2434 m = regexp.search(self.line, p)
2435 2435 if not m:
2436 2436 break
2437 2437 yield m.span()
2438 2438 p = m.end()
2439 2439
2440 2440 matches = {}
2441 2441 copies = {}
2442 2442 def grepbody(fn, rev, body):
2443 2443 matches[rev].setdefault(fn, [])
2444 2444 m = matches[rev][fn]
2445 2445 for lnum, cstart, cend, line in matchlines(body):
2446 2446 s = linestate(line, lnum, cstart, cend)
2447 2447 m.append(s)
2448 2448
2449 2449 def difflinestates(a, b):
2450 2450 sm = difflib.SequenceMatcher(None, a, b)
2451 2451 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2452 2452 if tag == 'insert':
2453 2453 for i in xrange(blo, bhi):
2454 2454 yield ('+', b[i])
2455 2455 elif tag == 'delete':
2456 2456 for i in xrange(alo, ahi):
2457 2457 yield ('-', a[i])
2458 2458 elif tag == 'replace':
2459 2459 for i in xrange(alo, ahi):
2460 2460 yield ('-', a[i])
2461 2461 for i in xrange(blo, bhi):
2462 2462 yield ('+', b[i])
2463 2463
2464 2464 def display(fm, fn, ctx, pstates, states):
2465 2465 rev = ctx.rev()
2466 2466 if fm.isplain():
2467 2467 formatuser = ui.shortuser
2468 2468 else:
2469 2469 formatuser = str
2470 2470 if ui.quiet:
2471 2471 datefmt = '%Y-%m-%d'
2472 2472 else:
2473 2473 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2474 2474 found = False
2475 2475 @util.cachefunc
2476 2476 def binary():
2477 2477 flog = getfile(fn)
2478 2478 return stringutil.binary(flog.read(ctx.filenode(fn)))
2479 2479
2480 2480 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2481 2481 if opts.get('all'):
2482 2482 iter = difflinestates(pstates, states)
2483 2483 else:
2484 2484 iter = [('', l) for l in states]
2485 2485 for change, l in iter:
2486 2486 fm.startitem()
2487 2487 fm.data(node=fm.hexfunc(ctx.node()))
2488 2488 cols = [
2489 2489 ('filename', fn, True),
2490 2490 ('rev', rev, True),
2491 2491 ('linenumber', l.linenum, opts.get('line_number')),
2492 2492 ]
2493 2493 if opts.get('all'):
2494 2494 cols.append(('change', change, True))
2495 2495 cols.extend([
2496 2496 ('user', formatuser(ctx.user()), opts.get('user')),
2497 2497 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2498 2498 ])
2499 2499 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2500 2500 for name, data, cond in cols:
2501 2501 field = fieldnamemap.get(name, name)
2502 2502 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2503 2503 if cond and name != lastcol:
2504 2504 fm.plain(sep, label='grep.sep')
2505 2505 if not opts.get('files_with_matches'):
2506 2506 fm.plain(sep, label='grep.sep')
2507 2507 if not opts.get('text') and binary():
2508 2508 fm.plain(_(" Binary file matches"))
2509 2509 else:
2510 2510 displaymatches(fm.nested('texts'), l)
2511 2511 fm.plain(eol)
2512 2512 found = True
2513 2513 if opts.get('files_with_matches'):
2514 2514 break
2515 2515 return found
2516 2516
2517 2517 def displaymatches(fm, l):
2518 2518 p = 0
2519 2519 for s, e in l.findpos():
2520 2520 if p < s:
2521 2521 fm.startitem()
2522 2522 fm.write('text', '%s', l.line[p:s])
2523 2523 fm.data(matched=False)
2524 2524 fm.startitem()
2525 2525 fm.write('text', '%s', l.line[s:e], label='grep.match')
2526 2526 fm.data(matched=True)
2527 2527 p = e
2528 2528 if p < len(l.line):
2529 2529 fm.startitem()
2530 2530 fm.write('text', '%s', l.line[p:])
2531 2531 fm.data(matched=False)
2532 2532 fm.end()
2533 2533
2534 2534 skip = {}
2535 2535 revfiles = {}
2536 2536 match = scmutil.match(repo[None], pats, opts)
2537 2537 found = False
2538 2538 follow = opts.get('follow')
2539 2539
2540 2540 def prep(ctx, fns):
2541 2541 rev = ctx.rev()
2542 2542 pctx = ctx.p1()
2543 2543 parent = pctx.rev()
2544 2544 matches.setdefault(rev, {})
2545 2545 matches.setdefault(parent, {})
2546 2546 files = revfiles.setdefault(rev, [])
2547 2547 for fn in fns:
2548 2548 flog = getfile(fn)
2549 2549 try:
2550 2550 fnode = ctx.filenode(fn)
2551 2551 except error.LookupError:
2552 2552 continue
2553 2553
2554 2554 copied = flog.renamed(fnode)
2555 2555 copy = follow and copied and copied[0]
2556 2556 if copy:
2557 2557 copies.setdefault(rev, {})[fn] = copy
2558 2558 if fn in skip:
2559 2559 if copy:
2560 2560 skip[copy] = True
2561 2561 continue
2562 2562 files.append(fn)
2563 2563
2564 2564 if fn not in matches[rev]:
2565 2565 grepbody(fn, rev, flog.read(fnode))
2566 2566
2567 2567 pfn = copy or fn
2568 2568 if pfn not in matches[parent]:
2569 2569 try:
2570 2570 fnode = pctx.filenode(pfn)
2571 2571 grepbody(pfn, parent, flog.read(fnode))
2572 2572 except error.LookupError:
2573 2573 pass
2574 2574
2575 2575 ui.pager('grep')
2576 2576 fm = ui.formatter('grep', opts)
2577 2577 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2578 2578 rev = ctx.rev()
2579 2579 parent = ctx.p1().rev()
2580 2580 for fn in sorted(revfiles.get(rev, [])):
2581 2581 states = matches[rev][fn]
2582 2582 copy = copies.get(rev, {}).get(fn)
2583 2583 if fn in skip:
2584 2584 if copy:
2585 2585 skip[copy] = True
2586 2586 continue
2587 2587 pstates = matches.get(parent, {}).get(copy or fn, [])
2588 2588 if pstates or states:
2589 2589 r = display(fm, fn, ctx, pstates, states)
2590 2590 found = found or r
2591 2591 if r and not opts.get('all'):
2592 2592 skip[fn] = True
2593 2593 if copy:
2594 2594 skip[copy] = True
2595 2595 del revfiles[rev]
2596 2596 # We will keep the matches dict for the duration of the window
2597 2597 # clear the matches dict once the window is over
2598 2598 if not revfiles:
2599 2599 matches.clear()
2600 2600 fm.end()
2601 2601
2602 2602 return not found
2603 2603
2604 2604 @command('heads',
2605 2605 [('r', 'rev', '',
2606 2606 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2607 2607 ('t', 'topo', False, _('show topological heads only')),
2608 2608 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2609 2609 ('c', 'closed', False, _('show normal and closed branch heads')),
2610 2610 ] + templateopts,
2611 2611 _('[-ct] [-r STARTREV] [REV]...'), cmdtype=readonly)
2612 2612 def heads(ui, repo, *branchrevs, **opts):
2613 2613 """show branch heads
2614 2614
2615 2615 With no arguments, show all open branch heads in the repository.
2616 2616 Branch heads are changesets that have no descendants on the
2617 2617 same branch. They are where development generally takes place and
2618 2618 are the usual targets for update and merge operations.
2619 2619
2620 2620 If one or more REVs are given, only open branch heads on the
2621 2621 branches associated with the specified changesets are shown. This
2622 2622 means that you can use :hg:`heads .` to see the heads on the
2623 2623 currently checked-out branch.
2624 2624
2625 2625 If -c/--closed is specified, also show branch heads marked closed
2626 2626 (see :hg:`commit --close-branch`).
2627 2627
2628 2628 If STARTREV is specified, only those heads that are descendants of
2629 2629 STARTREV will be displayed.
2630 2630
2631 2631 If -t/--topo is specified, named branch mechanics will be ignored and only
2632 2632 topological heads (changesets with no children) will be shown.
2633 2633
2634 2634 Returns 0 if matching heads are found, 1 if not.
2635 2635 """
2636 2636
2637 2637 opts = pycompat.byteskwargs(opts)
2638 2638 start = None
2639 2639 rev = opts.get('rev')
2640 2640 if rev:
2641 2641 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2642 2642 start = scmutil.revsingle(repo, rev, None).node()
2643 2643
2644 2644 if opts.get('topo'):
2645 2645 heads = [repo[h] for h in repo.heads(start)]
2646 2646 else:
2647 2647 heads = []
2648 2648 for branch in repo.branchmap():
2649 2649 heads += repo.branchheads(branch, start, opts.get('closed'))
2650 2650 heads = [repo[h] for h in heads]
2651 2651
2652 2652 if branchrevs:
2653 2653 branches = set(repo[br].branch() for br in branchrevs)
2654 2654 heads = [h for h in heads if h.branch() in branches]
2655 2655
2656 2656 if opts.get('active') and branchrevs:
2657 2657 dagheads = repo.heads(start)
2658 2658 heads = [h for h in heads if h.node() in dagheads]
2659 2659
2660 2660 if branchrevs:
2661 2661 haveheads = set(h.branch() for h in heads)
2662 2662 if branches - haveheads:
2663 2663 headless = ', '.join(b for b in branches - haveheads)
2664 2664 msg = _('no open branch heads found on branches %s')
2665 2665 if opts.get('rev'):
2666 2666 msg += _(' (started at %s)') % opts['rev']
2667 2667 ui.warn((msg + '\n') % headless)
2668 2668
2669 2669 if not heads:
2670 2670 return 1
2671 2671
2672 2672 ui.pager('heads')
2673 2673 heads = sorted(heads, key=lambda x: -x.rev())
2674 2674 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
2675 2675 for ctx in heads:
2676 2676 displayer.show(ctx)
2677 2677 displayer.close()
2678 2678
2679 2679 @command('help',
2680 2680 [('e', 'extension', None, _('show only help for extensions')),
2681 2681 ('c', 'command', None, _('show only help for commands')),
2682 2682 ('k', 'keyword', None, _('show topics matching keyword')),
2683 2683 ('s', 'system', [], _('show help for specific platform(s)')),
2684 2684 ],
2685 2685 _('[-ecks] [TOPIC]'),
2686 2686 norepo=True, cmdtype=readonly)
2687 2687 def help_(ui, name=None, **opts):
2688 2688 """show help for a given topic or a help overview
2689 2689
2690 2690 With no arguments, print a list of commands with short help messages.
2691 2691
2692 2692 Given a topic, extension, or command name, print help for that
2693 2693 topic.
2694 2694
2695 2695 Returns 0 if successful.
2696 2696 """
2697 2697
2698 2698 keep = opts.get(r'system') or []
2699 2699 if len(keep) == 0:
2700 2700 if pycompat.sysplatform.startswith('win'):
2701 2701 keep.append('windows')
2702 2702 elif pycompat.sysplatform == 'OpenVMS':
2703 2703 keep.append('vms')
2704 2704 elif pycompat.sysplatform == 'plan9':
2705 2705 keep.append('plan9')
2706 2706 else:
2707 2707 keep.append('unix')
2708 2708 keep.append(pycompat.sysplatform.lower())
2709 2709 if ui.verbose:
2710 2710 keep.append('verbose')
2711 2711
2712 2712 commands = sys.modules[__name__]
2713 2713 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2714 2714 ui.pager('help')
2715 2715 ui.write(formatted)
2716 2716
2717 2717
2718 2718 @command('identify|id',
2719 2719 [('r', 'rev', '',
2720 2720 _('identify the specified revision'), _('REV')),
2721 2721 ('n', 'num', None, _('show local revision number')),
2722 2722 ('i', 'id', None, _('show global revision id')),
2723 2723 ('b', 'branch', None, _('show branch')),
2724 2724 ('t', 'tags', None, _('show tags')),
2725 2725 ('B', 'bookmarks', None, _('show bookmarks')),
2726 2726 ] + remoteopts + formatteropts,
2727 2727 _('[-nibtB] [-r REV] [SOURCE]'),
2728 2728 optionalrepo=True, cmdtype=readonly)
2729 2729 def identify(ui, repo, source=None, rev=None,
2730 2730 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2731 2731 """identify the working directory or specified revision
2732 2732
2733 2733 Print a summary identifying the repository state at REV using one or
2734 2734 two parent hash identifiers, followed by a "+" if the working
2735 2735 directory has uncommitted changes, the branch name (if not default),
2736 2736 a list of tags, and a list of bookmarks.
2737 2737
2738 2738 When REV is not given, print a summary of the current state of the
2739 2739 repository including the working directory. Specify -r. to get information
2740 2740 of the working directory parent without scanning uncommitted changes.
2741 2741
2742 2742 Specifying a path to a repository root or Mercurial bundle will
2743 2743 cause lookup to operate on that repository/bundle.
2744 2744
2745 2745 .. container:: verbose
2746 2746
2747 2747 Examples:
2748 2748
2749 2749 - generate a build identifier for the working directory::
2750 2750
2751 2751 hg id --id > build-id.dat
2752 2752
2753 2753 - find the revision corresponding to a tag::
2754 2754
2755 2755 hg id -n -r 1.3
2756 2756
2757 2757 - check the most recent revision of a remote repository::
2758 2758
2759 2759 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2760 2760
2761 2761 See :hg:`log` for generating more information about specific revisions,
2762 2762 including full hash identifiers.
2763 2763
2764 2764 Returns 0 if successful.
2765 2765 """
2766 2766
2767 2767 opts = pycompat.byteskwargs(opts)
2768 2768 if not repo and not source:
2769 2769 raise error.Abort(_("there is no Mercurial repository here "
2770 2770 "(.hg not found)"))
2771 2771
2772 2772 if ui.debugflag:
2773 2773 hexfunc = hex
2774 2774 else:
2775 2775 hexfunc = short
2776 2776 default = not (num or id or branch or tags or bookmarks)
2777 2777 output = []
2778 2778 revs = []
2779 2779
2780 2780 if source:
2781 2781 source, branches = hg.parseurl(ui.expandpath(source))
2782 2782 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2783 2783 repo = peer.local()
2784 2784 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2785 2785
2786 2786 fm = ui.formatter('identify', opts)
2787 2787 fm.startitem()
2788 2788
2789 2789 if not repo:
2790 2790 if num or branch or tags:
2791 2791 raise error.Abort(
2792 2792 _("can't query remote revision number, branch, or tags"))
2793 2793 if not rev and revs:
2794 2794 rev = revs[0]
2795 2795 if not rev:
2796 2796 rev = "tip"
2797 2797
2798 2798 remoterev = peer.lookup(rev)
2799 2799 hexrev = hexfunc(remoterev)
2800 2800 if default or id:
2801 2801 output = [hexrev]
2802 2802 fm.data(id=hexrev)
2803 2803
2804 2804 def getbms():
2805 2805 bms = []
2806 2806
2807 2807 if 'bookmarks' in peer.listkeys('namespaces'):
2808 2808 hexremoterev = hex(remoterev)
2809 2809 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2810 2810 if bmr == hexremoterev]
2811 2811
2812 2812 return sorted(bms)
2813 2813
2814 2814 bms = getbms()
2815 2815 if bookmarks:
2816 2816 output.extend(bms)
2817 2817 elif default and not ui.quiet:
2818 2818 # multiple bookmarks for a single parent separated by '/'
2819 2819 bm = '/'.join(bms)
2820 2820 if bm:
2821 2821 output.append(bm)
2822 2822
2823 2823 fm.data(node=hex(remoterev))
2824 2824 fm.data(bookmarks=fm.formatlist(bms, name='bookmark'))
2825 2825 else:
2826 2826 if rev:
2827 2827 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2828 2828 ctx = scmutil.revsingle(repo, rev, None)
2829 2829
2830 2830 if ctx.rev() is None:
2831 2831 ctx = repo[None]
2832 2832 parents = ctx.parents()
2833 2833 taglist = []
2834 2834 for p in parents:
2835 2835 taglist.extend(p.tags())
2836 2836
2837 2837 dirty = ""
2838 2838 if ctx.dirty(missing=True, merge=False, branch=False):
2839 2839 dirty = '+'
2840 2840 fm.data(dirty=dirty)
2841 2841
2842 2842 hexoutput = [hexfunc(p.node()) for p in parents]
2843 2843 if default or id:
2844 2844 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
2845 2845 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
2846 2846
2847 2847 if num:
2848 2848 numoutput = ["%d" % p.rev() for p in parents]
2849 2849 output.append("%s%s" % ('+'.join(numoutput), dirty))
2850 2850
2851 2851 fn = fm.nested('parents')
2852 2852 for p in parents:
2853 2853 fn.startitem()
2854 2854 fn.data(rev=p.rev())
2855 2855 fn.data(node=p.hex())
2856 2856 fn.context(ctx=p)
2857 2857 fn.end()
2858 2858 else:
2859 2859 hexoutput = hexfunc(ctx.node())
2860 2860 if default or id:
2861 2861 output = [hexoutput]
2862 2862 fm.data(id=hexoutput)
2863 2863
2864 2864 if num:
2865 2865 output.append(pycompat.bytestr(ctx.rev()))
2866 2866 taglist = ctx.tags()
2867 2867
2868 2868 if default and not ui.quiet:
2869 2869 b = ctx.branch()
2870 2870 if b != 'default':
2871 2871 output.append("(%s)" % b)
2872 2872
2873 2873 # multiple tags for a single parent separated by '/'
2874 2874 t = '/'.join(taglist)
2875 2875 if t:
2876 2876 output.append(t)
2877 2877
2878 2878 # multiple bookmarks for a single parent separated by '/'
2879 2879 bm = '/'.join(ctx.bookmarks())
2880 2880 if bm:
2881 2881 output.append(bm)
2882 2882 else:
2883 2883 if branch:
2884 2884 output.append(ctx.branch())
2885 2885
2886 2886 if tags:
2887 2887 output.extend(taglist)
2888 2888
2889 2889 if bookmarks:
2890 2890 output.extend(ctx.bookmarks())
2891 2891
2892 2892 fm.data(node=ctx.hex())
2893 2893 fm.data(branch=ctx.branch())
2894 2894 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
2895 2895 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
2896 2896 fm.context(ctx=ctx)
2897 2897
2898 2898 fm.plain("%s\n" % ' '.join(output))
2899 2899 fm.end()
2900 2900
2901 2901 @command('import|patch',
2902 2902 [('p', 'strip', 1,
2903 2903 _('directory strip option for patch. This has the same '
2904 2904 'meaning as the corresponding patch option'), _('NUM')),
2905 2905 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2906 2906 ('e', 'edit', False, _('invoke editor on commit messages')),
2907 2907 ('f', 'force', None,
2908 2908 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2909 2909 ('', 'no-commit', None,
2910 2910 _("don't commit, just update the working directory")),
2911 2911 ('', 'bypass', None,
2912 2912 _("apply patch without touching the working directory")),
2913 2913 ('', 'partial', None,
2914 2914 _('commit even if some hunks fail')),
2915 2915 ('', 'exact', None,
2916 2916 _('abort if patch would apply lossily')),
2917 2917 ('', 'prefix', '',
2918 2918 _('apply patch to subdirectory'), _('DIR')),
2919 2919 ('', 'import-branch', None,
2920 2920 _('use any branch information in patch (implied by --exact)'))] +
2921 2921 commitopts + commitopts2 + similarityopts,
2922 2922 _('[OPTION]... PATCH...'))
2923 2923 def import_(ui, repo, patch1=None, *patches, **opts):
2924 2924 """import an ordered set of patches
2925 2925
2926 2926 Import a list of patches and commit them individually (unless
2927 2927 --no-commit is specified).
2928 2928
2929 2929 To read a patch from standard input (stdin), use "-" as the patch
2930 2930 name. If a URL is specified, the patch will be downloaded from
2931 2931 there.
2932 2932
2933 2933 Import first applies changes to the working directory (unless
2934 2934 --bypass is specified), import will abort if there are outstanding
2935 2935 changes.
2936 2936
2937 2937 Use --bypass to apply and commit patches directly to the
2938 2938 repository, without affecting the working directory. Without
2939 2939 --exact, patches will be applied on top of the working directory
2940 2940 parent revision.
2941 2941
2942 2942 You can import a patch straight from a mail message. Even patches
2943 2943 as attachments work (to use the body part, it must have type
2944 2944 text/plain or text/x-patch). From and Subject headers of email
2945 2945 message are used as default committer and commit message. All
2946 2946 text/plain body parts before first diff are added to the commit
2947 2947 message.
2948 2948
2949 2949 If the imported patch was generated by :hg:`export`, user and
2950 2950 description from patch override values from message headers and
2951 2951 body. Values given on command line with -m/--message and -u/--user
2952 2952 override these.
2953 2953
2954 2954 If --exact is specified, import will set the working directory to
2955 2955 the parent of each patch before applying it, and will abort if the
2956 2956 resulting changeset has a different ID than the one recorded in
2957 2957 the patch. This will guard against various ways that portable
2958 2958 patch formats and mail systems might fail to transfer Mercurial
2959 2959 data or metadata. See :hg:`bundle` for lossless transmission.
2960 2960
2961 2961 Use --partial to ensure a changeset will be created from the patch
2962 2962 even if some hunks fail to apply. Hunks that fail to apply will be
2963 2963 written to a <target-file>.rej file. Conflicts can then be resolved
2964 2964 by hand before :hg:`commit --amend` is run to update the created
2965 2965 changeset. This flag exists to let people import patches that
2966 2966 partially apply without losing the associated metadata (author,
2967 2967 date, description, ...).
2968 2968
2969 2969 .. note::
2970 2970
2971 2971 When no hunks apply cleanly, :hg:`import --partial` will create
2972 2972 an empty changeset, importing only the patch metadata.
2973 2973
2974 2974 With -s/--similarity, hg will attempt to discover renames and
2975 2975 copies in the patch in the same way as :hg:`addremove`.
2976 2976
2977 2977 It is possible to use external patch programs to perform the patch
2978 2978 by setting the ``ui.patch`` configuration option. For the default
2979 2979 internal tool, the fuzz can also be configured via ``patch.fuzz``.
2980 2980 See :hg:`help config` for more information about configuration
2981 2981 files and how to use these options.
2982 2982
2983 2983 See :hg:`help dates` for a list of formats valid for -d/--date.
2984 2984
2985 2985 .. container:: verbose
2986 2986
2987 2987 Examples:
2988 2988
2989 2989 - import a traditional patch from a website and detect renames::
2990 2990
2991 2991 hg import -s 80 http://example.com/bugfix.patch
2992 2992
2993 2993 - import a changeset from an hgweb server::
2994 2994
2995 2995 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
2996 2996
2997 2997 - import all the patches in an Unix-style mbox::
2998 2998
2999 2999 hg import incoming-patches.mbox
3000 3000
3001 3001 - import patches from stdin::
3002 3002
3003 3003 hg import -
3004 3004
3005 3005 - attempt to exactly restore an exported changeset (not always
3006 3006 possible)::
3007 3007
3008 3008 hg import --exact proposed-fix.patch
3009 3009
3010 3010 - use an external tool to apply a patch which is too fuzzy for
3011 3011 the default internal tool.
3012 3012
3013 3013 hg import --config ui.patch="patch --merge" fuzzy.patch
3014 3014
3015 3015 - change the default fuzzing from 2 to a less strict 7
3016 3016
3017 3017 hg import --config ui.fuzz=7 fuzz.patch
3018 3018
3019 3019 Returns 0 on success, 1 on partial success (see --partial).
3020 3020 """
3021 3021
3022 3022 opts = pycompat.byteskwargs(opts)
3023 3023 if not patch1:
3024 3024 raise error.Abort(_('need at least one patch to import'))
3025 3025
3026 3026 patches = (patch1,) + patches
3027 3027
3028 3028 date = opts.get('date')
3029 3029 if date:
3030 3030 opts['date'] = dateutil.parsedate(date)
3031 3031
3032 3032 exact = opts.get('exact')
3033 3033 update = not opts.get('bypass')
3034 3034 if not update and opts.get('no_commit'):
3035 3035 raise error.Abort(_('cannot use --no-commit with --bypass'))
3036 3036 try:
3037 3037 sim = float(opts.get('similarity') or 0)
3038 3038 except ValueError:
3039 3039 raise error.Abort(_('similarity must be a number'))
3040 3040 if sim < 0 or sim > 100:
3041 3041 raise error.Abort(_('similarity must be between 0 and 100'))
3042 3042 if sim and not update:
3043 3043 raise error.Abort(_('cannot use --similarity with --bypass'))
3044 3044 if exact:
3045 3045 if opts.get('edit'):
3046 3046 raise error.Abort(_('cannot use --exact with --edit'))
3047 3047 if opts.get('prefix'):
3048 3048 raise error.Abort(_('cannot use --exact with --prefix'))
3049 3049
3050 3050 base = opts["base"]
3051 3051 wlock = dsguard = lock = tr = None
3052 3052 msgs = []
3053 3053 ret = 0
3054 3054
3055 3055
3056 3056 try:
3057 3057 wlock = repo.wlock()
3058 3058
3059 3059 if update:
3060 3060 cmdutil.checkunfinished(repo)
3061 3061 if (exact or not opts.get('force')):
3062 3062 cmdutil.bailifchanged(repo)
3063 3063
3064 3064 if not opts.get('no_commit'):
3065 3065 lock = repo.lock()
3066 3066 tr = repo.transaction('import')
3067 3067 else:
3068 3068 dsguard = dirstateguard.dirstateguard(repo, 'import')
3069 3069 parents = repo[None].parents()
3070 3070 for patchurl in patches:
3071 3071 if patchurl == '-':
3072 3072 ui.status(_('applying patch from stdin\n'))
3073 3073 patchfile = ui.fin
3074 3074 patchurl = 'stdin' # for error message
3075 3075 else:
3076 3076 patchurl = os.path.join(base, patchurl)
3077 3077 ui.status(_('applying %s\n') % patchurl)
3078 3078 patchfile = hg.openpath(ui, patchurl)
3079 3079
3080 3080 haspatch = False
3081 3081 for hunk in patch.split(patchfile):
3082 3082 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3083 3083 parents, opts,
3084 3084 msgs, hg.clean)
3085 3085 if msg:
3086 3086 haspatch = True
3087 3087 ui.note(msg + '\n')
3088 3088 if update or exact:
3089 3089 parents = repo[None].parents()
3090 3090 else:
3091 3091 parents = [repo[node]]
3092 3092 if rej:
3093 3093 ui.write_err(_("patch applied partially\n"))
3094 3094 ui.write_err(_("(fix the .rej files and run "
3095 3095 "`hg commit --amend`)\n"))
3096 3096 ret = 1
3097 3097 break
3098 3098
3099 3099 if not haspatch:
3100 3100 raise error.Abort(_('%s: no diffs found') % patchurl)
3101 3101
3102 3102 if tr:
3103 3103 tr.close()
3104 3104 if msgs:
3105 3105 repo.savecommitmessage('\n* * *\n'.join(msgs))
3106 3106 if dsguard:
3107 3107 dsguard.close()
3108 3108 return ret
3109 3109 finally:
3110 3110 if tr:
3111 3111 tr.release()
3112 3112 release(lock, dsguard, wlock)
3113 3113
3114 3114 @command('incoming|in',
3115 3115 [('f', 'force', None,
3116 3116 _('run even if remote repository is unrelated')),
3117 3117 ('n', 'newest-first', None, _('show newest record first')),
3118 3118 ('', 'bundle', '',
3119 3119 _('file to store the bundles into'), _('FILE')),
3120 3120 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3121 3121 ('B', 'bookmarks', False, _("compare bookmarks")),
3122 3122 ('b', 'branch', [],
3123 3123 _('a specific branch you would like to pull'), _('BRANCH')),
3124 3124 ] + logopts + remoteopts + subrepoopts,
3125 3125 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3126 3126 def incoming(ui, repo, source="default", **opts):
3127 3127 """show new changesets found in source
3128 3128
3129 3129 Show new changesets found in the specified path/URL or the default
3130 3130 pull location. These are the changesets that would have been pulled
3131 3131 by :hg:`pull` at the time you issued this command.
3132 3132
3133 3133 See pull for valid source format details.
3134 3134
3135 3135 .. container:: verbose
3136 3136
3137 3137 With -B/--bookmarks, the result of bookmark comparison between
3138 3138 local and remote repositories is displayed. With -v/--verbose,
3139 3139 status is also displayed for each bookmark like below::
3140 3140
3141 3141 BM1 01234567890a added
3142 3142 BM2 1234567890ab advanced
3143 3143 BM3 234567890abc diverged
3144 3144 BM4 34567890abcd changed
3145 3145
3146 3146 The action taken locally when pulling depends on the
3147 3147 status of each bookmark:
3148 3148
3149 3149 :``added``: pull will create it
3150 3150 :``advanced``: pull will update it
3151 3151 :``diverged``: pull will create a divergent bookmark
3152 3152 :``changed``: result depends on remote changesets
3153 3153
3154 3154 From the point of view of pulling behavior, bookmark
3155 3155 existing only in the remote repository are treated as ``added``,
3156 3156 even if it is in fact locally deleted.
3157 3157
3158 3158 .. container:: verbose
3159 3159
3160 3160 For remote repository, using --bundle avoids downloading the
3161 3161 changesets twice if the incoming is followed by a pull.
3162 3162
3163 3163 Examples:
3164 3164
3165 3165 - show incoming changes with patches and full description::
3166 3166
3167 3167 hg incoming -vp
3168 3168
3169 3169 - show incoming changes excluding merges, store a bundle::
3170 3170
3171 3171 hg in -vpM --bundle incoming.hg
3172 3172 hg pull incoming.hg
3173 3173
3174 3174 - briefly list changes inside a bundle::
3175 3175
3176 3176 hg in changes.hg -T "{desc|firstline}\\n"
3177 3177
3178 3178 Returns 0 if there are incoming changes, 1 otherwise.
3179 3179 """
3180 3180 opts = pycompat.byteskwargs(opts)
3181 3181 if opts.get('graph'):
3182 3182 logcmdutil.checkunsupportedgraphflags([], opts)
3183 3183 def display(other, chlist, displayer):
3184 3184 revdag = logcmdutil.graphrevs(other, chlist, opts)
3185 3185 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3186 3186 graphmod.asciiedges)
3187 3187
3188 3188 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3189 3189 return 0
3190 3190
3191 3191 if opts.get('bundle') and opts.get('subrepos'):
3192 3192 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3193 3193
3194 3194 if opts.get('bookmarks'):
3195 3195 source, branches = hg.parseurl(ui.expandpath(source),
3196 3196 opts.get('branch'))
3197 3197 other = hg.peer(repo, opts, source)
3198 3198 if 'bookmarks' not in other.listkeys('namespaces'):
3199 3199 ui.warn(_("remote doesn't support bookmarks\n"))
3200 3200 return 0
3201 3201 ui.pager('incoming')
3202 3202 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3203 3203 return bookmarks.incoming(ui, repo, other)
3204 3204
3205 3205 repo._subtoppath = ui.expandpath(source)
3206 3206 try:
3207 3207 return hg.incoming(ui, repo, source, opts)
3208 3208 finally:
3209 3209 del repo._subtoppath
3210 3210
3211 3211
3212 3212 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3213 3213 norepo=True)
3214 3214 def init(ui, dest=".", **opts):
3215 3215 """create a new repository in the given directory
3216 3216
3217 3217 Initialize a new repository in the given directory. If the given
3218 3218 directory does not exist, it will be created.
3219 3219
3220 3220 If no directory is given, the current directory is used.
3221 3221
3222 3222 It is possible to specify an ``ssh://`` URL as the destination.
3223 3223 See :hg:`help urls` for more information.
3224 3224
3225 3225 Returns 0 on success.
3226 3226 """
3227 3227 opts = pycompat.byteskwargs(opts)
3228 3228 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3229 3229
3230 3230 @command('locate',
3231 3231 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3232 3232 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3233 3233 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3234 3234 ] + walkopts,
3235 3235 _('[OPTION]... [PATTERN]...'))
3236 3236 def locate(ui, repo, *pats, **opts):
3237 3237 """locate files matching specific patterns (DEPRECATED)
3238 3238
3239 3239 Print files under Mercurial control in the working directory whose
3240 3240 names match the given patterns.
3241 3241
3242 3242 By default, this command searches all directories in the working
3243 3243 directory. To search just the current directory and its
3244 3244 subdirectories, use "--include .".
3245 3245
3246 3246 If no patterns are given to match, this command prints the names
3247 3247 of all files under Mercurial control in the working directory.
3248 3248
3249 3249 If you want to feed the output of this command into the "xargs"
3250 3250 command, use the -0 option to both this command and "xargs". This
3251 3251 will avoid the problem of "xargs" treating single filenames that
3252 3252 contain whitespace as multiple filenames.
3253 3253
3254 3254 See :hg:`help files` for a more versatile command.
3255 3255
3256 3256 Returns 0 if a match is found, 1 otherwise.
3257 3257 """
3258 3258 opts = pycompat.byteskwargs(opts)
3259 3259 if opts.get('print0'):
3260 3260 end = '\0'
3261 3261 else:
3262 3262 end = '\n'
3263 3263 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3264 3264
3265 3265 ret = 1
3266 3266 m = scmutil.match(ctx, pats, opts, default='relglob',
3267 3267 badfn=lambda x, y: False)
3268 3268
3269 3269 ui.pager('locate')
3270 3270 for abs in ctx.matches(m):
3271 3271 if opts.get('fullpath'):
3272 3272 ui.write(repo.wjoin(abs), end)
3273 3273 else:
3274 3274 ui.write(((pats and m.rel(abs)) or abs), end)
3275 3275 ret = 0
3276 3276
3277 3277 return ret
3278 3278
3279 3279 @command('^log|history',
3280 3280 [('f', 'follow', None,
3281 3281 _('follow changeset history, or file history across copies and renames')),
3282 3282 ('', 'follow-first', None,
3283 3283 _('only follow the first parent of merge changesets (DEPRECATED)')),
3284 3284 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3285 3285 ('C', 'copies', None, _('show copied files')),
3286 3286 ('k', 'keyword', [],
3287 3287 _('do case-insensitive search for a given text'), _('TEXT')),
3288 3288 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3289 3289 ('L', 'line-range', [],
3290 3290 _('follow line range of specified file (EXPERIMENTAL)'),
3291 3291 _('FILE,RANGE')),
3292 3292 ('', 'removed', None, _('include revisions where files were removed')),
3293 3293 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3294 3294 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3295 3295 ('', 'only-branch', [],
3296 3296 _('show only changesets within the given named branch (DEPRECATED)'),
3297 3297 _('BRANCH')),
3298 3298 ('b', 'branch', [],
3299 3299 _('show changesets within the given named branch'), _('BRANCH')),
3300 3300 ('P', 'prune', [],
3301 3301 _('do not display revision or any of its ancestors'), _('REV')),
3302 3302 ] + logopts + walkopts,
3303 3303 _('[OPTION]... [FILE]'),
3304 3304 inferrepo=True, cmdtype=readonly)
3305 3305 def log(ui, repo, *pats, **opts):
3306 3306 """show revision history of entire repository or files
3307 3307
3308 3308 Print the revision history of the specified files or the entire
3309 3309 project.
3310 3310
3311 3311 If no revision range is specified, the default is ``tip:0`` unless
3312 3312 --follow is set, in which case the working directory parent is
3313 3313 used as the starting revision.
3314 3314
3315 3315 File history is shown without following rename or copy history of
3316 3316 files. Use -f/--follow with a filename to follow history across
3317 3317 renames and copies. --follow without a filename will only show
3318 3318 ancestors of the starting revision.
3319 3319
3320 3320 By default this command prints revision number and changeset id,
3321 3321 tags, non-trivial parents, user, date and time, and a summary for
3322 3322 each commit. When the -v/--verbose switch is used, the list of
3323 3323 changed files and full commit message are shown.
3324 3324
3325 3325 With --graph the revisions are shown as an ASCII art DAG with the most
3326 3326 recent changeset at the top.
3327 3327 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3328 3328 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3329 3329 changeset from the lines below is a parent of the 'o' merge on the same
3330 3330 line.
3331 3331 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3332 3332 of a '|' indicates one or more revisions in a path are omitted.
3333 3333
3334 3334 .. container:: verbose
3335 3335
3336 3336 Use -L/--line-range FILE,M:N options to follow the history of lines
3337 3337 from M to N in FILE. With -p/--patch only diff hunks affecting
3338 3338 specified line range will be shown. This option requires --follow;
3339 3339 it can be specified multiple times. Currently, this option is not
3340 3340 compatible with --graph. This option is experimental.
3341 3341
3342 3342 .. note::
3343 3343
3344 3344 :hg:`log --patch` may generate unexpected diff output for merge
3345 3345 changesets, as it will only compare the merge changeset against
3346 3346 its first parent. Also, only files different from BOTH parents
3347 3347 will appear in files:.
3348 3348
3349 3349 .. note::
3350 3350
3351 3351 For performance reasons, :hg:`log FILE` may omit duplicate changes
3352 3352 made on branches and will not show removals or mode changes. To
3353 3353 see all such changes, use the --removed switch.
3354 3354
3355 3355 .. container:: verbose
3356 3356
3357 3357 .. note::
3358 3358
3359 3359 The history resulting from -L/--line-range options depends on diff
3360 3360 options; for instance if white-spaces are ignored, respective changes
3361 3361 with only white-spaces in specified line range will not be listed.
3362 3362
3363 3363 .. container:: verbose
3364 3364
3365 3365 Some examples:
3366 3366
3367 3367 - changesets with full descriptions and file lists::
3368 3368
3369 3369 hg log -v
3370 3370
3371 3371 - changesets ancestral to the working directory::
3372 3372
3373 3373 hg log -f
3374 3374
3375 3375 - last 10 commits on the current branch::
3376 3376
3377 3377 hg log -l 10 -b .
3378 3378
3379 3379 - changesets showing all modifications of a file, including removals::
3380 3380
3381 3381 hg log --removed file.c
3382 3382
3383 3383 - all changesets that touch a directory, with diffs, excluding merges::
3384 3384
3385 3385 hg log -Mp lib/
3386 3386
3387 3387 - all revision numbers that match a keyword::
3388 3388
3389 3389 hg log -k bug --template "{rev}\\n"
3390 3390
3391 3391 - the full hash identifier of the working directory parent::
3392 3392
3393 3393 hg log -r . --template "{node}\\n"
3394 3394
3395 3395 - list available log templates::
3396 3396
3397 3397 hg log -T list
3398 3398
3399 3399 - check if a given changeset is included in a tagged release::
3400 3400
3401 3401 hg log -r "a21ccf and ancestor(1.9)"
3402 3402
3403 3403 - find all changesets by some user in a date range::
3404 3404
3405 3405 hg log -k alice -d "may 2008 to jul 2008"
3406 3406
3407 3407 - summary of all changesets after the last tag::
3408 3408
3409 3409 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3410 3410
3411 3411 - changesets touching lines 13 to 23 for file.c::
3412 3412
3413 3413 hg log -L file.c,13:23
3414 3414
3415 3415 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3416 3416 main.c with patch::
3417 3417
3418 3418 hg log -L file.c,13:23 -L main.c,2:6 -p
3419 3419
3420 3420 See :hg:`help dates` for a list of formats valid for -d/--date.
3421 3421
3422 3422 See :hg:`help revisions` for more about specifying and ordering
3423 3423 revisions.
3424 3424
3425 3425 See :hg:`help templates` for more about pre-packaged styles and
3426 3426 specifying custom templates. The default template used by the log
3427 3427 command can be customized via the ``ui.logtemplate`` configuration
3428 3428 setting.
3429 3429
3430 3430 Returns 0 on success.
3431 3431
3432 3432 """
3433 3433 opts = pycompat.byteskwargs(opts)
3434 3434 linerange = opts.get('line_range')
3435 3435
3436 3436 if linerange and not opts.get('follow'):
3437 3437 raise error.Abort(_('--line-range requires --follow'))
3438 3438
3439 3439 if linerange and pats:
3440 3440 # TODO: take pats as patterns with no line-range filter
3441 3441 raise error.Abort(
3442 3442 _('FILE arguments are not compatible with --line-range option')
3443 3443 )
3444 3444
3445 3445 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3446 3446 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3447 3447 if linerange:
3448 3448 # TODO: should follow file history from logcmdutil._initialrevs(),
3449 3449 # then filter the result by logcmdutil._makerevset() and --limit
3450 3450 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3451 3451
3452 3452 getrenamed = None
3453 3453 if opts.get('copies'):
3454 3454 endrev = None
3455 3455 if opts.get('rev'):
3456 3456 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3457 3457 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3458 3458
3459 3459 ui.pager('log')
3460 3460 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3461 3461 buffered=True)
3462 3462 if opts.get('graph'):
3463 3463 displayfn = logcmdutil.displaygraphrevs
3464 3464 else:
3465 3465 displayfn = logcmdutil.displayrevs
3466 3466 displayfn(ui, repo, revs, displayer, getrenamed)
3467 3467
3468 3468 @command('manifest',
3469 3469 [('r', 'rev', '', _('revision to display'), _('REV')),
3470 3470 ('', 'all', False, _("list files from all revisions"))]
3471 3471 + formatteropts,
3472 3472 _('[-r REV]'), cmdtype=readonly)
3473 3473 def manifest(ui, repo, node=None, rev=None, **opts):
3474 3474 """output the current or given revision of the project manifest
3475 3475
3476 3476 Print a list of version controlled files for the given revision.
3477 3477 If no revision is given, the first parent of the working directory
3478 3478 is used, or the null revision if no revision is checked out.
3479 3479
3480 3480 With -v, print file permissions, symlink and executable bits.
3481 3481 With --debug, print file revision hashes.
3482 3482
3483 3483 If option --all is specified, the list of all files from all revisions
3484 3484 is printed. This includes deleted and renamed files.
3485 3485
3486 3486 Returns 0 on success.
3487 3487 """
3488 3488 opts = pycompat.byteskwargs(opts)
3489 3489 fm = ui.formatter('manifest', opts)
3490 3490
3491 3491 if opts.get('all'):
3492 3492 if rev or node:
3493 3493 raise error.Abort(_("can't specify a revision with --all"))
3494 3494
3495 3495 res = []
3496 3496 prefix = "data/"
3497 3497 suffix = ".i"
3498 3498 plen = len(prefix)
3499 3499 slen = len(suffix)
3500 3500 with repo.lock():
3501 3501 for fn, b, size in repo.store.datafiles():
3502 3502 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3503 3503 res.append(fn[plen:-slen])
3504 3504 ui.pager('manifest')
3505 3505 for f in res:
3506 3506 fm.startitem()
3507 3507 fm.write("path", '%s\n', f)
3508 3508 fm.end()
3509 3509 return
3510 3510
3511 3511 if rev and node:
3512 3512 raise error.Abort(_("please specify just one revision"))
3513 3513
3514 3514 if not node:
3515 3515 node = rev
3516 3516
3517 3517 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3518 3518 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3519 3519 if node:
3520 3520 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3521 3521 ctx = scmutil.revsingle(repo, node)
3522 3522 mf = ctx.manifest()
3523 3523 ui.pager('manifest')
3524 3524 for f in ctx:
3525 3525 fm.startitem()
3526 3526 fl = ctx[f].flags()
3527 3527 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3528 3528 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3529 3529 fm.write('path', '%s\n', f)
3530 3530 fm.end()
3531 3531
3532 3532 @command('^merge',
3533 3533 [('f', 'force', None,
3534 3534 _('force a merge including outstanding changes (DEPRECATED)')),
3535 3535 ('r', 'rev', '', _('revision to merge'), _('REV')),
3536 3536 ('P', 'preview', None,
3537 3537 _('review revisions to merge (no merge is performed)')),
3538 3538 ('', 'abort', None, _('abort the ongoing merge')),
3539 3539 ] + mergetoolopts,
3540 3540 _('[-P] [[-r] REV]'))
3541 3541 def merge(ui, repo, node=None, **opts):
3542 3542 """merge another revision into working directory
3543 3543
3544 3544 The current working directory is updated with all changes made in
3545 3545 the requested revision since the last common predecessor revision.
3546 3546
3547 3547 Files that changed between either parent are marked as changed for
3548 3548 the next commit and a commit must be performed before any further
3549 3549 updates to the repository are allowed. The next commit will have
3550 3550 two parents.
3551 3551
3552 3552 ``--tool`` can be used to specify the merge tool used for file
3553 3553 merges. It overrides the HGMERGE environment variable and your
3554 3554 configuration files. See :hg:`help merge-tools` for options.
3555 3555
3556 3556 If no revision is specified, the working directory's parent is a
3557 3557 head revision, and the current branch contains exactly one other
3558 3558 head, the other head is merged with by default. Otherwise, an
3559 3559 explicit revision with which to merge with must be provided.
3560 3560
3561 3561 See :hg:`help resolve` for information on handling file conflicts.
3562 3562
3563 3563 To undo an uncommitted merge, use :hg:`merge --abort` which
3564 3564 will check out a clean copy of the original merge parent, losing
3565 3565 all changes.
3566 3566
3567 3567 Returns 0 on success, 1 if there are unresolved files.
3568 3568 """
3569 3569
3570 3570 opts = pycompat.byteskwargs(opts)
3571 3571 abort = opts.get('abort')
3572 3572 if abort and repo.dirstate.p2() == nullid:
3573 3573 cmdutil.wrongtooltocontinue(repo, _('merge'))
3574 3574 if abort:
3575 3575 if node:
3576 3576 raise error.Abort(_("cannot specify a node with --abort"))
3577 3577 if opts.get('rev'):
3578 3578 raise error.Abort(_("cannot specify both --rev and --abort"))
3579 3579 if opts.get('preview'):
3580 3580 raise error.Abort(_("cannot specify --preview with --abort"))
3581 3581 if opts.get('rev') and node:
3582 3582 raise error.Abort(_("please specify just one revision"))
3583 3583 if not node:
3584 3584 node = opts.get('rev')
3585 3585
3586 3586 if node:
3587 3587 node = scmutil.revsingle(repo, node).node()
3588 3588
3589 3589 if not node and not abort:
3590 3590 node = repo[destutil.destmerge(repo)].node()
3591 3591
3592 3592 if opts.get('preview'):
3593 3593 # find nodes that are ancestors of p2 but not of p1
3594 3594 p1 = repo.lookup('.')
3595 3595 p2 = repo.lookup(node)
3596 3596 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3597 3597
3598 3598 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3599 3599 for node in nodes:
3600 3600 displayer.show(repo[node])
3601 3601 displayer.close()
3602 3602 return 0
3603 3603
3604 3604 try:
3605 3605 # ui.forcemerge is an internal variable, do not document
3606 3606 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3607 3607 force = opts.get('force')
3608 3608 labels = ['working copy', 'merge rev']
3609 3609 return hg.merge(repo, node, force=force, mergeforce=force,
3610 3610 labels=labels, abort=abort)
3611 3611 finally:
3612 3612 ui.setconfig('ui', 'forcemerge', '', 'merge')
3613 3613
3614 3614 @command('outgoing|out',
3615 3615 [('f', 'force', None, _('run even when the destination is unrelated')),
3616 3616 ('r', 'rev', [],
3617 3617 _('a changeset intended to be included in the destination'), _('REV')),
3618 3618 ('n', 'newest-first', None, _('show newest record first')),
3619 3619 ('B', 'bookmarks', False, _('compare bookmarks')),
3620 3620 ('b', 'branch', [], _('a specific branch you would like to push'),
3621 3621 _('BRANCH')),
3622 3622 ] + logopts + remoteopts + subrepoopts,
3623 3623 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3624 3624 def outgoing(ui, repo, dest=None, **opts):
3625 3625 """show changesets not found in the destination
3626 3626
3627 3627 Show changesets not found in the specified destination repository
3628 3628 or the default push location. These are the changesets that would
3629 3629 be pushed if a push was requested.
3630 3630
3631 3631 See pull for details of valid destination formats.
3632 3632
3633 3633 .. container:: verbose
3634 3634
3635 3635 With -B/--bookmarks, the result of bookmark comparison between
3636 3636 local and remote repositories is displayed. With -v/--verbose,
3637 3637 status is also displayed for each bookmark like below::
3638 3638
3639 3639 BM1 01234567890a added
3640 3640 BM2 deleted
3641 3641 BM3 234567890abc advanced
3642 3642 BM4 34567890abcd diverged
3643 3643 BM5 4567890abcde changed
3644 3644
3645 3645 The action taken when pushing depends on the
3646 3646 status of each bookmark:
3647 3647
3648 3648 :``added``: push with ``-B`` will create it
3649 3649 :``deleted``: push with ``-B`` will delete it
3650 3650 :``advanced``: push will update it
3651 3651 :``diverged``: push with ``-B`` will update it
3652 3652 :``changed``: push with ``-B`` will update it
3653 3653
3654 3654 From the point of view of pushing behavior, bookmarks
3655 3655 existing only in the remote repository are treated as
3656 3656 ``deleted``, even if it is in fact added remotely.
3657 3657
3658 3658 Returns 0 if there are outgoing changes, 1 otherwise.
3659 3659 """
3660 3660 opts = pycompat.byteskwargs(opts)
3661 3661 if opts.get('graph'):
3662 3662 logcmdutil.checkunsupportedgraphflags([], opts)
3663 3663 o, other = hg._outgoing(ui, repo, dest, opts)
3664 3664 if not o:
3665 3665 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3666 3666 return
3667 3667
3668 3668 revdag = logcmdutil.graphrevs(repo, o, opts)
3669 3669 ui.pager('outgoing')
3670 3670 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
3671 3671 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3672 3672 graphmod.asciiedges)
3673 3673 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3674 3674 return 0
3675 3675
3676 3676 if opts.get('bookmarks'):
3677 3677 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3678 3678 dest, branches = hg.parseurl(dest, opts.get('branch'))
3679 3679 other = hg.peer(repo, opts, dest)
3680 3680 if 'bookmarks' not in other.listkeys('namespaces'):
3681 3681 ui.warn(_("remote doesn't support bookmarks\n"))
3682 3682 return 0
3683 3683 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3684 3684 ui.pager('outgoing')
3685 3685 return bookmarks.outgoing(ui, repo, other)
3686 3686
3687 3687 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3688 3688 try:
3689 3689 return hg.outgoing(ui, repo, dest, opts)
3690 3690 finally:
3691 3691 del repo._subtoppath
3692 3692
3693 3693 @command('parents',
3694 3694 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3695 3695 ] + templateopts,
3696 3696 _('[-r REV] [FILE]'),
3697 3697 inferrepo=True)
3698 3698 def parents(ui, repo, file_=None, **opts):
3699 3699 """show the parents of the working directory or revision (DEPRECATED)
3700 3700
3701 3701 Print the working directory's parent revisions. If a revision is
3702 3702 given via -r/--rev, the parent of that revision will be printed.
3703 3703 If a file argument is given, the revision in which the file was
3704 3704 last changed (before the working directory revision or the
3705 3705 argument to --rev if given) is printed.
3706 3706
3707 3707 This command is equivalent to::
3708 3708
3709 3709 hg log -r "p1()+p2()" or
3710 3710 hg log -r "p1(REV)+p2(REV)" or
3711 3711 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3712 3712 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3713 3713
3714 3714 See :hg:`summary` and :hg:`help revsets` for related information.
3715 3715
3716 3716 Returns 0 on success.
3717 3717 """
3718 3718
3719 3719 opts = pycompat.byteskwargs(opts)
3720 3720 rev = opts.get('rev')
3721 3721 if rev:
3722 3722 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3723 3723 ctx = scmutil.revsingle(repo, rev, None)
3724 3724
3725 3725 if file_:
3726 3726 m = scmutil.match(ctx, (file_,), opts)
3727 3727 if m.anypats() or len(m.files()) != 1:
3728 3728 raise error.Abort(_('can only specify an explicit filename'))
3729 3729 file_ = m.files()[0]
3730 3730 filenodes = []
3731 3731 for cp in ctx.parents():
3732 3732 if not cp:
3733 3733 continue
3734 3734 try:
3735 3735 filenodes.append(cp.filenode(file_))
3736 3736 except error.LookupError:
3737 3737 pass
3738 3738 if not filenodes:
3739 3739 raise error.Abort(_("'%s' not found in manifest!") % file_)
3740 3740 p = []
3741 3741 for fn in filenodes:
3742 3742 fctx = repo.filectx(file_, fileid=fn)
3743 3743 p.append(fctx.node())
3744 3744 else:
3745 3745 p = [cp.node() for cp in ctx.parents()]
3746 3746
3747 3747 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3748 3748 for n in p:
3749 3749 if n != nullid:
3750 3750 displayer.show(repo[n])
3751 3751 displayer.close()
3752 3752
3753 3753 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True,
3754 3754 cmdtype=readonly)
3755 3755 def paths(ui, repo, search=None, **opts):
3756 3756 """show aliases for remote repositories
3757 3757
3758 3758 Show definition of symbolic path name NAME. If no name is given,
3759 3759 show definition of all available names.
3760 3760
3761 3761 Option -q/--quiet suppresses all output when searching for NAME
3762 3762 and shows only the path names when listing all definitions.
3763 3763
3764 3764 Path names are defined in the [paths] section of your
3765 3765 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3766 3766 repository, ``.hg/hgrc`` is used, too.
3767 3767
3768 3768 The path names ``default`` and ``default-push`` have a special
3769 3769 meaning. When performing a push or pull operation, they are used
3770 3770 as fallbacks if no location is specified on the command-line.
3771 3771 When ``default-push`` is set, it will be used for push and
3772 3772 ``default`` will be used for pull; otherwise ``default`` is used
3773 3773 as the fallback for both. When cloning a repository, the clone
3774 3774 source is written as ``default`` in ``.hg/hgrc``.
3775 3775
3776 3776 .. note::
3777 3777
3778 3778 ``default`` and ``default-push`` apply to all inbound (e.g.
3779 3779 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3780 3780 and :hg:`bundle`) operations.
3781 3781
3782 3782 See :hg:`help urls` for more information.
3783 3783
3784 3784 Returns 0 on success.
3785 3785 """
3786 3786
3787 3787 opts = pycompat.byteskwargs(opts)
3788 3788 ui.pager('paths')
3789 3789 if search:
3790 3790 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3791 3791 if name == search]
3792 3792 else:
3793 3793 pathitems = sorted(ui.paths.iteritems())
3794 3794
3795 3795 fm = ui.formatter('paths', opts)
3796 3796 if fm.isplain():
3797 3797 hidepassword = util.hidepassword
3798 3798 else:
3799 3799 hidepassword = bytes
3800 3800 if ui.quiet:
3801 3801 namefmt = '%s\n'
3802 3802 else:
3803 3803 namefmt = '%s = '
3804 3804 showsubopts = not search and not ui.quiet
3805 3805
3806 3806 for name, path in pathitems:
3807 3807 fm.startitem()
3808 3808 fm.condwrite(not search, 'name', namefmt, name)
3809 3809 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3810 3810 for subopt, value in sorted(path.suboptions.items()):
3811 3811 assert subopt not in ('name', 'url')
3812 3812 if showsubopts:
3813 3813 fm.plain('%s:%s = ' % (name, subopt))
3814 3814 fm.condwrite(showsubopts, subopt, '%s\n', value)
3815 3815
3816 3816 fm.end()
3817 3817
3818 3818 if search and not pathitems:
3819 3819 if not ui.quiet:
3820 3820 ui.warn(_("not found!\n"))
3821 3821 return 1
3822 3822 else:
3823 3823 return 0
3824 3824
3825 3825 @command('phase',
3826 3826 [('p', 'public', False, _('set changeset phase to public')),
3827 3827 ('d', 'draft', False, _('set changeset phase to draft')),
3828 3828 ('s', 'secret', False, _('set changeset phase to secret')),
3829 3829 ('f', 'force', False, _('allow to move boundary backward')),
3830 3830 ('r', 'rev', [], _('target revision'), _('REV')),
3831 3831 ],
3832 3832 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3833 3833 def phase(ui, repo, *revs, **opts):
3834 3834 """set or show the current phase name
3835 3835
3836 3836 With no argument, show the phase name of the current revision(s).
3837 3837
3838 3838 With one of -p/--public, -d/--draft or -s/--secret, change the
3839 3839 phase value of the specified revisions.
3840 3840
3841 3841 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
3842 3842 lower phase to a higher phase. Phases are ordered as follows::
3843 3843
3844 3844 public < draft < secret
3845 3845
3846 3846 Returns 0 on success, 1 if some phases could not be changed.
3847 3847
3848 3848 (For more information about the phases concept, see :hg:`help phases`.)
3849 3849 """
3850 3850 opts = pycompat.byteskwargs(opts)
3851 3851 # search for a unique phase argument
3852 3852 targetphase = None
3853 3853 for idx, name in enumerate(phases.phasenames):
3854 3854 if opts[name]:
3855 3855 if targetphase is not None:
3856 3856 raise error.Abort(_('only one phase can be specified'))
3857 3857 targetphase = idx
3858 3858
3859 3859 # look for specified revision
3860 3860 revs = list(revs)
3861 3861 revs.extend(opts['rev'])
3862 3862 if not revs:
3863 3863 # display both parents as the second parent phase can influence
3864 3864 # the phase of a merge commit
3865 3865 revs = [c.rev() for c in repo[None].parents()]
3866 3866
3867 3867 revs = scmutil.revrange(repo, revs)
3868 3868
3869 3869 ret = 0
3870 3870 if targetphase is None:
3871 3871 # display
3872 3872 for r in revs:
3873 3873 ctx = repo[r]
3874 3874 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3875 3875 else:
3876 3876 with repo.lock(), repo.transaction("phase") as tr:
3877 3877 # set phase
3878 3878 if not revs:
3879 3879 raise error.Abort(_('empty revision set'))
3880 3880 nodes = [repo[r].node() for r in revs]
3881 3881 # moving revision from public to draft may hide them
3882 3882 # We have to check result on an unfiltered repository
3883 3883 unfi = repo.unfiltered()
3884 3884 getphase = unfi._phasecache.phase
3885 3885 olddata = [getphase(unfi, r) for r in unfi]
3886 3886 phases.advanceboundary(repo, tr, targetphase, nodes)
3887 3887 if opts['force']:
3888 3888 phases.retractboundary(repo, tr, targetphase, nodes)
3889 3889 getphase = unfi._phasecache.phase
3890 3890 newdata = [getphase(unfi, r) for r in unfi]
3891 3891 changes = sum(newdata[r] != olddata[r] for r in unfi)
3892 3892 cl = unfi.changelog
3893 3893 rejected = [n for n in nodes
3894 3894 if newdata[cl.rev(n)] < targetphase]
3895 3895 if rejected:
3896 3896 ui.warn(_('cannot move %i changesets to a higher '
3897 3897 'phase, use --force\n') % len(rejected))
3898 3898 ret = 1
3899 3899 if changes:
3900 3900 msg = _('phase changed for %i changesets\n') % changes
3901 3901 if ret:
3902 3902 ui.status(msg)
3903 3903 else:
3904 3904 ui.note(msg)
3905 3905 else:
3906 3906 ui.warn(_('no phases changed\n'))
3907 3907 return ret
3908 3908
3909 3909 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3910 3910 """Run after a changegroup has been added via pull/unbundle
3911 3911
3912 3912 This takes arguments below:
3913 3913
3914 3914 :modheads: change of heads by pull/unbundle
3915 3915 :optupdate: updating working directory is needed or not
3916 3916 :checkout: update destination revision (or None to default destination)
3917 3917 :brev: a name, which might be a bookmark to be activated after updating
3918 3918 """
3919 3919 if modheads == 0:
3920 3920 return
3921 3921 if optupdate:
3922 3922 try:
3923 3923 return hg.updatetotally(ui, repo, checkout, brev)
3924 3924 except error.UpdateAbort as inst:
3925 3925 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
3926 3926 hint = inst.hint
3927 3927 raise error.UpdateAbort(msg, hint=hint)
3928 3928 if modheads > 1:
3929 3929 currentbranchheads = len(repo.branchheads())
3930 3930 if currentbranchheads == modheads:
3931 3931 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3932 3932 elif currentbranchheads > 1:
3933 3933 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3934 3934 "merge)\n"))
3935 3935 else:
3936 3936 ui.status(_("(run 'hg heads' to see heads)\n"))
3937 3937 elif not ui.configbool('commands', 'update.requiredest'):
3938 3938 ui.status(_("(run 'hg update' to get a working copy)\n"))
3939 3939
3940 3940 @command('^pull',
3941 3941 [('u', 'update', None,
3942 3942 _('update to new branch head if new descendants were pulled')),
3943 3943 ('f', 'force', None, _('run even when remote repository is unrelated')),
3944 3944 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3945 3945 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3946 3946 ('b', 'branch', [], _('a specific branch you would like to pull'),
3947 3947 _('BRANCH')),
3948 3948 ] + remoteopts,
3949 3949 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3950 3950 def pull(ui, repo, source="default", **opts):
3951 3951 """pull changes from the specified source
3952 3952
3953 3953 Pull changes from a remote repository to a local one.
3954 3954
3955 3955 This finds all changes from the repository at the specified path
3956 3956 or URL and adds them to a local repository (the current one unless
3957 3957 -R is specified). By default, this does not update the copy of the
3958 3958 project in the working directory.
3959 3959
3960 3960 Use :hg:`incoming` if you want to see what would have been added
3961 3961 by a pull at the time you issued this command. If you then decide
3962 3962 to add those changes to the repository, you should use :hg:`pull
3963 3963 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3964 3964
3965 3965 If SOURCE is omitted, the 'default' path will be used.
3966 3966 See :hg:`help urls` for more information.
3967 3967
3968 3968 Specifying bookmark as ``.`` is equivalent to specifying the active
3969 3969 bookmark's name.
3970 3970
3971 3971 Returns 0 on success, 1 if an update had unresolved files.
3972 3972 """
3973 3973
3974 3974 opts = pycompat.byteskwargs(opts)
3975 3975 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3976 3976 msg = _('update destination required by configuration')
3977 3977 hint = _('use hg pull followed by hg update DEST')
3978 3978 raise error.Abort(msg, hint=hint)
3979 3979
3980 3980 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3981 3981 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3982 3982 other = hg.peer(repo, opts, source)
3983 3983 try:
3984 3984 revs, checkout = hg.addbranchrevs(repo, other, branches,
3985 3985 opts.get('rev'))
3986 3986
3987 3987
3988 3988 pullopargs = {}
3989 3989 if opts.get('bookmark'):
3990 3990 if not revs:
3991 3991 revs = []
3992 3992 # The list of bookmark used here is not the one used to actually
3993 3993 # update the bookmark name. This can result in the revision pulled
3994 3994 # not ending up with the name of the bookmark because of a race
3995 3995 # condition on the server. (See issue 4689 for details)
3996 3996 remotebookmarks = other.listkeys('bookmarks')
3997 3997 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
3998 3998 pullopargs['remotebookmarks'] = remotebookmarks
3999 3999 for b in opts['bookmark']:
4000 4000 b = repo._bookmarks.expandname(b)
4001 4001 if b not in remotebookmarks:
4002 4002 raise error.Abort(_('remote bookmark %s not found!') % b)
4003 4003 revs.append(hex(remotebookmarks[b]))
4004 4004
4005 4005 if revs:
4006 4006 try:
4007 4007 # When 'rev' is a bookmark name, we cannot guarantee that it
4008 4008 # will be updated with that name because of a race condition
4009 4009 # server side. (See issue 4689 for details)
4010 4010 oldrevs = revs
4011 4011 revs = [] # actually, nodes
4012 4012 for r in oldrevs:
4013 4013 node = other.lookup(r)
4014 4014 revs.append(node)
4015 4015 if r == checkout:
4016 4016 checkout = node
4017 4017 except error.CapabilityError:
4018 4018 err = _("other repository doesn't support revision lookup, "
4019 4019 "so a rev cannot be specified.")
4020 4020 raise error.Abort(err)
4021 4021
4022 4022 wlock = util.nullcontextmanager()
4023 4023 if opts.get('update'):
4024 4024 wlock = repo.wlock()
4025 4025 with wlock:
4026 4026 pullopargs.update(opts.get('opargs', {}))
4027 4027 modheads = exchange.pull(repo, other, heads=revs,
4028 4028 force=opts.get('force'),
4029 4029 bookmarks=opts.get('bookmark', ()),
4030 4030 opargs=pullopargs).cgresult
4031 4031
4032 4032 # brev is a name, which might be a bookmark to be activated at
4033 4033 # the end of the update. In other words, it is an explicit
4034 4034 # destination of the update
4035 4035 brev = None
4036 4036
4037 4037 if checkout:
4038 4038 checkout = "%d" % repo.changelog.rev(checkout)
4039 4039
4040 4040 # order below depends on implementation of
4041 4041 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4042 4042 # because 'checkout' is determined without it.
4043 4043 if opts.get('rev'):
4044 4044 brev = opts['rev'][0]
4045 4045 elif opts.get('branch'):
4046 4046 brev = opts['branch'][0]
4047 4047 else:
4048 4048 brev = branches[0]
4049 4049 repo._subtoppath = source
4050 4050 try:
4051 4051 ret = postincoming(ui, repo, modheads, opts.get('update'),
4052 4052 checkout, brev)
4053 4053
4054 4054 finally:
4055 4055 del repo._subtoppath
4056 4056
4057 4057 finally:
4058 4058 other.close()
4059 4059 return ret
4060 4060
4061 4061 @command('^push',
4062 4062 [('f', 'force', None, _('force push')),
4063 4063 ('r', 'rev', [],
4064 4064 _('a changeset intended to be included in the destination'),
4065 4065 _('REV')),
4066 4066 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4067 4067 ('b', 'branch', [],
4068 4068 _('a specific branch you would like to push'), _('BRANCH')),
4069 4069 ('', 'new-branch', False, _('allow pushing a new branch')),
4070 4070 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4071 4071 ] + remoteopts,
4072 4072 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4073 4073 def push(ui, repo, dest=None, **opts):
4074 4074 """push changes to the specified destination
4075 4075
4076 4076 Push changesets from the local repository to the specified
4077 4077 destination.
4078 4078
4079 4079 This operation is symmetrical to pull: it is identical to a pull
4080 4080 in the destination repository from the current one.
4081 4081
4082 4082 By default, push will not allow creation of new heads at the
4083 4083 destination, since multiple heads would make it unclear which head
4084 4084 to use. In this situation, it is recommended to pull and merge
4085 4085 before pushing.
4086 4086
4087 4087 Use --new-branch if you want to allow push to create a new named
4088 4088 branch that is not present at the destination. This allows you to
4089 4089 only create a new branch without forcing other changes.
4090 4090
4091 4091 .. note::
4092 4092
4093 4093 Extra care should be taken with the -f/--force option,
4094 4094 which will push all new heads on all branches, an action which will
4095 4095 almost always cause confusion for collaborators.
4096 4096
4097 4097 If -r/--rev is used, the specified revision and all its ancestors
4098 4098 will be pushed to the remote repository.
4099 4099
4100 4100 If -B/--bookmark is used, the specified bookmarked revision, its
4101 4101 ancestors, and the bookmark will be pushed to the remote
4102 4102 repository. Specifying ``.`` is equivalent to specifying the active
4103 4103 bookmark's name.
4104 4104
4105 4105 Please see :hg:`help urls` for important details about ``ssh://``
4106 4106 URLs. If DESTINATION is omitted, a default path will be used.
4107 4107
4108 4108 .. container:: verbose
4109 4109
4110 4110 The --pushvars option sends strings to the server that become
4111 4111 environment variables prepended with ``HG_USERVAR_``. For example,
4112 4112 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4113 4113 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4114 4114
4115 4115 pushvars can provide for user-overridable hooks as well as set debug
4116 4116 levels. One example is having a hook that blocks commits containing
4117 4117 conflict markers, but enables the user to override the hook if the file
4118 4118 is using conflict markers for testing purposes or the file format has
4119 4119 strings that look like conflict markers.
4120 4120
4121 4121 By default, servers will ignore `--pushvars`. To enable it add the
4122 4122 following to your configuration file::
4123 4123
4124 4124 [push]
4125 4125 pushvars.server = true
4126 4126
4127 4127 Returns 0 if push was successful, 1 if nothing to push.
4128 4128 """
4129 4129
4130 4130 opts = pycompat.byteskwargs(opts)
4131 4131 if opts.get('bookmark'):
4132 4132 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4133 4133 for b in opts['bookmark']:
4134 4134 # translate -B options to -r so changesets get pushed
4135 4135 b = repo._bookmarks.expandname(b)
4136 4136 if b in repo._bookmarks:
4137 4137 opts.setdefault('rev', []).append(b)
4138 4138 else:
4139 4139 # if we try to push a deleted bookmark, translate it to null
4140 4140 # this lets simultaneous -r, -b options continue working
4141 4141 opts.setdefault('rev', []).append("null")
4142 4142
4143 4143 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4144 4144 if not path:
4145 4145 raise error.Abort(_('default repository not configured!'),
4146 4146 hint=_("see 'hg help config.paths'"))
4147 4147 dest = path.pushloc or path.loc
4148 4148 branches = (path.branch, opts.get('branch') or [])
4149 4149 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4150 4150 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4151 4151 other = hg.peer(repo, opts, dest)
4152 4152
4153 4153 if revs:
4154 4154 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4155 4155 if not revs:
4156 4156 raise error.Abort(_("specified revisions evaluate to an empty set"),
4157 4157 hint=_("use different revision arguments"))
4158 4158 elif path.pushrev:
4159 4159 # It doesn't make any sense to specify ancestor revisions. So limit
4160 4160 # to DAG heads to make discovery simpler.
4161 4161 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4162 4162 revs = scmutil.revrange(repo, [expr])
4163 4163 revs = [repo[rev].node() for rev in revs]
4164 4164 if not revs:
4165 4165 raise error.Abort(_('default push revset for path evaluates to an '
4166 4166 'empty set'))
4167 4167
4168 4168 repo._subtoppath = dest
4169 4169 try:
4170 4170 # push subrepos depth-first for coherent ordering
4171 4171 c = repo['.']
4172 4172 subs = c.substate # only repos that are committed
4173 4173 for s in sorted(subs):
4174 4174 result = c.sub(s).push(opts)
4175 4175 if result == 0:
4176 4176 return not result
4177 4177 finally:
4178 4178 del repo._subtoppath
4179 4179
4180 4180 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4181 4181 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4182 4182
4183 4183 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4184 4184 newbranch=opts.get('new_branch'),
4185 4185 bookmarks=opts.get('bookmark', ()),
4186 4186 opargs=opargs)
4187 4187
4188 4188 result = not pushop.cgresult
4189 4189
4190 4190 if pushop.bkresult is not None:
4191 4191 if pushop.bkresult == 2:
4192 4192 result = 2
4193 4193 elif not result and pushop.bkresult:
4194 4194 result = 2
4195 4195
4196 4196 return result
4197 4197
4198 4198 @command('recover', [])
4199 4199 def recover(ui, repo):
4200 4200 """roll back an interrupted transaction
4201 4201
4202 4202 Recover from an interrupted commit or pull.
4203 4203
4204 4204 This command tries to fix the repository status after an
4205 4205 interrupted operation. It should only be necessary when Mercurial
4206 4206 suggests it.
4207 4207
4208 4208 Returns 0 if successful, 1 if nothing to recover or verify fails.
4209 4209 """
4210 4210 if repo.recover():
4211 4211 return hg.verify(repo)
4212 4212 return 1
4213 4213
4214 4214 @command('^remove|rm',
4215 4215 [('A', 'after', None, _('record delete for missing files')),
4216 4216 ('f', 'force', None,
4217 4217 _('forget added files, delete modified files')),
4218 4218 ] + subrepoopts + walkopts + dryrunopts,
4219 4219 _('[OPTION]... FILE...'),
4220 4220 inferrepo=True)
4221 4221 def remove(ui, repo, *pats, **opts):
4222 4222 """remove the specified files on the next commit
4223 4223
4224 4224 Schedule the indicated files for removal from the current branch.
4225 4225
4226 4226 This command schedules the files to be removed at the next commit.
4227 4227 To undo a remove before that, see :hg:`revert`. To undo added
4228 4228 files, see :hg:`forget`.
4229 4229
4230 4230 .. container:: verbose
4231 4231
4232 4232 -A/--after can be used to remove only files that have already
4233 4233 been deleted, -f/--force can be used to force deletion, and -Af
4234 4234 can be used to remove files from the next revision without
4235 4235 deleting them from the working directory.
4236 4236
4237 4237 The following table details the behavior of remove for different
4238 4238 file states (columns) and option combinations (rows). The file
4239 4239 states are Added [A], Clean [C], Modified [M] and Missing [!]
4240 4240 (as reported by :hg:`status`). The actions are Warn, Remove
4241 4241 (from branch) and Delete (from disk):
4242 4242
4243 4243 ========= == == == ==
4244 4244 opt/state A C M !
4245 4245 ========= == == == ==
4246 4246 none W RD W R
4247 4247 -f R RD RD R
4248 4248 -A W W W R
4249 4249 -Af R R R R
4250 4250 ========= == == == ==
4251 4251
4252 4252 .. note::
4253 4253
4254 4254 :hg:`remove` never deletes files in Added [A] state from the
4255 4255 working directory, not even if ``--force`` is specified.
4256 4256
4257 4257 Returns 0 on success, 1 if any warnings encountered.
4258 4258 """
4259 4259
4260 4260 opts = pycompat.byteskwargs(opts)
4261 4261 after, force = opts.get('after'), opts.get('force')
4262 4262 dryrun = opts.get('dry_run')
4263 4263 if not pats and not after:
4264 4264 raise error.Abort(_('no files specified'))
4265 4265
4266 4266 m = scmutil.match(repo[None], pats, opts)
4267 4267 subrepos = opts.get('subrepos')
4268 4268 return cmdutil.remove(ui, repo, m, "", after, force, subrepos,
4269 4269 dryrun=dryrun)
4270 4270
4271 4271 @command('rename|move|mv',
4272 4272 [('A', 'after', None, _('record a rename that has already occurred')),
4273 4273 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4274 4274 ] + walkopts + dryrunopts,
4275 4275 _('[OPTION]... SOURCE... DEST'))
4276 4276 def rename(ui, repo, *pats, **opts):
4277 4277 """rename files; equivalent of copy + remove
4278 4278
4279 4279 Mark dest as copies of sources; mark sources for deletion. If dest
4280 4280 is a directory, copies are put in that directory. If dest is a
4281 4281 file, there can only be one source.
4282 4282
4283 4283 By default, this command copies the contents of files as they
4284 4284 exist in the working directory. If invoked with -A/--after, the
4285 4285 operation is recorded, but no copying is performed.
4286 4286
4287 4287 This command takes effect at the next commit. To undo a rename
4288 4288 before that, see :hg:`revert`.
4289 4289
4290 4290 Returns 0 on success, 1 if errors are encountered.
4291 4291 """
4292 4292 opts = pycompat.byteskwargs(opts)
4293 4293 with repo.wlock(False):
4294 4294 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4295 4295
4296 4296 @command('resolve',
4297 4297 [('a', 'all', None, _('select all unresolved files')),
4298 4298 ('l', 'list', None, _('list state of files needing merge')),
4299 4299 ('m', 'mark', None, _('mark files as resolved')),
4300 4300 ('u', 'unmark', None, _('mark files as unresolved')),
4301 4301 ('n', 'no-status', None, _('hide status prefix'))]
4302 4302 + mergetoolopts + walkopts + formatteropts,
4303 4303 _('[OPTION]... [FILE]...'),
4304 4304 inferrepo=True)
4305 4305 def resolve(ui, repo, *pats, **opts):
4306 4306 """redo merges or set/view the merge status of files
4307 4307
4308 4308 Merges with unresolved conflicts are often the result of
4309 4309 non-interactive merging using the ``internal:merge`` configuration
4310 4310 setting, or a command-line merge tool like ``diff3``. The resolve
4311 4311 command is used to manage the files involved in a merge, after
4312 4312 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4313 4313 working directory must have two parents). See :hg:`help
4314 4314 merge-tools` for information on configuring merge tools.
4315 4315
4316 4316 The resolve command can be used in the following ways:
4317 4317
4318 4318 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4319 4319 files, discarding any previous merge attempts. Re-merging is not
4320 4320 performed for files already marked as resolved. Use ``--all/-a``
4321 4321 to select all unresolved files. ``--tool`` can be used to specify
4322 4322 the merge tool used for the given files. It overrides the HGMERGE
4323 4323 environment variable and your configuration files. Previous file
4324 4324 contents are saved with a ``.orig`` suffix.
4325 4325
4326 4326 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4327 4327 (e.g. after having manually fixed-up the files). The default is
4328 4328 to mark all unresolved files.
4329 4329
4330 4330 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4331 4331 default is to mark all resolved files.
4332 4332
4333 4333 - :hg:`resolve -l`: list files which had or still have conflicts.
4334 4334 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4335 4335 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4336 4336 the list. See :hg:`help filesets` for details.
4337 4337
4338 4338 .. note::
4339 4339
4340 4340 Mercurial will not let you commit files with unresolved merge
4341 4341 conflicts. You must use :hg:`resolve -m ...` before you can
4342 4342 commit after a conflicting merge.
4343 4343
4344 4344 Returns 0 on success, 1 if any files fail a resolve attempt.
4345 4345 """
4346 4346
4347 4347 opts = pycompat.byteskwargs(opts)
4348 4348 flaglist = 'all mark unmark list no_status'.split()
4349 4349 all, mark, unmark, show, nostatus = \
4350 4350 [opts.get(o) for o in flaglist]
4351 4351
4352 4352 if (show and (mark or unmark)) or (mark and unmark):
4353 4353 raise error.Abort(_("too many options specified"))
4354 4354 if pats and all:
4355 4355 raise error.Abort(_("can't specify --all and patterns"))
4356 4356 if not (all or pats or show or mark or unmark):
4357 4357 raise error.Abort(_('no files or directories specified'),
4358 4358 hint=('use --all to re-merge all unresolved files'))
4359 4359
4360 4360 if show:
4361 4361 ui.pager('resolve')
4362 4362 fm = ui.formatter('resolve', opts)
4363 4363 ms = mergemod.mergestate.read(repo)
4364 4364 m = scmutil.match(repo[None], pats, opts)
4365 4365
4366 4366 # Labels and keys based on merge state. Unresolved path conflicts show
4367 4367 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4368 4368 # resolved conflicts.
4369 4369 mergestateinfo = {
4370 4370 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4371 4371 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4372 4372 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4373 4373 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4374 4374 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4375 4375 'D'),
4376 4376 }
4377 4377
4378 4378 for f in ms:
4379 4379 if not m(f):
4380 4380 continue
4381 4381
4382 4382 label, key = mergestateinfo[ms[f]]
4383 4383 fm.startitem()
4384 4384 fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
4385 4385 fm.write('path', '%s\n', f, label=label)
4386 4386 fm.end()
4387 4387 return 0
4388 4388
4389 4389 with repo.wlock():
4390 4390 ms = mergemod.mergestate.read(repo)
4391 4391
4392 4392 if not (ms.active() or repo.dirstate.p2() != nullid):
4393 4393 raise error.Abort(
4394 4394 _('resolve command not applicable when not merging'))
4395 4395
4396 4396 wctx = repo[None]
4397 4397
4398 4398 if (ms.mergedriver
4399 4399 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4400 4400 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4401 4401 ms.commit()
4402 4402 # allow mark and unmark to go through
4403 4403 if not mark and not unmark and not proceed:
4404 4404 return 1
4405 4405
4406 4406 m = scmutil.match(wctx, pats, opts)
4407 4407 ret = 0
4408 4408 didwork = False
4409 4409 runconclude = False
4410 4410
4411 4411 tocomplete = []
4412 4412 for f in ms:
4413 4413 if not m(f):
4414 4414 continue
4415 4415
4416 4416 didwork = True
4417 4417
4418 4418 # don't let driver-resolved files be marked, and run the conclude
4419 4419 # step if asked to resolve
4420 4420 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4421 4421 exact = m.exact(f)
4422 4422 if mark:
4423 4423 if exact:
4424 4424 ui.warn(_('not marking %s as it is driver-resolved\n')
4425 4425 % f)
4426 4426 elif unmark:
4427 4427 if exact:
4428 4428 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4429 4429 % f)
4430 4430 else:
4431 4431 runconclude = True
4432 4432 continue
4433 4433
4434 4434 # path conflicts must be resolved manually
4435 4435 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4436 4436 mergemod.MERGE_RECORD_RESOLVED_PATH):
4437 4437 if mark:
4438 4438 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4439 4439 elif unmark:
4440 4440 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4441 4441 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4442 4442 ui.warn(_('%s: path conflict must be resolved manually\n')
4443 4443 % f)
4444 4444 continue
4445 4445
4446 4446 if mark:
4447 4447 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4448 4448 elif unmark:
4449 4449 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4450 4450 else:
4451 4451 # backup pre-resolve (merge uses .orig for its own purposes)
4452 4452 a = repo.wjoin(f)
4453 4453 try:
4454 4454 util.copyfile(a, a + ".resolve")
4455 4455 except (IOError, OSError) as inst:
4456 4456 if inst.errno != errno.ENOENT:
4457 4457 raise
4458 4458
4459 4459 try:
4460 4460 # preresolve file
4461 4461 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4462 4462 'resolve')
4463 4463 complete, r = ms.preresolve(f, wctx)
4464 4464 if not complete:
4465 4465 tocomplete.append(f)
4466 4466 elif r:
4467 4467 ret = 1
4468 4468 finally:
4469 4469 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4470 4470 ms.commit()
4471 4471
4472 4472 # replace filemerge's .orig file with our resolve file, but only
4473 4473 # for merges that are complete
4474 4474 if complete:
4475 4475 try:
4476 4476 util.rename(a + ".resolve",
4477 4477 scmutil.origpath(ui, repo, a))
4478 4478 except OSError as inst:
4479 4479 if inst.errno != errno.ENOENT:
4480 4480 raise
4481 4481
4482 4482 for f in tocomplete:
4483 4483 try:
4484 4484 # resolve file
4485 4485 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4486 4486 'resolve')
4487 4487 r = ms.resolve(f, wctx)
4488 4488 if r:
4489 4489 ret = 1
4490 4490 finally:
4491 4491 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4492 4492 ms.commit()
4493 4493
4494 4494 # replace filemerge's .orig file with our resolve file
4495 4495 a = repo.wjoin(f)
4496 4496 try:
4497 4497 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4498 4498 except OSError as inst:
4499 4499 if inst.errno != errno.ENOENT:
4500 4500 raise
4501 4501
4502 4502 ms.commit()
4503 4503 ms.recordactions()
4504 4504
4505 4505 if not didwork and pats:
4506 4506 hint = None
4507 4507 if not any([p for p in pats if p.find(':') >= 0]):
4508 4508 pats = ['path:%s' % p for p in pats]
4509 4509 m = scmutil.match(wctx, pats, opts)
4510 4510 for f in ms:
4511 4511 if not m(f):
4512 4512 continue
4513 4513 flags = ''.join(['-%s ' % o[0:1] for o in flaglist
4514 4514 if opts.get(o)])
4515 4515 hint = _("(try: hg resolve %s%s)\n") % (
4516 4516 flags,
4517 4517 ' '.join(pats))
4518 4518 break
4519 4519 ui.warn(_("arguments do not match paths that need resolving\n"))
4520 4520 if hint:
4521 4521 ui.warn(hint)
4522 4522 elif ms.mergedriver and ms.mdstate() != 's':
4523 4523 # run conclude step when either a driver-resolved file is requested
4524 4524 # or there are no driver-resolved files
4525 4525 # we can't use 'ret' to determine whether any files are unresolved
4526 4526 # because we might not have tried to resolve some
4527 4527 if ((runconclude or not list(ms.driverresolved()))
4528 4528 and not list(ms.unresolved())):
4529 4529 proceed = mergemod.driverconclude(repo, ms, wctx)
4530 4530 ms.commit()
4531 4531 if not proceed:
4532 4532 return 1
4533 4533
4534 4534 # Nudge users into finishing an unfinished operation
4535 4535 unresolvedf = list(ms.unresolved())
4536 4536 driverresolvedf = list(ms.driverresolved())
4537 4537 if not unresolvedf and not driverresolvedf:
4538 4538 ui.status(_('(no more unresolved files)\n'))
4539 4539 cmdutil.checkafterresolved(repo)
4540 4540 elif not unresolvedf:
4541 4541 ui.status(_('(no more unresolved files -- '
4542 4542 'run "hg resolve --all" to conclude)\n'))
4543 4543
4544 4544 return ret
4545 4545
4546 4546 @command('revert',
4547 4547 [('a', 'all', None, _('revert all changes when no arguments given')),
4548 4548 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4549 4549 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4550 4550 ('C', 'no-backup', None, _('do not save backup copies of files')),
4551 4551 ('i', 'interactive', None, _('interactively select the changes')),
4552 4552 ] + walkopts + dryrunopts,
4553 4553 _('[OPTION]... [-r REV] [NAME]...'))
4554 4554 def revert(ui, repo, *pats, **opts):
4555 4555 """restore files to their checkout state
4556 4556
4557 4557 .. note::
4558 4558
4559 4559 To check out earlier revisions, you should use :hg:`update REV`.
4560 4560 To cancel an uncommitted merge (and lose your changes),
4561 4561 use :hg:`merge --abort`.
4562 4562
4563 4563 With no revision specified, revert the specified files or directories
4564 4564 to the contents they had in the parent of the working directory.
4565 4565 This restores the contents of files to an unmodified
4566 4566 state and unschedules adds, removes, copies, and renames. If the
4567 4567 working directory has two parents, you must explicitly specify a
4568 4568 revision.
4569 4569
4570 4570 Using the -r/--rev or -d/--date options, revert the given files or
4571 4571 directories to their states as of a specific revision. Because
4572 4572 revert does not change the working directory parents, this will
4573 4573 cause these files to appear modified. This can be helpful to "back
4574 4574 out" some or all of an earlier change. See :hg:`backout` for a
4575 4575 related method.
4576 4576
4577 4577 Modified files are saved with a .orig suffix before reverting.
4578 4578 To disable these backups, use --no-backup. It is possible to store
4579 4579 the backup files in a custom directory relative to the root of the
4580 4580 repository by setting the ``ui.origbackuppath`` configuration
4581 4581 option.
4582 4582
4583 4583 See :hg:`help dates` for a list of formats valid for -d/--date.
4584 4584
4585 4585 See :hg:`help backout` for a way to reverse the effect of an
4586 4586 earlier changeset.
4587 4587
4588 4588 Returns 0 on success.
4589 4589 """
4590 4590
4591 4591 opts = pycompat.byteskwargs(opts)
4592 4592 if opts.get("date"):
4593 4593 if opts.get("rev"):
4594 4594 raise error.Abort(_("you can't specify a revision and a date"))
4595 4595 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4596 4596
4597 4597 parent, p2 = repo.dirstate.parents()
4598 4598 if not opts.get('rev') and p2 != nullid:
4599 4599 # revert after merge is a trap for new users (issue2915)
4600 4600 raise error.Abort(_('uncommitted merge with no revision specified'),
4601 4601 hint=_("use 'hg update' or see 'hg help revert'"))
4602 4602
4603 4603 rev = opts.get('rev')
4604 4604 if rev:
4605 4605 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4606 4606 ctx = scmutil.revsingle(repo, rev)
4607 4607
4608 4608 if (not (pats or opts.get('include') or opts.get('exclude') or
4609 4609 opts.get('all') or opts.get('interactive'))):
4610 4610 msg = _("no files or directories specified")
4611 4611 if p2 != nullid:
4612 4612 hint = _("uncommitted merge, use --all to discard all changes,"
4613 4613 " or 'hg update -C .' to abort the merge")
4614 4614 raise error.Abort(msg, hint=hint)
4615 4615 dirty = any(repo.status())
4616 4616 node = ctx.node()
4617 4617 if node != parent:
4618 4618 if dirty:
4619 4619 hint = _("uncommitted changes, use --all to discard all"
4620 4620 " changes, or 'hg update %s' to update") % ctx.rev()
4621 4621 else:
4622 4622 hint = _("use --all to revert all files,"
4623 4623 " or 'hg update %s' to update") % ctx.rev()
4624 4624 elif dirty:
4625 4625 hint = _("uncommitted changes, use --all to discard all changes")
4626 4626 else:
4627 4627 hint = _("use --all to revert all files")
4628 4628 raise error.Abort(msg, hint=hint)
4629 4629
4630 4630 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
4631 4631 **pycompat.strkwargs(opts))
4632 4632
4633 4633 @command('rollback', dryrunopts +
4634 4634 [('f', 'force', False, _('ignore safety measures'))])
4635 4635 def rollback(ui, repo, **opts):
4636 4636 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4637 4637
4638 4638 Please use :hg:`commit --amend` instead of rollback to correct
4639 4639 mistakes in the last commit.
4640 4640
4641 4641 This command should be used with care. There is only one level of
4642 4642 rollback, and there is no way to undo a rollback. It will also
4643 4643 restore the dirstate at the time of the last transaction, losing
4644 4644 any dirstate changes since that time. This command does not alter
4645 4645 the working directory.
4646 4646
4647 4647 Transactions are used to encapsulate the effects of all commands
4648 4648 that create new changesets or propagate existing changesets into a
4649 4649 repository.
4650 4650
4651 4651 .. container:: verbose
4652 4652
4653 4653 For example, the following commands are transactional, and their
4654 4654 effects can be rolled back:
4655 4655
4656 4656 - commit
4657 4657 - import
4658 4658 - pull
4659 4659 - push (with this repository as the destination)
4660 4660 - unbundle
4661 4661
4662 4662 To avoid permanent data loss, rollback will refuse to rollback a
4663 4663 commit transaction if it isn't checked out. Use --force to
4664 4664 override this protection.
4665 4665
4666 4666 The rollback command can be entirely disabled by setting the
4667 4667 ``ui.rollback`` configuration setting to false. If you're here
4668 4668 because you want to use rollback and it's disabled, you can
4669 4669 re-enable the command by setting ``ui.rollback`` to true.
4670 4670
4671 4671 This command is not intended for use on public repositories. Once
4672 4672 changes are visible for pull by other users, rolling a transaction
4673 4673 back locally is ineffective (someone else may already have pulled
4674 4674 the changes). Furthermore, a race is possible with readers of the
4675 4675 repository; for example an in-progress pull from the repository
4676 4676 may fail if a rollback is performed.
4677 4677
4678 4678 Returns 0 on success, 1 if no rollback data is available.
4679 4679 """
4680 4680 if not ui.configbool('ui', 'rollback'):
4681 4681 raise error.Abort(_('rollback is disabled because it is unsafe'),
4682 4682 hint=('see `hg help -v rollback` for information'))
4683 4683 return repo.rollback(dryrun=opts.get(r'dry_run'),
4684 4684 force=opts.get(r'force'))
4685 4685
4686 4686 @command('root', [], cmdtype=readonly)
4687 4687 def root(ui, repo):
4688 4688 """print the root (top) of the current working directory
4689 4689
4690 4690 Print the root directory of the current repository.
4691 4691
4692 4692 Returns 0 on success.
4693 4693 """
4694 4694 ui.write(repo.root + "\n")
4695 4695
4696 4696 @command('^serve',
4697 4697 [('A', 'accesslog', '', _('name of access log file to write to'),
4698 4698 _('FILE')),
4699 4699 ('d', 'daemon', None, _('run server in background')),
4700 4700 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4701 4701 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4702 4702 # use string type, then we can check if something was passed
4703 4703 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4704 4704 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4705 4705 _('ADDR')),
4706 4706 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4707 4707 _('PREFIX')),
4708 4708 ('n', 'name', '',
4709 4709 _('name to show in web pages (default: working directory)'), _('NAME')),
4710 4710 ('', 'web-conf', '',
4711 4711 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4712 4712 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4713 4713 _('FILE')),
4714 4714 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4715 4715 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4716 4716 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4717 4717 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4718 4718 ('', 'style', '', _('template style to use'), _('STYLE')),
4719 4719 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4720 4720 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4721 4721 + subrepoopts,
4722 4722 _('[OPTION]...'),
4723 4723 optionalrepo=True)
4724 4724 def serve(ui, repo, **opts):
4725 4725 """start stand-alone webserver
4726 4726
4727 4727 Start a local HTTP repository browser and pull server. You can use
4728 4728 this for ad-hoc sharing and browsing of repositories. It is
4729 4729 recommended to use a real web server to serve a repository for
4730 4730 longer periods of time.
4731 4731
4732 4732 Please note that the server does not implement access control.
4733 4733 This means that, by default, anybody can read from the server and
4734 4734 nobody can write to it by default. Set the ``web.allow-push``
4735 4735 option to ``*`` to allow everybody to push to the server. You
4736 4736 should use a real web server if you need to authenticate users.
4737 4737
4738 4738 By default, the server logs accesses to stdout and errors to
4739 4739 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4740 4740 files.
4741 4741
4742 4742 To have the server choose a free port number to listen on, specify
4743 4743 a port number of 0; in this case, the server will print the port
4744 4744 number it uses.
4745 4745
4746 4746 Returns 0 on success.
4747 4747 """
4748 4748
4749 4749 opts = pycompat.byteskwargs(opts)
4750 4750 if opts["stdio"] and opts["cmdserver"]:
4751 4751 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4752 4752
4753 4753 if opts["stdio"]:
4754 4754 if repo is None:
4755 4755 raise error.RepoError(_("there is no Mercurial repository here"
4756 4756 " (.hg not found)"))
4757 4757 s = wireprotoserver.sshserver(ui, repo)
4758 4758 s.serve_forever()
4759 4759
4760 4760 service = server.createservice(ui, repo, opts)
4761 4761 return server.runservice(opts, initfn=service.init, runfn=service.run)
4762 4762
4763 4763 @command('^status|st',
4764 4764 [('A', 'all', None, _('show status of all files')),
4765 4765 ('m', 'modified', None, _('show only modified files')),
4766 4766 ('a', 'added', None, _('show only added files')),
4767 4767 ('r', 'removed', None, _('show only removed files')),
4768 4768 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4769 4769 ('c', 'clean', None, _('show only files without changes')),
4770 4770 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4771 4771 ('i', 'ignored', None, _('show only ignored files')),
4772 4772 ('n', 'no-status', None, _('hide status prefix')),
4773 4773 ('t', 'terse', '', _('show the terse output (EXPERIMENTAL)')),
4774 4774 ('C', 'copies', None, _('show source of copied files')),
4775 4775 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4776 4776 ('', 'rev', [], _('show difference from revision'), _('REV')),
4777 4777 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4778 4778 ] + walkopts + subrepoopts + formatteropts,
4779 4779 _('[OPTION]... [FILE]...'),
4780 4780 inferrepo=True, cmdtype=readonly)
4781 4781 def status(ui, repo, *pats, **opts):
4782 4782 """show changed files in the working directory
4783 4783
4784 4784 Show status of files in the repository. If names are given, only
4785 4785 files that match are shown. Files that are clean or ignored or
4786 4786 the source of a copy/move operation, are not listed unless
4787 4787 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4788 4788 Unless options described with "show only ..." are given, the
4789 4789 options -mardu are used.
4790 4790
4791 4791 Option -q/--quiet hides untracked (unknown and ignored) files
4792 4792 unless explicitly requested with -u/--unknown or -i/--ignored.
4793 4793
4794 4794 .. note::
4795 4795
4796 4796 :hg:`status` may appear to disagree with diff if permissions have
4797 4797 changed or a merge has occurred. The standard diff format does
4798 4798 not report permission changes and diff only reports changes
4799 4799 relative to one merge parent.
4800 4800
4801 4801 If one revision is given, it is used as the base revision.
4802 4802 If two revisions are given, the differences between them are
4803 4803 shown. The --change option can also be used as a shortcut to list
4804 4804 the changed files of a revision from its first parent.
4805 4805
4806 4806 The codes used to show the status of files are::
4807 4807
4808 4808 M = modified
4809 4809 A = added
4810 4810 R = removed
4811 4811 C = clean
4812 4812 ! = missing (deleted by non-hg command, but still tracked)
4813 4813 ? = not tracked
4814 4814 I = ignored
4815 4815 = origin of the previous file (with --copies)
4816 4816
4817 4817 .. container:: verbose
4818 4818
4819 4819 The -t/--terse option abbreviates the output by showing only the directory
4820 4820 name if all the files in it share the same status. The option takes an
4821 4821 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
4822 4822 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
4823 4823 for 'ignored' and 'c' for clean.
4824 4824
4825 4825 It abbreviates only those statuses which are passed. Note that clean and
4826 4826 ignored files are not displayed with '--terse ic' unless the -c/--clean
4827 4827 and -i/--ignored options are also used.
4828 4828
4829 4829 The -v/--verbose option shows information when the repository is in an
4830 4830 unfinished merge, shelve, rebase state etc. You can have this behavior
4831 4831 turned on by default by enabling the ``commands.status.verbose`` option.
4832 4832
4833 4833 You can skip displaying some of these states by setting
4834 4834 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
4835 4835 'histedit', 'merge', 'rebase', or 'unshelve'.
4836 4836
4837 4837 Examples:
4838 4838
4839 4839 - show changes in the working directory relative to a
4840 4840 changeset::
4841 4841
4842 4842 hg status --rev 9353
4843 4843
4844 4844 - show changes in the working directory relative to the
4845 4845 current directory (see :hg:`help patterns` for more information)::
4846 4846
4847 4847 hg status re:
4848 4848
4849 4849 - show all changes including copies in an existing changeset::
4850 4850
4851 4851 hg status --copies --change 9353
4852 4852
4853 4853 - get a NUL separated list of added files, suitable for xargs::
4854 4854
4855 4855 hg status -an0
4856 4856
4857 4857 - show more information about the repository status, abbreviating
4858 4858 added, removed, modified, deleted, and untracked paths::
4859 4859
4860 4860 hg status -v -t mardu
4861 4861
4862 4862 Returns 0 on success.
4863 4863
4864 4864 """
4865 4865
4866 4866 opts = pycompat.byteskwargs(opts)
4867 4867 revs = opts.get('rev')
4868 4868 change = opts.get('change')
4869 4869 terse = opts.get('terse')
4870 4870
4871 4871 if revs and change:
4872 4872 msg = _('cannot specify --rev and --change at the same time')
4873 4873 raise error.Abort(msg)
4874 4874 elif revs and terse:
4875 4875 msg = _('cannot use --terse with --rev')
4876 4876 raise error.Abort(msg)
4877 4877 elif change:
4878 4878 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
4879 4879 node2 = scmutil.revsingle(repo, change, None).node()
4880 4880 node1 = repo[node2].p1().node()
4881 4881 else:
4882 4882 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
4883 node1, node2 = scmutil.revpair(repo, revs)
4883 node1, node2 = scmutil.revpairnodes(repo, revs)
4884 4884
4885 4885 if pats or ui.configbool('commands', 'status.relative'):
4886 4886 cwd = repo.getcwd()
4887 4887 else:
4888 4888 cwd = ''
4889 4889
4890 4890 if opts.get('print0'):
4891 4891 end = '\0'
4892 4892 else:
4893 4893 end = '\n'
4894 4894 copy = {}
4895 4895 states = 'modified added removed deleted unknown ignored clean'.split()
4896 4896 show = [k for k in states if opts.get(k)]
4897 4897 if opts.get('all'):
4898 4898 show += ui.quiet and (states[:4] + ['clean']) or states
4899 4899
4900 4900 if not show:
4901 4901 if ui.quiet:
4902 4902 show = states[:4]
4903 4903 else:
4904 4904 show = states[:5]
4905 4905
4906 4906 m = scmutil.match(repo[node2], pats, opts)
4907 4907 if terse:
4908 4908 # we need to compute clean and unknown to terse
4909 4909 stat = repo.status(node1, node2, m,
4910 4910 'ignored' in show or 'i' in terse,
4911 4911 True, True, opts.get('subrepos'))
4912 4912
4913 4913 stat = cmdutil.tersedir(stat, terse)
4914 4914 else:
4915 4915 stat = repo.status(node1, node2, m,
4916 4916 'ignored' in show, 'clean' in show,
4917 4917 'unknown' in show, opts.get('subrepos'))
4918 4918
4919 4919 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4920 4920
4921 4921 if (opts.get('all') or opts.get('copies')
4922 4922 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4923 4923 copy = copies.pathcopies(repo[node1], repo[node2], m)
4924 4924
4925 4925 ui.pager('status')
4926 4926 fm = ui.formatter('status', opts)
4927 4927 fmt = '%s' + end
4928 4928 showchar = not opts.get('no_status')
4929 4929
4930 4930 for state, char, files in changestates:
4931 4931 if state in show:
4932 4932 label = 'status.' + state
4933 4933 for f in files:
4934 4934 fm.startitem()
4935 4935 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4936 4936 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4937 4937 if f in copy:
4938 4938 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4939 4939 label='status.copied')
4940 4940
4941 4941 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
4942 4942 and not ui.plain()):
4943 4943 cmdutil.morestatus(repo, fm)
4944 4944 fm.end()
4945 4945
4946 4946 @command('^summary|sum',
4947 4947 [('', 'remote', None, _('check for push and pull'))],
4948 4948 '[--remote]', cmdtype=readonly)
4949 4949 def summary(ui, repo, **opts):
4950 4950 """summarize working directory state
4951 4951
4952 4952 This generates a brief summary of the working directory state,
4953 4953 including parents, branch, commit status, phase and available updates.
4954 4954
4955 4955 With the --remote option, this will check the default paths for
4956 4956 incoming and outgoing changes. This can be time-consuming.
4957 4957
4958 4958 Returns 0 on success.
4959 4959 """
4960 4960
4961 4961 opts = pycompat.byteskwargs(opts)
4962 4962 ui.pager('summary')
4963 4963 ctx = repo[None]
4964 4964 parents = ctx.parents()
4965 4965 pnode = parents[0].node()
4966 4966 marks = []
4967 4967
4968 4968 ms = None
4969 4969 try:
4970 4970 ms = mergemod.mergestate.read(repo)
4971 4971 except error.UnsupportedMergeRecords as e:
4972 4972 s = ' '.join(e.recordtypes)
4973 4973 ui.warn(
4974 4974 _('warning: merge state has unsupported record types: %s\n') % s)
4975 4975 unresolved = []
4976 4976 else:
4977 4977 unresolved = list(ms.unresolved())
4978 4978
4979 4979 for p in parents:
4980 4980 # label with log.changeset (instead of log.parent) since this
4981 4981 # shows a working directory parent *changeset*:
4982 4982 # i18n: column positioning for "hg summary"
4983 4983 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4984 4984 label=logcmdutil.changesetlabels(p))
4985 4985 ui.write(' '.join(p.tags()), label='log.tag')
4986 4986 if p.bookmarks():
4987 4987 marks.extend(p.bookmarks())
4988 4988 if p.rev() == -1:
4989 4989 if not len(repo):
4990 4990 ui.write(_(' (empty repository)'))
4991 4991 else:
4992 4992 ui.write(_(' (no revision checked out)'))
4993 4993 if p.obsolete():
4994 4994 ui.write(_(' (obsolete)'))
4995 4995 if p.isunstable():
4996 4996 instabilities = (ui.label(instability, 'trouble.%s' % instability)
4997 4997 for instability in p.instabilities())
4998 4998 ui.write(' ('
4999 4999 + ', '.join(instabilities)
5000 5000 + ')')
5001 5001 ui.write('\n')
5002 5002 if p.description():
5003 5003 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5004 5004 label='log.summary')
5005 5005
5006 5006 branch = ctx.branch()
5007 5007 bheads = repo.branchheads(branch)
5008 5008 # i18n: column positioning for "hg summary"
5009 5009 m = _('branch: %s\n') % branch
5010 5010 if branch != 'default':
5011 5011 ui.write(m, label='log.branch')
5012 5012 else:
5013 5013 ui.status(m, label='log.branch')
5014 5014
5015 5015 if marks:
5016 5016 active = repo._activebookmark
5017 5017 # i18n: column positioning for "hg summary"
5018 5018 ui.write(_('bookmarks:'), label='log.bookmark')
5019 5019 if active is not None:
5020 5020 if active in marks:
5021 5021 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5022 5022 marks.remove(active)
5023 5023 else:
5024 5024 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5025 5025 for m in marks:
5026 5026 ui.write(' ' + m, label='log.bookmark')
5027 5027 ui.write('\n', label='log.bookmark')
5028 5028
5029 5029 status = repo.status(unknown=True)
5030 5030
5031 5031 c = repo.dirstate.copies()
5032 5032 copied, renamed = [], []
5033 5033 for d, s in c.iteritems():
5034 5034 if s in status.removed:
5035 5035 status.removed.remove(s)
5036 5036 renamed.append(d)
5037 5037 else:
5038 5038 copied.append(d)
5039 5039 if d in status.added:
5040 5040 status.added.remove(d)
5041 5041
5042 5042 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5043 5043
5044 5044 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5045 5045 (ui.label(_('%d added'), 'status.added'), status.added),
5046 5046 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5047 5047 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5048 5048 (ui.label(_('%d copied'), 'status.copied'), copied),
5049 5049 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5050 5050 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5051 5051 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5052 5052 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5053 5053 t = []
5054 5054 for l, s in labels:
5055 5055 if s:
5056 5056 t.append(l % len(s))
5057 5057
5058 5058 t = ', '.join(t)
5059 5059 cleanworkdir = False
5060 5060
5061 5061 if repo.vfs.exists('graftstate'):
5062 5062 t += _(' (graft in progress)')
5063 5063 if repo.vfs.exists('updatestate'):
5064 5064 t += _(' (interrupted update)')
5065 5065 elif len(parents) > 1:
5066 5066 t += _(' (merge)')
5067 5067 elif branch != parents[0].branch():
5068 5068 t += _(' (new branch)')
5069 5069 elif (parents[0].closesbranch() and
5070 5070 pnode in repo.branchheads(branch, closed=True)):
5071 5071 t += _(' (head closed)')
5072 5072 elif not (status.modified or status.added or status.removed or renamed or
5073 5073 copied or subs):
5074 5074 t += _(' (clean)')
5075 5075 cleanworkdir = True
5076 5076 elif pnode not in bheads:
5077 5077 t += _(' (new branch head)')
5078 5078
5079 5079 if parents:
5080 5080 pendingphase = max(p.phase() for p in parents)
5081 5081 else:
5082 5082 pendingphase = phases.public
5083 5083
5084 5084 if pendingphase > phases.newcommitphase(ui):
5085 5085 t += ' (%s)' % phases.phasenames[pendingphase]
5086 5086
5087 5087 if cleanworkdir:
5088 5088 # i18n: column positioning for "hg summary"
5089 5089 ui.status(_('commit: %s\n') % t.strip())
5090 5090 else:
5091 5091 # i18n: column positioning for "hg summary"
5092 5092 ui.write(_('commit: %s\n') % t.strip())
5093 5093
5094 5094 # all ancestors of branch heads - all ancestors of parent = new csets
5095 5095 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5096 5096 bheads))
5097 5097
5098 5098 if new == 0:
5099 5099 # i18n: column positioning for "hg summary"
5100 5100 ui.status(_('update: (current)\n'))
5101 5101 elif pnode not in bheads:
5102 5102 # i18n: column positioning for "hg summary"
5103 5103 ui.write(_('update: %d new changesets (update)\n') % new)
5104 5104 else:
5105 5105 # i18n: column positioning for "hg summary"
5106 5106 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5107 5107 (new, len(bheads)))
5108 5108
5109 5109 t = []
5110 5110 draft = len(repo.revs('draft()'))
5111 5111 if draft:
5112 5112 t.append(_('%d draft') % draft)
5113 5113 secret = len(repo.revs('secret()'))
5114 5114 if secret:
5115 5115 t.append(_('%d secret') % secret)
5116 5116
5117 5117 if draft or secret:
5118 5118 ui.status(_('phases: %s\n') % ', '.join(t))
5119 5119
5120 5120 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5121 5121 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5122 5122 numtrouble = len(repo.revs(trouble + "()"))
5123 5123 # We write all the possibilities to ease translation
5124 5124 troublemsg = {
5125 5125 "orphan": _("orphan: %d changesets"),
5126 5126 "contentdivergent": _("content-divergent: %d changesets"),
5127 5127 "phasedivergent": _("phase-divergent: %d changesets"),
5128 5128 }
5129 5129 if numtrouble > 0:
5130 5130 ui.status(troublemsg[trouble] % numtrouble + "\n")
5131 5131
5132 5132 cmdutil.summaryhooks(ui, repo)
5133 5133
5134 5134 if opts.get('remote'):
5135 5135 needsincoming, needsoutgoing = True, True
5136 5136 else:
5137 5137 needsincoming, needsoutgoing = False, False
5138 5138 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5139 5139 if i:
5140 5140 needsincoming = True
5141 5141 if o:
5142 5142 needsoutgoing = True
5143 5143 if not needsincoming and not needsoutgoing:
5144 5144 return
5145 5145
5146 5146 def getincoming():
5147 5147 source, branches = hg.parseurl(ui.expandpath('default'))
5148 5148 sbranch = branches[0]
5149 5149 try:
5150 5150 other = hg.peer(repo, {}, source)
5151 5151 except error.RepoError:
5152 5152 if opts.get('remote'):
5153 5153 raise
5154 5154 return source, sbranch, None, None, None
5155 5155 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5156 5156 if revs:
5157 5157 revs = [other.lookup(rev) for rev in revs]
5158 5158 ui.debug('comparing with %s\n' % util.hidepassword(source))
5159 5159 repo.ui.pushbuffer()
5160 5160 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5161 5161 repo.ui.popbuffer()
5162 5162 return source, sbranch, other, commoninc, commoninc[1]
5163 5163
5164 5164 if needsincoming:
5165 5165 source, sbranch, sother, commoninc, incoming = getincoming()
5166 5166 else:
5167 5167 source = sbranch = sother = commoninc = incoming = None
5168 5168
5169 5169 def getoutgoing():
5170 5170 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5171 5171 dbranch = branches[0]
5172 5172 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5173 5173 if source != dest:
5174 5174 try:
5175 5175 dother = hg.peer(repo, {}, dest)
5176 5176 except error.RepoError:
5177 5177 if opts.get('remote'):
5178 5178 raise
5179 5179 return dest, dbranch, None, None
5180 5180 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5181 5181 elif sother is None:
5182 5182 # there is no explicit destination peer, but source one is invalid
5183 5183 return dest, dbranch, None, None
5184 5184 else:
5185 5185 dother = sother
5186 5186 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5187 5187 common = None
5188 5188 else:
5189 5189 common = commoninc
5190 5190 if revs:
5191 5191 revs = [repo.lookup(rev) for rev in revs]
5192 5192 repo.ui.pushbuffer()
5193 5193 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5194 5194 commoninc=common)
5195 5195 repo.ui.popbuffer()
5196 5196 return dest, dbranch, dother, outgoing
5197 5197
5198 5198 if needsoutgoing:
5199 5199 dest, dbranch, dother, outgoing = getoutgoing()
5200 5200 else:
5201 5201 dest = dbranch = dother = outgoing = None
5202 5202
5203 5203 if opts.get('remote'):
5204 5204 t = []
5205 5205 if incoming:
5206 5206 t.append(_('1 or more incoming'))
5207 5207 o = outgoing.missing
5208 5208 if o:
5209 5209 t.append(_('%d outgoing') % len(o))
5210 5210 other = dother or sother
5211 5211 if 'bookmarks' in other.listkeys('namespaces'):
5212 5212 counts = bookmarks.summary(repo, other)
5213 5213 if counts[0] > 0:
5214 5214 t.append(_('%d incoming bookmarks') % counts[0])
5215 5215 if counts[1] > 0:
5216 5216 t.append(_('%d outgoing bookmarks') % counts[1])
5217 5217
5218 5218 if t:
5219 5219 # i18n: column positioning for "hg summary"
5220 5220 ui.write(_('remote: %s\n') % (', '.join(t)))
5221 5221 else:
5222 5222 # i18n: column positioning for "hg summary"
5223 5223 ui.status(_('remote: (synced)\n'))
5224 5224
5225 5225 cmdutil.summaryremotehooks(ui, repo, opts,
5226 5226 ((source, sbranch, sother, commoninc),
5227 5227 (dest, dbranch, dother, outgoing)))
5228 5228
5229 5229 @command('tag',
5230 5230 [('f', 'force', None, _('force tag')),
5231 5231 ('l', 'local', None, _('make the tag local')),
5232 5232 ('r', 'rev', '', _('revision to tag'), _('REV')),
5233 5233 ('', 'remove', None, _('remove a tag')),
5234 5234 # -l/--local is already there, commitopts cannot be used
5235 5235 ('e', 'edit', None, _('invoke editor on commit messages')),
5236 5236 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5237 5237 ] + commitopts2,
5238 5238 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5239 5239 def tag(ui, repo, name1, *names, **opts):
5240 5240 """add one or more tags for the current or given revision
5241 5241
5242 5242 Name a particular revision using <name>.
5243 5243
5244 5244 Tags are used to name particular revisions of the repository and are
5245 5245 very useful to compare different revisions, to go back to significant
5246 5246 earlier versions or to mark branch points as releases, etc. Changing
5247 5247 an existing tag is normally disallowed; use -f/--force to override.
5248 5248
5249 5249 If no revision is given, the parent of the working directory is
5250 5250 used.
5251 5251
5252 5252 To facilitate version control, distribution, and merging of tags,
5253 5253 they are stored as a file named ".hgtags" which is managed similarly
5254 5254 to other project files and can be hand-edited if necessary. This
5255 5255 also means that tagging creates a new commit. The file
5256 5256 ".hg/localtags" is used for local tags (not shared among
5257 5257 repositories).
5258 5258
5259 5259 Tag commits are usually made at the head of a branch. If the parent
5260 5260 of the working directory is not a branch head, :hg:`tag` aborts; use
5261 5261 -f/--force to force the tag commit to be based on a non-head
5262 5262 changeset.
5263 5263
5264 5264 See :hg:`help dates` for a list of formats valid for -d/--date.
5265 5265
5266 5266 Since tag names have priority over branch names during revision
5267 5267 lookup, using an existing branch name as a tag name is discouraged.
5268 5268
5269 5269 Returns 0 on success.
5270 5270 """
5271 5271 opts = pycompat.byteskwargs(opts)
5272 5272 wlock = lock = None
5273 5273 try:
5274 5274 wlock = repo.wlock()
5275 5275 lock = repo.lock()
5276 5276 rev_ = "."
5277 5277 names = [t.strip() for t in (name1,) + names]
5278 5278 if len(names) != len(set(names)):
5279 5279 raise error.Abort(_('tag names must be unique'))
5280 5280 for n in names:
5281 5281 scmutil.checknewlabel(repo, n, 'tag')
5282 5282 if not n:
5283 5283 raise error.Abort(_('tag names cannot consist entirely of '
5284 5284 'whitespace'))
5285 5285 if opts.get('rev') and opts.get('remove'):
5286 5286 raise error.Abort(_("--rev and --remove are incompatible"))
5287 5287 if opts.get('rev'):
5288 5288 rev_ = opts['rev']
5289 5289 message = opts.get('message')
5290 5290 if opts.get('remove'):
5291 5291 if opts.get('local'):
5292 5292 expectedtype = 'local'
5293 5293 else:
5294 5294 expectedtype = 'global'
5295 5295
5296 5296 for n in names:
5297 5297 if not repo.tagtype(n):
5298 5298 raise error.Abort(_("tag '%s' does not exist") % n)
5299 5299 if repo.tagtype(n) != expectedtype:
5300 5300 if expectedtype == 'global':
5301 5301 raise error.Abort(_("tag '%s' is not a global tag") % n)
5302 5302 else:
5303 5303 raise error.Abort(_("tag '%s' is not a local tag") % n)
5304 5304 rev_ = 'null'
5305 5305 if not message:
5306 5306 # we don't translate commit messages
5307 5307 message = 'Removed tag %s' % ', '.join(names)
5308 5308 elif not opts.get('force'):
5309 5309 for n in names:
5310 5310 if n in repo.tags():
5311 5311 raise error.Abort(_("tag '%s' already exists "
5312 5312 "(use -f to force)") % n)
5313 5313 if not opts.get('local'):
5314 5314 p1, p2 = repo.dirstate.parents()
5315 5315 if p2 != nullid:
5316 5316 raise error.Abort(_('uncommitted merge'))
5317 5317 bheads = repo.branchheads()
5318 5318 if not opts.get('force') and bheads and p1 not in bheads:
5319 5319 raise error.Abort(_('working directory is not at a branch head '
5320 5320 '(use -f to force)'))
5321 5321 node = scmutil.revsingle(repo, rev_).node()
5322 5322
5323 5323 if not message:
5324 5324 # we don't translate commit messages
5325 5325 message = ('Added tag %s for changeset %s' %
5326 5326 (', '.join(names), short(node)))
5327 5327
5328 5328 date = opts.get('date')
5329 5329 if date:
5330 5330 date = dateutil.parsedate(date)
5331 5331
5332 5332 if opts.get('remove'):
5333 5333 editform = 'tag.remove'
5334 5334 else:
5335 5335 editform = 'tag.add'
5336 5336 editor = cmdutil.getcommiteditor(editform=editform,
5337 5337 **pycompat.strkwargs(opts))
5338 5338
5339 5339 # don't allow tagging the null rev
5340 5340 if (not opts.get('remove') and
5341 5341 scmutil.revsingle(repo, rev_).rev() == nullrev):
5342 5342 raise error.Abort(_("cannot tag null revision"))
5343 5343
5344 5344 tagsmod.tag(repo, names, node, message, opts.get('local'),
5345 5345 opts.get('user'), date, editor=editor)
5346 5346 finally:
5347 5347 release(lock, wlock)
5348 5348
5349 5349 @command('tags', formatteropts, '', cmdtype=readonly)
5350 5350 def tags(ui, repo, **opts):
5351 5351 """list repository tags
5352 5352
5353 5353 This lists both regular and local tags. When the -v/--verbose
5354 5354 switch is used, a third column "local" is printed for local tags.
5355 5355 When the -q/--quiet switch is used, only the tag name is printed.
5356 5356
5357 5357 Returns 0 on success.
5358 5358 """
5359 5359
5360 5360 opts = pycompat.byteskwargs(opts)
5361 5361 ui.pager('tags')
5362 5362 fm = ui.formatter('tags', opts)
5363 5363 hexfunc = fm.hexfunc
5364 5364 tagtype = ""
5365 5365
5366 5366 for t, n in reversed(repo.tagslist()):
5367 5367 hn = hexfunc(n)
5368 5368 label = 'tags.normal'
5369 5369 tagtype = ''
5370 5370 if repo.tagtype(t) == 'local':
5371 5371 label = 'tags.local'
5372 5372 tagtype = 'local'
5373 5373
5374 5374 fm.startitem()
5375 5375 fm.write('tag', '%s', t, label=label)
5376 5376 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5377 5377 fm.condwrite(not ui.quiet, 'rev node', fmt,
5378 5378 repo.changelog.rev(n), hn, label=label)
5379 5379 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5380 5380 tagtype, label=label)
5381 5381 fm.plain('\n')
5382 5382 fm.end()
5383 5383
5384 5384 @command('tip',
5385 5385 [('p', 'patch', None, _('show patch')),
5386 5386 ('g', 'git', None, _('use git extended diff format')),
5387 5387 ] + templateopts,
5388 5388 _('[-p] [-g]'))
5389 5389 def tip(ui, repo, **opts):
5390 5390 """show the tip revision (DEPRECATED)
5391 5391
5392 5392 The tip revision (usually just called the tip) is the changeset
5393 5393 most recently added to the repository (and therefore the most
5394 5394 recently changed head).
5395 5395
5396 5396 If you have just made a commit, that commit will be the tip. If
5397 5397 you have just pulled changes from another repository, the tip of
5398 5398 that repository becomes the current tip. The "tip" tag is special
5399 5399 and cannot be renamed or assigned to a different changeset.
5400 5400
5401 5401 This command is deprecated, please use :hg:`heads` instead.
5402 5402
5403 5403 Returns 0 on success.
5404 5404 """
5405 5405 opts = pycompat.byteskwargs(opts)
5406 5406 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5407 5407 displayer.show(repo['tip'])
5408 5408 displayer.close()
5409 5409
5410 5410 @command('unbundle',
5411 5411 [('u', 'update', None,
5412 5412 _('update to new branch head if changesets were unbundled'))],
5413 5413 _('[-u] FILE...'))
5414 5414 def unbundle(ui, repo, fname1, *fnames, **opts):
5415 5415 """apply one or more bundle files
5416 5416
5417 5417 Apply one or more bundle files generated by :hg:`bundle`.
5418 5418
5419 5419 Returns 0 on success, 1 if an update has unresolved files.
5420 5420 """
5421 5421 fnames = (fname1,) + fnames
5422 5422
5423 5423 with repo.lock():
5424 5424 for fname in fnames:
5425 5425 f = hg.openpath(ui, fname)
5426 5426 gen = exchange.readbundle(ui, f, fname)
5427 5427 if isinstance(gen, streamclone.streamcloneapplier):
5428 5428 raise error.Abort(
5429 5429 _('packed bundles cannot be applied with '
5430 5430 '"hg unbundle"'),
5431 5431 hint=_('use "hg debugapplystreamclonebundle"'))
5432 5432 url = 'bundle:' + fname
5433 5433 try:
5434 5434 txnname = 'unbundle'
5435 5435 if not isinstance(gen, bundle2.unbundle20):
5436 5436 txnname = 'unbundle\n%s' % util.hidepassword(url)
5437 5437 with repo.transaction(txnname) as tr:
5438 5438 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5439 5439 url=url)
5440 5440 except error.BundleUnknownFeatureError as exc:
5441 5441 raise error.Abort(
5442 5442 _('%s: unknown bundle feature, %s') % (fname, exc),
5443 5443 hint=_("see https://mercurial-scm.org/"
5444 5444 "wiki/BundleFeature for more "
5445 5445 "information"))
5446 5446 modheads = bundle2.combinechangegroupresults(op)
5447 5447
5448 5448 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5449 5449
5450 5450 @command('^update|up|checkout|co',
5451 5451 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5452 5452 ('c', 'check', None, _('require clean working directory')),
5453 5453 ('m', 'merge', None, _('merge uncommitted changes')),
5454 5454 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5455 5455 ('r', 'rev', '', _('revision'), _('REV'))
5456 5456 ] + mergetoolopts,
5457 5457 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5458 5458 def update(ui, repo, node=None, **opts):
5459 5459 """update working directory (or switch revisions)
5460 5460
5461 5461 Update the repository's working directory to the specified
5462 5462 changeset. If no changeset is specified, update to the tip of the
5463 5463 current named branch and move the active bookmark (see :hg:`help
5464 5464 bookmarks`).
5465 5465
5466 5466 Update sets the working directory's parent revision to the specified
5467 5467 changeset (see :hg:`help parents`).
5468 5468
5469 5469 If the changeset is not a descendant or ancestor of the working
5470 5470 directory's parent and there are uncommitted changes, the update is
5471 5471 aborted. With the -c/--check option, the working directory is checked
5472 5472 for uncommitted changes; if none are found, the working directory is
5473 5473 updated to the specified changeset.
5474 5474
5475 5475 .. container:: verbose
5476 5476
5477 5477 The -C/--clean, -c/--check, and -m/--merge options control what
5478 5478 happens if the working directory contains uncommitted changes.
5479 5479 At most of one of them can be specified.
5480 5480
5481 5481 1. If no option is specified, and if
5482 5482 the requested changeset is an ancestor or descendant of
5483 5483 the working directory's parent, the uncommitted changes
5484 5484 are merged into the requested changeset and the merged
5485 5485 result is left uncommitted. If the requested changeset is
5486 5486 not an ancestor or descendant (that is, it is on another
5487 5487 branch), the update is aborted and the uncommitted changes
5488 5488 are preserved.
5489 5489
5490 5490 2. With the -m/--merge option, the update is allowed even if the
5491 5491 requested changeset is not an ancestor or descendant of
5492 5492 the working directory's parent.
5493 5493
5494 5494 3. With the -c/--check option, the update is aborted and the
5495 5495 uncommitted changes are preserved.
5496 5496
5497 5497 4. With the -C/--clean option, uncommitted changes are discarded and
5498 5498 the working directory is updated to the requested changeset.
5499 5499
5500 5500 To cancel an uncommitted merge (and lose your changes), use
5501 5501 :hg:`merge --abort`.
5502 5502
5503 5503 Use null as the changeset to remove the working directory (like
5504 5504 :hg:`clone -U`).
5505 5505
5506 5506 If you want to revert just one file to an older revision, use
5507 5507 :hg:`revert [-r REV] NAME`.
5508 5508
5509 5509 See :hg:`help dates` for a list of formats valid for -d/--date.
5510 5510
5511 5511 Returns 0 on success, 1 if there are unresolved files.
5512 5512 """
5513 5513 rev = opts.get(r'rev')
5514 5514 date = opts.get(r'date')
5515 5515 clean = opts.get(r'clean')
5516 5516 check = opts.get(r'check')
5517 5517 merge = opts.get(r'merge')
5518 5518 if rev and node:
5519 5519 raise error.Abort(_("please specify just one revision"))
5520 5520
5521 5521 if ui.configbool('commands', 'update.requiredest'):
5522 5522 if not node and not rev and not date:
5523 5523 raise error.Abort(_('you must specify a destination'),
5524 5524 hint=_('for example: hg update ".::"'))
5525 5525
5526 5526 if rev is None or rev == '':
5527 5527 rev = node
5528 5528
5529 5529 if date and rev is not None:
5530 5530 raise error.Abort(_("you can't specify a revision and a date"))
5531 5531
5532 5532 if len([x for x in (clean, check, merge) if x]) > 1:
5533 5533 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5534 5534 "or -m/--merge"))
5535 5535
5536 5536 updatecheck = None
5537 5537 if check:
5538 5538 updatecheck = 'abort'
5539 5539 elif merge:
5540 5540 updatecheck = 'none'
5541 5541
5542 5542 with repo.wlock():
5543 5543 cmdutil.clearunfinished(repo)
5544 5544
5545 5545 if date:
5546 5546 rev = cmdutil.finddate(ui, repo, date)
5547 5547
5548 5548 # if we defined a bookmark, we have to remember the original name
5549 5549 brev = rev
5550 5550 if rev:
5551 5551 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5552 5552 ctx = scmutil.revsingle(repo, rev, rev)
5553 5553 rev = ctx.rev()
5554 5554 if ctx.hidden():
5555 5555 ctxstr = ctx.hex()[:12]
5556 5556 ui.warn(_("updating to a hidden changeset %s\n") % ctxstr)
5557 5557
5558 5558 if ctx.obsolete():
5559 5559 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
5560 5560 ui.warn("(%s)\n" % obsfatemsg)
5561 5561
5562 5562 repo.ui.setconfig('ui', 'forcemerge', opts.get(r'tool'), 'update')
5563 5563
5564 5564 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5565 5565 updatecheck=updatecheck)
5566 5566
5567 5567 @command('verify', [])
5568 5568 def verify(ui, repo):
5569 5569 """verify the integrity of the repository
5570 5570
5571 5571 Verify the integrity of the current repository.
5572 5572
5573 5573 This will perform an extensive check of the repository's
5574 5574 integrity, validating the hashes and checksums of each entry in
5575 5575 the changelog, manifest, and tracked files, as well as the
5576 5576 integrity of their crosslinks and indices.
5577 5577
5578 5578 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5579 5579 for more information about recovery from corruption of the
5580 5580 repository.
5581 5581
5582 5582 Returns 0 on success, 1 if errors are encountered.
5583 5583 """
5584 5584 return hg.verify(repo)
5585 5585
5586 5586 @command('version', [] + formatteropts, norepo=True, cmdtype=readonly)
5587 5587 def version_(ui, **opts):
5588 5588 """output version and copyright information"""
5589 5589 opts = pycompat.byteskwargs(opts)
5590 5590 if ui.verbose:
5591 5591 ui.pager('version')
5592 5592 fm = ui.formatter("version", opts)
5593 5593 fm.startitem()
5594 5594 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5595 5595 util.version())
5596 5596 license = _(
5597 5597 "(see https://mercurial-scm.org for more information)\n"
5598 5598 "\nCopyright (C) 2005-2018 Matt Mackall and others\n"
5599 5599 "This is free software; see the source for copying conditions. "
5600 5600 "There is NO\nwarranty; "
5601 5601 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5602 5602 )
5603 5603 if not ui.quiet:
5604 5604 fm.plain(license)
5605 5605
5606 5606 if ui.verbose:
5607 5607 fm.plain(_("\nEnabled extensions:\n\n"))
5608 5608 # format names and versions into columns
5609 5609 names = []
5610 5610 vers = []
5611 5611 isinternals = []
5612 5612 for name, module in extensions.extensions():
5613 5613 names.append(name)
5614 5614 vers.append(extensions.moduleversion(module) or None)
5615 5615 isinternals.append(extensions.ismoduleinternal(module))
5616 5616 fn = fm.nested("extensions")
5617 5617 if names:
5618 5618 namefmt = " %%-%ds " % max(len(n) for n in names)
5619 5619 places = [_("external"), _("internal")]
5620 5620 for n, v, p in zip(names, vers, isinternals):
5621 5621 fn.startitem()
5622 5622 fn.condwrite(ui.verbose, "name", namefmt, n)
5623 5623 if ui.verbose:
5624 5624 fn.plain("%s " % places[p])
5625 5625 fn.data(bundled=p)
5626 5626 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5627 5627 if ui.verbose:
5628 5628 fn.plain("\n")
5629 5629 fn.end()
5630 5630 fm.end()
5631 5631
5632 5632 def loadcmdtable(ui, name, cmdtable):
5633 5633 """Load command functions from specified cmdtable
5634 5634 """
5635 5635 overrides = [cmd for cmd in cmdtable if cmd in table]
5636 5636 if overrides:
5637 5637 ui.warn(_("extension '%s' overrides commands: %s\n")
5638 5638 % (name, " ".join(overrides)))
5639 5639 table.update(cmdtable)
@@ -1,664 +1,664 b''
1 1 # fileset.py - file set queries for mercurial
2 2 #
3 3 # Copyright 2010 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 re
11 11
12 12 from .i18n import _
13 13 from . import (
14 14 error,
15 15 match as matchmod,
16 16 merge,
17 17 parser,
18 18 pycompat,
19 19 registrar,
20 20 scmutil,
21 21 util,
22 22 )
23 23 from .utils import (
24 24 stringutil,
25 25 )
26 26
27 27 elements = {
28 28 # token-type: binding-strength, primary, prefix, infix, suffix
29 29 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
30 30 ":": (15, None, None, ("kindpat", 15), None),
31 31 "-": (5, None, ("negate", 19), ("minus", 5), None),
32 32 "not": (10, None, ("not", 10), None, None),
33 33 "!": (10, None, ("not", 10), None, None),
34 34 "and": (5, None, None, ("and", 5), None),
35 35 "&": (5, None, None, ("and", 5), None),
36 36 "or": (4, None, None, ("or", 4), None),
37 37 "|": (4, None, None, ("or", 4), None),
38 38 "+": (4, None, None, ("or", 4), None),
39 39 ",": (2, None, None, ("list", 2), None),
40 40 ")": (0, None, None, None, None),
41 41 "symbol": (0, "symbol", None, None, None),
42 42 "string": (0, "string", None, None, None),
43 43 "end": (0, None, None, None, None),
44 44 }
45 45
46 46 keywords = {'and', 'or', 'not'}
47 47
48 48 globchars = ".*{}[]?/\\_"
49 49
50 50 def tokenize(program):
51 51 pos, l = 0, len(program)
52 52 program = pycompat.bytestr(program)
53 53 while pos < l:
54 54 c = program[pos]
55 55 if c.isspace(): # skip inter-token whitespace
56 56 pass
57 57 elif c in "(),-:|&+!": # handle simple operators
58 58 yield (c, None, pos)
59 59 elif (c in '"\'' or c == 'r' and
60 60 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
61 61 if c == 'r':
62 62 pos += 1
63 63 c = program[pos]
64 64 decode = lambda x: x
65 65 else:
66 66 decode = parser.unescapestr
67 67 pos += 1
68 68 s = pos
69 69 while pos < l: # find closing quote
70 70 d = program[pos]
71 71 if d == '\\': # skip over escaped characters
72 72 pos += 2
73 73 continue
74 74 if d == c:
75 75 yield ('string', decode(program[s:pos]), s)
76 76 break
77 77 pos += 1
78 78 else:
79 79 raise error.ParseError(_("unterminated string"), s)
80 80 elif c.isalnum() or c in globchars or ord(c) > 127:
81 81 # gather up a symbol/keyword
82 82 s = pos
83 83 pos += 1
84 84 while pos < l: # find end of symbol
85 85 d = program[pos]
86 86 if not (d.isalnum() or d in globchars or ord(d) > 127):
87 87 break
88 88 pos += 1
89 89 sym = program[s:pos]
90 90 if sym in keywords: # operator keywords
91 91 yield (sym, None, s)
92 92 else:
93 93 yield ('symbol', sym, s)
94 94 pos -= 1
95 95 else:
96 96 raise error.ParseError(_("syntax error"), pos)
97 97 pos += 1
98 98 yield ('end', None, pos)
99 99
100 100 def parse(expr):
101 101 p = parser.parser(elements)
102 102 tree, pos = p.parse(tokenize(expr))
103 103 if pos != len(expr):
104 104 raise error.ParseError(_("invalid token"), pos)
105 105 return tree
106 106
107 107 def getsymbol(x):
108 108 if x and x[0] == 'symbol':
109 109 return x[1]
110 110 raise error.ParseError(_('not a symbol'))
111 111
112 112 def getstring(x, err):
113 113 if x and (x[0] == 'string' or x[0] == 'symbol'):
114 114 return x[1]
115 115 raise error.ParseError(err)
116 116
117 117 def _getkindpat(x, y, allkinds, err):
118 118 kind = getsymbol(x)
119 119 pat = getstring(y, err)
120 120 if kind not in allkinds:
121 121 raise error.ParseError(_("invalid pattern kind: %s") % kind)
122 122 return '%s:%s' % (kind, pat)
123 123
124 124 def getpattern(x, allkinds, err):
125 125 if x and x[0] == 'kindpat':
126 126 return _getkindpat(x[1], x[2], allkinds, err)
127 127 return getstring(x, err)
128 128
129 129 def getset(mctx, x):
130 130 if not x:
131 131 raise error.ParseError(_("missing argument"))
132 132 return methods[x[0]](mctx, *x[1:])
133 133
134 134 def stringset(mctx, x):
135 135 m = mctx.matcher([x])
136 136 return [f for f in mctx.subset if m(f)]
137 137
138 138 def kindpatset(mctx, x, y):
139 139 return stringset(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
140 140 _("pattern must be a string")))
141 141
142 142 def andset(mctx, x, y):
143 143 return getset(mctx.narrow(getset(mctx, x)), y)
144 144
145 145 def orset(mctx, x, y):
146 146 # needs optimizing
147 147 xl = getset(mctx, x)
148 148 yl = getset(mctx, y)
149 149 return xl + [f for f in yl if f not in xl]
150 150
151 151 def notset(mctx, x):
152 152 s = set(getset(mctx, x))
153 153 return [r for r in mctx.subset if r not in s]
154 154
155 155 def minusset(mctx, x, y):
156 156 xl = getset(mctx, x)
157 157 yl = set(getset(mctx, y))
158 158 return [f for f in xl if f not in yl]
159 159
160 160 def negateset(mctx, x):
161 161 raise error.ParseError(_("can't use negate operator in this context"))
162 162
163 163 def listset(mctx, a, b):
164 164 raise error.ParseError(_("can't use a list in this context"),
165 165 hint=_('see hg help "filesets.x or y"'))
166 166
167 167 # symbols are callable like:
168 168 # fun(mctx, x)
169 169 # with:
170 170 # mctx - current matchctx instance
171 171 # x - argument in tree form
172 172 symbols = {}
173 173
174 174 # filesets using matchctx.status()
175 175 _statuscallers = set()
176 176
177 177 # filesets using matchctx.existing()
178 178 _existingcallers = set()
179 179
180 180 predicate = registrar.filesetpredicate()
181 181
182 182 @predicate('modified()', callstatus=True)
183 183 def modified(mctx, x):
184 184 """File that is modified according to :hg:`status`.
185 185 """
186 186 # i18n: "modified" is a keyword
187 187 getargs(x, 0, 0, _("modified takes no arguments"))
188 188 s = set(mctx.status().modified)
189 189 return [f for f in mctx.subset if f in s]
190 190
191 191 @predicate('added()', callstatus=True)
192 192 def added(mctx, x):
193 193 """File that is added according to :hg:`status`.
194 194 """
195 195 # i18n: "added" is a keyword
196 196 getargs(x, 0, 0, _("added takes no arguments"))
197 197 s = set(mctx.status().added)
198 198 return [f for f in mctx.subset if f in s]
199 199
200 200 @predicate('removed()', callstatus=True)
201 201 def removed(mctx, x):
202 202 """File that is removed according to :hg:`status`.
203 203 """
204 204 # i18n: "removed" is a keyword
205 205 getargs(x, 0, 0, _("removed takes no arguments"))
206 206 s = set(mctx.status().removed)
207 207 return [f for f in mctx.subset if f in s]
208 208
209 209 @predicate('deleted()', callstatus=True)
210 210 def deleted(mctx, x):
211 211 """Alias for ``missing()``.
212 212 """
213 213 # i18n: "deleted" is a keyword
214 214 getargs(x, 0, 0, _("deleted takes no arguments"))
215 215 s = set(mctx.status().deleted)
216 216 return [f for f in mctx.subset if f in s]
217 217
218 218 @predicate('missing()', callstatus=True)
219 219 def missing(mctx, x):
220 220 """File that is missing according to :hg:`status`.
221 221 """
222 222 # i18n: "missing" is a keyword
223 223 getargs(x, 0, 0, _("missing takes no arguments"))
224 224 s = set(mctx.status().deleted)
225 225 return [f for f in mctx.subset if f in s]
226 226
227 227 @predicate('unknown()', callstatus=True)
228 228 def unknown(mctx, x):
229 229 """File that is unknown according to :hg:`status`. These files will only be
230 230 considered if this predicate is used.
231 231 """
232 232 # i18n: "unknown" is a keyword
233 233 getargs(x, 0, 0, _("unknown takes no arguments"))
234 234 s = set(mctx.status().unknown)
235 235 return [f for f in mctx.subset if f in s]
236 236
237 237 @predicate('ignored()', callstatus=True)
238 238 def ignored(mctx, x):
239 239 """File that is ignored according to :hg:`status`. These files will only be
240 240 considered if this predicate is used.
241 241 """
242 242 # i18n: "ignored" is a keyword
243 243 getargs(x, 0, 0, _("ignored takes no arguments"))
244 244 s = set(mctx.status().ignored)
245 245 return [f for f in mctx.subset if f in s]
246 246
247 247 @predicate('clean()', callstatus=True)
248 248 def clean(mctx, x):
249 249 """File that is clean according to :hg:`status`.
250 250 """
251 251 # i18n: "clean" is a keyword
252 252 getargs(x, 0, 0, _("clean takes no arguments"))
253 253 s = set(mctx.status().clean)
254 254 return [f for f in mctx.subset if f in s]
255 255
256 256 def func(mctx, a, b):
257 257 funcname = getsymbol(a)
258 258 if funcname in symbols:
259 259 enabled = mctx._existingenabled
260 260 mctx._existingenabled = funcname in _existingcallers
261 261 try:
262 262 return symbols[funcname](mctx, b)
263 263 finally:
264 264 mctx._existingenabled = enabled
265 265
266 266 keep = lambda fn: getattr(fn, '__doc__', None) is not None
267 267
268 268 syms = [s for (s, fn) in symbols.items() if keep(fn)]
269 269 raise error.UnknownIdentifier(funcname, syms)
270 270
271 271 def getlist(x):
272 272 if not x:
273 273 return []
274 274 if x[0] == 'list':
275 275 return getlist(x[1]) + [x[2]]
276 276 return [x]
277 277
278 278 def getargs(x, min, max, err):
279 279 l = getlist(x)
280 280 if len(l) < min or len(l) > max:
281 281 raise error.ParseError(err)
282 282 return l
283 283
284 284 @predicate('binary()', callexisting=True)
285 285 def binary(mctx, x):
286 286 """File that appears to be binary (contains NUL bytes).
287 287 """
288 288 # i18n: "binary" is a keyword
289 289 getargs(x, 0, 0, _("binary takes no arguments"))
290 290 return [f for f in mctx.existing() if mctx.ctx[f].isbinary()]
291 291
292 292 @predicate('exec()', callexisting=True)
293 293 def exec_(mctx, x):
294 294 """File that is marked as executable.
295 295 """
296 296 # i18n: "exec" is a keyword
297 297 getargs(x, 0, 0, _("exec takes no arguments"))
298 298 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
299 299
300 300 @predicate('symlink()', callexisting=True)
301 301 def symlink(mctx, x):
302 302 """File that is marked as a symlink.
303 303 """
304 304 # i18n: "symlink" is a keyword
305 305 getargs(x, 0, 0, _("symlink takes no arguments"))
306 306 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
307 307
308 308 @predicate('resolved()')
309 309 def resolved(mctx, x):
310 310 """File that is marked resolved according to :hg:`resolve -l`.
311 311 """
312 312 # i18n: "resolved" is a keyword
313 313 getargs(x, 0, 0, _("resolved takes no arguments"))
314 314 if mctx.ctx.rev() is not None:
315 315 return []
316 316 ms = merge.mergestate.read(mctx.ctx.repo())
317 317 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
318 318
319 319 @predicate('unresolved()')
320 320 def unresolved(mctx, x):
321 321 """File that is marked unresolved according to :hg:`resolve -l`.
322 322 """
323 323 # i18n: "unresolved" is a keyword
324 324 getargs(x, 0, 0, _("unresolved takes no arguments"))
325 325 if mctx.ctx.rev() is not None:
326 326 return []
327 327 ms = merge.mergestate.read(mctx.ctx.repo())
328 328 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
329 329
330 330 @predicate('hgignore()')
331 331 def hgignore(mctx, x):
332 332 """File that matches the active .hgignore pattern.
333 333 """
334 334 # i18n: "hgignore" is a keyword
335 335 getargs(x, 0, 0, _("hgignore takes no arguments"))
336 336 ignore = mctx.ctx.repo().dirstate._ignore
337 337 return [f for f in mctx.subset if ignore(f)]
338 338
339 339 @predicate('portable()')
340 340 def portable(mctx, x):
341 341 """File that has a portable name. (This doesn't include filenames with case
342 342 collisions.)
343 343 """
344 344 # i18n: "portable" is a keyword
345 345 getargs(x, 0, 0, _("portable takes no arguments"))
346 346 checkwinfilename = util.checkwinfilename
347 347 return [f for f in mctx.subset if checkwinfilename(f) is None]
348 348
349 349 @predicate('grep(regex)', callexisting=True)
350 350 def grep(mctx, x):
351 351 """File contains the given regular expression.
352 352 """
353 353 try:
354 354 # i18n: "grep" is a keyword
355 355 r = re.compile(getstring(x, _("grep requires a pattern")))
356 356 except re.error as e:
357 357 raise error.ParseError(_('invalid match pattern: %s') % e)
358 358 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
359 359
360 360 def _sizetomax(s):
361 361 try:
362 362 s = s.strip().lower()
363 363 for k, v in util._sizeunits:
364 364 if s.endswith(k):
365 365 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
366 366 n = s[:-len(k)]
367 367 inc = 1.0
368 368 if "." in n:
369 369 inc /= 10 ** len(n.split(".")[1])
370 370 return int((float(n) + inc) * v) - 1
371 371 # no extension, this is a precise value
372 372 return int(s)
373 373 except ValueError:
374 374 raise error.ParseError(_("couldn't parse size: %s") % s)
375 375
376 376 def sizematcher(x):
377 377 """Return a function(size) -> bool from the ``size()`` expression"""
378 378
379 379 # i18n: "size" is a keyword
380 380 expr = getstring(x, _("size requires an expression")).strip()
381 381 if '-' in expr: # do we have a range?
382 382 a, b = expr.split('-', 1)
383 383 a = util.sizetoint(a)
384 384 b = util.sizetoint(b)
385 385 return lambda x: x >= a and x <= b
386 386 elif expr.startswith("<="):
387 387 a = util.sizetoint(expr[2:])
388 388 return lambda x: x <= a
389 389 elif expr.startswith("<"):
390 390 a = util.sizetoint(expr[1:])
391 391 return lambda x: x < a
392 392 elif expr.startswith(">="):
393 393 a = util.sizetoint(expr[2:])
394 394 return lambda x: x >= a
395 395 elif expr.startswith(">"):
396 396 a = util.sizetoint(expr[1:])
397 397 return lambda x: x > a
398 398 else:
399 399 a = util.sizetoint(expr)
400 400 b = _sizetomax(expr)
401 401 return lambda x: x >= a and x <= b
402 402
403 403 @predicate('size(expression)', callexisting=True)
404 404 def size(mctx, x):
405 405 """File size matches the given expression. Examples:
406 406
407 407 - size('1k') - files from 1024 to 2047 bytes
408 408 - size('< 20k') - files less than 20480 bytes
409 409 - size('>= .5MB') - files at least 524288 bytes
410 410 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
411 411 """
412 412 m = sizematcher(x)
413 413 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
414 414
415 415 @predicate('encoding(name)', callexisting=True)
416 416 def encoding(mctx, x):
417 417 """File can be successfully decoded with the given character
418 418 encoding. May not be useful for encodings other than ASCII and
419 419 UTF-8.
420 420 """
421 421
422 422 # i18n: "encoding" is a keyword
423 423 enc = getstring(x, _("encoding requires an encoding name"))
424 424
425 425 s = []
426 426 for f in mctx.existing():
427 427 d = mctx.ctx[f].data()
428 428 try:
429 429 d.decode(enc)
430 430 except LookupError:
431 431 raise error.Abort(_("unknown encoding '%s'") % enc)
432 432 except UnicodeDecodeError:
433 433 continue
434 434 s.append(f)
435 435
436 436 return s
437 437
438 438 @predicate('eol(style)', callexisting=True)
439 439 def eol(mctx, x):
440 440 """File contains newlines of the given style (dos, unix, mac). Binary
441 441 files are excluded, files with mixed line endings match multiple
442 442 styles.
443 443 """
444 444
445 445 # i18n: "eol" is a keyword
446 446 enc = getstring(x, _("eol requires a style name"))
447 447
448 448 s = []
449 449 for f in mctx.existing():
450 450 d = mctx.ctx[f].data()
451 451 if stringutil.binary(d):
452 452 continue
453 453 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
454 454 s.append(f)
455 455 elif enc == 'unix' and re.search('(?<!\r)\n', d):
456 456 s.append(f)
457 457 elif enc == 'mac' and re.search('\r(?!\n)', d):
458 458 s.append(f)
459 459 return s
460 460
461 461 @predicate('copied()')
462 462 def copied(mctx, x):
463 463 """File that is recorded as being copied.
464 464 """
465 465 # i18n: "copied" is a keyword
466 466 getargs(x, 0, 0, _("copied takes no arguments"))
467 467 s = []
468 468 for f in mctx.subset:
469 469 if f in mctx.ctx:
470 470 p = mctx.ctx[f].parents()
471 471 if p and p[0].path() != f:
472 472 s.append(f)
473 473 return s
474 474
475 475 @predicate('revs(revs, pattern)')
476 476 def revs(mctx, x):
477 477 """Evaluate set in the specified revisions. If the revset match multiple
478 478 revs, this will return file matching pattern in any of the revision.
479 479 """
480 480 # i18n: "revs" is a keyword
481 481 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
482 482 # i18n: "revs" is a keyword
483 483 revspec = getstring(r, _("first argument to revs must be a revision"))
484 484 repo = mctx.ctx.repo()
485 485 revs = scmutil.revrange(repo, [revspec])
486 486
487 487 found = set()
488 488 result = []
489 489 for r in revs:
490 490 ctx = repo[r]
491 491 for f in getset(mctx.switch(ctx, _buildstatus(ctx, x)), x):
492 492 if f not in found:
493 493 found.add(f)
494 494 result.append(f)
495 495 return result
496 496
497 497 @predicate('status(base, rev, pattern)')
498 498 def status(mctx, x):
499 499 """Evaluate predicate using status change between ``base`` and
500 500 ``rev``. Examples:
501 501
502 502 - ``status(3, 7, added())`` - matches files added from "3" to "7"
503 503 """
504 504 repo = mctx.ctx.repo()
505 505 # i18n: "status" is a keyword
506 506 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
507 507 # i18n: "status" is a keyword
508 508 baseerr = _("first argument to status must be a revision")
509 509 baserevspec = getstring(b, baseerr)
510 510 if not baserevspec:
511 511 raise error.ParseError(baseerr)
512 512 reverr = _("second argument to status must be a revision")
513 513 revspec = getstring(r, reverr)
514 514 if not revspec:
515 515 raise error.ParseError(reverr)
516 basenode, node = scmutil.revpair(repo, [baserevspec, revspec])
516 basenode, node = scmutil.revpairnodes(repo, [baserevspec, revspec])
517 517 basectx = repo[basenode]
518 518 ctx = repo[node]
519 519 return getset(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
520 520
521 521 @predicate('subrepo([pattern])')
522 522 def subrepo(mctx, x):
523 523 """Subrepositories whose paths match the given pattern.
524 524 """
525 525 # i18n: "subrepo" is a keyword
526 526 getargs(x, 0, 1, _("subrepo takes at most one argument"))
527 527 ctx = mctx.ctx
528 528 sstate = sorted(ctx.substate)
529 529 if x:
530 530 pat = getpattern(x, matchmod.allpatternkinds,
531 531 # i18n: "subrepo" is a keyword
532 532 _("subrepo requires a pattern or no arguments"))
533 533 fast = not matchmod.patkind(pat)
534 534 if fast:
535 535 def m(s):
536 536 return (s == pat)
537 537 else:
538 538 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
539 539 return [sub for sub in sstate if m(sub)]
540 540 else:
541 541 return [sub for sub in sstate]
542 542
543 543 methods = {
544 544 'string': stringset,
545 545 'symbol': stringset,
546 546 'kindpat': kindpatset,
547 547 'and': andset,
548 548 'or': orset,
549 549 'minus': minusset,
550 550 'negate': negateset,
551 551 'list': listset,
552 552 'group': getset,
553 553 'not': notset,
554 554 'func': func,
555 555 }
556 556
557 557 class matchctx(object):
558 558 def __init__(self, ctx, subset, status=None):
559 559 self.ctx = ctx
560 560 self.subset = subset
561 561 self._status = status
562 562 self._existingenabled = False
563 563 def status(self):
564 564 return self._status
565 565 def matcher(self, patterns):
566 566 return self.ctx.match(patterns)
567 567 def filter(self, files):
568 568 return [f for f in files if f in self.subset]
569 569 def existing(self):
570 570 assert self._existingenabled, 'unexpected existing() invocation'
571 571 if self._status is not None:
572 572 removed = set(self._status[3])
573 573 unknown = set(self._status[4] + self._status[5])
574 574 else:
575 575 removed = set()
576 576 unknown = set()
577 577 return (f for f in self.subset
578 578 if (f in self.ctx and f not in removed) or f in unknown)
579 579 def narrow(self, files):
580 580 return matchctx(self.ctx, self.filter(files), self._status)
581 581 def switch(self, ctx, status=None):
582 582 subset = self.filter(_buildsubset(ctx, status))
583 583 return matchctx(ctx, subset, status)
584 584
585 585 class fullmatchctx(matchctx):
586 586 """A match context where any files in any revisions should be valid"""
587 587
588 588 def __init__(self, ctx, status=None):
589 589 subset = _buildsubset(ctx, status)
590 590 super(fullmatchctx, self).__init__(ctx, subset, status)
591 591 def switch(self, ctx, status=None):
592 592 return fullmatchctx(ctx, status)
593 593
594 594 # filesets using matchctx.switch()
595 595 _switchcallers = [
596 596 'revs',
597 597 'status',
598 598 ]
599 599
600 600 def _intree(funcs, tree):
601 601 if isinstance(tree, tuple):
602 602 if tree[0] == 'func' and tree[1][0] == 'symbol':
603 603 if tree[1][1] in funcs:
604 604 return True
605 605 if tree[1][1] in _switchcallers:
606 606 # arguments won't be evaluated in the current context
607 607 return False
608 608 for s in tree[1:]:
609 609 if _intree(funcs, s):
610 610 return True
611 611 return False
612 612
613 613 def _buildsubset(ctx, status):
614 614 if status:
615 615 subset = []
616 616 for c in status:
617 617 subset.extend(c)
618 618 return subset
619 619 else:
620 620 return list(ctx.walk(ctx.match([])))
621 621
622 622 def getfileset(ctx, expr):
623 623 tree = parse(expr)
624 624 return getset(fullmatchctx(ctx, _buildstatus(ctx, tree)), tree)
625 625
626 626 def _buildstatus(ctx, tree, basectx=None):
627 627 # do we need status info?
628 628
629 629 # temporaty boolean to simplify the next conditional
630 630 purewdir = ctx.rev() is None and basectx is None
631 631
632 632 if (_intree(_statuscallers, tree) or
633 633 # Using matchctx.existing() on a workingctx requires us to check
634 634 # for deleted files.
635 635 (purewdir and _intree(_existingcallers, tree))):
636 636 unknown = _intree(['unknown'], tree)
637 637 ignored = _intree(['ignored'], tree)
638 638
639 639 r = ctx.repo()
640 640 if basectx is None:
641 641 basectx = ctx.p1()
642 642 return r.status(basectx, ctx,
643 643 unknown=unknown, ignored=ignored, clean=True)
644 644 else:
645 645 return None
646 646
647 647 def prettyformat(tree):
648 648 return parser.prettyformat(tree, ('string', 'symbol'))
649 649
650 650 def loadpredicate(ui, extname, registrarobj):
651 651 """Load fileset predicates from specified registrarobj
652 652 """
653 653 for name, func in registrarobj._table.iteritems():
654 654 symbols[name] = func
655 655 if func._callstatus:
656 656 _statuscallers.add(name)
657 657 if func._callexisting:
658 658 _existingcallers.add(name)
659 659
660 660 # load built-in predicates explicitly to setup _statuscallers/_existingcallers
661 661 loadpredicate(None, None, predicate)
662 662
663 663 # tell hggettext to extract docstrings from these functions:
664 664 i18nfunctions = symbols.values()
@@ -1,1431 +1,1434 b''
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright 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 errno
11 11 import glob
12 12 import hashlib
13 13 import os
14 14 import re
15 15 import socket
16 16 import subprocess
17 17 import weakref
18 18
19 19 from .i18n import _
20 20 from .node import (
21 21 hex,
22 22 nullid,
23 23 short,
24 24 wdirid,
25 25 wdirrev,
26 26 )
27 27
28 28 from . import (
29 29 encoding,
30 30 error,
31 31 match as matchmod,
32 32 obsolete,
33 33 obsutil,
34 34 pathutil,
35 35 phases,
36 36 pycompat,
37 37 revsetlang,
38 38 similar,
39 39 url,
40 40 util,
41 41 vfs,
42 42 )
43 43
44 44 from .utils import (
45 45 procutil,
46 46 stringutil,
47 47 )
48 48
49 49 if pycompat.iswindows:
50 50 from . import scmwindows as scmplatform
51 51 else:
52 52 from . import scmposix as scmplatform
53 53
54 54 termsize = scmplatform.termsize
55 55
56 56 class status(tuple):
57 57 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
58 58 and 'ignored' properties are only relevant to the working copy.
59 59 '''
60 60
61 61 __slots__ = ()
62 62
63 63 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
64 64 clean):
65 65 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
66 66 ignored, clean))
67 67
68 68 @property
69 69 def modified(self):
70 70 '''files that have been modified'''
71 71 return self[0]
72 72
73 73 @property
74 74 def added(self):
75 75 '''files that have been added'''
76 76 return self[1]
77 77
78 78 @property
79 79 def removed(self):
80 80 '''files that have been removed'''
81 81 return self[2]
82 82
83 83 @property
84 84 def deleted(self):
85 85 '''files that are in the dirstate, but have been deleted from the
86 86 working copy (aka "missing")
87 87 '''
88 88 return self[3]
89 89
90 90 @property
91 91 def unknown(self):
92 92 '''files not in the dirstate that are not ignored'''
93 93 return self[4]
94 94
95 95 @property
96 96 def ignored(self):
97 97 '''files not in the dirstate that are ignored (by _dirignore())'''
98 98 return self[5]
99 99
100 100 @property
101 101 def clean(self):
102 102 '''files that have not been modified'''
103 103 return self[6]
104 104
105 105 def __repr__(self, *args, **kwargs):
106 106 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
107 107 'unknown=%r, ignored=%r, clean=%r>') % self)
108 108
109 109 def itersubrepos(ctx1, ctx2):
110 110 """find subrepos in ctx1 or ctx2"""
111 111 # Create a (subpath, ctx) mapping where we prefer subpaths from
112 112 # ctx1. The subpaths from ctx2 are important when the .hgsub file
113 113 # has been modified (in ctx2) but not yet committed (in ctx1).
114 114 subpaths = dict.fromkeys(ctx2.substate, ctx2)
115 115 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
116 116
117 117 missing = set()
118 118
119 119 for subpath in ctx2.substate:
120 120 if subpath not in ctx1.substate:
121 121 del subpaths[subpath]
122 122 missing.add(subpath)
123 123
124 124 for subpath, ctx in sorted(subpaths.iteritems()):
125 125 yield subpath, ctx.sub(subpath)
126 126
127 127 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
128 128 # status and diff will have an accurate result when it does
129 129 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
130 130 # against itself.
131 131 for subpath in missing:
132 132 yield subpath, ctx2.nullsub(subpath, ctx1)
133 133
134 134 def nochangesfound(ui, repo, excluded=None):
135 135 '''Report no changes for push/pull, excluded is None or a list of
136 136 nodes excluded from the push/pull.
137 137 '''
138 138 secretlist = []
139 139 if excluded:
140 140 for n in excluded:
141 141 ctx = repo[n]
142 142 if ctx.phase() >= phases.secret and not ctx.extinct():
143 143 secretlist.append(n)
144 144
145 145 if secretlist:
146 146 ui.status(_("no changes found (ignored %d secret changesets)\n")
147 147 % len(secretlist))
148 148 else:
149 149 ui.status(_("no changes found\n"))
150 150
151 151 def callcatch(ui, func):
152 152 """call func() with global exception handling
153 153
154 154 return func() if no exception happens. otherwise do some error handling
155 155 and return an exit code accordingly. does not handle all exceptions.
156 156 """
157 157 try:
158 158 try:
159 159 return func()
160 160 except: # re-raises
161 161 ui.traceback()
162 162 raise
163 163 # Global exception handling, alphabetically
164 164 # Mercurial-specific first, followed by built-in and library exceptions
165 165 except error.LockHeld as inst:
166 166 if inst.errno == errno.ETIMEDOUT:
167 167 reason = _('timed out waiting for lock held by %r') % inst.locker
168 168 else:
169 169 reason = _('lock held by %r') % inst.locker
170 170 ui.warn(_("abort: %s: %s\n")
171 171 % (inst.desc or stringutil.forcebytestr(inst.filename), reason))
172 172 if not inst.locker:
173 173 ui.warn(_("(lock might be very busy)\n"))
174 174 except error.LockUnavailable as inst:
175 175 ui.warn(_("abort: could not lock %s: %s\n") %
176 176 (inst.desc or stringutil.forcebytestr(inst.filename),
177 177 encoding.strtolocal(inst.strerror)))
178 178 except error.OutOfBandError as inst:
179 179 if inst.args:
180 180 msg = _("abort: remote error:\n")
181 181 else:
182 182 msg = _("abort: remote error\n")
183 183 ui.warn(msg)
184 184 if inst.args:
185 185 ui.warn(''.join(inst.args))
186 186 if inst.hint:
187 187 ui.warn('(%s)\n' % inst.hint)
188 188 except error.RepoError as inst:
189 189 ui.warn(_("abort: %s!\n") % inst)
190 190 if inst.hint:
191 191 ui.warn(_("(%s)\n") % inst.hint)
192 192 except error.ResponseError as inst:
193 193 ui.warn(_("abort: %s") % inst.args[0])
194 194 msg = inst.args[1]
195 195 if isinstance(msg, type(u'')):
196 196 msg = pycompat.sysbytes(msg)
197 197 if not isinstance(msg, bytes):
198 198 ui.warn(" %r\n" % (msg,))
199 199 elif not msg:
200 200 ui.warn(_(" empty string\n"))
201 201 else:
202 202 ui.warn("\n%r\n" % stringutil.ellipsis(msg))
203 203 except error.CensoredNodeError as inst:
204 204 ui.warn(_("abort: file censored %s!\n") % inst)
205 205 except error.RevlogError as inst:
206 206 ui.warn(_("abort: %s!\n") % inst)
207 207 except error.InterventionRequired as inst:
208 208 ui.warn("%s\n" % inst)
209 209 if inst.hint:
210 210 ui.warn(_("(%s)\n") % inst.hint)
211 211 return 1
212 212 except error.WdirUnsupported:
213 213 ui.warn(_("abort: working directory revision cannot be specified\n"))
214 214 except error.Abort as inst:
215 215 ui.warn(_("abort: %s\n") % inst)
216 216 if inst.hint:
217 217 ui.warn(_("(%s)\n") % inst.hint)
218 218 except ImportError as inst:
219 219 ui.warn(_("abort: %s!\n") % stringutil.forcebytestr(inst))
220 220 m = stringutil.forcebytestr(inst).split()[-1]
221 221 if m in "mpatch bdiff".split():
222 222 ui.warn(_("(did you forget to compile extensions?)\n"))
223 223 elif m in "zlib".split():
224 224 ui.warn(_("(is your Python install correct?)\n"))
225 225 except IOError as inst:
226 226 if util.safehasattr(inst, "code"):
227 227 ui.warn(_("abort: %s\n") % stringutil.forcebytestr(inst))
228 228 elif util.safehasattr(inst, "reason"):
229 229 try: # usually it is in the form (errno, strerror)
230 230 reason = inst.reason.args[1]
231 231 except (AttributeError, IndexError):
232 232 # it might be anything, for example a string
233 233 reason = inst.reason
234 234 if isinstance(reason, unicode):
235 235 # SSLError of Python 2.7.9 contains a unicode
236 236 reason = encoding.unitolocal(reason)
237 237 ui.warn(_("abort: error: %s\n") % reason)
238 238 elif (util.safehasattr(inst, "args")
239 239 and inst.args and inst.args[0] == errno.EPIPE):
240 240 pass
241 241 elif getattr(inst, "strerror", None):
242 242 if getattr(inst, "filename", None):
243 243 ui.warn(_("abort: %s: %s\n") % (
244 244 encoding.strtolocal(inst.strerror),
245 245 stringutil.forcebytestr(inst.filename)))
246 246 else:
247 247 ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
248 248 else:
249 249 raise
250 250 except OSError as inst:
251 251 if getattr(inst, "filename", None) is not None:
252 252 ui.warn(_("abort: %s: '%s'\n") % (
253 253 encoding.strtolocal(inst.strerror),
254 254 stringutil.forcebytestr(inst.filename)))
255 255 else:
256 256 ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
257 257 except MemoryError:
258 258 ui.warn(_("abort: out of memory\n"))
259 259 except SystemExit as inst:
260 260 # Commands shouldn't sys.exit directly, but give a return code.
261 261 # Just in case catch this and and pass exit code to caller.
262 262 return inst.code
263 263 except socket.error as inst:
264 264 ui.warn(_("abort: %s\n") % stringutil.forcebytestr(inst.args[-1]))
265 265
266 266 return -1
267 267
268 268 def checknewlabel(repo, lbl, kind):
269 269 # Do not use the "kind" parameter in ui output.
270 270 # It makes strings difficult to translate.
271 271 if lbl in ['tip', '.', 'null']:
272 272 raise error.Abort(_("the name '%s' is reserved") % lbl)
273 273 for c in (':', '\0', '\n', '\r'):
274 274 if c in lbl:
275 275 raise error.Abort(
276 276 _("%r cannot be used in a name") % pycompat.bytestr(c))
277 277 try:
278 278 int(lbl)
279 279 raise error.Abort(_("cannot use an integer as a name"))
280 280 except ValueError:
281 281 pass
282 282 if lbl.strip() != lbl:
283 283 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
284 284
285 285 def checkfilename(f):
286 286 '''Check that the filename f is an acceptable filename for a tracked file'''
287 287 if '\r' in f or '\n' in f:
288 288 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
289 289
290 290 def checkportable(ui, f):
291 291 '''Check if filename f is portable and warn or abort depending on config'''
292 292 checkfilename(f)
293 293 abort, warn = checkportabilityalert(ui)
294 294 if abort or warn:
295 295 msg = util.checkwinfilename(f)
296 296 if msg:
297 297 msg = "%s: %s" % (msg, procutil.shellquote(f))
298 298 if abort:
299 299 raise error.Abort(msg)
300 300 ui.warn(_("warning: %s\n") % msg)
301 301
302 302 def checkportabilityalert(ui):
303 303 '''check if the user's config requests nothing, a warning, or abort for
304 304 non-portable filenames'''
305 305 val = ui.config('ui', 'portablefilenames')
306 306 lval = val.lower()
307 307 bval = stringutil.parsebool(val)
308 308 abort = pycompat.iswindows or lval == 'abort'
309 309 warn = bval or lval == 'warn'
310 310 if bval is None and not (warn or abort or lval == 'ignore'):
311 311 raise error.ConfigError(
312 312 _("ui.portablefilenames value is invalid ('%s')") % val)
313 313 return abort, warn
314 314
315 315 class casecollisionauditor(object):
316 316 def __init__(self, ui, abort, dirstate):
317 317 self._ui = ui
318 318 self._abort = abort
319 319 allfiles = '\0'.join(dirstate._map)
320 320 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
321 321 self._dirstate = dirstate
322 322 # The purpose of _newfiles is so that we don't complain about
323 323 # case collisions if someone were to call this object with the
324 324 # same filename twice.
325 325 self._newfiles = set()
326 326
327 327 def __call__(self, f):
328 328 if f in self._newfiles:
329 329 return
330 330 fl = encoding.lower(f)
331 331 if fl in self._loweredfiles and f not in self._dirstate:
332 332 msg = _('possible case-folding collision for %s') % f
333 333 if self._abort:
334 334 raise error.Abort(msg)
335 335 self._ui.warn(_("warning: %s\n") % msg)
336 336 self._loweredfiles.add(fl)
337 337 self._newfiles.add(f)
338 338
339 339 def filteredhash(repo, maxrev):
340 340 """build hash of filtered revisions in the current repoview.
341 341
342 342 Multiple caches perform up-to-date validation by checking that the
343 343 tiprev and tipnode stored in the cache file match the current repository.
344 344 However, this is not sufficient for validating repoviews because the set
345 345 of revisions in the view may change without the repository tiprev and
346 346 tipnode changing.
347 347
348 348 This function hashes all the revs filtered from the view and returns
349 349 that SHA-1 digest.
350 350 """
351 351 cl = repo.changelog
352 352 if not cl.filteredrevs:
353 353 return None
354 354 key = None
355 355 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
356 356 if revs:
357 357 s = hashlib.sha1()
358 358 for rev in revs:
359 359 s.update('%d;' % rev)
360 360 key = s.digest()
361 361 return key
362 362
363 363 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
364 364 '''yield every hg repository under path, always recursively.
365 365 The recurse flag will only control recursion into repo working dirs'''
366 366 def errhandler(err):
367 367 if err.filename == path:
368 368 raise err
369 369 samestat = getattr(os.path, 'samestat', None)
370 370 if followsym and samestat is not None:
371 371 def adddir(dirlst, dirname):
372 372 dirstat = os.stat(dirname)
373 373 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
374 374 if not match:
375 375 dirlst.append(dirstat)
376 376 return not match
377 377 else:
378 378 followsym = False
379 379
380 380 if (seen_dirs is None) and followsym:
381 381 seen_dirs = []
382 382 adddir(seen_dirs, path)
383 383 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
384 384 dirs.sort()
385 385 if '.hg' in dirs:
386 386 yield root # found a repository
387 387 qroot = os.path.join(root, '.hg', 'patches')
388 388 if os.path.isdir(os.path.join(qroot, '.hg')):
389 389 yield qroot # we have a patch queue repo here
390 390 if recurse:
391 391 # avoid recursing inside the .hg directory
392 392 dirs.remove('.hg')
393 393 else:
394 394 dirs[:] = [] # don't descend further
395 395 elif followsym:
396 396 newdirs = []
397 397 for d in dirs:
398 398 fname = os.path.join(root, d)
399 399 if adddir(seen_dirs, fname):
400 400 if os.path.islink(fname):
401 401 for hgname in walkrepos(fname, True, seen_dirs):
402 402 yield hgname
403 403 else:
404 404 newdirs.append(d)
405 405 dirs[:] = newdirs
406 406
407 407 def binnode(ctx):
408 408 """Return binary node id for a given basectx"""
409 409 node = ctx.node()
410 410 if node is None:
411 411 return wdirid
412 412 return node
413 413
414 414 def intrev(ctx):
415 415 """Return integer for a given basectx that can be used in comparison or
416 416 arithmetic operation"""
417 417 rev = ctx.rev()
418 418 if rev is None:
419 419 return wdirrev
420 420 return rev
421 421
422 422 def formatchangeid(ctx):
423 423 """Format changectx as '{rev}:{node|formatnode}', which is the default
424 424 template provided by logcmdutil.changesettemplater"""
425 425 repo = ctx.repo()
426 426 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
427 427
428 428 def formatrevnode(ui, rev, node):
429 429 """Format given revision and node depending on the current verbosity"""
430 430 if ui.debugflag:
431 431 hexfunc = hex
432 432 else:
433 433 hexfunc = short
434 434 return '%d:%s' % (rev, hexfunc(node))
435 435
436 436 def revsingle(repo, revspec, default='.', localalias=None):
437 437 if not revspec and revspec != 0:
438 438 return repo[default]
439 439
440 440 l = revrange(repo, [revspec], localalias=localalias)
441 441 if not l:
442 442 raise error.Abort(_('empty revision set'))
443 443 return repo[l.last()]
444 444
445 445 def _pairspec(revspec):
446 446 tree = revsetlang.parse(revspec)
447 447 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
448 448
449 def revpairnodes(repo, revs):
450 return revpair(repo, revs)
451
449 452 def revpair(repo, revs):
450 453 if not revs:
451 454 return repo.dirstate.p1(), None
452 455
453 456 l = revrange(repo, revs)
454 457
455 458 if not l:
456 459 first = second = None
457 460 elif l.isascending():
458 461 first = l.min()
459 462 second = l.max()
460 463 elif l.isdescending():
461 464 first = l.max()
462 465 second = l.min()
463 466 else:
464 467 first = l.first()
465 468 second = l.last()
466 469
467 470 if first is None:
468 471 raise error.Abort(_('empty revision range'))
469 472 if (first == second and len(revs) >= 2
470 473 and not all(revrange(repo, [r]) for r in revs)):
471 474 raise error.Abort(_('empty revision on one side of range'))
472 475
473 476 # if top-level is range expression, the result must always be a pair
474 477 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
475 478 return repo.lookup(first), None
476 479
477 480 return repo.lookup(first), repo.lookup(second)
478 481
479 482 def revrange(repo, specs, localalias=None):
480 483 """Execute 1 to many revsets and return the union.
481 484
482 485 This is the preferred mechanism for executing revsets using user-specified
483 486 config options, such as revset aliases.
484 487
485 488 The revsets specified by ``specs`` will be executed via a chained ``OR``
486 489 expression. If ``specs`` is empty, an empty result is returned.
487 490
488 491 ``specs`` can contain integers, in which case they are assumed to be
489 492 revision numbers.
490 493
491 494 It is assumed the revsets are already formatted. If you have arguments
492 495 that need to be expanded in the revset, call ``revsetlang.formatspec()``
493 496 and pass the result as an element of ``specs``.
494 497
495 498 Specifying a single revset is allowed.
496 499
497 500 Returns a ``revset.abstractsmartset`` which is a list-like interface over
498 501 integer revisions.
499 502 """
500 503 allspecs = []
501 504 for spec in specs:
502 505 if isinstance(spec, int):
503 506 spec = revsetlang.formatspec('rev(%d)', spec)
504 507 allspecs.append(spec)
505 508 return repo.anyrevs(allspecs, user=True, localalias=localalias)
506 509
507 510 def meaningfulparents(repo, ctx):
508 511 """Return list of meaningful (or all if debug) parentrevs for rev.
509 512
510 513 For merges (two non-nullrev revisions) both parents are meaningful.
511 514 Otherwise the first parent revision is considered meaningful if it
512 515 is not the preceding revision.
513 516 """
514 517 parents = ctx.parents()
515 518 if len(parents) > 1:
516 519 return parents
517 520 if repo.ui.debugflag:
518 521 return [parents[0], repo['null']]
519 522 if parents[0].rev() >= intrev(ctx) - 1:
520 523 return []
521 524 return parents
522 525
523 526 def expandpats(pats):
524 527 '''Expand bare globs when running on windows.
525 528 On posix we assume it already has already been done by sh.'''
526 529 if not util.expandglobs:
527 530 return list(pats)
528 531 ret = []
529 532 for kindpat in pats:
530 533 kind, pat = matchmod._patsplit(kindpat, None)
531 534 if kind is None:
532 535 try:
533 536 globbed = glob.glob(pat)
534 537 except re.error:
535 538 globbed = [pat]
536 539 if globbed:
537 540 ret.extend(globbed)
538 541 continue
539 542 ret.append(kindpat)
540 543 return ret
541 544
542 545 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
543 546 badfn=None):
544 547 '''Return a matcher and the patterns that were used.
545 548 The matcher will warn about bad matches, unless an alternate badfn callback
546 549 is provided.'''
547 550 if pats == ("",):
548 551 pats = []
549 552 if opts is None:
550 553 opts = {}
551 554 if not globbed and default == 'relpath':
552 555 pats = expandpats(pats or [])
553 556
554 557 def bad(f, msg):
555 558 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
556 559
557 560 if badfn is None:
558 561 badfn = bad
559 562
560 563 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
561 564 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
562 565
563 566 if m.always():
564 567 pats = []
565 568 return m, pats
566 569
567 570 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
568 571 badfn=None):
569 572 '''Return a matcher that will warn about bad matches.'''
570 573 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
571 574
572 575 def matchall(repo):
573 576 '''Return a matcher that will efficiently match everything.'''
574 577 return matchmod.always(repo.root, repo.getcwd())
575 578
576 579 def matchfiles(repo, files, badfn=None):
577 580 '''Return a matcher that will efficiently match exactly these files.'''
578 581 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
579 582
580 583 def parsefollowlinespattern(repo, rev, pat, msg):
581 584 """Return a file name from `pat` pattern suitable for usage in followlines
582 585 logic.
583 586 """
584 587 if not matchmod.patkind(pat):
585 588 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
586 589 else:
587 590 ctx = repo[rev]
588 591 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
589 592 files = [f for f in ctx if m(f)]
590 593 if len(files) != 1:
591 594 raise error.ParseError(msg)
592 595 return files[0]
593 596
594 597 def origpath(ui, repo, filepath):
595 598 '''customize where .orig files are created
596 599
597 600 Fetch user defined path from config file: [ui] origbackuppath = <path>
598 601 Fall back to default (filepath with .orig suffix) if not specified
599 602 '''
600 603 origbackuppath = ui.config('ui', 'origbackuppath')
601 604 if not origbackuppath:
602 605 return filepath + ".orig"
603 606
604 607 # Convert filepath from an absolute path into a path inside the repo.
605 608 filepathfromroot = util.normpath(os.path.relpath(filepath,
606 609 start=repo.root))
607 610
608 611 origvfs = vfs.vfs(repo.wjoin(origbackuppath))
609 612 origbackupdir = origvfs.dirname(filepathfromroot)
610 613 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
611 614 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
612 615
613 616 # Remove any files that conflict with the backup file's path
614 617 for f in reversed(list(util.finddirs(filepathfromroot))):
615 618 if origvfs.isfileorlink(f):
616 619 ui.note(_('removing conflicting file: %s\n')
617 620 % origvfs.join(f))
618 621 origvfs.unlink(f)
619 622 break
620 623
621 624 origvfs.makedirs(origbackupdir)
622 625
623 626 if origvfs.isdir(filepathfromroot) and not origvfs.islink(filepathfromroot):
624 627 ui.note(_('removing conflicting directory: %s\n')
625 628 % origvfs.join(filepathfromroot))
626 629 origvfs.rmtree(filepathfromroot, forcibly=True)
627 630
628 631 return origvfs.join(filepathfromroot)
629 632
630 633 class _containsnode(object):
631 634 """proxy __contains__(node) to container.__contains__ which accepts revs"""
632 635
633 636 def __init__(self, repo, revcontainer):
634 637 self._torev = repo.changelog.rev
635 638 self._revcontains = revcontainer.__contains__
636 639
637 640 def __contains__(self, node):
638 641 return self._revcontains(self._torev(node))
639 642
640 643 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None):
641 644 """do common cleanups when old nodes are replaced by new nodes
642 645
643 646 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
644 647 (we might also want to move working directory parent in the future)
645 648
646 649 By default, bookmark moves are calculated automatically from 'replacements',
647 650 but 'moves' can be used to override that. Also, 'moves' may include
648 651 additional bookmark moves that should not have associated obsmarkers.
649 652
650 653 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
651 654 have replacements. operation is a string, like "rebase".
652 655
653 656 metadata is dictionary containing metadata to be stored in obsmarker if
654 657 obsolescence is enabled.
655 658 """
656 659 if not replacements and not moves:
657 660 return
658 661
659 662 # translate mapping's other forms
660 663 if not util.safehasattr(replacements, 'items'):
661 664 replacements = {n: () for n in replacements}
662 665
663 666 # Calculate bookmark movements
664 667 if moves is None:
665 668 moves = {}
666 669 # Unfiltered repo is needed since nodes in replacements might be hidden.
667 670 unfi = repo.unfiltered()
668 671 for oldnode, newnodes in replacements.items():
669 672 if oldnode in moves:
670 673 continue
671 674 if len(newnodes) > 1:
672 675 # usually a split, take the one with biggest rev number
673 676 newnode = next(unfi.set('max(%ln)', newnodes)).node()
674 677 elif len(newnodes) == 0:
675 678 # move bookmark backwards
676 679 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
677 680 list(replacements)))
678 681 if roots:
679 682 newnode = roots[0].node()
680 683 else:
681 684 newnode = nullid
682 685 else:
683 686 newnode = newnodes[0]
684 687 moves[oldnode] = newnode
685 688
686 689 with repo.transaction('cleanup') as tr:
687 690 # Move bookmarks
688 691 bmarks = repo._bookmarks
689 692 bmarkchanges = []
690 693 allnewnodes = [n for ns in replacements.values() for n in ns]
691 694 for oldnode, newnode in moves.items():
692 695 oldbmarks = repo.nodebookmarks(oldnode)
693 696 if not oldbmarks:
694 697 continue
695 698 from . import bookmarks # avoid import cycle
696 699 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
697 700 (util.rapply(pycompat.maybebytestr, oldbmarks),
698 701 hex(oldnode), hex(newnode)))
699 702 # Delete divergent bookmarks being parents of related newnodes
700 703 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
701 704 allnewnodes, newnode, oldnode)
702 705 deletenodes = _containsnode(repo, deleterevs)
703 706 for name in oldbmarks:
704 707 bmarkchanges.append((name, newnode))
705 708 for b in bookmarks.divergent2delete(repo, deletenodes, name):
706 709 bmarkchanges.append((b, None))
707 710
708 711 if bmarkchanges:
709 712 bmarks.applychanges(repo, tr, bmarkchanges)
710 713
711 714 # Obsolete or strip nodes
712 715 if obsolete.isenabled(repo, obsolete.createmarkersopt):
713 716 # If a node is already obsoleted, and we want to obsolete it
714 717 # without a successor, skip that obssolete request since it's
715 718 # unnecessary. That's the "if s or not isobs(n)" check below.
716 719 # Also sort the node in topology order, that might be useful for
717 720 # some obsstore logic.
718 721 # NOTE: the filtering and sorting might belong to createmarkers.
719 722 isobs = unfi.obsstore.successors.__contains__
720 723 torev = unfi.changelog.rev
721 724 sortfunc = lambda ns: torev(ns[0])
722 725 rels = [(unfi[n], tuple(unfi[m] for m in s))
723 726 for n, s in sorted(replacements.items(), key=sortfunc)
724 727 if s or not isobs(n)]
725 728 if rels:
726 729 obsolete.createmarkers(repo, rels, operation=operation,
727 730 metadata=metadata)
728 731 else:
729 732 from . import repair # avoid import cycle
730 733 tostrip = list(replacements)
731 734 if tostrip:
732 735 repair.delayedstrip(repo.ui, repo, tostrip, operation)
733 736
734 737 def addremove(repo, matcher, prefix, opts=None, dry_run=None, similarity=None):
735 738 if opts is None:
736 739 opts = {}
737 740 m = matcher
738 741 if dry_run is None:
739 742 dry_run = opts.get('dry_run')
740 743 if similarity is None:
741 744 similarity = float(opts.get('similarity') or 0)
742 745
743 746 ret = 0
744 747 join = lambda f: os.path.join(prefix, f)
745 748
746 749 wctx = repo[None]
747 750 for subpath in sorted(wctx.substate):
748 751 submatch = matchmod.subdirmatcher(subpath, m)
749 752 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
750 753 sub = wctx.sub(subpath)
751 754 try:
752 755 if sub.addremove(submatch, prefix, opts, dry_run, similarity):
753 756 ret = 1
754 757 except error.LookupError:
755 758 repo.ui.status(_("skipping missing subrepository: %s\n")
756 759 % join(subpath))
757 760
758 761 rejected = []
759 762 def badfn(f, msg):
760 763 if f in m.files():
761 764 m.bad(f, msg)
762 765 rejected.append(f)
763 766
764 767 badmatch = matchmod.badmatch(m, badfn)
765 768 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
766 769 badmatch)
767 770
768 771 unknownset = set(unknown + forgotten)
769 772 toprint = unknownset.copy()
770 773 toprint.update(deleted)
771 774 for abs in sorted(toprint):
772 775 if repo.ui.verbose or not m.exact(abs):
773 776 if abs in unknownset:
774 777 status = _('adding %s\n') % m.uipath(abs)
775 778 else:
776 779 status = _('removing %s\n') % m.uipath(abs)
777 780 repo.ui.status(status)
778 781
779 782 renames = _findrenames(repo, m, added + unknown, removed + deleted,
780 783 similarity)
781 784
782 785 if not dry_run:
783 786 _markchanges(repo, unknown + forgotten, deleted, renames)
784 787
785 788 for f in rejected:
786 789 if f in m.files():
787 790 return 1
788 791 return ret
789 792
790 793 def marktouched(repo, files, similarity=0.0):
791 794 '''Assert that files have somehow been operated upon. files are relative to
792 795 the repo root.'''
793 796 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
794 797 rejected = []
795 798
796 799 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
797 800
798 801 if repo.ui.verbose:
799 802 unknownset = set(unknown + forgotten)
800 803 toprint = unknownset.copy()
801 804 toprint.update(deleted)
802 805 for abs in sorted(toprint):
803 806 if abs in unknownset:
804 807 status = _('adding %s\n') % abs
805 808 else:
806 809 status = _('removing %s\n') % abs
807 810 repo.ui.status(status)
808 811
809 812 renames = _findrenames(repo, m, added + unknown, removed + deleted,
810 813 similarity)
811 814
812 815 _markchanges(repo, unknown + forgotten, deleted, renames)
813 816
814 817 for f in rejected:
815 818 if f in m.files():
816 819 return 1
817 820 return 0
818 821
819 822 def _interestingfiles(repo, matcher):
820 823 '''Walk dirstate with matcher, looking for files that addremove would care
821 824 about.
822 825
823 826 This is different from dirstate.status because it doesn't care about
824 827 whether files are modified or clean.'''
825 828 added, unknown, deleted, removed, forgotten = [], [], [], [], []
826 829 audit_path = pathutil.pathauditor(repo.root, cached=True)
827 830
828 831 ctx = repo[None]
829 832 dirstate = repo.dirstate
830 833 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
831 834 unknown=True, ignored=False, full=False)
832 835 for abs, st in walkresults.iteritems():
833 836 dstate = dirstate[abs]
834 837 if dstate == '?' and audit_path.check(abs):
835 838 unknown.append(abs)
836 839 elif dstate != 'r' and not st:
837 840 deleted.append(abs)
838 841 elif dstate == 'r' and st:
839 842 forgotten.append(abs)
840 843 # for finding renames
841 844 elif dstate == 'r' and not st:
842 845 removed.append(abs)
843 846 elif dstate == 'a':
844 847 added.append(abs)
845 848
846 849 return added, unknown, deleted, removed, forgotten
847 850
848 851 def _findrenames(repo, matcher, added, removed, similarity):
849 852 '''Find renames from removed files to added ones.'''
850 853 renames = {}
851 854 if similarity > 0:
852 855 for old, new, score in similar.findrenames(repo, added, removed,
853 856 similarity):
854 857 if (repo.ui.verbose or not matcher.exact(old)
855 858 or not matcher.exact(new)):
856 859 repo.ui.status(_('recording removal of %s as rename to %s '
857 860 '(%d%% similar)\n') %
858 861 (matcher.rel(old), matcher.rel(new),
859 862 score * 100))
860 863 renames[new] = old
861 864 return renames
862 865
863 866 def _markchanges(repo, unknown, deleted, renames):
864 867 '''Marks the files in unknown as added, the files in deleted as removed,
865 868 and the files in renames as copied.'''
866 869 wctx = repo[None]
867 870 with repo.wlock():
868 871 wctx.forget(deleted)
869 872 wctx.add(unknown)
870 873 for new, old in renames.iteritems():
871 874 wctx.copy(old, new)
872 875
873 876 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
874 877 """Update the dirstate to reflect the intent of copying src to dst. For
875 878 different reasons it might not end with dst being marked as copied from src.
876 879 """
877 880 origsrc = repo.dirstate.copied(src) or src
878 881 if dst == origsrc: # copying back a copy?
879 882 if repo.dirstate[dst] not in 'mn' and not dryrun:
880 883 repo.dirstate.normallookup(dst)
881 884 else:
882 885 if repo.dirstate[origsrc] == 'a' and origsrc == src:
883 886 if not ui.quiet:
884 887 ui.warn(_("%s has not been committed yet, so no copy "
885 888 "data will be stored for %s.\n")
886 889 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
887 890 if repo.dirstate[dst] in '?r' and not dryrun:
888 891 wctx.add([dst])
889 892 elif not dryrun:
890 893 wctx.copy(origsrc, dst)
891 894
892 895 def readrequires(opener, supported):
893 896 '''Reads and parses .hg/requires and checks if all entries found
894 897 are in the list of supported features.'''
895 898 requirements = set(opener.read("requires").splitlines())
896 899 missings = []
897 900 for r in requirements:
898 901 if r not in supported:
899 902 if not r or not r[0:1].isalnum():
900 903 raise error.RequirementError(_(".hg/requires file is corrupt"))
901 904 missings.append(r)
902 905 missings.sort()
903 906 if missings:
904 907 raise error.RequirementError(
905 908 _("repository requires features unknown to this Mercurial: %s")
906 909 % " ".join(missings),
907 910 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
908 911 " for more information"))
909 912 return requirements
910 913
911 914 def writerequires(opener, requirements):
912 915 with opener('requires', 'w') as fp:
913 916 for r in sorted(requirements):
914 917 fp.write("%s\n" % r)
915 918
916 919 class filecachesubentry(object):
917 920 def __init__(self, path, stat):
918 921 self.path = path
919 922 self.cachestat = None
920 923 self._cacheable = None
921 924
922 925 if stat:
923 926 self.cachestat = filecachesubentry.stat(self.path)
924 927
925 928 if self.cachestat:
926 929 self._cacheable = self.cachestat.cacheable()
927 930 else:
928 931 # None means we don't know yet
929 932 self._cacheable = None
930 933
931 934 def refresh(self):
932 935 if self.cacheable():
933 936 self.cachestat = filecachesubentry.stat(self.path)
934 937
935 938 def cacheable(self):
936 939 if self._cacheable is not None:
937 940 return self._cacheable
938 941
939 942 # we don't know yet, assume it is for now
940 943 return True
941 944
942 945 def changed(self):
943 946 # no point in going further if we can't cache it
944 947 if not self.cacheable():
945 948 return True
946 949
947 950 newstat = filecachesubentry.stat(self.path)
948 951
949 952 # we may not know if it's cacheable yet, check again now
950 953 if newstat and self._cacheable is None:
951 954 self._cacheable = newstat.cacheable()
952 955
953 956 # check again
954 957 if not self._cacheable:
955 958 return True
956 959
957 960 if self.cachestat != newstat:
958 961 self.cachestat = newstat
959 962 return True
960 963 else:
961 964 return False
962 965
963 966 @staticmethod
964 967 def stat(path):
965 968 try:
966 969 return util.cachestat(path)
967 970 except OSError as e:
968 971 if e.errno != errno.ENOENT:
969 972 raise
970 973
971 974 class filecacheentry(object):
972 975 def __init__(self, paths, stat=True):
973 976 self._entries = []
974 977 for path in paths:
975 978 self._entries.append(filecachesubentry(path, stat))
976 979
977 980 def changed(self):
978 981 '''true if any entry has changed'''
979 982 for entry in self._entries:
980 983 if entry.changed():
981 984 return True
982 985 return False
983 986
984 987 def refresh(self):
985 988 for entry in self._entries:
986 989 entry.refresh()
987 990
988 991 class filecache(object):
989 992 '''A property like decorator that tracks files under .hg/ for updates.
990 993
991 994 Records stat info when called in _filecache.
992 995
993 996 On subsequent calls, compares old stat info with new info, and recreates the
994 997 object when any of the files changes, updating the new stat info in
995 998 _filecache.
996 999
997 1000 Mercurial either atomic renames or appends for files under .hg,
998 1001 so to ensure the cache is reliable we need the filesystem to be able
999 1002 to tell us if a file has been replaced. If it can't, we fallback to
1000 1003 recreating the object on every call (essentially the same behavior as
1001 1004 propertycache).
1002 1005
1003 1006 '''
1004 1007 def __init__(self, *paths):
1005 1008 self.paths = paths
1006 1009
1007 1010 def join(self, obj, fname):
1008 1011 """Used to compute the runtime path of a cached file.
1009 1012
1010 1013 Users should subclass filecache and provide their own version of this
1011 1014 function to call the appropriate join function on 'obj' (an instance
1012 1015 of the class that its member function was decorated).
1013 1016 """
1014 1017 raise NotImplementedError
1015 1018
1016 1019 def __call__(self, func):
1017 1020 self.func = func
1018 1021 self.name = func.__name__.encode('ascii')
1019 1022 return self
1020 1023
1021 1024 def __get__(self, obj, type=None):
1022 1025 # if accessed on the class, return the descriptor itself.
1023 1026 if obj is None:
1024 1027 return self
1025 1028 # do we need to check if the file changed?
1026 1029 if self.name in obj.__dict__:
1027 1030 assert self.name in obj._filecache, self.name
1028 1031 return obj.__dict__[self.name]
1029 1032
1030 1033 entry = obj._filecache.get(self.name)
1031 1034
1032 1035 if entry:
1033 1036 if entry.changed():
1034 1037 entry.obj = self.func(obj)
1035 1038 else:
1036 1039 paths = [self.join(obj, path) for path in self.paths]
1037 1040
1038 1041 # We stat -before- creating the object so our cache doesn't lie if
1039 1042 # a writer modified between the time we read and stat
1040 1043 entry = filecacheentry(paths, True)
1041 1044 entry.obj = self.func(obj)
1042 1045
1043 1046 obj._filecache[self.name] = entry
1044 1047
1045 1048 obj.__dict__[self.name] = entry.obj
1046 1049 return entry.obj
1047 1050
1048 1051 def __set__(self, obj, value):
1049 1052 if self.name not in obj._filecache:
1050 1053 # we add an entry for the missing value because X in __dict__
1051 1054 # implies X in _filecache
1052 1055 paths = [self.join(obj, path) for path in self.paths]
1053 1056 ce = filecacheentry(paths, False)
1054 1057 obj._filecache[self.name] = ce
1055 1058 else:
1056 1059 ce = obj._filecache[self.name]
1057 1060
1058 1061 ce.obj = value # update cached copy
1059 1062 obj.__dict__[self.name] = value # update copy returned by obj.x
1060 1063
1061 1064 def __delete__(self, obj):
1062 1065 try:
1063 1066 del obj.__dict__[self.name]
1064 1067 except KeyError:
1065 1068 raise AttributeError(self.name)
1066 1069
1067 1070 def extdatasource(repo, source):
1068 1071 """Gather a map of rev -> value dict from the specified source
1069 1072
1070 1073 A source spec is treated as a URL, with a special case shell: type
1071 1074 for parsing the output from a shell command.
1072 1075
1073 1076 The data is parsed as a series of newline-separated records where
1074 1077 each record is a revision specifier optionally followed by a space
1075 1078 and a freeform string value. If the revision is known locally, it
1076 1079 is converted to a rev, otherwise the record is skipped.
1077 1080
1078 1081 Note that both key and value are treated as UTF-8 and converted to
1079 1082 the local encoding. This allows uniformity between local and
1080 1083 remote data sources.
1081 1084 """
1082 1085
1083 1086 spec = repo.ui.config("extdata", source)
1084 1087 if not spec:
1085 1088 raise error.Abort(_("unknown extdata source '%s'") % source)
1086 1089
1087 1090 data = {}
1088 1091 src = proc = None
1089 1092 try:
1090 1093 if spec.startswith("shell:"):
1091 1094 # external commands should be run relative to the repo root
1092 1095 cmd = spec[6:]
1093 1096 proc = subprocess.Popen(cmd, shell=True, bufsize=-1,
1094 1097 close_fds=procutil.closefds,
1095 1098 stdout=subprocess.PIPE, cwd=repo.root)
1096 1099 src = proc.stdout
1097 1100 else:
1098 1101 # treat as a URL or file
1099 1102 src = url.open(repo.ui, spec)
1100 1103 for l in src:
1101 1104 if " " in l:
1102 1105 k, v = l.strip().split(" ", 1)
1103 1106 else:
1104 1107 k, v = l.strip(), ""
1105 1108
1106 1109 k = encoding.tolocal(k)
1107 1110 try:
1108 1111 data[repo[k].rev()] = encoding.tolocal(v)
1109 1112 except (error.LookupError, error.RepoLookupError):
1110 1113 pass # we ignore data for nodes that don't exist locally
1111 1114 finally:
1112 1115 if proc:
1113 1116 proc.communicate()
1114 1117 if src:
1115 1118 src.close()
1116 1119 if proc and proc.returncode != 0:
1117 1120 raise error.Abort(_("extdata command '%s' failed: %s")
1118 1121 % (cmd, procutil.explainexit(proc.returncode)[0]))
1119 1122
1120 1123 return data
1121 1124
1122 1125 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1123 1126 if lock is None:
1124 1127 raise error.LockInheritanceContractViolation(
1125 1128 'lock can only be inherited while held')
1126 1129 if environ is None:
1127 1130 environ = {}
1128 1131 with lock.inherit() as locker:
1129 1132 environ[envvar] = locker
1130 1133 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1131 1134
1132 1135 def wlocksub(repo, cmd, *args, **kwargs):
1133 1136 """run cmd as a subprocess that allows inheriting repo's wlock
1134 1137
1135 1138 This can only be called while the wlock is held. This takes all the
1136 1139 arguments that ui.system does, and returns the exit code of the
1137 1140 subprocess."""
1138 1141 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1139 1142 **kwargs)
1140 1143
1141 1144 def gdinitconfig(ui):
1142 1145 """helper function to know if a repo should be created as general delta
1143 1146 """
1144 1147 # experimental config: format.generaldelta
1145 1148 return (ui.configbool('format', 'generaldelta')
1146 1149 or ui.configbool('format', 'usegeneraldelta'))
1147 1150
1148 1151 def gddeltaconfig(ui):
1149 1152 """helper function to know if incoming delta should be optimised
1150 1153 """
1151 1154 # experimental config: format.generaldelta
1152 1155 return ui.configbool('format', 'generaldelta')
1153 1156
1154 1157 class simplekeyvaluefile(object):
1155 1158 """A simple file with key=value lines
1156 1159
1157 1160 Keys must be alphanumerics and start with a letter, values must not
1158 1161 contain '\n' characters"""
1159 1162 firstlinekey = '__firstline'
1160 1163
1161 1164 def __init__(self, vfs, path, keys=None):
1162 1165 self.vfs = vfs
1163 1166 self.path = path
1164 1167
1165 1168 def read(self, firstlinenonkeyval=False):
1166 1169 """Read the contents of a simple key-value file
1167 1170
1168 1171 'firstlinenonkeyval' indicates whether the first line of file should
1169 1172 be treated as a key-value pair or reuturned fully under the
1170 1173 __firstline key."""
1171 1174 lines = self.vfs.readlines(self.path)
1172 1175 d = {}
1173 1176 if firstlinenonkeyval:
1174 1177 if not lines:
1175 1178 e = _("empty simplekeyvalue file")
1176 1179 raise error.CorruptedState(e)
1177 1180 # we don't want to include '\n' in the __firstline
1178 1181 d[self.firstlinekey] = lines[0][:-1]
1179 1182 del lines[0]
1180 1183
1181 1184 try:
1182 1185 # the 'if line.strip()' part prevents us from failing on empty
1183 1186 # lines which only contain '\n' therefore are not skipped
1184 1187 # by 'if line'
1185 1188 updatedict = dict(line[:-1].split('=', 1) for line in lines
1186 1189 if line.strip())
1187 1190 if self.firstlinekey in updatedict:
1188 1191 e = _("%r can't be used as a key")
1189 1192 raise error.CorruptedState(e % self.firstlinekey)
1190 1193 d.update(updatedict)
1191 1194 except ValueError as e:
1192 1195 raise error.CorruptedState(str(e))
1193 1196 return d
1194 1197
1195 1198 def write(self, data, firstline=None):
1196 1199 """Write key=>value mapping to a file
1197 1200 data is a dict. Keys must be alphanumerical and start with a letter.
1198 1201 Values must not contain newline characters.
1199 1202
1200 1203 If 'firstline' is not None, it is written to file before
1201 1204 everything else, as it is, not in a key=value form"""
1202 1205 lines = []
1203 1206 if firstline is not None:
1204 1207 lines.append('%s\n' % firstline)
1205 1208
1206 1209 for k, v in data.items():
1207 1210 if k == self.firstlinekey:
1208 1211 e = "key name '%s' is reserved" % self.firstlinekey
1209 1212 raise error.ProgrammingError(e)
1210 1213 if not k[0:1].isalpha():
1211 1214 e = "keys must start with a letter in a key-value file"
1212 1215 raise error.ProgrammingError(e)
1213 1216 if not k.isalnum():
1214 1217 e = "invalid key name in a simple key-value file"
1215 1218 raise error.ProgrammingError(e)
1216 1219 if '\n' in v:
1217 1220 e = "invalid value in a simple key-value file"
1218 1221 raise error.ProgrammingError(e)
1219 1222 lines.append("%s=%s\n" % (k, v))
1220 1223 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1221 1224 fp.write(''.join(lines))
1222 1225
1223 1226 _reportobsoletedsource = [
1224 1227 'debugobsolete',
1225 1228 'pull',
1226 1229 'push',
1227 1230 'serve',
1228 1231 'unbundle',
1229 1232 ]
1230 1233
1231 1234 _reportnewcssource = [
1232 1235 'pull',
1233 1236 'unbundle',
1234 1237 ]
1235 1238
1236 1239 # a list of (repo, ctx, files) functions called by various commands to allow
1237 1240 # extensions to ensure the corresponding files are available locally, before the
1238 1241 # command uses them.
1239 1242 fileprefetchhooks = util.hooks()
1240 1243
1241 1244 # A marker that tells the evolve extension to suppress its own reporting
1242 1245 _reportstroubledchangesets = True
1243 1246
1244 1247 def registersummarycallback(repo, otr, txnname=''):
1245 1248 """register a callback to issue a summary after the transaction is closed
1246 1249 """
1247 1250 def txmatch(sources):
1248 1251 return any(txnname.startswith(source) for source in sources)
1249 1252
1250 1253 categories = []
1251 1254
1252 1255 def reportsummary(func):
1253 1256 """decorator for report callbacks."""
1254 1257 # The repoview life cycle is shorter than the one of the actual
1255 1258 # underlying repository. So the filtered object can die before the
1256 1259 # weakref is used leading to troubles. We keep a reference to the
1257 1260 # unfiltered object and restore the filtering when retrieving the
1258 1261 # repository through the weakref.
1259 1262 filtername = repo.filtername
1260 1263 reporef = weakref.ref(repo.unfiltered())
1261 1264 def wrapped(tr):
1262 1265 repo = reporef()
1263 1266 if filtername:
1264 1267 repo = repo.filtered(filtername)
1265 1268 func(repo, tr)
1266 1269 newcat = '%02i-txnreport' % len(categories)
1267 1270 otr.addpostclose(newcat, wrapped)
1268 1271 categories.append(newcat)
1269 1272 return wrapped
1270 1273
1271 1274 if txmatch(_reportobsoletedsource):
1272 1275 @reportsummary
1273 1276 def reportobsoleted(repo, tr):
1274 1277 obsoleted = obsutil.getobsoleted(repo, tr)
1275 1278 if obsoleted:
1276 1279 repo.ui.status(_('obsoleted %i changesets\n')
1277 1280 % len(obsoleted))
1278 1281
1279 1282 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1280 1283 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1281 1284 instabilitytypes = [
1282 1285 ('orphan', 'orphan'),
1283 1286 ('phase-divergent', 'phasedivergent'),
1284 1287 ('content-divergent', 'contentdivergent'),
1285 1288 ]
1286 1289
1287 1290 def getinstabilitycounts(repo):
1288 1291 filtered = repo.changelog.filteredrevs
1289 1292 counts = {}
1290 1293 for instability, revset in instabilitytypes:
1291 1294 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1292 1295 filtered)
1293 1296 return counts
1294 1297
1295 1298 oldinstabilitycounts = getinstabilitycounts(repo)
1296 1299 @reportsummary
1297 1300 def reportnewinstabilities(repo, tr):
1298 1301 newinstabilitycounts = getinstabilitycounts(repo)
1299 1302 for instability, revset in instabilitytypes:
1300 1303 delta = (newinstabilitycounts[instability] -
1301 1304 oldinstabilitycounts[instability])
1302 1305 if delta > 0:
1303 1306 repo.ui.warn(_('%i new %s changesets\n') %
1304 1307 (delta, instability))
1305 1308
1306 1309 if txmatch(_reportnewcssource):
1307 1310 @reportsummary
1308 1311 def reportnewcs(repo, tr):
1309 1312 """Report the range of new revisions pulled/unbundled."""
1310 1313 newrevs = tr.changes.get('revs', xrange(0, 0))
1311 1314 if not newrevs:
1312 1315 return
1313 1316
1314 1317 # Compute the bounds of new revisions' range, excluding obsoletes.
1315 1318 unfi = repo.unfiltered()
1316 1319 revs = unfi.revs('%ld and not obsolete()', newrevs)
1317 1320 if not revs:
1318 1321 # Got only obsoletes.
1319 1322 return
1320 1323 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1321 1324
1322 1325 if minrev == maxrev:
1323 1326 revrange = minrev
1324 1327 else:
1325 1328 revrange = '%s:%s' % (minrev, maxrev)
1326 1329 repo.ui.status(_('new changesets %s\n') % revrange)
1327 1330
1328 1331 def nodesummaries(repo, nodes, maxnumnodes=4):
1329 1332 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1330 1333 return ' '.join(short(h) for h in nodes)
1331 1334 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1332 1335 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1333 1336
1334 1337 def enforcesinglehead(repo, tr, desc):
1335 1338 """check that no named branch has multiple heads"""
1336 1339 if desc in ('strip', 'repair'):
1337 1340 # skip the logic during strip
1338 1341 return
1339 1342 visible = repo.filtered('visible')
1340 1343 # possible improvement: we could restrict the check to affected branch
1341 1344 for name, heads in visible.branchmap().iteritems():
1342 1345 if len(heads) > 1:
1343 1346 msg = _('rejecting multiple heads on branch "%s"')
1344 1347 msg %= name
1345 1348 hint = _('%d heads: %s')
1346 1349 hint %= (len(heads), nodesummaries(repo, heads))
1347 1350 raise error.Abort(msg, hint=hint)
1348 1351
1349 1352 def wrapconvertsink(sink):
1350 1353 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1351 1354 before it is used, whether or not the convert extension was formally loaded.
1352 1355 """
1353 1356 return sink
1354 1357
1355 1358 def unhidehashlikerevs(repo, specs, hiddentype):
1356 1359 """parse the user specs and unhide changesets whose hash or revision number
1357 1360 is passed.
1358 1361
1359 1362 hiddentype can be: 1) 'warn': warn while unhiding changesets
1360 1363 2) 'nowarn': don't warn while unhiding changesets
1361 1364
1362 1365 returns a repo object with the required changesets unhidden
1363 1366 """
1364 1367 if not repo.filtername or not repo.ui.configbool('experimental',
1365 1368 'directaccess'):
1366 1369 return repo
1367 1370
1368 1371 if repo.filtername not in ('visible', 'visible-hidden'):
1369 1372 return repo
1370 1373
1371 1374 symbols = set()
1372 1375 for spec in specs:
1373 1376 try:
1374 1377 tree = revsetlang.parse(spec)
1375 1378 except error.ParseError: # will be reported by scmutil.revrange()
1376 1379 continue
1377 1380
1378 1381 symbols.update(revsetlang.gethashlikesymbols(tree))
1379 1382
1380 1383 if not symbols:
1381 1384 return repo
1382 1385
1383 1386 revs = _getrevsfromsymbols(repo, symbols)
1384 1387
1385 1388 if not revs:
1386 1389 return repo
1387 1390
1388 1391 if hiddentype == 'warn':
1389 1392 unfi = repo.unfiltered()
1390 1393 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1391 1394 repo.ui.warn(_("warning: accessing hidden changesets for write "
1392 1395 "operation: %s\n") % revstr)
1393 1396
1394 1397 # we have to use new filtername to separate branch/tags cache until we can
1395 1398 # disbale these cache when revisions are dynamically pinned.
1396 1399 return repo.filtered('visible-hidden', revs)
1397 1400
1398 1401 def _getrevsfromsymbols(repo, symbols):
1399 1402 """parse the list of symbols and returns a set of revision numbers of hidden
1400 1403 changesets present in symbols"""
1401 1404 revs = set()
1402 1405 unfi = repo.unfiltered()
1403 1406 unficl = unfi.changelog
1404 1407 cl = repo.changelog
1405 1408 tiprev = len(unficl)
1406 1409 pmatch = unficl._partialmatch
1407 1410 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1408 1411 for s in symbols:
1409 1412 try:
1410 1413 n = int(s)
1411 1414 if n <= tiprev:
1412 1415 if not allowrevnums:
1413 1416 continue
1414 1417 else:
1415 1418 if n not in cl:
1416 1419 revs.add(n)
1417 1420 continue
1418 1421 except ValueError:
1419 1422 pass
1420 1423
1421 1424 try:
1422 1425 s = pmatch(s)
1423 1426 except (error.LookupError, error.WdirUnsupported):
1424 1427 s = None
1425 1428
1426 1429 if s is not None:
1427 1430 rev = unficl.rev(s)
1428 1431 if rev not in cl:
1429 1432 revs.add(rev)
1430 1433
1431 1434 return revs
@@ -1,50 +1,50 b''
1 1 # Extension dedicated to test patch.diff() upgrade modes
2 2
3 3 from __future__ import absolute_import
4 4
5 5 from mercurial import (
6 6 error,
7 7 patch,
8 8 registrar,
9 9 scmutil,
10 10 )
11 11
12 12 cmdtable = {}
13 13 command = registrar.command(cmdtable)
14 14
15 15 @command(b'autodiff',
16 16 [(b'', b'git', b'', b'git upgrade mode (yes/no/auto/warn/abort)')],
17 17 b'[OPTION]... [FILE]...')
18 18 def autodiff(ui, repo, *pats, **opts):
19 19 diffopts = patch.difffeatureopts(ui, opts)
20 20 git = opts.get(b'git', b'no')
21 21 brokenfiles = set()
22 22 losedatafn = None
23 23 if git in (b'yes', b'no'):
24 24 diffopts.git = git == b'yes'
25 25 diffopts.upgrade = False
26 26 elif git == b'auto':
27 27 diffopts.git = False
28 28 diffopts.upgrade = True
29 29 elif git == b'warn':
30 30 diffopts.git = False
31 31 diffopts.upgrade = True
32 32 def losedatafn(fn=None, **kwargs):
33 33 brokenfiles.add(fn)
34 34 return True
35 35 elif git == b'abort':
36 36 diffopts.git = False
37 37 diffopts.upgrade = True
38 38 def losedatafn(fn=None, **kwargs):
39 39 raise error.Abort(b'losing data for %s' % fn)
40 40 else:
41 41 raise error.Abort(b'--git must be yes, no or auto')
42 42
43 node1, node2 = scmutil.revpair(repo, [])
43 node1, node2 = scmutil.revpairnodes(repo, [])
44 44 m = scmutil.match(repo[node2], pats, opts)
45 45 it = patch.diff(repo, node1, node2, match=m, opts=diffopts,
46 46 losedatafn=losedatafn)
47 47 for chunk in it:
48 48 ui.write(chunk)
49 49 for fn in sorted(brokenfiles):
50 50 ui.write((b'data lost for: %s\n' % fn))
General Comments 0
You need to be logged in to leave comments. Login now