##// END OF EJS Templates
subrepo: use root-repo-relative path from `hg files` with ui.relative-paths=no...
Martin von Zweigbergk -
r41914:980e0520 default
parent child Browse files
Show More
@@ -1,3351 +1,3352 b''
1 1 # cmdutil.py - help for command processing in 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 errno
11 11 import os
12 12 import re
13 13
14 14 from .i18n import _
15 15 from .node import (
16 16 hex,
17 17 nullid,
18 18 nullrev,
19 19 short,
20 20 )
21 21
22 22 from . import (
23 23 bookmarks,
24 24 changelog,
25 25 copies,
26 26 crecord as crecordmod,
27 27 dirstateguard,
28 28 encoding,
29 29 error,
30 30 formatter,
31 31 logcmdutil,
32 32 match as matchmod,
33 33 merge as mergemod,
34 34 mergeutil,
35 35 obsolete,
36 36 patch,
37 37 pathutil,
38 38 phases,
39 39 pycompat,
40 40 revlog,
41 41 rewriteutil,
42 42 scmutil,
43 43 smartset,
44 44 subrepoutil,
45 45 templatekw,
46 46 templater,
47 47 util,
48 48 vfs as vfsmod,
49 49 )
50 50
51 51 from .utils import (
52 52 dateutil,
53 53 stringutil,
54 54 )
55 55
56 56 stringio = util.stringio
57 57
58 58 # templates of common command options
59 59
60 60 dryrunopts = [
61 61 ('n', 'dry-run', None,
62 62 _('do not perform actions, just print output')),
63 63 ]
64 64
65 65 confirmopts = [
66 66 ('', 'confirm', None,
67 67 _('ask before applying actions')),
68 68 ]
69 69
70 70 remoteopts = [
71 71 ('e', 'ssh', '',
72 72 _('specify ssh command to use'), _('CMD')),
73 73 ('', 'remotecmd', '',
74 74 _('specify hg command to run on the remote side'), _('CMD')),
75 75 ('', 'insecure', None,
76 76 _('do not verify server certificate (ignoring web.cacerts config)')),
77 77 ]
78 78
79 79 walkopts = [
80 80 ('I', 'include', [],
81 81 _('include names matching the given patterns'), _('PATTERN')),
82 82 ('X', 'exclude', [],
83 83 _('exclude names matching the given patterns'), _('PATTERN')),
84 84 ]
85 85
86 86 commitopts = [
87 87 ('m', 'message', '',
88 88 _('use text as commit message'), _('TEXT')),
89 89 ('l', 'logfile', '',
90 90 _('read commit message from file'), _('FILE')),
91 91 ]
92 92
93 93 commitopts2 = [
94 94 ('d', 'date', '',
95 95 _('record the specified date as commit date'), _('DATE')),
96 96 ('u', 'user', '',
97 97 _('record the specified user as committer'), _('USER')),
98 98 ]
99 99
100 100 formatteropts = [
101 101 ('T', 'template', '',
102 102 _('display with template'), _('TEMPLATE')),
103 103 ]
104 104
105 105 templateopts = [
106 106 ('', 'style', '',
107 107 _('display using template map file (DEPRECATED)'), _('STYLE')),
108 108 ('T', 'template', '',
109 109 _('display with template'), _('TEMPLATE')),
110 110 ]
111 111
112 112 logopts = [
113 113 ('p', 'patch', None, _('show patch')),
114 114 ('g', 'git', None, _('use git extended diff format')),
115 115 ('l', 'limit', '',
116 116 _('limit number of changes displayed'), _('NUM')),
117 117 ('M', 'no-merges', None, _('do not show merges')),
118 118 ('', 'stat', None, _('output diffstat-style summary of changes')),
119 119 ('G', 'graph', None, _("show the revision DAG")),
120 120 ] + templateopts
121 121
122 122 diffopts = [
123 123 ('a', 'text', None, _('treat all files as text')),
124 124 ('g', 'git', None, _('use git extended diff format')),
125 125 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
126 126 ('', 'nodates', None, _('omit dates from diff headers'))
127 127 ]
128 128
129 129 diffwsopts = [
130 130 ('w', 'ignore-all-space', None,
131 131 _('ignore white space when comparing lines')),
132 132 ('b', 'ignore-space-change', None,
133 133 _('ignore changes in the amount of white space')),
134 134 ('B', 'ignore-blank-lines', None,
135 135 _('ignore changes whose lines are all blank')),
136 136 ('Z', 'ignore-space-at-eol', None,
137 137 _('ignore changes in whitespace at EOL')),
138 138 ]
139 139
140 140 diffopts2 = [
141 141 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
142 142 ('p', 'show-function', None, _('show which function each change is in')),
143 143 ('', 'reverse', None, _('produce a diff that undoes the changes')),
144 144 ] + diffwsopts + [
145 145 ('U', 'unified', '',
146 146 _('number of lines of context to show'), _('NUM')),
147 147 ('', 'stat', None, _('output diffstat-style summary of changes')),
148 148 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
149 149 ]
150 150
151 151 mergetoolopts = [
152 152 ('t', 'tool', '', _('specify merge tool'), _('TOOL')),
153 153 ]
154 154
155 155 similarityopts = [
156 156 ('s', 'similarity', '',
157 157 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
158 158 ]
159 159
160 160 subrepoopts = [
161 161 ('S', 'subrepos', None,
162 162 _('recurse into subrepositories'))
163 163 ]
164 164
165 165 debugrevlogopts = [
166 166 ('c', 'changelog', False, _('open changelog')),
167 167 ('m', 'manifest', False, _('open manifest')),
168 168 ('', 'dir', '', _('open directory manifest')),
169 169 ]
170 170
171 171 # special string such that everything below this line will be ingored in the
172 172 # editor text
173 173 _linebelow = "^HG: ------------------------ >8 ------------------------$"
174 174
175 175 def ishunk(x):
176 176 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
177 177 return isinstance(x, hunkclasses)
178 178
179 179 def newandmodified(chunks, originalchunks):
180 180 newlyaddedandmodifiedfiles = set()
181 181 for chunk in chunks:
182 182 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
183 183 originalchunks:
184 184 newlyaddedandmodifiedfiles.add(chunk.header.filename())
185 185 return newlyaddedandmodifiedfiles
186 186
187 187 def parsealiases(cmd):
188 188 return cmd.split("|")
189 189
190 190 def setupwrapcolorwrite(ui):
191 191 # wrap ui.write so diff output can be labeled/colorized
192 192 def wrapwrite(orig, *args, **kw):
193 193 label = kw.pop(r'label', '')
194 194 for chunk, l in patch.difflabel(lambda: args):
195 195 orig(chunk, label=label + l)
196 196
197 197 oldwrite = ui.write
198 198 def wrap(*args, **kwargs):
199 199 return wrapwrite(oldwrite, *args, **kwargs)
200 200 setattr(ui, 'write', wrap)
201 201 return oldwrite
202 202
203 203 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
204 204 try:
205 205 if usecurses:
206 206 if testfile:
207 207 recordfn = crecordmod.testdecorator(
208 208 testfile, crecordmod.testchunkselector)
209 209 else:
210 210 recordfn = crecordmod.chunkselector
211 211
212 212 return crecordmod.filterpatch(ui, originalhunks, recordfn,
213 213 operation)
214 214 except crecordmod.fallbackerror as e:
215 215 ui.warn('%s\n' % e.message)
216 216 ui.warn(_('falling back to text mode\n'))
217 217
218 218 return patch.filterpatch(ui, originalhunks, operation)
219 219
220 220 def recordfilter(ui, originalhunks, operation=None):
221 221 """ Prompts the user to filter the originalhunks and return a list of
222 222 selected hunks.
223 223 *operation* is used for to build ui messages to indicate the user what
224 224 kind of filtering they are doing: reverting, committing, shelving, etc.
225 225 (see patch.filterpatch).
226 226 """
227 227 usecurses = crecordmod.checkcurses(ui)
228 228 testfile = ui.config('experimental', 'crecordtest')
229 229 oldwrite = setupwrapcolorwrite(ui)
230 230 try:
231 231 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
232 232 testfile, operation)
233 233 finally:
234 234 ui.write = oldwrite
235 235 return newchunks, newopts
236 236
237 237 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
238 238 filterfn, *pats, **opts):
239 239 opts = pycompat.byteskwargs(opts)
240 240 if not ui.interactive():
241 241 if cmdsuggest:
242 242 msg = _('running non-interactively, use %s instead') % cmdsuggest
243 243 else:
244 244 msg = _('running non-interactively')
245 245 raise error.Abort(msg)
246 246
247 247 # make sure username is set before going interactive
248 248 if not opts.get('user'):
249 249 ui.username() # raise exception, username not provided
250 250
251 251 def recordfunc(ui, repo, message, match, opts):
252 252 """This is generic record driver.
253 253
254 254 Its job is to interactively filter local changes, and
255 255 accordingly prepare working directory into a state in which the
256 256 job can be delegated to a non-interactive commit command such as
257 257 'commit' or 'qrefresh'.
258 258
259 259 After the actual job is done by non-interactive command, the
260 260 working directory is restored to its original state.
261 261
262 262 In the end we'll record interesting changes, and everything else
263 263 will be left in place, so the user can continue working.
264 264 """
265 265
266 266 checkunfinished(repo, commit=True)
267 267 wctx = repo[None]
268 268 merge = len(wctx.parents()) > 1
269 269 if merge:
270 270 raise error.Abort(_('cannot partially commit a merge '
271 271 '(use "hg commit" instead)'))
272 272
273 273 def fail(f, msg):
274 274 raise error.Abort('%s: %s' % (f, msg))
275 275
276 276 force = opts.get('force')
277 277 if not force:
278 278 vdirs = []
279 279 match.explicitdir = vdirs.append
280 280 match.bad = fail
281 281
282 282 status = repo.status(match=match)
283 283 if not force:
284 284 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
285 285 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True,
286 286 section='commands',
287 287 configprefix='commit.interactive.')
288 288 diffopts.nodates = True
289 289 diffopts.git = True
290 290 diffopts.showfunc = True
291 291 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
292 292 originalchunks = patch.parsepatch(originaldiff)
293 293
294 294 # 1. filter patch, since we are intending to apply subset of it
295 295 try:
296 296 chunks, newopts = filterfn(ui, originalchunks)
297 297 except error.PatchError as err:
298 298 raise error.Abort(_('error parsing patch: %s') % err)
299 299 opts.update(newopts)
300 300
301 301 # We need to keep a backup of files that have been newly added and
302 302 # modified during the recording process because there is a previous
303 303 # version without the edit in the workdir
304 304 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
305 305 contenders = set()
306 306 for h in chunks:
307 307 try:
308 308 contenders.update(set(h.files()))
309 309 except AttributeError:
310 310 pass
311 311
312 312 changed = status.modified + status.added + status.removed
313 313 newfiles = [f for f in changed if f in contenders]
314 314 if not newfiles:
315 315 ui.status(_('no changes to record\n'))
316 316 return 0
317 317
318 318 modified = set(status.modified)
319 319
320 320 # 2. backup changed files, so we can restore them in the end
321 321
322 322 if backupall:
323 323 tobackup = changed
324 324 else:
325 325 tobackup = [f for f in newfiles if f in modified or f in \
326 326 newlyaddedandmodifiedfiles]
327 327 backups = {}
328 328 if tobackup:
329 329 backupdir = repo.vfs.join('record-backups')
330 330 try:
331 331 os.mkdir(backupdir)
332 332 except OSError as err:
333 333 if err.errno != errno.EEXIST:
334 334 raise
335 335 try:
336 336 # backup continues
337 337 for f in tobackup:
338 338 fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.',
339 339 dir=backupdir)
340 340 os.close(fd)
341 341 ui.debug('backup %r as %r\n' % (f, tmpname))
342 342 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
343 343 backups[f] = tmpname
344 344
345 345 fp = stringio()
346 346 for c in chunks:
347 347 fname = c.filename()
348 348 if fname in backups:
349 349 c.write(fp)
350 350 dopatch = fp.tell()
351 351 fp.seek(0)
352 352
353 353 # 2.5 optionally review / modify patch in text editor
354 354 if opts.get('review', False):
355 355 patchtext = (crecordmod.diffhelptext
356 356 + crecordmod.patchhelptext
357 357 + fp.read())
358 358 reviewedpatch = ui.edit(patchtext, "",
359 359 action="diff",
360 360 repopath=repo.path)
361 361 fp.truncate(0)
362 362 fp.write(reviewedpatch)
363 363 fp.seek(0)
364 364
365 365 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
366 366 # 3a. apply filtered patch to clean repo (clean)
367 367 if backups:
368 368 # Equivalent to hg.revert
369 369 m = scmutil.matchfiles(repo, backups.keys())
370 370 mergemod.update(repo, repo.dirstate.p1(), branchmerge=False,
371 371 force=True, matcher=m)
372 372
373 373 # 3b. (apply)
374 374 if dopatch:
375 375 try:
376 376 ui.debug('applying patch\n')
377 377 ui.debug(fp.getvalue())
378 378 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
379 379 except error.PatchError as err:
380 380 raise error.Abort(pycompat.bytestr(err))
381 381 del fp
382 382
383 383 # 4. We prepared working directory according to filtered
384 384 # patch. Now is the time to delegate the job to
385 385 # commit/qrefresh or the like!
386 386
387 387 # Make all of the pathnames absolute.
388 388 newfiles = [repo.wjoin(nf) for nf in newfiles]
389 389 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
390 390 finally:
391 391 # 5. finally restore backed-up files
392 392 try:
393 393 dirstate = repo.dirstate
394 394 for realname, tmpname in backups.iteritems():
395 395 ui.debug('restoring %r to %r\n' % (tmpname, realname))
396 396
397 397 if dirstate[realname] == 'n':
398 398 # without normallookup, restoring timestamp
399 399 # may cause partially committed files
400 400 # to be treated as unmodified
401 401 dirstate.normallookup(realname)
402 402
403 403 # copystat=True here and above are a hack to trick any
404 404 # editors that have f open that we haven't modified them.
405 405 #
406 406 # Also note that this racy as an editor could notice the
407 407 # file's mtime before we've finished writing it.
408 408 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
409 409 os.unlink(tmpname)
410 410 if tobackup:
411 411 os.rmdir(backupdir)
412 412 except OSError:
413 413 pass
414 414
415 415 def recordinwlock(ui, repo, message, match, opts):
416 416 with repo.wlock():
417 417 return recordfunc(ui, repo, message, match, opts)
418 418
419 419 return commit(ui, repo, recordinwlock, pats, opts)
420 420
421 421 class dirnode(object):
422 422 """
423 423 Represent a directory in user working copy with information required for
424 424 the purpose of tersing its status.
425 425
426 426 path is the path to the directory, without a trailing '/'
427 427
428 428 statuses is a set of statuses of all files in this directory (this includes
429 429 all the files in all the subdirectories too)
430 430
431 431 files is a list of files which are direct child of this directory
432 432
433 433 subdirs is a dictionary of sub-directory name as the key and it's own
434 434 dirnode object as the value
435 435 """
436 436
437 437 def __init__(self, dirpath):
438 438 self.path = dirpath
439 439 self.statuses = set([])
440 440 self.files = []
441 441 self.subdirs = {}
442 442
443 443 def _addfileindir(self, filename, status):
444 444 """Add a file in this directory as a direct child."""
445 445 self.files.append((filename, status))
446 446
447 447 def addfile(self, filename, status):
448 448 """
449 449 Add a file to this directory or to its direct parent directory.
450 450
451 451 If the file is not direct child of this directory, we traverse to the
452 452 directory of which this file is a direct child of and add the file
453 453 there.
454 454 """
455 455
456 456 # the filename contains a path separator, it means it's not the direct
457 457 # child of this directory
458 458 if '/' in filename:
459 459 subdir, filep = filename.split('/', 1)
460 460
461 461 # does the dirnode object for subdir exists
462 462 if subdir not in self.subdirs:
463 463 subdirpath = pathutil.join(self.path, subdir)
464 464 self.subdirs[subdir] = dirnode(subdirpath)
465 465
466 466 # try adding the file in subdir
467 467 self.subdirs[subdir].addfile(filep, status)
468 468
469 469 else:
470 470 self._addfileindir(filename, status)
471 471
472 472 if status not in self.statuses:
473 473 self.statuses.add(status)
474 474
475 475 def iterfilepaths(self):
476 476 """Yield (status, path) for files directly under this directory."""
477 477 for f, st in self.files:
478 478 yield st, pathutil.join(self.path, f)
479 479
480 480 def tersewalk(self, terseargs):
481 481 """
482 482 Yield (status, path) obtained by processing the status of this
483 483 dirnode.
484 484
485 485 terseargs is the string of arguments passed by the user with `--terse`
486 486 flag.
487 487
488 488 Following are the cases which can happen:
489 489
490 490 1) All the files in the directory (including all the files in its
491 491 subdirectories) share the same status and the user has asked us to terse
492 492 that status. -> yield (status, dirpath). dirpath will end in '/'.
493 493
494 494 2) Otherwise, we do following:
495 495
496 496 a) Yield (status, filepath) for all the files which are in this
497 497 directory (only the ones in this directory, not the subdirs)
498 498
499 499 b) Recurse the function on all the subdirectories of this
500 500 directory
501 501 """
502 502
503 503 if len(self.statuses) == 1:
504 504 onlyst = self.statuses.pop()
505 505
506 506 # Making sure we terse only when the status abbreviation is
507 507 # passed as terse argument
508 508 if onlyst in terseargs:
509 509 yield onlyst, self.path + '/'
510 510 return
511 511
512 512 # add the files to status list
513 513 for st, fpath in self.iterfilepaths():
514 514 yield st, fpath
515 515
516 516 #recurse on the subdirs
517 517 for dirobj in self.subdirs.values():
518 518 for st, fpath in dirobj.tersewalk(terseargs):
519 519 yield st, fpath
520 520
521 521 def tersedir(statuslist, terseargs):
522 522 """
523 523 Terse the status if all the files in a directory shares the same status.
524 524
525 525 statuslist is scmutil.status() object which contains a list of files for
526 526 each status.
527 527 terseargs is string which is passed by the user as the argument to `--terse`
528 528 flag.
529 529
530 530 The function makes a tree of objects of dirnode class, and at each node it
531 531 stores the information required to know whether we can terse a certain
532 532 directory or not.
533 533 """
534 534 # the order matters here as that is used to produce final list
535 535 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
536 536
537 537 # checking the argument validity
538 538 for s in pycompat.bytestr(terseargs):
539 539 if s not in allst:
540 540 raise error.Abort(_("'%s' not recognized") % s)
541 541
542 542 # creating a dirnode object for the root of the repo
543 543 rootobj = dirnode('')
544 544 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
545 545 'ignored', 'removed')
546 546
547 547 tersedict = {}
548 548 for attrname in pstatus:
549 549 statuschar = attrname[0:1]
550 550 for f in getattr(statuslist, attrname):
551 551 rootobj.addfile(f, statuschar)
552 552 tersedict[statuschar] = []
553 553
554 554 # we won't be tersing the root dir, so add files in it
555 555 for st, fpath in rootobj.iterfilepaths():
556 556 tersedict[st].append(fpath)
557 557
558 558 # process each sub-directory and build tersedict
559 559 for subdir in rootobj.subdirs.values():
560 560 for st, f in subdir.tersewalk(terseargs):
561 561 tersedict[st].append(f)
562 562
563 563 tersedlist = []
564 564 for st in allst:
565 565 tersedict[st].sort()
566 566 tersedlist.append(tersedict[st])
567 567
568 568 return tersedlist
569 569
570 570 def _commentlines(raw):
571 571 '''Surround lineswith a comment char and a new line'''
572 572 lines = raw.splitlines()
573 573 commentedlines = ['# %s' % line for line in lines]
574 574 return '\n'.join(commentedlines) + '\n'
575 575
576 576 def _conflictsmsg(repo):
577 577 mergestate = mergemod.mergestate.read(repo)
578 578 if not mergestate.active():
579 579 return
580 580
581 581 m = scmutil.match(repo[None])
582 582 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
583 583 if unresolvedlist:
584 584 mergeliststr = '\n'.join(
585 585 [' %s' % util.pathto(repo.root, encoding.getcwd(), path)
586 586 for path in sorted(unresolvedlist)])
587 587 msg = _('''Unresolved merge conflicts:
588 588
589 589 %s
590 590
591 591 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
592 592 else:
593 593 msg = _('No unresolved merge conflicts.')
594 594
595 595 return _commentlines(msg)
596 596
597 597 def _helpmessage(continuecmd, abortcmd):
598 598 msg = _('To continue: %s\n'
599 599 'To abort: %s') % (continuecmd, abortcmd)
600 600 return _commentlines(msg)
601 601
602 602 def _rebasemsg():
603 603 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
604 604
605 605 def _histeditmsg():
606 606 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
607 607
608 608 def _unshelvemsg():
609 609 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
610 610
611 611 def _graftmsg():
612 612 return _helpmessage('hg graft --continue', 'hg graft --abort')
613 613
614 614 def _mergemsg():
615 615 return _helpmessage('hg commit', 'hg merge --abort')
616 616
617 617 def _bisectmsg():
618 618 msg = _('To mark the changeset good: hg bisect --good\n'
619 619 'To mark the changeset bad: hg bisect --bad\n'
620 620 'To abort: hg bisect --reset\n')
621 621 return _commentlines(msg)
622 622
623 623 def fileexistspredicate(filename):
624 624 return lambda repo: repo.vfs.exists(filename)
625 625
626 626 def _mergepredicate(repo):
627 627 return len(repo[None].parents()) > 1
628 628
629 629 STATES = (
630 630 # (state, predicate to detect states, helpful message function)
631 631 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
632 632 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
633 633 ('graft', fileexistspredicate('graftstate'), _graftmsg),
634 634 ('unshelve', fileexistspredicate('shelvedstate'), _unshelvemsg),
635 635 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
636 636 # The merge state is part of a list that will be iterated over.
637 637 # They need to be last because some of the other unfinished states may also
638 638 # be in a merge or update state (eg. rebase, histedit, graft, etc).
639 639 # We want those to have priority.
640 640 ('merge', _mergepredicate, _mergemsg),
641 641 )
642 642
643 643 def _getrepostate(repo):
644 644 # experimental config: commands.status.skipstates
645 645 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
646 646 for state, statedetectionpredicate, msgfn in STATES:
647 647 if state in skip:
648 648 continue
649 649 if statedetectionpredicate(repo):
650 650 return (state, statedetectionpredicate, msgfn)
651 651
652 652 def morestatus(repo, fm):
653 653 statetuple = _getrepostate(repo)
654 654 label = 'status.morestatus'
655 655 if statetuple:
656 656 state, statedetectionpredicate, helpfulmsg = statetuple
657 657 statemsg = _('The repository is in an unfinished *%s* state.') % state
658 658 fm.plain('%s\n' % _commentlines(statemsg), label=label)
659 659 conmsg = _conflictsmsg(repo)
660 660 if conmsg:
661 661 fm.plain('%s\n' % conmsg, label=label)
662 662 if helpfulmsg:
663 663 helpmsg = helpfulmsg()
664 664 fm.plain('%s\n' % helpmsg, label=label)
665 665
666 666 def findpossible(cmd, table, strict=False):
667 667 """
668 668 Return cmd -> (aliases, command table entry)
669 669 for each matching command.
670 670 Return debug commands (or their aliases) only if no normal command matches.
671 671 """
672 672 choice = {}
673 673 debugchoice = {}
674 674
675 675 if cmd in table:
676 676 # short-circuit exact matches, "log" alias beats "log|history"
677 677 keys = [cmd]
678 678 else:
679 679 keys = table.keys()
680 680
681 681 allcmds = []
682 682 for e in keys:
683 683 aliases = parsealiases(e)
684 684 allcmds.extend(aliases)
685 685 found = None
686 686 if cmd in aliases:
687 687 found = cmd
688 688 elif not strict:
689 689 for a in aliases:
690 690 if a.startswith(cmd):
691 691 found = a
692 692 break
693 693 if found is not None:
694 694 if aliases[0].startswith("debug") or found.startswith("debug"):
695 695 debugchoice[found] = (aliases, table[e])
696 696 else:
697 697 choice[found] = (aliases, table[e])
698 698
699 699 if not choice and debugchoice:
700 700 choice = debugchoice
701 701
702 702 return choice, allcmds
703 703
704 704 def findcmd(cmd, table, strict=True):
705 705 """Return (aliases, command table entry) for command string."""
706 706 choice, allcmds = findpossible(cmd, table, strict)
707 707
708 708 if cmd in choice:
709 709 return choice[cmd]
710 710
711 711 if len(choice) > 1:
712 712 clist = sorted(choice)
713 713 raise error.AmbiguousCommand(cmd, clist)
714 714
715 715 if choice:
716 716 return list(choice.values())[0]
717 717
718 718 raise error.UnknownCommand(cmd, allcmds)
719 719
720 720 def changebranch(ui, repo, revs, label):
721 721 """ Change the branch name of given revs to label """
722 722
723 723 with repo.wlock(), repo.lock(), repo.transaction('branches'):
724 724 # abort in case of uncommitted merge or dirty wdir
725 725 bailifchanged(repo)
726 726 revs = scmutil.revrange(repo, revs)
727 727 if not revs:
728 728 raise error.Abort("empty revision set")
729 729 roots = repo.revs('roots(%ld)', revs)
730 730 if len(roots) > 1:
731 731 raise error.Abort(_("cannot change branch of non-linear revisions"))
732 732 rewriteutil.precheck(repo, revs, 'change branch of')
733 733
734 734 root = repo[roots.first()]
735 735 rpb = {parent.branch() for parent in root.parents()}
736 736 if label not in rpb and label in repo.branchmap():
737 737 raise error.Abort(_("a branch of the same name already exists"))
738 738
739 739 if repo.revs('obsolete() and %ld', revs):
740 740 raise error.Abort(_("cannot change branch of a obsolete changeset"))
741 741
742 742 # make sure only topological heads
743 743 if repo.revs('heads(%ld) - head()', revs):
744 744 raise error.Abort(_("cannot change branch in middle of a stack"))
745 745
746 746 replacements = {}
747 747 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
748 748 # mercurial.subrepo -> mercurial.cmdutil
749 749 from . import context
750 750 for rev in revs:
751 751 ctx = repo[rev]
752 752 oldbranch = ctx.branch()
753 753 # check if ctx has same branch
754 754 if oldbranch == label:
755 755 continue
756 756
757 757 def filectxfn(repo, newctx, path):
758 758 try:
759 759 return ctx[path]
760 760 except error.ManifestLookupError:
761 761 return None
762 762
763 763 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
764 764 % (hex(ctx.node()), oldbranch, label))
765 765 extra = ctx.extra()
766 766 extra['branch_change'] = hex(ctx.node())
767 767 # While changing branch of set of linear commits, make sure that
768 768 # we base our commits on new parent rather than old parent which
769 769 # was obsoleted while changing the branch
770 770 p1 = ctx.p1().node()
771 771 p2 = ctx.p2().node()
772 772 if p1 in replacements:
773 773 p1 = replacements[p1][0]
774 774 if p2 in replacements:
775 775 p2 = replacements[p2][0]
776 776
777 777 mc = context.memctx(repo, (p1, p2),
778 778 ctx.description(),
779 779 ctx.files(),
780 780 filectxfn,
781 781 user=ctx.user(),
782 782 date=ctx.date(),
783 783 extra=extra,
784 784 branch=label)
785 785
786 786 newnode = repo.commitctx(mc)
787 787 replacements[ctx.node()] = (newnode,)
788 788 ui.debug('new node id is %s\n' % hex(newnode))
789 789
790 790 # create obsmarkers and move bookmarks
791 791 scmutil.cleanupnodes(repo, replacements, 'branch-change', fixphase=True)
792 792
793 793 # move the working copy too
794 794 wctx = repo[None]
795 795 # in-progress merge is a bit too complex for now.
796 796 if len(wctx.parents()) == 1:
797 797 newid = replacements.get(wctx.p1().node())
798 798 if newid is not None:
799 799 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
800 800 # mercurial.cmdutil
801 801 from . import hg
802 802 hg.update(repo, newid[0], quietempty=True)
803 803
804 804 ui.status(_("changed branch on %d changesets\n") % len(replacements))
805 805
806 806 def findrepo(p):
807 807 while not os.path.isdir(os.path.join(p, ".hg")):
808 808 oldp, p = p, os.path.dirname(p)
809 809 if p == oldp:
810 810 return None
811 811
812 812 return p
813 813
814 814 def bailifchanged(repo, merge=True, hint=None):
815 815 """ enforce the precondition that working directory must be clean.
816 816
817 817 'merge' can be set to false if a pending uncommitted merge should be
818 818 ignored (such as when 'update --check' runs).
819 819
820 820 'hint' is the usual hint given to Abort exception.
821 821 """
822 822
823 823 if merge and repo.dirstate.p2() != nullid:
824 824 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
825 825 modified, added, removed, deleted = repo.status()[:4]
826 826 if modified or added or removed or deleted:
827 827 raise error.Abort(_('uncommitted changes'), hint=hint)
828 828 ctx = repo[None]
829 829 for s in sorted(ctx.substate):
830 830 ctx.sub(s).bailifchanged(hint=hint)
831 831
832 832 def logmessage(ui, opts):
833 833 """ get the log message according to -m and -l option """
834 834 message = opts.get('message')
835 835 logfile = opts.get('logfile')
836 836
837 837 if message and logfile:
838 838 raise error.Abort(_('options --message and --logfile are mutually '
839 839 'exclusive'))
840 840 if not message and logfile:
841 841 try:
842 842 if isstdiofilename(logfile):
843 843 message = ui.fin.read()
844 844 else:
845 845 message = '\n'.join(util.readfile(logfile).splitlines())
846 846 except IOError as inst:
847 847 raise error.Abort(_("can't read commit message '%s': %s") %
848 848 (logfile, encoding.strtolocal(inst.strerror)))
849 849 return message
850 850
851 851 def mergeeditform(ctxorbool, baseformname):
852 852 """return appropriate editform name (referencing a committemplate)
853 853
854 854 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
855 855 merging is committed.
856 856
857 857 This returns baseformname with '.merge' appended if it is a merge,
858 858 otherwise '.normal' is appended.
859 859 """
860 860 if isinstance(ctxorbool, bool):
861 861 if ctxorbool:
862 862 return baseformname + ".merge"
863 863 elif len(ctxorbool.parents()) > 1:
864 864 return baseformname + ".merge"
865 865
866 866 return baseformname + ".normal"
867 867
868 868 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
869 869 editform='', **opts):
870 870 """get appropriate commit message editor according to '--edit' option
871 871
872 872 'finishdesc' is a function to be called with edited commit message
873 873 (= 'description' of the new changeset) just after editing, but
874 874 before checking empty-ness. It should return actual text to be
875 875 stored into history. This allows to change description before
876 876 storing.
877 877
878 878 'extramsg' is a extra message to be shown in the editor instead of
879 879 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
880 880 is automatically added.
881 881
882 882 'editform' is a dot-separated list of names, to distinguish
883 883 the purpose of commit text editing.
884 884
885 885 'getcommiteditor' returns 'commitforceeditor' regardless of
886 886 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
887 887 they are specific for usage in MQ.
888 888 """
889 889 if edit or finishdesc or extramsg:
890 890 return lambda r, c, s: commitforceeditor(r, c, s,
891 891 finishdesc=finishdesc,
892 892 extramsg=extramsg,
893 893 editform=editform)
894 894 elif editform:
895 895 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
896 896 else:
897 897 return commiteditor
898 898
899 899 def _escapecommandtemplate(tmpl):
900 900 parts = []
901 901 for typ, start, end in templater.scantemplate(tmpl, raw=True):
902 902 if typ == b'string':
903 903 parts.append(stringutil.escapestr(tmpl[start:end]))
904 904 else:
905 905 parts.append(tmpl[start:end])
906 906 return b''.join(parts)
907 907
908 908 def rendercommandtemplate(ui, tmpl, props):
909 909 r"""Expand a literal template 'tmpl' in a way suitable for command line
910 910
911 911 '\' in outermost string is not taken as an escape character because it
912 912 is a directory separator on Windows.
913 913
914 914 >>> from . import ui as uimod
915 915 >>> ui = uimod.ui()
916 916 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
917 917 'c:\\foo'
918 918 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
919 919 'c:{path}'
920 920 """
921 921 if not tmpl:
922 922 return tmpl
923 923 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
924 924 return t.renderdefault(props)
925 925
926 926 def rendertemplate(ctx, tmpl, props=None):
927 927 """Expand a literal template 'tmpl' byte-string against one changeset
928 928
929 929 Each props item must be a stringify-able value or a callable returning
930 930 such value, i.e. no bare list nor dict should be passed.
931 931 """
932 932 repo = ctx.repo()
933 933 tres = formatter.templateresources(repo.ui, repo)
934 934 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
935 935 resources=tres)
936 936 mapping = {'ctx': ctx}
937 937 if props:
938 938 mapping.update(props)
939 939 return t.renderdefault(mapping)
940 940
941 941 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
942 942 r"""Convert old-style filename format string to template string
943 943
944 944 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
945 945 'foo-{reporoot|basename}-{seqno}.patch'
946 946 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
947 947 '{rev}{tags % "{tag}"}{node}'
948 948
949 949 '\' in outermost strings has to be escaped because it is a directory
950 950 separator on Windows:
951 951
952 952 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
953 953 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
954 954 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
955 955 '\\\\\\\\foo\\\\bar.patch'
956 956 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
957 957 '\\\\{tags % "{tag}"}'
958 958
959 959 but inner strings follow the template rules (i.e. '\' is taken as an
960 960 escape character):
961 961
962 962 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
963 963 '{"c:\\tmp"}'
964 964 """
965 965 expander = {
966 966 b'H': b'{node}',
967 967 b'R': b'{rev}',
968 968 b'h': b'{node|short}',
969 969 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
970 970 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
971 971 b'%': b'%',
972 972 b'b': b'{reporoot|basename}',
973 973 }
974 974 if total is not None:
975 975 expander[b'N'] = b'{total}'
976 976 if seqno is not None:
977 977 expander[b'n'] = b'{seqno}'
978 978 if total is not None and seqno is not None:
979 979 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
980 980 if pathname is not None:
981 981 expander[b's'] = b'{pathname|basename}'
982 982 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
983 983 expander[b'p'] = b'{pathname}'
984 984
985 985 newname = []
986 986 for typ, start, end in templater.scantemplate(pat, raw=True):
987 987 if typ != b'string':
988 988 newname.append(pat[start:end])
989 989 continue
990 990 i = start
991 991 while i < end:
992 992 n = pat.find(b'%', i, end)
993 993 if n < 0:
994 994 newname.append(stringutil.escapestr(pat[i:end]))
995 995 break
996 996 newname.append(stringutil.escapestr(pat[i:n]))
997 997 if n + 2 > end:
998 998 raise error.Abort(_("incomplete format spec in output "
999 999 "filename"))
1000 1000 c = pat[n + 1:n + 2]
1001 1001 i = n + 2
1002 1002 try:
1003 1003 newname.append(expander[c])
1004 1004 except KeyError:
1005 1005 raise error.Abort(_("invalid format spec '%%%s' in output "
1006 1006 "filename") % c)
1007 1007 return ''.join(newname)
1008 1008
1009 1009 def makefilename(ctx, pat, **props):
1010 1010 if not pat:
1011 1011 return pat
1012 1012 tmpl = _buildfntemplate(pat, **props)
1013 1013 # BUG: alias expansion shouldn't be made against template fragments
1014 1014 # rewritten from %-format strings, but we have no easy way to partially
1015 1015 # disable the expansion.
1016 1016 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1017 1017
1018 1018 def isstdiofilename(pat):
1019 1019 """True if the given pat looks like a filename denoting stdin/stdout"""
1020 1020 return not pat or pat == '-'
1021 1021
1022 1022 class _unclosablefile(object):
1023 1023 def __init__(self, fp):
1024 1024 self._fp = fp
1025 1025
1026 1026 def close(self):
1027 1027 pass
1028 1028
1029 1029 def __iter__(self):
1030 1030 return iter(self._fp)
1031 1031
1032 1032 def __getattr__(self, attr):
1033 1033 return getattr(self._fp, attr)
1034 1034
1035 1035 def __enter__(self):
1036 1036 return self
1037 1037
1038 1038 def __exit__(self, exc_type, exc_value, exc_tb):
1039 1039 pass
1040 1040
1041 1041 def makefileobj(ctx, pat, mode='wb', **props):
1042 1042 writable = mode not in ('r', 'rb')
1043 1043
1044 1044 if isstdiofilename(pat):
1045 1045 repo = ctx.repo()
1046 1046 if writable:
1047 1047 fp = repo.ui.fout
1048 1048 else:
1049 1049 fp = repo.ui.fin
1050 1050 return _unclosablefile(fp)
1051 1051 fn = makefilename(ctx, pat, **props)
1052 1052 return open(fn, mode)
1053 1053
1054 1054 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1055 1055 """opens the changelog, manifest, a filelog or a given revlog"""
1056 1056 cl = opts['changelog']
1057 1057 mf = opts['manifest']
1058 1058 dir = opts['dir']
1059 1059 msg = None
1060 1060 if cl and mf:
1061 1061 msg = _('cannot specify --changelog and --manifest at the same time')
1062 1062 elif cl and dir:
1063 1063 msg = _('cannot specify --changelog and --dir at the same time')
1064 1064 elif cl or mf or dir:
1065 1065 if file_:
1066 1066 msg = _('cannot specify filename with --changelog or --manifest')
1067 1067 elif not repo:
1068 1068 msg = _('cannot specify --changelog or --manifest or --dir '
1069 1069 'without a repository')
1070 1070 if msg:
1071 1071 raise error.Abort(msg)
1072 1072
1073 1073 r = None
1074 1074 if repo:
1075 1075 if cl:
1076 1076 r = repo.unfiltered().changelog
1077 1077 elif dir:
1078 1078 if 'treemanifest' not in repo.requirements:
1079 1079 raise error.Abort(_("--dir can only be used on repos with "
1080 1080 "treemanifest enabled"))
1081 1081 if not dir.endswith('/'):
1082 1082 dir = dir + '/'
1083 1083 dirlog = repo.manifestlog.getstorage(dir)
1084 1084 if len(dirlog):
1085 1085 r = dirlog
1086 1086 elif mf:
1087 1087 r = repo.manifestlog.getstorage(b'')
1088 1088 elif file_:
1089 1089 filelog = repo.file(file_)
1090 1090 if len(filelog):
1091 1091 r = filelog
1092 1092
1093 1093 # Not all storage may be revlogs. If requested, try to return an actual
1094 1094 # revlog instance.
1095 1095 if returnrevlog:
1096 1096 if isinstance(r, revlog.revlog):
1097 1097 pass
1098 1098 elif util.safehasattr(r, '_revlog'):
1099 1099 r = r._revlog
1100 1100 elif r is not None:
1101 1101 raise error.Abort(_('%r does not appear to be a revlog') % r)
1102 1102
1103 1103 if not r:
1104 1104 if not returnrevlog:
1105 1105 raise error.Abort(_('cannot give path to non-revlog'))
1106 1106
1107 1107 if not file_:
1108 1108 raise error.CommandError(cmd, _('invalid arguments'))
1109 1109 if not os.path.isfile(file_):
1110 1110 raise error.Abort(_("revlog '%s' not found") % file_)
1111 1111 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False),
1112 1112 file_[:-2] + ".i")
1113 1113 return r
1114 1114
1115 1115 def openrevlog(repo, cmd, file_, opts):
1116 1116 """Obtain a revlog backing storage of an item.
1117 1117
1118 1118 This is similar to ``openstorage()`` except it always returns a revlog.
1119 1119
1120 1120 In most cases, a caller cares about the main storage object - not the
1121 1121 revlog backing it. Therefore, this function should only be used by code
1122 1122 that needs to examine low-level revlog implementation details. e.g. debug
1123 1123 commands.
1124 1124 """
1125 1125 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1126 1126
1127 1127 def copy(ui, repo, pats, opts, rename=False):
1128 1128 # called with the repo lock held
1129 1129 #
1130 1130 # hgsep => pathname that uses "/" to separate directories
1131 1131 # ossep => pathname that uses os.sep to separate directories
1132 1132 cwd = repo.getcwd()
1133 1133 targets = {}
1134 1134 after = opts.get("after")
1135 1135 dryrun = opts.get("dry_run")
1136 1136 wctx = repo[None]
1137 1137
1138 1138 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1139 1139 def walkpat(pat):
1140 1140 srcs = []
1141 1141 if after:
1142 1142 badstates = '?'
1143 1143 else:
1144 1144 badstates = '?r'
1145 1145 m = scmutil.match(wctx, [pat], opts, globbed=True)
1146 1146 for abs in wctx.walk(m):
1147 1147 state = repo.dirstate[abs]
1148 1148 rel = uipathfn(abs)
1149 1149 exact = m.exact(abs)
1150 1150 if state in badstates:
1151 1151 if exact and state == '?':
1152 1152 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1153 1153 if exact and state == 'r':
1154 1154 ui.warn(_('%s: not copying - file has been marked for'
1155 1155 ' remove\n') % rel)
1156 1156 continue
1157 1157 # abs: hgsep
1158 1158 # rel: ossep
1159 1159 srcs.append((abs, rel, exact))
1160 1160 return srcs
1161 1161
1162 1162 # abssrc: hgsep
1163 1163 # relsrc: ossep
1164 1164 # otarget: ossep
1165 1165 def copyfile(abssrc, relsrc, otarget, exact):
1166 1166 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1167 1167 if '/' in abstarget:
1168 1168 # We cannot normalize abstarget itself, this would prevent
1169 1169 # case only renames, like a => A.
1170 1170 abspath, absname = abstarget.rsplit('/', 1)
1171 1171 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1172 1172 reltarget = repo.pathto(abstarget, cwd)
1173 1173 target = repo.wjoin(abstarget)
1174 1174 src = repo.wjoin(abssrc)
1175 1175 state = repo.dirstate[abstarget]
1176 1176
1177 1177 scmutil.checkportable(ui, abstarget)
1178 1178
1179 1179 # check for collisions
1180 1180 prevsrc = targets.get(abstarget)
1181 1181 if prevsrc is not None:
1182 1182 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1183 1183 (reltarget, repo.pathto(abssrc, cwd),
1184 1184 repo.pathto(prevsrc, cwd)))
1185 1185 return True # report a failure
1186 1186
1187 1187 # check for overwrites
1188 1188 exists = os.path.lexists(target)
1189 1189 samefile = False
1190 1190 if exists and abssrc != abstarget:
1191 1191 if (repo.dirstate.normalize(abssrc) ==
1192 1192 repo.dirstate.normalize(abstarget)):
1193 1193 if not rename:
1194 1194 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1195 1195 return True # report a failure
1196 1196 exists = False
1197 1197 samefile = True
1198 1198
1199 1199 if not after and exists or after and state in 'mn':
1200 1200 if not opts['force']:
1201 1201 if state in 'mn':
1202 1202 msg = _('%s: not overwriting - file already committed\n')
1203 1203 if after:
1204 1204 flags = '--after --force'
1205 1205 else:
1206 1206 flags = '--force'
1207 1207 if rename:
1208 1208 hint = _("('hg rename %s' to replace the file by "
1209 1209 'recording a rename)\n') % flags
1210 1210 else:
1211 1211 hint = _("('hg copy %s' to replace the file by "
1212 1212 'recording a copy)\n') % flags
1213 1213 else:
1214 1214 msg = _('%s: not overwriting - file exists\n')
1215 1215 if rename:
1216 1216 hint = _("('hg rename --after' to record the rename)\n")
1217 1217 else:
1218 1218 hint = _("('hg copy --after' to record the copy)\n")
1219 1219 ui.warn(msg % reltarget)
1220 1220 ui.warn(hint)
1221 1221 return True # report a failure
1222 1222
1223 1223 if after:
1224 1224 if not exists:
1225 1225 if rename:
1226 1226 ui.warn(_('%s: not recording move - %s does not exist\n') %
1227 1227 (relsrc, reltarget))
1228 1228 else:
1229 1229 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1230 1230 (relsrc, reltarget))
1231 1231 return True # report a failure
1232 1232 elif not dryrun:
1233 1233 try:
1234 1234 if exists:
1235 1235 os.unlink(target)
1236 1236 targetdir = os.path.dirname(target) or '.'
1237 1237 if not os.path.isdir(targetdir):
1238 1238 os.makedirs(targetdir)
1239 1239 if samefile:
1240 1240 tmp = target + "~hgrename"
1241 1241 os.rename(src, tmp)
1242 1242 os.rename(tmp, target)
1243 1243 else:
1244 1244 # Preserve stat info on renames, not on copies; this matches
1245 1245 # Linux CLI behavior.
1246 1246 util.copyfile(src, target, copystat=rename)
1247 1247 srcexists = True
1248 1248 except IOError as inst:
1249 1249 if inst.errno == errno.ENOENT:
1250 1250 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1251 1251 srcexists = False
1252 1252 else:
1253 1253 ui.warn(_('%s: cannot copy - %s\n') %
1254 1254 (relsrc, encoding.strtolocal(inst.strerror)))
1255 1255 return True # report a failure
1256 1256
1257 1257 if ui.verbose or not exact:
1258 1258 if rename:
1259 1259 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1260 1260 else:
1261 1261 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1262 1262
1263 1263 targets[abstarget] = abssrc
1264 1264
1265 1265 # fix up dirstate
1266 1266 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1267 1267 dryrun=dryrun, cwd=cwd)
1268 1268 if rename and not dryrun:
1269 1269 if not after and srcexists and not samefile:
1270 1270 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
1271 1271 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1272 1272 wctx.forget([abssrc])
1273 1273
1274 1274 # pat: ossep
1275 1275 # dest ossep
1276 1276 # srcs: list of (hgsep, hgsep, ossep, bool)
1277 1277 # return: function that takes hgsep and returns ossep
1278 1278 def targetpathfn(pat, dest, srcs):
1279 1279 if os.path.isdir(pat):
1280 1280 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1281 1281 abspfx = util.localpath(abspfx)
1282 1282 if destdirexists:
1283 1283 striplen = len(os.path.split(abspfx)[0])
1284 1284 else:
1285 1285 striplen = len(abspfx)
1286 1286 if striplen:
1287 1287 striplen += len(pycompat.ossep)
1288 1288 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1289 1289 elif destdirexists:
1290 1290 res = lambda p: os.path.join(dest,
1291 1291 os.path.basename(util.localpath(p)))
1292 1292 else:
1293 1293 res = lambda p: dest
1294 1294 return res
1295 1295
1296 1296 # pat: ossep
1297 1297 # dest ossep
1298 1298 # srcs: list of (hgsep, hgsep, ossep, bool)
1299 1299 # return: function that takes hgsep and returns ossep
1300 1300 def targetpathafterfn(pat, dest, srcs):
1301 1301 if matchmod.patkind(pat):
1302 1302 # a mercurial pattern
1303 1303 res = lambda p: os.path.join(dest,
1304 1304 os.path.basename(util.localpath(p)))
1305 1305 else:
1306 1306 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1307 1307 if len(abspfx) < len(srcs[0][0]):
1308 1308 # A directory. Either the target path contains the last
1309 1309 # component of the source path or it does not.
1310 1310 def evalpath(striplen):
1311 1311 score = 0
1312 1312 for s in srcs:
1313 1313 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1314 1314 if os.path.lexists(t):
1315 1315 score += 1
1316 1316 return score
1317 1317
1318 1318 abspfx = util.localpath(abspfx)
1319 1319 striplen = len(abspfx)
1320 1320 if striplen:
1321 1321 striplen += len(pycompat.ossep)
1322 1322 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1323 1323 score = evalpath(striplen)
1324 1324 striplen1 = len(os.path.split(abspfx)[0])
1325 1325 if striplen1:
1326 1326 striplen1 += len(pycompat.ossep)
1327 1327 if evalpath(striplen1) > score:
1328 1328 striplen = striplen1
1329 1329 res = lambda p: os.path.join(dest,
1330 1330 util.localpath(p)[striplen:])
1331 1331 else:
1332 1332 # a file
1333 1333 if destdirexists:
1334 1334 res = lambda p: os.path.join(dest,
1335 1335 os.path.basename(util.localpath(p)))
1336 1336 else:
1337 1337 res = lambda p: dest
1338 1338 return res
1339 1339
1340 1340 pats = scmutil.expandpats(pats)
1341 1341 if not pats:
1342 1342 raise error.Abort(_('no source or destination specified'))
1343 1343 if len(pats) == 1:
1344 1344 raise error.Abort(_('no destination specified'))
1345 1345 dest = pats.pop()
1346 1346 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1347 1347 if not destdirexists:
1348 1348 if len(pats) > 1 or matchmod.patkind(pats[0]):
1349 1349 raise error.Abort(_('with multiple sources, destination must be an '
1350 1350 'existing directory'))
1351 1351 if util.endswithsep(dest):
1352 1352 raise error.Abort(_('destination %s is not a directory') % dest)
1353 1353
1354 1354 tfn = targetpathfn
1355 1355 if after:
1356 1356 tfn = targetpathafterfn
1357 1357 copylist = []
1358 1358 for pat in pats:
1359 1359 srcs = walkpat(pat)
1360 1360 if not srcs:
1361 1361 continue
1362 1362 copylist.append((tfn(pat, dest, srcs), srcs))
1363 1363 if not copylist:
1364 1364 raise error.Abort(_('no files to copy'))
1365 1365
1366 1366 errors = 0
1367 1367 for targetpath, srcs in copylist:
1368 1368 for abssrc, relsrc, exact in srcs:
1369 1369 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1370 1370 errors += 1
1371 1371
1372 1372 return errors != 0
1373 1373
1374 1374 ## facility to let extension process additional data into an import patch
1375 1375 # list of identifier to be executed in order
1376 1376 extrapreimport = [] # run before commit
1377 1377 extrapostimport = [] # run after commit
1378 1378 # mapping from identifier to actual import function
1379 1379 #
1380 1380 # 'preimport' are run before the commit is made and are provided the following
1381 1381 # arguments:
1382 1382 # - repo: the localrepository instance,
1383 1383 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1384 1384 # - extra: the future extra dictionary of the changeset, please mutate it,
1385 1385 # - opts: the import options.
1386 1386 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1387 1387 # mutation of in memory commit and more. Feel free to rework the code to get
1388 1388 # there.
1389 1389 extrapreimportmap = {}
1390 1390 # 'postimport' are run after the commit is made and are provided the following
1391 1391 # argument:
1392 1392 # - ctx: the changectx created by import.
1393 1393 extrapostimportmap = {}
1394 1394
1395 1395 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1396 1396 """Utility function used by commands.import to import a single patch
1397 1397
1398 1398 This function is explicitly defined here to help the evolve extension to
1399 1399 wrap this part of the import logic.
1400 1400
1401 1401 The API is currently a bit ugly because it a simple code translation from
1402 1402 the import command. Feel free to make it better.
1403 1403
1404 1404 :patchdata: a dictionary containing parsed patch data (such as from
1405 1405 ``patch.extract()``)
1406 1406 :parents: nodes that will be parent of the created commit
1407 1407 :opts: the full dict of option passed to the import command
1408 1408 :msgs: list to save commit message to.
1409 1409 (used in case we need to save it when failing)
1410 1410 :updatefunc: a function that update a repo to a given node
1411 1411 updatefunc(<repo>, <node>)
1412 1412 """
1413 1413 # avoid cycle context -> subrepo -> cmdutil
1414 1414 from . import context
1415 1415
1416 1416 tmpname = patchdata.get('filename')
1417 1417 message = patchdata.get('message')
1418 1418 user = opts.get('user') or patchdata.get('user')
1419 1419 date = opts.get('date') or patchdata.get('date')
1420 1420 branch = patchdata.get('branch')
1421 1421 nodeid = patchdata.get('nodeid')
1422 1422 p1 = patchdata.get('p1')
1423 1423 p2 = patchdata.get('p2')
1424 1424
1425 1425 nocommit = opts.get('no_commit')
1426 1426 importbranch = opts.get('import_branch')
1427 1427 update = not opts.get('bypass')
1428 1428 strip = opts["strip"]
1429 1429 prefix = opts["prefix"]
1430 1430 sim = float(opts.get('similarity') or 0)
1431 1431
1432 1432 if not tmpname:
1433 1433 return None, None, False
1434 1434
1435 1435 rejects = False
1436 1436
1437 1437 cmdline_message = logmessage(ui, opts)
1438 1438 if cmdline_message:
1439 1439 # pickup the cmdline msg
1440 1440 message = cmdline_message
1441 1441 elif message:
1442 1442 # pickup the patch msg
1443 1443 message = message.strip()
1444 1444 else:
1445 1445 # launch the editor
1446 1446 message = None
1447 1447 ui.debug('message:\n%s\n' % (message or ''))
1448 1448
1449 1449 if len(parents) == 1:
1450 1450 parents.append(repo[nullid])
1451 1451 if opts.get('exact'):
1452 1452 if not nodeid or not p1:
1453 1453 raise error.Abort(_('not a Mercurial patch'))
1454 1454 p1 = repo[p1]
1455 1455 p2 = repo[p2 or nullid]
1456 1456 elif p2:
1457 1457 try:
1458 1458 p1 = repo[p1]
1459 1459 p2 = repo[p2]
1460 1460 # Without any options, consider p2 only if the
1461 1461 # patch is being applied on top of the recorded
1462 1462 # first parent.
1463 1463 if p1 != parents[0]:
1464 1464 p1 = parents[0]
1465 1465 p2 = repo[nullid]
1466 1466 except error.RepoError:
1467 1467 p1, p2 = parents
1468 1468 if p2.node() == nullid:
1469 1469 ui.warn(_("warning: import the patch as a normal revision\n"
1470 1470 "(use --exact to import the patch as a merge)\n"))
1471 1471 else:
1472 1472 p1, p2 = parents
1473 1473
1474 1474 n = None
1475 1475 if update:
1476 1476 if p1 != parents[0]:
1477 1477 updatefunc(repo, p1.node())
1478 1478 if p2 != parents[1]:
1479 1479 repo.setparents(p1.node(), p2.node())
1480 1480
1481 1481 if opts.get('exact') or importbranch:
1482 1482 repo.dirstate.setbranch(branch or 'default')
1483 1483
1484 1484 partial = opts.get('partial', False)
1485 1485 files = set()
1486 1486 try:
1487 1487 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1488 1488 files=files, eolmode=None, similarity=sim / 100.0)
1489 1489 except error.PatchError as e:
1490 1490 if not partial:
1491 1491 raise error.Abort(pycompat.bytestr(e))
1492 1492 if partial:
1493 1493 rejects = True
1494 1494
1495 1495 files = list(files)
1496 1496 if nocommit:
1497 1497 if message:
1498 1498 msgs.append(message)
1499 1499 else:
1500 1500 if opts.get('exact') or p2:
1501 1501 # If you got here, you either use --force and know what
1502 1502 # you are doing or used --exact or a merge patch while
1503 1503 # being updated to its first parent.
1504 1504 m = None
1505 1505 else:
1506 1506 m = scmutil.matchfiles(repo, files or [])
1507 1507 editform = mergeeditform(repo[None], 'import.normal')
1508 1508 if opts.get('exact'):
1509 1509 editor = None
1510 1510 else:
1511 1511 editor = getcommiteditor(editform=editform,
1512 1512 **pycompat.strkwargs(opts))
1513 1513 extra = {}
1514 1514 for idfunc in extrapreimport:
1515 1515 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1516 1516 overrides = {}
1517 1517 if partial:
1518 1518 overrides[('ui', 'allowemptycommit')] = True
1519 1519 with repo.ui.configoverride(overrides, 'import'):
1520 1520 n = repo.commit(message, user,
1521 1521 date, match=m,
1522 1522 editor=editor, extra=extra)
1523 1523 for idfunc in extrapostimport:
1524 1524 extrapostimportmap[idfunc](repo[n])
1525 1525 else:
1526 1526 if opts.get('exact') or importbranch:
1527 1527 branch = branch or 'default'
1528 1528 else:
1529 1529 branch = p1.branch()
1530 1530 store = patch.filestore()
1531 1531 try:
1532 1532 files = set()
1533 1533 try:
1534 1534 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1535 1535 files, eolmode=None)
1536 1536 except error.PatchError as e:
1537 1537 raise error.Abort(stringutil.forcebytestr(e))
1538 1538 if opts.get('exact'):
1539 1539 editor = None
1540 1540 else:
1541 1541 editor = getcommiteditor(editform='import.bypass')
1542 1542 memctx = context.memctx(repo, (p1.node(), p2.node()),
1543 1543 message,
1544 1544 files=files,
1545 1545 filectxfn=store,
1546 1546 user=user,
1547 1547 date=date,
1548 1548 branch=branch,
1549 1549 editor=editor)
1550 1550 n = memctx.commit()
1551 1551 finally:
1552 1552 store.close()
1553 1553 if opts.get('exact') and nocommit:
1554 1554 # --exact with --no-commit is still useful in that it does merge
1555 1555 # and branch bits
1556 1556 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1557 1557 elif opts.get('exact') and (not n or hex(n) != nodeid):
1558 1558 raise error.Abort(_('patch is damaged or loses information'))
1559 1559 msg = _('applied to working directory')
1560 1560 if n:
1561 1561 # i18n: refers to a short changeset id
1562 1562 msg = _('created %s') % short(n)
1563 1563 return msg, n, rejects
1564 1564
1565 1565 # facility to let extensions include additional data in an exported patch
1566 1566 # list of identifiers to be executed in order
1567 1567 extraexport = []
1568 1568 # mapping from identifier to actual export function
1569 1569 # function as to return a string to be added to the header or None
1570 1570 # it is given two arguments (sequencenumber, changectx)
1571 1571 extraexportmap = {}
1572 1572
1573 1573 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1574 1574 node = scmutil.binnode(ctx)
1575 1575 parents = [p.node() for p in ctx.parents() if p]
1576 1576 branch = ctx.branch()
1577 1577 if switch_parent:
1578 1578 parents.reverse()
1579 1579
1580 1580 if parents:
1581 1581 prev = parents[0]
1582 1582 else:
1583 1583 prev = nullid
1584 1584
1585 1585 fm.context(ctx=ctx)
1586 1586 fm.plain('# HG changeset patch\n')
1587 1587 fm.write('user', '# User %s\n', ctx.user())
1588 1588 fm.plain('# Date %d %d\n' % ctx.date())
1589 1589 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1590 1590 fm.condwrite(branch and branch != 'default',
1591 1591 'branch', '# Branch %s\n', branch)
1592 1592 fm.write('node', '# Node ID %s\n', hex(node))
1593 1593 fm.plain('# Parent %s\n' % hex(prev))
1594 1594 if len(parents) > 1:
1595 1595 fm.plain('# Parent %s\n' % hex(parents[1]))
1596 1596 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1597 1597
1598 1598 # TODO: redesign extraexportmap function to support formatter
1599 1599 for headerid in extraexport:
1600 1600 header = extraexportmap[headerid](seqno, ctx)
1601 1601 if header is not None:
1602 1602 fm.plain('# %s\n' % header)
1603 1603
1604 1604 fm.write('desc', '%s\n', ctx.description().rstrip())
1605 1605 fm.plain('\n')
1606 1606
1607 1607 if fm.isplain():
1608 1608 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1609 1609 for chunk, label in chunkiter:
1610 1610 fm.plain(chunk, label=label)
1611 1611 else:
1612 1612 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1613 1613 # TODO: make it structured?
1614 1614 fm.data(diff=b''.join(chunkiter))
1615 1615
1616 1616 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1617 1617 """Export changesets to stdout or a single file"""
1618 1618 for seqno, rev in enumerate(revs, 1):
1619 1619 ctx = repo[rev]
1620 1620 if not dest.startswith('<'):
1621 1621 repo.ui.note("%s\n" % dest)
1622 1622 fm.startitem()
1623 1623 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1624 1624
1625 1625 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1626 1626 match):
1627 1627 """Export changesets to possibly multiple files"""
1628 1628 total = len(revs)
1629 1629 revwidth = max(len(str(rev)) for rev in revs)
1630 1630 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1631 1631
1632 1632 for seqno, rev in enumerate(revs, 1):
1633 1633 ctx = repo[rev]
1634 1634 dest = makefilename(ctx, fntemplate,
1635 1635 total=total, seqno=seqno, revwidth=revwidth)
1636 1636 filemap.setdefault(dest, []).append((seqno, rev))
1637 1637
1638 1638 for dest in filemap:
1639 1639 with formatter.maybereopen(basefm, dest) as fm:
1640 1640 repo.ui.note("%s\n" % dest)
1641 1641 for seqno, rev in filemap[dest]:
1642 1642 fm.startitem()
1643 1643 ctx = repo[rev]
1644 1644 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1645 1645 diffopts)
1646 1646
1647 1647 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1648 1648 opts=None, match=None):
1649 1649 '''export changesets as hg patches
1650 1650
1651 1651 Args:
1652 1652 repo: The repository from which we're exporting revisions.
1653 1653 revs: A list of revisions to export as revision numbers.
1654 1654 basefm: A formatter to which patches should be written.
1655 1655 fntemplate: An optional string to use for generating patch file names.
1656 1656 switch_parent: If True, show diffs against second parent when not nullid.
1657 1657 Default is false, which always shows diff against p1.
1658 1658 opts: diff options to use for generating the patch.
1659 1659 match: If specified, only export changes to files matching this matcher.
1660 1660
1661 1661 Returns:
1662 1662 Nothing.
1663 1663
1664 1664 Side Effect:
1665 1665 "HG Changeset Patch" data is emitted to one of the following
1666 1666 destinations:
1667 1667 fntemplate specified: Each rev is written to a unique file named using
1668 1668 the given template.
1669 1669 Otherwise: All revs will be written to basefm.
1670 1670 '''
1671 1671 scmutil.prefetchfiles(repo, revs, match)
1672 1672
1673 1673 if not fntemplate:
1674 1674 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1675 1675 else:
1676 1676 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1677 1677 match)
1678 1678
1679 1679 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1680 1680 """Export changesets to the given file stream"""
1681 1681 scmutil.prefetchfiles(repo, revs, match)
1682 1682
1683 1683 dest = getattr(fp, 'name', '<unnamed>')
1684 1684 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1685 1685 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1686 1686
1687 1687 def showmarker(fm, marker, index=None):
1688 1688 """utility function to display obsolescence marker in a readable way
1689 1689
1690 1690 To be used by debug function."""
1691 1691 if index is not None:
1692 1692 fm.write('index', '%i ', index)
1693 1693 fm.write('prednode', '%s ', hex(marker.prednode()))
1694 1694 succs = marker.succnodes()
1695 1695 fm.condwrite(succs, 'succnodes', '%s ',
1696 1696 fm.formatlist(map(hex, succs), name='node'))
1697 1697 fm.write('flag', '%X ', marker.flags())
1698 1698 parents = marker.parentnodes()
1699 1699 if parents is not None:
1700 1700 fm.write('parentnodes', '{%s} ',
1701 1701 fm.formatlist(map(hex, parents), name='node', sep=', '))
1702 1702 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1703 1703 meta = marker.metadata().copy()
1704 1704 meta.pop('date', None)
1705 1705 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
1706 1706 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1707 1707 fm.plain('\n')
1708 1708
1709 1709 def finddate(ui, repo, date):
1710 1710 """Find the tipmost changeset that matches the given date spec"""
1711 1711
1712 1712 df = dateutil.matchdate(date)
1713 1713 m = scmutil.matchall(repo)
1714 1714 results = {}
1715 1715
1716 1716 def prep(ctx, fns):
1717 1717 d = ctx.date()
1718 1718 if df(d[0]):
1719 1719 results[ctx.rev()] = d
1720 1720
1721 1721 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1722 1722 rev = ctx.rev()
1723 1723 if rev in results:
1724 1724 ui.status(_("found revision %s from %s\n") %
1725 1725 (rev, dateutil.datestr(results[rev])))
1726 1726 return '%d' % rev
1727 1727
1728 1728 raise error.Abort(_("revision matching date not found"))
1729 1729
1730 1730 def increasingwindows(windowsize=8, sizelimit=512):
1731 1731 while True:
1732 1732 yield windowsize
1733 1733 if windowsize < sizelimit:
1734 1734 windowsize *= 2
1735 1735
1736 1736 def _walkrevs(repo, opts):
1737 1737 # Default --rev value depends on --follow but --follow behavior
1738 1738 # depends on revisions resolved from --rev...
1739 1739 follow = opts.get('follow') or opts.get('follow_first')
1740 1740 if opts.get('rev'):
1741 1741 revs = scmutil.revrange(repo, opts['rev'])
1742 1742 elif follow and repo.dirstate.p1() == nullid:
1743 1743 revs = smartset.baseset()
1744 1744 elif follow:
1745 1745 revs = repo.revs('reverse(:.)')
1746 1746 else:
1747 1747 revs = smartset.spanset(repo)
1748 1748 revs.reverse()
1749 1749 return revs
1750 1750
1751 1751 class FileWalkError(Exception):
1752 1752 pass
1753 1753
1754 1754 def walkfilerevs(repo, match, follow, revs, fncache):
1755 1755 '''Walks the file history for the matched files.
1756 1756
1757 1757 Returns the changeset revs that are involved in the file history.
1758 1758
1759 1759 Throws FileWalkError if the file history can't be walked using
1760 1760 filelogs alone.
1761 1761 '''
1762 1762 wanted = set()
1763 1763 copies = []
1764 1764 minrev, maxrev = min(revs), max(revs)
1765 1765 def filerevgen(filelog, last):
1766 1766 """
1767 1767 Only files, no patterns. Check the history of each file.
1768 1768
1769 1769 Examines filelog entries within minrev, maxrev linkrev range
1770 1770 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1771 1771 tuples in backwards order
1772 1772 """
1773 1773 cl_count = len(repo)
1774 1774 revs = []
1775 1775 for j in pycompat.xrange(0, last + 1):
1776 1776 linkrev = filelog.linkrev(j)
1777 1777 if linkrev < minrev:
1778 1778 continue
1779 1779 # only yield rev for which we have the changelog, it can
1780 1780 # happen while doing "hg log" during a pull or commit
1781 1781 if linkrev >= cl_count:
1782 1782 break
1783 1783
1784 1784 parentlinkrevs = []
1785 1785 for p in filelog.parentrevs(j):
1786 1786 if p != nullrev:
1787 1787 parentlinkrevs.append(filelog.linkrev(p))
1788 1788 n = filelog.node(j)
1789 1789 revs.append((linkrev, parentlinkrevs,
1790 1790 follow and filelog.renamed(n)))
1791 1791
1792 1792 return reversed(revs)
1793 1793 def iterfiles():
1794 1794 pctx = repo['.']
1795 1795 for filename in match.files():
1796 1796 if follow:
1797 1797 if filename not in pctx:
1798 1798 raise error.Abort(_('cannot follow file not in parent '
1799 1799 'revision: "%s"') % filename)
1800 1800 yield filename, pctx[filename].filenode()
1801 1801 else:
1802 1802 yield filename, None
1803 1803 for filename_node in copies:
1804 1804 yield filename_node
1805 1805
1806 1806 for file_, node in iterfiles():
1807 1807 filelog = repo.file(file_)
1808 1808 if not len(filelog):
1809 1809 if node is None:
1810 1810 # A zero count may be a directory or deleted file, so
1811 1811 # try to find matching entries on the slow path.
1812 1812 if follow:
1813 1813 raise error.Abort(
1814 1814 _('cannot follow nonexistent file: "%s"') % file_)
1815 1815 raise FileWalkError("Cannot walk via filelog")
1816 1816 else:
1817 1817 continue
1818 1818
1819 1819 if node is None:
1820 1820 last = len(filelog) - 1
1821 1821 else:
1822 1822 last = filelog.rev(node)
1823 1823
1824 1824 # keep track of all ancestors of the file
1825 1825 ancestors = {filelog.linkrev(last)}
1826 1826
1827 1827 # iterate from latest to oldest revision
1828 1828 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1829 1829 if not follow:
1830 1830 if rev > maxrev:
1831 1831 continue
1832 1832 else:
1833 1833 # Note that last might not be the first interesting
1834 1834 # rev to us:
1835 1835 # if the file has been changed after maxrev, we'll
1836 1836 # have linkrev(last) > maxrev, and we still need
1837 1837 # to explore the file graph
1838 1838 if rev not in ancestors:
1839 1839 continue
1840 1840 # XXX insert 1327 fix here
1841 1841 if flparentlinkrevs:
1842 1842 ancestors.update(flparentlinkrevs)
1843 1843
1844 1844 fncache.setdefault(rev, []).append(file_)
1845 1845 wanted.add(rev)
1846 1846 if copied:
1847 1847 copies.append(copied)
1848 1848
1849 1849 return wanted
1850 1850
1851 1851 class _followfilter(object):
1852 1852 def __init__(self, repo, onlyfirst=False):
1853 1853 self.repo = repo
1854 1854 self.startrev = nullrev
1855 1855 self.roots = set()
1856 1856 self.onlyfirst = onlyfirst
1857 1857
1858 1858 def match(self, rev):
1859 1859 def realparents(rev):
1860 1860 if self.onlyfirst:
1861 1861 return self.repo.changelog.parentrevs(rev)[0:1]
1862 1862 else:
1863 1863 return filter(lambda x: x != nullrev,
1864 1864 self.repo.changelog.parentrevs(rev))
1865 1865
1866 1866 if self.startrev == nullrev:
1867 1867 self.startrev = rev
1868 1868 return True
1869 1869
1870 1870 if rev > self.startrev:
1871 1871 # forward: all descendants
1872 1872 if not self.roots:
1873 1873 self.roots.add(self.startrev)
1874 1874 for parent in realparents(rev):
1875 1875 if parent in self.roots:
1876 1876 self.roots.add(rev)
1877 1877 return True
1878 1878 else:
1879 1879 # backwards: all parents
1880 1880 if not self.roots:
1881 1881 self.roots.update(realparents(self.startrev))
1882 1882 if rev in self.roots:
1883 1883 self.roots.remove(rev)
1884 1884 self.roots.update(realparents(rev))
1885 1885 return True
1886 1886
1887 1887 return False
1888 1888
1889 1889 def walkchangerevs(repo, match, opts, prepare):
1890 1890 '''Iterate over files and the revs in which they changed.
1891 1891
1892 1892 Callers most commonly need to iterate backwards over the history
1893 1893 in which they are interested. Doing so has awful (quadratic-looking)
1894 1894 performance, so we use iterators in a "windowed" way.
1895 1895
1896 1896 We walk a window of revisions in the desired order. Within the
1897 1897 window, we first walk forwards to gather data, then in the desired
1898 1898 order (usually backwards) to display it.
1899 1899
1900 1900 This function returns an iterator yielding contexts. Before
1901 1901 yielding each context, the iterator will first call the prepare
1902 1902 function on each context in the window in forward order.'''
1903 1903
1904 1904 allfiles = opts.get('all_files')
1905 1905 follow = opts.get('follow') or opts.get('follow_first')
1906 1906 revs = _walkrevs(repo, opts)
1907 1907 if not revs:
1908 1908 return []
1909 1909 wanted = set()
1910 1910 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1911 1911 fncache = {}
1912 1912 change = repo.__getitem__
1913 1913
1914 1914 # First step is to fill wanted, the set of revisions that we want to yield.
1915 1915 # When it does not induce extra cost, we also fill fncache for revisions in
1916 1916 # wanted: a cache of filenames that were changed (ctx.files()) and that
1917 1917 # match the file filtering conditions.
1918 1918
1919 1919 if match.always() or allfiles:
1920 1920 # No files, no patterns. Display all revs.
1921 1921 wanted = revs
1922 1922 elif not slowpath:
1923 1923 # We only have to read through the filelog to find wanted revisions
1924 1924
1925 1925 try:
1926 1926 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1927 1927 except FileWalkError:
1928 1928 slowpath = True
1929 1929
1930 1930 # We decided to fall back to the slowpath because at least one
1931 1931 # of the paths was not a file. Check to see if at least one of them
1932 1932 # existed in history, otherwise simply return
1933 1933 for path in match.files():
1934 1934 if path == '.' or path in repo.store:
1935 1935 break
1936 1936 else:
1937 1937 return []
1938 1938
1939 1939 if slowpath:
1940 1940 # We have to read the changelog to match filenames against
1941 1941 # changed files
1942 1942
1943 1943 if follow:
1944 1944 raise error.Abort(_('can only follow copies/renames for explicit '
1945 1945 'filenames'))
1946 1946
1947 1947 # The slow path checks files modified in every changeset.
1948 1948 # This is really slow on large repos, so compute the set lazily.
1949 1949 class lazywantedset(object):
1950 1950 def __init__(self):
1951 1951 self.set = set()
1952 1952 self.revs = set(revs)
1953 1953
1954 1954 # No need to worry about locality here because it will be accessed
1955 1955 # in the same order as the increasing window below.
1956 1956 def __contains__(self, value):
1957 1957 if value in self.set:
1958 1958 return True
1959 1959 elif not value in self.revs:
1960 1960 return False
1961 1961 else:
1962 1962 self.revs.discard(value)
1963 1963 ctx = change(value)
1964 1964 if allfiles:
1965 1965 matches = list(ctx.manifest().walk(match))
1966 1966 else:
1967 1967 matches = [f for f in ctx.files() if match(f)]
1968 1968 if matches:
1969 1969 fncache[value] = matches
1970 1970 self.set.add(value)
1971 1971 return True
1972 1972 return False
1973 1973
1974 1974 def discard(self, value):
1975 1975 self.revs.discard(value)
1976 1976 self.set.discard(value)
1977 1977
1978 1978 wanted = lazywantedset()
1979 1979
1980 1980 # it might be worthwhile to do this in the iterator if the rev range
1981 1981 # is descending and the prune args are all within that range
1982 1982 for rev in opts.get('prune', ()):
1983 1983 rev = repo[rev].rev()
1984 1984 ff = _followfilter(repo)
1985 1985 stop = min(revs[0], revs[-1])
1986 1986 for x in pycompat.xrange(rev, stop - 1, -1):
1987 1987 if ff.match(x):
1988 1988 wanted = wanted - [x]
1989 1989
1990 1990 # Now that wanted is correctly initialized, we can iterate over the
1991 1991 # revision range, yielding only revisions in wanted.
1992 1992 def iterate():
1993 1993 if follow and match.always():
1994 1994 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1995 1995 def want(rev):
1996 1996 return ff.match(rev) and rev in wanted
1997 1997 else:
1998 1998 def want(rev):
1999 1999 return rev in wanted
2000 2000
2001 2001 it = iter(revs)
2002 2002 stopiteration = False
2003 2003 for windowsize in increasingwindows():
2004 2004 nrevs = []
2005 2005 for i in pycompat.xrange(windowsize):
2006 2006 rev = next(it, None)
2007 2007 if rev is None:
2008 2008 stopiteration = True
2009 2009 break
2010 2010 elif want(rev):
2011 2011 nrevs.append(rev)
2012 2012 for rev in sorted(nrevs):
2013 2013 fns = fncache.get(rev)
2014 2014 ctx = change(rev)
2015 2015 if not fns:
2016 2016 def fns_generator():
2017 2017 if allfiles:
2018 2018 fiter = iter(ctx)
2019 2019 else:
2020 2020 fiter = ctx.files()
2021 2021 for f in fiter:
2022 2022 if match(f):
2023 2023 yield f
2024 2024 fns = fns_generator()
2025 2025 prepare(ctx, fns)
2026 2026 for rev in nrevs:
2027 2027 yield change(rev)
2028 2028
2029 2029 if stopiteration:
2030 2030 break
2031 2031
2032 2032 return iterate()
2033 2033
2034 2034 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2035 2035 bad = []
2036 2036
2037 2037 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2038 2038 names = []
2039 2039 wctx = repo[None]
2040 2040 cca = None
2041 2041 abort, warn = scmutil.checkportabilityalert(ui)
2042 2042 if abort or warn:
2043 2043 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2044 2044
2045 2045 match = repo.narrowmatch(match, includeexact=True)
2046 2046 badmatch = matchmod.badmatch(match, badfn)
2047 2047 dirstate = repo.dirstate
2048 2048 # We don't want to just call wctx.walk here, since it would return a lot of
2049 2049 # clean files, which we aren't interested in and takes time.
2050 2050 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2051 2051 unknown=True, ignored=False, full=False)):
2052 2052 exact = match.exact(f)
2053 2053 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2054 2054 if cca:
2055 2055 cca(f)
2056 2056 names.append(f)
2057 2057 if ui.verbose or not exact:
2058 2058 ui.status(_('adding %s\n') % uipathfn(f),
2059 2059 label='ui.addremove.added')
2060 2060
2061 2061 for subpath in sorted(wctx.substate):
2062 2062 sub = wctx.sub(subpath)
2063 2063 try:
2064 2064 submatch = matchmod.subdirmatcher(subpath, match)
2065 2065 subprefix = repo.wvfs.reljoin(prefix, subpath)
2066 2066 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2067 2067 if opts.get(r'subrepos'):
2068 2068 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, False,
2069 2069 **opts))
2070 2070 else:
2071 2071 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, True,
2072 2072 **opts))
2073 2073 except error.LookupError:
2074 2074 ui.status(_("skipping missing subrepository: %s\n")
2075 2075 % uipathfn(subpath))
2076 2076
2077 2077 if not opts.get(r'dry_run'):
2078 2078 rejected = wctx.add(names, prefix)
2079 2079 bad.extend(f for f in rejected if f in match.files())
2080 2080 return bad
2081 2081
2082 2082 def addwebdirpath(repo, serverpath, webconf):
2083 2083 webconf[serverpath] = repo.root
2084 2084 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2085 2085
2086 2086 for r in repo.revs('filelog("path:.hgsub")'):
2087 2087 ctx = repo[r]
2088 2088 for subpath in ctx.substate:
2089 2089 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2090 2090
2091 2091 def forget(ui, repo, match, prefix, uipathfn, explicitonly, dryrun,
2092 2092 interactive):
2093 2093 if dryrun and interactive:
2094 2094 raise error.Abort(_("cannot specify both --dry-run and --interactive"))
2095 2095 bad = []
2096 2096 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2097 2097 wctx = repo[None]
2098 2098 forgot = []
2099 2099
2100 2100 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2101 2101 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2102 2102 if explicitonly:
2103 2103 forget = [f for f in forget if match.exact(f)]
2104 2104
2105 2105 for subpath in sorted(wctx.substate):
2106 2106 sub = wctx.sub(subpath)
2107 2107 submatch = matchmod.subdirmatcher(subpath, match)
2108 2108 subprefix = repo.wvfs.reljoin(prefix, subpath)
2109 2109 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2110 2110 try:
2111 2111 subbad, subforgot = sub.forget(submatch, subprefix, subuipathfn,
2112 2112 dryrun=dryrun,
2113 2113 interactive=interactive)
2114 2114 bad.extend([subpath + '/' + f for f in subbad])
2115 2115 forgot.extend([subpath + '/' + f for f in subforgot])
2116 2116 except error.LookupError:
2117 2117 ui.status(_("skipping missing subrepository: %s\n")
2118 2118 % uipathfn(subpath))
2119 2119
2120 2120 if not explicitonly:
2121 2121 for f in match.files():
2122 2122 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2123 2123 if f not in forgot:
2124 2124 if repo.wvfs.exists(f):
2125 2125 # Don't complain if the exact case match wasn't given.
2126 2126 # But don't do this until after checking 'forgot', so
2127 2127 # that subrepo files aren't normalized, and this op is
2128 2128 # purely from data cached by the status walk above.
2129 2129 if repo.dirstate.normalize(f) in repo.dirstate:
2130 2130 continue
2131 2131 ui.warn(_('not removing %s: '
2132 2132 'file is already untracked\n')
2133 2133 % uipathfn(f))
2134 2134 bad.append(f)
2135 2135
2136 2136 if interactive:
2137 2137 responses = _('[Ynsa?]'
2138 2138 '$$ &Yes, forget this file'
2139 2139 '$$ &No, skip this file'
2140 2140 '$$ &Skip remaining files'
2141 2141 '$$ Include &all remaining files'
2142 2142 '$$ &? (display help)')
2143 2143 for filename in forget[:]:
2144 2144 r = ui.promptchoice(_('forget %s %s') %
2145 2145 (uipathfn(filename), responses))
2146 2146 if r == 4: # ?
2147 2147 while r == 4:
2148 2148 for c, t in ui.extractchoices(responses)[1]:
2149 2149 ui.write('%s - %s\n' % (c, encoding.lower(t)))
2150 2150 r = ui.promptchoice(_('forget %s %s') %
2151 2151 (uipathfn(filename), responses))
2152 2152 if r == 0: # yes
2153 2153 continue
2154 2154 elif r == 1: # no
2155 2155 forget.remove(filename)
2156 2156 elif r == 2: # Skip
2157 2157 fnindex = forget.index(filename)
2158 2158 del forget[fnindex:]
2159 2159 break
2160 2160 elif r == 3: # All
2161 2161 break
2162 2162
2163 2163 for f in forget:
2164 2164 if ui.verbose or not match.exact(f) or interactive:
2165 2165 ui.status(_('removing %s\n') % uipathfn(f),
2166 2166 label='ui.addremove.removed')
2167 2167
2168 2168 if not dryrun:
2169 2169 rejected = wctx.forget(forget, prefix)
2170 2170 bad.extend(f for f in rejected if f in match.files())
2171 2171 forgot.extend(f for f in forget if f not in rejected)
2172 2172 return bad, forgot
2173 2173
2174 def files(ui, ctx, m, fm, fmt, subrepos):
2174 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2175 2175 ret = 1
2176 2176
2177 2177 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint()
2178 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2179 2178 for f in ctx.matches(m):
2180 2179 fm.startitem()
2181 2180 fm.context(ctx=ctx)
2182 2181 if needsfctx:
2183 2182 fc = ctx[f]
2184 2183 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2185 2184 fm.data(path=f)
2186 2185 fm.plain(fmt % uipathfn(f))
2187 2186 ret = 0
2188 2187
2189 2188 for subpath in sorted(ctx.substate):
2190 2189 submatch = matchmod.subdirmatcher(subpath, m)
2190 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2191 2191 if (subrepos or m.exact(subpath) or any(submatch.files())):
2192 2192 sub = ctx.sub(subpath)
2193 2193 try:
2194 2194 recurse = m.exact(subpath) or subrepos
2195 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2195 if sub.printfiles(ui, submatch, subuipathfn, fm, fmt,
2196 recurse) == 0:
2196 2197 ret = 0
2197 2198 except error.LookupError:
2198 2199 ui.status(_("skipping missing subrepository: %s\n")
2199 2200 % uipathfn(subpath))
2200 2201
2201 2202 return ret
2202 2203
2203 2204 def remove(ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun,
2204 2205 warnings=None):
2205 2206 ret = 0
2206 2207 s = repo.status(match=m, clean=True)
2207 2208 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2208 2209
2209 2210 wctx = repo[None]
2210 2211
2211 2212 if warnings is None:
2212 2213 warnings = []
2213 2214 warn = True
2214 2215 else:
2215 2216 warn = False
2216 2217
2217 2218 subs = sorted(wctx.substate)
2218 2219 progress = ui.makeprogress(_('searching'), total=len(subs),
2219 2220 unit=_('subrepos'))
2220 2221 for subpath in subs:
2221 2222 submatch = matchmod.subdirmatcher(subpath, m)
2222 2223 subprefix = repo.wvfs.reljoin(prefix, subpath)
2223 2224 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2224 2225 if subrepos or m.exact(subpath) or any(submatch.files()):
2225 2226 progress.increment()
2226 2227 sub = wctx.sub(subpath)
2227 2228 try:
2228 2229 if sub.removefiles(submatch, subprefix, subuipathfn, after,
2229 2230 force, subrepos, dryrun, warnings):
2230 2231 ret = 1
2231 2232 except error.LookupError:
2232 2233 warnings.append(_("skipping missing subrepository: %s\n")
2233 2234 % uipathfn(subpath))
2234 2235 progress.complete()
2235 2236
2236 2237 # warn about failure to delete explicit files/dirs
2237 2238 deleteddirs = util.dirs(deleted)
2238 2239 files = m.files()
2239 2240 progress = ui.makeprogress(_('deleting'), total=len(files),
2240 2241 unit=_('files'))
2241 2242 for f in files:
2242 2243 def insubrepo():
2243 2244 for subpath in wctx.substate:
2244 2245 if f.startswith(subpath + '/'):
2245 2246 return True
2246 2247 return False
2247 2248
2248 2249 progress.increment()
2249 2250 isdir = f in deleteddirs or wctx.hasdir(f)
2250 2251 if (f in repo.dirstate or isdir or f == '.'
2251 2252 or insubrepo() or f in subs):
2252 2253 continue
2253 2254
2254 2255 if repo.wvfs.exists(f):
2255 2256 if repo.wvfs.isdir(f):
2256 2257 warnings.append(_('not removing %s: no tracked files\n')
2257 2258 % uipathfn(f))
2258 2259 else:
2259 2260 warnings.append(_('not removing %s: file is untracked\n')
2260 2261 % uipathfn(f))
2261 2262 # missing files will generate a warning elsewhere
2262 2263 ret = 1
2263 2264 progress.complete()
2264 2265
2265 2266 if force:
2266 2267 list = modified + deleted + clean + added
2267 2268 elif after:
2268 2269 list = deleted
2269 2270 remaining = modified + added + clean
2270 2271 progress = ui.makeprogress(_('skipping'), total=len(remaining),
2271 2272 unit=_('files'))
2272 2273 for f in remaining:
2273 2274 progress.increment()
2274 2275 if ui.verbose or (f in files):
2275 2276 warnings.append(_('not removing %s: file still exists\n')
2276 2277 % uipathfn(f))
2277 2278 ret = 1
2278 2279 progress.complete()
2279 2280 else:
2280 2281 list = deleted + clean
2281 2282 progress = ui.makeprogress(_('skipping'),
2282 2283 total=(len(modified) + len(added)),
2283 2284 unit=_('files'))
2284 2285 for f in modified:
2285 2286 progress.increment()
2286 2287 warnings.append(_('not removing %s: file is modified (use -f'
2287 2288 ' to force removal)\n') % uipathfn(f))
2288 2289 ret = 1
2289 2290 for f in added:
2290 2291 progress.increment()
2291 2292 warnings.append(_("not removing %s: file has been marked for add"
2292 2293 " (use 'hg forget' to undo add)\n") % uipathfn(f))
2293 2294 ret = 1
2294 2295 progress.complete()
2295 2296
2296 2297 list = sorted(list)
2297 2298 progress = ui.makeprogress(_('deleting'), total=len(list),
2298 2299 unit=_('files'))
2299 2300 for f in list:
2300 2301 if ui.verbose or not m.exact(f):
2301 2302 progress.increment()
2302 2303 ui.status(_('removing %s\n') % uipathfn(f),
2303 2304 label='ui.addremove.removed')
2304 2305 progress.complete()
2305 2306
2306 2307 if not dryrun:
2307 2308 with repo.wlock():
2308 2309 if not after:
2309 2310 for f in list:
2310 2311 if f in added:
2311 2312 continue # we never unlink added files on remove
2312 2313 rmdir = repo.ui.configbool('experimental',
2313 2314 'removeemptydirs')
2314 2315 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2315 2316 repo[None].forget(list)
2316 2317
2317 2318 if warn:
2318 2319 for warning in warnings:
2319 2320 ui.warn(warning)
2320 2321
2321 2322 return ret
2322 2323
2323 2324 def _updatecatformatter(fm, ctx, matcher, path, decode):
2324 2325 """Hook for adding data to the formatter used by ``hg cat``.
2325 2326
2326 2327 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2327 2328 this method first."""
2328 2329 data = ctx[path].data()
2329 2330 if decode:
2330 2331 data = ctx.repo().wwritedata(path, data)
2331 2332 fm.startitem()
2332 2333 fm.context(ctx=ctx)
2333 2334 fm.write('data', '%s', data)
2334 2335 fm.data(path=path)
2335 2336
2336 2337 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2337 2338 err = 1
2338 2339 opts = pycompat.byteskwargs(opts)
2339 2340
2340 2341 def write(path):
2341 2342 filename = None
2342 2343 if fntemplate:
2343 2344 filename = makefilename(ctx, fntemplate,
2344 2345 pathname=os.path.join(prefix, path))
2345 2346 # attempt to create the directory if it does not already exist
2346 2347 try:
2347 2348 os.makedirs(os.path.dirname(filename))
2348 2349 except OSError:
2349 2350 pass
2350 2351 with formatter.maybereopen(basefm, filename) as fm:
2351 2352 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2352 2353
2353 2354 # Automation often uses hg cat on single files, so special case it
2354 2355 # for performance to avoid the cost of parsing the manifest.
2355 2356 if len(matcher.files()) == 1 and not matcher.anypats():
2356 2357 file = matcher.files()[0]
2357 2358 mfl = repo.manifestlog
2358 2359 mfnode = ctx.manifestnode()
2359 2360 try:
2360 2361 if mfnode and mfl[mfnode].find(file)[0]:
2361 2362 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2362 2363 write(file)
2363 2364 return 0
2364 2365 except KeyError:
2365 2366 pass
2366 2367
2367 2368 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2368 2369
2369 2370 for abs in ctx.walk(matcher):
2370 2371 write(abs)
2371 2372 err = 0
2372 2373
2373 2374 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2374 2375 for subpath in sorted(ctx.substate):
2375 2376 sub = ctx.sub(subpath)
2376 2377 try:
2377 2378 submatch = matchmod.subdirmatcher(subpath, matcher)
2378 2379 subprefix = os.path.join(prefix, subpath)
2379 2380 if not sub.cat(submatch, basefm, fntemplate, subprefix,
2380 2381 **pycompat.strkwargs(opts)):
2381 2382 err = 0
2382 2383 except error.RepoLookupError:
2383 2384 ui.status(_("skipping missing subrepository: %s\n") %
2384 2385 uipathfn(subpath))
2385 2386
2386 2387 return err
2387 2388
2388 2389 def commit(ui, repo, commitfunc, pats, opts):
2389 2390 '''commit the specified files or all outstanding changes'''
2390 2391 date = opts.get('date')
2391 2392 if date:
2392 2393 opts['date'] = dateutil.parsedate(date)
2393 2394 message = logmessage(ui, opts)
2394 2395 matcher = scmutil.match(repo[None], pats, opts)
2395 2396
2396 2397 dsguard = None
2397 2398 # extract addremove carefully -- this function can be called from a command
2398 2399 # that doesn't support addremove
2399 2400 if opts.get('addremove'):
2400 2401 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2401 2402 with dsguard or util.nullcontextmanager():
2402 2403 if dsguard:
2403 2404 relative = scmutil.anypats(pats, opts)
2404 2405 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2405 2406 if scmutil.addremove(repo, matcher, "", uipathfn, opts) != 0:
2406 2407 raise error.Abort(
2407 2408 _("failed to mark all new/missing files as added/removed"))
2408 2409
2409 2410 return commitfunc(ui, repo, message, matcher, opts)
2410 2411
2411 2412 def samefile(f, ctx1, ctx2):
2412 2413 if f in ctx1.manifest():
2413 2414 a = ctx1.filectx(f)
2414 2415 if f in ctx2.manifest():
2415 2416 b = ctx2.filectx(f)
2416 2417 return (not a.cmp(b)
2417 2418 and a.flags() == b.flags())
2418 2419 else:
2419 2420 return False
2420 2421 else:
2421 2422 return f not in ctx2.manifest()
2422 2423
2423 2424 def amend(ui, repo, old, extra, pats, opts):
2424 2425 # avoid cycle context -> subrepo -> cmdutil
2425 2426 from . import context
2426 2427
2427 2428 # amend will reuse the existing user if not specified, but the obsolete
2428 2429 # marker creation requires that the current user's name is specified.
2429 2430 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2430 2431 ui.username() # raise exception if username not set
2431 2432
2432 2433 ui.note(_('amending changeset %s\n') % old)
2433 2434 base = old.p1()
2434 2435
2435 2436 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2436 2437 # Participating changesets:
2437 2438 #
2438 2439 # wctx o - workingctx that contains changes from working copy
2439 2440 # | to go into amending commit
2440 2441 # |
2441 2442 # old o - changeset to amend
2442 2443 # |
2443 2444 # base o - first parent of the changeset to amend
2444 2445 wctx = repo[None]
2445 2446
2446 2447 # Copy to avoid mutating input
2447 2448 extra = extra.copy()
2448 2449 # Update extra dict from amended commit (e.g. to preserve graft
2449 2450 # source)
2450 2451 extra.update(old.extra())
2451 2452
2452 2453 # Also update it from the from the wctx
2453 2454 extra.update(wctx.extra())
2454 2455
2455 2456 user = opts.get('user') or old.user()
2456 2457
2457 2458 datemaydiffer = False # date-only change should be ignored?
2458 2459 if opts.get('date') and opts.get('currentdate'):
2459 2460 raise error.Abort(_('--date and --currentdate are mutually '
2460 2461 'exclusive'))
2461 2462 if opts.get('date'):
2462 2463 date = dateutil.parsedate(opts.get('date'))
2463 2464 elif opts.get('currentdate'):
2464 2465 date = dateutil.makedate()
2465 2466 elif (ui.configbool('rewrite', 'update-timestamp')
2466 2467 and opts.get('currentdate') is None):
2467 2468 date = dateutil.makedate()
2468 2469 datemaydiffer = True
2469 2470 else:
2470 2471 date = old.date()
2471 2472
2472 2473 if len(old.parents()) > 1:
2473 2474 # ctx.files() isn't reliable for merges, so fall back to the
2474 2475 # slower repo.status() method
2475 2476 files = set([fn for st in base.status(old)[:3]
2476 2477 for fn in st])
2477 2478 else:
2478 2479 files = set(old.files())
2479 2480
2480 2481 # add/remove the files to the working copy if the "addremove" option
2481 2482 # was specified.
2482 2483 matcher = scmutil.match(wctx, pats, opts)
2483 2484 relative = scmutil.anypats(pats, opts)
2484 2485 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2485 2486 if (opts.get('addremove')
2486 2487 and scmutil.addremove(repo, matcher, "", uipathfn, opts)):
2487 2488 raise error.Abort(
2488 2489 _("failed to mark all new/missing files as added/removed"))
2489 2490
2490 2491 # Check subrepos. This depends on in-place wctx._status update in
2491 2492 # subrepo.precommit(). To minimize the risk of this hack, we do
2492 2493 # nothing if .hgsub does not exist.
2493 2494 if '.hgsub' in wctx or '.hgsub' in old:
2494 2495 subs, commitsubs, newsubstate = subrepoutil.precommit(
2495 2496 ui, wctx, wctx._status, matcher)
2496 2497 # amend should abort if commitsubrepos is enabled
2497 2498 assert not commitsubs
2498 2499 if subs:
2499 2500 subrepoutil.writestate(repo, newsubstate)
2500 2501
2501 2502 ms = mergemod.mergestate.read(repo)
2502 2503 mergeutil.checkunresolved(ms)
2503 2504
2504 2505 filestoamend = set(f for f in wctx.files() if matcher(f))
2505 2506
2506 2507 changes = (len(filestoamend) > 0)
2507 2508 if changes:
2508 2509 # Recompute copies (avoid recording a -> b -> a)
2509 2510 copied = copies.pathcopies(base, wctx, matcher)
2510 2511 if old.p2:
2511 2512 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2512 2513
2513 2514 # Prune files which were reverted by the updates: if old
2514 2515 # introduced file X and the file was renamed in the working
2515 2516 # copy, then those two files are the same and
2516 2517 # we can discard X from our list of files. Likewise if X
2517 2518 # was removed, it's no longer relevant. If X is missing (aka
2518 2519 # deleted), old X must be preserved.
2519 2520 files.update(filestoamend)
2520 2521 files = [f for f in files if (not samefile(f, wctx, base)
2521 2522 or f in wctx.deleted())]
2522 2523
2523 2524 def filectxfn(repo, ctx_, path):
2524 2525 try:
2525 2526 # If the file being considered is not amongst the files
2526 2527 # to be amended, we should return the file context from the
2527 2528 # old changeset. This avoids issues when only some files in
2528 2529 # the working copy are being amended but there are also
2529 2530 # changes to other files from the old changeset.
2530 2531 if path not in filestoamend:
2531 2532 return old.filectx(path)
2532 2533
2533 2534 # Return None for removed files.
2534 2535 if path in wctx.removed():
2535 2536 return None
2536 2537
2537 2538 fctx = wctx[path]
2538 2539 flags = fctx.flags()
2539 2540 mctx = context.memfilectx(repo, ctx_,
2540 2541 fctx.path(), fctx.data(),
2541 2542 islink='l' in flags,
2542 2543 isexec='x' in flags,
2543 2544 copied=copied.get(path))
2544 2545 return mctx
2545 2546 except KeyError:
2546 2547 return None
2547 2548 else:
2548 2549 ui.note(_('copying changeset %s to %s\n') % (old, base))
2549 2550
2550 2551 # Use version of files as in the old cset
2551 2552 def filectxfn(repo, ctx_, path):
2552 2553 try:
2553 2554 return old.filectx(path)
2554 2555 except KeyError:
2555 2556 return None
2556 2557
2557 2558 # See if we got a message from -m or -l, if not, open the editor with
2558 2559 # the message of the changeset to amend.
2559 2560 message = logmessage(ui, opts)
2560 2561
2561 2562 editform = mergeeditform(old, 'commit.amend')
2562 2563 editor = getcommiteditor(editform=editform,
2563 2564 **pycompat.strkwargs(opts))
2564 2565
2565 2566 if not message:
2566 2567 editor = getcommiteditor(edit=True, editform=editform)
2567 2568 message = old.description()
2568 2569
2569 2570 pureextra = extra.copy()
2570 2571 extra['amend_source'] = old.hex()
2571 2572
2572 2573 new = context.memctx(repo,
2573 2574 parents=[base.node(), old.p2().node()],
2574 2575 text=message,
2575 2576 files=files,
2576 2577 filectxfn=filectxfn,
2577 2578 user=user,
2578 2579 date=date,
2579 2580 extra=extra,
2580 2581 editor=editor)
2581 2582
2582 2583 newdesc = changelog.stripdesc(new.description())
2583 2584 if ((not changes)
2584 2585 and newdesc == old.description()
2585 2586 and user == old.user()
2586 2587 and (date == old.date() or datemaydiffer)
2587 2588 and pureextra == old.extra()):
2588 2589 # nothing changed. continuing here would create a new node
2589 2590 # anyway because of the amend_source noise.
2590 2591 #
2591 2592 # This not what we expect from amend.
2592 2593 return old.node()
2593 2594
2594 2595 commitphase = None
2595 2596 if opts.get('secret'):
2596 2597 commitphase = phases.secret
2597 2598 newid = repo.commitctx(new)
2598 2599
2599 2600 # Reroute the working copy parent to the new changeset
2600 2601 repo.setparents(newid, nullid)
2601 2602 mapping = {old.node(): (newid,)}
2602 2603 obsmetadata = None
2603 2604 if opts.get('note'):
2604 2605 obsmetadata = {'note': encoding.fromlocal(opts['note'])}
2605 2606 backup = ui.configbool('rewrite', 'backup-bundle')
2606 2607 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata,
2607 2608 fixphase=True, targetphase=commitphase,
2608 2609 backup=backup)
2609 2610
2610 2611 # Fixing the dirstate because localrepo.commitctx does not update
2611 2612 # it. This is rather convenient because we did not need to update
2612 2613 # the dirstate for all the files in the new commit which commitctx
2613 2614 # could have done if it updated the dirstate. Now, we can
2614 2615 # selectively update the dirstate only for the amended files.
2615 2616 dirstate = repo.dirstate
2616 2617
2617 2618 # Update the state of the files which were added and
2618 2619 # and modified in the amend to "normal" in the dirstate.
2619 2620 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2620 2621 for f in normalfiles:
2621 2622 dirstate.normal(f)
2622 2623
2623 2624 # Update the state of files which were removed in the amend
2624 2625 # to "removed" in the dirstate.
2625 2626 removedfiles = set(wctx.removed()) & filestoamend
2626 2627 for f in removedfiles:
2627 2628 dirstate.drop(f)
2628 2629
2629 2630 return newid
2630 2631
2631 2632 def commiteditor(repo, ctx, subs, editform=''):
2632 2633 if ctx.description():
2633 2634 return ctx.description()
2634 2635 return commitforceeditor(repo, ctx, subs, editform=editform,
2635 2636 unchangedmessagedetection=True)
2636 2637
2637 2638 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2638 2639 editform='', unchangedmessagedetection=False):
2639 2640 if not extramsg:
2640 2641 extramsg = _("Leave message empty to abort commit.")
2641 2642
2642 2643 forms = [e for e in editform.split('.') if e]
2643 2644 forms.insert(0, 'changeset')
2644 2645 templatetext = None
2645 2646 while forms:
2646 2647 ref = '.'.join(forms)
2647 2648 if repo.ui.config('committemplate', ref):
2648 2649 templatetext = committext = buildcommittemplate(
2649 2650 repo, ctx, subs, extramsg, ref)
2650 2651 break
2651 2652 forms.pop()
2652 2653 else:
2653 2654 committext = buildcommittext(repo, ctx, subs, extramsg)
2654 2655
2655 2656 # run editor in the repository root
2656 2657 olddir = encoding.getcwd()
2657 2658 os.chdir(repo.root)
2658 2659
2659 2660 # make in-memory changes visible to external process
2660 2661 tr = repo.currenttransaction()
2661 2662 repo.dirstate.write(tr)
2662 2663 pending = tr and tr.writepending() and repo.root
2663 2664
2664 2665 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2665 2666 editform=editform, pending=pending,
2666 2667 repopath=repo.path, action='commit')
2667 2668 text = editortext
2668 2669
2669 2670 # strip away anything below this special string (used for editors that want
2670 2671 # to display the diff)
2671 2672 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2672 2673 if stripbelow:
2673 2674 text = text[:stripbelow.start()]
2674 2675
2675 2676 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2676 2677 os.chdir(olddir)
2677 2678
2678 2679 if finishdesc:
2679 2680 text = finishdesc(text)
2680 2681 if not text.strip():
2681 2682 raise error.Abort(_("empty commit message"))
2682 2683 if unchangedmessagedetection and editortext == templatetext:
2683 2684 raise error.Abort(_("commit message unchanged"))
2684 2685
2685 2686 return text
2686 2687
2687 2688 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2688 2689 ui = repo.ui
2689 2690 spec = formatter.templatespec(ref, None, None)
2690 2691 t = logcmdutil.changesettemplater(ui, repo, spec)
2691 2692 t.t.cache.update((k, templater.unquotestring(v))
2692 2693 for k, v in repo.ui.configitems('committemplate'))
2693 2694
2694 2695 if not extramsg:
2695 2696 extramsg = '' # ensure that extramsg is string
2696 2697
2697 2698 ui.pushbuffer()
2698 2699 t.show(ctx, extramsg=extramsg)
2699 2700 return ui.popbuffer()
2700 2701
2701 2702 def hgprefix(msg):
2702 2703 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2703 2704
2704 2705 def buildcommittext(repo, ctx, subs, extramsg):
2705 2706 edittext = []
2706 2707 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2707 2708 if ctx.description():
2708 2709 edittext.append(ctx.description())
2709 2710 edittext.append("")
2710 2711 edittext.append("") # Empty line between message and comments.
2711 2712 edittext.append(hgprefix(_("Enter commit message."
2712 2713 " Lines beginning with 'HG:' are removed.")))
2713 2714 edittext.append(hgprefix(extramsg))
2714 2715 edittext.append("HG: --")
2715 2716 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2716 2717 if ctx.p2():
2717 2718 edittext.append(hgprefix(_("branch merge")))
2718 2719 if ctx.branch():
2719 2720 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2720 2721 if bookmarks.isactivewdirparent(repo):
2721 2722 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2722 2723 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2723 2724 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2724 2725 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2725 2726 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2726 2727 if not added and not modified and not removed:
2727 2728 edittext.append(hgprefix(_("no files changed")))
2728 2729 edittext.append("")
2729 2730
2730 2731 return "\n".join(edittext)
2731 2732
2732 2733 def commitstatus(repo, node, branch, bheads=None, opts=None):
2733 2734 if opts is None:
2734 2735 opts = {}
2735 2736 ctx = repo[node]
2736 2737 parents = ctx.parents()
2737 2738
2738 2739 if (not opts.get('amend') and bheads and node not in bheads and not
2739 2740 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2740 2741 repo.ui.status(_('created new head\n'))
2741 2742 # The message is not printed for initial roots. For the other
2742 2743 # changesets, it is printed in the following situations:
2743 2744 #
2744 2745 # Par column: for the 2 parents with ...
2745 2746 # N: null or no parent
2746 2747 # B: parent is on another named branch
2747 2748 # C: parent is a regular non head changeset
2748 2749 # H: parent was a branch head of the current branch
2749 2750 # Msg column: whether we print "created new head" message
2750 2751 # In the following, it is assumed that there already exists some
2751 2752 # initial branch heads of the current branch, otherwise nothing is
2752 2753 # printed anyway.
2753 2754 #
2754 2755 # Par Msg Comment
2755 2756 # N N y additional topo root
2756 2757 #
2757 2758 # B N y additional branch root
2758 2759 # C N y additional topo head
2759 2760 # H N n usual case
2760 2761 #
2761 2762 # B B y weird additional branch root
2762 2763 # C B y branch merge
2763 2764 # H B n merge with named branch
2764 2765 #
2765 2766 # C C y additional head from merge
2766 2767 # C H n merge with a head
2767 2768 #
2768 2769 # H H n head merge: head count decreases
2769 2770
2770 2771 if not opts.get('close_branch'):
2771 2772 for r in parents:
2772 2773 if r.closesbranch() and r.branch() == branch:
2773 2774 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2774 2775
2775 2776 if repo.ui.debugflag:
2776 2777 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2777 2778 elif repo.ui.verbose:
2778 2779 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2779 2780
2780 2781 def postcommitstatus(repo, pats, opts):
2781 2782 return repo.status(match=scmutil.match(repo[None], pats, opts))
2782 2783
2783 2784 def revert(ui, repo, ctx, parents, *pats, **opts):
2784 2785 opts = pycompat.byteskwargs(opts)
2785 2786 parent, p2 = parents
2786 2787 node = ctx.node()
2787 2788
2788 2789 mf = ctx.manifest()
2789 2790 if node == p2:
2790 2791 parent = p2
2791 2792
2792 2793 # need all matching names in dirstate and manifest of target rev,
2793 2794 # so have to walk both. do not print errors if files exist in one
2794 2795 # but not other. in both cases, filesets should be evaluated against
2795 2796 # workingctx to get consistent result (issue4497). this means 'set:**'
2796 2797 # cannot be used to select missing files from target rev.
2797 2798
2798 2799 # `names` is a mapping for all elements in working copy and target revision
2799 2800 # The mapping is in the form:
2800 2801 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2801 2802 names = {}
2802 2803 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2803 2804
2804 2805 with repo.wlock():
2805 2806 ## filling of the `names` mapping
2806 2807 # walk dirstate to fill `names`
2807 2808
2808 2809 interactive = opts.get('interactive', False)
2809 2810 wctx = repo[None]
2810 2811 m = scmutil.match(wctx, pats, opts)
2811 2812
2812 2813 # we'll need this later
2813 2814 targetsubs = sorted(s for s in wctx.substate if m(s))
2814 2815
2815 2816 if not m.always():
2816 2817 matcher = matchmod.badmatch(m, lambda x, y: False)
2817 2818 for abs in wctx.walk(matcher):
2818 2819 names[abs] = m.exact(abs)
2819 2820
2820 2821 # walk target manifest to fill `names`
2821 2822
2822 2823 def badfn(path, msg):
2823 2824 if path in names:
2824 2825 return
2825 2826 if path in ctx.substate:
2826 2827 return
2827 2828 path_ = path + '/'
2828 2829 for f in names:
2829 2830 if f.startswith(path_):
2830 2831 return
2831 2832 ui.warn("%s: %s\n" % (uipathfn(path), msg))
2832 2833
2833 2834 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2834 2835 if abs not in names:
2835 2836 names[abs] = m.exact(abs)
2836 2837
2837 2838 # Find status of all file in `names`.
2838 2839 m = scmutil.matchfiles(repo, names)
2839 2840
2840 2841 changes = repo.status(node1=node, match=m,
2841 2842 unknown=True, ignored=True, clean=True)
2842 2843 else:
2843 2844 changes = repo.status(node1=node, match=m)
2844 2845 for kind in changes:
2845 2846 for abs in kind:
2846 2847 names[abs] = m.exact(abs)
2847 2848
2848 2849 m = scmutil.matchfiles(repo, names)
2849 2850
2850 2851 modified = set(changes.modified)
2851 2852 added = set(changes.added)
2852 2853 removed = set(changes.removed)
2853 2854 _deleted = set(changes.deleted)
2854 2855 unknown = set(changes.unknown)
2855 2856 unknown.update(changes.ignored)
2856 2857 clean = set(changes.clean)
2857 2858 modadded = set()
2858 2859
2859 2860 # We need to account for the state of the file in the dirstate,
2860 2861 # even when we revert against something else than parent. This will
2861 2862 # slightly alter the behavior of revert (doing back up or not, delete
2862 2863 # or just forget etc).
2863 2864 if parent == node:
2864 2865 dsmodified = modified
2865 2866 dsadded = added
2866 2867 dsremoved = removed
2867 2868 # store all local modifications, useful later for rename detection
2868 2869 localchanges = dsmodified | dsadded
2869 2870 modified, added, removed = set(), set(), set()
2870 2871 else:
2871 2872 changes = repo.status(node1=parent, match=m)
2872 2873 dsmodified = set(changes.modified)
2873 2874 dsadded = set(changes.added)
2874 2875 dsremoved = set(changes.removed)
2875 2876 # store all local modifications, useful later for rename detection
2876 2877 localchanges = dsmodified | dsadded
2877 2878
2878 2879 # only take into account for removes between wc and target
2879 2880 clean |= dsremoved - removed
2880 2881 dsremoved &= removed
2881 2882 # distinct between dirstate remove and other
2882 2883 removed -= dsremoved
2883 2884
2884 2885 modadded = added & dsmodified
2885 2886 added -= modadded
2886 2887
2887 2888 # tell newly modified apart.
2888 2889 dsmodified &= modified
2889 2890 dsmodified |= modified & dsadded # dirstate added may need backup
2890 2891 modified -= dsmodified
2891 2892
2892 2893 # We need to wait for some post-processing to update this set
2893 2894 # before making the distinction. The dirstate will be used for
2894 2895 # that purpose.
2895 2896 dsadded = added
2896 2897
2897 2898 # in case of merge, files that are actually added can be reported as
2898 2899 # modified, we need to post process the result
2899 2900 if p2 != nullid:
2900 2901 mergeadd = set(dsmodified)
2901 2902 for path in dsmodified:
2902 2903 if path in mf:
2903 2904 mergeadd.remove(path)
2904 2905 dsadded |= mergeadd
2905 2906 dsmodified -= mergeadd
2906 2907
2907 2908 # if f is a rename, update `names` to also revert the source
2908 2909 for f in localchanges:
2909 2910 src = repo.dirstate.copied(f)
2910 2911 # XXX should we check for rename down to target node?
2911 2912 if src and src not in names and repo.dirstate[src] == 'r':
2912 2913 dsremoved.add(src)
2913 2914 names[src] = True
2914 2915
2915 2916 # determine the exact nature of the deleted changesets
2916 2917 deladded = set(_deleted)
2917 2918 for path in _deleted:
2918 2919 if path in mf:
2919 2920 deladded.remove(path)
2920 2921 deleted = _deleted - deladded
2921 2922
2922 2923 # distinguish between file to forget and the other
2923 2924 added = set()
2924 2925 for abs in dsadded:
2925 2926 if repo.dirstate[abs] != 'a':
2926 2927 added.add(abs)
2927 2928 dsadded -= added
2928 2929
2929 2930 for abs in deladded:
2930 2931 if repo.dirstate[abs] == 'a':
2931 2932 dsadded.add(abs)
2932 2933 deladded -= dsadded
2933 2934
2934 2935 # For files marked as removed, we check if an unknown file is present at
2935 2936 # the same path. If a such file exists it may need to be backed up.
2936 2937 # Making the distinction at this stage helps have simpler backup
2937 2938 # logic.
2938 2939 removunk = set()
2939 2940 for abs in removed:
2940 2941 target = repo.wjoin(abs)
2941 2942 if os.path.lexists(target):
2942 2943 removunk.add(abs)
2943 2944 removed -= removunk
2944 2945
2945 2946 dsremovunk = set()
2946 2947 for abs in dsremoved:
2947 2948 target = repo.wjoin(abs)
2948 2949 if os.path.lexists(target):
2949 2950 dsremovunk.add(abs)
2950 2951 dsremoved -= dsremovunk
2951 2952
2952 2953 # action to be actually performed by revert
2953 2954 # (<list of file>, message>) tuple
2954 2955 actions = {'revert': ([], _('reverting %s\n')),
2955 2956 'add': ([], _('adding %s\n')),
2956 2957 'remove': ([], _('removing %s\n')),
2957 2958 'drop': ([], _('removing %s\n')),
2958 2959 'forget': ([], _('forgetting %s\n')),
2959 2960 'undelete': ([], _('undeleting %s\n')),
2960 2961 'noop': (None, _('no changes needed to %s\n')),
2961 2962 'unknown': (None, _('file not managed: %s\n')),
2962 2963 }
2963 2964
2964 2965 # "constant" that convey the backup strategy.
2965 2966 # All set to `discard` if `no-backup` is set do avoid checking
2966 2967 # no_backup lower in the code.
2967 2968 # These values are ordered for comparison purposes
2968 2969 backupinteractive = 3 # do backup if interactively modified
2969 2970 backup = 2 # unconditionally do backup
2970 2971 check = 1 # check if the existing file differs from target
2971 2972 discard = 0 # never do backup
2972 2973 if opts.get('no_backup'):
2973 2974 backupinteractive = backup = check = discard
2974 2975 if interactive:
2975 2976 dsmodifiedbackup = backupinteractive
2976 2977 else:
2977 2978 dsmodifiedbackup = backup
2978 2979 tobackup = set()
2979 2980
2980 2981 backupanddel = actions['remove']
2981 2982 if not opts.get('no_backup'):
2982 2983 backupanddel = actions['drop']
2983 2984
2984 2985 disptable = (
2985 2986 # dispatch table:
2986 2987 # file state
2987 2988 # action
2988 2989 # make backup
2989 2990
2990 2991 ## Sets that results that will change file on disk
2991 2992 # Modified compared to target, no local change
2992 2993 (modified, actions['revert'], discard),
2993 2994 # Modified compared to target, but local file is deleted
2994 2995 (deleted, actions['revert'], discard),
2995 2996 # Modified compared to target, local change
2996 2997 (dsmodified, actions['revert'], dsmodifiedbackup),
2997 2998 # Added since target
2998 2999 (added, actions['remove'], discard),
2999 3000 # Added in working directory
3000 3001 (dsadded, actions['forget'], discard),
3001 3002 # Added since target, have local modification
3002 3003 (modadded, backupanddel, backup),
3003 3004 # Added since target but file is missing in working directory
3004 3005 (deladded, actions['drop'], discard),
3005 3006 # Removed since target, before working copy parent
3006 3007 (removed, actions['add'], discard),
3007 3008 # Same as `removed` but an unknown file exists at the same path
3008 3009 (removunk, actions['add'], check),
3009 3010 # Removed since targe, marked as such in working copy parent
3010 3011 (dsremoved, actions['undelete'], discard),
3011 3012 # Same as `dsremoved` but an unknown file exists at the same path
3012 3013 (dsremovunk, actions['undelete'], check),
3013 3014 ## the following sets does not result in any file changes
3014 3015 # File with no modification
3015 3016 (clean, actions['noop'], discard),
3016 3017 # Existing file, not tracked anywhere
3017 3018 (unknown, actions['unknown'], discard),
3018 3019 )
3019 3020
3020 3021 for abs, exact in sorted(names.items()):
3021 3022 # target file to be touch on disk (relative to cwd)
3022 3023 target = repo.wjoin(abs)
3023 3024 # search the entry in the dispatch table.
3024 3025 # if the file is in any of these sets, it was touched in the working
3025 3026 # directory parent and we are sure it needs to be reverted.
3026 3027 for table, (xlist, msg), dobackup in disptable:
3027 3028 if abs not in table:
3028 3029 continue
3029 3030 if xlist is not None:
3030 3031 xlist.append(abs)
3031 3032 if dobackup:
3032 3033 # If in interactive mode, don't automatically create
3033 3034 # .orig files (issue4793)
3034 3035 if dobackup == backupinteractive:
3035 3036 tobackup.add(abs)
3036 3037 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3037 3038 absbakname = scmutil.backuppath(ui, repo, abs)
3038 3039 bakname = os.path.relpath(absbakname,
3039 3040 start=repo.root)
3040 3041 ui.note(_('saving current version of %s as %s\n') %
3041 3042 (uipathfn(abs), uipathfn(bakname)))
3042 3043 if not opts.get('dry_run'):
3043 3044 if interactive:
3044 3045 util.copyfile(target, absbakname)
3045 3046 else:
3046 3047 util.rename(target, absbakname)
3047 3048 if opts.get('dry_run'):
3048 3049 if ui.verbose or not exact:
3049 3050 ui.status(msg % uipathfn(abs))
3050 3051 elif exact:
3051 3052 ui.warn(msg % uipathfn(abs))
3052 3053 break
3053 3054
3054 3055 if not opts.get('dry_run'):
3055 3056 needdata = ('revert', 'add', 'undelete')
3056 3057 oplist = [actions[name][0] for name in needdata]
3057 3058 prefetch = scmutil.prefetchfiles
3058 3059 matchfiles = scmutil.matchfiles
3059 3060 prefetch(repo, [ctx.rev()],
3060 3061 matchfiles(repo,
3061 3062 [f for sublist in oplist for f in sublist]))
3062 3063 _performrevert(repo, parents, ctx, names, uipathfn, actions,
3063 3064 interactive, tobackup)
3064 3065
3065 3066 if targetsubs:
3066 3067 # Revert the subrepos on the revert list
3067 3068 for sub in targetsubs:
3068 3069 try:
3069 3070 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3070 3071 **pycompat.strkwargs(opts))
3071 3072 except KeyError:
3072 3073 raise error.Abort("subrepository '%s' does not exist in %s!"
3073 3074 % (sub, short(ctx.node())))
3074 3075
3075 3076 def _performrevert(repo, parents, ctx, names, uipathfn, actions,
3076 3077 interactive=False, tobackup=None):
3077 3078 """function that actually perform all the actions computed for revert
3078 3079
3079 3080 This is an independent function to let extension to plug in and react to
3080 3081 the imminent revert.
3081 3082
3082 3083 Make sure you have the working directory locked when calling this function.
3083 3084 """
3084 3085 parent, p2 = parents
3085 3086 node = ctx.node()
3086 3087 excluded_files = []
3087 3088
3088 3089 def checkout(f):
3089 3090 fc = ctx[f]
3090 3091 repo.wwrite(f, fc.data(), fc.flags())
3091 3092
3092 3093 def doremove(f):
3093 3094 try:
3094 3095 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
3095 3096 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3096 3097 except OSError:
3097 3098 pass
3098 3099 repo.dirstate.remove(f)
3099 3100
3100 3101 def prntstatusmsg(action, f):
3101 3102 exact = names[f]
3102 3103 if repo.ui.verbose or not exact:
3103 3104 repo.ui.status(actions[action][1] % uipathfn(f))
3104 3105
3105 3106 audit_path = pathutil.pathauditor(repo.root, cached=True)
3106 3107 for f in actions['forget'][0]:
3107 3108 if interactive:
3108 3109 choice = repo.ui.promptchoice(
3109 3110 _("forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3110 3111 if choice == 0:
3111 3112 prntstatusmsg('forget', f)
3112 3113 repo.dirstate.drop(f)
3113 3114 else:
3114 3115 excluded_files.append(f)
3115 3116 else:
3116 3117 prntstatusmsg('forget', f)
3117 3118 repo.dirstate.drop(f)
3118 3119 for f in actions['remove'][0]:
3119 3120 audit_path(f)
3120 3121 if interactive:
3121 3122 choice = repo.ui.promptchoice(
3122 3123 _("remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3123 3124 if choice == 0:
3124 3125 prntstatusmsg('remove', f)
3125 3126 doremove(f)
3126 3127 else:
3127 3128 excluded_files.append(f)
3128 3129 else:
3129 3130 prntstatusmsg('remove', f)
3130 3131 doremove(f)
3131 3132 for f in actions['drop'][0]:
3132 3133 audit_path(f)
3133 3134 prntstatusmsg('drop', f)
3134 3135 repo.dirstate.remove(f)
3135 3136
3136 3137 normal = None
3137 3138 if node == parent:
3138 3139 # We're reverting to our parent. If possible, we'd like status
3139 3140 # to report the file as clean. We have to use normallookup for
3140 3141 # merges to avoid losing information about merged/dirty files.
3141 3142 if p2 != nullid:
3142 3143 normal = repo.dirstate.normallookup
3143 3144 else:
3144 3145 normal = repo.dirstate.normal
3145 3146
3146 3147 newlyaddedandmodifiedfiles = set()
3147 3148 if interactive:
3148 3149 # Prompt the user for changes to revert
3149 3150 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3150 3151 m = scmutil.matchfiles(repo, torevert)
3151 3152 diffopts = patch.difffeatureopts(repo.ui, whitespace=True,
3152 3153 section='commands',
3153 3154 configprefix='revert.interactive.')
3154 3155 diffopts.nodates = True
3155 3156 diffopts.git = True
3156 3157 operation = 'discard'
3157 3158 reversehunks = True
3158 3159 if node != parent:
3159 3160 operation = 'apply'
3160 3161 reversehunks = False
3161 3162 if reversehunks:
3162 3163 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3163 3164 else:
3164 3165 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3165 3166 originalchunks = patch.parsepatch(diff)
3166 3167
3167 3168 try:
3168 3169
3169 3170 chunks, opts = recordfilter(repo.ui, originalchunks,
3170 3171 operation=operation)
3171 3172 if reversehunks:
3172 3173 chunks = patch.reversehunks(chunks)
3173 3174
3174 3175 except error.PatchError as err:
3175 3176 raise error.Abort(_('error parsing patch: %s') % err)
3176 3177
3177 3178 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3178 3179 if tobackup is None:
3179 3180 tobackup = set()
3180 3181 # Apply changes
3181 3182 fp = stringio()
3182 3183 # chunks are serialized per file, but files aren't sorted
3183 3184 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3184 3185 prntstatusmsg('revert', f)
3185 3186 for c in chunks:
3186 3187 if ishunk(c):
3187 3188 abs = c.header.filename()
3188 3189 # Create a backup file only if this hunk should be backed up
3189 3190 if c.header.filename() in tobackup:
3190 3191 target = repo.wjoin(abs)
3191 3192 bakname = scmutil.backuppath(repo.ui, repo, abs)
3192 3193 util.copyfile(target, bakname)
3193 3194 tobackup.remove(abs)
3194 3195 c.write(fp)
3195 3196 dopatch = fp.tell()
3196 3197 fp.seek(0)
3197 3198 if dopatch:
3198 3199 try:
3199 3200 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3200 3201 except error.PatchError as err:
3201 3202 raise error.Abort(pycompat.bytestr(err))
3202 3203 del fp
3203 3204 else:
3204 3205 for f in actions['revert'][0]:
3205 3206 prntstatusmsg('revert', f)
3206 3207 checkout(f)
3207 3208 if normal:
3208 3209 normal(f)
3209 3210
3210 3211 for f in actions['add'][0]:
3211 3212 # Don't checkout modified files, they are already created by the diff
3212 3213 if f not in newlyaddedandmodifiedfiles:
3213 3214 prntstatusmsg('add', f)
3214 3215 checkout(f)
3215 3216 repo.dirstate.add(f)
3216 3217
3217 3218 normal = repo.dirstate.normallookup
3218 3219 if node == parent and p2 == nullid:
3219 3220 normal = repo.dirstate.normal
3220 3221 for f in actions['undelete'][0]:
3221 3222 if interactive:
3222 3223 choice = repo.ui.promptchoice(
3223 3224 _("add back removed file %s (Yn)?$$ &Yes $$ &No") % f)
3224 3225 if choice == 0:
3225 3226 prntstatusmsg('undelete', f)
3226 3227 checkout(f)
3227 3228 normal(f)
3228 3229 else:
3229 3230 excluded_files.append(f)
3230 3231 else:
3231 3232 prntstatusmsg('undelete', f)
3232 3233 checkout(f)
3233 3234 normal(f)
3234 3235
3235 3236 copied = copies.pathcopies(repo[parent], ctx)
3236 3237
3237 3238 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3238 3239 if f in copied:
3239 3240 repo.dirstate.copy(copied[f], f)
3240 3241
3241 3242 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3242 3243 # commands.outgoing. "missing" is "missing" of the result of
3243 3244 # "findcommonoutgoing()"
3244 3245 outgoinghooks = util.hooks()
3245 3246
3246 3247 # a list of (ui, repo) functions called by commands.summary
3247 3248 summaryhooks = util.hooks()
3248 3249
3249 3250 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3250 3251 #
3251 3252 # functions should return tuple of booleans below, if 'changes' is None:
3252 3253 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3253 3254 #
3254 3255 # otherwise, 'changes' is a tuple of tuples below:
3255 3256 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3256 3257 # - (desturl, destbranch, destpeer, outgoing)
3257 3258 summaryremotehooks = util.hooks()
3258 3259
3259 3260 # A list of state files kept by multistep operations like graft.
3260 3261 # Since graft cannot be aborted, it is considered 'clearable' by update.
3261 3262 # note: bisect is intentionally excluded
3262 3263 # (state file, clearable, allowcommit, error, hint)
3263 3264 unfinishedstates = [
3264 3265 ('graftstate', True, False, _('graft in progress'),
3265 3266 _("use 'hg graft --continue' or 'hg graft --stop' to stop")),
3266 3267 ('updatestate', True, False, _('last update was interrupted'),
3267 3268 _("use 'hg update' to get a consistent checkout"))
3268 3269 ]
3269 3270
3270 3271 def checkunfinished(repo, commit=False):
3271 3272 '''Look for an unfinished multistep operation, like graft, and abort
3272 3273 if found. It's probably good to check this right before
3273 3274 bailifchanged().
3274 3275 '''
3275 3276 # Check for non-clearable states first, so things like rebase will take
3276 3277 # precedence over update.
3277 3278 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3278 3279 if clearable or (commit and allowcommit):
3279 3280 continue
3280 3281 if repo.vfs.exists(f):
3281 3282 raise error.Abort(msg, hint=hint)
3282 3283
3283 3284 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3284 3285 if not clearable or (commit and allowcommit):
3285 3286 continue
3286 3287 if repo.vfs.exists(f):
3287 3288 raise error.Abort(msg, hint=hint)
3288 3289
3289 3290 def clearunfinished(repo):
3290 3291 '''Check for unfinished operations (as above), and clear the ones
3291 3292 that are clearable.
3292 3293 '''
3293 3294 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3294 3295 if not clearable and repo.vfs.exists(f):
3295 3296 raise error.Abort(msg, hint=hint)
3296 3297 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3297 3298 if clearable and repo.vfs.exists(f):
3298 3299 util.unlink(repo.vfs.join(f))
3299 3300
3300 3301 afterresolvedstates = [
3301 3302 ('graftstate',
3302 3303 _('hg graft --continue')),
3303 3304 ]
3304 3305
3305 3306 def howtocontinue(repo):
3306 3307 '''Check for an unfinished operation and return the command to finish
3307 3308 it.
3308 3309
3309 3310 afterresolvedstates tuples define a .hg/{file} and the corresponding
3310 3311 command needed to finish it.
3311 3312
3312 3313 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3313 3314 a boolean.
3314 3315 '''
3315 3316 contmsg = _("continue: %s")
3316 3317 for f, msg in afterresolvedstates:
3317 3318 if repo.vfs.exists(f):
3318 3319 return contmsg % msg, True
3319 3320 if repo[None].dirty(missing=True, merge=False, branch=False):
3320 3321 return contmsg % _("hg commit"), False
3321 3322 return None, None
3322 3323
3323 3324 def checkafterresolved(repo):
3324 3325 '''Inform the user about the next action after completing hg resolve
3325 3326
3326 3327 If there's a matching afterresolvedstates, howtocontinue will yield
3327 3328 repo.ui.warn as the reporter.
3328 3329
3329 3330 Otherwise, it will yield repo.ui.note.
3330 3331 '''
3331 3332 msg, warning = howtocontinue(repo)
3332 3333 if msg is not None:
3333 3334 if warning:
3334 3335 repo.ui.warn("%s\n" % msg)
3335 3336 else:
3336 3337 repo.ui.note("%s\n" % msg)
3337 3338
3338 3339 def wrongtooltocontinue(repo, task):
3339 3340 '''Raise an abort suggesting how to properly continue if there is an
3340 3341 active task.
3341 3342
3342 3343 Uses howtocontinue() to find the active task.
3343 3344
3344 3345 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3345 3346 a hint.
3346 3347 '''
3347 3348 after = howtocontinue(repo)
3348 3349 hint = None
3349 3350 if after[1]:
3350 3351 hint = after[0]
3351 3352 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,6238 +1,6240 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import difflib
11 11 import errno
12 12 import os
13 13 import re
14 14 import sys
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 hex,
19 19 nullid,
20 20 nullrev,
21 21 short,
22 22 wdirhex,
23 23 wdirrev,
24 24 )
25 25 from . import (
26 26 archival,
27 27 bookmarks,
28 28 bundle2,
29 29 changegroup,
30 30 cmdutil,
31 31 copies,
32 32 debugcommands as debugcommandsmod,
33 33 destutil,
34 34 dirstateguard,
35 35 discovery,
36 36 encoding,
37 37 error,
38 38 exchange,
39 39 extensions,
40 40 filemerge,
41 41 formatter,
42 42 graphmod,
43 43 hbisect,
44 44 help,
45 45 hg,
46 46 logcmdutil,
47 47 merge as mergemod,
48 48 narrowspec,
49 49 obsolete,
50 50 obsutil,
51 51 patch,
52 52 phases,
53 53 pycompat,
54 54 rcutil,
55 55 registrar,
56 56 repair,
57 57 revsetlang,
58 58 rewriteutil,
59 59 scmutil,
60 60 server,
61 61 state as statemod,
62 62 streamclone,
63 63 tags as tagsmod,
64 64 templatekw,
65 65 ui as uimod,
66 66 util,
67 67 wireprotoserver,
68 68 )
69 69 from .utils import (
70 70 dateutil,
71 71 stringutil,
72 72 )
73 73
74 74 table = {}
75 75 table.update(debugcommandsmod.command._table)
76 76
77 77 command = registrar.command(table)
78 78 INTENT_READONLY = registrar.INTENT_READONLY
79 79
80 80 # common command options
81 81
82 82 globalopts = [
83 83 ('R', 'repository', '',
84 84 _('repository root directory or name of overlay bundle file'),
85 85 _('REPO')),
86 86 ('', 'cwd', '',
87 87 _('change working directory'), _('DIR')),
88 88 ('y', 'noninteractive', None,
89 89 _('do not prompt, automatically pick the first choice for all prompts')),
90 90 ('q', 'quiet', None, _('suppress output')),
91 91 ('v', 'verbose', None, _('enable additional output')),
92 92 ('', 'color', '',
93 93 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
94 94 # and should not be translated
95 95 _("when to colorize (boolean, always, auto, never, or debug)"),
96 96 _('TYPE')),
97 97 ('', 'config', [],
98 98 _('set/override config option (use \'section.name=value\')'),
99 99 _('CONFIG')),
100 100 ('', 'debug', None, _('enable debugging output')),
101 101 ('', 'debugger', None, _('start debugger')),
102 102 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
103 103 _('ENCODE')),
104 104 ('', 'encodingmode', encoding.encodingmode,
105 105 _('set the charset encoding mode'), _('MODE')),
106 106 ('', 'traceback', None, _('always print a traceback on exception')),
107 107 ('', 'time', None, _('time how long the command takes')),
108 108 ('', 'profile', None, _('print command execution profile')),
109 109 ('', 'version', None, _('output version information and exit')),
110 110 ('h', 'help', None, _('display help and exit')),
111 111 ('', 'hidden', False, _('consider hidden changesets')),
112 112 ('', 'pager', 'auto',
113 113 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
114 114 ]
115 115
116 116 dryrunopts = cmdutil.dryrunopts
117 117 remoteopts = cmdutil.remoteopts
118 118 walkopts = cmdutil.walkopts
119 119 commitopts = cmdutil.commitopts
120 120 commitopts2 = cmdutil.commitopts2
121 121 formatteropts = cmdutil.formatteropts
122 122 templateopts = cmdutil.templateopts
123 123 logopts = cmdutil.logopts
124 124 diffopts = cmdutil.diffopts
125 125 diffwsopts = cmdutil.diffwsopts
126 126 diffopts2 = cmdutil.diffopts2
127 127 mergetoolopts = cmdutil.mergetoolopts
128 128 similarityopts = cmdutil.similarityopts
129 129 subrepoopts = cmdutil.subrepoopts
130 130 debugrevlogopts = cmdutil.debugrevlogopts
131 131
132 132 # Commands start here, listed alphabetically
133 133
134 134 @command('add',
135 135 walkopts + subrepoopts + dryrunopts,
136 136 _('[OPTION]... [FILE]...'),
137 137 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
138 138 helpbasic=True, inferrepo=True)
139 139 def add(ui, repo, *pats, **opts):
140 140 """add the specified files on the next commit
141 141
142 142 Schedule files to be version controlled and added to the
143 143 repository.
144 144
145 145 The files will be added to the repository at the next commit. To
146 146 undo an add before that, see :hg:`forget`.
147 147
148 148 If no names are given, add all files to the repository (except
149 149 files matching ``.hgignore``).
150 150
151 151 .. container:: verbose
152 152
153 153 Examples:
154 154
155 155 - New (unknown) files are added
156 156 automatically by :hg:`add`::
157 157
158 158 $ ls
159 159 foo.c
160 160 $ hg status
161 161 ? foo.c
162 162 $ hg add
163 163 adding foo.c
164 164 $ hg status
165 165 A foo.c
166 166
167 167 - Specific files to be added can be specified::
168 168
169 169 $ ls
170 170 bar.c foo.c
171 171 $ hg status
172 172 ? bar.c
173 173 ? foo.c
174 174 $ hg add bar.c
175 175 $ hg status
176 176 A bar.c
177 177 ? foo.c
178 178
179 179 Returns 0 if all files are successfully added.
180 180 """
181 181
182 182 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
183 183 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
184 184 rejected = cmdutil.add(ui, repo, m, "", uipathfn, False, **opts)
185 185 return rejected and 1 or 0
186 186
187 187 @command('addremove',
188 188 similarityopts + subrepoopts + walkopts + dryrunopts,
189 189 _('[OPTION]... [FILE]...'),
190 190 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
191 191 inferrepo=True)
192 192 def addremove(ui, repo, *pats, **opts):
193 193 """add all new files, delete all missing files
194 194
195 195 Add all new files and remove all missing files from the
196 196 repository.
197 197
198 198 Unless names are given, new files are ignored if they match any of
199 199 the patterns in ``.hgignore``. As with add, these changes take
200 200 effect at the next commit.
201 201
202 202 Use the -s/--similarity option to detect renamed files. This
203 203 option takes a percentage between 0 (disabled) and 100 (files must
204 204 be identical) as its parameter. With a parameter greater than 0,
205 205 this compares every removed file with every added file and records
206 206 those similar enough as renames. Detecting renamed files this way
207 207 can be expensive. After using this option, :hg:`status -C` can be
208 208 used to check which files were identified as moved or renamed. If
209 209 not specified, -s/--similarity defaults to 100 and only renames of
210 210 identical files are detected.
211 211
212 212 .. container:: verbose
213 213
214 214 Examples:
215 215
216 216 - A number of files (bar.c and foo.c) are new,
217 217 while foobar.c has been removed (without using :hg:`remove`)
218 218 from the repository::
219 219
220 220 $ ls
221 221 bar.c foo.c
222 222 $ hg status
223 223 ! foobar.c
224 224 ? bar.c
225 225 ? foo.c
226 226 $ hg addremove
227 227 adding bar.c
228 228 adding foo.c
229 229 removing foobar.c
230 230 $ hg status
231 231 A bar.c
232 232 A foo.c
233 233 R foobar.c
234 234
235 235 - A file foobar.c was moved to foo.c without using :hg:`rename`.
236 236 Afterwards, it was edited slightly::
237 237
238 238 $ ls
239 239 foo.c
240 240 $ hg status
241 241 ! foobar.c
242 242 ? foo.c
243 243 $ hg addremove --similarity 90
244 244 removing foobar.c
245 245 adding foo.c
246 246 recording removal of foobar.c as rename to foo.c (94% similar)
247 247 $ hg status -C
248 248 A foo.c
249 249 foobar.c
250 250 R foobar.c
251 251
252 252 Returns 0 if all files are successfully added.
253 253 """
254 254 opts = pycompat.byteskwargs(opts)
255 255 if not opts.get('similarity'):
256 256 opts['similarity'] = '100'
257 257 matcher = scmutil.match(repo[None], pats, opts)
258 258 relative = scmutil.anypats(pats, opts)
259 259 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
260 260 return scmutil.addremove(repo, matcher, "", uipathfn, opts)
261 261
262 262 @command('annotate|blame',
263 263 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
264 264 ('', 'follow', None,
265 265 _('follow copies/renames and list the filename (DEPRECATED)')),
266 266 ('', 'no-follow', None, _("don't follow copies and renames")),
267 267 ('a', 'text', None, _('treat all files as text')),
268 268 ('u', 'user', None, _('list the author (long with -v)')),
269 269 ('f', 'file', None, _('list the filename')),
270 270 ('d', 'date', None, _('list the date (short with -q)')),
271 271 ('n', 'number', None, _('list the revision number (default)')),
272 272 ('c', 'changeset', None, _('list the changeset')),
273 273 ('l', 'line-number', None, _('show line number at the first appearance')),
274 274 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
275 275 ] + diffwsopts + walkopts + formatteropts,
276 276 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
277 277 helpcategory=command.CATEGORY_FILE_CONTENTS,
278 278 helpbasic=True, inferrepo=True)
279 279 def annotate(ui, repo, *pats, **opts):
280 280 """show changeset information by line for each file
281 281
282 282 List changes in files, showing the revision id responsible for
283 283 each line.
284 284
285 285 This command is useful for discovering when a change was made and
286 286 by whom.
287 287
288 288 If you include --file, --user, or --date, the revision number is
289 289 suppressed unless you also include --number.
290 290
291 291 Without the -a/--text option, annotate will avoid processing files
292 292 it detects as binary. With -a, annotate will annotate the file
293 293 anyway, although the results will probably be neither useful
294 294 nor desirable.
295 295
296 296 .. container:: verbose
297 297
298 298 Template:
299 299
300 300 The following keywords are supported in addition to the common template
301 301 keywords and functions. See also :hg:`help templates`.
302 302
303 303 :lines: List of lines with annotation data.
304 304 :path: String. Repository-absolute path of the specified file.
305 305
306 306 And each entry of ``{lines}`` provides the following sub-keywords in
307 307 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
308 308
309 309 :line: String. Line content.
310 310 :lineno: Integer. Line number at that revision.
311 311 :path: String. Repository-absolute path of the file at that revision.
312 312
313 313 See :hg:`help templates.operators` for the list expansion syntax.
314 314
315 315 Returns 0 on success.
316 316 """
317 317 opts = pycompat.byteskwargs(opts)
318 318 if not pats:
319 319 raise error.Abort(_('at least one filename or pattern is required'))
320 320
321 321 if opts.get('follow'):
322 322 # --follow is deprecated and now just an alias for -f/--file
323 323 # to mimic the behavior of Mercurial before version 1.5
324 324 opts['file'] = True
325 325
326 326 if (not opts.get('user') and not opts.get('changeset')
327 327 and not opts.get('date') and not opts.get('file')):
328 328 opts['number'] = True
329 329
330 330 linenumber = opts.get('line_number') is not None
331 331 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
332 332 raise error.Abort(_('at least one of -n/-c is required for -l'))
333 333
334 334 rev = opts.get('rev')
335 335 if rev:
336 336 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
337 337 ctx = scmutil.revsingle(repo, rev)
338 338
339 339 ui.pager('annotate')
340 340 rootfm = ui.formatter('annotate', opts)
341 341 if ui.debugflag:
342 342 shorthex = pycompat.identity
343 343 else:
344 344 def shorthex(h):
345 345 return h[:12]
346 346 if ui.quiet:
347 347 datefunc = dateutil.shortdate
348 348 else:
349 349 datefunc = dateutil.datestr
350 350 if ctx.rev() is None:
351 351 if opts.get('changeset'):
352 352 # omit "+" suffix which is appended to node hex
353 353 def formatrev(rev):
354 354 if rev == wdirrev:
355 355 return '%d' % ctx.p1().rev()
356 356 else:
357 357 return '%d' % rev
358 358 else:
359 359 def formatrev(rev):
360 360 if rev == wdirrev:
361 361 return '%d+' % ctx.p1().rev()
362 362 else:
363 363 return '%d ' % rev
364 364 def formathex(h):
365 365 if h == wdirhex:
366 366 return '%s+' % shorthex(hex(ctx.p1().node()))
367 367 else:
368 368 return '%s ' % shorthex(h)
369 369 else:
370 370 formatrev = b'%d'.__mod__
371 371 formathex = shorthex
372 372
373 373 opmap = [
374 374 ('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
375 375 ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
376 376 ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
377 377 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
378 378 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
379 379 ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
380 380 ]
381 381 opnamemap = {
382 382 'rev': 'number',
383 383 'node': 'changeset',
384 384 'path': 'file',
385 385 'lineno': 'line_number',
386 386 }
387 387
388 388 if rootfm.isplain():
389 389 def makefunc(get, fmt):
390 390 return lambda x: fmt(get(x))
391 391 else:
392 392 def makefunc(get, fmt):
393 393 return get
394 394 datahint = rootfm.datahint()
395 395 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
396 396 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
397 397 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
398 398 fields = ' '.join(fn for fn, sep, get, fmt in opmap
399 399 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
400 400
401 401 def bad(x, y):
402 402 raise error.Abort("%s: %s" % (x, y))
403 403
404 404 m = scmutil.match(ctx, pats, opts, badfn=bad)
405 405
406 406 follow = not opts.get('no_follow')
407 407 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
408 408 whitespace=True)
409 409 skiprevs = opts.get('skip')
410 410 if skiprevs:
411 411 skiprevs = scmutil.revrange(repo, skiprevs)
412 412
413 413 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
414 414 for abs in ctx.walk(m):
415 415 fctx = ctx[abs]
416 416 rootfm.startitem()
417 417 rootfm.data(path=abs)
418 418 if not opts.get('text') and fctx.isbinary():
419 419 rootfm.plain(_("%s: binary file\n") % uipathfn(abs))
420 420 continue
421 421
422 422 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
423 423 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
424 424 diffopts=diffopts)
425 425 if not lines:
426 426 fm.end()
427 427 continue
428 428 formats = []
429 429 pieces = []
430 430
431 431 for f, sep in funcmap:
432 432 l = [f(n) for n in lines]
433 433 if fm.isplain():
434 434 sizes = [encoding.colwidth(x) for x in l]
435 435 ml = max(sizes)
436 436 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
437 437 else:
438 438 formats.append(['%s' for x in l])
439 439 pieces.append(l)
440 440
441 441 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
442 442 fm.startitem()
443 443 fm.context(fctx=n.fctx)
444 444 fm.write(fields, "".join(f), *p)
445 445 if n.skip:
446 446 fmt = "* %s"
447 447 else:
448 448 fmt = ": %s"
449 449 fm.write('line', fmt, n.text)
450 450
451 451 if not lines[-1].text.endswith('\n'):
452 452 fm.plain('\n')
453 453 fm.end()
454 454
455 455 rootfm.end()
456 456
457 457 @command('archive',
458 458 [('', 'no-decode', None, _('do not pass files through decoders')),
459 459 ('p', 'prefix', '', _('directory prefix for files in archive'),
460 460 _('PREFIX')),
461 461 ('r', 'rev', '', _('revision to distribute'), _('REV')),
462 462 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
463 463 ] + subrepoopts + walkopts,
464 464 _('[OPTION]... DEST'),
465 465 helpcategory=command.CATEGORY_IMPORT_EXPORT)
466 466 def archive(ui, repo, dest, **opts):
467 467 '''create an unversioned archive of a repository revision
468 468
469 469 By default, the revision used is the parent of the working
470 470 directory; use -r/--rev to specify a different revision.
471 471
472 472 The archive type is automatically detected based on file
473 473 extension (to override, use -t/--type).
474 474
475 475 .. container:: verbose
476 476
477 477 Examples:
478 478
479 479 - create a zip file containing the 1.0 release::
480 480
481 481 hg archive -r 1.0 project-1.0.zip
482 482
483 483 - create a tarball excluding .hg files::
484 484
485 485 hg archive project.tar.gz -X ".hg*"
486 486
487 487 Valid types are:
488 488
489 489 :``files``: a directory full of files (default)
490 490 :``tar``: tar archive, uncompressed
491 491 :``tbz2``: tar archive, compressed using bzip2
492 492 :``tgz``: tar archive, compressed using gzip
493 493 :``uzip``: zip archive, uncompressed
494 494 :``zip``: zip archive, compressed using deflate
495 495
496 496 The exact name of the destination archive or directory is given
497 497 using a format string; see :hg:`help export` for details.
498 498
499 499 Each member added to an archive file has a directory prefix
500 500 prepended. Use -p/--prefix to specify a format string for the
501 501 prefix. The default is the basename of the archive, with suffixes
502 502 removed.
503 503
504 504 Returns 0 on success.
505 505 '''
506 506
507 507 opts = pycompat.byteskwargs(opts)
508 508 rev = opts.get('rev')
509 509 if rev:
510 510 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
511 511 ctx = scmutil.revsingle(repo, rev)
512 512 if not ctx:
513 513 raise error.Abort(_('no working directory: please specify a revision'))
514 514 node = ctx.node()
515 515 dest = cmdutil.makefilename(ctx, dest)
516 516 if os.path.realpath(dest) == repo.root:
517 517 raise error.Abort(_('repository root cannot be destination'))
518 518
519 519 kind = opts.get('type') or archival.guesskind(dest) or 'files'
520 520 prefix = opts.get('prefix')
521 521
522 522 if dest == '-':
523 523 if kind == 'files':
524 524 raise error.Abort(_('cannot archive plain files to stdout'))
525 525 dest = cmdutil.makefileobj(ctx, dest)
526 526 if not prefix:
527 527 prefix = os.path.basename(repo.root) + '-%h'
528 528
529 529 prefix = cmdutil.makefilename(ctx, prefix)
530 530 match = scmutil.match(ctx, [], opts)
531 531 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
532 532 match, prefix, subrepos=opts.get('subrepos'))
533 533
534 534 @command('backout',
535 535 [('', 'merge', None, _('merge with old dirstate parent after backout')),
536 536 ('', 'commit', None,
537 537 _('commit if no conflicts were encountered (DEPRECATED)')),
538 538 ('', 'no-commit', None, _('do not commit')),
539 539 ('', 'parent', '',
540 540 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
541 541 ('r', 'rev', '', _('revision to backout'), _('REV')),
542 542 ('e', 'edit', False, _('invoke editor on commit messages')),
543 543 ] + mergetoolopts + walkopts + commitopts + commitopts2,
544 544 _('[OPTION]... [-r] REV'),
545 545 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
546 546 def backout(ui, repo, node=None, rev=None, **opts):
547 547 '''reverse effect of earlier changeset
548 548
549 549 Prepare a new changeset with the effect of REV undone in the
550 550 current working directory. If no conflicts were encountered,
551 551 it will be committed immediately.
552 552
553 553 If REV is the parent of the working directory, then this new changeset
554 554 is committed automatically (unless --no-commit is specified).
555 555
556 556 .. note::
557 557
558 558 :hg:`backout` cannot be used to fix either an unwanted or
559 559 incorrect merge.
560 560
561 561 .. container:: verbose
562 562
563 563 Examples:
564 564
565 565 - Reverse the effect of the parent of the working directory.
566 566 This backout will be committed immediately::
567 567
568 568 hg backout -r .
569 569
570 570 - Reverse the effect of previous bad revision 23::
571 571
572 572 hg backout -r 23
573 573
574 574 - Reverse the effect of previous bad revision 23 and
575 575 leave changes uncommitted::
576 576
577 577 hg backout -r 23 --no-commit
578 578 hg commit -m "Backout revision 23"
579 579
580 580 By default, the pending changeset will have one parent,
581 581 maintaining a linear history. With --merge, the pending
582 582 changeset will instead have two parents: the old parent of the
583 583 working directory and a new child of REV that simply undoes REV.
584 584
585 585 Before version 1.7, the behavior without --merge was equivalent
586 586 to specifying --merge followed by :hg:`update --clean .` to
587 587 cancel the merge and leave the child of REV as a head to be
588 588 merged separately.
589 589
590 590 See :hg:`help dates` for a list of formats valid for -d/--date.
591 591
592 592 See :hg:`help revert` for a way to restore files to the state
593 593 of another revision.
594 594
595 595 Returns 0 on success, 1 if nothing to backout or there are unresolved
596 596 files.
597 597 '''
598 598 with repo.wlock(), repo.lock():
599 599 return _dobackout(ui, repo, node, rev, **opts)
600 600
601 601 def _dobackout(ui, repo, node=None, rev=None, **opts):
602 602 opts = pycompat.byteskwargs(opts)
603 603 if opts.get('commit') and opts.get('no_commit'):
604 604 raise error.Abort(_("cannot use --commit with --no-commit"))
605 605 if opts.get('merge') and opts.get('no_commit'):
606 606 raise error.Abort(_("cannot use --merge with --no-commit"))
607 607
608 608 if rev and node:
609 609 raise error.Abort(_("please specify just one revision"))
610 610
611 611 if not rev:
612 612 rev = node
613 613
614 614 if not rev:
615 615 raise error.Abort(_("please specify a revision to backout"))
616 616
617 617 date = opts.get('date')
618 618 if date:
619 619 opts['date'] = dateutil.parsedate(date)
620 620
621 621 cmdutil.checkunfinished(repo)
622 622 cmdutil.bailifchanged(repo)
623 623 node = scmutil.revsingle(repo, rev).node()
624 624
625 625 op1, op2 = repo.dirstate.parents()
626 626 if not repo.changelog.isancestor(node, op1):
627 627 raise error.Abort(_('cannot backout change that is not an ancestor'))
628 628
629 629 p1, p2 = repo.changelog.parents(node)
630 630 if p1 == nullid:
631 631 raise error.Abort(_('cannot backout a change with no parents'))
632 632 if p2 != nullid:
633 633 if not opts.get('parent'):
634 634 raise error.Abort(_('cannot backout a merge changeset'))
635 635 p = repo.lookup(opts['parent'])
636 636 if p not in (p1, p2):
637 637 raise error.Abort(_('%s is not a parent of %s') %
638 638 (short(p), short(node)))
639 639 parent = p
640 640 else:
641 641 if opts.get('parent'):
642 642 raise error.Abort(_('cannot use --parent on non-merge changeset'))
643 643 parent = p1
644 644
645 645 # the backout should appear on the same branch
646 646 branch = repo.dirstate.branch()
647 647 bheads = repo.branchheads(branch)
648 648 rctx = scmutil.revsingle(repo, hex(parent))
649 649 if not opts.get('merge') and op1 != node:
650 650 with dirstateguard.dirstateguard(repo, 'backout'):
651 651 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
652 652 with ui.configoverride(overrides, 'backout'):
653 653 stats = mergemod.update(repo, parent, branchmerge=True,
654 654 force=True, ancestor=node,
655 655 mergeancestor=False)
656 656 repo.setparents(op1, op2)
657 657 hg._showstats(repo, stats)
658 658 if stats.unresolvedcount:
659 659 repo.ui.status(_("use 'hg resolve' to retry unresolved "
660 660 "file merges\n"))
661 661 return 1
662 662 else:
663 663 hg.clean(repo, node, show_stats=False)
664 664 repo.dirstate.setbranch(branch)
665 665 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
666 666
667 667 if opts.get('no_commit'):
668 668 msg = _("changeset %s backed out, "
669 669 "don't forget to commit.\n")
670 670 ui.status(msg % short(node))
671 671 return 0
672 672
673 673 def commitfunc(ui, repo, message, match, opts):
674 674 editform = 'backout'
675 675 e = cmdutil.getcommiteditor(editform=editform,
676 676 **pycompat.strkwargs(opts))
677 677 if not message:
678 678 # we don't translate commit messages
679 679 message = "Backed out changeset %s" % short(node)
680 680 e = cmdutil.getcommiteditor(edit=True, editform=editform)
681 681 return repo.commit(message, opts.get('user'), opts.get('date'),
682 682 match, editor=e)
683 683 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
684 684 if not newnode:
685 685 ui.status(_("nothing changed\n"))
686 686 return 1
687 687 cmdutil.commitstatus(repo, newnode, branch, bheads)
688 688
689 689 def nice(node):
690 690 return '%d:%s' % (repo.changelog.rev(node), short(node))
691 691 ui.status(_('changeset %s backs out changeset %s\n') %
692 692 (nice(repo.changelog.tip()), nice(node)))
693 693 if opts.get('merge') and op1 != node:
694 694 hg.clean(repo, op1, show_stats=False)
695 695 ui.status(_('merging with changeset %s\n')
696 696 % nice(repo.changelog.tip()))
697 697 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
698 698 with ui.configoverride(overrides, 'backout'):
699 699 return hg.merge(repo, hex(repo.changelog.tip()))
700 700 return 0
701 701
702 702 @command('bisect',
703 703 [('r', 'reset', False, _('reset bisect state')),
704 704 ('g', 'good', False, _('mark changeset good')),
705 705 ('b', 'bad', False, _('mark changeset bad')),
706 706 ('s', 'skip', False, _('skip testing changeset')),
707 707 ('e', 'extend', False, _('extend the bisect range')),
708 708 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
709 709 ('U', 'noupdate', False, _('do not update to target'))],
710 710 _("[-gbsr] [-U] [-c CMD] [REV]"),
711 711 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
712 712 def bisect(ui, repo, rev=None, extra=None, command=None,
713 713 reset=None, good=None, bad=None, skip=None, extend=None,
714 714 noupdate=None):
715 715 """subdivision search of changesets
716 716
717 717 This command helps to find changesets which introduce problems. To
718 718 use, mark the earliest changeset you know exhibits the problem as
719 719 bad, then mark the latest changeset which is free from the problem
720 720 as good. Bisect will update your working directory to a revision
721 721 for testing (unless the -U/--noupdate option is specified). Once
722 722 you have performed tests, mark the working directory as good or
723 723 bad, and bisect will either update to another candidate changeset
724 724 or announce that it has found the bad revision.
725 725
726 726 As a shortcut, you can also use the revision argument to mark a
727 727 revision as good or bad without checking it out first.
728 728
729 729 If you supply a command, it will be used for automatic bisection.
730 730 The environment variable HG_NODE will contain the ID of the
731 731 changeset being tested. The exit status of the command will be
732 732 used to mark revisions as good or bad: status 0 means good, 125
733 733 means to skip the revision, 127 (command not found) will abort the
734 734 bisection, and any other non-zero exit status means the revision
735 735 is bad.
736 736
737 737 .. container:: verbose
738 738
739 739 Some examples:
740 740
741 741 - start a bisection with known bad revision 34, and good revision 12::
742 742
743 743 hg bisect --bad 34
744 744 hg bisect --good 12
745 745
746 746 - advance the current bisection by marking current revision as good or
747 747 bad::
748 748
749 749 hg bisect --good
750 750 hg bisect --bad
751 751
752 752 - mark the current revision, or a known revision, to be skipped (e.g. if
753 753 that revision is not usable because of another issue)::
754 754
755 755 hg bisect --skip
756 756 hg bisect --skip 23
757 757
758 758 - skip all revisions that do not touch directories ``foo`` or ``bar``::
759 759
760 760 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
761 761
762 762 - forget the current bisection::
763 763
764 764 hg bisect --reset
765 765
766 766 - use 'make && make tests' to automatically find the first broken
767 767 revision::
768 768
769 769 hg bisect --reset
770 770 hg bisect --bad 34
771 771 hg bisect --good 12
772 772 hg bisect --command "make && make tests"
773 773
774 774 - see all changesets whose states are already known in the current
775 775 bisection::
776 776
777 777 hg log -r "bisect(pruned)"
778 778
779 779 - see the changeset currently being bisected (especially useful
780 780 if running with -U/--noupdate)::
781 781
782 782 hg log -r "bisect(current)"
783 783
784 784 - see all changesets that took part in the current bisection::
785 785
786 786 hg log -r "bisect(range)"
787 787
788 788 - you can even get a nice graph::
789 789
790 790 hg log --graph -r "bisect(range)"
791 791
792 792 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
793 793
794 794 Returns 0 on success.
795 795 """
796 796 # backward compatibility
797 797 if rev in "good bad reset init".split():
798 798 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
799 799 cmd, rev, extra = rev, extra, None
800 800 if cmd == "good":
801 801 good = True
802 802 elif cmd == "bad":
803 803 bad = True
804 804 else:
805 805 reset = True
806 806 elif extra:
807 807 raise error.Abort(_('incompatible arguments'))
808 808
809 809 incompatibles = {
810 810 '--bad': bad,
811 811 '--command': bool(command),
812 812 '--extend': extend,
813 813 '--good': good,
814 814 '--reset': reset,
815 815 '--skip': skip,
816 816 }
817 817
818 818 enabled = [x for x in incompatibles if incompatibles[x]]
819 819
820 820 if len(enabled) > 1:
821 821 raise error.Abort(_('%s and %s are incompatible') %
822 822 tuple(sorted(enabled)[0:2]))
823 823
824 824 if reset:
825 825 hbisect.resetstate(repo)
826 826 return
827 827
828 828 state = hbisect.load_state(repo)
829 829
830 830 # update state
831 831 if good or bad or skip:
832 832 if rev:
833 833 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
834 834 else:
835 835 nodes = [repo.lookup('.')]
836 836 if good:
837 837 state['good'] += nodes
838 838 elif bad:
839 839 state['bad'] += nodes
840 840 elif skip:
841 841 state['skip'] += nodes
842 842 hbisect.save_state(repo, state)
843 843 if not (state['good'] and state['bad']):
844 844 return
845 845
846 846 def mayupdate(repo, node, show_stats=True):
847 847 """common used update sequence"""
848 848 if noupdate:
849 849 return
850 850 cmdutil.checkunfinished(repo)
851 851 cmdutil.bailifchanged(repo)
852 852 return hg.clean(repo, node, show_stats=show_stats)
853 853
854 854 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
855 855
856 856 if command:
857 857 changesets = 1
858 858 if noupdate:
859 859 try:
860 860 node = state['current'][0]
861 861 except LookupError:
862 862 raise error.Abort(_('current bisect revision is unknown - '
863 863 'start a new bisect to fix'))
864 864 else:
865 865 node, p2 = repo.dirstate.parents()
866 866 if p2 != nullid:
867 867 raise error.Abort(_('current bisect revision is a merge'))
868 868 if rev:
869 869 node = repo[scmutil.revsingle(repo, rev, node)].node()
870 870 try:
871 871 while changesets:
872 872 # update state
873 873 state['current'] = [node]
874 874 hbisect.save_state(repo, state)
875 875 status = ui.system(command, environ={'HG_NODE': hex(node)},
876 876 blockedtag='bisect_check')
877 877 if status == 125:
878 878 transition = "skip"
879 879 elif status == 0:
880 880 transition = "good"
881 881 # status < 0 means process was killed
882 882 elif status == 127:
883 883 raise error.Abort(_("failed to execute %s") % command)
884 884 elif status < 0:
885 885 raise error.Abort(_("%s killed") % command)
886 886 else:
887 887 transition = "bad"
888 888 state[transition].append(node)
889 889 ctx = repo[node]
890 890 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
891 891 transition))
892 892 hbisect.checkstate(state)
893 893 # bisect
894 894 nodes, changesets, bgood = hbisect.bisect(repo, state)
895 895 # update to next check
896 896 node = nodes[0]
897 897 mayupdate(repo, node, show_stats=False)
898 898 finally:
899 899 state['current'] = [node]
900 900 hbisect.save_state(repo, state)
901 901 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
902 902 return
903 903
904 904 hbisect.checkstate(state)
905 905
906 906 # actually bisect
907 907 nodes, changesets, good = hbisect.bisect(repo, state)
908 908 if extend:
909 909 if not changesets:
910 910 extendnode = hbisect.extendrange(repo, state, nodes, good)
911 911 if extendnode is not None:
912 912 ui.write(_("Extending search to changeset %d:%s\n")
913 913 % (extendnode.rev(), extendnode))
914 914 state['current'] = [extendnode.node()]
915 915 hbisect.save_state(repo, state)
916 916 return mayupdate(repo, extendnode.node())
917 917 raise error.Abort(_("nothing to extend"))
918 918
919 919 if changesets == 0:
920 920 hbisect.printresult(ui, repo, state, displayer, nodes, good)
921 921 else:
922 922 assert len(nodes) == 1 # only a single node can be tested next
923 923 node = nodes[0]
924 924 # compute the approximate number of remaining tests
925 925 tests, size = 0, 2
926 926 while size <= changesets:
927 927 tests, size = tests + 1, size * 2
928 928 rev = repo.changelog.rev(node)
929 929 ui.write(_("Testing changeset %d:%s "
930 930 "(%d changesets remaining, ~%d tests)\n")
931 931 % (rev, short(node), changesets, tests))
932 932 state['current'] = [node]
933 933 hbisect.save_state(repo, state)
934 934 return mayupdate(repo, node)
935 935
936 936 @command('bookmarks|bookmark',
937 937 [('f', 'force', False, _('force')),
938 938 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
939 939 ('d', 'delete', False, _('delete a given bookmark')),
940 940 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
941 941 ('i', 'inactive', False, _('mark a bookmark inactive')),
942 942 ('l', 'list', False, _('list existing bookmarks')),
943 943 ] + formatteropts,
944 944 _('hg bookmarks [OPTIONS]... [NAME]...'),
945 945 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
946 946 def bookmark(ui, repo, *names, **opts):
947 947 '''create a new bookmark or list existing bookmarks
948 948
949 949 Bookmarks are labels on changesets to help track lines of development.
950 950 Bookmarks are unversioned and can be moved, renamed and deleted.
951 951 Deleting or moving a bookmark has no effect on the associated changesets.
952 952
953 953 Creating or updating to a bookmark causes it to be marked as 'active'.
954 954 The active bookmark is indicated with a '*'.
955 955 When a commit is made, the active bookmark will advance to the new commit.
956 956 A plain :hg:`update` will also advance an active bookmark, if possible.
957 957 Updating away from a bookmark will cause it to be deactivated.
958 958
959 959 Bookmarks can be pushed and pulled between repositories (see
960 960 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
961 961 diverged, a new 'divergent bookmark' of the form 'name@path' will
962 962 be created. Using :hg:`merge` will resolve the divergence.
963 963
964 964 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
965 965 the active bookmark's name.
966 966
967 967 A bookmark named '@' has the special property that :hg:`clone` will
968 968 check it out by default if it exists.
969 969
970 970 .. container:: verbose
971 971
972 972 Template:
973 973
974 974 The following keywords are supported in addition to the common template
975 975 keywords and functions such as ``{bookmark}``. See also
976 976 :hg:`help templates`.
977 977
978 978 :active: Boolean. True if the bookmark is active.
979 979
980 980 Examples:
981 981
982 982 - create an active bookmark for a new line of development::
983 983
984 984 hg book new-feature
985 985
986 986 - create an inactive bookmark as a place marker::
987 987
988 988 hg book -i reviewed
989 989
990 990 - create an inactive bookmark on another changeset::
991 991
992 992 hg book -r .^ tested
993 993
994 994 - rename bookmark turkey to dinner::
995 995
996 996 hg book -m turkey dinner
997 997
998 998 - move the '@' bookmark from another branch::
999 999
1000 1000 hg book -f @
1001 1001
1002 1002 - print only the active bookmark name::
1003 1003
1004 1004 hg book -ql .
1005 1005 '''
1006 1006 opts = pycompat.byteskwargs(opts)
1007 1007 force = opts.get('force')
1008 1008 rev = opts.get('rev')
1009 1009 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1010 1010
1011 1011 selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
1012 1012 if len(selactions) > 1:
1013 1013 raise error.Abort(_('--%s and --%s are incompatible')
1014 1014 % tuple(selactions[:2]))
1015 1015 if selactions:
1016 1016 action = selactions[0]
1017 1017 elif names or rev:
1018 1018 action = 'add'
1019 1019 elif inactive:
1020 1020 action = 'inactive' # meaning deactivate
1021 1021 else:
1022 1022 action = 'list'
1023 1023
1024 1024 if rev and action in {'delete', 'rename', 'list'}:
1025 1025 raise error.Abort(_("--rev is incompatible with --%s") % action)
1026 1026 if inactive and action in {'delete', 'list'}:
1027 1027 raise error.Abort(_("--inactive is incompatible with --%s") % action)
1028 1028 if not names and action in {'add', 'delete'}:
1029 1029 raise error.Abort(_("bookmark name required"))
1030 1030
1031 1031 if action in {'add', 'delete', 'rename', 'inactive'}:
1032 1032 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
1033 1033 if action == 'delete':
1034 1034 names = pycompat.maplist(repo._bookmarks.expandname, names)
1035 1035 bookmarks.delete(repo, tr, names)
1036 1036 elif action == 'rename':
1037 1037 if not names:
1038 1038 raise error.Abort(_("new bookmark name required"))
1039 1039 elif len(names) > 1:
1040 1040 raise error.Abort(_("only one new bookmark name allowed"))
1041 1041 oldname = repo._bookmarks.expandname(opts['rename'])
1042 1042 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1043 1043 elif action == 'add':
1044 1044 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1045 1045 elif action == 'inactive':
1046 1046 if len(repo._bookmarks) == 0:
1047 1047 ui.status(_("no bookmarks set\n"))
1048 1048 elif not repo._activebookmark:
1049 1049 ui.status(_("no active bookmark\n"))
1050 1050 else:
1051 1051 bookmarks.deactivate(repo)
1052 1052 elif action == 'list':
1053 1053 names = pycompat.maplist(repo._bookmarks.expandname, names)
1054 1054 with ui.formatter('bookmarks', opts) as fm:
1055 1055 bookmarks.printbookmarks(ui, repo, fm, names)
1056 1056 else:
1057 1057 raise error.ProgrammingError('invalid action: %s' % action)
1058 1058
1059 1059 @command('branch',
1060 1060 [('f', 'force', None,
1061 1061 _('set branch name even if it shadows an existing branch')),
1062 1062 ('C', 'clean', None, _('reset branch name to parent branch name')),
1063 1063 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1064 1064 ],
1065 1065 _('[-fC] [NAME]'),
1066 1066 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
1067 1067 def branch(ui, repo, label=None, **opts):
1068 1068 """set or show the current branch name
1069 1069
1070 1070 .. note::
1071 1071
1072 1072 Branch names are permanent and global. Use :hg:`bookmark` to create a
1073 1073 light-weight bookmark instead. See :hg:`help glossary` for more
1074 1074 information about named branches and bookmarks.
1075 1075
1076 1076 With no argument, show the current branch name. With one argument,
1077 1077 set the working directory branch name (the branch will not exist
1078 1078 in the repository until the next commit). Standard practice
1079 1079 recommends that primary development take place on the 'default'
1080 1080 branch.
1081 1081
1082 1082 Unless -f/--force is specified, branch will not let you set a
1083 1083 branch name that already exists.
1084 1084
1085 1085 Use -C/--clean to reset the working directory branch to that of
1086 1086 the parent of the working directory, negating a previous branch
1087 1087 change.
1088 1088
1089 1089 Use the command :hg:`update` to switch to an existing branch. Use
1090 1090 :hg:`commit --close-branch` to mark this branch head as closed.
1091 1091 When all heads of a branch are closed, the branch will be
1092 1092 considered closed.
1093 1093
1094 1094 Returns 0 on success.
1095 1095 """
1096 1096 opts = pycompat.byteskwargs(opts)
1097 1097 revs = opts.get('rev')
1098 1098 if label:
1099 1099 label = label.strip()
1100 1100
1101 1101 if not opts.get('clean') and not label:
1102 1102 if revs:
1103 1103 raise error.Abort(_("no branch name specified for the revisions"))
1104 1104 ui.write("%s\n" % repo.dirstate.branch())
1105 1105 return
1106 1106
1107 1107 with repo.wlock():
1108 1108 if opts.get('clean'):
1109 1109 label = repo['.'].branch()
1110 1110 repo.dirstate.setbranch(label)
1111 1111 ui.status(_('reset working directory to branch %s\n') % label)
1112 1112 elif label:
1113 1113
1114 1114 scmutil.checknewlabel(repo, label, 'branch')
1115 1115 if revs:
1116 1116 return cmdutil.changebranch(ui, repo, revs, label)
1117 1117
1118 1118 if not opts.get('force') and label in repo.branchmap():
1119 1119 if label not in [p.branch() for p in repo[None].parents()]:
1120 1120 raise error.Abort(_('a branch of the same name already'
1121 1121 ' exists'),
1122 1122 # i18n: "it" refers to an existing branch
1123 1123 hint=_("use 'hg update' to switch to it"))
1124 1124
1125 1125 repo.dirstate.setbranch(label)
1126 1126 ui.status(_('marked working directory as branch %s\n') % label)
1127 1127
1128 1128 # find any open named branches aside from default
1129 1129 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1130 1130 if n != "default" and not c]
1131 1131 if not others:
1132 1132 ui.status(_('(branches are permanent and global, '
1133 1133 'did you want a bookmark?)\n'))
1134 1134
1135 1135 @command('branches',
1136 1136 [('a', 'active', False,
1137 1137 _('show only branches that have unmerged heads (DEPRECATED)')),
1138 1138 ('c', 'closed', False, _('show normal and closed branches')),
1139 1139 ('r', 'rev', [], _('show branch name(s) of the given rev'))
1140 1140 ] + formatteropts,
1141 1141 _('[-c]'),
1142 1142 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1143 1143 intents={INTENT_READONLY})
1144 1144 def branches(ui, repo, active=False, closed=False, **opts):
1145 1145 """list repository named branches
1146 1146
1147 1147 List the repository's named branches, indicating which ones are
1148 1148 inactive. If -c/--closed is specified, also list branches which have
1149 1149 been marked closed (see :hg:`commit --close-branch`).
1150 1150
1151 1151 Use the command :hg:`update` to switch to an existing branch.
1152 1152
1153 1153 .. container:: verbose
1154 1154
1155 1155 Template:
1156 1156
1157 1157 The following keywords are supported in addition to the common template
1158 1158 keywords and functions such as ``{branch}``. See also
1159 1159 :hg:`help templates`.
1160 1160
1161 1161 :active: Boolean. True if the branch is active.
1162 1162 :closed: Boolean. True if the branch is closed.
1163 1163 :current: Boolean. True if it is the current branch.
1164 1164
1165 1165 Returns 0.
1166 1166 """
1167 1167
1168 1168 opts = pycompat.byteskwargs(opts)
1169 1169 revs = opts.get('rev')
1170 1170 selectedbranches = None
1171 1171 if revs:
1172 1172 revs = scmutil.revrange(repo, revs)
1173 1173 getbi = repo.revbranchcache().branchinfo
1174 1174 selectedbranches = {getbi(r)[0] for r in revs}
1175 1175
1176 1176 ui.pager('branches')
1177 1177 fm = ui.formatter('branches', opts)
1178 1178 hexfunc = fm.hexfunc
1179 1179
1180 1180 allheads = set(repo.heads())
1181 1181 branches = []
1182 1182 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1183 1183 if selectedbranches is not None and tag not in selectedbranches:
1184 1184 continue
1185 1185 isactive = False
1186 1186 if not isclosed:
1187 1187 openheads = set(repo.branchmap().iteropen(heads))
1188 1188 isactive = bool(openheads & allheads)
1189 1189 branches.append((tag, repo[tip], isactive, not isclosed))
1190 1190 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1191 1191 reverse=True)
1192 1192
1193 1193 for tag, ctx, isactive, isopen in branches:
1194 1194 if active and not isactive:
1195 1195 continue
1196 1196 if isactive:
1197 1197 label = 'branches.active'
1198 1198 notice = ''
1199 1199 elif not isopen:
1200 1200 if not closed:
1201 1201 continue
1202 1202 label = 'branches.closed'
1203 1203 notice = _(' (closed)')
1204 1204 else:
1205 1205 label = 'branches.inactive'
1206 1206 notice = _(' (inactive)')
1207 1207 current = (tag == repo.dirstate.branch())
1208 1208 if current:
1209 1209 label = 'branches.current'
1210 1210
1211 1211 fm.startitem()
1212 1212 fm.write('branch', '%s', tag, label=label)
1213 1213 rev = ctx.rev()
1214 1214 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1215 1215 fmt = ' ' * padsize + ' %d:%s'
1216 1216 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1217 1217 label='log.changeset changeset.%s' % ctx.phasestr())
1218 1218 fm.context(ctx=ctx)
1219 1219 fm.data(active=isactive, closed=not isopen, current=current)
1220 1220 if not ui.quiet:
1221 1221 fm.plain(notice)
1222 1222 fm.plain('\n')
1223 1223 fm.end()
1224 1224
1225 1225 @command('bundle',
1226 1226 [('f', 'force', None, _('run even when the destination is unrelated')),
1227 1227 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1228 1228 _('REV')),
1229 1229 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1230 1230 _('BRANCH')),
1231 1231 ('', 'base', [],
1232 1232 _('a base changeset assumed to be available at the destination'),
1233 1233 _('REV')),
1234 1234 ('a', 'all', None, _('bundle all changesets in the repository')),
1235 1235 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1236 1236 ] + remoteopts,
1237 1237 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1238 1238 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1239 1239 def bundle(ui, repo, fname, dest=None, **opts):
1240 1240 """create a bundle file
1241 1241
1242 1242 Generate a bundle file containing data to be transferred to another
1243 1243 repository.
1244 1244
1245 1245 To create a bundle containing all changesets, use -a/--all
1246 1246 (or --base null). Otherwise, hg assumes the destination will have
1247 1247 all the nodes you specify with --base parameters. Otherwise, hg
1248 1248 will assume the repository has all the nodes in destination, or
1249 1249 default-push/default if no destination is specified, where destination
1250 1250 is the repository you provide through DEST option.
1251 1251
1252 1252 You can change bundle format with the -t/--type option. See
1253 1253 :hg:`help bundlespec` for documentation on this format. By default,
1254 1254 the most appropriate format is used and compression defaults to
1255 1255 bzip2.
1256 1256
1257 1257 The bundle file can then be transferred using conventional means
1258 1258 and applied to another repository with the unbundle or pull
1259 1259 command. This is useful when direct push and pull are not
1260 1260 available or when exporting an entire repository is undesirable.
1261 1261
1262 1262 Applying bundles preserves all changeset contents including
1263 1263 permissions, copy/rename information, and revision history.
1264 1264
1265 1265 Returns 0 on success, 1 if no changes found.
1266 1266 """
1267 1267 opts = pycompat.byteskwargs(opts)
1268 1268 revs = None
1269 1269 if 'rev' in opts:
1270 1270 revstrings = opts['rev']
1271 1271 revs = scmutil.revrange(repo, revstrings)
1272 1272 if revstrings and not revs:
1273 1273 raise error.Abort(_('no commits to bundle'))
1274 1274
1275 1275 bundletype = opts.get('type', 'bzip2').lower()
1276 1276 try:
1277 1277 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1278 1278 except error.UnsupportedBundleSpecification as e:
1279 1279 raise error.Abort(pycompat.bytestr(e),
1280 1280 hint=_("see 'hg help bundlespec' for supported "
1281 1281 "values for --type"))
1282 1282 cgversion = bundlespec.contentopts["cg.version"]
1283 1283
1284 1284 # Packed bundles are a pseudo bundle format for now.
1285 1285 if cgversion == 's1':
1286 1286 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1287 1287 hint=_("use 'hg debugcreatestreamclonebundle'"))
1288 1288
1289 1289 if opts.get('all'):
1290 1290 if dest:
1291 1291 raise error.Abort(_("--all is incompatible with specifying "
1292 1292 "a destination"))
1293 1293 if opts.get('base'):
1294 1294 ui.warn(_("ignoring --base because --all was specified\n"))
1295 1295 base = [nullrev]
1296 1296 else:
1297 1297 base = scmutil.revrange(repo, opts.get('base'))
1298 1298 if cgversion not in changegroup.supportedoutgoingversions(repo):
1299 1299 raise error.Abort(_("repository does not support bundle version %s") %
1300 1300 cgversion)
1301 1301
1302 1302 if base:
1303 1303 if dest:
1304 1304 raise error.Abort(_("--base is incompatible with specifying "
1305 1305 "a destination"))
1306 1306 common = [repo[rev].node() for rev in base]
1307 1307 heads = [repo[r].node() for r in revs] if revs else None
1308 1308 outgoing = discovery.outgoing(repo, common, heads)
1309 1309 else:
1310 1310 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1311 1311 dest, branches = hg.parseurl(dest, opts.get('branch'))
1312 1312 other = hg.peer(repo, opts, dest)
1313 1313 revs = [repo[r].hex() for r in revs]
1314 1314 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1315 1315 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1316 1316 outgoing = discovery.findcommonoutgoing(repo, other,
1317 1317 onlyheads=heads,
1318 1318 force=opts.get('force'),
1319 1319 portable=True)
1320 1320
1321 1321 if not outgoing.missing:
1322 1322 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1323 1323 return 1
1324 1324
1325 1325 if cgversion == '01': #bundle1
1326 1326 bversion = 'HG10' + bundlespec.wirecompression
1327 1327 bcompression = None
1328 1328 elif cgversion in ('02', '03'):
1329 1329 bversion = 'HG20'
1330 1330 bcompression = bundlespec.wirecompression
1331 1331 else:
1332 1332 raise error.ProgrammingError(
1333 1333 'bundle: unexpected changegroup version %s' % cgversion)
1334 1334
1335 1335 # TODO compression options should be derived from bundlespec parsing.
1336 1336 # This is a temporary hack to allow adjusting bundle compression
1337 1337 # level without a) formalizing the bundlespec changes to declare it
1338 1338 # b) introducing a command flag.
1339 1339 compopts = {}
1340 1340 complevel = ui.configint('experimental',
1341 1341 'bundlecomplevel.' + bundlespec.compression)
1342 1342 if complevel is None:
1343 1343 complevel = ui.configint('experimental', 'bundlecomplevel')
1344 1344 if complevel is not None:
1345 1345 compopts['level'] = complevel
1346 1346
1347 1347 # Allow overriding the bundling of obsmarker in phases through
1348 1348 # configuration while we don't have a bundle version that include them
1349 1349 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1350 1350 bundlespec.contentopts['obsolescence'] = True
1351 1351 if repo.ui.configbool('experimental', 'bundle-phases'):
1352 1352 bundlespec.contentopts['phases'] = True
1353 1353
1354 1354 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1355 1355 bundlespec.contentopts, compression=bcompression,
1356 1356 compopts=compopts)
1357 1357
1358 1358 @command('cat',
1359 1359 [('o', 'output', '',
1360 1360 _('print output to file with formatted name'), _('FORMAT')),
1361 1361 ('r', 'rev', '', _('print the given revision'), _('REV')),
1362 1362 ('', 'decode', None, _('apply any matching decode filter')),
1363 1363 ] + walkopts + formatteropts,
1364 1364 _('[OPTION]... FILE...'),
1365 1365 helpcategory=command.CATEGORY_FILE_CONTENTS,
1366 1366 inferrepo=True,
1367 1367 intents={INTENT_READONLY})
1368 1368 def cat(ui, repo, file1, *pats, **opts):
1369 1369 """output the current or given revision of files
1370 1370
1371 1371 Print the specified files as they were at the given revision. If
1372 1372 no revision is given, the parent of the working directory is used.
1373 1373
1374 1374 Output may be to a file, in which case the name of the file is
1375 1375 given using a template string. See :hg:`help templates`. In addition
1376 1376 to the common template keywords, the following formatting rules are
1377 1377 supported:
1378 1378
1379 1379 :``%%``: literal "%" character
1380 1380 :``%s``: basename of file being printed
1381 1381 :``%d``: dirname of file being printed, or '.' if in repository root
1382 1382 :``%p``: root-relative path name of file being printed
1383 1383 :``%H``: changeset hash (40 hexadecimal digits)
1384 1384 :``%R``: changeset revision number
1385 1385 :``%h``: short-form changeset hash (12 hexadecimal digits)
1386 1386 :``%r``: zero-padded changeset revision number
1387 1387 :``%b``: basename of the exporting repository
1388 1388 :``\\``: literal "\\" character
1389 1389
1390 1390 .. container:: verbose
1391 1391
1392 1392 Template:
1393 1393
1394 1394 The following keywords are supported in addition to the common template
1395 1395 keywords and functions. See also :hg:`help templates`.
1396 1396
1397 1397 :data: String. File content.
1398 1398 :path: String. Repository-absolute path of the file.
1399 1399
1400 1400 Returns 0 on success.
1401 1401 """
1402 1402 opts = pycompat.byteskwargs(opts)
1403 1403 rev = opts.get('rev')
1404 1404 if rev:
1405 1405 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1406 1406 ctx = scmutil.revsingle(repo, rev)
1407 1407 m = scmutil.match(ctx, (file1,) + pats, opts)
1408 1408 fntemplate = opts.pop('output', '')
1409 1409 if cmdutil.isstdiofilename(fntemplate):
1410 1410 fntemplate = ''
1411 1411
1412 1412 if fntemplate:
1413 1413 fm = formatter.nullformatter(ui, 'cat', opts)
1414 1414 else:
1415 1415 ui.pager('cat')
1416 1416 fm = ui.formatter('cat', opts)
1417 1417 with fm:
1418 1418 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1419 1419 **pycompat.strkwargs(opts))
1420 1420
1421 1421 @command('clone',
1422 1422 [('U', 'noupdate', None, _('the clone will include an empty working '
1423 1423 'directory (only a repository)')),
1424 1424 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1425 1425 _('REV')),
1426 1426 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1427 1427 ' and its ancestors'), _('REV')),
1428 1428 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1429 1429 ' changesets and their ancestors'), _('BRANCH')),
1430 1430 ('', 'pull', None, _('use pull protocol to copy metadata')),
1431 1431 ('', 'uncompressed', None,
1432 1432 _('an alias to --stream (DEPRECATED)')),
1433 1433 ('', 'stream', None,
1434 1434 _('clone with minimal data processing')),
1435 1435 ] + remoteopts,
1436 1436 _('[OPTION]... SOURCE [DEST]'),
1437 1437 helpcategory=command.CATEGORY_REPO_CREATION,
1438 1438 helpbasic=True, norepo=True)
1439 1439 def clone(ui, source, dest=None, **opts):
1440 1440 """make a copy of an existing repository
1441 1441
1442 1442 Create a copy of an existing repository in a new directory.
1443 1443
1444 1444 If no destination directory name is specified, it defaults to the
1445 1445 basename of the source.
1446 1446
1447 1447 The location of the source is added to the new repository's
1448 1448 ``.hg/hgrc`` file, as the default to be used for future pulls.
1449 1449
1450 1450 Only local paths and ``ssh://`` URLs are supported as
1451 1451 destinations. For ``ssh://`` destinations, no working directory or
1452 1452 ``.hg/hgrc`` will be created on the remote side.
1453 1453
1454 1454 If the source repository has a bookmark called '@' set, that
1455 1455 revision will be checked out in the new repository by default.
1456 1456
1457 1457 To check out a particular version, use -u/--update, or
1458 1458 -U/--noupdate to create a clone with no working directory.
1459 1459
1460 1460 To pull only a subset of changesets, specify one or more revisions
1461 1461 identifiers with -r/--rev or branches with -b/--branch. The
1462 1462 resulting clone will contain only the specified changesets and
1463 1463 their ancestors. These options (or 'clone src#rev dest') imply
1464 1464 --pull, even for local source repositories.
1465 1465
1466 1466 In normal clone mode, the remote normalizes repository data into a common
1467 1467 exchange format and the receiving end translates this data into its local
1468 1468 storage format. --stream activates a different clone mode that essentially
1469 1469 copies repository files from the remote with minimal data processing. This
1470 1470 significantly reduces the CPU cost of a clone both remotely and locally.
1471 1471 However, it often increases the transferred data size by 30-40%. This can
1472 1472 result in substantially faster clones where I/O throughput is plentiful,
1473 1473 especially for larger repositories. A side-effect of --stream clones is
1474 1474 that storage settings and requirements on the remote are applied locally:
1475 1475 a modern client may inherit legacy or inefficient storage used by the
1476 1476 remote or a legacy Mercurial client may not be able to clone from a
1477 1477 modern Mercurial remote.
1478 1478
1479 1479 .. note::
1480 1480
1481 1481 Specifying a tag will include the tagged changeset but not the
1482 1482 changeset containing the tag.
1483 1483
1484 1484 .. container:: verbose
1485 1485
1486 1486 For efficiency, hardlinks are used for cloning whenever the
1487 1487 source and destination are on the same filesystem (note this
1488 1488 applies only to the repository data, not to the working
1489 1489 directory). Some filesystems, such as AFS, implement hardlinking
1490 1490 incorrectly, but do not report errors. In these cases, use the
1491 1491 --pull option to avoid hardlinking.
1492 1492
1493 1493 Mercurial will update the working directory to the first applicable
1494 1494 revision from this list:
1495 1495
1496 1496 a) null if -U or the source repository has no changesets
1497 1497 b) if -u . and the source repository is local, the first parent of
1498 1498 the source repository's working directory
1499 1499 c) the changeset specified with -u (if a branch name, this means the
1500 1500 latest head of that branch)
1501 1501 d) the changeset specified with -r
1502 1502 e) the tipmost head specified with -b
1503 1503 f) the tipmost head specified with the url#branch source syntax
1504 1504 g) the revision marked with the '@' bookmark, if present
1505 1505 h) the tipmost head of the default branch
1506 1506 i) tip
1507 1507
1508 1508 When cloning from servers that support it, Mercurial may fetch
1509 1509 pre-generated data from a server-advertised URL or inline from the
1510 1510 same stream. When this is done, hooks operating on incoming changesets
1511 1511 and changegroups may fire more than once, once for each pre-generated
1512 1512 bundle and as well as for any additional remaining data. In addition,
1513 1513 if an error occurs, the repository may be rolled back to a partial
1514 1514 clone. This behavior may change in future releases.
1515 1515 See :hg:`help -e clonebundles` for more.
1516 1516
1517 1517 Examples:
1518 1518
1519 1519 - clone a remote repository to a new directory named hg/::
1520 1520
1521 1521 hg clone https://www.mercurial-scm.org/repo/hg/
1522 1522
1523 1523 - create a lightweight local clone::
1524 1524
1525 1525 hg clone project/ project-feature/
1526 1526
1527 1527 - clone from an absolute path on an ssh server (note double-slash)::
1528 1528
1529 1529 hg clone ssh://user@server//home/projects/alpha/
1530 1530
1531 1531 - do a streaming clone while checking out a specified version::
1532 1532
1533 1533 hg clone --stream http://server/repo -u 1.5
1534 1534
1535 1535 - create a repository without changesets after a particular revision::
1536 1536
1537 1537 hg clone -r 04e544 experimental/ good/
1538 1538
1539 1539 - clone (and track) a particular named branch::
1540 1540
1541 1541 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1542 1542
1543 1543 See :hg:`help urls` for details on specifying URLs.
1544 1544
1545 1545 Returns 0 on success.
1546 1546 """
1547 1547 opts = pycompat.byteskwargs(opts)
1548 1548 if opts.get('noupdate') and opts.get('updaterev'):
1549 1549 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1550 1550
1551 1551 # --include/--exclude can come from narrow or sparse.
1552 1552 includepats, excludepats = None, None
1553 1553
1554 1554 # hg.clone() differentiates between None and an empty set. So make sure
1555 1555 # patterns are sets if narrow is requested without patterns.
1556 1556 if opts.get('narrow'):
1557 1557 includepats = set()
1558 1558 excludepats = set()
1559 1559
1560 1560 if opts.get('include'):
1561 1561 includepats = narrowspec.parsepatterns(opts.get('include'))
1562 1562 if opts.get('exclude'):
1563 1563 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1564 1564
1565 1565 r = hg.clone(ui, opts, source, dest,
1566 1566 pull=opts.get('pull'),
1567 1567 stream=opts.get('stream') or opts.get('uncompressed'),
1568 1568 revs=opts.get('rev'),
1569 1569 update=opts.get('updaterev') or not opts.get('noupdate'),
1570 1570 branch=opts.get('branch'),
1571 1571 shareopts=opts.get('shareopts'),
1572 1572 storeincludepats=includepats,
1573 1573 storeexcludepats=excludepats,
1574 1574 depth=opts.get('depth') or None)
1575 1575
1576 1576 return r is None
1577 1577
1578 1578 @command('commit|ci',
1579 1579 [('A', 'addremove', None,
1580 1580 _('mark new/missing files as added/removed before committing')),
1581 1581 ('', 'close-branch', None,
1582 1582 _('mark a branch head as closed')),
1583 1583 ('', 'amend', None, _('amend the parent of the working directory')),
1584 1584 ('s', 'secret', None, _('use the secret phase for committing')),
1585 1585 ('e', 'edit', None, _('invoke editor on commit messages')),
1586 1586 ('i', 'interactive', None, _('use interactive mode')),
1587 1587 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1588 1588 _('[OPTION]... [FILE]...'),
1589 1589 helpcategory=command.CATEGORY_COMMITTING, helpbasic=True,
1590 1590 inferrepo=True)
1591 1591 def commit(ui, repo, *pats, **opts):
1592 1592 """commit the specified files or all outstanding changes
1593 1593
1594 1594 Commit changes to the given files into the repository. Unlike a
1595 1595 centralized SCM, this operation is a local operation. See
1596 1596 :hg:`push` for a way to actively distribute your changes.
1597 1597
1598 1598 If a list of files is omitted, all changes reported by :hg:`status`
1599 1599 will be committed.
1600 1600
1601 1601 If you are committing the result of a merge, do not provide any
1602 1602 filenames or -I/-X filters.
1603 1603
1604 1604 If no commit message is specified, Mercurial starts your
1605 1605 configured editor where you can enter a message. In case your
1606 1606 commit fails, you will find a backup of your message in
1607 1607 ``.hg/last-message.txt``.
1608 1608
1609 1609 The --close-branch flag can be used to mark the current branch
1610 1610 head closed. When all heads of a branch are closed, the branch
1611 1611 will be considered closed and no longer listed.
1612 1612
1613 1613 The --amend flag can be used to amend the parent of the
1614 1614 working directory with a new commit that contains the changes
1615 1615 in the parent in addition to those currently reported by :hg:`status`,
1616 1616 if there are any. The old commit is stored in a backup bundle in
1617 1617 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1618 1618 on how to restore it).
1619 1619
1620 1620 Message, user and date are taken from the amended commit unless
1621 1621 specified. When a message isn't specified on the command line,
1622 1622 the editor will open with the message of the amended commit.
1623 1623
1624 1624 It is not possible to amend public changesets (see :hg:`help phases`)
1625 1625 or changesets that have children.
1626 1626
1627 1627 See :hg:`help dates` for a list of formats valid for -d/--date.
1628 1628
1629 1629 Returns 0 on success, 1 if nothing changed.
1630 1630
1631 1631 .. container:: verbose
1632 1632
1633 1633 Examples:
1634 1634
1635 1635 - commit all files ending in .py::
1636 1636
1637 1637 hg commit --include "set:**.py"
1638 1638
1639 1639 - commit all non-binary files::
1640 1640
1641 1641 hg commit --exclude "set:binary()"
1642 1642
1643 1643 - amend the current commit and set the date to now::
1644 1644
1645 1645 hg commit --amend --date now
1646 1646 """
1647 1647 with repo.wlock(), repo.lock():
1648 1648 return _docommit(ui, repo, *pats, **opts)
1649 1649
1650 1650 def _docommit(ui, repo, *pats, **opts):
1651 1651 if opts.get(r'interactive'):
1652 1652 opts.pop(r'interactive')
1653 1653 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1654 1654 cmdutil.recordfilter, *pats,
1655 1655 **opts)
1656 1656 # ret can be 0 (no changes to record) or the value returned by
1657 1657 # commit(), 1 if nothing changed or None on success.
1658 1658 return 1 if ret == 0 else ret
1659 1659
1660 1660 opts = pycompat.byteskwargs(opts)
1661 1661 if opts.get('subrepos'):
1662 1662 if opts.get('amend'):
1663 1663 raise error.Abort(_('cannot amend with --subrepos'))
1664 1664 # Let --subrepos on the command line override config setting.
1665 1665 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1666 1666
1667 1667 cmdutil.checkunfinished(repo, commit=True)
1668 1668
1669 1669 branch = repo[None].branch()
1670 1670 bheads = repo.branchheads(branch)
1671 1671
1672 1672 extra = {}
1673 1673 if opts.get('close_branch'):
1674 1674 extra['close'] = '1'
1675 1675
1676 1676 if not bheads:
1677 1677 raise error.Abort(_('can only close branch heads'))
1678 1678 elif opts.get('amend'):
1679 1679 if repo['.'].p1().branch() != branch and \
1680 1680 repo['.'].p2().branch() != branch:
1681 1681 raise error.Abort(_('can only close branch heads'))
1682 1682
1683 1683 if opts.get('amend'):
1684 1684 if ui.configbool('ui', 'commitsubrepos'):
1685 1685 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1686 1686
1687 1687 old = repo['.']
1688 1688 rewriteutil.precheck(repo, [old.rev()], 'amend')
1689 1689
1690 1690 # Currently histedit gets confused if an amend happens while histedit
1691 1691 # is in progress. Since we have a checkunfinished command, we are
1692 1692 # temporarily honoring it.
1693 1693 #
1694 1694 # Note: eventually this guard will be removed. Please do not expect
1695 1695 # this behavior to remain.
1696 1696 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1697 1697 cmdutil.checkunfinished(repo)
1698 1698
1699 1699 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1700 1700 if node == old.node():
1701 1701 ui.status(_("nothing changed\n"))
1702 1702 return 1
1703 1703 else:
1704 1704 def commitfunc(ui, repo, message, match, opts):
1705 1705 overrides = {}
1706 1706 if opts.get('secret'):
1707 1707 overrides[('phases', 'new-commit')] = 'secret'
1708 1708
1709 1709 baseui = repo.baseui
1710 1710 with baseui.configoverride(overrides, 'commit'):
1711 1711 with ui.configoverride(overrides, 'commit'):
1712 1712 editform = cmdutil.mergeeditform(repo[None],
1713 1713 'commit.normal')
1714 1714 editor = cmdutil.getcommiteditor(
1715 1715 editform=editform, **pycompat.strkwargs(opts))
1716 1716 return repo.commit(message,
1717 1717 opts.get('user'),
1718 1718 opts.get('date'),
1719 1719 match,
1720 1720 editor=editor,
1721 1721 extra=extra)
1722 1722
1723 1723 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1724 1724
1725 1725 if not node:
1726 1726 stat = cmdutil.postcommitstatus(repo, pats, opts)
1727 1727 if stat[3]:
1728 1728 ui.status(_("nothing changed (%d missing files, see "
1729 1729 "'hg status')\n") % len(stat[3]))
1730 1730 else:
1731 1731 ui.status(_("nothing changed\n"))
1732 1732 return 1
1733 1733
1734 1734 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1735 1735
1736 1736 @command('config|showconfig|debugconfig',
1737 1737 [('u', 'untrusted', None, _('show untrusted configuration options')),
1738 1738 ('e', 'edit', None, _('edit user config')),
1739 1739 ('l', 'local', None, _('edit repository config')),
1740 1740 ('g', 'global', None, _('edit global config'))] + formatteropts,
1741 1741 _('[-u] [NAME]...'),
1742 1742 helpcategory=command.CATEGORY_HELP,
1743 1743 optionalrepo=True,
1744 1744 intents={INTENT_READONLY})
1745 1745 def config(ui, repo, *values, **opts):
1746 1746 """show combined config settings from all hgrc files
1747 1747
1748 1748 With no arguments, print names and values of all config items.
1749 1749
1750 1750 With one argument of the form section.name, print just the value
1751 1751 of that config item.
1752 1752
1753 1753 With multiple arguments, print names and values of all config
1754 1754 items with matching section names or section.names.
1755 1755
1756 1756 With --edit, start an editor on the user-level config file. With
1757 1757 --global, edit the system-wide config file. With --local, edit the
1758 1758 repository-level config file.
1759 1759
1760 1760 With --debug, the source (filename and line number) is printed
1761 1761 for each config item.
1762 1762
1763 1763 See :hg:`help config` for more information about config files.
1764 1764
1765 1765 .. container:: verbose
1766 1766
1767 1767 Template:
1768 1768
1769 1769 The following keywords are supported. See also :hg:`help templates`.
1770 1770
1771 1771 :name: String. Config name.
1772 1772 :source: String. Filename and line number where the item is defined.
1773 1773 :value: String. Config value.
1774 1774
1775 1775 Returns 0 on success, 1 if NAME does not exist.
1776 1776
1777 1777 """
1778 1778
1779 1779 opts = pycompat.byteskwargs(opts)
1780 1780 if opts.get('edit') or opts.get('local') or opts.get('global'):
1781 1781 if opts.get('local') and opts.get('global'):
1782 1782 raise error.Abort(_("can't use --local and --global together"))
1783 1783
1784 1784 if opts.get('local'):
1785 1785 if not repo:
1786 1786 raise error.Abort(_("can't use --local outside a repository"))
1787 1787 paths = [repo.vfs.join('hgrc')]
1788 1788 elif opts.get('global'):
1789 1789 paths = rcutil.systemrcpath()
1790 1790 else:
1791 1791 paths = rcutil.userrcpath()
1792 1792
1793 1793 for f in paths:
1794 1794 if os.path.exists(f):
1795 1795 break
1796 1796 else:
1797 1797 if opts.get('global'):
1798 1798 samplehgrc = uimod.samplehgrcs['global']
1799 1799 elif opts.get('local'):
1800 1800 samplehgrc = uimod.samplehgrcs['local']
1801 1801 else:
1802 1802 samplehgrc = uimod.samplehgrcs['user']
1803 1803
1804 1804 f = paths[0]
1805 1805 fp = open(f, "wb")
1806 1806 fp.write(util.tonativeeol(samplehgrc))
1807 1807 fp.close()
1808 1808
1809 1809 editor = ui.geteditor()
1810 1810 ui.system("%s \"%s\"" % (editor, f),
1811 1811 onerr=error.Abort, errprefix=_("edit failed"),
1812 1812 blockedtag='config_edit')
1813 1813 return
1814 1814 ui.pager('config')
1815 1815 fm = ui.formatter('config', opts)
1816 1816 for t, f in rcutil.rccomponents():
1817 1817 if t == 'path':
1818 1818 ui.debug('read config from: %s\n' % f)
1819 1819 elif t == 'items':
1820 1820 for section, name, value, source in f:
1821 1821 ui.debug('set config by: %s\n' % source)
1822 1822 else:
1823 1823 raise error.ProgrammingError('unknown rctype: %s' % t)
1824 1824 untrusted = bool(opts.get('untrusted'))
1825 1825
1826 1826 selsections = selentries = []
1827 1827 if values:
1828 1828 selsections = [v for v in values if '.' not in v]
1829 1829 selentries = [v for v in values if '.' in v]
1830 1830 uniquesel = (len(selentries) == 1 and not selsections)
1831 1831 selsections = set(selsections)
1832 1832 selentries = set(selentries)
1833 1833
1834 1834 matched = False
1835 1835 for section, name, value in ui.walkconfig(untrusted=untrusted):
1836 1836 source = ui.configsource(section, name, untrusted)
1837 1837 value = pycompat.bytestr(value)
1838 1838 if fm.isplain():
1839 1839 source = source or 'none'
1840 1840 value = value.replace('\n', '\\n')
1841 1841 entryname = section + '.' + name
1842 1842 if values and not (section in selsections or entryname in selentries):
1843 1843 continue
1844 1844 fm.startitem()
1845 1845 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1846 1846 if uniquesel:
1847 1847 fm.data(name=entryname)
1848 1848 fm.write('value', '%s\n', value)
1849 1849 else:
1850 1850 fm.write('name value', '%s=%s\n', entryname, value)
1851 1851 matched = True
1852 1852 fm.end()
1853 1853 if matched:
1854 1854 return 0
1855 1855 return 1
1856 1856
1857 1857 @command('copy|cp',
1858 1858 [('A', 'after', None, _('record a copy that has already occurred')),
1859 1859 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1860 1860 ] + walkopts + dryrunopts,
1861 1861 _('[OPTION]... [SOURCE]... DEST'),
1862 1862 helpcategory=command.CATEGORY_FILE_CONTENTS)
1863 1863 def copy(ui, repo, *pats, **opts):
1864 1864 """mark files as copied for the next commit
1865 1865
1866 1866 Mark dest as having copies of source files. If dest is a
1867 1867 directory, copies are put in that directory. If dest is a file,
1868 1868 the source must be a single file.
1869 1869
1870 1870 By default, this command copies the contents of files as they
1871 1871 exist in the working directory. If invoked with -A/--after, the
1872 1872 operation is recorded, but no copying is performed.
1873 1873
1874 1874 This command takes effect with the next commit. To undo a copy
1875 1875 before that, see :hg:`revert`.
1876 1876
1877 1877 Returns 0 on success, 1 if errors are encountered.
1878 1878 """
1879 1879 opts = pycompat.byteskwargs(opts)
1880 1880 with repo.wlock(False):
1881 1881 return cmdutil.copy(ui, repo, pats, opts)
1882 1882
1883 1883 @command(
1884 1884 'debugcommands', [], _('[COMMAND]'),
1885 1885 helpcategory=command.CATEGORY_HELP,
1886 1886 norepo=True)
1887 1887 def debugcommands(ui, cmd='', *args):
1888 1888 """list all available commands and options"""
1889 1889 for cmd, vals in sorted(table.iteritems()):
1890 1890 cmd = cmd.split('|')[0]
1891 1891 opts = ', '.join([i[1] for i in vals[1]])
1892 1892 ui.write('%s: %s\n' % (cmd, opts))
1893 1893
1894 1894 @command('debugcomplete',
1895 1895 [('o', 'options', None, _('show the command options'))],
1896 1896 _('[-o] CMD'),
1897 1897 helpcategory=command.CATEGORY_HELP,
1898 1898 norepo=True)
1899 1899 def debugcomplete(ui, cmd='', **opts):
1900 1900 """returns the completion list associated with the given command"""
1901 1901
1902 1902 if opts.get(r'options'):
1903 1903 options = []
1904 1904 otables = [globalopts]
1905 1905 if cmd:
1906 1906 aliases, entry = cmdutil.findcmd(cmd, table, False)
1907 1907 otables.append(entry[1])
1908 1908 for t in otables:
1909 1909 for o in t:
1910 1910 if "(DEPRECATED)" in o[3]:
1911 1911 continue
1912 1912 if o[0]:
1913 1913 options.append('-%s' % o[0])
1914 1914 options.append('--%s' % o[1])
1915 1915 ui.write("%s\n" % "\n".join(options))
1916 1916 return
1917 1917
1918 1918 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1919 1919 if ui.verbose:
1920 1920 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1921 1921 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1922 1922
1923 1923 @command('diff',
1924 1924 [('r', 'rev', [], _('revision'), _('REV')),
1925 1925 ('c', 'change', '', _('change made by revision'), _('REV'))
1926 1926 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1927 1927 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1928 1928 helpcategory=command.CATEGORY_FILE_CONTENTS,
1929 1929 helpbasic=True, inferrepo=True, intents={INTENT_READONLY})
1930 1930 def diff(ui, repo, *pats, **opts):
1931 1931 """diff repository (or selected files)
1932 1932
1933 1933 Show differences between revisions for the specified files.
1934 1934
1935 1935 Differences between files are shown using the unified diff format.
1936 1936
1937 1937 .. note::
1938 1938
1939 1939 :hg:`diff` may generate unexpected results for merges, as it will
1940 1940 default to comparing against the working directory's first
1941 1941 parent changeset if no revisions are specified.
1942 1942
1943 1943 When two revision arguments are given, then changes are shown
1944 1944 between those revisions. If only one revision is specified then
1945 1945 that revision is compared to the working directory, and, when no
1946 1946 revisions are specified, the working directory files are compared
1947 1947 to its first parent.
1948 1948
1949 1949 Alternatively you can specify -c/--change with a revision to see
1950 1950 the changes in that changeset relative to its first parent.
1951 1951
1952 1952 Without the -a/--text option, diff will avoid generating diffs of
1953 1953 files it detects as binary. With -a, diff will generate a diff
1954 1954 anyway, probably with undesirable results.
1955 1955
1956 1956 Use the -g/--git option to generate diffs in the git extended diff
1957 1957 format. For more information, read :hg:`help diffs`.
1958 1958
1959 1959 .. container:: verbose
1960 1960
1961 1961 Examples:
1962 1962
1963 1963 - compare a file in the current working directory to its parent::
1964 1964
1965 1965 hg diff foo.c
1966 1966
1967 1967 - compare two historical versions of a directory, with rename info::
1968 1968
1969 1969 hg diff --git -r 1.0:1.2 lib/
1970 1970
1971 1971 - get change stats relative to the last change on some date::
1972 1972
1973 1973 hg diff --stat -r "date('may 2')"
1974 1974
1975 1975 - diff all newly-added files that contain a keyword::
1976 1976
1977 1977 hg diff "set:added() and grep(GNU)"
1978 1978
1979 1979 - compare a revision and its parents::
1980 1980
1981 1981 hg diff -c 9353 # compare against first parent
1982 1982 hg diff -r 9353^:9353 # same using revset syntax
1983 1983 hg diff -r 9353^2:9353 # compare against the second parent
1984 1984
1985 1985 Returns 0 on success.
1986 1986 """
1987 1987
1988 1988 opts = pycompat.byteskwargs(opts)
1989 1989 revs = opts.get('rev')
1990 1990 change = opts.get('change')
1991 1991 stat = opts.get('stat')
1992 1992 reverse = opts.get('reverse')
1993 1993
1994 1994 if revs and change:
1995 1995 msg = _('cannot specify --rev and --change at the same time')
1996 1996 raise error.Abort(msg)
1997 1997 elif change:
1998 1998 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1999 1999 ctx2 = scmutil.revsingle(repo, change, None)
2000 2000 ctx1 = ctx2.p1()
2001 2001 else:
2002 2002 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
2003 2003 ctx1, ctx2 = scmutil.revpair(repo, revs)
2004 2004 node1, node2 = ctx1.node(), ctx2.node()
2005 2005
2006 2006 if reverse:
2007 2007 node1, node2 = node2, node1
2008 2008
2009 2009 diffopts = patch.diffallopts(ui, opts)
2010 2010 m = scmutil.match(ctx2, pats, opts)
2011 2011 m = repo.narrowmatch(m)
2012 2012 ui.pager('diff')
2013 2013 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2014 2014 listsubrepos=opts.get('subrepos'),
2015 2015 root=opts.get('root'))
2016 2016
2017 2017 @command('export',
2018 2018 [('B', 'bookmark', '',
2019 2019 _('export changes only reachable by given bookmark'), _('BOOKMARK')),
2020 2020 ('o', 'output', '',
2021 2021 _('print output to file with formatted name'), _('FORMAT')),
2022 2022 ('', 'switch-parent', None, _('diff against the second parent')),
2023 2023 ('r', 'rev', [], _('revisions to export'), _('REV')),
2024 2024 ] + diffopts + formatteropts,
2025 2025 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2026 2026 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2027 2027 helpbasic=True, intents={INTENT_READONLY})
2028 2028 def export(ui, repo, *changesets, **opts):
2029 2029 """dump the header and diffs for one or more changesets
2030 2030
2031 2031 Print the changeset header and diffs for one or more revisions.
2032 2032 If no revision is given, the parent of the working directory is used.
2033 2033
2034 2034 The information shown in the changeset header is: author, date,
2035 2035 branch name (if non-default), changeset hash, parent(s) and commit
2036 2036 comment.
2037 2037
2038 2038 .. note::
2039 2039
2040 2040 :hg:`export` may generate unexpected diff output for merge
2041 2041 changesets, as it will compare the merge changeset against its
2042 2042 first parent only.
2043 2043
2044 2044 Output may be to a file, in which case the name of the file is
2045 2045 given using a template string. See :hg:`help templates`. In addition
2046 2046 to the common template keywords, the following formatting rules are
2047 2047 supported:
2048 2048
2049 2049 :``%%``: literal "%" character
2050 2050 :``%H``: changeset hash (40 hexadecimal digits)
2051 2051 :``%N``: number of patches being generated
2052 2052 :``%R``: changeset revision number
2053 2053 :``%b``: basename of the exporting repository
2054 2054 :``%h``: short-form changeset hash (12 hexadecimal digits)
2055 2055 :``%m``: first line of the commit message (only alphanumeric characters)
2056 2056 :``%n``: zero-padded sequence number, starting at 1
2057 2057 :``%r``: zero-padded changeset revision number
2058 2058 :``\\``: literal "\\" character
2059 2059
2060 2060 Without the -a/--text option, export will avoid generating diffs
2061 2061 of files it detects as binary. With -a, export will generate a
2062 2062 diff anyway, probably with undesirable results.
2063 2063
2064 2064 With -B/--bookmark changesets reachable by the given bookmark are
2065 2065 selected.
2066 2066
2067 2067 Use the -g/--git option to generate diffs in the git extended diff
2068 2068 format. See :hg:`help diffs` for more information.
2069 2069
2070 2070 With the --switch-parent option, the diff will be against the
2071 2071 second parent. It can be useful to review a merge.
2072 2072
2073 2073 .. container:: verbose
2074 2074
2075 2075 Template:
2076 2076
2077 2077 The following keywords are supported in addition to the common template
2078 2078 keywords and functions. See also :hg:`help templates`.
2079 2079
2080 2080 :diff: String. Diff content.
2081 2081 :parents: List of strings. Parent nodes of the changeset.
2082 2082
2083 2083 Examples:
2084 2084
2085 2085 - use export and import to transplant a bugfix to the current
2086 2086 branch::
2087 2087
2088 2088 hg export -r 9353 | hg import -
2089 2089
2090 2090 - export all the changesets between two revisions to a file with
2091 2091 rename information::
2092 2092
2093 2093 hg export --git -r 123:150 > changes.txt
2094 2094
2095 2095 - split outgoing changes into a series of patches with
2096 2096 descriptive names::
2097 2097
2098 2098 hg export -r "outgoing()" -o "%n-%m.patch"
2099 2099
2100 2100 Returns 0 on success.
2101 2101 """
2102 2102 opts = pycompat.byteskwargs(opts)
2103 2103 bookmark = opts.get('bookmark')
2104 2104 changesets += tuple(opts.get('rev', []))
2105 2105
2106 2106 if bookmark and changesets:
2107 2107 raise error.Abort(_("-r and -B are mutually exclusive"))
2108 2108
2109 2109 if bookmark:
2110 2110 if bookmark not in repo._bookmarks:
2111 2111 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2112 2112
2113 2113 revs = scmutil.bookmarkrevs(repo, bookmark)
2114 2114 else:
2115 2115 if not changesets:
2116 2116 changesets = ['.']
2117 2117
2118 2118 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2119 2119 revs = scmutil.revrange(repo, changesets)
2120 2120
2121 2121 if not revs:
2122 2122 raise error.Abort(_("export requires at least one changeset"))
2123 2123 if len(revs) > 1:
2124 2124 ui.note(_('exporting patches:\n'))
2125 2125 else:
2126 2126 ui.note(_('exporting patch:\n'))
2127 2127
2128 2128 fntemplate = opts.get('output')
2129 2129 if cmdutil.isstdiofilename(fntemplate):
2130 2130 fntemplate = ''
2131 2131
2132 2132 if fntemplate:
2133 2133 fm = formatter.nullformatter(ui, 'export', opts)
2134 2134 else:
2135 2135 ui.pager('export')
2136 2136 fm = ui.formatter('export', opts)
2137 2137 with fm:
2138 2138 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2139 2139 switch_parent=opts.get('switch_parent'),
2140 2140 opts=patch.diffallopts(ui, opts))
2141 2141
2142 2142 @command('files',
2143 2143 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2144 2144 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2145 2145 ] + walkopts + formatteropts + subrepoopts,
2146 2146 _('[OPTION]... [FILE]...'),
2147 2147 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2148 2148 intents={INTENT_READONLY})
2149 2149 def files(ui, repo, *pats, **opts):
2150 2150 """list tracked files
2151 2151
2152 2152 Print files under Mercurial control in the working directory or
2153 2153 specified revision for given files (excluding removed files).
2154 2154 Files can be specified as filenames or filesets.
2155 2155
2156 2156 If no files are given to match, this command prints the names
2157 2157 of all files under Mercurial control.
2158 2158
2159 2159 .. container:: verbose
2160 2160
2161 2161 Template:
2162 2162
2163 2163 The following keywords are supported in addition to the common template
2164 2164 keywords and functions. See also :hg:`help templates`.
2165 2165
2166 2166 :flags: String. Character denoting file's symlink and executable bits.
2167 2167 :path: String. Repository-absolute path of the file.
2168 2168 :size: Integer. Size of the file in bytes.
2169 2169
2170 2170 Examples:
2171 2171
2172 2172 - list all files under the current directory::
2173 2173
2174 2174 hg files .
2175 2175
2176 2176 - shows sizes and flags for current revision::
2177 2177
2178 2178 hg files -vr .
2179 2179
2180 2180 - list all files named README::
2181 2181
2182 2182 hg files -I "**/README"
2183 2183
2184 2184 - list all binary files::
2185 2185
2186 2186 hg files "set:binary()"
2187 2187
2188 2188 - find files containing a regular expression::
2189 2189
2190 2190 hg files "set:grep('bob')"
2191 2191
2192 2192 - search tracked file contents with xargs and grep::
2193 2193
2194 2194 hg files -0 | xargs -0 grep foo
2195 2195
2196 2196 See :hg:`help patterns` and :hg:`help filesets` for more information
2197 2197 on specifying file patterns.
2198 2198
2199 2199 Returns 0 if a match is found, 1 otherwise.
2200 2200
2201 2201 """
2202 2202
2203 2203 opts = pycompat.byteskwargs(opts)
2204 2204 rev = opts.get('rev')
2205 2205 if rev:
2206 2206 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2207 2207 ctx = scmutil.revsingle(repo, rev, None)
2208 2208
2209 2209 end = '\n'
2210 2210 if opts.get('print0'):
2211 2211 end = '\0'
2212 2212 fmt = '%s' + end
2213 2213
2214 2214 m = scmutil.match(ctx, pats, opts)
2215 2215 ui.pager('files')
2216 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2216 2217 with ui.formatter('files', opts) as fm:
2217 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2218 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt,
2219 opts.get('subrepos'))
2218 2220
2219 2221 @command(
2220 2222 'forget',
2221 2223 [('i', 'interactive', None, _('use interactive mode')),
2222 2224 ] + walkopts + dryrunopts,
2223 2225 _('[OPTION]... FILE...'),
2224 2226 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2225 2227 helpbasic=True, inferrepo=True)
2226 2228 def forget(ui, repo, *pats, **opts):
2227 2229 """forget the specified files on the next commit
2228 2230
2229 2231 Mark the specified files so they will no longer be tracked
2230 2232 after the next commit.
2231 2233
2232 2234 This only removes files from the current branch, not from the
2233 2235 entire project history, and it does not delete them from the
2234 2236 working directory.
2235 2237
2236 2238 To delete the file from the working directory, see :hg:`remove`.
2237 2239
2238 2240 To undo a forget before the next commit, see :hg:`add`.
2239 2241
2240 2242 .. container:: verbose
2241 2243
2242 2244 Examples:
2243 2245
2244 2246 - forget newly-added binary files::
2245 2247
2246 2248 hg forget "set:added() and binary()"
2247 2249
2248 2250 - forget files that would be excluded by .hgignore::
2249 2251
2250 2252 hg forget "set:hgignore()"
2251 2253
2252 2254 Returns 0 on success.
2253 2255 """
2254 2256
2255 2257 opts = pycompat.byteskwargs(opts)
2256 2258 if not pats:
2257 2259 raise error.Abort(_('no files specified'))
2258 2260
2259 2261 m = scmutil.match(repo[None], pats, opts)
2260 2262 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2261 2263 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2262 2264 rejected = cmdutil.forget(ui, repo, m, prefix="", uipathfn=uipathfn,
2263 2265 explicitonly=False, dryrun=dryrun,
2264 2266 interactive=interactive)[0]
2265 2267 return rejected and 1 or 0
2266 2268
2267 2269 @command(
2268 2270 'graft',
2269 2271 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2270 2272 ('', 'base', '',
2271 2273 _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
2272 2274 ('c', 'continue', False, _('resume interrupted graft')),
2273 2275 ('', 'stop', False, _('stop interrupted graft')),
2274 2276 ('', 'abort', False, _('abort interrupted graft')),
2275 2277 ('e', 'edit', False, _('invoke editor on commit messages')),
2276 2278 ('', 'log', None, _('append graft info to log message')),
2277 2279 ('', 'no-commit', None,
2278 2280 _("don't commit, just apply the changes in working directory")),
2279 2281 ('f', 'force', False, _('force graft')),
2280 2282 ('D', 'currentdate', False,
2281 2283 _('record the current date as commit date')),
2282 2284 ('U', 'currentuser', False,
2283 2285 _('record the current user as committer'))]
2284 2286 + commitopts2 + mergetoolopts + dryrunopts,
2285 2287 _('[OPTION]... [-r REV]... REV...'),
2286 2288 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
2287 2289 def graft(ui, repo, *revs, **opts):
2288 2290 '''copy changes from other branches onto the current branch
2289 2291
2290 2292 This command uses Mercurial's merge logic to copy individual
2291 2293 changes from other branches without merging branches in the
2292 2294 history graph. This is sometimes known as 'backporting' or
2293 2295 'cherry-picking'. By default, graft will copy user, date, and
2294 2296 description from the source changesets.
2295 2297
2296 2298 Changesets that are ancestors of the current revision, that have
2297 2299 already been grafted, or that are merges will be skipped.
2298 2300
2299 2301 If --log is specified, log messages will have a comment appended
2300 2302 of the form::
2301 2303
2302 2304 (grafted from CHANGESETHASH)
2303 2305
2304 2306 If --force is specified, revisions will be grafted even if they
2305 2307 are already ancestors of, or have been grafted to, the destination.
2306 2308 This is useful when the revisions have since been backed out.
2307 2309
2308 2310 If a graft merge results in conflicts, the graft process is
2309 2311 interrupted so that the current merge can be manually resolved.
2310 2312 Once all conflicts are addressed, the graft process can be
2311 2313 continued with the -c/--continue option.
2312 2314
2313 2315 The -c/--continue option reapplies all the earlier options.
2314 2316
2315 2317 .. container:: verbose
2316 2318
2317 2319 The --base option exposes more of how graft internally uses merge with a
2318 2320 custom base revision. --base can be used to specify another ancestor than
2319 2321 the first and only parent.
2320 2322
2321 2323 The command::
2322 2324
2323 2325 hg graft -r 345 --base 234
2324 2326
2325 2327 is thus pretty much the same as::
2326 2328
2327 2329 hg diff -r 234 -r 345 | hg import
2328 2330
2329 2331 but using merge to resolve conflicts and track moved files.
2330 2332
2331 2333 The result of a merge can thus be backported as a single commit by
2332 2334 specifying one of the merge parents as base, and thus effectively
2333 2335 grafting the changes from the other side.
2334 2336
2335 2337 It is also possible to collapse multiple changesets and clean up history
2336 2338 by specifying another ancestor as base, much like rebase --collapse
2337 2339 --keep.
2338 2340
2339 2341 The commit message can be tweaked after the fact using commit --amend .
2340 2342
2341 2343 For using non-ancestors as the base to backout changes, see the backout
2342 2344 command and the hidden --parent option.
2343 2345
2344 2346 .. container:: verbose
2345 2347
2346 2348 Examples:
2347 2349
2348 2350 - copy a single change to the stable branch and edit its description::
2349 2351
2350 2352 hg update stable
2351 2353 hg graft --edit 9393
2352 2354
2353 2355 - graft a range of changesets with one exception, updating dates::
2354 2356
2355 2357 hg graft -D "2085::2093 and not 2091"
2356 2358
2357 2359 - continue a graft after resolving conflicts::
2358 2360
2359 2361 hg graft -c
2360 2362
2361 2363 - show the source of a grafted changeset::
2362 2364
2363 2365 hg log --debug -r .
2364 2366
2365 2367 - show revisions sorted by date::
2366 2368
2367 2369 hg log -r "sort(all(), date)"
2368 2370
2369 2371 - backport the result of a merge as a single commit::
2370 2372
2371 2373 hg graft -r 123 --base 123^
2372 2374
2373 2375 - land a feature branch as one changeset::
2374 2376
2375 2377 hg up -cr default
2376 2378 hg graft -r featureX --base "ancestor('featureX', 'default')"
2377 2379
2378 2380 See :hg:`help revisions` for more about specifying revisions.
2379 2381
2380 2382 Returns 0 on successful completion.
2381 2383 '''
2382 2384 with repo.wlock():
2383 2385 return _dograft(ui, repo, *revs, **opts)
2384 2386
2385 2387 def _dograft(ui, repo, *revs, **opts):
2386 2388 opts = pycompat.byteskwargs(opts)
2387 2389 if revs and opts.get('rev'):
2388 2390 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2389 2391 'revision ordering!\n'))
2390 2392
2391 2393 revs = list(revs)
2392 2394 revs.extend(opts.get('rev'))
2393 2395 basectx = None
2394 2396 if opts.get('base'):
2395 2397 basectx = scmutil.revsingle(repo, opts['base'], None)
2396 2398 # a dict of data to be stored in state file
2397 2399 statedata = {}
2398 2400 # list of new nodes created by ongoing graft
2399 2401 statedata['newnodes'] = []
2400 2402
2401 2403 if opts.get('user') and opts.get('currentuser'):
2402 2404 raise error.Abort(_('--user and --currentuser are mutually exclusive'))
2403 2405 if opts.get('date') and opts.get('currentdate'):
2404 2406 raise error.Abort(_('--date and --currentdate are mutually exclusive'))
2405 2407 if not opts.get('user') and opts.get('currentuser'):
2406 2408 opts['user'] = ui.username()
2407 2409 if not opts.get('date') and opts.get('currentdate'):
2408 2410 opts['date'] = "%d %d" % dateutil.makedate()
2409 2411
2410 2412 editor = cmdutil.getcommiteditor(editform='graft',
2411 2413 **pycompat.strkwargs(opts))
2412 2414
2413 2415 cont = False
2414 2416 if opts.get('no_commit'):
2415 2417 if opts.get('edit'):
2416 2418 raise error.Abort(_("cannot specify --no-commit and "
2417 2419 "--edit together"))
2418 2420 if opts.get('currentuser'):
2419 2421 raise error.Abort(_("cannot specify --no-commit and "
2420 2422 "--currentuser together"))
2421 2423 if opts.get('currentdate'):
2422 2424 raise error.Abort(_("cannot specify --no-commit and "
2423 2425 "--currentdate together"))
2424 2426 if opts.get('log'):
2425 2427 raise error.Abort(_("cannot specify --no-commit and "
2426 2428 "--log together"))
2427 2429
2428 2430 graftstate = statemod.cmdstate(repo, 'graftstate')
2429 2431
2430 2432 if opts.get('stop'):
2431 2433 if opts.get('continue'):
2432 2434 raise error.Abort(_("cannot use '--continue' and "
2433 2435 "'--stop' together"))
2434 2436 if opts.get('abort'):
2435 2437 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2436 2438
2437 2439 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2438 2440 opts.get('date'), opts.get('currentdate'),
2439 2441 opts.get('currentuser'), opts.get('rev'))):
2440 2442 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2441 2443 return _stopgraft(ui, repo, graftstate)
2442 2444 elif opts.get('abort'):
2443 2445 if opts.get('continue'):
2444 2446 raise error.Abort(_("cannot use '--continue' and "
2445 2447 "'--abort' together"))
2446 2448 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2447 2449 opts.get('date'), opts.get('currentdate'),
2448 2450 opts.get('currentuser'), opts.get('rev'))):
2449 2451 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2450 2452
2451 2453 return _abortgraft(ui, repo, graftstate)
2452 2454 elif opts.get('continue'):
2453 2455 cont = True
2454 2456 if revs:
2455 2457 raise error.Abort(_("can't specify --continue and revisions"))
2456 2458 # read in unfinished revisions
2457 2459 if graftstate.exists():
2458 2460 statedata = _readgraftstate(repo, graftstate)
2459 2461 if statedata.get('date'):
2460 2462 opts['date'] = statedata['date']
2461 2463 if statedata.get('user'):
2462 2464 opts['user'] = statedata['user']
2463 2465 if statedata.get('log'):
2464 2466 opts['log'] = True
2465 2467 if statedata.get('no_commit'):
2466 2468 opts['no_commit'] = statedata.get('no_commit')
2467 2469 nodes = statedata['nodes']
2468 2470 revs = [repo[node].rev() for node in nodes]
2469 2471 else:
2470 2472 cmdutil.wrongtooltocontinue(repo, _('graft'))
2471 2473 else:
2472 2474 if not revs:
2473 2475 raise error.Abort(_('no revisions specified'))
2474 2476 cmdutil.checkunfinished(repo)
2475 2477 cmdutil.bailifchanged(repo)
2476 2478 revs = scmutil.revrange(repo, revs)
2477 2479
2478 2480 skipped = set()
2479 2481 if basectx is None:
2480 2482 # check for merges
2481 2483 for rev in repo.revs('%ld and merge()', revs):
2482 2484 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2483 2485 skipped.add(rev)
2484 2486 revs = [r for r in revs if r not in skipped]
2485 2487 if not revs:
2486 2488 return -1
2487 2489 if basectx is not None and len(revs) != 1:
2488 2490 raise error.Abort(_('only one revision allowed with --base '))
2489 2491
2490 2492 # Don't check in the --continue case, in effect retaining --force across
2491 2493 # --continues. That's because without --force, any revisions we decided to
2492 2494 # skip would have been filtered out here, so they wouldn't have made their
2493 2495 # way to the graftstate. With --force, any revisions we would have otherwise
2494 2496 # skipped would not have been filtered out, and if they hadn't been applied
2495 2497 # already, they'd have been in the graftstate.
2496 2498 if not (cont or opts.get('force')) and basectx is None:
2497 2499 # check for ancestors of dest branch
2498 2500 crev = repo['.'].rev()
2499 2501 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2500 2502 # XXX make this lazy in the future
2501 2503 # don't mutate while iterating, create a copy
2502 2504 for rev in list(revs):
2503 2505 if rev in ancestors:
2504 2506 ui.warn(_('skipping ancestor revision %d:%s\n') %
2505 2507 (rev, repo[rev]))
2506 2508 # XXX remove on list is slow
2507 2509 revs.remove(rev)
2508 2510 if not revs:
2509 2511 return -1
2510 2512
2511 2513 # analyze revs for earlier grafts
2512 2514 ids = {}
2513 2515 for ctx in repo.set("%ld", revs):
2514 2516 ids[ctx.hex()] = ctx.rev()
2515 2517 n = ctx.extra().get('source')
2516 2518 if n:
2517 2519 ids[n] = ctx.rev()
2518 2520
2519 2521 # check ancestors for earlier grafts
2520 2522 ui.debug('scanning for duplicate grafts\n')
2521 2523
2522 2524 # The only changesets we can be sure doesn't contain grafts of any
2523 2525 # revs, are the ones that are common ancestors of *all* revs:
2524 2526 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2525 2527 ctx = repo[rev]
2526 2528 n = ctx.extra().get('source')
2527 2529 if n in ids:
2528 2530 try:
2529 2531 r = repo[n].rev()
2530 2532 except error.RepoLookupError:
2531 2533 r = None
2532 2534 if r in revs:
2533 2535 ui.warn(_('skipping revision %d:%s '
2534 2536 '(already grafted to %d:%s)\n')
2535 2537 % (r, repo[r], rev, ctx))
2536 2538 revs.remove(r)
2537 2539 elif ids[n] in revs:
2538 2540 if r is None:
2539 2541 ui.warn(_('skipping already grafted revision %d:%s '
2540 2542 '(%d:%s also has unknown origin %s)\n')
2541 2543 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2542 2544 else:
2543 2545 ui.warn(_('skipping already grafted revision %d:%s '
2544 2546 '(%d:%s also has origin %d:%s)\n')
2545 2547 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2546 2548 revs.remove(ids[n])
2547 2549 elif ctx.hex() in ids:
2548 2550 r = ids[ctx.hex()]
2549 2551 if r in revs:
2550 2552 ui.warn(_('skipping already grafted revision %d:%s '
2551 2553 '(was grafted from %d:%s)\n') %
2552 2554 (r, repo[r], rev, ctx))
2553 2555 revs.remove(r)
2554 2556 if not revs:
2555 2557 return -1
2556 2558
2557 2559 if opts.get('no_commit'):
2558 2560 statedata['no_commit'] = True
2559 2561 for pos, ctx in enumerate(repo.set("%ld", revs)):
2560 2562 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2561 2563 ctx.description().split('\n', 1)[0])
2562 2564 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2563 2565 if names:
2564 2566 desc += ' (%s)' % ' '.join(names)
2565 2567 ui.status(_('grafting %s\n') % desc)
2566 2568 if opts.get('dry_run'):
2567 2569 continue
2568 2570
2569 2571 source = ctx.extra().get('source')
2570 2572 extra = {}
2571 2573 if source:
2572 2574 extra['source'] = source
2573 2575 extra['intermediate-source'] = ctx.hex()
2574 2576 else:
2575 2577 extra['source'] = ctx.hex()
2576 2578 user = ctx.user()
2577 2579 if opts.get('user'):
2578 2580 user = opts['user']
2579 2581 statedata['user'] = user
2580 2582 date = ctx.date()
2581 2583 if opts.get('date'):
2582 2584 date = opts['date']
2583 2585 statedata['date'] = date
2584 2586 message = ctx.description()
2585 2587 if opts.get('log'):
2586 2588 message += '\n(grafted from %s)' % ctx.hex()
2587 2589 statedata['log'] = True
2588 2590
2589 2591 # we don't merge the first commit when continuing
2590 2592 if not cont:
2591 2593 # perform the graft merge with p1(rev) as 'ancestor'
2592 2594 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2593 2595 base = ctx.p1() if basectx is None else basectx
2594 2596 with ui.configoverride(overrides, 'graft'):
2595 2597 stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
2596 2598 # report any conflicts
2597 2599 if stats.unresolvedcount > 0:
2598 2600 # write out state for --continue
2599 2601 nodes = [repo[rev].hex() for rev in revs[pos:]]
2600 2602 statedata['nodes'] = nodes
2601 2603 stateversion = 1
2602 2604 graftstate.save(stateversion, statedata)
2603 2605 hint = _("use 'hg resolve' and 'hg graft --continue'")
2604 2606 raise error.Abort(
2605 2607 _("unresolved conflicts, can't continue"),
2606 2608 hint=hint)
2607 2609 else:
2608 2610 cont = False
2609 2611
2610 2612 # commit if --no-commit is false
2611 2613 if not opts.get('no_commit'):
2612 2614 node = repo.commit(text=message, user=user, date=date, extra=extra,
2613 2615 editor=editor)
2614 2616 if node is None:
2615 2617 ui.warn(
2616 2618 _('note: graft of %d:%s created no changes to commit\n') %
2617 2619 (ctx.rev(), ctx))
2618 2620 # checking that newnodes exist because old state files won't have it
2619 2621 elif statedata.get('newnodes') is not None:
2620 2622 statedata['newnodes'].append(node)
2621 2623
2622 2624 # remove state when we complete successfully
2623 2625 if not opts.get('dry_run'):
2624 2626 graftstate.delete()
2625 2627
2626 2628 return 0
2627 2629
2628 2630 def _abortgraft(ui, repo, graftstate):
2629 2631 """abort the interrupted graft and rollbacks to the state before interrupted
2630 2632 graft"""
2631 2633 if not graftstate.exists():
2632 2634 raise error.Abort(_("no interrupted graft to abort"))
2633 2635 statedata = _readgraftstate(repo, graftstate)
2634 2636 newnodes = statedata.get('newnodes')
2635 2637 if newnodes is None:
2636 2638 # and old graft state which does not have all the data required to abort
2637 2639 # the graft
2638 2640 raise error.Abort(_("cannot abort using an old graftstate"))
2639 2641
2640 2642 # changeset from which graft operation was started
2641 2643 if len(newnodes) > 0:
2642 2644 startctx = repo[newnodes[0]].p1()
2643 2645 else:
2644 2646 startctx = repo['.']
2645 2647 # whether to strip or not
2646 2648 cleanup = False
2647 2649 if newnodes:
2648 2650 newnodes = [repo[r].rev() for r in newnodes]
2649 2651 cleanup = True
2650 2652 # checking that none of the newnodes turned public or is public
2651 2653 immutable = [c for c in newnodes if not repo[c].mutable()]
2652 2654 if immutable:
2653 2655 repo.ui.warn(_("cannot clean up public changesets %s\n")
2654 2656 % ', '.join(bytes(repo[r]) for r in immutable),
2655 2657 hint=_("see 'hg help phases' for details"))
2656 2658 cleanup = False
2657 2659
2658 2660 # checking that no new nodes are created on top of grafted revs
2659 2661 desc = set(repo.changelog.descendants(newnodes))
2660 2662 if desc - set(newnodes):
2661 2663 repo.ui.warn(_("new changesets detected on destination "
2662 2664 "branch, can't strip\n"))
2663 2665 cleanup = False
2664 2666
2665 2667 if cleanup:
2666 2668 with repo.wlock(), repo.lock():
2667 2669 hg.updaterepo(repo, startctx.node(), overwrite=True)
2668 2670 # stripping the new nodes created
2669 2671 strippoints = [c.node() for c in repo.set("roots(%ld)",
2670 2672 newnodes)]
2671 2673 repair.strip(repo.ui, repo, strippoints, backup=False)
2672 2674
2673 2675 if not cleanup:
2674 2676 # we don't update to the startnode if we can't strip
2675 2677 startctx = repo['.']
2676 2678 hg.updaterepo(repo, startctx.node(), overwrite=True)
2677 2679
2678 2680 ui.status(_("graft aborted\n"))
2679 2681 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
2680 2682 graftstate.delete()
2681 2683 return 0
2682 2684
2683 2685 def _readgraftstate(repo, graftstate):
2684 2686 """read the graft state file and return a dict of the data stored in it"""
2685 2687 try:
2686 2688 return graftstate.read()
2687 2689 except error.CorruptedState:
2688 2690 nodes = repo.vfs.read('graftstate').splitlines()
2689 2691 return {'nodes': nodes}
2690 2692
2691 2693 def _stopgraft(ui, repo, graftstate):
2692 2694 """stop the interrupted graft"""
2693 2695 if not graftstate.exists():
2694 2696 raise error.Abort(_("no interrupted graft found"))
2695 2697 pctx = repo['.']
2696 2698 hg.updaterepo(repo, pctx.node(), overwrite=True)
2697 2699 graftstate.delete()
2698 2700 ui.status(_("stopped the interrupted graft\n"))
2699 2701 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2700 2702 return 0
2701 2703
2702 2704 @command('grep',
2703 2705 [('0', 'print0', None, _('end fields with NUL')),
2704 2706 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2705 2707 ('', 'diff', None, _('print all revisions when the term was introduced '
2706 2708 'or removed')),
2707 2709 ('a', 'text', None, _('treat all files as text')),
2708 2710 ('f', 'follow', None,
2709 2711 _('follow changeset history,'
2710 2712 ' or file history across copies and renames')),
2711 2713 ('i', 'ignore-case', None, _('ignore case when matching')),
2712 2714 ('l', 'files-with-matches', None,
2713 2715 _('print only filenames and revisions that match')),
2714 2716 ('n', 'line-number', None, _('print matching line numbers')),
2715 2717 ('r', 'rev', [],
2716 2718 _('only search files changed within revision range'), _('REV')),
2717 2719 ('', 'all-files', None,
2718 2720 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2719 2721 ('u', 'user', None, _('list the author (long with -v)')),
2720 2722 ('d', 'date', None, _('list the date (short with -q)')),
2721 2723 ] + formatteropts + walkopts,
2722 2724 _('[OPTION]... PATTERN [FILE]...'),
2723 2725 helpcategory=command.CATEGORY_FILE_CONTENTS,
2724 2726 inferrepo=True,
2725 2727 intents={INTENT_READONLY})
2726 2728 def grep(ui, repo, pattern, *pats, **opts):
2727 2729 """search revision history for a pattern in specified files
2728 2730
2729 2731 Search revision history for a regular expression in the specified
2730 2732 files or the entire project.
2731 2733
2732 2734 By default, grep prints the most recent revision number for each
2733 2735 file in which it finds a match. To get it to print every revision
2734 2736 that contains a change in match status ("-" for a match that becomes
2735 2737 a non-match, or "+" for a non-match that becomes a match), use the
2736 2738 --diff flag.
2737 2739
2738 2740 PATTERN can be any Python (roughly Perl-compatible) regular
2739 2741 expression.
2740 2742
2741 2743 If no FILEs are specified (and -f/--follow isn't set), all files in
2742 2744 the repository are searched, including those that don't exist in the
2743 2745 current branch or have been deleted in a prior changeset.
2744 2746
2745 2747 .. container:: verbose
2746 2748
2747 2749 Template:
2748 2750
2749 2751 The following keywords are supported in addition to the common template
2750 2752 keywords and functions. See also :hg:`help templates`.
2751 2753
2752 2754 :change: String. Character denoting insertion ``+`` or removal ``-``.
2753 2755 Available if ``--diff`` is specified.
2754 2756 :lineno: Integer. Line number of the match.
2755 2757 :path: String. Repository-absolute path of the file.
2756 2758 :texts: List of text chunks.
2757 2759
2758 2760 And each entry of ``{texts}`` provides the following sub-keywords.
2759 2761
2760 2762 :matched: Boolean. True if the chunk matches the specified pattern.
2761 2763 :text: String. Chunk content.
2762 2764
2763 2765 See :hg:`help templates.operators` for the list expansion syntax.
2764 2766
2765 2767 Returns 0 if a match is found, 1 otherwise.
2766 2768 """
2767 2769 opts = pycompat.byteskwargs(opts)
2768 2770 diff = opts.get('all') or opts.get('diff')
2769 2771 all_files = opts.get('all_files')
2770 2772 if diff and opts.get('all_files'):
2771 2773 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2772 2774 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2773 2775 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2774 2776 # experimental config: commands.grep.all-files
2775 2777 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2776 2778 plaingrep = opts.get('all_files') and not opts.get('rev')
2777 2779 if plaingrep:
2778 2780 opts['rev'] = ['wdir()']
2779 2781
2780 2782 reflags = re.M
2781 2783 if opts.get('ignore_case'):
2782 2784 reflags |= re.I
2783 2785 try:
2784 2786 regexp = util.re.compile(pattern, reflags)
2785 2787 except re.error as inst:
2786 2788 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2787 2789 return 1
2788 2790 sep, eol = ':', '\n'
2789 2791 if opts.get('print0'):
2790 2792 sep = eol = '\0'
2791 2793
2792 2794 getfile = util.lrucachefunc(repo.file)
2793 2795
2794 2796 def matchlines(body):
2795 2797 begin = 0
2796 2798 linenum = 0
2797 2799 while begin < len(body):
2798 2800 match = regexp.search(body, begin)
2799 2801 if not match:
2800 2802 break
2801 2803 mstart, mend = match.span()
2802 2804 linenum += body.count('\n', begin, mstart) + 1
2803 2805 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2804 2806 begin = body.find('\n', mend) + 1 or len(body) + 1
2805 2807 lend = begin - 1
2806 2808 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2807 2809
2808 2810 class linestate(object):
2809 2811 def __init__(self, line, linenum, colstart, colend):
2810 2812 self.line = line
2811 2813 self.linenum = linenum
2812 2814 self.colstart = colstart
2813 2815 self.colend = colend
2814 2816
2815 2817 def __hash__(self):
2816 2818 return hash((self.linenum, self.line))
2817 2819
2818 2820 def __eq__(self, other):
2819 2821 return self.line == other.line
2820 2822
2821 2823 def findpos(self):
2822 2824 """Iterate all (start, end) indices of matches"""
2823 2825 yield self.colstart, self.colend
2824 2826 p = self.colend
2825 2827 while p < len(self.line):
2826 2828 m = regexp.search(self.line, p)
2827 2829 if not m:
2828 2830 break
2829 2831 yield m.span()
2830 2832 p = m.end()
2831 2833
2832 2834 matches = {}
2833 2835 copies = {}
2834 2836 def grepbody(fn, rev, body):
2835 2837 matches[rev].setdefault(fn, [])
2836 2838 m = matches[rev][fn]
2837 2839 for lnum, cstart, cend, line in matchlines(body):
2838 2840 s = linestate(line, lnum, cstart, cend)
2839 2841 m.append(s)
2840 2842
2841 2843 def difflinestates(a, b):
2842 2844 sm = difflib.SequenceMatcher(None, a, b)
2843 2845 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2844 2846 if tag == r'insert':
2845 2847 for i in pycompat.xrange(blo, bhi):
2846 2848 yield ('+', b[i])
2847 2849 elif tag == r'delete':
2848 2850 for i in pycompat.xrange(alo, ahi):
2849 2851 yield ('-', a[i])
2850 2852 elif tag == r'replace':
2851 2853 for i in pycompat.xrange(alo, ahi):
2852 2854 yield ('-', a[i])
2853 2855 for i in pycompat.xrange(blo, bhi):
2854 2856 yield ('+', b[i])
2855 2857
2856 2858 uipathfn = scmutil.getuipathfn(repo)
2857 2859 def display(fm, fn, ctx, pstates, states):
2858 2860 rev = scmutil.intrev(ctx)
2859 2861 if fm.isplain():
2860 2862 formatuser = ui.shortuser
2861 2863 else:
2862 2864 formatuser = pycompat.bytestr
2863 2865 if ui.quiet:
2864 2866 datefmt = '%Y-%m-%d'
2865 2867 else:
2866 2868 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2867 2869 found = False
2868 2870 @util.cachefunc
2869 2871 def binary():
2870 2872 flog = getfile(fn)
2871 2873 try:
2872 2874 return stringutil.binary(flog.read(ctx.filenode(fn)))
2873 2875 except error.WdirUnsupported:
2874 2876 return ctx[fn].isbinary()
2875 2877
2876 2878 fieldnamemap = {'linenumber': 'lineno'}
2877 2879 if diff:
2878 2880 iter = difflinestates(pstates, states)
2879 2881 else:
2880 2882 iter = [('', l) for l in states]
2881 2883 for change, l in iter:
2882 2884 fm.startitem()
2883 2885 fm.context(ctx=ctx)
2884 2886 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
2885 2887 fm.plain(uipathfn(fn), label='grep.filename')
2886 2888
2887 2889 cols = [
2888 2890 ('rev', '%d', rev, not plaingrep, ''),
2889 2891 ('linenumber', '%d', l.linenum, opts.get('line_number'), ''),
2890 2892 ]
2891 2893 if diff:
2892 2894 cols.append(
2893 2895 ('change', '%s', change, True,
2894 2896 'grep.inserted ' if change == '+' else 'grep.deleted ')
2895 2897 )
2896 2898 cols.extend([
2897 2899 ('user', '%s', formatuser(ctx.user()), opts.get('user'), ''),
2898 2900 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2899 2901 opts.get('date'), ''),
2900 2902 ])
2901 2903 for name, fmt, data, cond, extra_label in cols:
2902 2904 if cond:
2903 2905 fm.plain(sep, label='grep.sep')
2904 2906 field = fieldnamemap.get(name, name)
2905 2907 label = extra_label + ('grep.%s' % name)
2906 2908 fm.condwrite(cond, field, fmt, data, label=label)
2907 2909 if not opts.get('files_with_matches'):
2908 2910 fm.plain(sep, label='grep.sep')
2909 2911 if not opts.get('text') and binary():
2910 2912 fm.plain(_(" Binary file matches"))
2911 2913 else:
2912 2914 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2913 2915 fm.plain(eol)
2914 2916 found = True
2915 2917 if opts.get('files_with_matches'):
2916 2918 break
2917 2919 return found
2918 2920
2919 2921 def displaymatches(fm, l):
2920 2922 p = 0
2921 2923 for s, e in l.findpos():
2922 2924 if p < s:
2923 2925 fm.startitem()
2924 2926 fm.write('text', '%s', l.line[p:s])
2925 2927 fm.data(matched=False)
2926 2928 fm.startitem()
2927 2929 fm.write('text', '%s', l.line[s:e], label='grep.match')
2928 2930 fm.data(matched=True)
2929 2931 p = e
2930 2932 if p < len(l.line):
2931 2933 fm.startitem()
2932 2934 fm.write('text', '%s', l.line[p:])
2933 2935 fm.data(matched=False)
2934 2936 fm.end()
2935 2937
2936 2938 skip = set()
2937 2939 revfiles = {}
2938 2940 match = scmutil.match(repo[None], pats, opts)
2939 2941 found = False
2940 2942 follow = opts.get('follow')
2941 2943
2942 2944 def prep(ctx, fns):
2943 2945 rev = ctx.rev()
2944 2946 pctx = ctx.p1()
2945 2947 parent = pctx.rev()
2946 2948 matches.setdefault(rev, {})
2947 2949 matches.setdefault(parent, {})
2948 2950 files = revfiles.setdefault(rev, [])
2949 2951 for fn in fns:
2950 2952 flog = getfile(fn)
2951 2953 try:
2952 2954 fnode = ctx.filenode(fn)
2953 2955 except error.LookupError:
2954 2956 continue
2955 2957 copy = None
2956 2958 if follow:
2957 2959 try:
2958 2960 copied = flog.renamed(fnode)
2959 2961 except error.WdirUnsupported:
2960 2962 copied = ctx[fn].renamed()
2961 2963 copy = copied and copied[0]
2962 2964 if copy:
2963 2965 copies.setdefault(rev, {})[fn] = copy
2964 2966 if fn in skip:
2965 2967 skip.add(copy)
2966 2968 if fn in skip:
2967 2969 continue
2968 2970 files.append(fn)
2969 2971
2970 2972 if fn not in matches[rev]:
2971 2973 try:
2972 2974 content = flog.read(fnode)
2973 2975 except error.WdirUnsupported:
2974 2976 content = ctx[fn].data()
2975 2977 grepbody(fn, rev, content)
2976 2978
2977 2979 pfn = copy or fn
2978 2980 if pfn not in matches[parent]:
2979 2981 try:
2980 2982 fnode = pctx.filenode(pfn)
2981 2983 grepbody(pfn, parent, flog.read(fnode))
2982 2984 except error.LookupError:
2983 2985 pass
2984 2986
2985 2987 ui.pager('grep')
2986 2988 fm = ui.formatter('grep', opts)
2987 2989 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2988 2990 rev = ctx.rev()
2989 2991 parent = ctx.p1().rev()
2990 2992 for fn in sorted(revfiles.get(rev, [])):
2991 2993 states = matches[rev][fn]
2992 2994 copy = copies.get(rev, {}).get(fn)
2993 2995 if fn in skip:
2994 2996 if copy:
2995 2997 skip.add(copy)
2996 2998 continue
2997 2999 pstates = matches.get(parent, {}).get(copy or fn, [])
2998 3000 if pstates or states:
2999 3001 r = display(fm, fn, ctx, pstates, states)
3000 3002 found = found or r
3001 3003 if r and not diff and not all_files:
3002 3004 skip.add(fn)
3003 3005 if copy:
3004 3006 skip.add(copy)
3005 3007 del revfiles[rev]
3006 3008 # We will keep the matches dict for the duration of the window
3007 3009 # clear the matches dict once the window is over
3008 3010 if not revfiles:
3009 3011 matches.clear()
3010 3012 fm.end()
3011 3013
3012 3014 return not found
3013 3015
3014 3016 @command('heads',
3015 3017 [('r', 'rev', '',
3016 3018 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3017 3019 ('t', 'topo', False, _('show topological heads only')),
3018 3020 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3019 3021 ('c', 'closed', False, _('show normal and closed branch heads')),
3020 3022 ] + templateopts,
3021 3023 _('[-ct] [-r STARTREV] [REV]...'),
3022 3024 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3023 3025 intents={INTENT_READONLY})
3024 3026 def heads(ui, repo, *branchrevs, **opts):
3025 3027 """show branch heads
3026 3028
3027 3029 With no arguments, show all open branch heads in the repository.
3028 3030 Branch heads are changesets that have no descendants on the
3029 3031 same branch. They are where development generally takes place and
3030 3032 are the usual targets for update and merge operations.
3031 3033
3032 3034 If one or more REVs are given, only open branch heads on the
3033 3035 branches associated with the specified changesets are shown. This
3034 3036 means that you can use :hg:`heads .` to see the heads on the
3035 3037 currently checked-out branch.
3036 3038
3037 3039 If -c/--closed is specified, also show branch heads marked closed
3038 3040 (see :hg:`commit --close-branch`).
3039 3041
3040 3042 If STARTREV is specified, only those heads that are descendants of
3041 3043 STARTREV will be displayed.
3042 3044
3043 3045 If -t/--topo is specified, named branch mechanics will be ignored and only
3044 3046 topological heads (changesets with no children) will be shown.
3045 3047
3046 3048 Returns 0 if matching heads are found, 1 if not.
3047 3049 """
3048 3050
3049 3051 opts = pycompat.byteskwargs(opts)
3050 3052 start = None
3051 3053 rev = opts.get('rev')
3052 3054 if rev:
3053 3055 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3054 3056 start = scmutil.revsingle(repo, rev, None).node()
3055 3057
3056 3058 if opts.get('topo'):
3057 3059 heads = [repo[h] for h in repo.heads(start)]
3058 3060 else:
3059 3061 heads = []
3060 3062 for branch in repo.branchmap():
3061 3063 heads += repo.branchheads(branch, start, opts.get('closed'))
3062 3064 heads = [repo[h] for h in heads]
3063 3065
3064 3066 if branchrevs:
3065 3067 branches = set(repo[r].branch()
3066 3068 for r in scmutil.revrange(repo, branchrevs))
3067 3069 heads = [h for h in heads if h.branch() in branches]
3068 3070
3069 3071 if opts.get('active') and branchrevs:
3070 3072 dagheads = repo.heads(start)
3071 3073 heads = [h for h in heads if h.node() in dagheads]
3072 3074
3073 3075 if branchrevs:
3074 3076 haveheads = set(h.branch() for h in heads)
3075 3077 if branches - haveheads:
3076 3078 headless = ', '.join(b for b in branches - haveheads)
3077 3079 msg = _('no open branch heads found on branches %s')
3078 3080 if opts.get('rev'):
3079 3081 msg += _(' (started at %s)') % opts['rev']
3080 3082 ui.warn((msg + '\n') % headless)
3081 3083
3082 3084 if not heads:
3083 3085 return 1
3084 3086
3085 3087 ui.pager('heads')
3086 3088 heads = sorted(heads, key=lambda x: -x.rev())
3087 3089 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3088 3090 for ctx in heads:
3089 3091 displayer.show(ctx)
3090 3092 displayer.close()
3091 3093
3092 3094 @command('help',
3093 3095 [('e', 'extension', None, _('show only help for extensions')),
3094 3096 ('c', 'command', None, _('show only help for commands')),
3095 3097 ('k', 'keyword', None, _('show topics matching keyword')),
3096 3098 ('s', 'system', [],
3097 3099 _('show help for specific platform(s)'), _('PLATFORM')),
3098 3100 ],
3099 3101 _('[-eck] [-s PLATFORM] [TOPIC]'),
3100 3102 helpcategory=command.CATEGORY_HELP,
3101 3103 norepo=True,
3102 3104 intents={INTENT_READONLY})
3103 3105 def help_(ui, name=None, **opts):
3104 3106 """show help for a given topic or a help overview
3105 3107
3106 3108 With no arguments, print a list of commands with short help messages.
3107 3109
3108 3110 Given a topic, extension, or command name, print help for that
3109 3111 topic.
3110 3112
3111 3113 Returns 0 if successful.
3112 3114 """
3113 3115
3114 3116 keep = opts.get(r'system') or []
3115 3117 if len(keep) == 0:
3116 3118 if pycompat.sysplatform.startswith('win'):
3117 3119 keep.append('windows')
3118 3120 elif pycompat.sysplatform == 'OpenVMS':
3119 3121 keep.append('vms')
3120 3122 elif pycompat.sysplatform == 'plan9':
3121 3123 keep.append('plan9')
3122 3124 else:
3123 3125 keep.append('unix')
3124 3126 keep.append(pycompat.sysplatform.lower())
3125 3127 if ui.verbose:
3126 3128 keep.append('verbose')
3127 3129
3128 3130 commands = sys.modules[__name__]
3129 3131 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3130 3132 ui.pager('help')
3131 3133 ui.write(formatted)
3132 3134
3133 3135
3134 3136 @command('identify|id',
3135 3137 [('r', 'rev', '',
3136 3138 _('identify the specified revision'), _('REV')),
3137 3139 ('n', 'num', None, _('show local revision number')),
3138 3140 ('i', 'id', None, _('show global revision id')),
3139 3141 ('b', 'branch', None, _('show branch')),
3140 3142 ('t', 'tags', None, _('show tags')),
3141 3143 ('B', 'bookmarks', None, _('show bookmarks')),
3142 3144 ] + remoteopts + formatteropts,
3143 3145 _('[-nibtB] [-r REV] [SOURCE]'),
3144 3146 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3145 3147 optionalrepo=True,
3146 3148 intents={INTENT_READONLY})
3147 3149 def identify(ui, repo, source=None, rev=None,
3148 3150 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3149 3151 """identify the working directory or specified revision
3150 3152
3151 3153 Print a summary identifying the repository state at REV using one or
3152 3154 two parent hash identifiers, followed by a "+" if the working
3153 3155 directory has uncommitted changes, the branch name (if not default),
3154 3156 a list of tags, and a list of bookmarks.
3155 3157
3156 3158 When REV is not given, print a summary of the current state of the
3157 3159 repository including the working directory. Specify -r. to get information
3158 3160 of the working directory parent without scanning uncommitted changes.
3159 3161
3160 3162 Specifying a path to a repository root or Mercurial bundle will
3161 3163 cause lookup to operate on that repository/bundle.
3162 3164
3163 3165 .. container:: verbose
3164 3166
3165 3167 Template:
3166 3168
3167 3169 The following keywords are supported in addition to the common template
3168 3170 keywords and functions. See also :hg:`help templates`.
3169 3171
3170 3172 :dirty: String. Character ``+`` denoting if the working directory has
3171 3173 uncommitted changes.
3172 3174 :id: String. One or two nodes, optionally followed by ``+``.
3173 3175 :parents: List of strings. Parent nodes of the changeset.
3174 3176
3175 3177 Examples:
3176 3178
3177 3179 - generate a build identifier for the working directory::
3178 3180
3179 3181 hg id --id > build-id.dat
3180 3182
3181 3183 - find the revision corresponding to a tag::
3182 3184
3183 3185 hg id -n -r 1.3
3184 3186
3185 3187 - check the most recent revision of a remote repository::
3186 3188
3187 3189 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3188 3190
3189 3191 See :hg:`log` for generating more information about specific revisions,
3190 3192 including full hash identifiers.
3191 3193
3192 3194 Returns 0 if successful.
3193 3195 """
3194 3196
3195 3197 opts = pycompat.byteskwargs(opts)
3196 3198 if not repo and not source:
3197 3199 raise error.Abort(_("there is no Mercurial repository here "
3198 3200 "(.hg not found)"))
3199 3201
3200 3202 default = not (num or id or branch or tags or bookmarks)
3201 3203 output = []
3202 3204 revs = []
3203 3205
3204 3206 if source:
3205 3207 source, branches = hg.parseurl(ui.expandpath(source))
3206 3208 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3207 3209 repo = peer.local()
3208 3210 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3209 3211
3210 3212 fm = ui.formatter('identify', opts)
3211 3213 fm.startitem()
3212 3214
3213 3215 if not repo:
3214 3216 if num or branch or tags:
3215 3217 raise error.Abort(
3216 3218 _("can't query remote revision number, branch, or tags"))
3217 3219 if not rev and revs:
3218 3220 rev = revs[0]
3219 3221 if not rev:
3220 3222 rev = "tip"
3221 3223
3222 3224 remoterev = peer.lookup(rev)
3223 3225 hexrev = fm.hexfunc(remoterev)
3224 3226 if default or id:
3225 3227 output = [hexrev]
3226 3228 fm.data(id=hexrev)
3227 3229
3228 3230 @util.cachefunc
3229 3231 def getbms():
3230 3232 bms = []
3231 3233
3232 3234 if 'bookmarks' in peer.listkeys('namespaces'):
3233 3235 hexremoterev = hex(remoterev)
3234 3236 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3235 3237 if bmr == hexremoterev]
3236 3238
3237 3239 return sorted(bms)
3238 3240
3239 3241 if fm.isplain():
3240 3242 if bookmarks:
3241 3243 output.extend(getbms())
3242 3244 elif default and not ui.quiet:
3243 3245 # multiple bookmarks for a single parent separated by '/'
3244 3246 bm = '/'.join(getbms())
3245 3247 if bm:
3246 3248 output.append(bm)
3247 3249 else:
3248 3250 fm.data(node=hex(remoterev))
3249 3251 if bookmarks or 'bookmarks' in fm.datahint():
3250 3252 fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark'))
3251 3253 else:
3252 3254 if rev:
3253 3255 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3254 3256 ctx = scmutil.revsingle(repo, rev, None)
3255 3257
3256 3258 if ctx.rev() is None:
3257 3259 ctx = repo[None]
3258 3260 parents = ctx.parents()
3259 3261 taglist = []
3260 3262 for p in parents:
3261 3263 taglist.extend(p.tags())
3262 3264
3263 3265 dirty = ""
3264 3266 if ctx.dirty(missing=True, merge=False, branch=False):
3265 3267 dirty = '+'
3266 3268 fm.data(dirty=dirty)
3267 3269
3268 3270 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3269 3271 if default or id:
3270 3272 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3271 3273 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3272 3274
3273 3275 if num:
3274 3276 numoutput = ["%d" % p.rev() for p in parents]
3275 3277 output.append("%s%s" % ('+'.join(numoutput), dirty))
3276 3278
3277 3279 fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
3278 3280 for p in parents], name='node'))
3279 3281 else:
3280 3282 hexoutput = fm.hexfunc(ctx.node())
3281 3283 if default or id:
3282 3284 output = [hexoutput]
3283 3285 fm.data(id=hexoutput)
3284 3286
3285 3287 if num:
3286 3288 output.append(pycompat.bytestr(ctx.rev()))
3287 3289 taglist = ctx.tags()
3288 3290
3289 3291 if default and not ui.quiet:
3290 3292 b = ctx.branch()
3291 3293 if b != 'default':
3292 3294 output.append("(%s)" % b)
3293 3295
3294 3296 # multiple tags for a single parent separated by '/'
3295 3297 t = '/'.join(taglist)
3296 3298 if t:
3297 3299 output.append(t)
3298 3300
3299 3301 # multiple bookmarks for a single parent separated by '/'
3300 3302 bm = '/'.join(ctx.bookmarks())
3301 3303 if bm:
3302 3304 output.append(bm)
3303 3305 else:
3304 3306 if branch:
3305 3307 output.append(ctx.branch())
3306 3308
3307 3309 if tags:
3308 3310 output.extend(taglist)
3309 3311
3310 3312 if bookmarks:
3311 3313 output.extend(ctx.bookmarks())
3312 3314
3313 3315 fm.data(node=ctx.hex())
3314 3316 fm.data(branch=ctx.branch())
3315 3317 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3316 3318 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3317 3319 fm.context(ctx=ctx)
3318 3320
3319 3321 fm.plain("%s\n" % ' '.join(output))
3320 3322 fm.end()
3321 3323
3322 3324 @command('import|patch',
3323 3325 [('p', 'strip', 1,
3324 3326 _('directory strip option for patch. This has the same '
3325 3327 'meaning as the corresponding patch option'), _('NUM')),
3326 3328 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3327 3329 ('e', 'edit', False, _('invoke editor on commit messages')),
3328 3330 ('f', 'force', None,
3329 3331 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3330 3332 ('', 'no-commit', None,
3331 3333 _("don't commit, just update the working directory")),
3332 3334 ('', 'bypass', None,
3333 3335 _("apply patch without touching the working directory")),
3334 3336 ('', 'partial', None,
3335 3337 _('commit even if some hunks fail')),
3336 3338 ('', 'exact', None,
3337 3339 _('abort if patch would apply lossily')),
3338 3340 ('', 'prefix', '',
3339 3341 _('apply patch to subdirectory'), _('DIR')),
3340 3342 ('', 'import-branch', None,
3341 3343 _('use any branch information in patch (implied by --exact)'))] +
3342 3344 commitopts + commitopts2 + similarityopts,
3343 3345 _('[OPTION]... PATCH...'),
3344 3346 helpcategory=command.CATEGORY_IMPORT_EXPORT)
3345 3347 def import_(ui, repo, patch1=None, *patches, **opts):
3346 3348 """import an ordered set of patches
3347 3349
3348 3350 Import a list of patches and commit them individually (unless
3349 3351 --no-commit is specified).
3350 3352
3351 3353 To read a patch from standard input (stdin), use "-" as the patch
3352 3354 name. If a URL is specified, the patch will be downloaded from
3353 3355 there.
3354 3356
3355 3357 Import first applies changes to the working directory (unless
3356 3358 --bypass is specified), import will abort if there are outstanding
3357 3359 changes.
3358 3360
3359 3361 Use --bypass to apply and commit patches directly to the
3360 3362 repository, without affecting the working directory. Without
3361 3363 --exact, patches will be applied on top of the working directory
3362 3364 parent revision.
3363 3365
3364 3366 You can import a patch straight from a mail message. Even patches
3365 3367 as attachments work (to use the body part, it must have type
3366 3368 text/plain or text/x-patch). From and Subject headers of email
3367 3369 message are used as default committer and commit message. All
3368 3370 text/plain body parts before first diff are added to the commit
3369 3371 message.
3370 3372
3371 3373 If the imported patch was generated by :hg:`export`, user and
3372 3374 description from patch override values from message headers and
3373 3375 body. Values given on command line with -m/--message and -u/--user
3374 3376 override these.
3375 3377
3376 3378 If --exact is specified, import will set the working directory to
3377 3379 the parent of each patch before applying it, and will abort if the
3378 3380 resulting changeset has a different ID than the one recorded in
3379 3381 the patch. This will guard against various ways that portable
3380 3382 patch formats and mail systems might fail to transfer Mercurial
3381 3383 data or metadata. See :hg:`bundle` for lossless transmission.
3382 3384
3383 3385 Use --partial to ensure a changeset will be created from the patch
3384 3386 even if some hunks fail to apply. Hunks that fail to apply will be
3385 3387 written to a <target-file>.rej file. Conflicts can then be resolved
3386 3388 by hand before :hg:`commit --amend` is run to update the created
3387 3389 changeset. This flag exists to let people import patches that
3388 3390 partially apply without losing the associated metadata (author,
3389 3391 date, description, ...).
3390 3392
3391 3393 .. note::
3392 3394
3393 3395 When no hunks apply cleanly, :hg:`import --partial` will create
3394 3396 an empty changeset, importing only the patch metadata.
3395 3397
3396 3398 With -s/--similarity, hg will attempt to discover renames and
3397 3399 copies in the patch in the same way as :hg:`addremove`.
3398 3400
3399 3401 It is possible to use external patch programs to perform the patch
3400 3402 by setting the ``ui.patch`` configuration option. For the default
3401 3403 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3402 3404 See :hg:`help config` for more information about configuration
3403 3405 files and how to use these options.
3404 3406
3405 3407 See :hg:`help dates` for a list of formats valid for -d/--date.
3406 3408
3407 3409 .. container:: verbose
3408 3410
3409 3411 Examples:
3410 3412
3411 3413 - import a traditional patch from a website and detect renames::
3412 3414
3413 3415 hg import -s 80 http://example.com/bugfix.patch
3414 3416
3415 3417 - import a changeset from an hgweb server::
3416 3418
3417 3419 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3418 3420
3419 3421 - import all the patches in an Unix-style mbox::
3420 3422
3421 3423 hg import incoming-patches.mbox
3422 3424
3423 3425 - import patches from stdin::
3424 3426
3425 3427 hg import -
3426 3428
3427 3429 - attempt to exactly restore an exported changeset (not always
3428 3430 possible)::
3429 3431
3430 3432 hg import --exact proposed-fix.patch
3431 3433
3432 3434 - use an external tool to apply a patch which is too fuzzy for
3433 3435 the default internal tool.
3434 3436
3435 3437 hg import --config ui.patch="patch --merge" fuzzy.patch
3436 3438
3437 3439 - change the default fuzzing from 2 to a less strict 7
3438 3440
3439 3441 hg import --config ui.fuzz=7 fuzz.patch
3440 3442
3441 3443 Returns 0 on success, 1 on partial success (see --partial).
3442 3444 """
3443 3445
3444 3446 opts = pycompat.byteskwargs(opts)
3445 3447 if not patch1:
3446 3448 raise error.Abort(_('need at least one patch to import'))
3447 3449
3448 3450 patches = (patch1,) + patches
3449 3451
3450 3452 date = opts.get('date')
3451 3453 if date:
3452 3454 opts['date'] = dateutil.parsedate(date)
3453 3455
3454 3456 exact = opts.get('exact')
3455 3457 update = not opts.get('bypass')
3456 3458 if not update and opts.get('no_commit'):
3457 3459 raise error.Abort(_('cannot use --no-commit with --bypass'))
3458 3460 try:
3459 3461 sim = float(opts.get('similarity') or 0)
3460 3462 except ValueError:
3461 3463 raise error.Abort(_('similarity must be a number'))
3462 3464 if sim < 0 or sim > 100:
3463 3465 raise error.Abort(_('similarity must be between 0 and 100'))
3464 3466 if sim and not update:
3465 3467 raise error.Abort(_('cannot use --similarity with --bypass'))
3466 3468 if exact:
3467 3469 if opts.get('edit'):
3468 3470 raise error.Abort(_('cannot use --exact with --edit'))
3469 3471 if opts.get('prefix'):
3470 3472 raise error.Abort(_('cannot use --exact with --prefix'))
3471 3473
3472 3474 base = opts["base"]
3473 3475 msgs = []
3474 3476 ret = 0
3475 3477
3476 3478 with repo.wlock():
3477 3479 if update:
3478 3480 cmdutil.checkunfinished(repo)
3479 3481 if (exact or not opts.get('force')):
3480 3482 cmdutil.bailifchanged(repo)
3481 3483
3482 3484 if not opts.get('no_commit'):
3483 3485 lock = repo.lock
3484 3486 tr = lambda: repo.transaction('import')
3485 3487 dsguard = util.nullcontextmanager
3486 3488 else:
3487 3489 lock = util.nullcontextmanager
3488 3490 tr = util.nullcontextmanager
3489 3491 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3490 3492 with lock(), tr(), dsguard():
3491 3493 parents = repo[None].parents()
3492 3494 for patchurl in patches:
3493 3495 if patchurl == '-':
3494 3496 ui.status(_('applying patch from stdin\n'))
3495 3497 patchfile = ui.fin
3496 3498 patchurl = 'stdin' # for error message
3497 3499 else:
3498 3500 patchurl = os.path.join(base, patchurl)
3499 3501 ui.status(_('applying %s\n') % patchurl)
3500 3502 patchfile = hg.openpath(ui, patchurl)
3501 3503
3502 3504 haspatch = False
3503 3505 for hunk in patch.split(patchfile):
3504 3506 with patch.extract(ui, hunk) as patchdata:
3505 3507 msg, node, rej = cmdutil.tryimportone(ui, repo,
3506 3508 patchdata,
3507 3509 parents, opts,
3508 3510 msgs, hg.clean)
3509 3511 if msg:
3510 3512 haspatch = True
3511 3513 ui.note(msg + '\n')
3512 3514 if update or exact:
3513 3515 parents = repo[None].parents()
3514 3516 else:
3515 3517 parents = [repo[node]]
3516 3518 if rej:
3517 3519 ui.write_err(_("patch applied partially\n"))
3518 3520 ui.write_err(_("(fix the .rej files and run "
3519 3521 "`hg commit --amend`)\n"))
3520 3522 ret = 1
3521 3523 break
3522 3524
3523 3525 if not haspatch:
3524 3526 raise error.Abort(_('%s: no diffs found') % patchurl)
3525 3527
3526 3528 if msgs:
3527 3529 repo.savecommitmessage('\n* * *\n'.join(msgs))
3528 3530 return ret
3529 3531
3530 3532 @command('incoming|in',
3531 3533 [('f', 'force', None,
3532 3534 _('run even if remote repository is unrelated')),
3533 3535 ('n', 'newest-first', None, _('show newest record first')),
3534 3536 ('', 'bundle', '',
3535 3537 _('file to store the bundles into'), _('FILE')),
3536 3538 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3537 3539 ('B', 'bookmarks', False, _("compare bookmarks")),
3538 3540 ('b', 'branch', [],
3539 3541 _('a specific branch you would like to pull'), _('BRANCH')),
3540 3542 ] + logopts + remoteopts + subrepoopts,
3541 3543 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
3542 3544 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
3543 3545 def incoming(ui, repo, source="default", **opts):
3544 3546 """show new changesets found in source
3545 3547
3546 3548 Show new changesets found in the specified path/URL or the default
3547 3549 pull location. These are the changesets that would have been pulled
3548 3550 by :hg:`pull` at the time you issued this command.
3549 3551
3550 3552 See pull for valid source format details.
3551 3553
3552 3554 .. container:: verbose
3553 3555
3554 3556 With -B/--bookmarks, the result of bookmark comparison between
3555 3557 local and remote repositories is displayed. With -v/--verbose,
3556 3558 status is also displayed for each bookmark like below::
3557 3559
3558 3560 BM1 01234567890a added
3559 3561 BM2 1234567890ab advanced
3560 3562 BM3 234567890abc diverged
3561 3563 BM4 34567890abcd changed
3562 3564
3563 3565 The action taken locally when pulling depends on the
3564 3566 status of each bookmark:
3565 3567
3566 3568 :``added``: pull will create it
3567 3569 :``advanced``: pull will update it
3568 3570 :``diverged``: pull will create a divergent bookmark
3569 3571 :``changed``: result depends on remote changesets
3570 3572
3571 3573 From the point of view of pulling behavior, bookmark
3572 3574 existing only in the remote repository are treated as ``added``,
3573 3575 even if it is in fact locally deleted.
3574 3576
3575 3577 .. container:: verbose
3576 3578
3577 3579 For remote repository, using --bundle avoids downloading the
3578 3580 changesets twice if the incoming is followed by a pull.
3579 3581
3580 3582 Examples:
3581 3583
3582 3584 - show incoming changes with patches and full description::
3583 3585
3584 3586 hg incoming -vp
3585 3587
3586 3588 - show incoming changes excluding merges, store a bundle::
3587 3589
3588 3590 hg in -vpM --bundle incoming.hg
3589 3591 hg pull incoming.hg
3590 3592
3591 3593 - briefly list changes inside a bundle::
3592 3594
3593 3595 hg in changes.hg -T "{desc|firstline}\\n"
3594 3596
3595 3597 Returns 0 if there are incoming changes, 1 otherwise.
3596 3598 """
3597 3599 opts = pycompat.byteskwargs(opts)
3598 3600 if opts.get('graph'):
3599 3601 logcmdutil.checkunsupportedgraphflags([], opts)
3600 3602 def display(other, chlist, displayer):
3601 3603 revdag = logcmdutil.graphrevs(other, chlist, opts)
3602 3604 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3603 3605 graphmod.asciiedges)
3604 3606
3605 3607 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3606 3608 return 0
3607 3609
3608 3610 if opts.get('bundle') and opts.get('subrepos'):
3609 3611 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3610 3612
3611 3613 if opts.get('bookmarks'):
3612 3614 source, branches = hg.parseurl(ui.expandpath(source),
3613 3615 opts.get('branch'))
3614 3616 other = hg.peer(repo, opts, source)
3615 3617 if 'bookmarks' not in other.listkeys('namespaces'):
3616 3618 ui.warn(_("remote doesn't support bookmarks\n"))
3617 3619 return 0
3618 3620 ui.pager('incoming')
3619 3621 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3620 3622 return bookmarks.incoming(ui, repo, other)
3621 3623
3622 3624 repo._subtoppath = ui.expandpath(source)
3623 3625 try:
3624 3626 return hg.incoming(ui, repo, source, opts)
3625 3627 finally:
3626 3628 del repo._subtoppath
3627 3629
3628 3630
3629 3631 @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3630 3632 helpcategory=command.CATEGORY_REPO_CREATION,
3631 3633 helpbasic=True, norepo=True)
3632 3634 def init(ui, dest=".", **opts):
3633 3635 """create a new repository in the given directory
3634 3636
3635 3637 Initialize a new repository in the given directory. If the given
3636 3638 directory does not exist, it will be created.
3637 3639
3638 3640 If no directory is given, the current directory is used.
3639 3641
3640 3642 It is possible to specify an ``ssh://`` URL as the destination.
3641 3643 See :hg:`help urls` for more information.
3642 3644
3643 3645 Returns 0 on success.
3644 3646 """
3645 3647 opts = pycompat.byteskwargs(opts)
3646 3648 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3647 3649
3648 3650 @command('locate',
3649 3651 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3650 3652 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3651 3653 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3652 3654 ] + walkopts,
3653 3655 _('[OPTION]... [PATTERN]...'),
3654 3656 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
3655 3657 def locate(ui, repo, *pats, **opts):
3656 3658 """locate files matching specific patterns (DEPRECATED)
3657 3659
3658 3660 Print files under Mercurial control in the working directory whose
3659 3661 names match the given patterns.
3660 3662
3661 3663 By default, this command searches all directories in the working
3662 3664 directory. To search just the current directory and its
3663 3665 subdirectories, use "--include .".
3664 3666
3665 3667 If no patterns are given to match, this command prints the names
3666 3668 of all files under Mercurial control in the working directory.
3667 3669
3668 3670 If you want to feed the output of this command into the "xargs"
3669 3671 command, use the -0 option to both this command and "xargs". This
3670 3672 will avoid the problem of "xargs" treating single filenames that
3671 3673 contain whitespace as multiple filenames.
3672 3674
3673 3675 See :hg:`help files` for a more versatile command.
3674 3676
3675 3677 Returns 0 if a match is found, 1 otherwise.
3676 3678 """
3677 3679 opts = pycompat.byteskwargs(opts)
3678 3680 if opts.get('print0'):
3679 3681 end = '\0'
3680 3682 else:
3681 3683 end = '\n'
3682 3684 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3683 3685
3684 3686 ret = 1
3685 3687 m = scmutil.match(ctx, pats, opts, default='relglob',
3686 3688 badfn=lambda x, y: False)
3687 3689
3688 3690 ui.pager('locate')
3689 3691 if ctx.rev() is None:
3690 3692 # When run on the working copy, "locate" includes removed files, so
3691 3693 # we get the list of files from the dirstate.
3692 3694 filesgen = sorted(repo.dirstate.matches(m))
3693 3695 else:
3694 3696 filesgen = ctx.matches(m)
3695 3697 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
3696 3698 for abs in filesgen:
3697 3699 if opts.get('fullpath'):
3698 3700 ui.write(repo.wjoin(abs), end)
3699 3701 else:
3700 3702 ui.write(uipathfn(abs), end)
3701 3703 ret = 0
3702 3704
3703 3705 return ret
3704 3706
3705 3707 @command('log|history',
3706 3708 [('f', 'follow', None,
3707 3709 _('follow changeset history, or file history across copies and renames')),
3708 3710 ('', 'follow-first', None,
3709 3711 _('only follow the first parent of merge changesets (DEPRECATED)')),
3710 3712 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3711 3713 ('C', 'copies', None, _('show copied files')),
3712 3714 ('k', 'keyword', [],
3713 3715 _('do case-insensitive search for a given text'), _('TEXT')),
3714 3716 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3715 3717 ('L', 'line-range', [],
3716 3718 _('follow line range of specified file (EXPERIMENTAL)'),
3717 3719 _('FILE,RANGE')),
3718 3720 ('', 'removed', None, _('include revisions where files were removed')),
3719 3721 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3720 3722 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3721 3723 ('', 'only-branch', [],
3722 3724 _('show only changesets within the given named branch (DEPRECATED)'),
3723 3725 _('BRANCH')),
3724 3726 ('b', 'branch', [],
3725 3727 _('show changesets within the given named branch'), _('BRANCH')),
3726 3728 ('P', 'prune', [],
3727 3729 _('do not display revision or any of its ancestors'), _('REV')),
3728 3730 ] + logopts + walkopts,
3729 3731 _('[OPTION]... [FILE]'),
3730 3732 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3731 3733 helpbasic=True, inferrepo=True,
3732 3734 intents={INTENT_READONLY})
3733 3735 def log(ui, repo, *pats, **opts):
3734 3736 """show revision history of entire repository or files
3735 3737
3736 3738 Print the revision history of the specified files or the entire
3737 3739 project.
3738 3740
3739 3741 If no revision range is specified, the default is ``tip:0`` unless
3740 3742 --follow is set, in which case the working directory parent is
3741 3743 used as the starting revision.
3742 3744
3743 3745 File history is shown without following rename or copy history of
3744 3746 files. Use -f/--follow with a filename to follow history across
3745 3747 renames and copies. --follow without a filename will only show
3746 3748 ancestors of the starting revision.
3747 3749
3748 3750 By default this command prints revision number and changeset id,
3749 3751 tags, non-trivial parents, user, date and time, and a summary for
3750 3752 each commit. When the -v/--verbose switch is used, the list of
3751 3753 changed files and full commit message are shown.
3752 3754
3753 3755 With --graph the revisions are shown as an ASCII art DAG with the most
3754 3756 recent changeset at the top.
3755 3757 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3756 3758 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3757 3759 changeset from the lines below is a parent of the 'o' merge on the same
3758 3760 line.
3759 3761 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3760 3762 of a '|' indicates one or more revisions in a path are omitted.
3761 3763
3762 3764 .. container:: verbose
3763 3765
3764 3766 Use -L/--line-range FILE,M:N options to follow the history of lines
3765 3767 from M to N in FILE. With -p/--patch only diff hunks affecting
3766 3768 specified line range will be shown. This option requires --follow;
3767 3769 it can be specified multiple times. Currently, this option is not
3768 3770 compatible with --graph. This option is experimental.
3769 3771
3770 3772 .. note::
3771 3773
3772 3774 :hg:`log --patch` may generate unexpected diff output for merge
3773 3775 changesets, as it will only compare the merge changeset against
3774 3776 its first parent. Also, only files different from BOTH parents
3775 3777 will appear in files:.
3776 3778
3777 3779 .. note::
3778 3780
3779 3781 For performance reasons, :hg:`log FILE` may omit duplicate changes
3780 3782 made on branches and will not show removals or mode changes. To
3781 3783 see all such changes, use the --removed switch.
3782 3784
3783 3785 .. container:: verbose
3784 3786
3785 3787 .. note::
3786 3788
3787 3789 The history resulting from -L/--line-range options depends on diff
3788 3790 options; for instance if white-spaces are ignored, respective changes
3789 3791 with only white-spaces in specified line range will not be listed.
3790 3792
3791 3793 .. container:: verbose
3792 3794
3793 3795 Some examples:
3794 3796
3795 3797 - changesets with full descriptions and file lists::
3796 3798
3797 3799 hg log -v
3798 3800
3799 3801 - changesets ancestral to the working directory::
3800 3802
3801 3803 hg log -f
3802 3804
3803 3805 - last 10 commits on the current branch::
3804 3806
3805 3807 hg log -l 10 -b .
3806 3808
3807 3809 - changesets showing all modifications of a file, including removals::
3808 3810
3809 3811 hg log --removed file.c
3810 3812
3811 3813 - all changesets that touch a directory, with diffs, excluding merges::
3812 3814
3813 3815 hg log -Mp lib/
3814 3816
3815 3817 - all revision numbers that match a keyword::
3816 3818
3817 3819 hg log -k bug --template "{rev}\\n"
3818 3820
3819 3821 - the full hash identifier of the working directory parent::
3820 3822
3821 3823 hg log -r . --template "{node}\\n"
3822 3824
3823 3825 - list available log templates::
3824 3826
3825 3827 hg log -T list
3826 3828
3827 3829 - check if a given changeset is included in a tagged release::
3828 3830
3829 3831 hg log -r "a21ccf and ancestor(1.9)"
3830 3832
3831 3833 - find all changesets by some user in a date range::
3832 3834
3833 3835 hg log -k alice -d "may 2008 to jul 2008"
3834 3836
3835 3837 - summary of all changesets after the last tag::
3836 3838
3837 3839 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3838 3840
3839 3841 - changesets touching lines 13 to 23 for file.c::
3840 3842
3841 3843 hg log -L file.c,13:23
3842 3844
3843 3845 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3844 3846 main.c with patch::
3845 3847
3846 3848 hg log -L file.c,13:23 -L main.c,2:6 -p
3847 3849
3848 3850 See :hg:`help dates` for a list of formats valid for -d/--date.
3849 3851
3850 3852 See :hg:`help revisions` for more about specifying and ordering
3851 3853 revisions.
3852 3854
3853 3855 See :hg:`help templates` for more about pre-packaged styles and
3854 3856 specifying custom templates. The default template used by the log
3855 3857 command can be customized via the ``ui.logtemplate`` configuration
3856 3858 setting.
3857 3859
3858 3860 Returns 0 on success.
3859 3861
3860 3862 """
3861 3863 opts = pycompat.byteskwargs(opts)
3862 3864 linerange = opts.get('line_range')
3863 3865
3864 3866 if linerange and not opts.get('follow'):
3865 3867 raise error.Abort(_('--line-range requires --follow'))
3866 3868
3867 3869 if linerange and pats:
3868 3870 # TODO: take pats as patterns with no line-range filter
3869 3871 raise error.Abort(
3870 3872 _('FILE arguments are not compatible with --line-range option')
3871 3873 )
3872 3874
3873 3875 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3874 3876 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3875 3877 if linerange:
3876 3878 # TODO: should follow file history from logcmdutil._initialrevs(),
3877 3879 # then filter the result by logcmdutil._makerevset() and --limit
3878 3880 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3879 3881
3880 3882 getrenamed = None
3881 3883 if opts.get('copies'):
3882 3884 endrev = None
3883 3885 if revs:
3884 3886 endrev = revs.max() + 1
3885 3887 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3886 3888
3887 3889 ui.pager('log')
3888 3890 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3889 3891 buffered=True)
3890 3892 if opts.get('graph'):
3891 3893 displayfn = logcmdutil.displaygraphrevs
3892 3894 else:
3893 3895 displayfn = logcmdutil.displayrevs
3894 3896 displayfn(ui, repo, revs, displayer, getrenamed)
3895 3897
3896 3898 @command('manifest',
3897 3899 [('r', 'rev', '', _('revision to display'), _('REV')),
3898 3900 ('', 'all', False, _("list files from all revisions"))]
3899 3901 + formatteropts,
3900 3902 _('[-r REV]'),
3901 3903 helpcategory=command.CATEGORY_MAINTENANCE,
3902 3904 intents={INTENT_READONLY})
3903 3905 def manifest(ui, repo, node=None, rev=None, **opts):
3904 3906 """output the current or given revision of the project manifest
3905 3907
3906 3908 Print a list of version controlled files for the given revision.
3907 3909 If no revision is given, the first parent of the working directory
3908 3910 is used, or the null revision if no revision is checked out.
3909 3911
3910 3912 With -v, print file permissions, symlink and executable bits.
3911 3913 With --debug, print file revision hashes.
3912 3914
3913 3915 If option --all is specified, the list of all files from all revisions
3914 3916 is printed. This includes deleted and renamed files.
3915 3917
3916 3918 Returns 0 on success.
3917 3919 """
3918 3920 opts = pycompat.byteskwargs(opts)
3919 3921 fm = ui.formatter('manifest', opts)
3920 3922
3921 3923 if opts.get('all'):
3922 3924 if rev or node:
3923 3925 raise error.Abort(_("can't specify a revision with --all"))
3924 3926
3925 3927 res = set()
3926 3928 for rev in repo:
3927 3929 ctx = repo[rev]
3928 3930 res |= set(ctx.files())
3929 3931
3930 3932 ui.pager('manifest')
3931 3933 for f in sorted(res):
3932 3934 fm.startitem()
3933 3935 fm.write("path", '%s\n', f)
3934 3936 fm.end()
3935 3937 return
3936 3938
3937 3939 if rev and node:
3938 3940 raise error.Abort(_("please specify just one revision"))
3939 3941
3940 3942 if not node:
3941 3943 node = rev
3942 3944
3943 3945 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3944 3946 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3945 3947 if node:
3946 3948 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3947 3949 ctx = scmutil.revsingle(repo, node)
3948 3950 mf = ctx.manifest()
3949 3951 ui.pager('manifest')
3950 3952 for f in ctx:
3951 3953 fm.startitem()
3952 3954 fm.context(ctx=ctx)
3953 3955 fl = ctx[f].flags()
3954 3956 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3955 3957 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3956 3958 fm.write('path', '%s\n', f)
3957 3959 fm.end()
3958 3960
3959 3961 @command('merge',
3960 3962 [('f', 'force', None,
3961 3963 _('force a merge including outstanding changes (DEPRECATED)')),
3962 3964 ('r', 'rev', '', _('revision to merge'), _('REV')),
3963 3965 ('P', 'preview', None,
3964 3966 _('review revisions to merge (no merge is performed)')),
3965 3967 ('', 'abort', None, _('abort the ongoing merge')),
3966 3968 ] + mergetoolopts,
3967 3969 _('[-P] [[-r] REV]'),
3968 3970 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
3969 3971 def merge(ui, repo, node=None, **opts):
3970 3972 """merge another revision into working directory
3971 3973
3972 3974 The current working directory is updated with all changes made in
3973 3975 the requested revision since the last common predecessor revision.
3974 3976
3975 3977 Files that changed between either parent are marked as changed for
3976 3978 the next commit and a commit must be performed before any further
3977 3979 updates to the repository are allowed. The next commit will have
3978 3980 two parents.
3979 3981
3980 3982 ``--tool`` can be used to specify the merge tool used for file
3981 3983 merges. It overrides the HGMERGE environment variable and your
3982 3984 configuration files. See :hg:`help merge-tools` for options.
3983 3985
3984 3986 If no revision is specified, the working directory's parent is a
3985 3987 head revision, and the current branch contains exactly one other
3986 3988 head, the other head is merged with by default. Otherwise, an
3987 3989 explicit revision with which to merge with must be provided.
3988 3990
3989 3991 See :hg:`help resolve` for information on handling file conflicts.
3990 3992
3991 3993 To undo an uncommitted merge, use :hg:`merge --abort` which
3992 3994 will check out a clean copy of the original merge parent, losing
3993 3995 all changes.
3994 3996
3995 3997 Returns 0 on success, 1 if there are unresolved files.
3996 3998 """
3997 3999
3998 4000 opts = pycompat.byteskwargs(opts)
3999 4001 abort = opts.get('abort')
4000 4002 if abort and repo.dirstate.p2() == nullid:
4001 4003 cmdutil.wrongtooltocontinue(repo, _('merge'))
4002 4004 if abort:
4003 4005 if node:
4004 4006 raise error.Abort(_("cannot specify a node with --abort"))
4005 4007 if opts.get('rev'):
4006 4008 raise error.Abort(_("cannot specify both --rev and --abort"))
4007 4009 if opts.get('preview'):
4008 4010 raise error.Abort(_("cannot specify --preview with --abort"))
4009 4011 if opts.get('rev') and node:
4010 4012 raise error.Abort(_("please specify just one revision"))
4011 4013 if not node:
4012 4014 node = opts.get('rev')
4013 4015
4014 4016 if node:
4015 4017 node = scmutil.revsingle(repo, node).node()
4016 4018
4017 4019 if not node and not abort:
4018 4020 node = repo[destutil.destmerge(repo)].node()
4019 4021
4020 4022 if opts.get('preview'):
4021 4023 # find nodes that are ancestors of p2 but not of p1
4022 4024 p1 = repo.lookup('.')
4023 4025 p2 = node
4024 4026 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4025 4027
4026 4028 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4027 4029 for node in nodes:
4028 4030 displayer.show(repo[node])
4029 4031 displayer.close()
4030 4032 return 0
4031 4033
4032 4034 # ui.forcemerge is an internal variable, do not document
4033 4035 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4034 4036 with ui.configoverride(overrides, 'merge'):
4035 4037 force = opts.get('force')
4036 4038 labels = ['working copy', 'merge rev']
4037 4039 return hg.merge(repo, node, force=force, mergeforce=force,
4038 4040 labels=labels, abort=abort)
4039 4041
4040 4042 @command('outgoing|out',
4041 4043 [('f', 'force', None, _('run even when the destination is unrelated')),
4042 4044 ('r', 'rev', [],
4043 4045 _('a changeset intended to be included in the destination'), _('REV')),
4044 4046 ('n', 'newest-first', None, _('show newest record first')),
4045 4047 ('B', 'bookmarks', False, _('compare bookmarks')),
4046 4048 ('b', 'branch', [], _('a specific branch you would like to push'),
4047 4049 _('BRANCH')),
4048 4050 ] + logopts + remoteopts + subrepoopts,
4049 4051 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4050 4052 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
4051 4053 def outgoing(ui, repo, dest=None, **opts):
4052 4054 """show changesets not found in the destination
4053 4055
4054 4056 Show changesets not found in the specified destination repository
4055 4057 or the default push location. These are the changesets that would
4056 4058 be pushed if a push was requested.
4057 4059
4058 4060 See pull for details of valid destination formats.
4059 4061
4060 4062 .. container:: verbose
4061 4063
4062 4064 With -B/--bookmarks, the result of bookmark comparison between
4063 4065 local and remote repositories is displayed. With -v/--verbose,
4064 4066 status is also displayed for each bookmark like below::
4065 4067
4066 4068 BM1 01234567890a added
4067 4069 BM2 deleted
4068 4070 BM3 234567890abc advanced
4069 4071 BM4 34567890abcd diverged
4070 4072 BM5 4567890abcde changed
4071 4073
4072 4074 The action taken when pushing depends on the
4073 4075 status of each bookmark:
4074 4076
4075 4077 :``added``: push with ``-B`` will create it
4076 4078 :``deleted``: push with ``-B`` will delete it
4077 4079 :``advanced``: push will update it
4078 4080 :``diverged``: push with ``-B`` will update it
4079 4081 :``changed``: push with ``-B`` will update it
4080 4082
4081 4083 From the point of view of pushing behavior, bookmarks
4082 4084 existing only in the remote repository are treated as
4083 4085 ``deleted``, even if it is in fact added remotely.
4084 4086
4085 4087 Returns 0 if there are outgoing changes, 1 otherwise.
4086 4088 """
4087 4089 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4088 4090 # style URLs, so don't overwrite dest.
4089 4091 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4090 4092 if not path:
4091 4093 raise error.Abort(_('default repository not configured!'),
4092 4094 hint=_("see 'hg help config.paths'"))
4093 4095
4094 4096 opts = pycompat.byteskwargs(opts)
4095 4097 if opts.get('graph'):
4096 4098 logcmdutil.checkunsupportedgraphflags([], opts)
4097 4099 o, other = hg._outgoing(ui, repo, dest, opts)
4098 4100 if not o:
4099 4101 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4100 4102 return
4101 4103
4102 4104 revdag = logcmdutil.graphrevs(repo, o, opts)
4103 4105 ui.pager('outgoing')
4104 4106 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4105 4107 logcmdutil.displaygraph(ui, repo, revdag, displayer,
4106 4108 graphmod.asciiedges)
4107 4109 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4108 4110 return 0
4109 4111
4110 4112 if opts.get('bookmarks'):
4111 4113 dest = path.pushloc or path.loc
4112 4114 other = hg.peer(repo, opts, dest)
4113 4115 if 'bookmarks' not in other.listkeys('namespaces'):
4114 4116 ui.warn(_("remote doesn't support bookmarks\n"))
4115 4117 return 0
4116 4118 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4117 4119 ui.pager('outgoing')
4118 4120 return bookmarks.outgoing(ui, repo, other)
4119 4121
4120 4122 repo._subtoppath = path.pushloc or path.loc
4121 4123 try:
4122 4124 return hg.outgoing(ui, repo, dest, opts)
4123 4125 finally:
4124 4126 del repo._subtoppath
4125 4127
4126 4128 @command('parents',
4127 4129 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4128 4130 ] + templateopts,
4129 4131 _('[-r REV] [FILE]'),
4130 4132 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4131 4133 inferrepo=True)
4132 4134 def parents(ui, repo, file_=None, **opts):
4133 4135 """show the parents of the working directory or revision (DEPRECATED)
4134 4136
4135 4137 Print the working directory's parent revisions. If a revision is
4136 4138 given via -r/--rev, the parent of that revision will be printed.
4137 4139 If a file argument is given, the revision in which the file was
4138 4140 last changed (before the working directory revision or the
4139 4141 argument to --rev if given) is printed.
4140 4142
4141 4143 This command is equivalent to::
4142 4144
4143 4145 hg log -r "p1()+p2()" or
4144 4146 hg log -r "p1(REV)+p2(REV)" or
4145 4147 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4146 4148 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4147 4149
4148 4150 See :hg:`summary` and :hg:`help revsets` for related information.
4149 4151
4150 4152 Returns 0 on success.
4151 4153 """
4152 4154
4153 4155 opts = pycompat.byteskwargs(opts)
4154 4156 rev = opts.get('rev')
4155 4157 if rev:
4156 4158 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4157 4159 ctx = scmutil.revsingle(repo, rev, None)
4158 4160
4159 4161 if file_:
4160 4162 m = scmutil.match(ctx, (file_,), opts)
4161 4163 if m.anypats() or len(m.files()) != 1:
4162 4164 raise error.Abort(_('can only specify an explicit filename'))
4163 4165 file_ = m.files()[0]
4164 4166 filenodes = []
4165 4167 for cp in ctx.parents():
4166 4168 if not cp:
4167 4169 continue
4168 4170 try:
4169 4171 filenodes.append(cp.filenode(file_))
4170 4172 except error.LookupError:
4171 4173 pass
4172 4174 if not filenodes:
4173 4175 raise error.Abort(_("'%s' not found in manifest!") % file_)
4174 4176 p = []
4175 4177 for fn in filenodes:
4176 4178 fctx = repo.filectx(file_, fileid=fn)
4177 4179 p.append(fctx.node())
4178 4180 else:
4179 4181 p = [cp.node() for cp in ctx.parents()]
4180 4182
4181 4183 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4182 4184 for n in p:
4183 4185 if n != nullid:
4184 4186 displayer.show(repo[n])
4185 4187 displayer.close()
4186 4188
4187 4189 @command('paths', formatteropts, _('[NAME]'),
4188 4190 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4189 4191 optionalrepo=True, intents={INTENT_READONLY})
4190 4192 def paths(ui, repo, search=None, **opts):
4191 4193 """show aliases for remote repositories
4192 4194
4193 4195 Show definition of symbolic path name NAME. If no name is given,
4194 4196 show definition of all available names.
4195 4197
4196 4198 Option -q/--quiet suppresses all output when searching for NAME
4197 4199 and shows only the path names when listing all definitions.
4198 4200
4199 4201 Path names are defined in the [paths] section of your
4200 4202 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4201 4203 repository, ``.hg/hgrc`` is used, too.
4202 4204
4203 4205 The path names ``default`` and ``default-push`` have a special
4204 4206 meaning. When performing a push or pull operation, they are used
4205 4207 as fallbacks if no location is specified on the command-line.
4206 4208 When ``default-push`` is set, it will be used for push and
4207 4209 ``default`` will be used for pull; otherwise ``default`` is used
4208 4210 as the fallback for both. When cloning a repository, the clone
4209 4211 source is written as ``default`` in ``.hg/hgrc``.
4210 4212
4211 4213 .. note::
4212 4214
4213 4215 ``default`` and ``default-push`` apply to all inbound (e.g.
4214 4216 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
4215 4217 and :hg:`bundle`) operations.
4216 4218
4217 4219 See :hg:`help urls` for more information.
4218 4220
4219 4221 .. container:: verbose
4220 4222
4221 4223 Template:
4222 4224
4223 4225 The following keywords are supported. See also :hg:`help templates`.
4224 4226
4225 4227 :name: String. Symbolic name of the path alias.
4226 4228 :pushurl: String. URL for push operations.
4227 4229 :url: String. URL or directory path for the other operations.
4228 4230
4229 4231 Returns 0 on success.
4230 4232 """
4231 4233
4232 4234 opts = pycompat.byteskwargs(opts)
4233 4235 ui.pager('paths')
4234 4236 if search:
4235 4237 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4236 4238 if name == search]
4237 4239 else:
4238 4240 pathitems = sorted(ui.paths.iteritems())
4239 4241
4240 4242 fm = ui.formatter('paths', opts)
4241 4243 if fm.isplain():
4242 4244 hidepassword = util.hidepassword
4243 4245 else:
4244 4246 hidepassword = bytes
4245 4247 if ui.quiet:
4246 4248 namefmt = '%s\n'
4247 4249 else:
4248 4250 namefmt = '%s = '
4249 4251 showsubopts = not search and not ui.quiet
4250 4252
4251 4253 for name, path in pathitems:
4252 4254 fm.startitem()
4253 4255 fm.condwrite(not search, 'name', namefmt, name)
4254 4256 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4255 4257 for subopt, value in sorted(path.suboptions.items()):
4256 4258 assert subopt not in ('name', 'url')
4257 4259 if showsubopts:
4258 4260 fm.plain('%s:%s = ' % (name, subopt))
4259 4261 fm.condwrite(showsubopts, subopt, '%s\n', value)
4260 4262
4261 4263 fm.end()
4262 4264
4263 4265 if search and not pathitems:
4264 4266 if not ui.quiet:
4265 4267 ui.warn(_("not found!\n"))
4266 4268 return 1
4267 4269 else:
4268 4270 return 0
4269 4271
4270 4272 @command('phase',
4271 4273 [('p', 'public', False, _('set changeset phase to public')),
4272 4274 ('d', 'draft', False, _('set changeset phase to draft')),
4273 4275 ('s', 'secret', False, _('set changeset phase to secret')),
4274 4276 ('f', 'force', False, _('allow to move boundary backward')),
4275 4277 ('r', 'rev', [], _('target revision'), _('REV')),
4276 4278 ],
4277 4279 _('[-p|-d|-s] [-f] [-r] [REV...]'),
4278 4280 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
4279 4281 def phase(ui, repo, *revs, **opts):
4280 4282 """set or show the current phase name
4281 4283
4282 4284 With no argument, show the phase name of the current revision(s).
4283 4285
4284 4286 With one of -p/--public, -d/--draft or -s/--secret, change the
4285 4287 phase value of the specified revisions.
4286 4288
4287 4289 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4288 4290 lower phase to a higher phase. Phases are ordered as follows::
4289 4291
4290 4292 public < draft < secret
4291 4293
4292 4294 Returns 0 on success, 1 if some phases could not be changed.
4293 4295
4294 4296 (For more information about the phases concept, see :hg:`help phases`.)
4295 4297 """
4296 4298 opts = pycompat.byteskwargs(opts)
4297 4299 # search for a unique phase argument
4298 4300 targetphase = None
4299 4301 for idx, name in enumerate(phases.cmdphasenames):
4300 4302 if opts[name]:
4301 4303 if targetphase is not None:
4302 4304 raise error.Abort(_('only one phase can be specified'))
4303 4305 targetphase = idx
4304 4306
4305 4307 # look for specified revision
4306 4308 revs = list(revs)
4307 4309 revs.extend(opts['rev'])
4308 4310 if not revs:
4309 4311 # display both parents as the second parent phase can influence
4310 4312 # the phase of a merge commit
4311 4313 revs = [c.rev() for c in repo[None].parents()]
4312 4314
4313 4315 revs = scmutil.revrange(repo, revs)
4314 4316
4315 4317 ret = 0
4316 4318 if targetphase is None:
4317 4319 # display
4318 4320 for r in revs:
4319 4321 ctx = repo[r]
4320 4322 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4321 4323 else:
4322 4324 with repo.lock(), repo.transaction("phase") as tr:
4323 4325 # set phase
4324 4326 if not revs:
4325 4327 raise error.Abort(_('empty revision set'))
4326 4328 nodes = [repo[r].node() for r in revs]
4327 4329 # moving revision from public to draft may hide them
4328 4330 # We have to check result on an unfiltered repository
4329 4331 unfi = repo.unfiltered()
4330 4332 getphase = unfi._phasecache.phase
4331 4333 olddata = [getphase(unfi, r) for r in unfi]
4332 4334 phases.advanceboundary(repo, tr, targetphase, nodes)
4333 4335 if opts['force']:
4334 4336 phases.retractboundary(repo, tr, targetphase, nodes)
4335 4337 getphase = unfi._phasecache.phase
4336 4338 newdata = [getphase(unfi, r) for r in unfi]
4337 4339 changes = sum(newdata[r] != olddata[r] for r in unfi)
4338 4340 cl = unfi.changelog
4339 4341 rejected = [n for n in nodes
4340 4342 if newdata[cl.rev(n)] < targetphase]
4341 4343 if rejected:
4342 4344 ui.warn(_('cannot move %i changesets to a higher '
4343 4345 'phase, use --force\n') % len(rejected))
4344 4346 ret = 1
4345 4347 if changes:
4346 4348 msg = _('phase changed for %i changesets\n') % changes
4347 4349 if ret:
4348 4350 ui.status(msg)
4349 4351 else:
4350 4352 ui.note(msg)
4351 4353 else:
4352 4354 ui.warn(_('no phases changed\n'))
4353 4355 return ret
4354 4356
4355 4357 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4356 4358 """Run after a changegroup has been added via pull/unbundle
4357 4359
4358 4360 This takes arguments below:
4359 4361
4360 4362 :modheads: change of heads by pull/unbundle
4361 4363 :optupdate: updating working directory is needed or not
4362 4364 :checkout: update destination revision (or None to default destination)
4363 4365 :brev: a name, which might be a bookmark to be activated after updating
4364 4366 """
4365 4367 if modheads == 0:
4366 4368 return
4367 4369 if optupdate:
4368 4370 try:
4369 4371 return hg.updatetotally(ui, repo, checkout, brev)
4370 4372 except error.UpdateAbort as inst:
4371 4373 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4372 4374 hint = inst.hint
4373 4375 raise error.UpdateAbort(msg, hint=hint)
4374 4376 if modheads is not None and modheads > 1:
4375 4377 currentbranchheads = len(repo.branchheads())
4376 4378 if currentbranchheads == modheads:
4377 4379 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4378 4380 elif currentbranchheads > 1:
4379 4381 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4380 4382 "merge)\n"))
4381 4383 else:
4382 4384 ui.status(_("(run 'hg heads' to see heads)\n"))
4383 4385 elif not ui.configbool('commands', 'update.requiredest'):
4384 4386 ui.status(_("(run 'hg update' to get a working copy)\n"))
4385 4387
4386 4388 @command('pull',
4387 4389 [('u', 'update', None,
4388 4390 _('update to new branch head if new descendants were pulled')),
4389 4391 ('f', 'force', None, _('run even when remote repository is unrelated')),
4390 4392 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4391 4393 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4392 4394 ('b', 'branch', [], _('a specific branch you would like to pull'),
4393 4395 _('BRANCH')),
4394 4396 ] + remoteopts,
4395 4397 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
4396 4398 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4397 4399 helpbasic=True)
4398 4400 def pull(ui, repo, source="default", **opts):
4399 4401 """pull changes from the specified source
4400 4402
4401 4403 Pull changes from a remote repository to a local one.
4402 4404
4403 4405 This finds all changes from the repository at the specified path
4404 4406 or URL and adds them to a local repository (the current one unless
4405 4407 -R is specified). By default, this does not update the copy of the
4406 4408 project in the working directory.
4407 4409
4408 4410 When cloning from servers that support it, Mercurial may fetch
4409 4411 pre-generated data. When this is done, hooks operating on incoming
4410 4412 changesets and changegroups may fire more than once, once for each
4411 4413 pre-generated bundle and as well as for any additional remaining
4412 4414 data. See :hg:`help -e clonebundles` for more.
4413 4415
4414 4416 Use :hg:`incoming` if you want to see what would have been added
4415 4417 by a pull at the time you issued this command. If you then decide
4416 4418 to add those changes to the repository, you should use :hg:`pull
4417 4419 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4418 4420
4419 4421 If SOURCE is omitted, the 'default' path will be used.
4420 4422 See :hg:`help urls` for more information.
4421 4423
4422 4424 Specifying bookmark as ``.`` is equivalent to specifying the active
4423 4425 bookmark's name.
4424 4426
4425 4427 Returns 0 on success, 1 if an update had unresolved files.
4426 4428 """
4427 4429
4428 4430 opts = pycompat.byteskwargs(opts)
4429 4431 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4430 4432 msg = _('update destination required by configuration')
4431 4433 hint = _('use hg pull followed by hg update DEST')
4432 4434 raise error.Abort(msg, hint=hint)
4433 4435
4434 4436 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4435 4437 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4436 4438 other = hg.peer(repo, opts, source)
4437 4439 try:
4438 4440 revs, checkout = hg.addbranchrevs(repo, other, branches,
4439 4441 opts.get('rev'))
4440 4442
4441 4443 pullopargs = {}
4442 4444
4443 4445 nodes = None
4444 4446 if opts.get('bookmark') or revs:
4445 4447 # The list of bookmark used here is the same used to actually update
4446 4448 # the bookmark names, to avoid the race from issue 4689 and we do
4447 4449 # all lookup and bookmark queries in one go so they see the same
4448 4450 # version of the server state (issue 4700).
4449 4451 nodes = []
4450 4452 fnodes = []
4451 4453 revs = revs or []
4452 4454 if revs and not other.capable('lookup'):
4453 4455 err = _("other repository doesn't support revision lookup, "
4454 4456 "so a rev cannot be specified.")
4455 4457 raise error.Abort(err)
4456 4458 with other.commandexecutor() as e:
4457 4459 fremotebookmarks = e.callcommand('listkeys', {
4458 4460 'namespace': 'bookmarks'
4459 4461 })
4460 4462 for r in revs:
4461 4463 fnodes.append(e.callcommand('lookup', {'key': r}))
4462 4464 remotebookmarks = fremotebookmarks.result()
4463 4465 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4464 4466 pullopargs['remotebookmarks'] = remotebookmarks
4465 4467 for b in opts.get('bookmark', []):
4466 4468 b = repo._bookmarks.expandname(b)
4467 4469 if b not in remotebookmarks:
4468 4470 raise error.Abort(_('remote bookmark %s not found!') % b)
4469 4471 nodes.append(remotebookmarks[b])
4470 4472 for i, rev in enumerate(revs):
4471 4473 node = fnodes[i].result()
4472 4474 nodes.append(node)
4473 4475 if rev == checkout:
4474 4476 checkout = node
4475 4477
4476 4478 wlock = util.nullcontextmanager()
4477 4479 if opts.get('update'):
4478 4480 wlock = repo.wlock()
4479 4481 with wlock:
4480 4482 pullopargs.update(opts.get('opargs', {}))
4481 4483 modheads = exchange.pull(repo, other, heads=nodes,
4482 4484 force=opts.get('force'),
4483 4485 bookmarks=opts.get('bookmark', ()),
4484 4486 opargs=pullopargs).cgresult
4485 4487
4486 4488 # brev is a name, which might be a bookmark to be activated at
4487 4489 # the end of the update. In other words, it is an explicit
4488 4490 # destination of the update
4489 4491 brev = None
4490 4492
4491 4493 if checkout:
4492 4494 checkout = repo.changelog.rev(checkout)
4493 4495
4494 4496 # order below depends on implementation of
4495 4497 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4496 4498 # because 'checkout' is determined without it.
4497 4499 if opts.get('rev'):
4498 4500 brev = opts['rev'][0]
4499 4501 elif opts.get('branch'):
4500 4502 brev = opts['branch'][0]
4501 4503 else:
4502 4504 brev = branches[0]
4503 4505 repo._subtoppath = source
4504 4506 try:
4505 4507 ret = postincoming(ui, repo, modheads, opts.get('update'),
4506 4508 checkout, brev)
4507 4509
4508 4510 finally:
4509 4511 del repo._subtoppath
4510 4512
4511 4513 finally:
4512 4514 other.close()
4513 4515 return ret
4514 4516
4515 4517 @command('push',
4516 4518 [('f', 'force', None, _('force push')),
4517 4519 ('r', 'rev', [],
4518 4520 _('a changeset intended to be included in the destination'),
4519 4521 _('REV')),
4520 4522 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4521 4523 ('b', 'branch', [],
4522 4524 _('a specific branch you would like to push'), _('BRANCH')),
4523 4525 ('', 'new-branch', False, _('allow pushing a new branch')),
4524 4526 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4525 4527 ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')),
4526 4528 ] + remoteopts,
4527 4529 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
4528 4530 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4529 4531 helpbasic=True)
4530 4532 def push(ui, repo, dest=None, **opts):
4531 4533 """push changes to the specified destination
4532 4534
4533 4535 Push changesets from the local repository to the specified
4534 4536 destination.
4535 4537
4536 4538 This operation is symmetrical to pull: it is identical to a pull
4537 4539 in the destination repository from the current one.
4538 4540
4539 4541 By default, push will not allow creation of new heads at the
4540 4542 destination, since multiple heads would make it unclear which head
4541 4543 to use. In this situation, it is recommended to pull and merge
4542 4544 before pushing.
4543 4545
4544 4546 Use --new-branch if you want to allow push to create a new named
4545 4547 branch that is not present at the destination. This allows you to
4546 4548 only create a new branch without forcing other changes.
4547 4549
4548 4550 .. note::
4549 4551
4550 4552 Extra care should be taken with the -f/--force option,
4551 4553 which will push all new heads on all branches, an action which will
4552 4554 almost always cause confusion for collaborators.
4553 4555
4554 4556 If -r/--rev is used, the specified revision and all its ancestors
4555 4557 will be pushed to the remote repository.
4556 4558
4557 4559 If -B/--bookmark is used, the specified bookmarked revision, its
4558 4560 ancestors, and the bookmark will be pushed to the remote
4559 4561 repository. Specifying ``.`` is equivalent to specifying the active
4560 4562 bookmark's name.
4561 4563
4562 4564 Please see :hg:`help urls` for important details about ``ssh://``
4563 4565 URLs. If DESTINATION is omitted, a default path will be used.
4564 4566
4565 4567 .. container:: verbose
4566 4568
4567 4569 The --pushvars option sends strings to the server that become
4568 4570 environment variables prepended with ``HG_USERVAR_``. For example,
4569 4571 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4570 4572 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4571 4573
4572 4574 pushvars can provide for user-overridable hooks as well as set debug
4573 4575 levels. One example is having a hook that blocks commits containing
4574 4576 conflict markers, but enables the user to override the hook if the file
4575 4577 is using conflict markers for testing purposes or the file format has
4576 4578 strings that look like conflict markers.
4577 4579
4578 4580 By default, servers will ignore `--pushvars`. To enable it add the
4579 4581 following to your configuration file::
4580 4582
4581 4583 [push]
4582 4584 pushvars.server = true
4583 4585
4584 4586 Returns 0 if push was successful, 1 if nothing to push.
4585 4587 """
4586 4588
4587 4589 opts = pycompat.byteskwargs(opts)
4588 4590 if opts.get('bookmark'):
4589 4591 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4590 4592 for b in opts['bookmark']:
4591 4593 # translate -B options to -r so changesets get pushed
4592 4594 b = repo._bookmarks.expandname(b)
4593 4595 if b in repo._bookmarks:
4594 4596 opts.setdefault('rev', []).append(b)
4595 4597 else:
4596 4598 # if we try to push a deleted bookmark, translate it to null
4597 4599 # this lets simultaneous -r, -b options continue working
4598 4600 opts.setdefault('rev', []).append("null")
4599 4601
4600 4602 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4601 4603 if not path:
4602 4604 raise error.Abort(_('default repository not configured!'),
4603 4605 hint=_("see 'hg help config.paths'"))
4604 4606 dest = path.pushloc or path.loc
4605 4607 branches = (path.branch, opts.get('branch') or [])
4606 4608 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4607 4609 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4608 4610 other = hg.peer(repo, opts, dest)
4609 4611
4610 4612 if revs:
4611 4613 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4612 4614 if not revs:
4613 4615 raise error.Abort(_("specified revisions evaluate to an empty set"),
4614 4616 hint=_("use different revision arguments"))
4615 4617 elif path.pushrev:
4616 4618 # It doesn't make any sense to specify ancestor revisions. So limit
4617 4619 # to DAG heads to make discovery simpler.
4618 4620 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4619 4621 revs = scmutil.revrange(repo, [expr])
4620 4622 revs = [repo[rev].node() for rev in revs]
4621 4623 if not revs:
4622 4624 raise error.Abort(_('default push revset for path evaluates to an '
4623 4625 'empty set'))
4624 4626
4625 4627 repo._subtoppath = dest
4626 4628 try:
4627 4629 # push subrepos depth-first for coherent ordering
4628 4630 c = repo['.']
4629 4631 subs = c.substate # only repos that are committed
4630 4632 for s in sorted(subs):
4631 4633 result = c.sub(s).push(opts)
4632 4634 if result == 0:
4633 4635 return not result
4634 4636 finally:
4635 4637 del repo._subtoppath
4636 4638
4637 4639 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4638 4640 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4639 4641
4640 4642 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4641 4643 newbranch=opts.get('new_branch'),
4642 4644 bookmarks=opts.get('bookmark', ()),
4643 4645 publish=opts.get('publish'),
4644 4646 opargs=opargs)
4645 4647
4646 4648 result = not pushop.cgresult
4647 4649
4648 4650 if pushop.bkresult is not None:
4649 4651 if pushop.bkresult == 2:
4650 4652 result = 2
4651 4653 elif not result and pushop.bkresult:
4652 4654 result = 2
4653 4655
4654 4656 return result
4655 4657
4656 4658 @command('recover', [], helpcategory=command.CATEGORY_MAINTENANCE)
4657 4659 def recover(ui, repo):
4658 4660 """roll back an interrupted transaction
4659 4661
4660 4662 Recover from an interrupted commit or pull.
4661 4663
4662 4664 This command tries to fix the repository status after an
4663 4665 interrupted operation. It should only be necessary when Mercurial
4664 4666 suggests it.
4665 4667
4666 4668 Returns 0 if successful, 1 if nothing to recover or verify fails.
4667 4669 """
4668 4670 if repo.recover():
4669 4671 return hg.verify(repo)
4670 4672 return 1
4671 4673
4672 4674 @command('remove|rm',
4673 4675 [('A', 'after', None, _('record delete for missing files')),
4674 4676 ('f', 'force', None,
4675 4677 _('forget added files, delete modified files')),
4676 4678 ] + subrepoopts + walkopts + dryrunopts,
4677 4679 _('[OPTION]... FILE...'),
4678 4680 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4679 4681 helpbasic=True, inferrepo=True)
4680 4682 def remove(ui, repo, *pats, **opts):
4681 4683 """remove the specified files on the next commit
4682 4684
4683 4685 Schedule the indicated files for removal from the current branch.
4684 4686
4685 4687 This command schedules the files to be removed at the next commit.
4686 4688 To undo a remove before that, see :hg:`revert`. To undo added
4687 4689 files, see :hg:`forget`.
4688 4690
4689 4691 .. container:: verbose
4690 4692
4691 4693 -A/--after can be used to remove only files that have already
4692 4694 been deleted, -f/--force can be used to force deletion, and -Af
4693 4695 can be used to remove files from the next revision without
4694 4696 deleting them from the working directory.
4695 4697
4696 4698 The following table details the behavior of remove for different
4697 4699 file states (columns) and option combinations (rows). The file
4698 4700 states are Added [A], Clean [C], Modified [M] and Missing [!]
4699 4701 (as reported by :hg:`status`). The actions are Warn, Remove
4700 4702 (from branch) and Delete (from disk):
4701 4703
4702 4704 ========= == == == ==
4703 4705 opt/state A C M !
4704 4706 ========= == == == ==
4705 4707 none W RD W R
4706 4708 -f R RD RD R
4707 4709 -A W W W R
4708 4710 -Af R R R R
4709 4711 ========= == == == ==
4710 4712
4711 4713 .. note::
4712 4714
4713 4715 :hg:`remove` never deletes files in Added [A] state from the
4714 4716 working directory, not even if ``--force`` is specified.
4715 4717
4716 4718 Returns 0 on success, 1 if any warnings encountered.
4717 4719 """
4718 4720
4719 4721 opts = pycompat.byteskwargs(opts)
4720 4722 after, force = opts.get('after'), opts.get('force')
4721 4723 dryrun = opts.get('dry_run')
4722 4724 if not pats and not after:
4723 4725 raise error.Abort(_('no files specified'))
4724 4726
4725 4727 m = scmutil.match(repo[None], pats, opts)
4726 4728 subrepos = opts.get('subrepos')
4727 4729 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
4728 4730 return cmdutil.remove(ui, repo, m, "", uipathfn, after, force, subrepos,
4729 4731 dryrun=dryrun)
4730 4732
4731 4733 @command('rename|move|mv',
4732 4734 [('A', 'after', None, _('record a rename that has already occurred')),
4733 4735 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4734 4736 ] + walkopts + dryrunopts,
4735 4737 _('[OPTION]... SOURCE... DEST'),
4736 4738 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
4737 4739 def rename(ui, repo, *pats, **opts):
4738 4740 """rename files; equivalent of copy + remove
4739 4741
4740 4742 Mark dest as copies of sources; mark sources for deletion. If dest
4741 4743 is a directory, copies are put in that directory. If dest is a
4742 4744 file, there can only be one source.
4743 4745
4744 4746 By default, this command copies the contents of files as they
4745 4747 exist in the working directory. If invoked with -A/--after, the
4746 4748 operation is recorded, but no copying is performed.
4747 4749
4748 4750 This command takes effect at the next commit. To undo a rename
4749 4751 before that, see :hg:`revert`.
4750 4752
4751 4753 Returns 0 on success, 1 if errors are encountered.
4752 4754 """
4753 4755 opts = pycompat.byteskwargs(opts)
4754 4756 with repo.wlock(False):
4755 4757 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4756 4758
4757 4759 @command('resolve',
4758 4760 [('a', 'all', None, _('select all unresolved files')),
4759 4761 ('l', 'list', None, _('list state of files needing merge')),
4760 4762 ('m', 'mark', None, _('mark files as resolved')),
4761 4763 ('u', 'unmark', None, _('mark files as unresolved')),
4762 4764 ('n', 'no-status', None, _('hide status prefix')),
4763 4765 ('', 're-merge', None, _('re-merge files'))]
4764 4766 + mergetoolopts + walkopts + formatteropts,
4765 4767 _('[OPTION]... [FILE]...'),
4766 4768 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4767 4769 inferrepo=True)
4768 4770 def resolve(ui, repo, *pats, **opts):
4769 4771 """redo merges or set/view the merge status of files
4770 4772
4771 4773 Merges with unresolved conflicts are often the result of
4772 4774 non-interactive merging using the ``internal:merge`` configuration
4773 4775 setting, or a command-line merge tool like ``diff3``. The resolve
4774 4776 command is used to manage the files involved in a merge, after
4775 4777 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4776 4778 working directory must have two parents). See :hg:`help
4777 4779 merge-tools` for information on configuring merge tools.
4778 4780
4779 4781 The resolve command can be used in the following ways:
4780 4782
4781 4783 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4782 4784 the specified files, discarding any previous merge attempts. Re-merging
4783 4785 is not performed for files already marked as resolved. Use ``--all/-a``
4784 4786 to select all unresolved files. ``--tool`` can be used to specify
4785 4787 the merge tool used for the given files. It overrides the HGMERGE
4786 4788 environment variable and your configuration files. Previous file
4787 4789 contents are saved with a ``.orig`` suffix.
4788 4790
4789 4791 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4790 4792 (e.g. after having manually fixed-up the files). The default is
4791 4793 to mark all unresolved files.
4792 4794
4793 4795 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4794 4796 default is to mark all resolved files.
4795 4797
4796 4798 - :hg:`resolve -l`: list files which had or still have conflicts.
4797 4799 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4798 4800 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4799 4801 the list. See :hg:`help filesets` for details.
4800 4802
4801 4803 .. note::
4802 4804
4803 4805 Mercurial will not let you commit files with unresolved merge
4804 4806 conflicts. You must use :hg:`resolve -m ...` before you can
4805 4807 commit after a conflicting merge.
4806 4808
4807 4809 .. container:: verbose
4808 4810
4809 4811 Template:
4810 4812
4811 4813 The following keywords are supported in addition to the common template
4812 4814 keywords and functions. See also :hg:`help templates`.
4813 4815
4814 4816 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
4815 4817 :path: String. Repository-absolute path of the file.
4816 4818
4817 4819 Returns 0 on success, 1 if any files fail a resolve attempt.
4818 4820 """
4819 4821
4820 4822 opts = pycompat.byteskwargs(opts)
4821 4823 confirm = ui.configbool('commands', 'resolve.confirm')
4822 4824 flaglist = 'all mark unmark list no_status re_merge'.split()
4823 4825 all, mark, unmark, show, nostatus, remerge = \
4824 4826 [opts.get(o) for o in flaglist]
4825 4827
4826 4828 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4827 4829 if actioncount > 1:
4828 4830 raise error.Abort(_("too many actions specified"))
4829 4831 elif (actioncount == 0
4830 4832 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4831 4833 hint = _('use --mark, --unmark, --list or --re-merge')
4832 4834 raise error.Abort(_('no action specified'), hint=hint)
4833 4835 if pats and all:
4834 4836 raise error.Abort(_("can't specify --all and patterns"))
4835 4837 if not (all or pats or show or mark or unmark):
4836 4838 raise error.Abort(_('no files or directories specified'),
4837 4839 hint=('use --all to re-merge all unresolved files'))
4838 4840
4839 4841 if confirm:
4840 4842 if all:
4841 4843 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4842 4844 b'$$ &Yes $$ &No')):
4843 4845 raise error.Abort(_('user quit'))
4844 4846 if mark and not pats:
4845 4847 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4846 4848 b'$$ &Yes $$ &No')):
4847 4849 raise error.Abort(_('user quit'))
4848 4850 if unmark and not pats:
4849 4851 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4850 4852 b'$$ &Yes $$ &No')):
4851 4853 raise error.Abort(_('user quit'))
4852 4854
4853 4855 uipathfn = scmutil.getuipathfn(repo)
4854 4856
4855 4857 if show:
4856 4858 ui.pager('resolve')
4857 4859 fm = ui.formatter('resolve', opts)
4858 4860 ms = mergemod.mergestate.read(repo)
4859 4861 wctx = repo[None]
4860 4862 m = scmutil.match(wctx, pats, opts)
4861 4863
4862 4864 # Labels and keys based on merge state. Unresolved path conflicts show
4863 4865 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4864 4866 # resolved conflicts.
4865 4867 mergestateinfo = {
4866 4868 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4867 4869 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4868 4870 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4869 4871 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4870 4872 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4871 4873 'D'),
4872 4874 }
4873 4875
4874 4876 for f in ms:
4875 4877 if not m(f):
4876 4878 continue
4877 4879
4878 4880 label, key = mergestateinfo[ms[f]]
4879 4881 fm.startitem()
4880 4882 fm.context(ctx=wctx)
4881 4883 fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label)
4882 4884 fm.data(path=f)
4883 4885 fm.plain('%s\n' % uipathfn(f), label=label)
4884 4886 fm.end()
4885 4887 return 0
4886 4888
4887 4889 with repo.wlock():
4888 4890 ms = mergemod.mergestate.read(repo)
4889 4891
4890 4892 if not (ms.active() or repo.dirstate.p2() != nullid):
4891 4893 raise error.Abort(
4892 4894 _('resolve command not applicable when not merging'))
4893 4895
4894 4896 wctx = repo[None]
4895 4897
4896 4898 if (ms.mergedriver
4897 4899 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4898 4900 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4899 4901 ms.commit()
4900 4902 # allow mark and unmark to go through
4901 4903 if not mark and not unmark and not proceed:
4902 4904 return 1
4903 4905
4904 4906 m = scmutil.match(wctx, pats, opts)
4905 4907 ret = 0
4906 4908 didwork = False
4907 4909 runconclude = False
4908 4910
4909 4911 tocomplete = []
4910 4912 hasconflictmarkers = []
4911 4913 if mark:
4912 4914 markcheck = ui.config('commands', 'resolve.mark-check')
4913 4915 if markcheck not in ['warn', 'abort']:
4914 4916 # Treat all invalid / unrecognized values as 'none'.
4915 4917 markcheck = False
4916 4918 for f in ms:
4917 4919 if not m(f):
4918 4920 continue
4919 4921
4920 4922 didwork = True
4921 4923
4922 4924 # don't let driver-resolved files be marked, and run the conclude
4923 4925 # step if asked to resolve
4924 4926 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4925 4927 exact = m.exact(f)
4926 4928 if mark:
4927 4929 if exact:
4928 4930 ui.warn(_('not marking %s as it is driver-resolved\n')
4929 4931 % uipathfn(f))
4930 4932 elif unmark:
4931 4933 if exact:
4932 4934 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4933 4935 % uipathfn(f))
4934 4936 else:
4935 4937 runconclude = True
4936 4938 continue
4937 4939
4938 4940 # path conflicts must be resolved manually
4939 4941 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4940 4942 mergemod.MERGE_RECORD_RESOLVED_PATH):
4941 4943 if mark:
4942 4944 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4943 4945 elif unmark:
4944 4946 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4945 4947 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4946 4948 ui.warn(_('%s: path conflict must be resolved manually\n')
4947 4949 % uipathfn(f))
4948 4950 continue
4949 4951
4950 4952 if mark:
4951 4953 if markcheck:
4952 4954 fdata = repo.wvfs.tryread(f)
4953 4955 if filemerge.hasconflictmarkers(fdata) and \
4954 4956 ms[f] != mergemod.MERGE_RECORD_RESOLVED:
4955 4957 hasconflictmarkers.append(f)
4956 4958 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4957 4959 elif unmark:
4958 4960 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4959 4961 else:
4960 4962 # backup pre-resolve (merge uses .orig for its own purposes)
4961 4963 a = repo.wjoin(f)
4962 4964 try:
4963 4965 util.copyfile(a, a + ".resolve")
4964 4966 except (IOError, OSError) as inst:
4965 4967 if inst.errno != errno.ENOENT:
4966 4968 raise
4967 4969
4968 4970 try:
4969 4971 # preresolve file
4970 4972 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4971 4973 with ui.configoverride(overrides, 'resolve'):
4972 4974 complete, r = ms.preresolve(f, wctx)
4973 4975 if not complete:
4974 4976 tocomplete.append(f)
4975 4977 elif r:
4976 4978 ret = 1
4977 4979 finally:
4978 4980 ms.commit()
4979 4981
4980 4982 # replace filemerge's .orig file with our resolve file, but only
4981 4983 # for merges that are complete
4982 4984 if complete:
4983 4985 try:
4984 4986 util.rename(a + ".resolve",
4985 4987 scmutil.backuppath(ui, repo, f))
4986 4988 except OSError as inst:
4987 4989 if inst.errno != errno.ENOENT:
4988 4990 raise
4989 4991
4990 4992 if hasconflictmarkers:
4991 4993 ui.warn(_('warning: the following files still have conflict '
4992 4994 'markers:\n') + ''.join(' ' + uipathfn(f) + '\n'
4993 4995 for f in hasconflictmarkers))
4994 4996 if markcheck == 'abort' and not all and not pats:
4995 4997 raise error.Abort(_('conflict markers detected'),
4996 4998 hint=_('use --all to mark anyway'))
4997 4999
4998 5000 for f in tocomplete:
4999 5001 try:
5000 5002 # resolve file
5001 5003 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
5002 5004 with ui.configoverride(overrides, 'resolve'):
5003 5005 r = ms.resolve(f, wctx)
5004 5006 if r:
5005 5007 ret = 1
5006 5008 finally:
5007 5009 ms.commit()
5008 5010
5009 5011 # replace filemerge's .orig file with our resolve file
5010 5012 a = repo.wjoin(f)
5011 5013 try:
5012 5014 util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f))
5013 5015 except OSError as inst:
5014 5016 if inst.errno != errno.ENOENT:
5015 5017 raise
5016 5018
5017 5019 ms.commit()
5018 5020 ms.recordactions()
5019 5021
5020 5022 if not didwork and pats:
5021 5023 hint = None
5022 5024 if not any([p for p in pats if p.find(':') >= 0]):
5023 5025 pats = ['path:%s' % p for p in pats]
5024 5026 m = scmutil.match(wctx, pats, opts)
5025 5027 for f in ms:
5026 5028 if not m(f):
5027 5029 continue
5028 5030 def flag(o):
5029 5031 if o == 're_merge':
5030 5032 return '--re-merge '
5031 5033 return '-%s ' % o[0:1]
5032 5034 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
5033 5035 hint = _("(try: hg resolve %s%s)\n") % (
5034 5036 flags,
5035 5037 ' '.join(pats))
5036 5038 break
5037 5039 ui.warn(_("arguments do not match paths that need resolving\n"))
5038 5040 if hint:
5039 5041 ui.warn(hint)
5040 5042 elif ms.mergedriver and ms.mdstate() != 's':
5041 5043 # run conclude step when either a driver-resolved file is requested
5042 5044 # or there are no driver-resolved files
5043 5045 # we can't use 'ret' to determine whether any files are unresolved
5044 5046 # because we might not have tried to resolve some
5045 5047 if ((runconclude or not list(ms.driverresolved()))
5046 5048 and not list(ms.unresolved())):
5047 5049 proceed = mergemod.driverconclude(repo, ms, wctx)
5048 5050 ms.commit()
5049 5051 if not proceed:
5050 5052 return 1
5051 5053
5052 5054 # Nudge users into finishing an unfinished operation
5053 5055 unresolvedf = list(ms.unresolved())
5054 5056 driverresolvedf = list(ms.driverresolved())
5055 5057 if not unresolvedf and not driverresolvedf:
5056 5058 ui.status(_('(no more unresolved files)\n'))
5057 5059 cmdutil.checkafterresolved(repo)
5058 5060 elif not unresolvedf:
5059 5061 ui.status(_('(no more unresolved files -- '
5060 5062 'run "hg resolve --all" to conclude)\n'))
5061 5063
5062 5064 return ret
5063 5065
5064 5066 @command('revert',
5065 5067 [('a', 'all', None, _('revert all changes when no arguments given')),
5066 5068 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5067 5069 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5068 5070 ('C', 'no-backup', None, _('do not save backup copies of files')),
5069 5071 ('i', 'interactive', None, _('interactively select the changes')),
5070 5072 ] + walkopts + dryrunopts,
5071 5073 _('[OPTION]... [-r REV] [NAME]...'),
5072 5074 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5073 5075 def revert(ui, repo, *pats, **opts):
5074 5076 """restore files to their checkout state
5075 5077
5076 5078 .. note::
5077 5079
5078 5080 To check out earlier revisions, you should use :hg:`update REV`.
5079 5081 To cancel an uncommitted merge (and lose your changes),
5080 5082 use :hg:`merge --abort`.
5081 5083
5082 5084 With no revision specified, revert the specified files or directories
5083 5085 to the contents they had in the parent of the working directory.
5084 5086 This restores the contents of files to an unmodified
5085 5087 state and unschedules adds, removes, copies, and renames. If the
5086 5088 working directory has two parents, you must explicitly specify a
5087 5089 revision.
5088 5090
5089 5091 Using the -r/--rev or -d/--date options, revert the given files or
5090 5092 directories to their states as of a specific revision. Because
5091 5093 revert does not change the working directory parents, this will
5092 5094 cause these files to appear modified. This can be helpful to "back
5093 5095 out" some or all of an earlier change. See :hg:`backout` for a
5094 5096 related method.
5095 5097
5096 5098 Modified files are saved with a .orig suffix before reverting.
5097 5099 To disable these backups, use --no-backup. It is possible to store
5098 5100 the backup files in a custom directory relative to the root of the
5099 5101 repository by setting the ``ui.origbackuppath`` configuration
5100 5102 option.
5101 5103
5102 5104 See :hg:`help dates` for a list of formats valid for -d/--date.
5103 5105
5104 5106 See :hg:`help backout` for a way to reverse the effect of an
5105 5107 earlier changeset.
5106 5108
5107 5109 Returns 0 on success.
5108 5110 """
5109 5111
5110 5112 opts = pycompat.byteskwargs(opts)
5111 5113 if opts.get("date"):
5112 5114 if opts.get("rev"):
5113 5115 raise error.Abort(_("you can't specify a revision and a date"))
5114 5116 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5115 5117
5116 5118 parent, p2 = repo.dirstate.parents()
5117 5119 if not opts.get('rev') and p2 != nullid:
5118 5120 # revert after merge is a trap for new users (issue2915)
5119 5121 raise error.Abort(_('uncommitted merge with no revision specified'),
5120 5122 hint=_("use 'hg update' or see 'hg help revert'"))
5121 5123
5122 5124 rev = opts.get('rev')
5123 5125 if rev:
5124 5126 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5125 5127 ctx = scmutil.revsingle(repo, rev)
5126 5128
5127 5129 if (not (pats or opts.get('include') or opts.get('exclude') or
5128 5130 opts.get('all') or opts.get('interactive'))):
5129 5131 msg = _("no files or directories specified")
5130 5132 if p2 != nullid:
5131 5133 hint = _("uncommitted merge, use --all to discard all changes,"
5132 5134 " or 'hg update -C .' to abort the merge")
5133 5135 raise error.Abort(msg, hint=hint)
5134 5136 dirty = any(repo.status())
5135 5137 node = ctx.node()
5136 5138 if node != parent:
5137 5139 if dirty:
5138 5140 hint = _("uncommitted changes, use --all to discard all"
5139 5141 " changes, or 'hg update %d' to update") % ctx.rev()
5140 5142 else:
5141 5143 hint = _("use --all to revert all files,"
5142 5144 " or 'hg update %d' to update") % ctx.rev()
5143 5145 elif dirty:
5144 5146 hint = _("uncommitted changes, use --all to discard all changes")
5145 5147 else:
5146 5148 hint = _("use --all to revert all files")
5147 5149 raise error.Abort(msg, hint=hint)
5148 5150
5149 5151 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
5150 5152 **pycompat.strkwargs(opts))
5151 5153
5152 5154 @command(
5153 5155 'rollback',
5154 5156 dryrunopts + [('f', 'force', False, _('ignore safety measures'))],
5155 5157 helpcategory=command.CATEGORY_MAINTENANCE)
5156 5158 def rollback(ui, repo, **opts):
5157 5159 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5158 5160
5159 5161 Please use :hg:`commit --amend` instead of rollback to correct
5160 5162 mistakes in the last commit.
5161 5163
5162 5164 This command should be used with care. There is only one level of
5163 5165 rollback, and there is no way to undo a rollback. It will also
5164 5166 restore the dirstate at the time of the last transaction, losing
5165 5167 any dirstate changes since that time. This command does not alter
5166 5168 the working directory.
5167 5169
5168 5170 Transactions are used to encapsulate the effects of all commands
5169 5171 that create new changesets or propagate existing changesets into a
5170 5172 repository.
5171 5173
5172 5174 .. container:: verbose
5173 5175
5174 5176 For example, the following commands are transactional, and their
5175 5177 effects can be rolled back:
5176 5178
5177 5179 - commit
5178 5180 - import
5179 5181 - pull
5180 5182 - push (with this repository as the destination)
5181 5183 - unbundle
5182 5184
5183 5185 To avoid permanent data loss, rollback will refuse to rollback a
5184 5186 commit transaction if it isn't checked out. Use --force to
5185 5187 override this protection.
5186 5188
5187 5189 The rollback command can be entirely disabled by setting the
5188 5190 ``ui.rollback`` configuration setting to false. If you're here
5189 5191 because you want to use rollback and it's disabled, you can
5190 5192 re-enable the command by setting ``ui.rollback`` to true.
5191 5193
5192 5194 This command is not intended for use on public repositories. Once
5193 5195 changes are visible for pull by other users, rolling a transaction
5194 5196 back locally is ineffective (someone else may already have pulled
5195 5197 the changes). Furthermore, a race is possible with readers of the
5196 5198 repository; for example an in-progress pull from the repository
5197 5199 may fail if a rollback is performed.
5198 5200
5199 5201 Returns 0 on success, 1 if no rollback data is available.
5200 5202 """
5201 5203 if not ui.configbool('ui', 'rollback'):
5202 5204 raise error.Abort(_('rollback is disabled because it is unsafe'),
5203 5205 hint=('see `hg help -v rollback` for information'))
5204 5206 return repo.rollback(dryrun=opts.get(r'dry_run'),
5205 5207 force=opts.get(r'force'))
5206 5208
5207 5209 @command(
5208 5210 'root', [], intents={INTENT_READONLY},
5209 5211 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5210 5212 def root(ui, repo):
5211 5213 """print the root (top) of the current working directory
5212 5214
5213 5215 Print the root directory of the current repository.
5214 5216
5215 5217 Returns 0 on success.
5216 5218 """
5217 5219 ui.write(repo.root + "\n")
5218 5220
5219 5221 @command('serve',
5220 5222 [('A', 'accesslog', '', _('name of access log file to write to'),
5221 5223 _('FILE')),
5222 5224 ('d', 'daemon', None, _('run server in background')),
5223 5225 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
5224 5226 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5225 5227 # use string type, then we can check if something was passed
5226 5228 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5227 5229 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5228 5230 _('ADDR')),
5229 5231 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5230 5232 _('PREFIX')),
5231 5233 ('n', 'name', '',
5232 5234 _('name to show in web pages (default: working directory)'), _('NAME')),
5233 5235 ('', 'web-conf', '',
5234 5236 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
5235 5237 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5236 5238 _('FILE')),
5237 5239 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5238 5240 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
5239 5241 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
5240 5242 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5241 5243 ('', 'style', '', _('template style to use'), _('STYLE')),
5242 5244 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5243 5245 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
5244 5246 ('', 'print-url', None, _('start and print only the URL'))]
5245 5247 + subrepoopts,
5246 5248 _('[OPTION]...'),
5247 5249 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5248 5250 helpbasic=True, optionalrepo=True)
5249 5251 def serve(ui, repo, **opts):
5250 5252 """start stand-alone webserver
5251 5253
5252 5254 Start a local HTTP repository browser and pull server. You can use
5253 5255 this for ad-hoc sharing and browsing of repositories. It is
5254 5256 recommended to use a real web server to serve a repository for
5255 5257 longer periods of time.
5256 5258
5257 5259 Please note that the server does not implement access control.
5258 5260 This means that, by default, anybody can read from the server and
5259 5261 nobody can write to it by default. Set the ``web.allow-push``
5260 5262 option to ``*`` to allow everybody to push to the server. You
5261 5263 should use a real web server if you need to authenticate users.
5262 5264
5263 5265 By default, the server logs accesses to stdout and errors to
5264 5266 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5265 5267 files.
5266 5268
5267 5269 To have the server choose a free port number to listen on, specify
5268 5270 a port number of 0; in this case, the server will print the port
5269 5271 number it uses.
5270 5272
5271 5273 Returns 0 on success.
5272 5274 """
5273 5275
5274 5276 opts = pycompat.byteskwargs(opts)
5275 5277 if opts["stdio"] and opts["cmdserver"]:
5276 5278 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5277 5279 if opts["print_url"] and ui.verbose:
5278 5280 raise error.Abort(_("cannot use --print-url with --verbose"))
5279 5281
5280 5282 if opts["stdio"]:
5281 5283 if repo is None:
5282 5284 raise error.RepoError(_("there is no Mercurial repository here"
5283 5285 " (.hg not found)"))
5284 5286 s = wireprotoserver.sshserver(ui, repo)
5285 5287 s.serve_forever()
5286 5288
5287 5289 service = server.createservice(ui, repo, opts)
5288 5290 return server.runservice(opts, initfn=service.init, runfn=service.run)
5289 5291
5290 5292 _NOTTERSE = 'nothing'
5291 5293
5292 5294 @command('status|st',
5293 5295 [('A', 'all', None, _('show status of all files')),
5294 5296 ('m', 'modified', None, _('show only modified files')),
5295 5297 ('a', 'added', None, _('show only added files')),
5296 5298 ('r', 'removed', None, _('show only removed files')),
5297 5299 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5298 5300 ('c', 'clean', None, _('show only files without changes')),
5299 5301 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5300 5302 ('i', 'ignored', None, _('show only ignored files')),
5301 5303 ('n', 'no-status', None, _('hide status prefix')),
5302 5304 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5303 5305 ('C', 'copies', None, _('show source of copied files')),
5304 5306 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5305 5307 ('', 'rev', [], _('show difference from revision'), _('REV')),
5306 5308 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5307 5309 ] + walkopts + subrepoopts + formatteropts,
5308 5310 _('[OPTION]... [FILE]...'),
5309 5311 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5310 5312 helpbasic=True, inferrepo=True,
5311 5313 intents={INTENT_READONLY})
5312 5314 def status(ui, repo, *pats, **opts):
5313 5315 """show changed files in the working directory
5314 5316
5315 5317 Show status of files in the repository. If names are given, only
5316 5318 files that match are shown. Files that are clean or ignored or
5317 5319 the source of a copy/move operation, are not listed unless
5318 5320 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5319 5321 Unless options described with "show only ..." are given, the
5320 5322 options -mardu are used.
5321 5323
5322 5324 Option -q/--quiet hides untracked (unknown and ignored) files
5323 5325 unless explicitly requested with -u/--unknown or -i/--ignored.
5324 5326
5325 5327 .. note::
5326 5328
5327 5329 :hg:`status` may appear to disagree with diff if permissions have
5328 5330 changed or a merge has occurred. The standard diff format does
5329 5331 not report permission changes and diff only reports changes
5330 5332 relative to one merge parent.
5331 5333
5332 5334 If one revision is given, it is used as the base revision.
5333 5335 If two revisions are given, the differences between them are
5334 5336 shown. The --change option can also be used as a shortcut to list
5335 5337 the changed files of a revision from its first parent.
5336 5338
5337 5339 The codes used to show the status of files are::
5338 5340
5339 5341 M = modified
5340 5342 A = added
5341 5343 R = removed
5342 5344 C = clean
5343 5345 ! = missing (deleted by non-hg command, but still tracked)
5344 5346 ? = not tracked
5345 5347 I = ignored
5346 5348 = origin of the previous file (with --copies)
5347 5349
5348 5350 .. container:: verbose
5349 5351
5350 5352 The -t/--terse option abbreviates the output by showing only the directory
5351 5353 name if all the files in it share the same status. The option takes an
5352 5354 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5353 5355 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5354 5356 for 'ignored' and 'c' for clean.
5355 5357
5356 5358 It abbreviates only those statuses which are passed. Note that clean and
5357 5359 ignored files are not displayed with '--terse ic' unless the -c/--clean
5358 5360 and -i/--ignored options are also used.
5359 5361
5360 5362 The -v/--verbose option shows information when the repository is in an
5361 5363 unfinished merge, shelve, rebase state etc. You can have this behavior
5362 5364 turned on by default by enabling the ``commands.status.verbose`` option.
5363 5365
5364 5366 You can skip displaying some of these states by setting
5365 5367 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5366 5368 'histedit', 'merge', 'rebase', or 'unshelve'.
5367 5369
5368 5370 Template:
5369 5371
5370 5372 The following keywords are supported in addition to the common template
5371 5373 keywords and functions. See also :hg:`help templates`.
5372 5374
5373 5375 :path: String. Repository-absolute path of the file.
5374 5376 :source: String. Repository-absolute path of the file originated from.
5375 5377 Available if ``--copies`` is specified.
5376 5378 :status: String. Character denoting file's status.
5377 5379
5378 5380 Examples:
5379 5381
5380 5382 - show changes in the working directory relative to a
5381 5383 changeset::
5382 5384
5383 5385 hg status --rev 9353
5384 5386
5385 5387 - show changes in the working directory relative to the
5386 5388 current directory (see :hg:`help patterns` for more information)::
5387 5389
5388 5390 hg status re:
5389 5391
5390 5392 - show all changes including copies in an existing changeset::
5391 5393
5392 5394 hg status --copies --change 9353
5393 5395
5394 5396 - get a NUL separated list of added files, suitable for xargs::
5395 5397
5396 5398 hg status -an0
5397 5399
5398 5400 - show more information about the repository status, abbreviating
5399 5401 added, removed, modified, deleted, and untracked paths::
5400 5402
5401 5403 hg status -v -t mardu
5402 5404
5403 5405 Returns 0 on success.
5404 5406
5405 5407 """
5406 5408
5407 5409 opts = pycompat.byteskwargs(opts)
5408 5410 revs = opts.get('rev')
5409 5411 change = opts.get('change')
5410 5412 terse = opts.get('terse')
5411 5413 if terse is _NOTTERSE:
5412 5414 if revs:
5413 5415 terse = ''
5414 5416 else:
5415 5417 terse = ui.config('commands', 'status.terse')
5416 5418
5417 5419 if revs and change:
5418 5420 msg = _('cannot specify --rev and --change at the same time')
5419 5421 raise error.Abort(msg)
5420 5422 elif revs and terse:
5421 5423 msg = _('cannot use --terse with --rev')
5422 5424 raise error.Abort(msg)
5423 5425 elif change:
5424 5426 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5425 5427 ctx2 = scmutil.revsingle(repo, change, None)
5426 5428 ctx1 = ctx2.p1()
5427 5429 else:
5428 5430 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5429 5431 ctx1, ctx2 = scmutil.revpair(repo, revs)
5430 5432
5431 5433 forcerelativevalue = None
5432 5434 if ui.hasconfig('commands', 'status.relative'):
5433 5435 forcerelativevalue = ui.configbool('commands', 'status.relative')
5434 5436 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats),
5435 5437 forcerelativevalue=forcerelativevalue)
5436 5438
5437 5439 if opts.get('print0'):
5438 5440 end = '\0'
5439 5441 else:
5440 5442 end = '\n'
5441 5443 copy = {}
5442 5444 states = 'modified added removed deleted unknown ignored clean'.split()
5443 5445 show = [k for k in states if opts.get(k)]
5444 5446 if opts.get('all'):
5445 5447 show += ui.quiet and (states[:4] + ['clean']) or states
5446 5448
5447 5449 if not show:
5448 5450 if ui.quiet:
5449 5451 show = states[:4]
5450 5452 else:
5451 5453 show = states[:5]
5452 5454
5453 5455 m = scmutil.match(ctx2, pats, opts)
5454 5456 if terse:
5455 5457 # we need to compute clean and unknown to terse
5456 5458 stat = repo.status(ctx1.node(), ctx2.node(), m,
5457 5459 'ignored' in show or 'i' in terse,
5458 5460 clean=True, unknown=True,
5459 5461 listsubrepos=opts.get('subrepos'))
5460 5462
5461 5463 stat = cmdutil.tersedir(stat, terse)
5462 5464 else:
5463 5465 stat = repo.status(ctx1.node(), ctx2.node(), m,
5464 5466 'ignored' in show, 'clean' in show,
5465 5467 'unknown' in show, opts.get('subrepos'))
5466 5468
5467 5469 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5468 5470
5469 5471 if (opts.get('all') or opts.get('copies')
5470 5472 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5471 5473 copy = copies.pathcopies(ctx1, ctx2, m)
5472 5474
5473 5475 ui.pager('status')
5474 5476 fm = ui.formatter('status', opts)
5475 5477 fmt = '%s' + end
5476 5478 showchar = not opts.get('no_status')
5477 5479
5478 5480 for state, char, files in changestates:
5479 5481 if state in show:
5480 5482 label = 'status.' + state
5481 5483 for f in files:
5482 5484 fm.startitem()
5483 5485 fm.context(ctx=ctx2)
5484 5486 fm.data(path=f)
5485 5487 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5486 5488 fm.plain(fmt % uipathfn(f), label=label)
5487 5489 if f in copy:
5488 5490 fm.data(source=copy[f])
5489 5491 fm.plain((' %s' + end) % uipathfn(copy[f]),
5490 5492 label='status.copied')
5491 5493
5492 5494 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5493 5495 and not ui.plain()):
5494 5496 cmdutil.morestatus(repo, fm)
5495 5497 fm.end()
5496 5498
5497 5499 @command('summary|sum',
5498 5500 [('', 'remote', None, _('check for push and pull'))],
5499 5501 '[--remote]',
5500 5502 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5501 5503 helpbasic=True,
5502 5504 intents={INTENT_READONLY})
5503 5505 def summary(ui, repo, **opts):
5504 5506 """summarize working directory state
5505 5507
5506 5508 This generates a brief summary of the working directory state,
5507 5509 including parents, branch, commit status, phase and available updates.
5508 5510
5509 5511 With the --remote option, this will check the default paths for
5510 5512 incoming and outgoing changes. This can be time-consuming.
5511 5513
5512 5514 Returns 0 on success.
5513 5515 """
5514 5516
5515 5517 opts = pycompat.byteskwargs(opts)
5516 5518 ui.pager('summary')
5517 5519 ctx = repo[None]
5518 5520 parents = ctx.parents()
5519 5521 pnode = parents[0].node()
5520 5522 marks = []
5521 5523
5522 5524 try:
5523 5525 ms = mergemod.mergestate.read(repo)
5524 5526 except error.UnsupportedMergeRecords as e:
5525 5527 s = ' '.join(e.recordtypes)
5526 5528 ui.warn(
5527 5529 _('warning: merge state has unsupported record types: %s\n') % s)
5528 5530 unresolved = []
5529 5531 else:
5530 5532 unresolved = list(ms.unresolved())
5531 5533
5532 5534 for p in parents:
5533 5535 # label with log.changeset (instead of log.parent) since this
5534 5536 # shows a working directory parent *changeset*:
5535 5537 # i18n: column positioning for "hg summary"
5536 5538 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5537 5539 label=logcmdutil.changesetlabels(p))
5538 5540 ui.write(' '.join(p.tags()), label='log.tag')
5539 5541 if p.bookmarks():
5540 5542 marks.extend(p.bookmarks())
5541 5543 if p.rev() == -1:
5542 5544 if not len(repo):
5543 5545 ui.write(_(' (empty repository)'))
5544 5546 else:
5545 5547 ui.write(_(' (no revision checked out)'))
5546 5548 if p.obsolete():
5547 5549 ui.write(_(' (obsolete)'))
5548 5550 if p.isunstable():
5549 5551 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5550 5552 for instability in p.instabilities())
5551 5553 ui.write(' ('
5552 5554 + ', '.join(instabilities)
5553 5555 + ')')
5554 5556 ui.write('\n')
5555 5557 if p.description():
5556 5558 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5557 5559 label='log.summary')
5558 5560
5559 5561 branch = ctx.branch()
5560 5562 bheads = repo.branchheads(branch)
5561 5563 # i18n: column positioning for "hg summary"
5562 5564 m = _('branch: %s\n') % branch
5563 5565 if branch != 'default':
5564 5566 ui.write(m, label='log.branch')
5565 5567 else:
5566 5568 ui.status(m, label='log.branch')
5567 5569
5568 5570 if marks:
5569 5571 active = repo._activebookmark
5570 5572 # i18n: column positioning for "hg summary"
5571 5573 ui.write(_('bookmarks:'), label='log.bookmark')
5572 5574 if active is not None:
5573 5575 if active in marks:
5574 5576 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5575 5577 marks.remove(active)
5576 5578 else:
5577 5579 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5578 5580 for m in marks:
5579 5581 ui.write(' ' + m, label='log.bookmark')
5580 5582 ui.write('\n', label='log.bookmark')
5581 5583
5582 5584 status = repo.status(unknown=True)
5583 5585
5584 5586 c = repo.dirstate.copies()
5585 5587 copied, renamed = [], []
5586 5588 for d, s in c.iteritems():
5587 5589 if s in status.removed:
5588 5590 status.removed.remove(s)
5589 5591 renamed.append(d)
5590 5592 else:
5591 5593 copied.append(d)
5592 5594 if d in status.added:
5593 5595 status.added.remove(d)
5594 5596
5595 5597 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5596 5598
5597 5599 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5598 5600 (ui.label(_('%d added'), 'status.added'), status.added),
5599 5601 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5600 5602 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5601 5603 (ui.label(_('%d copied'), 'status.copied'), copied),
5602 5604 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5603 5605 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5604 5606 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5605 5607 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5606 5608 t = []
5607 5609 for l, s in labels:
5608 5610 if s:
5609 5611 t.append(l % len(s))
5610 5612
5611 5613 t = ', '.join(t)
5612 5614 cleanworkdir = False
5613 5615
5614 5616 if repo.vfs.exists('graftstate'):
5615 5617 t += _(' (graft in progress)')
5616 5618 if repo.vfs.exists('updatestate'):
5617 5619 t += _(' (interrupted update)')
5618 5620 elif len(parents) > 1:
5619 5621 t += _(' (merge)')
5620 5622 elif branch != parents[0].branch():
5621 5623 t += _(' (new branch)')
5622 5624 elif (parents[0].closesbranch() and
5623 5625 pnode in repo.branchheads(branch, closed=True)):
5624 5626 t += _(' (head closed)')
5625 5627 elif not (status.modified or status.added or status.removed or renamed or
5626 5628 copied or subs):
5627 5629 t += _(' (clean)')
5628 5630 cleanworkdir = True
5629 5631 elif pnode not in bheads:
5630 5632 t += _(' (new branch head)')
5631 5633
5632 5634 if parents:
5633 5635 pendingphase = max(p.phase() for p in parents)
5634 5636 else:
5635 5637 pendingphase = phases.public
5636 5638
5637 5639 if pendingphase > phases.newcommitphase(ui):
5638 5640 t += ' (%s)' % phases.phasenames[pendingphase]
5639 5641
5640 5642 if cleanworkdir:
5641 5643 # i18n: column positioning for "hg summary"
5642 5644 ui.status(_('commit: %s\n') % t.strip())
5643 5645 else:
5644 5646 # i18n: column positioning for "hg summary"
5645 5647 ui.write(_('commit: %s\n') % t.strip())
5646 5648
5647 5649 # all ancestors of branch heads - all ancestors of parent = new csets
5648 5650 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5649 5651 bheads))
5650 5652
5651 5653 if new == 0:
5652 5654 # i18n: column positioning for "hg summary"
5653 5655 ui.status(_('update: (current)\n'))
5654 5656 elif pnode not in bheads:
5655 5657 # i18n: column positioning for "hg summary"
5656 5658 ui.write(_('update: %d new changesets (update)\n') % new)
5657 5659 else:
5658 5660 # i18n: column positioning for "hg summary"
5659 5661 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5660 5662 (new, len(bheads)))
5661 5663
5662 5664 t = []
5663 5665 draft = len(repo.revs('draft()'))
5664 5666 if draft:
5665 5667 t.append(_('%d draft') % draft)
5666 5668 secret = len(repo.revs('secret()'))
5667 5669 if secret:
5668 5670 t.append(_('%d secret') % secret)
5669 5671
5670 5672 if draft or secret:
5671 5673 ui.status(_('phases: %s\n') % ', '.join(t))
5672 5674
5673 5675 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5674 5676 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5675 5677 numtrouble = len(repo.revs(trouble + "()"))
5676 5678 # We write all the possibilities to ease translation
5677 5679 troublemsg = {
5678 5680 "orphan": _("orphan: %d changesets"),
5679 5681 "contentdivergent": _("content-divergent: %d changesets"),
5680 5682 "phasedivergent": _("phase-divergent: %d changesets"),
5681 5683 }
5682 5684 if numtrouble > 0:
5683 5685 ui.status(troublemsg[trouble] % numtrouble + "\n")
5684 5686
5685 5687 cmdutil.summaryhooks(ui, repo)
5686 5688
5687 5689 if opts.get('remote'):
5688 5690 needsincoming, needsoutgoing = True, True
5689 5691 else:
5690 5692 needsincoming, needsoutgoing = False, False
5691 5693 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5692 5694 if i:
5693 5695 needsincoming = True
5694 5696 if o:
5695 5697 needsoutgoing = True
5696 5698 if not needsincoming and not needsoutgoing:
5697 5699 return
5698 5700
5699 5701 def getincoming():
5700 5702 source, branches = hg.parseurl(ui.expandpath('default'))
5701 5703 sbranch = branches[0]
5702 5704 try:
5703 5705 other = hg.peer(repo, {}, source)
5704 5706 except error.RepoError:
5705 5707 if opts.get('remote'):
5706 5708 raise
5707 5709 return source, sbranch, None, None, None
5708 5710 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5709 5711 if revs:
5710 5712 revs = [other.lookup(rev) for rev in revs]
5711 5713 ui.debug('comparing with %s\n' % util.hidepassword(source))
5712 5714 repo.ui.pushbuffer()
5713 5715 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5714 5716 repo.ui.popbuffer()
5715 5717 return source, sbranch, other, commoninc, commoninc[1]
5716 5718
5717 5719 if needsincoming:
5718 5720 source, sbranch, sother, commoninc, incoming = getincoming()
5719 5721 else:
5720 5722 source = sbranch = sother = commoninc = incoming = None
5721 5723
5722 5724 def getoutgoing():
5723 5725 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5724 5726 dbranch = branches[0]
5725 5727 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5726 5728 if source != dest:
5727 5729 try:
5728 5730 dother = hg.peer(repo, {}, dest)
5729 5731 except error.RepoError:
5730 5732 if opts.get('remote'):
5731 5733 raise
5732 5734 return dest, dbranch, None, None
5733 5735 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5734 5736 elif sother is None:
5735 5737 # there is no explicit destination peer, but source one is invalid
5736 5738 return dest, dbranch, None, None
5737 5739 else:
5738 5740 dother = sother
5739 5741 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5740 5742 common = None
5741 5743 else:
5742 5744 common = commoninc
5743 5745 if revs:
5744 5746 revs = [repo.lookup(rev) for rev in revs]
5745 5747 repo.ui.pushbuffer()
5746 5748 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5747 5749 commoninc=common)
5748 5750 repo.ui.popbuffer()
5749 5751 return dest, dbranch, dother, outgoing
5750 5752
5751 5753 if needsoutgoing:
5752 5754 dest, dbranch, dother, outgoing = getoutgoing()
5753 5755 else:
5754 5756 dest = dbranch = dother = outgoing = None
5755 5757
5756 5758 if opts.get('remote'):
5757 5759 t = []
5758 5760 if incoming:
5759 5761 t.append(_('1 or more incoming'))
5760 5762 o = outgoing.missing
5761 5763 if o:
5762 5764 t.append(_('%d outgoing') % len(o))
5763 5765 other = dother or sother
5764 5766 if 'bookmarks' in other.listkeys('namespaces'):
5765 5767 counts = bookmarks.summary(repo, other)
5766 5768 if counts[0] > 0:
5767 5769 t.append(_('%d incoming bookmarks') % counts[0])
5768 5770 if counts[1] > 0:
5769 5771 t.append(_('%d outgoing bookmarks') % counts[1])
5770 5772
5771 5773 if t:
5772 5774 # i18n: column positioning for "hg summary"
5773 5775 ui.write(_('remote: %s\n') % (', '.join(t)))
5774 5776 else:
5775 5777 # i18n: column positioning for "hg summary"
5776 5778 ui.status(_('remote: (synced)\n'))
5777 5779
5778 5780 cmdutil.summaryremotehooks(ui, repo, opts,
5779 5781 ((source, sbranch, sother, commoninc),
5780 5782 (dest, dbranch, dother, outgoing)))
5781 5783
5782 5784 @command('tag',
5783 5785 [('f', 'force', None, _('force tag')),
5784 5786 ('l', 'local', None, _('make the tag local')),
5785 5787 ('r', 'rev', '', _('revision to tag'), _('REV')),
5786 5788 ('', 'remove', None, _('remove a tag')),
5787 5789 # -l/--local is already there, commitopts cannot be used
5788 5790 ('e', 'edit', None, _('invoke editor on commit messages')),
5789 5791 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5790 5792 ] + commitopts2,
5791 5793 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
5792 5794 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
5793 5795 def tag(ui, repo, name1, *names, **opts):
5794 5796 """add one or more tags for the current or given revision
5795 5797
5796 5798 Name a particular revision using <name>.
5797 5799
5798 5800 Tags are used to name particular revisions of the repository and are
5799 5801 very useful to compare different revisions, to go back to significant
5800 5802 earlier versions or to mark branch points as releases, etc. Changing
5801 5803 an existing tag is normally disallowed; use -f/--force to override.
5802 5804
5803 5805 If no revision is given, the parent of the working directory is
5804 5806 used.
5805 5807
5806 5808 To facilitate version control, distribution, and merging of tags,
5807 5809 they are stored as a file named ".hgtags" which is managed similarly
5808 5810 to other project files and can be hand-edited if necessary. This
5809 5811 also means that tagging creates a new commit. The file
5810 5812 ".hg/localtags" is used for local tags (not shared among
5811 5813 repositories).
5812 5814
5813 5815 Tag commits are usually made at the head of a branch. If the parent
5814 5816 of the working directory is not a branch head, :hg:`tag` aborts; use
5815 5817 -f/--force to force the tag commit to be based on a non-head
5816 5818 changeset.
5817 5819
5818 5820 See :hg:`help dates` for a list of formats valid for -d/--date.
5819 5821
5820 5822 Since tag names have priority over branch names during revision
5821 5823 lookup, using an existing branch name as a tag name is discouraged.
5822 5824
5823 5825 Returns 0 on success.
5824 5826 """
5825 5827 opts = pycompat.byteskwargs(opts)
5826 5828 with repo.wlock(), repo.lock():
5827 5829 rev_ = "."
5828 5830 names = [t.strip() for t in (name1,) + names]
5829 5831 if len(names) != len(set(names)):
5830 5832 raise error.Abort(_('tag names must be unique'))
5831 5833 for n in names:
5832 5834 scmutil.checknewlabel(repo, n, 'tag')
5833 5835 if not n:
5834 5836 raise error.Abort(_('tag names cannot consist entirely of '
5835 5837 'whitespace'))
5836 5838 if opts.get('rev') and opts.get('remove'):
5837 5839 raise error.Abort(_("--rev and --remove are incompatible"))
5838 5840 if opts.get('rev'):
5839 5841 rev_ = opts['rev']
5840 5842 message = opts.get('message')
5841 5843 if opts.get('remove'):
5842 5844 if opts.get('local'):
5843 5845 expectedtype = 'local'
5844 5846 else:
5845 5847 expectedtype = 'global'
5846 5848
5847 5849 for n in names:
5848 5850 if repo.tagtype(n) == 'global':
5849 5851 alltags = tagsmod.findglobaltags(ui, repo)
5850 5852 if alltags[n][0] == nullid:
5851 5853 raise error.Abort(_("tag '%s' is already removed") % n)
5852 5854 if not repo.tagtype(n):
5853 5855 raise error.Abort(_("tag '%s' does not exist") % n)
5854 5856 if repo.tagtype(n) != expectedtype:
5855 5857 if expectedtype == 'global':
5856 5858 raise error.Abort(_("tag '%s' is not a global tag") % n)
5857 5859 else:
5858 5860 raise error.Abort(_("tag '%s' is not a local tag") % n)
5859 5861 rev_ = 'null'
5860 5862 if not message:
5861 5863 # we don't translate commit messages
5862 5864 message = 'Removed tag %s' % ', '.join(names)
5863 5865 elif not opts.get('force'):
5864 5866 for n in names:
5865 5867 if n in repo.tags():
5866 5868 raise error.Abort(_("tag '%s' already exists "
5867 5869 "(use -f to force)") % n)
5868 5870 if not opts.get('local'):
5869 5871 p1, p2 = repo.dirstate.parents()
5870 5872 if p2 != nullid:
5871 5873 raise error.Abort(_('uncommitted merge'))
5872 5874 bheads = repo.branchheads()
5873 5875 if not opts.get('force') and bheads and p1 not in bheads:
5874 5876 raise error.Abort(_('working directory is not at a branch head '
5875 5877 '(use -f to force)'))
5876 5878 node = scmutil.revsingle(repo, rev_).node()
5877 5879
5878 5880 if not message:
5879 5881 # we don't translate commit messages
5880 5882 message = ('Added tag %s for changeset %s' %
5881 5883 (', '.join(names), short(node)))
5882 5884
5883 5885 date = opts.get('date')
5884 5886 if date:
5885 5887 date = dateutil.parsedate(date)
5886 5888
5887 5889 if opts.get('remove'):
5888 5890 editform = 'tag.remove'
5889 5891 else:
5890 5892 editform = 'tag.add'
5891 5893 editor = cmdutil.getcommiteditor(editform=editform,
5892 5894 **pycompat.strkwargs(opts))
5893 5895
5894 5896 # don't allow tagging the null rev
5895 5897 if (not opts.get('remove') and
5896 5898 scmutil.revsingle(repo, rev_).rev() == nullrev):
5897 5899 raise error.Abort(_("cannot tag null revision"))
5898 5900
5899 5901 tagsmod.tag(repo, names, node, message, opts.get('local'),
5900 5902 opts.get('user'), date, editor=editor)
5901 5903
5902 5904 @command(
5903 5905 'tags', formatteropts, '',
5904 5906 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5905 5907 intents={INTENT_READONLY})
5906 5908 def tags(ui, repo, **opts):
5907 5909 """list repository tags
5908 5910
5909 5911 This lists both regular and local tags. When the -v/--verbose
5910 5912 switch is used, a third column "local" is printed for local tags.
5911 5913 When the -q/--quiet switch is used, only the tag name is printed.
5912 5914
5913 5915 .. container:: verbose
5914 5916
5915 5917 Template:
5916 5918
5917 5919 The following keywords are supported in addition to the common template
5918 5920 keywords and functions such as ``{tag}``. See also
5919 5921 :hg:`help templates`.
5920 5922
5921 5923 :type: String. ``local`` for local tags.
5922 5924
5923 5925 Returns 0 on success.
5924 5926 """
5925 5927
5926 5928 opts = pycompat.byteskwargs(opts)
5927 5929 ui.pager('tags')
5928 5930 fm = ui.formatter('tags', opts)
5929 5931 hexfunc = fm.hexfunc
5930 5932
5931 5933 for t, n in reversed(repo.tagslist()):
5932 5934 hn = hexfunc(n)
5933 5935 label = 'tags.normal'
5934 5936 tagtype = ''
5935 5937 if repo.tagtype(t) == 'local':
5936 5938 label = 'tags.local'
5937 5939 tagtype = 'local'
5938 5940
5939 5941 fm.startitem()
5940 5942 fm.context(repo=repo)
5941 5943 fm.write('tag', '%s', t, label=label)
5942 5944 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5943 5945 fm.condwrite(not ui.quiet, 'rev node', fmt,
5944 5946 repo.changelog.rev(n), hn, label=label)
5945 5947 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5946 5948 tagtype, label=label)
5947 5949 fm.plain('\n')
5948 5950 fm.end()
5949 5951
5950 5952 @command('tip',
5951 5953 [('p', 'patch', None, _('show patch')),
5952 5954 ('g', 'git', None, _('use git extended diff format')),
5953 5955 ] + templateopts,
5954 5956 _('[-p] [-g]'),
5955 5957 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
5956 5958 def tip(ui, repo, **opts):
5957 5959 """show the tip revision (DEPRECATED)
5958 5960
5959 5961 The tip revision (usually just called the tip) is the changeset
5960 5962 most recently added to the repository (and therefore the most
5961 5963 recently changed head).
5962 5964
5963 5965 If you have just made a commit, that commit will be the tip. If
5964 5966 you have just pulled changes from another repository, the tip of
5965 5967 that repository becomes the current tip. The "tip" tag is special
5966 5968 and cannot be renamed or assigned to a different changeset.
5967 5969
5968 5970 This command is deprecated, please use :hg:`heads` instead.
5969 5971
5970 5972 Returns 0 on success.
5971 5973 """
5972 5974 opts = pycompat.byteskwargs(opts)
5973 5975 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5974 5976 displayer.show(repo['tip'])
5975 5977 displayer.close()
5976 5978
5977 5979 @command('unbundle',
5978 5980 [('u', 'update', None,
5979 5981 _('update to new branch head if changesets were unbundled'))],
5980 5982 _('[-u] FILE...'),
5981 5983 helpcategory=command.CATEGORY_IMPORT_EXPORT)
5982 5984 def unbundle(ui, repo, fname1, *fnames, **opts):
5983 5985 """apply one or more bundle files
5984 5986
5985 5987 Apply one or more bundle files generated by :hg:`bundle`.
5986 5988
5987 5989 Returns 0 on success, 1 if an update has unresolved files.
5988 5990 """
5989 5991 fnames = (fname1,) + fnames
5990 5992
5991 5993 with repo.lock():
5992 5994 for fname in fnames:
5993 5995 f = hg.openpath(ui, fname)
5994 5996 gen = exchange.readbundle(ui, f, fname)
5995 5997 if isinstance(gen, streamclone.streamcloneapplier):
5996 5998 raise error.Abort(
5997 5999 _('packed bundles cannot be applied with '
5998 6000 '"hg unbundle"'),
5999 6001 hint=_('use "hg debugapplystreamclonebundle"'))
6000 6002 url = 'bundle:' + fname
6001 6003 try:
6002 6004 txnname = 'unbundle'
6003 6005 if not isinstance(gen, bundle2.unbundle20):
6004 6006 txnname = 'unbundle\n%s' % util.hidepassword(url)
6005 6007 with repo.transaction(txnname) as tr:
6006 6008 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
6007 6009 url=url)
6008 6010 except error.BundleUnknownFeatureError as exc:
6009 6011 raise error.Abort(
6010 6012 _('%s: unknown bundle feature, %s') % (fname, exc),
6011 6013 hint=_("see https://mercurial-scm.org/"
6012 6014 "wiki/BundleFeature for more "
6013 6015 "information"))
6014 6016 modheads = bundle2.combinechangegroupresults(op)
6015 6017
6016 6018 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
6017 6019
6018 6020 @command('update|up|checkout|co',
6019 6021 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6020 6022 ('c', 'check', None, _('require clean working directory')),
6021 6023 ('m', 'merge', None, _('merge uncommitted changes')),
6022 6024 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6023 6025 ('r', 'rev', '', _('revision'), _('REV'))
6024 6026 ] + mergetoolopts,
6025 6027 _('[-C|-c|-m] [-d DATE] [[-r] REV]'),
6026 6028 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6027 6029 helpbasic=True)
6028 6030 def update(ui, repo, node=None, **opts):
6029 6031 """update working directory (or switch revisions)
6030 6032
6031 6033 Update the repository's working directory to the specified
6032 6034 changeset. If no changeset is specified, update to the tip of the
6033 6035 current named branch and move the active bookmark (see :hg:`help
6034 6036 bookmarks`).
6035 6037
6036 6038 Update sets the working directory's parent revision to the specified
6037 6039 changeset (see :hg:`help parents`).
6038 6040
6039 6041 If the changeset is not a descendant or ancestor of the working
6040 6042 directory's parent and there are uncommitted changes, the update is
6041 6043 aborted. With the -c/--check option, the working directory is checked
6042 6044 for uncommitted changes; if none are found, the working directory is
6043 6045 updated to the specified changeset.
6044 6046
6045 6047 .. container:: verbose
6046 6048
6047 6049 The -C/--clean, -c/--check, and -m/--merge options control what
6048 6050 happens if the working directory contains uncommitted changes.
6049 6051 At most of one of them can be specified.
6050 6052
6051 6053 1. If no option is specified, and if
6052 6054 the requested changeset is an ancestor or descendant of
6053 6055 the working directory's parent, the uncommitted changes
6054 6056 are merged into the requested changeset and the merged
6055 6057 result is left uncommitted. If the requested changeset is
6056 6058 not an ancestor or descendant (that is, it is on another
6057 6059 branch), the update is aborted and the uncommitted changes
6058 6060 are preserved.
6059 6061
6060 6062 2. With the -m/--merge option, the update is allowed even if the
6061 6063 requested changeset is not an ancestor or descendant of
6062 6064 the working directory's parent.
6063 6065
6064 6066 3. With the -c/--check option, the update is aborted and the
6065 6067 uncommitted changes are preserved.
6066 6068
6067 6069 4. With the -C/--clean option, uncommitted changes are discarded and
6068 6070 the working directory is updated to the requested changeset.
6069 6071
6070 6072 To cancel an uncommitted merge (and lose your changes), use
6071 6073 :hg:`merge --abort`.
6072 6074
6073 6075 Use null as the changeset to remove the working directory (like
6074 6076 :hg:`clone -U`).
6075 6077
6076 6078 If you want to revert just one file to an older revision, use
6077 6079 :hg:`revert [-r REV] NAME`.
6078 6080
6079 6081 See :hg:`help dates` for a list of formats valid for -d/--date.
6080 6082
6081 6083 Returns 0 on success, 1 if there are unresolved files.
6082 6084 """
6083 6085 rev = opts.get(r'rev')
6084 6086 date = opts.get(r'date')
6085 6087 clean = opts.get(r'clean')
6086 6088 check = opts.get(r'check')
6087 6089 merge = opts.get(r'merge')
6088 6090 if rev and node:
6089 6091 raise error.Abort(_("please specify just one revision"))
6090 6092
6091 6093 if ui.configbool('commands', 'update.requiredest'):
6092 6094 if not node and not rev and not date:
6093 6095 raise error.Abort(_('you must specify a destination'),
6094 6096 hint=_('for example: hg update ".::"'))
6095 6097
6096 6098 if rev is None or rev == '':
6097 6099 rev = node
6098 6100
6099 6101 if date and rev is not None:
6100 6102 raise error.Abort(_("you can't specify a revision and a date"))
6101 6103
6102 6104 if len([x for x in (clean, check, merge) if x]) > 1:
6103 6105 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
6104 6106 "or -m/--merge"))
6105 6107
6106 6108 updatecheck = None
6107 6109 if check:
6108 6110 updatecheck = 'abort'
6109 6111 elif merge:
6110 6112 updatecheck = 'none'
6111 6113
6112 6114 with repo.wlock():
6113 6115 cmdutil.clearunfinished(repo)
6114 6116
6115 6117 if date:
6116 6118 rev = cmdutil.finddate(ui, repo, date)
6117 6119
6118 6120 # if we defined a bookmark, we have to remember the original name
6119 6121 brev = rev
6120 6122 if rev:
6121 6123 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
6122 6124 ctx = scmutil.revsingle(repo, rev, default=None)
6123 6125 rev = ctx.rev()
6124 6126 hidden = ctx.hidden()
6125 6127 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
6126 6128 with ui.configoverride(overrides, 'update'):
6127 6129 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
6128 6130 updatecheck=updatecheck)
6129 6131 if hidden:
6130 6132 ctxstr = ctx.hex()[:12]
6131 6133 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
6132 6134
6133 6135 if ctx.obsolete():
6134 6136 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
6135 6137 ui.warn("(%s)\n" % obsfatemsg)
6136 6138 return ret
6137 6139
6138 6140 @command('verify', [], helpcategory=command.CATEGORY_MAINTENANCE)
6139 6141 def verify(ui, repo):
6140 6142 """verify the integrity of the repository
6141 6143
6142 6144 Verify the integrity of the current repository.
6143 6145
6144 6146 This will perform an extensive check of the repository's
6145 6147 integrity, validating the hashes and checksums of each entry in
6146 6148 the changelog, manifest, and tracked files, as well as the
6147 6149 integrity of their crosslinks and indices.
6148 6150
6149 6151 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
6150 6152 for more information about recovery from corruption of the
6151 6153 repository.
6152 6154
6153 6155 Returns 0 on success, 1 if errors are encountered.
6154 6156 """
6155 6157 return hg.verify(repo)
6156 6158
6157 6159 @command(
6158 6160 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP,
6159 6161 norepo=True, intents={INTENT_READONLY})
6160 6162 def version_(ui, **opts):
6161 6163 """output version and copyright information
6162 6164
6163 6165 .. container:: verbose
6164 6166
6165 6167 Template:
6166 6168
6167 6169 The following keywords are supported. See also :hg:`help templates`.
6168 6170
6169 6171 :extensions: List of extensions.
6170 6172 :ver: String. Version number.
6171 6173
6172 6174 And each entry of ``{extensions}`` provides the following sub-keywords
6173 6175 in addition to ``{ver}``.
6174 6176
6175 6177 :bundled: Boolean. True if included in the release.
6176 6178 :name: String. Extension name.
6177 6179 """
6178 6180 opts = pycompat.byteskwargs(opts)
6179 6181 if ui.verbose:
6180 6182 ui.pager('version')
6181 6183 fm = ui.formatter("version", opts)
6182 6184 fm.startitem()
6183 6185 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
6184 6186 util.version())
6185 6187 license = _(
6186 6188 "(see https://mercurial-scm.org for more information)\n"
6187 6189 "\nCopyright (C) 2005-2019 Matt Mackall and others\n"
6188 6190 "This is free software; see the source for copying conditions. "
6189 6191 "There is NO\nwarranty; "
6190 6192 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6191 6193 )
6192 6194 if not ui.quiet:
6193 6195 fm.plain(license)
6194 6196
6195 6197 if ui.verbose:
6196 6198 fm.plain(_("\nEnabled extensions:\n\n"))
6197 6199 # format names and versions into columns
6198 6200 names = []
6199 6201 vers = []
6200 6202 isinternals = []
6201 6203 for name, module in extensions.extensions():
6202 6204 names.append(name)
6203 6205 vers.append(extensions.moduleversion(module) or None)
6204 6206 isinternals.append(extensions.ismoduleinternal(module))
6205 6207 fn = fm.nested("extensions", tmpl='{name}\n')
6206 6208 if names:
6207 6209 namefmt = " %%-%ds " % max(len(n) for n in names)
6208 6210 places = [_("external"), _("internal")]
6209 6211 for n, v, p in zip(names, vers, isinternals):
6210 6212 fn.startitem()
6211 6213 fn.condwrite(ui.verbose, "name", namefmt, n)
6212 6214 if ui.verbose:
6213 6215 fn.plain("%s " % places[p])
6214 6216 fn.data(bundled=p)
6215 6217 fn.condwrite(ui.verbose and v, "ver", "%s", v)
6216 6218 if ui.verbose:
6217 6219 fn.plain("\n")
6218 6220 fn.end()
6219 6221 fm.end()
6220 6222
6221 6223 def loadcmdtable(ui, name, cmdtable):
6222 6224 """Load command functions from specified cmdtable
6223 6225 """
6224 6226 cmdtable = cmdtable.copy()
6225 6227 for cmd in list(cmdtable):
6226 6228 if not cmd.startswith('^'):
6227 6229 continue
6228 6230 ui.deprecwarn("old-style command registration '%s' in extension '%s'"
6229 6231 % (cmd, name), '4.8')
6230 6232 entry = cmdtable.pop(cmd)
6231 6233 entry[0].helpbasic = True
6232 6234 cmdtable[cmd[1:]] = entry
6233 6235
6234 6236 overrides = [cmd for cmd in cmdtable if cmd in table]
6235 6237 if overrides:
6236 6238 ui.warn(_("extension '%s' overrides commands: %s\n")
6237 6239 % (name, " ".join(overrides)))
6238 6240 table.update(cmdtable)
@@ -1,1838 +1,1838 b''
1 1 # subrepo.py - sub-repository classes and factory
2 2 #
3 3 # Copyright 2009-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 copy
11 11 import errno
12 12 import hashlib
13 13 import os
14 14 import re
15 15 import stat
16 16 import subprocess
17 17 import sys
18 18 import tarfile
19 19 import xml.dom.minidom
20 20
21 21 from .i18n import _
22 22 from . import (
23 23 cmdutil,
24 24 encoding,
25 25 error,
26 26 exchange,
27 27 logcmdutil,
28 28 match as matchmod,
29 29 node,
30 30 pathutil,
31 31 phases,
32 32 pycompat,
33 33 scmutil,
34 34 subrepoutil,
35 35 util,
36 36 vfs as vfsmod,
37 37 )
38 38 from .utils import (
39 39 dateutil,
40 40 procutil,
41 41 stringutil,
42 42 )
43 43
44 44 hg = None
45 45 reporelpath = subrepoutil.reporelpath
46 46 subrelpath = subrepoutil.subrelpath
47 47 _abssource = subrepoutil._abssource
48 48 propertycache = util.propertycache
49 49
50 50 def _expandedabspath(path):
51 51 '''
52 52 get a path or url and if it is a path expand it and return an absolute path
53 53 '''
54 54 expandedpath = util.urllocalpath(util.expandpath(path))
55 55 u = util.url(expandedpath)
56 56 if not u.scheme:
57 57 path = util.normpath(os.path.abspath(u.path))
58 58 return path
59 59
60 60 def _getstorehashcachename(remotepath):
61 61 '''get a unique filename for the store hash cache of a remote repository'''
62 62 return node.hex(hashlib.sha1(_expandedabspath(remotepath)).digest())[0:12]
63 63
64 64 class SubrepoAbort(error.Abort):
65 65 """Exception class used to avoid handling a subrepo error more than once"""
66 66 def __init__(self, *args, **kw):
67 67 self.subrepo = kw.pop(r'subrepo', None)
68 68 self.cause = kw.pop(r'cause', None)
69 69 error.Abort.__init__(self, *args, **kw)
70 70
71 71 def annotatesubrepoerror(func):
72 72 def decoratedmethod(self, *args, **kargs):
73 73 try:
74 74 res = func(self, *args, **kargs)
75 75 except SubrepoAbort as ex:
76 76 # This exception has already been handled
77 77 raise ex
78 78 except error.Abort as ex:
79 79 subrepo = subrelpath(self)
80 80 errormsg = (stringutil.forcebytestr(ex) + ' '
81 81 + _('(in subrepository "%s")') % subrepo)
82 82 # avoid handling this exception by raising a SubrepoAbort exception
83 83 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
84 84 cause=sys.exc_info())
85 85 return res
86 86 return decoratedmethod
87 87
88 88 def _updateprompt(ui, sub, dirty, local, remote):
89 89 if dirty:
90 90 msg = (_(' subrepository sources for %s differ\n'
91 91 'use (l)ocal source (%s) or (r)emote source (%s)?'
92 92 '$$ &Local $$ &Remote')
93 93 % (subrelpath(sub), local, remote))
94 94 else:
95 95 msg = (_(' subrepository sources for %s differ (in checked out '
96 96 'version)\n'
97 97 'use (l)ocal source (%s) or (r)emote source (%s)?'
98 98 '$$ &Local $$ &Remote')
99 99 % (subrelpath(sub), local, remote))
100 100 return ui.promptchoice(msg, 0)
101 101
102 102 def _sanitize(ui, vfs, ignore):
103 103 for dirname, dirs, names in vfs.walk():
104 104 for i, d in enumerate(dirs):
105 105 if d.lower() == ignore:
106 106 del dirs[i]
107 107 break
108 108 if vfs.basename(dirname).lower() != '.hg':
109 109 continue
110 110 for f in names:
111 111 if f.lower() == 'hgrc':
112 112 ui.warn(_("warning: removing potentially hostile 'hgrc' "
113 113 "in '%s'\n") % vfs.join(dirname))
114 114 vfs.unlink(vfs.reljoin(dirname, f))
115 115
116 116 def _auditsubrepopath(repo, path):
117 117 # sanity check for potentially unsafe paths such as '~' and '$FOO'
118 118 if path.startswith('~') or '$' in path or util.expandpath(path) != path:
119 119 raise error.Abort(_('subrepo path contains illegal component: %s')
120 120 % path)
121 121 # auditor doesn't check if the path itself is a symlink
122 122 pathutil.pathauditor(repo.root)(path)
123 123 if repo.wvfs.islink(path):
124 124 raise error.Abort(_("subrepo '%s' traverses symbolic link") % path)
125 125
126 126 SUBREPO_ALLOWED_DEFAULTS = {
127 127 'hg': True,
128 128 'git': False,
129 129 'svn': False,
130 130 }
131 131
132 132 def _checktype(ui, kind):
133 133 # subrepos.allowed is a master kill switch. If disabled, subrepos are
134 134 # disabled period.
135 135 if not ui.configbool('subrepos', 'allowed', True):
136 136 raise error.Abort(_('subrepos not enabled'),
137 137 hint=_("see 'hg help config.subrepos' for details"))
138 138
139 139 default = SUBREPO_ALLOWED_DEFAULTS.get(kind, False)
140 140 if not ui.configbool('subrepos', '%s:allowed' % kind, default):
141 141 raise error.Abort(_('%s subrepos not allowed') % kind,
142 142 hint=_("see 'hg help config.subrepos' for details"))
143 143
144 144 if kind not in types:
145 145 raise error.Abort(_('unknown subrepo type %s') % kind)
146 146
147 147 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
148 148 """return instance of the right subrepo class for subrepo in path"""
149 149 # subrepo inherently violates our import layering rules
150 150 # because it wants to make repo objects from deep inside the stack
151 151 # so we manually delay the circular imports to not break
152 152 # scripts that don't use our demand-loading
153 153 global hg
154 154 from . import hg as h
155 155 hg = h
156 156
157 157 repo = ctx.repo()
158 158 _auditsubrepopath(repo, path)
159 159 state = ctx.substate[path]
160 160 _checktype(repo.ui, state[2])
161 161 if allowwdir:
162 162 state = (state[0], ctx.subrev(path), state[2])
163 163 return types[state[2]](ctx, path, state[:2], allowcreate)
164 164
165 165 def nullsubrepo(ctx, path, pctx):
166 166 """return an empty subrepo in pctx for the extant subrepo in ctx"""
167 167 # subrepo inherently violates our import layering rules
168 168 # because it wants to make repo objects from deep inside the stack
169 169 # so we manually delay the circular imports to not break
170 170 # scripts that don't use our demand-loading
171 171 global hg
172 172 from . import hg as h
173 173 hg = h
174 174
175 175 repo = ctx.repo()
176 176 _auditsubrepopath(repo, path)
177 177 state = ctx.substate[path]
178 178 _checktype(repo.ui, state[2])
179 179 subrev = ''
180 180 if state[2] == 'hg':
181 181 subrev = "0" * 40
182 182 return types[state[2]](pctx, path, (state[0], subrev), True)
183 183
184 184 # subrepo classes need to implement the following abstract class:
185 185
186 186 class abstractsubrepo(object):
187 187
188 188 def __init__(self, ctx, path):
189 189 """Initialize abstractsubrepo part
190 190
191 191 ``ctx`` is the context referring this subrepository in the
192 192 parent repository.
193 193
194 194 ``path`` is the path to this subrepository as seen from
195 195 innermost repository.
196 196 """
197 197 self.ui = ctx.repo().ui
198 198 self._ctx = ctx
199 199 self._path = path
200 200
201 201 def addwebdirpath(self, serverpath, webconf):
202 202 """Add the hgwebdir entries for this subrepo, and any of its subrepos.
203 203
204 204 ``serverpath`` is the path component of the URL for this repo.
205 205
206 206 ``webconf`` is the dictionary of hgwebdir entries.
207 207 """
208 208 pass
209 209
210 210 def storeclean(self, path):
211 211 """
212 212 returns true if the repository has not changed since it was last
213 213 cloned from or pushed to a given repository.
214 214 """
215 215 return False
216 216
217 217 def dirty(self, ignoreupdate=False, missing=False):
218 218 """returns true if the dirstate of the subrepo is dirty or does not
219 219 match current stored state. If ignoreupdate is true, only check
220 220 whether the subrepo has uncommitted changes in its dirstate. If missing
221 221 is true, check for deleted files.
222 222 """
223 223 raise NotImplementedError
224 224
225 225 def dirtyreason(self, ignoreupdate=False, missing=False):
226 226 """return reason string if it is ``dirty()``
227 227
228 228 Returned string should have enough information for the message
229 229 of exception.
230 230
231 231 This returns None, otherwise.
232 232 """
233 233 if self.dirty(ignoreupdate=ignoreupdate, missing=missing):
234 234 return _('uncommitted changes in subrepository "%s"'
235 235 ) % subrelpath(self)
236 236
237 237 def bailifchanged(self, ignoreupdate=False, hint=None):
238 238 """raise Abort if subrepository is ``dirty()``
239 239 """
240 240 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate,
241 241 missing=True)
242 242 if dirtyreason:
243 243 raise error.Abort(dirtyreason, hint=hint)
244 244
245 245 def basestate(self):
246 246 """current working directory base state, disregarding .hgsubstate
247 247 state and working directory modifications"""
248 248 raise NotImplementedError
249 249
250 250 def checknested(self, path):
251 251 """check if path is a subrepository within this repository"""
252 252 return False
253 253
254 254 def commit(self, text, user, date):
255 255 """commit the current changes to the subrepo with the given
256 256 log message. Use given user and date if possible. Return the
257 257 new state of the subrepo.
258 258 """
259 259 raise NotImplementedError
260 260
261 261 def phase(self, state):
262 262 """returns phase of specified state in the subrepository.
263 263 """
264 264 return phases.public
265 265
266 266 def remove(self):
267 267 """remove the subrepo
268 268
269 269 (should verify the dirstate is not dirty first)
270 270 """
271 271 raise NotImplementedError
272 272
273 273 def get(self, state, overwrite=False):
274 274 """run whatever commands are needed to put the subrepo into
275 275 this state
276 276 """
277 277 raise NotImplementedError
278 278
279 279 def merge(self, state):
280 280 """merge currently-saved state with the new state."""
281 281 raise NotImplementedError
282 282
283 283 def push(self, opts):
284 284 """perform whatever action is analogous to 'hg push'
285 285
286 286 This may be a no-op on some systems.
287 287 """
288 288 raise NotImplementedError
289 289
290 290 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
291 291 return []
292 292
293 293 def addremove(self, matcher, prefix, uipathfn, opts):
294 294 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
295 295 return 1
296 296
297 297 def cat(self, match, fm, fntemplate, prefix, **opts):
298 298 return 1
299 299
300 300 def status(self, rev2, **opts):
301 301 return scmutil.status([], [], [], [], [], [], [])
302 302
303 303 def diff(self, ui, diffopts, node2, match, prefix, **opts):
304 304 pass
305 305
306 306 def outgoing(self, ui, dest, opts):
307 307 return 1
308 308
309 309 def incoming(self, ui, source, opts):
310 310 return 1
311 311
312 312 def files(self):
313 313 """return filename iterator"""
314 314 raise NotImplementedError
315 315
316 316 def filedata(self, name, decode):
317 317 """return file data, optionally passed through repo decoders"""
318 318 raise NotImplementedError
319 319
320 320 def fileflags(self, name):
321 321 """return file flags"""
322 322 return ''
323 323
324 324 def matchfileset(self, expr, badfn=None):
325 325 """Resolve the fileset expression for this repo"""
326 326 return matchmod.never(badfn=badfn)
327 327
328 def printfiles(self, ui, m, fm, fmt, subrepos):
328 def printfiles(self, ui, m, uipathfn, fm, fmt, subrepos):
329 329 """handle the files command for this subrepo"""
330 330 return 1
331 331
332 332 def archive(self, archiver, prefix, match=None, decode=True):
333 333 if match is not None:
334 334 files = [f for f in self.files() if match(f)]
335 335 else:
336 336 files = self.files()
337 337 total = len(files)
338 338 relpath = subrelpath(self)
339 339 progress = self.ui.makeprogress(_('archiving (%s)') % relpath,
340 340 unit=_('files'), total=total)
341 341 progress.update(0)
342 342 for name in files:
343 343 flags = self.fileflags(name)
344 344 mode = 'x' in flags and 0o755 or 0o644
345 345 symlink = 'l' in flags
346 346 archiver.addfile(prefix + name, mode, symlink,
347 347 self.filedata(name, decode))
348 348 progress.increment()
349 349 progress.complete()
350 350 return total
351 351
352 352 def walk(self, match):
353 353 '''
354 354 walk recursively through the directory tree, finding all files
355 355 matched by the match function
356 356 '''
357 357
358 358 def forget(self, match, prefix, uipathfn, dryrun, interactive):
359 359 return ([], [])
360 360
361 361 def removefiles(self, matcher, prefix, uipathfn, after, force, subrepos,
362 362 dryrun, warnings):
363 363 """remove the matched files from the subrepository and the filesystem,
364 364 possibly by force and/or after the file has been removed from the
365 365 filesystem. Return 0 on success, 1 on any warning.
366 366 """
367 367 warnings.append(_("warning: removefiles not implemented (%s)")
368 368 % self._path)
369 369 return 1
370 370
371 371 def revert(self, substate, *pats, **opts):
372 372 self.ui.warn(_('%s: reverting %s subrepos is unsupported\n') \
373 373 % (substate[0], substate[2]))
374 374 return []
375 375
376 376 def shortid(self, revid):
377 377 return revid
378 378
379 379 def unshare(self):
380 380 '''
381 381 convert this repository from shared to normal storage.
382 382 '''
383 383
384 384 def verify(self):
385 385 '''verify the integrity of the repository. Return 0 on success or
386 386 warning, 1 on any error.
387 387 '''
388 388 return 0
389 389
390 390 @propertycache
391 391 def wvfs(self):
392 392 """return vfs to access the working directory of this subrepository
393 393 """
394 394 return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
395 395
396 396 @propertycache
397 397 def _relpath(self):
398 398 """return path to this subrepository as seen from outermost repository
399 399 """
400 400 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
401 401
402 402 class hgsubrepo(abstractsubrepo):
403 403 def __init__(self, ctx, path, state, allowcreate):
404 404 super(hgsubrepo, self).__init__(ctx, path)
405 405 self._state = state
406 406 r = ctx.repo()
407 407 root = r.wjoin(util.localpath(path))
408 408 create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
409 409 # repository constructor does expand variables in path, which is
410 410 # unsafe since subrepo path might come from untrusted source.
411 411 if os.path.realpath(util.expandpath(root)) != root:
412 412 raise error.Abort(_('subrepo path contains illegal component: %s')
413 413 % path)
414 414 self._repo = hg.repository(r.baseui, root, create=create)
415 415 if self._repo.root != root:
416 416 raise error.ProgrammingError('failed to reject unsafe subrepo '
417 417 'path: %s (expanded to %s)'
418 418 % (root, self._repo.root))
419 419
420 420 # Propagate the parent's --hidden option
421 421 if r is r.unfiltered():
422 422 self._repo = self._repo.unfiltered()
423 423
424 424 self.ui = self._repo.ui
425 425 for s, k in [('ui', 'commitsubrepos')]:
426 426 v = r.ui.config(s, k)
427 427 if v:
428 428 self.ui.setconfig(s, k, v, 'subrepo')
429 429 # internal config: ui._usedassubrepo
430 430 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
431 431 self._initrepo(r, state[0], create)
432 432
433 433 @annotatesubrepoerror
434 434 def addwebdirpath(self, serverpath, webconf):
435 435 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
436 436
437 437 def storeclean(self, path):
438 438 with self._repo.lock():
439 439 return self._storeclean(path)
440 440
441 441 def _storeclean(self, path):
442 442 clean = True
443 443 itercache = self._calcstorehash(path)
444 444 for filehash in self._readstorehashcache(path):
445 445 if filehash != next(itercache, None):
446 446 clean = False
447 447 break
448 448 if clean:
449 449 # if not empty:
450 450 # the cached and current pull states have a different size
451 451 clean = next(itercache, None) is None
452 452 return clean
453 453
454 454 def _calcstorehash(self, remotepath):
455 455 '''calculate a unique "store hash"
456 456
457 457 This method is used to to detect when there are changes that may
458 458 require a push to a given remote path.'''
459 459 # sort the files that will be hashed in increasing (likely) file size
460 460 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
461 461 yield '# %s\n' % _expandedabspath(remotepath)
462 462 vfs = self._repo.vfs
463 463 for relname in filelist:
464 464 filehash = node.hex(hashlib.sha1(vfs.tryread(relname)).digest())
465 465 yield '%s = %s\n' % (relname, filehash)
466 466
467 467 @propertycache
468 468 def _cachestorehashvfs(self):
469 469 return vfsmod.vfs(self._repo.vfs.join('cache/storehash'))
470 470
471 471 def _readstorehashcache(self, remotepath):
472 472 '''read the store hash cache for a given remote repository'''
473 473 cachefile = _getstorehashcachename(remotepath)
474 474 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
475 475
476 476 def _cachestorehash(self, remotepath):
477 477 '''cache the current store hash
478 478
479 479 Each remote repo requires its own store hash cache, because a subrepo
480 480 store may be "clean" versus a given remote repo, but not versus another
481 481 '''
482 482 cachefile = _getstorehashcachename(remotepath)
483 483 with self._repo.lock():
484 484 storehash = list(self._calcstorehash(remotepath))
485 485 vfs = self._cachestorehashvfs
486 486 vfs.writelines(cachefile, storehash, mode='wb', notindexed=True)
487 487
488 488 def _getctx(self):
489 489 '''fetch the context for this subrepo revision, possibly a workingctx
490 490 '''
491 491 if self._ctx.rev() is None:
492 492 return self._repo[None] # workingctx if parent is workingctx
493 493 else:
494 494 rev = self._state[1]
495 495 return self._repo[rev]
496 496
497 497 @annotatesubrepoerror
498 498 def _initrepo(self, parentrepo, source, create):
499 499 self._repo._subparent = parentrepo
500 500 self._repo._subsource = source
501 501
502 502 if create:
503 503 lines = ['[paths]\n']
504 504
505 505 def addpathconfig(key, value):
506 506 if value:
507 507 lines.append('%s = %s\n' % (key, value))
508 508 self.ui.setconfig('paths', key, value, 'subrepo')
509 509
510 510 defpath = _abssource(self._repo, abort=False)
511 511 defpushpath = _abssource(self._repo, True, abort=False)
512 512 addpathconfig('default', defpath)
513 513 if defpath != defpushpath:
514 514 addpathconfig('default-push', defpushpath)
515 515
516 516 self._repo.vfs.write('hgrc', util.tonativeeol(''.join(lines)))
517 517
518 518 @annotatesubrepoerror
519 519 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
520 520 return cmdutil.add(ui, self._repo, match, prefix, uipathfn,
521 521 explicitonly, **opts)
522 522
523 523 @annotatesubrepoerror
524 524 def addremove(self, m, prefix, uipathfn, opts):
525 525 # In the same way as sub directories are processed, once in a subrepo,
526 526 # always entry any of its subrepos. Don't corrupt the options that will
527 527 # be used to process sibling subrepos however.
528 528 opts = copy.copy(opts)
529 529 opts['subrepos'] = True
530 530 return scmutil.addremove(self._repo, m, prefix, uipathfn, opts)
531 531
532 532 @annotatesubrepoerror
533 533 def cat(self, match, fm, fntemplate, prefix, **opts):
534 534 rev = self._state[1]
535 535 ctx = self._repo[rev]
536 536 return cmdutil.cat(self.ui, self._repo, ctx, match, fm, fntemplate,
537 537 prefix, **opts)
538 538
539 539 @annotatesubrepoerror
540 540 def status(self, rev2, **opts):
541 541 try:
542 542 rev1 = self._state[1]
543 543 ctx1 = self._repo[rev1]
544 544 ctx2 = self._repo[rev2]
545 545 return self._repo.status(ctx1, ctx2, **opts)
546 546 except error.RepoLookupError as inst:
547 547 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
548 548 % (inst, subrelpath(self)))
549 549 return scmutil.status([], [], [], [], [], [], [])
550 550
551 551 @annotatesubrepoerror
552 552 def diff(self, ui, diffopts, node2, match, prefix, **opts):
553 553 try:
554 554 node1 = node.bin(self._state[1])
555 555 # We currently expect node2 to come from substate and be
556 556 # in hex format
557 557 if node2 is not None:
558 558 node2 = node.bin(node2)
559 559 logcmdutil.diffordiffstat(ui, self._repo, diffopts, node1, node2,
560 560 match, prefix=prefix, listsubrepos=True,
561 561 **opts)
562 562 except error.RepoLookupError as inst:
563 563 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
564 564 % (inst, subrelpath(self)))
565 565
566 566 @annotatesubrepoerror
567 567 def archive(self, archiver, prefix, match=None, decode=True):
568 568 self._get(self._state + ('hg',))
569 569 files = self.files()
570 570 if match:
571 571 files = [f for f in files if match(f)]
572 572 rev = self._state[1]
573 573 ctx = self._repo[rev]
574 574 scmutil.prefetchfiles(self._repo, [ctx.rev()],
575 575 scmutil.matchfiles(self._repo, files))
576 576 total = abstractsubrepo.archive(self, archiver, prefix, match)
577 577 for subpath in ctx.substate:
578 578 s = subrepo(ctx, subpath, True)
579 579 submatch = matchmod.subdirmatcher(subpath, match)
580 580 subprefix = prefix + subpath + '/'
581 581 total += s.archive(archiver, subprefix, submatch,
582 582 decode)
583 583 return total
584 584
585 585 @annotatesubrepoerror
586 586 def dirty(self, ignoreupdate=False, missing=False):
587 587 r = self._state[1]
588 588 if r == '' and not ignoreupdate: # no state recorded
589 589 return True
590 590 w = self._repo[None]
591 591 if r != w.p1().hex() and not ignoreupdate:
592 592 # different version checked out
593 593 return True
594 594 return w.dirty(missing=missing) # working directory changed
595 595
596 596 def basestate(self):
597 597 return self._repo['.'].hex()
598 598
599 599 def checknested(self, path):
600 600 return self._repo._checknested(self._repo.wjoin(path))
601 601
602 602 @annotatesubrepoerror
603 603 def commit(self, text, user, date):
604 604 # don't bother committing in the subrepo if it's only been
605 605 # updated
606 606 if not self.dirty(True):
607 607 return self._repo['.'].hex()
608 608 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
609 609 n = self._repo.commit(text, user, date)
610 610 if not n:
611 611 return self._repo['.'].hex() # different version checked out
612 612 return node.hex(n)
613 613
614 614 @annotatesubrepoerror
615 615 def phase(self, state):
616 616 return self._repo[state or '.'].phase()
617 617
618 618 @annotatesubrepoerror
619 619 def remove(self):
620 620 # we can't fully delete the repository as it may contain
621 621 # local-only history
622 622 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
623 623 hg.clean(self._repo, node.nullid, False)
624 624
625 625 def _get(self, state):
626 626 source, revision, kind = state
627 627 parentrepo = self._repo._subparent
628 628
629 629 if revision in self._repo.unfiltered():
630 630 # Allow shared subrepos tracked at null to setup the sharedpath
631 631 if len(self._repo) != 0 or not parentrepo.shared():
632 632 return True
633 633 self._repo._subsource = source
634 634 srcurl = _abssource(self._repo)
635 635
636 636 # Defer creating the peer until after the status message is logged, in
637 637 # case there are network problems.
638 638 getpeer = lambda: hg.peer(self._repo, {}, srcurl)
639 639
640 640 if len(self._repo) == 0:
641 641 # use self._repo.vfs instead of self.wvfs to remove .hg only
642 642 self._repo.vfs.rmtree()
643 643
644 644 # A remote subrepo could be shared if there is a local copy
645 645 # relative to the parent's share source. But clone pooling doesn't
646 646 # assemble the repos in a tree, so that can't be consistently done.
647 647 # A simpler option is for the user to configure clone pooling, and
648 648 # work with that.
649 649 if parentrepo.shared() and hg.islocal(srcurl):
650 650 self.ui.status(_('sharing subrepo %s from %s\n')
651 651 % (subrelpath(self), srcurl))
652 652 shared = hg.share(self._repo._subparent.baseui,
653 653 getpeer(), self._repo.root,
654 654 update=False, bookmarks=False)
655 655 self._repo = shared.local()
656 656 else:
657 657 # TODO: find a common place for this and this code in the
658 658 # share.py wrap of the clone command.
659 659 if parentrepo.shared():
660 660 pool = self.ui.config('share', 'pool')
661 661 if pool:
662 662 pool = util.expandpath(pool)
663 663
664 664 shareopts = {
665 665 'pool': pool,
666 666 'mode': self.ui.config('share', 'poolnaming'),
667 667 }
668 668 else:
669 669 shareopts = {}
670 670
671 671 self.ui.status(_('cloning subrepo %s from %s\n')
672 672 % (subrelpath(self), util.hidepassword(srcurl)))
673 673 other, cloned = hg.clone(self._repo._subparent.baseui, {},
674 674 getpeer(), self._repo.root,
675 675 update=False, shareopts=shareopts)
676 676 self._repo = cloned.local()
677 677 self._initrepo(parentrepo, source, create=True)
678 678 self._cachestorehash(srcurl)
679 679 else:
680 680 self.ui.status(_('pulling subrepo %s from %s\n')
681 681 % (subrelpath(self), util.hidepassword(srcurl)))
682 682 cleansub = self.storeclean(srcurl)
683 683 exchange.pull(self._repo, getpeer())
684 684 if cleansub:
685 685 # keep the repo clean after pull
686 686 self._cachestorehash(srcurl)
687 687 return False
688 688
689 689 @annotatesubrepoerror
690 690 def get(self, state, overwrite=False):
691 691 inrepo = self._get(state)
692 692 source, revision, kind = state
693 693 repo = self._repo
694 694 repo.ui.debug("getting subrepo %s\n" % self._path)
695 695 if inrepo:
696 696 urepo = repo.unfiltered()
697 697 ctx = urepo[revision]
698 698 if ctx.hidden():
699 699 urepo.ui.warn(
700 700 _('revision %s in subrepository "%s" is hidden\n') \
701 701 % (revision[0:12], self._path))
702 702 repo = urepo
703 703 hg.updaterepo(repo, revision, overwrite)
704 704
705 705 @annotatesubrepoerror
706 706 def merge(self, state):
707 707 self._get(state)
708 708 cur = self._repo['.']
709 709 dst = self._repo[state[1]]
710 710 anc = dst.ancestor(cur)
711 711
712 712 def mergefunc():
713 713 if anc == cur and dst.branch() == cur.branch():
714 714 self.ui.debug('updating subrepository "%s"\n'
715 715 % subrelpath(self))
716 716 hg.update(self._repo, state[1])
717 717 elif anc == dst:
718 718 self.ui.debug('skipping subrepository "%s"\n'
719 719 % subrelpath(self))
720 720 else:
721 721 self.ui.debug('merging subrepository "%s"\n' % subrelpath(self))
722 722 hg.merge(self._repo, state[1], remind=False)
723 723
724 724 wctx = self._repo[None]
725 725 if self.dirty():
726 726 if anc != dst:
727 727 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
728 728 mergefunc()
729 729 else:
730 730 mergefunc()
731 731 else:
732 732 mergefunc()
733 733
734 734 @annotatesubrepoerror
735 735 def push(self, opts):
736 736 force = opts.get('force')
737 737 newbranch = opts.get('new_branch')
738 738 ssh = opts.get('ssh')
739 739
740 740 # push subrepos depth-first for coherent ordering
741 741 c = self._repo['.']
742 742 subs = c.substate # only repos that are committed
743 743 for s in sorted(subs):
744 744 if c.sub(s).push(opts) == 0:
745 745 return False
746 746
747 747 dsturl = _abssource(self._repo, True)
748 748 if not force:
749 749 if self.storeclean(dsturl):
750 750 self.ui.status(
751 751 _('no changes made to subrepo %s since last push to %s\n')
752 752 % (subrelpath(self), util.hidepassword(dsturl)))
753 753 return None
754 754 self.ui.status(_('pushing subrepo %s to %s\n') %
755 755 (subrelpath(self), util.hidepassword(dsturl)))
756 756 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
757 757 res = exchange.push(self._repo, other, force, newbranch=newbranch)
758 758
759 759 # the repo is now clean
760 760 self._cachestorehash(dsturl)
761 761 return res.cgresult
762 762
763 763 @annotatesubrepoerror
764 764 def outgoing(self, ui, dest, opts):
765 765 if 'rev' in opts or 'branch' in opts:
766 766 opts = copy.copy(opts)
767 767 opts.pop('rev', None)
768 768 opts.pop('branch', None)
769 769 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
770 770
771 771 @annotatesubrepoerror
772 772 def incoming(self, ui, source, opts):
773 773 if 'rev' in opts or 'branch' in opts:
774 774 opts = copy.copy(opts)
775 775 opts.pop('rev', None)
776 776 opts.pop('branch', None)
777 777 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
778 778
779 779 @annotatesubrepoerror
780 780 def files(self):
781 781 rev = self._state[1]
782 782 ctx = self._repo[rev]
783 783 return ctx.manifest().keys()
784 784
785 785 def filedata(self, name, decode):
786 786 rev = self._state[1]
787 787 data = self._repo[rev][name].data()
788 788 if decode:
789 789 data = self._repo.wwritedata(name, data)
790 790 return data
791 791
792 792 def fileflags(self, name):
793 793 rev = self._state[1]
794 794 ctx = self._repo[rev]
795 795 return ctx.flags(name)
796 796
797 797 @annotatesubrepoerror
798 def printfiles(self, ui, m, fm, fmt, subrepos):
798 def printfiles(self, ui, m, uipathfn, fm, fmt, subrepos):
799 799 # If the parent context is a workingctx, use the workingctx here for
800 800 # consistency.
801 801 if self._ctx.rev() is None:
802 802 ctx = self._repo[None]
803 803 else:
804 804 rev = self._state[1]
805 805 ctx = self._repo[rev]
806 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
806 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt, subrepos)
807 807
808 808 @annotatesubrepoerror
809 809 def matchfileset(self, expr, badfn=None):
810 810 if self._ctx.rev() is None:
811 811 ctx = self._repo[None]
812 812 else:
813 813 rev = self._state[1]
814 814 ctx = self._repo[rev]
815 815
816 816 matchers = [ctx.matchfileset(expr, badfn=badfn)]
817 817
818 818 for subpath in ctx.substate:
819 819 sub = ctx.sub(subpath)
820 820
821 821 try:
822 822 sm = sub.matchfileset(expr, badfn=badfn)
823 823 pm = matchmod.prefixdirmatcher(subpath, sm, badfn=badfn)
824 824 matchers.append(pm)
825 825 except error.LookupError:
826 826 self.ui.status(_("skipping missing subrepository: %s\n")
827 827 % self.wvfs.reljoin(reporelpath(self), subpath))
828 828 if len(matchers) == 1:
829 829 return matchers[0]
830 830 return matchmod.unionmatcher(matchers)
831 831
832 832 def walk(self, match):
833 833 ctx = self._repo[None]
834 834 return ctx.walk(match)
835 835
836 836 @annotatesubrepoerror
837 837 def forget(self, match, prefix, uipathfn, dryrun, interactive):
838 838 return cmdutil.forget(self.ui, self._repo, match, prefix, uipathfn,
839 839 True, dryrun=dryrun, interactive=interactive)
840 840
841 841 @annotatesubrepoerror
842 842 def removefiles(self, matcher, prefix, uipathfn, after, force, subrepos,
843 843 dryrun, warnings):
844 844 return cmdutil.remove(self.ui, self._repo, matcher, prefix, uipathfn,
845 845 after, force, subrepos, dryrun)
846 846
847 847 @annotatesubrepoerror
848 848 def revert(self, substate, *pats, **opts):
849 849 # reverting a subrepo is a 2 step process:
850 850 # 1. if the no_backup is not set, revert all modified
851 851 # files inside the subrepo
852 852 # 2. update the subrepo to the revision specified in
853 853 # the corresponding substate dictionary
854 854 self.ui.status(_('reverting subrepo %s\n') % substate[0])
855 855 if not opts.get(r'no_backup'):
856 856 # Revert all files on the subrepo, creating backups
857 857 # Note that this will not recursively revert subrepos
858 858 # We could do it if there was a set:subrepos() predicate
859 859 opts = opts.copy()
860 860 opts[r'date'] = None
861 861 opts[r'rev'] = substate[1]
862 862
863 863 self.filerevert(*pats, **opts)
864 864
865 865 # Update the repo to the revision specified in the given substate
866 866 if not opts.get(r'dry_run'):
867 867 self.get(substate, overwrite=True)
868 868
869 869 def filerevert(self, *pats, **opts):
870 870 ctx = self._repo[opts[r'rev']]
871 871 parents = self._repo.dirstate.parents()
872 872 if opts.get(r'all'):
873 873 pats = ['set:modified()']
874 874 else:
875 875 pats = []
876 876 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
877 877
878 878 def shortid(self, revid):
879 879 return revid[:12]
880 880
881 881 @annotatesubrepoerror
882 882 def unshare(self):
883 883 # subrepo inherently violates our import layering rules
884 884 # because it wants to make repo objects from deep inside the stack
885 885 # so we manually delay the circular imports to not break
886 886 # scripts that don't use our demand-loading
887 887 global hg
888 888 from . import hg as h
889 889 hg = h
890 890
891 891 # Nothing prevents a user from sharing in a repo, and then making that a
892 892 # subrepo. Alternately, the previous unshare attempt may have failed
893 893 # part way through. So recurse whether or not this layer is shared.
894 894 if self._repo.shared():
895 895 self.ui.status(_("unsharing subrepo '%s'\n") % self._relpath)
896 896
897 897 hg.unshare(self.ui, self._repo)
898 898
899 899 def verify(self):
900 900 try:
901 901 rev = self._state[1]
902 902 ctx = self._repo.unfiltered()[rev]
903 903 if ctx.hidden():
904 904 # Since hidden revisions aren't pushed/pulled, it seems worth an
905 905 # explicit warning.
906 906 ui = self._repo.ui
907 907 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
908 908 (self._relpath, node.short(self._ctx.node())))
909 909 return 0
910 910 except error.RepoLookupError:
911 911 # A missing subrepo revision may be a case of needing to pull it, so
912 912 # don't treat this as an error.
913 913 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
914 914 (self._relpath, node.short(self._ctx.node())))
915 915 return 0
916 916
917 917 @propertycache
918 918 def wvfs(self):
919 919 """return own wvfs for efficiency and consistency
920 920 """
921 921 return self._repo.wvfs
922 922
923 923 @propertycache
924 924 def _relpath(self):
925 925 """return path to this subrepository as seen from outermost repository
926 926 """
927 927 # Keep consistent dir separators by avoiding vfs.join(self._path)
928 928 return reporelpath(self._repo)
929 929
930 930 class svnsubrepo(abstractsubrepo):
931 931 def __init__(self, ctx, path, state, allowcreate):
932 932 super(svnsubrepo, self).__init__(ctx, path)
933 933 self._state = state
934 934 self._exe = procutil.findexe('svn')
935 935 if not self._exe:
936 936 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
937 937 % self._path)
938 938
939 939 def _svncommand(self, commands, filename='', failok=False):
940 940 cmd = [self._exe]
941 941 extrakw = {}
942 942 if not self.ui.interactive():
943 943 # Making stdin be a pipe should prevent svn from behaving
944 944 # interactively even if we can't pass --non-interactive.
945 945 extrakw[r'stdin'] = subprocess.PIPE
946 946 # Starting in svn 1.5 --non-interactive is a global flag
947 947 # instead of being per-command, but we need to support 1.4 so
948 948 # we have to be intelligent about what commands take
949 949 # --non-interactive.
950 950 if commands[0] in ('update', 'checkout', 'commit'):
951 951 cmd.append('--non-interactive')
952 952 cmd.extend(commands)
953 953 if filename is not None:
954 954 path = self.wvfs.reljoin(self._ctx.repo().origroot,
955 955 self._path, filename)
956 956 cmd.append(path)
957 957 env = dict(encoding.environ)
958 958 # Avoid localized output, preserve current locale for everything else.
959 959 lc_all = env.get('LC_ALL')
960 960 if lc_all:
961 961 env['LANG'] = lc_all
962 962 del env['LC_ALL']
963 963 env['LC_MESSAGES'] = 'C'
964 964 p = subprocess.Popen(pycompat.rapply(procutil.tonativestr, cmd),
965 965 bufsize=-1, close_fds=procutil.closefds,
966 966 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
967 967 env=procutil.tonativeenv(env), **extrakw)
968 968 stdout, stderr = map(util.fromnativeeol, p.communicate())
969 969 stderr = stderr.strip()
970 970 if not failok:
971 971 if p.returncode:
972 972 raise error.Abort(stderr or 'exited with code %d'
973 973 % p.returncode)
974 974 if stderr:
975 975 self.ui.warn(stderr + '\n')
976 976 return stdout, stderr
977 977
978 978 @propertycache
979 979 def _svnversion(self):
980 980 output, err = self._svncommand(['--version', '--quiet'], filename=None)
981 981 m = re.search(br'^(\d+)\.(\d+)', output)
982 982 if not m:
983 983 raise error.Abort(_('cannot retrieve svn tool version'))
984 984 return (int(m.group(1)), int(m.group(2)))
985 985
986 986 def _svnmissing(self):
987 987 return not self.wvfs.exists('.svn')
988 988
989 989 def _wcrevs(self):
990 990 # Get the working directory revision as well as the last
991 991 # commit revision so we can compare the subrepo state with
992 992 # both. We used to store the working directory one.
993 993 output, err = self._svncommand(['info', '--xml'])
994 994 doc = xml.dom.minidom.parseString(output)
995 995 entries = doc.getElementsByTagName(r'entry')
996 996 lastrev, rev = '0', '0'
997 997 if entries:
998 998 rev = pycompat.bytestr(entries[0].getAttribute(r'revision')) or '0'
999 999 commits = entries[0].getElementsByTagName(r'commit')
1000 1000 if commits:
1001 1001 lastrev = pycompat.bytestr(
1002 1002 commits[0].getAttribute(r'revision')) or '0'
1003 1003 return (lastrev, rev)
1004 1004
1005 1005 def _wcrev(self):
1006 1006 return self._wcrevs()[0]
1007 1007
1008 1008 def _wcchanged(self):
1009 1009 """Return (changes, extchanges, missing) where changes is True
1010 1010 if the working directory was changed, extchanges is
1011 1011 True if any of these changes concern an external entry and missing
1012 1012 is True if any change is a missing entry.
1013 1013 """
1014 1014 output, err = self._svncommand(['status', '--xml'])
1015 1015 externals, changes, missing = [], [], []
1016 1016 doc = xml.dom.minidom.parseString(output)
1017 1017 for e in doc.getElementsByTagName(r'entry'):
1018 1018 s = e.getElementsByTagName(r'wc-status')
1019 1019 if not s:
1020 1020 continue
1021 1021 item = s[0].getAttribute(r'item')
1022 1022 props = s[0].getAttribute(r'props')
1023 1023 path = e.getAttribute(r'path').encode('utf8')
1024 1024 if item == r'external':
1025 1025 externals.append(path)
1026 1026 elif item == r'missing':
1027 1027 missing.append(path)
1028 1028 if (item not in (r'', r'normal', r'unversioned', r'external')
1029 1029 or props not in (r'', r'none', r'normal')):
1030 1030 changes.append(path)
1031 1031 for path in changes:
1032 1032 for ext in externals:
1033 1033 if path == ext or path.startswith(ext + pycompat.ossep):
1034 1034 return True, True, bool(missing)
1035 1035 return bool(changes), False, bool(missing)
1036 1036
1037 1037 @annotatesubrepoerror
1038 1038 def dirty(self, ignoreupdate=False, missing=False):
1039 1039 if self._svnmissing():
1040 1040 return self._state[1] != ''
1041 1041 wcchanged = self._wcchanged()
1042 1042 changed = wcchanged[0] or (missing and wcchanged[2])
1043 1043 if not changed:
1044 1044 if self._state[1] in self._wcrevs() or ignoreupdate:
1045 1045 return False
1046 1046 return True
1047 1047
1048 1048 def basestate(self):
1049 1049 lastrev, rev = self._wcrevs()
1050 1050 if lastrev != rev:
1051 1051 # Last committed rev is not the same than rev. We would
1052 1052 # like to take lastrev but we do not know if the subrepo
1053 1053 # URL exists at lastrev. Test it and fallback to rev it
1054 1054 # is not there.
1055 1055 try:
1056 1056 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1057 1057 return lastrev
1058 1058 except error.Abort:
1059 1059 pass
1060 1060 return rev
1061 1061
1062 1062 @annotatesubrepoerror
1063 1063 def commit(self, text, user, date):
1064 1064 # user and date are out of our hands since svn is centralized
1065 1065 changed, extchanged, missing = self._wcchanged()
1066 1066 if not changed:
1067 1067 return self.basestate()
1068 1068 if extchanged:
1069 1069 # Do not try to commit externals
1070 1070 raise error.Abort(_('cannot commit svn externals'))
1071 1071 if missing:
1072 1072 # svn can commit with missing entries but aborting like hg
1073 1073 # seems a better approach.
1074 1074 raise error.Abort(_('cannot commit missing svn entries'))
1075 1075 commitinfo, err = self._svncommand(['commit', '-m', text])
1076 1076 self.ui.status(commitinfo)
1077 1077 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1078 1078 if not newrev:
1079 1079 if not commitinfo.strip():
1080 1080 # Sometimes, our definition of "changed" differs from
1081 1081 # svn one. For instance, svn ignores missing files
1082 1082 # when committing. If there are only missing files, no
1083 1083 # commit is made, no output and no error code.
1084 1084 raise error.Abort(_('failed to commit svn changes'))
1085 1085 raise error.Abort(commitinfo.splitlines()[-1])
1086 1086 newrev = newrev.groups()[0]
1087 1087 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1088 1088 return newrev
1089 1089
1090 1090 @annotatesubrepoerror
1091 1091 def remove(self):
1092 1092 if self.dirty():
1093 1093 self.ui.warn(_('not removing repo %s because '
1094 1094 'it has changes.\n') % self._path)
1095 1095 return
1096 1096 self.ui.note(_('removing subrepo %s\n') % self._path)
1097 1097
1098 1098 self.wvfs.rmtree(forcibly=True)
1099 1099 try:
1100 1100 pwvfs = self._ctx.repo().wvfs
1101 1101 pwvfs.removedirs(pwvfs.dirname(self._path))
1102 1102 except OSError:
1103 1103 pass
1104 1104
1105 1105 @annotatesubrepoerror
1106 1106 def get(self, state, overwrite=False):
1107 1107 if overwrite:
1108 1108 self._svncommand(['revert', '--recursive'])
1109 1109 args = ['checkout']
1110 1110 if self._svnversion >= (1, 5):
1111 1111 args.append('--force')
1112 1112 # The revision must be specified at the end of the URL to properly
1113 1113 # update to a directory which has since been deleted and recreated.
1114 1114 args.append('%s@%s' % (state[0], state[1]))
1115 1115
1116 1116 # SEC: check that the ssh url is safe
1117 1117 util.checksafessh(state[0])
1118 1118
1119 1119 status, err = self._svncommand(args, failok=True)
1120 1120 _sanitize(self.ui, self.wvfs, '.svn')
1121 1121 if not re.search('Checked out revision [0-9]+.', status):
1122 1122 if ('is already a working copy for a different URL' in err
1123 1123 and (self._wcchanged()[:2] == (False, False))):
1124 1124 # obstructed but clean working copy, so just blow it away.
1125 1125 self.remove()
1126 1126 self.get(state, overwrite=False)
1127 1127 return
1128 1128 raise error.Abort((status or err).splitlines()[-1])
1129 1129 self.ui.status(status)
1130 1130
1131 1131 @annotatesubrepoerror
1132 1132 def merge(self, state):
1133 1133 old = self._state[1]
1134 1134 new = state[1]
1135 1135 wcrev = self._wcrev()
1136 1136 if new != wcrev:
1137 1137 dirty = old == wcrev or self._wcchanged()[0]
1138 1138 if _updateprompt(self.ui, self, dirty, wcrev, new):
1139 1139 self.get(state, False)
1140 1140
1141 1141 def push(self, opts):
1142 1142 # push is a no-op for SVN
1143 1143 return True
1144 1144
1145 1145 @annotatesubrepoerror
1146 1146 def files(self):
1147 1147 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1148 1148 doc = xml.dom.minidom.parseString(output)
1149 1149 paths = []
1150 1150 for e in doc.getElementsByTagName(r'entry'):
1151 1151 kind = pycompat.bytestr(e.getAttribute(r'kind'))
1152 1152 if kind != 'file':
1153 1153 continue
1154 1154 name = r''.join(c.data for c
1155 1155 in e.getElementsByTagName(r'name')[0].childNodes
1156 1156 if c.nodeType == c.TEXT_NODE)
1157 1157 paths.append(name.encode('utf8'))
1158 1158 return paths
1159 1159
1160 1160 def filedata(self, name, decode):
1161 1161 return self._svncommand(['cat'], name)[0]
1162 1162
1163 1163
1164 1164 class gitsubrepo(abstractsubrepo):
1165 1165 def __init__(self, ctx, path, state, allowcreate):
1166 1166 super(gitsubrepo, self).__init__(ctx, path)
1167 1167 self._state = state
1168 1168 self._abspath = ctx.repo().wjoin(path)
1169 1169 self._subparent = ctx.repo()
1170 1170 self._ensuregit()
1171 1171
1172 1172 def _ensuregit(self):
1173 1173 try:
1174 1174 self._gitexecutable = 'git'
1175 1175 out, err = self._gitnodir(['--version'])
1176 1176 except OSError as e:
1177 1177 genericerror = _("error executing git for subrepo '%s': %s")
1178 1178 notfoundhint = _("check git is installed and in your PATH")
1179 1179 if e.errno != errno.ENOENT:
1180 1180 raise error.Abort(genericerror % (
1181 1181 self._path, encoding.strtolocal(e.strerror)))
1182 1182 elif pycompat.iswindows:
1183 1183 try:
1184 1184 self._gitexecutable = 'git.cmd'
1185 1185 out, err = self._gitnodir(['--version'])
1186 1186 except OSError as e2:
1187 1187 if e2.errno == errno.ENOENT:
1188 1188 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1189 1189 " for subrepo '%s'") % self._path,
1190 1190 hint=notfoundhint)
1191 1191 else:
1192 1192 raise error.Abort(genericerror % (self._path,
1193 1193 encoding.strtolocal(e2.strerror)))
1194 1194 else:
1195 1195 raise error.Abort(_("couldn't find git for subrepo '%s'")
1196 1196 % self._path, hint=notfoundhint)
1197 1197 versionstatus = self._checkversion(out)
1198 1198 if versionstatus == 'unknown':
1199 1199 self.ui.warn(_('cannot retrieve git version\n'))
1200 1200 elif versionstatus == 'abort':
1201 1201 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1202 1202 elif versionstatus == 'warning':
1203 1203 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1204 1204
1205 1205 @staticmethod
1206 1206 def _gitversion(out):
1207 1207 m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
1208 1208 if m:
1209 1209 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1210 1210
1211 1211 m = re.search(br'^git version (\d+)\.(\d+)', out)
1212 1212 if m:
1213 1213 return (int(m.group(1)), int(m.group(2)), 0)
1214 1214
1215 1215 return -1
1216 1216
1217 1217 @staticmethod
1218 1218 def _checkversion(out):
1219 1219 '''ensure git version is new enough
1220 1220
1221 1221 >>> _checkversion = gitsubrepo._checkversion
1222 1222 >>> _checkversion(b'git version 1.6.0')
1223 1223 'ok'
1224 1224 >>> _checkversion(b'git version 1.8.5')
1225 1225 'ok'
1226 1226 >>> _checkversion(b'git version 1.4.0')
1227 1227 'abort'
1228 1228 >>> _checkversion(b'git version 1.5.0')
1229 1229 'warning'
1230 1230 >>> _checkversion(b'git version 1.9-rc0')
1231 1231 'ok'
1232 1232 >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
1233 1233 'ok'
1234 1234 >>> _checkversion(b'git version 1.9.0.GIT')
1235 1235 'ok'
1236 1236 >>> _checkversion(b'git version 12345')
1237 1237 'unknown'
1238 1238 >>> _checkversion(b'no')
1239 1239 'unknown'
1240 1240 '''
1241 1241 version = gitsubrepo._gitversion(out)
1242 1242 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1243 1243 # despite the docstring comment. For now, error on 1.4.0, warn on
1244 1244 # 1.5.0 but attempt to continue.
1245 1245 if version == -1:
1246 1246 return 'unknown'
1247 1247 if version < (1, 5, 0):
1248 1248 return 'abort'
1249 1249 elif version < (1, 6, 0):
1250 1250 return 'warning'
1251 1251 return 'ok'
1252 1252
1253 1253 def _gitcommand(self, commands, env=None, stream=False):
1254 1254 return self._gitdir(commands, env=env, stream=stream)[0]
1255 1255
1256 1256 def _gitdir(self, commands, env=None, stream=False):
1257 1257 return self._gitnodir(commands, env=env, stream=stream,
1258 1258 cwd=self._abspath)
1259 1259
1260 1260 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1261 1261 """Calls the git command
1262 1262
1263 1263 The methods tries to call the git command. versions prior to 1.6.0
1264 1264 are not supported and very probably fail.
1265 1265 """
1266 1266 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1267 1267 if env is None:
1268 1268 env = encoding.environ.copy()
1269 1269 # disable localization for Git output (issue5176)
1270 1270 env['LC_ALL'] = 'C'
1271 1271 # fix for Git CVE-2015-7545
1272 1272 if 'GIT_ALLOW_PROTOCOL' not in env:
1273 1273 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1274 1274 # unless ui.quiet is set, print git's stderr,
1275 1275 # which is mostly progress and useful info
1276 1276 errpipe = None
1277 1277 if self.ui.quiet:
1278 1278 errpipe = open(os.devnull, 'w')
1279 1279 if self.ui._colormode and len(commands) and commands[0] == "diff":
1280 1280 # insert the argument in the front,
1281 1281 # the end of git diff arguments is used for paths
1282 1282 commands.insert(1, '--color')
1283 1283 p = subprocess.Popen(pycompat.rapply(procutil.tonativestr,
1284 1284 [self._gitexecutable] + commands),
1285 1285 bufsize=-1,
1286 1286 cwd=pycompat.rapply(procutil.tonativestr, cwd),
1287 1287 env=procutil.tonativeenv(env),
1288 1288 close_fds=procutil.closefds,
1289 1289 stdout=subprocess.PIPE, stderr=errpipe)
1290 1290 if stream:
1291 1291 return p.stdout, None
1292 1292
1293 1293 retdata = p.stdout.read().strip()
1294 1294 # wait for the child to exit to avoid race condition.
1295 1295 p.wait()
1296 1296
1297 1297 if p.returncode != 0 and p.returncode != 1:
1298 1298 # there are certain error codes that are ok
1299 1299 command = commands[0]
1300 1300 if command in ('cat-file', 'symbolic-ref'):
1301 1301 return retdata, p.returncode
1302 1302 # for all others, abort
1303 1303 raise error.Abort(_('git %s error %d in %s') %
1304 1304 (command, p.returncode, self._relpath))
1305 1305
1306 1306 return retdata, p.returncode
1307 1307
1308 1308 def _gitmissing(self):
1309 1309 return not self.wvfs.exists('.git')
1310 1310
1311 1311 def _gitstate(self):
1312 1312 return self._gitcommand(['rev-parse', 'HEAD'])
1313 1313
1314 1314 def _gitcurrentbranch(self):
1315 1315 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1316 1316 if err:
1317 1317 current = None
1318 1318 return current
1319 1319
1320 1320 def _gitremote(self, remote):
1321 1321 out = self._gitcommand(['remote', 'show', '-n', remote])
1322 1322 line = out.split('\n')[1]
1323 1323 i = line.index('URL: ') + len('URL: ')
1324 1324 return line[i:]
1325 1325
1326 1326 def _githavelocally(self, revision):
1327 1327 out, code = self._gitdir(['cat-file', '-e', revision])
1328 1328 return code == 0
1329 1329
1330 1330 def _gitisancestor(self, r1, r2):
1331 1331 base = self._gitcommand(['merge-base', r1, r2])
1332 1332 return base == r1
1333 1333
1334 1334 def _gitisbare(self):
1335 1335 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1336 1336
1337 1337 def _gitupdatestat(self):
1338 1338 """This must be run before git diff-index.
1339 1339 diff-index only looks at changes to file stat;
1340 1340 this command looks at file contents and updates the stat."""
1341 1341 self._gitcommand(['update-index', '-q', '--refresh'])
1342 1342
1343 1343 def _gitbranchmap(self):
1344 1344 '''returns 2 things:
1345 1345 a map from git branch to revision
1346 1346 a map from revision to branches'''
1347 1347 branch2rev = {}
1348 1348 rev2branch = {}
1349 1349
1350 1350 out = self._gitcommand(['for-each-ref', '--format',
1351 1351 '%(objectname) %(refname)'])
1352 1352 for line in out.split('\n'):
1353 1353 revision, ref = line.split(' ')
1354 1354 if (not ref.startswith('refs/heads/') and
1355 1355 not ref.startswith('refs/remotes/')):
1356 1356 continue
1357 1357 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1358 1358 continue # ignore remote/HEAD redirects
1359 1359 branch2rev[ref] = revision
1360 1360 rev2branch.setdefault(revision, []).append(ref)
1361 1361 return branch2rev, rev2branch
1362 1362
1363 1363 def _gittracking(self, branches):
1364 1364 'return map of remote branch to local tracking branch'
1365 1365 # assumes no more than one local tracking branch for each remote
1366 1366 tracking = {}
1367 1367 for b in branches:
1368 1368 if b.startswith('refs/remotes/'):
1369 1369 continue
1370 1370 bname = b.split('/', 2)[2]
1371 1371 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1372 1372 if remote:
1373 1373 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1374 1374 tracking['refs/remotes/%s/%s' %
1375 1375 (remote, ref.split('/', 2)[2])] = b
1376 1376 return tracking
1377 1377
1378 1378 def _abssource(self, source):
1379 1379 if '://' not in source:
1380 1380 # recognize the scp syntax as an absolute source
1381 1381 colon = source.find(':')
1382 1382 if colon != -1 and '/' not in source[:colon]:
1383 1383 return source
1384 1384 self._subsource = source
1385 1385 return _abssource(self)
1386 1386
1387 1387 def _fetch(self, source, revision):
1388 1388 if self._gitmissing():
1389 1389 # SEC: check for safe ssh url
1390 1390 util.checksafessh(source)
1391 1391
1392 1392 source = self._abssource(source)
1393 1393 self.ui.status(_('cloning subrepo %s from %s\n') %
1394 1394 (self._relpath, source))
1395 1395 self._gitnodir(['clone', source, self._abspath])
1396 1396 if self._githavelocally(revision):
1397 1397 return
1398 1398 self.ui.status(_('pulling subrepo %s from %s\n') %
1399 1399 (self._relpath, self._gitremote('origin')))
1400 1400 # try only origin: the originally cloned repo
1401 1401 self._gitcommand(['fetch'])
1402 1402 if not self._githavelocally(revision):
1403 1403 raise error.Abort(_('revision %s does not exist in subrepository '
1404 1404 '"%s"\n') % (revision, self._relpath))
1405 1405
1406 1406 @annotatesubrepoerror
1407 1407 def dirty(self, ignoreupdate=False, missing=False):
1408 1408 if self._gitmissing():
1409 1409 return self._state[1] != ''
1410 1410 if self._gitisbare():
1411 1411 return True
1412 1412 if not ignoreupdate and self._state[1] != self._gitstate():
1413 1413 # different version checked out
1414 1414 return True
1415 1415 # check for staged changes or modified files; ignore untracked files
1416 1416 self._gitupdatestat()
1417 1417 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1418 1418 return code == 1
1419 1419
1420 1420 def basestate(self):
1421 1421 return self._gitstate()
1422 1422
1423 1423 @annotatesubrepoerror
1424 1424 def get(self, state, overwrite=False):
1425 1425 source, revision, kind = state
1426 1426 if not revision:
1427 1427 self.remove()
1428 1428 return
1429 1429 self._fetch(source, revision)
1430 1430 # if the repo was set to be bare, unbare it
1431 1431 if self._gitisbare():
1432 1432 self._gitcommand(['config', 'core.bare', 'false'])
1433 1433 if self._gitstate() == revision:
1434 1434 self._gitcommand(['reset', '--hard', 'HEAD'])
1435 1435 return
1436 1436 elif self._gitstate() == revision:
1437 1437 if overwrite:
1438 1438 # first reset the index to unmark new files for commit, because
1439 1439 # reset --hard will otherwise throw away files added for commit,
1440 1440 # not just unmark them.
1441 1441 self._gitcommand(['reset', 'HEAD'])
1442 1442 self._gitcommand(['reset', '--hard', 'HEAD'])
1443 1443 return
1444 1444 branch2rev, rev2branch = self._gitbranchmap()
1445 1445
1446 1446 def checkout(args):
1447 1447 cmd = ['checkout']
1448 1448 if overwrite:
1449 1449 # first reset the index to unmark new files for commit, because
1450 1450 # the -f option will otherwise throw away files added for
1451 1451 # commit, not just unmark them.
1452 1452 self._gitcommand(['reset', 'HEAD'])
1453 1453 cmd.append('-f')
1454 1454 self._gitcommand(cmd + args)
1455 1455 _sanitize(self.ui, self.wvfs, '.git')
1456 1456
1457 1457 def rawcheckout():
1458 1458 # no branch to checkout, check it out with no branch
1459 1459 self.ui.warn(_('checking out detached HEAD in '
1460 1460 'subrepository "%s"\n') % self._relpath)
1461 1461 self.ui.warn(_('check out a git branch if you intend '
1462 1462 'to make changes\n'))
1463 1463 checkout(['-q', revision])
1464 1464
1465 1465 if revision not in rev2branch:
1466 1466 rawcheckout()
1467 1467 return
1468 1468 branches = rev2branch[revision]
1469 1469 firstlocalbranch = None
1470 1470 for b in branches:
1471 1471 if b == 'refs/heads/master':
1472 1472 # master trumps all other branches
1473 1473 checkout(['refs/heads/master'])
1474 1474 return
1475 1475 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1476 1476 firstlocalbranch = b
1477 1477 if firstlocalbranch:
1478 1478 checkout([firstlocalbranch])
1479 1479 return
1480 1480
1481 1481 tracking = self._gittracking(branch2rev.keys())
1482 1482 # choose a remote branch already tracked if possible
1483 1483 remote = branches[0]
1484 1484 if remote not in tracking:
1485 1485 for b in branches:
1486 1486 if b in tracking:
1487 1487 remote = b
1488 1488 break
1489 1489
1490 1490 if remote not in tracking:
1491 1491 # create a new local tracking branch
1492 1492 local = remote.split('/', 3)[3]
1493 1493 checkout(['-b', local, remote])
1494 1494 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1495 1495 # When updating to a tracked remote branch,
1496 1496 # if the local tracking branch is downstream of it,
1497 1497 # a normal `git pull` would have performed a "fast-forward merge"
1498 1498 # which is equivalent to updating the local branch to the remote.
1499 1499 # Since we are only looking at branching at update, we need to
1500 1500 # detect this situation and perform this action lazily.
1501 1501 if tracking[remote] != self._gitcurrentbranch():
1502 1502 checkout([tracking[remote]])
1503 1503 self._gitcommand(['merge', '--ff', remote])
1504 1504 _sanitize(self.ui, self.wvfs, '.git')
1505 1505 else:
1506 1506 # a real merge would be required, just checkout the revision
1507 1507 rawcheckout()
1508 1508
1509 1509 @annotatesubrepoerror
1510 1510 def commit(self, text, user, date):
1511 1511 if self._gitmissing():
1512 1512 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1513 1513 cmd = ['commit', '-a', '-m', text]
1514 1514 env = encoding.environ.copy()
1515 1515 if user:
1516 1516 cmd += ['--author', user]
1517 1517 if date:
1518 1518 # git's date parser silently ignores when seconds < 1e9
1519 1519 # convert to ISO8601
1520 1520 env['GIT_AUTHOR_DATE'] = dateutil.datestr(date,
1521 1521 '%Y-%m-%dT%H:%M:%S %1%2')
1522 1522 self._gitcommand(cmd, env=env)
1523 1523 # make sure commit works otherwise HEAD might not exist under certain
1524 1524 # circumstances
1525 1525 return self._gitstate()
1526 1526
1527 1527 @annotatesubrepoerror
1528 1528 def merge(self, state):
1529 1529 source, revision, kind = state
1530 1530 self._fetch(source, revision)
1531 1531 base = self._gitcommand(['merge-base', revision, self._state[1]])
1532 1532 self._gitupdatestat()
1533 1533 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1534 1534
1535 1535 def mergefunc():
1536 1536 if base == revision:
1537 1537 self.get(state) # fast forward merge
1538 1538 elif base != self._state[1]:
1539 1539 self._gitcommand(['merge', '--no-commit', revision])
1540 1540 _sanitize(self.ui, self.wvfs, '.git')
1541 1541
1542 1542 if self.dirty():
1543 1543 if self._gitstate() != revision:
1544 1544 dirty = self._gitstate() == self._state[1] or code != 0
1545 1545 if _updateprompt(self.ui, self, dirty,
1546 1546 self._state[1][:7], revision[:7]):
1547 1547 mergefunc()
1548 1548 else:
1549 1549 mergefunc()
1550 1550
1551 1551 @annotatesubrepoerror
1552 1552 def push(self, opts):
1553 1553 force = opts.get('force')
1554 1554
1555 1555 if not self._state[1]:
1556 1556 return True
1557 1557 if self._gitmissing():
1558 1558 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1559 1559 # if a branch in origin contains the revision, nothing to do
1560 1560 branch2rev, rev2branch = self._gitbranchmap()
1561 1561 if self._state[1] in rev2branch:
1562 1562 for b in rev2branch[self._state[1]]:
1563 1563 if b.startswith('refs/remotes/origin/'):
1564 1564 return True
1565 1565 for b, revision in branch2rev.iteritems():
1566 1566 if b.startswith('refs/remotes/origin/'):
1567 1567 if self._gitisancestor(self._state[1], revision):
1568 1568 return True
1569 1569 # otherwise, try to push the currently checked out branch
1570 1570 cmd = ['push']
1571 1571 if force:
1572 1572 cmd.append('--force')
1573 1573
1574 1574 current = self._gitcurrentbranch()
1575 1575 if current:
1576 1576 # determine if the current branch is even useful
1577 1577 if not self._gitisancestor(self._state[1], current):
1578 1578 self.ui.warn(_('unrelated git branch checked out '
1579 1579 'in subrepository "%s"\n') % self._relpath)
1580 1580 return False
1581 1581 self.ui.status(_('pushing branch %s of subrepository "%s"\n') %
1582 1582 (current.split('/', 2)[2], self._relpath))
1583 1583 ret = self._gitdir(cmd + ['origin', current])
1584 1584 return ret[1] == 0
1585 1585 else:
1586 1586 self.ui.warn(_('no branch checked out in subrepository "%s"\n'
1587 1587 'cannot push revision %s\n') %
1588 1588 (self._relpath, self._state[1]))
1589 1589 return False
1590 1590
1591 1591 @annotatesubrepoerror
1592 1592 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
1593 1593 if self._gitmissing():
1594 1594 return []
1595 1595
1596 1596 s = self.status(None, unknown=True, clean=True)
1597 1597
1598 1598 tracked = set()
1599 1599 # dirstates 'amn' warn, 'r' is added again
1600 1600 for l in (s.modified, s.added, s.deleted, s.clean):
1601 1601 tracked.update(l)
1602 1602
1603 1603 # Unknown files not of interest will be rejected by the matcher
1604 1604 files = s.unknown
1605 1605 files.extend(match.files())
1606 1606
1607 1607 rejected = []
1608 1608
1609 1609 files = [f for f in sorted(set(files)) if match(f)]
1610 1610 for f in files:
1611 1611 exact = match.exact(f)
1612 1612 command = ["add"]
1613 1613 if exact:
1614 1614 command.append("-f") #should be added, even if ignored
1615 1615 if ui.verbose or not exact:
1616 1616 ui.status(_('adding %s\n') % uipathfn(f))
1617 1617
1618 1618 if f in tracked: # hg prints 'adding' even if already tracked
1619 1619 if exact:
1620 1620 rejected.append(f)
1621 1621 continue
1622 1622 if not opts.get(r'dry_run'):
1623 1623 self._gitcommand(command + [f])
1624 1624
1625 1625 for f in rejected:
1626 1626 ui.warn(_("%s already tracked!\n") % uipathfn(f))
1627 1627
1628 1628 return rejected
1629 1629
1630 1630 @annotatesubrepoerror
1631 1631 def remove(self):
1632 1632 if self._gitmissing():
1633 1633 return
1634 1634 if self.dirty():
1635 1635 self.ui.warn(_('not removing repo %s because '
1636 1636 'it has changes.\n') % self._relpath)
1637 1637 return
1638 1638 # we can't fully delete the repository as it may contain
1639 1639 # local-only history
1640 1640 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1641 1641 self._gitcommand(['config', 'core.bare', 'true'])
1642 1642 for f, kind in self.wvfs.readdir():
1643 1643 if f == '.git':
1644 1644 continue
1645 1645 if kind == stat.S_IFDIR:
1646 1646 self.wvfs.rmtree(f)
1647 1647 else:
1648 1648 self.wvfs.unlink(f)
1649 1649
1650 1650 def archive(self, archiver, prefix, match=None, decode=True):
1651 1651 total = 0
1652 1652 source, revision = self._state
1653 1653 if not revision:
1654 1654 return total
1655 1655 self._fetch(source, revision)
1656 1656
1657 1657 # Parse git's native archive command.
1658 1658 # This should be much faster than manually traversing the trees
1659 1659 # and objects with many subprocess calls.
1660 1660 tarstream = self._gitcommand(['archive', revision], stream=True)
1661 1661 tar = tarfile.open(fileobj=tarstream, mode=r'r|')
1662 1662 relpath = subrelpath(self)
1663 1663 progress = self.ui.makeprogress(_('archiving (%s)') % relpath,
1664 1664 unit=_('files'))
1665 1665 progress.update(0)
1666 1666 for info in tar:
1667 1667 if info.isdir():
1668 1668 continue
1669 1669 bname = pycompat.fsencode(info.name)
1670 1670 if match and not match(bname):
1671 1671 continue
1672 1672 if info.issym():
1673 1673 data = info.linkname
1674 1674 else:
1675 1675 data = tar.extractfile(info).read()
1676 1676 archiver.addfile(prefix + bname, info.mode, info.issym(), data)
1677 1677 total += 1
1678 1678 progress.increment()
1679 1679 progress.complete()
1680 1680 return total
1681 1681
1682 1682
1683 1683 @annotatesubrepoerror
1684 1684 def cat(self, match, fm, fntemplate, prefix, **opts):
1685 1685 rev = self._state[1]
1686 1686 if match.anypats():
1687 1687 return 1 #No support for include/exclude yet
1688 1688
1689 1689 if not match.files():
1690 1690 return 1
1691 1691
1692 1692 # TODO: add support for non-plain formatter (see cmdutil.cat())
1693 1693 for f in match.files():
1694 1694 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1695 1695 fp = cmdutil.makefileobj(self._ctx, fntemplate,
1696 1696 pathname=self.wvfs.reljoin(prefix, f))
1697 1697 fp.write(output)
1698 1698 fp.close()
1699 1699 return 0
1700 1700
1701 1701
1702 1702 @annotatesubrepoerror
1703 1703 def status(self, rev2, **opts):
1704 1704 rev1 = self._state[1]
1705 1705 if self._gitmissing() or not rev1:
1706 1706 # if the repo is missing, return no results
1707 1707 return scmutil.status([], [], [], [], [], [], [])
1708 1708 modified, added, removed = [], [], []
1709 1709 self._gitupdatestat()
1710 1710 if rev2:
1711 1711 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
1712 1712 else:
1713 1713 command = ['diff-index', '--no-renames', rev1]
1714 1714 out = self._gitcommand(command)
1715 1715 for line in out.split('\n'):
1716 1716 tab = line.find('\t')
1717 1717 if tab == -1:
1718 1718 continue
1719 1719 status, f = line[tab - 1:tab], line[tab + 1:]
1720 1720 if status == 'M':
1721 1721 modified.append(f)
1722 1722 elif status == 'A':
1723 1723 added.append(f)
1724 1724 elif status == 'D':
1725 1725 removed.append(f)
1726 1726
1727 1727 deleted, unknown, ignored, clean = [], [], [], []
1728 1728
1729 1729 command = ['status', '--porcelain', '-z']
1730 1730 if opts.get(r'unknown'):
1731 1731 command += ['--untracked-files=all']
1732 1732 if opts.get(r'ignored'):
1733 1733 command += ['--ignored']
1734 1734 out = self._gitcommand(command)
1735 1735
1736 1736 changedfiles = set()
1737 1737 changedfiles.update(modified)
1738 1738 changedfiles.update(added)
1739 1739 changedfiles.update(removed)
1740 1740 for line in out.split('\0'):
1741 1741 if not line:
1742 1742 continue
1743 1743 st = line[0:2]
1744 1744 #moves and copies show 2 files on one line
1745 1745 if line.find('\0') >= 0:
1746 1746 filename1, filename2 = line[3:].split('\0')
1747 1747 else:
1748 1748 filename1 = line[3:]
1749 1749 filename2 = None
1750 1750
1751 1751 changedfiles.add(filename1)
1752 1752 if filename2:
1753 1753 changedfiles.add(filename2)
1754 1754
1755 1755 if st == '??':
1756 1756 unknown.append(filename1)
1757 1757 elif st == '!!':
1758 1758 ignored.append(filename1)
1759 1759
1760 1760 if opts.get(r'clean'):
1761 1761 out = self._gitcommand(['ls-files'])
1762 1762 for f in out.split('\n'):
1763 1763 if not f in changedfiles:
1764 1764 clean.append(f)
1765 1765
1766 1766 return scmutil.status(modified, added, removed, deleted,
1767 1767 unknown, ignored, clean)
1768 1768
1769 1769 @annotatesubrepoerror
1770 1770 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1771 1771 node1 = self._state[1]
1772 1772 cmd = ['diff', '--no-renames']
1773 1773 if opts[r'stat']:
1774 1774 cmd.append('--stat')
1775 1775 else:
1776 1776 # for Git, this also implies '-p'
1777 1777 cmd.append('-U%d' % diffopts.context)
1778 1778
1779 1779 if diffopts.noprefix:
1780 1780 cmd.extend(['--src-prefix=%s/' % prefix,
1781 1781 '--dst-prefix=%s/' % prefix])
1782 1782 else:
1783 1783 cmd.extend(['--src-prefix=a/%s/' % prefix,
1784 1784 '--dst-prefix=b/%s/' % prefix])
1785 1785
1786 1786 if diffopts.ignorews:
1787 1787 cmd.append('--ignore-all-space')
1788 1788 if diffopts.ignorewsamount:
1789 1789 cmd.append('--ignore-space-change')
1790 1790 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1791 1791 and diffopts.ignoreblanklines:
1792 1792 cmd.append('--ignore-blank-lines')
1793 1793
1794 1794 cmd.append(node1)
1795 1795 if node2:
1796 1796 cmd.append(node2)
1797 1797
1798 1798 output = ""
1799 1799 if match.always():
1800 1800 output += self._gitcommand(cmd) + '\n'
1801 1801 else:
1802 1802 st = self.status(node2)[:3]
1803 1803 files = [f for sublist in st for f in sublist]
1804 1804 for f in files:
1805 1805 if match(f):
1806 1806 output += self._gitcommand(cmd + ['--', f]) + '\n'
1807 1807
1808 1808 if output.strip():
1809 1809 ui.write(output)
1810 1810
1811 1811 @annotatesubrepoerror
1812 1812 def revert(self, substate, *pats, **opts):
1813 1813 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1814 1814 if not opts.get(r'no_backup'):
1815 1815 status = self.status(None)
1816 1816 names = status.modified
1817 1817 for name in names:
1818 1818 # backuppath() expects a path relative to the parent repo (the
1819 1819 # repo that ui.origbackuppath is relative to)
1820 1820 parentname = os.path.join(self._path, name)
1821 1821 bakname = scmutil.backuppath(self.ui, self._subparent,
1822 1822 parentname)
1823 1823 self.ui.note(_('saving current version of %s as %s\n') %
1824 1824 (name, os.path.relpath(bakname)))
1825 1825 util.rename(self.wvfs.join(name), bakname)
1826 1826
1827 1827 if not opts.get(r'dry_run'):
1828 1828 self.get(substate, overwrite=True)
1829 1829 return []
1830 1830
1831 1831 def shortid(self, revid):
1832 1832 return revid[:7]
1833 1833
1834 1834 types = {
1835 1835 'hg': hgsubrepo,
1836 1836 'svn': svnsubrepo,
1837 1837 'git': gitsubrepo,
1838 1838 }
@@ -1,1996 +1,1996 b''
1 1 Let commit recurse into subrepos by default to match pre-2.0 behavior:
2 2
3 3 $ echo "[ui]" >> $HGRCPATH
4 4 $ echo "commitsubrepos = Yes" >> $HGRCPATH
5 5
6 6 $ hg init t
7 7 $ cd t
8 8
9 9 first revision, no sub
10 10
11 11 $ echo a > a
12 12 $ hg ci -Am0
13 13 adding a
14 14
15 15 add first sub
16 16
17 17 $ echo s = s > .hgsub
18 18 $ hg add .hgsub
19 19 $ hg init s
20 20 $ echo a > s/a
21 21
22 22 Issue2232: committing a subrepo without .hgsub
23 23
24 24 $ hg ci -mbad s
25 25 abort: can't commit subrepos without .hgsub
26 26 [255]
27 27
28 28 $ hg -R s add s/a
29 29 $ hg files -S
30 30 .hgsub
31 31 a
32 32 s/a
33 33
34 34 `hg files` respects ui.relative-paths
35 35 BROKEN: shows subrepo paths relative to the subrepo
36 36 $ hg files -S --config ui.relative-paths=no
37 37 .hgsub
38 38 a
39 a
39 s/a
40 40
41 41 $ hg -R s ci -Ams0
42 42 $ hg sum
43 43 parent: 0:f7b1eb17ad24 tip
44 44 0
45 45 branch: default
46 46 commit: 1 added, 1 subrepos
47 47 update: (current)
48 48 phases: 1 draft
49 49 $ hg ci -m1
50 50
51 51 test handling .hgsubstate "added" explicitly.
52 52
53 53 $ hg parents --template '{node}\n{files}\n'
54 54 7cf8cfea66e410e8e3336508dfeec07b3192de51
55 55 .hgsub .hgsubstate
56 56 $ hg rollback -q
57 57 $ hg add .hgsubstate
58 58 $ hg ci -m1
59 59 $ hg parents --template '{node}\n{files}\n'
60 60 7cf8cfea66e410e8e3336508dfeec07b3192de51
61 61 .hgsub .hgsubstate
62 62
63 63 Subrepopath which overlaps with filepath, does not change warnings in remove()
64 64
65 65 $ mkdir snot
66 66 $ touch snot/file
67 67 $ hg remove -S snot/file
68 68 not removing snot/file: file is untracked
69 69 [1]
70 70 $ hg cat snot/filenot
71 71 snot/filenot: no such file in rev 7cf8cfea66e4
72 72 [1]
73 73 $ rm -r snot
74 74
75 75 Revert subrepo and test subrepo fileset keyword:
76 76
77 77 $ echo b > s/a
78 78 $ hg revert --dry-run "set:subrepo('glob:s*')"
79 79 reverting subrepo s
80 80 reverting s/a
81 81 $ cat s/a
82 82 b
83 83 $ hg revert "set:subrepo('glob:s*')"
84 84 reverting subrepo s
85 85 reverting s/a
86 86 $ cat s/a
87 87 a
88 88 $ rm s/a.orig
89 89
90 90 Revert subrepo with no backup. The "reverting s/a" line is gone since
91 91 we're really running 'hg update' in the subrepo:
92 92
93 93 $ echo b > s/a
94 94 $ hg revert --no-backup s
95 95 reverting subrepo s
96 96
97 97 Issue2022: update -C
98 98
99 99 $ echo b > s/a
100 100 $ hg sum
101 101 parent: 1:7cf8cfea66e4 tip
102 102 1
103 103 branch: default
104 104 commit: 1 subrepos
105 105 update: (current)
106 106 phases: 2 draft
107 107 $ hg co -C 1
108 108 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
109 109 $ hg sum
110 110 parent: 1:7cf8cfea66e4 tip
111 111 1
112 112 branch: default
113 113 commit: (clean)
114 114 update: (current)
115 115 phases: 2 draft
116 116
117 117 commands that require a clean repo should respect subrepos
118 118
119 119 $ echo b >> s/a
120 120 $ hg backout tip
121 121 abort: uncommitted changes in subrepository "s"
122 122 [255]
123 123 $ hg revert -C -R s s/a
124 124
125 125 add sub sub
126 126
127 127 $ echo ss = ss > s/.hgsub
128 128 $ hg init s/ss
129 129 $ echo a > s/ss/a
130 130 $ hg -R s add s/.hgsub
131 131 $ hg -R s/ss add s/ss/a
132 132 $ hg sum
133 133 parent: 1:7cf8cfea66e4 tip
134 134 1
135 135 branch: default
136 136 commit: 1 subrepos
137 137 update: (current)
138 138 phases: 2 draft
139 139 $ hg ci -m2
140 140 committing subrepository s
141 141 committing subrepository s/ss
142 142 $ hg sum
143 143 parent: 2:df30734270ae tip
144 144 2
145 145 branch: default
146 146 commit: (clean)
147 147 update: (current)
148 148 phases: 3 draft
149 149
150 150 test handling .hgsubstate "modified" explicitly.
151 151
152 152 $ hg parents --template '{node}\n{files}\n'
153 153 df30734270ae757feb35e643b7018e818e78a9aa
154 154 .hgsubstate
155 155 $ hg rollback -q
156 156 $ hg status -A .hgsubstate
157 157 M .hgsubstate
158 158 $ hg ci -m2
159 159 $ hg parents --template '{node}\n{files}\n'
160 160 df30734270ae757feb35e643b7018e818e78a9aa
161 161 .hgsubstate
162 162
163 163 bump sub rev (and check it is ignored by ui.commitsubrepos)
164 164
165 165 $ echo b > s/a
166 166 $ hg -R s ci -ms1
167 167 $ hg --config ui.commitsubrepos=no ci -m3
168 168
169 169 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
170 170
171 171 $ echo c > s/a
172 172 $ hg --config ui.commitsubrepos=no ci -m4
173 173 abort: uncommitted changes in subrepository "s"
174 174 (use --subrepos for recursive commit)
175 175 [255]
176 176 $ hg id
177 177 f6affe3fbfaa+ tip
178 178 $ hg -R s ci -mc
179 179 $ hg id
180 180 f6affe3fbfaa+ tip
181 181 $ echo d > s/a
182 182 $ hg ci -m4
183 183 committing subrepository s
184 184 $ hg tip -R s
185 185 changeset: 4:02dcf1d70411
186 186 tag: tip
187 187 user: test
188 188 date: Thu Jan 01 00:00:00 1970 +0000
189 189 summary: 4
190 190
191 191
192 192 check caching
193 193
194 194 $ hg co 0
195 195 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
196 196 $ hg debugsub
197 197
198 198 restore
199 199
200 200 $ hg co
201 201 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
202 202 $ hg debugsub
203 203 path s
204 204 source s
205 205 revision 02dcf1d704118aee3ee306ccfa1910850d5b05ef
206 206
207 207 new branch for merge tests
208 208
209 209 $ hg co 1
210 210 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
211 211 $ echo t = t >> .hgsub
212 212 $ hg init t
213 213 $ echo t > t/t
214 214 $ hg -R t add t
215 215 adding t/t
216 216
217 217 5
218 218
219 219 $ hg ci -m5 # add sub
220 220 committing subrepository t
221 221 created new head
222 222 $ echo t2 > t/t
223 223
224 224 6
225 225
226 226 $ hg st -R s
227 227 $ hg ci -m6 # change sub
228 228 committing subrepository t
229 229 $ hg debugsub
230 230 path s
231 231 source s
232 232 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
233 233 path t
234 234 source t
235 235 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
236 236 $ echo t3 > t/t
237 237
238 238 7
239 239
240 240 $ hg ci -m7 # change sub again for conflict test
241 241 committing subrepository t
242 242 $ hg rm .hgsub
243 243
244 244 8
245 245
246 246 $ hg ci -m8 # remove sub
247 247
248 248 test handling .hgsubstate "removed" explicitly.
249 249
250 250 $ hg parents --template '{node}\n{files}\n'
251 251 96615c1dad2dc8e3796d7332c77ce69156f7b78e
252 252 .hgsub .hgsubstate
253 253 $ hg rollback -q
254 254 $ hg remove .hgsubstate
255 255 $ hg ci -m8
256 256 $ hg parents --template '{node}\n{files}\n'
257 257 96615c1dad2dc8e3796d7332c77ce69156f7b78e
258 258 .hgsub .hgsubstate
259 259
260 260 merge tests
261 261
262 262 $ hg co -C 3
263 263 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
264 264 $ hg merge 5 # test adding
265 265 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
266 266 (branch merge, don't forget to commit)
267 267 $ hg debugsub
268 268 path s
269 269 source s
270 270 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
271 271 path t
272 272 source t
273 273 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
274 274 $ hg ci -m9
275 275 created new head
276 276 $ hg merge 6 --debug # test change
277 277 searching for copies back to rev 2
278 278 resolving manifests
279 279 branchmerge: True, force: False, partial: False
280 280 ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
281 281 starting 4 threads for background file closing (?)
282 282 .hgsubstate: versions differ -> m (premerge)
283 283 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
284 284 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
285 285 getting subrepo t
286 286 resolving manifests
287 287 branchmerge: False, force: False, partial: False
288 288 ancestor: 60ca1237c194, local: 60ca1237c194+, remote: 6747d179aa9a
289 289 t: remote is newer -> g
290 290 getting t
291 291 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
292 292 (branch merge, don't forget to commit)
293 293 $ hg debugsub
294 294 path s
295 295 source s
296 296 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
297 297 path t
298 298 source t
299 299 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
300 300 $ echo conflict > t/t
301 301 $ hg ci -m10
302 302 committing subrepository t
303 303 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
304 304 searching for copies back to rev 2
305 305 resolving manifests
306 306 branchmerge: True, force: False, partial: False
307 307 ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
308 308 starting 4 threads for background file closing (?)
309 309 .hgsubstate: versions differ -> m (premerge)
310 310 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
311 311 subrepo t: both sides changed
312 312 subrepository t diverged (local revision: 20a0db6fbf6c, remote revision: 7af322bc1198)
313 313 starting 4 threads for background file closing (?)
314 314 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
315 315 merging subrepository "t"
316 316 searching for copies back to rev 2
317 317 resolving manifests
318 318 branchmerge: True, force: False, partial: False
319 319 ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
320 320 preserving t for resolve of t
321 321 starting 4 threads for background file closing (?)
322 322 t: versions differ -> m (premerge)
323 323 picked tool ':merge' for t (binary False symlink False changedelete False)
324 324 merging t
325 325 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
326 326 t: versions differ -> m (merge)
327 327 picked tool ':merge' for t (binary False symlink False changedelete False)
328 328 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
329 329 warning: conflicts while merging t! (edit, then use 'hg resolve --mark')
330 330 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
331 331 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
332 332 subrepo t: merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
333 333 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
334 334 (branch merge, don't forget to commit)
335 335
336 336 should conflict
337 337
338 338 $ cat t/t
339 339 <<<<<<< local: 20a0db6fbf6c - test: 10
340 340 conflict
341 341 =======
342 342 t3
343 343 >>>>>>> other: 7af322bc1198 - test: 7
344 344
345 345 11: remove subrepo t
346 346
347 347 $ hg co -C 5
348 348 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
349 349 $ hg revert -r 4 .hgsub # remove t
350 350 $ hg ci -m11
351 351 created new head
352 352 $ hg debugsub
353 353 path s
354 354 source s
355 355 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
356 356
357 357 local removed, remote changed, keep changed
358 358
359 359 $ hg merge 6
360 360 remote [merge rev] changed subrepository t which local [working copy] removed
361 361 use (c)hanged version or (d)elete? c
362 362 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
363 363 (branch merge, don't forget to commit)
364 364 BROKEN: should include subrepo t
365 365 $ hg debugsub
366 366 path s
367 367 source s
368 368 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
369 369 $ cat .hgsubstate
370 370 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
371 371 6747d179aa9a688023c4b0cad32e4c92bb7f34ad t
372 372 $ hg ci -m 'local removed, remote changed, keep changed'
373 373 BROKEN: should include subrepo t
374 374 $ hg debugsub
375 375 path s
376 376 source s
377 377 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
378 378 BROKEN: should include subrepo t
379 379 $ cat .hgsubstate
380 380 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
381 381 $ cat t/t
382 382 t2
383 383
384 384 local removed, remote changed, keep removed
385 385
386 386 $ hg co -C 11
387 387 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
388 388 $ hg merge --config ui.interactive=true 6 <<EOF
389 389 > d
390 390 > EOF
391 391 remote [merge rev] changed subrepository t which local [working copy] removed
392 392 use (c)hanged version or (d)elete? d
393 393 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
394 394 (branch merge, don't forget to commit)
395 395 $ hg debugsub
396 396 path s
397 397 source s
398 398 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
399 399 $ cat .hgsubstate
400 400 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
401 401 $ hg ci -m 'local removed, remote changed, keep removed'
402 402 created new head
403 403 $ hg debugsub
404 404 path s
405 405 source s
406 406 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
407 407 $ cat .hgsubstate
408 408 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
409 409
410 410 local changed, remote removed, keep changed
411 411
412 412 $ hg co -C 6
413 413 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
414 414 $ hg merge 11
415 415 local [working copy] changed subrepository t which remote [merge rev] removed
416 416 use (c)hanged version or (d)elete? c
417 417 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
418 418 (branch merge, don't forget to commit)
419 419 BROKEN: should include subrepo t
420 420 $ hg debugsub
421 421 path s
422 422 source s
423 423 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
424 424 BROKEN: should include subrepo t
425 425 $ cat .hgsubstate
426 426 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
427 427 $ hg ci -m 'local changed, remote removed, keep changed'
428 428 created new head
429 429 BROKEN: should include subrepo t
430 430 $ hg debugsub
431 431 path s
432 432 source s
433 433 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
434 434 BROKEN: should include subrepo t
435 435 $ cat .hgsubstate
436 436 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
437 437 $ cat t/t
438 438 t2
439 439
440 440 local changed, remote removed, keep removed
441 441
442 442 $ hg co -C 6
443 443 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
444 444 $ hg merge --config ui.interactive=true 11 <<EOF
445 445 > d
446 446 > EOF
447 447 local [working copy] changed subrepository t which remote [merge rev] removed
448 448 use (c)hanged version or (d)elete? d
449 449 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
450 450 (branch merge, don't forget to commit)
451 451 $ hg debugsub
452 452 path s
453 453 source s
454 454 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
455 455 $ cat .hgsubstate
456 456 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
457 457 $ hg ci -m 'local changed, remote removed, keep removed'
458 458 created new head
459 459 $ hg debugsub
460 460 path s
461 461 source s
462 462 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
463 463 $ cat .hgsubstate
464 464 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
465 465
466 466 clean up to avoid having to fix up the tests below
467 467
468 468 $ hg co -C 10
469 469 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
470 470 $ cat >> $HGRCPATH <<EOF
471 471 > [extensions]
472 472 > strip=
473 473 > EOF
474 474 $ hg strip -r 11:15
475 475 saved backup bundle to $TESTTMP/t/.hg/strip-backup/*-backup.hg (glob)
476 476
477 477 clone
478 478
479 479 $ cd ..
480 480 $ hg clone t tc
481 481 updating to branch default
482 482 cloning subrepo s from $TESTTMP/t/s
483 483 cloning subrepo s/ss from $TESTTMP/t/s/ss
484 484 cloning subrepo t from $TESTTMP/t/t
485 485 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
486 486 $ cd tc
487 487 $ hg debugsub
488 488 path s
489 489 source s
490 490 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
491 491 path t
492 492 source t
493 493 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
494 494 $ cd ..
495 495
496 496 clone with subrepo disabled (update should fail)
497 497
498 498 $ hg clone t -U tc2 --config subrepos.allowed=false
499 499 $ hg update -R tc2 --config subrepos.allowed=false
500 500 abort: subrepos not enabled
501 501 (see 'hg help config.subrepos' for details)
502 502 [255]
503 503 $ ls tc2
504 504 a
505 505
506 506 $ hg clone t tc3 --config subrepos.allowed=false
507 507 updating to branch default
508 508 abort: subrepos not enabled
509 509 (see 'hg help config.subrepos' for details)
510 510 [255]
511 511 $ ls tc3
512 512 a
513 513
514 514 And again with just the hg type disabled
515 515
516 516 $ hg clone t -U tc4 --config subrepos.hg:allowed=false
517 517 $ hg update -R tc4 --config subrepos.hg:allowed=false
518 518 abort: hg subrepos not allowed
519 519 (see 'hg help config.subrepos' for details)
520 520 [255]
521 521 $ ls tc4
522 522 a
523 523
524 524 $ hg clone t tc5 --config subrepos.hg:allowed=false
525 525 updating to branch default
526 526 abort: hg subrepos not allowed
527 527 (see 'hg help config.subrepos' for details)
528 528 [255]
529 529 $ ls tc5
530 530 a
531 531
532 532 push
533 533
534 534 $ cd tc
535 535 $ echo bah > t/t
536 536 $ hg ci -m11
537 537 committing subrepository t
538 538 $ hg push
539 539 pushing to $TESTTMP/t
540 540 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
541 541 no changes made to subrepo s since last push to $TESTTMP/t/s
542 542 pushing subrepo t to $TESTTMP/t/t
543 543 searching for changes
544 544 adding changesets
545 545 adding manifests
546 546 adding file changes
547 547 added 1 changesets with 1 changes to 1 files
548 548 searching for changes
549 549 adding changesets
550 550 adding manifests
551 551 adding file changes
552 552 added 1 changesets with 1 changes to 1 files
553 553
554 554 push -f
555 555
556 556 $ echo bah > s/a
557 557 $ hg ci -m12
558 558 committing subrepository s
559 559 $ hg push
560 560 pushing to $TESTTMP/t
561 561 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
562 562 pushing subrepo s to $TESTTMP/t/s
563 563 searching for changes
564 564 abort: push creates new remote head 12a213df6fa9! (in subrepository "s")
565 565 (merge or see 'hg help push' for details about pushing new heads)
566 566 [255]
567 567 $ hg push -f
568 568 pushing to $TESTTMP/t
569 569 pushing subrepo s/ss to $TESTTMP/t/s/ss
570 570 searching for changes
571 571 no changes found
572 572 pushing subrepo s to $TESTTMP/t/s
573 573 searching for changes
574 574 adding changesets
575 575 adding manifests
576 576 adding file changes
577 577 added 1 changesets with 1 changes to 1 files (+1 heads)
578 578 pushing subrepo t to $TESTTMP/t/t
579 579 searching for changes
580 580 no changes found
581 581 searching for changes
582 582 adding changesets
583 583 adding manifests
584 584 adding file changes
585 585 added 1 changesets with 1 changes to 1 files
586 586
587 587 check that unmodified subrepos are not pushed
588 588
589 589 $ hg clone . ../tcc
590 590 updating to branch default
591 591 cloning subrepo s from $TESTTMP/tc/s
592 592 cloning subrepo s/ss from $TESTTMP/tc/s/ss
593 593 cloning subrepo t from $TESTTMP/tc/t
594 594 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
595 595
596 596 the subrepos on the new clone have nothing to push to its source
597 597
598 598 $ hg push -R ../tcc .
599 599 pushing to .
600 600 no changes made to subrepo s/ss since last push to s/ss
601 601 no changes made to subrepo s since last push to s
602 602 no changes made to subrepo t since last push to t
603 603 searching for changes
604 604 no changes found
605 605 [1]
606 606
607 607 the subrepos on the source do not have a clean store versus the clone target
608 608 because they were never explicitly pushed to the source
609 609
610 610 $ hg push ../tcc
611 611 pushing to ../tcc
612 612 pushing subrepo s/ss to ../tcc/s/ss
613 613 searching for changes
614 614 no changes found
615 615 pushing subrepo s to ../tcc/s
616 616 searching for changes
617 617 no changes found
618 618 pushing subrepo t to ../tcc/t
619 619 searching for changes
620 620 no changes found
621 621 searching for changes
622 622 no changes found
623 623 [1]
624 624
625 625 after push their stores become clean
626 626
627 627 $ hg push ../tcc
628 628 pushing to ../tcc
629 629 no changes made to subrepo s/ss since last push to ../tcc/s/ss
630 630 no changes made to subrepo s since last push to ../tcc/s
631 631 no changes made to subrepo t since last push to ../tcc/t
632 632 searching for changes
633 633 no changes found
634 634 [1]
635 635
636 636 updating a subrepo to a different revision or changing
637 637 its working directory does not make its store dirty
638 638
639 639 $ hg -R s update '.^'
640 640 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
641 641 $ hg push
642 642 pushing to $TESTTMP/t
643 643 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
644 644 no changes made to subrepo s since last push to $TESTTMP/t/s
645 645 no changes made to subrepo t since last push to $TESTTMP/t/t
646 646 searching for changes
647 647 no changes found
648 648 [1]
649 649 $ echo foo >> s/a
650 650 $ hg push
651 651 pushing to $TESTTMP/t
652 652 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
653 653 no changes made to subrepo s since last push to $TESTTMP/t/s
654 654 no changes made to subrepo t since last push to $TESTTMP/t/t
655 655 searching for changes
656 656 no changes found
657 657 [1]
658 658 $ hg -R s update -C tip
659 659 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
660 660
661 661 committing into a subrepo makes its store (but not its parent's store) dirty
662 662
663 663 $ echo foo >> s/ss/a
664 664 $ hg -R s/ss commit -m 'test dirty store detection'
665 665
666 666 $ hg out -S -r `hg log -r tip -T "{node|short}"`
667 667 comparing with $TESTTMP/t
668 668 searching for changes
669 669 no changes found
670 670 comparing with $TESTTMP/t/s
671 671 searching for changes
672 672 no changes found
673 673 comparing with $TESTTMP/t/s/ss
674 674 searching for changes
675 675 changeset: 1:79ea5566a333
676 676 tag: tip
677 677 user: test
678 678 date: Thu Jan 01 00:00:00 1970 +0000
679 679 summary: test dirty store detection
680 680
681 681 comparing with $TESTTMP/t/t
682 682 searching for changes
683 683 no changes found
684 684
685 685 $ hg push
686 686 pushing to $TESTTMP/t
687 687 pushing subrepo s/ss to $TESTTMP/t/s/ss
688 688 searching for changes
689 689 adding changesets
690 690 adding manifests
691 691 adding file changes
692 692 added 1 changesets with 1 changes to 1 files
693 693 no changes made to subrepo s since last push to $TESTTMP/t/s
694 694 no changes made to subrepo t since last push to $TESTTMP/t/t
695 695 searching for changes
696 696 no changes found
697 697 [1]
698 698
699 699 a subrepo store may be clean versus one repo but not versus another
700 700
701 701 $ hg push
702 702 pushing to $TESTTMP/t
703 703 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
704 704 no changes made to subrepo s since last push to $TESTTMP/t/s
705 705 no changes made to subrepo t since last push to $TESTTMP/t/t
706 706 searching for changes
707 707 no changes found
708 708 [1]
709 709 $ hg push ../tcc
710 710 pushing to ../tcc
711 711 pushing subrepo s/ss to ../tcc/s/ss
712 712 searching for changes
713 713 adding changesets
714 714 adding manifests
715 715 adding file changes
716 716 added 1 changesets with 1 changes to 1 files
717 717 no changes made to subrepo s since last push to ../tcc/s
718 718 no changes made to subrepo t since last push to ../tcc/t
719 719 searching for changes
720 720 no changes found
721 721 [1]
722 722
723 723 update
724 724
725 725 $ cd ../t
726 726 $ hg up -C # discard our earlier merge
727 727 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
728 728 updated to "c373c8102e68: 12"
729 729 2 other heads for branch "default"
730 730 $ echo blah > t/t
731 731 $ hg ci -m13
732 732 committing subrepository t
733 733
734 734 backout calls revert internally with minimal opts, which should not raise
735 735 KeyError
736 736
737 737 $ hg backout ".^" --no-commit
738 738 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
739 739 changeset c373c8102e68 backed out, don't forget to commit.
740 740
741 741 $ hg up -C # discard changes
742 742 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
743 743 updated to "925c17564ef8: 13"
744 744 2 other heads for branch "default"
745 745
746 746 pull
747 747
748 748 $ cd ../tc
749 749 $ hg pull
750 750 pulling from $TESTTMP/t
751 751 searching for changes
752 752 adding changesets
753 753 adding manifests
754 754 adding file changes
755 755 added 1 changesets with 1 changes to 1 files
756 756 new changesets 925c17564ef8
757 757 (run 'hg update' to get a working copy)
758 758
759 759 should pull t
760 760
761 761 $ hg incoming -S -r `hg log -r tip -T "{node|short}"`
762 762 comparing with $TESTTMP/t
763 763 no changes found
764 764 comparing with $TESTTMP/t/s
765 765 searching for changes
766 766 no changes found
767 767 comparing with $TESTTMP/t/s/ss
768 768 searching for changes
769 769 no changes found
770 770 comparing with $TESTTMP/t/t
771 771 searching for changes
772 772 changeset: 5:52c0adc0515a
773 773 tag: tip
774 774 user: test
775 775 date: Thu Jan 01 00:00:00 1970 +0000
776 776 summary: 13
777 777
778 778
779 779 $ hg up
780 780 pulling subrepo t from $TESTTMP/t/t
781 781 searching for changes
782 782 adding changesets
783 783 adding manifests
784 784 adding file changes
785 785 added 1 changesets with 1 changes to 1 files
786 786 new changesets 52c0adc0515a
787 787 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
788 788 updated to "925c17564ef8: 13"
789 789 2 other heads for branch "default"
790 790 $ cat t/t
791 791 blah
792 792
793 793 bogus subrepo path aborts
794 794
795 795 $ echo 'bogus=[boguspath' >> .hgsub
796 796 $ hg ci -m 'bogus subrepo path'
797 797 abort: missing ] in subrepository source
798 798 [255]
799 799
800 800 Issue1986: merge aborts when trying to merge a subrepo that
801 801 shouldn't need merging
802 802
803 803 # subrepo layout
804 804 #
805 805 # o 5 br
806 806 # /|
807 807 # o | 4 default
808 808 # | |
809 809 # | o 3 br
810 810 # |/|
811 811 # o | 2 default
812 812 # | |
813 813 # | o 1 br
814 814 # |/
815 815 # o 0 default
816 816
817 817 $ cd ..
818 818 $ rm -rf sub
819 819 $ hg init main
820 820 $ cd main
821 821 $ hg init s
822 822 $ cd s
823 823 $ echo a > a
824 824 $ hg ci -Am1
825 825 adding a
826 826 $ hg branch br
827 827 marked working directory as branch br
828 828 (branches are permanent and global, did you want a bookmark?)
829 829 $ echo a >> a
830 830 $ hg ci -m1
831 831 $ hg up default
832 832 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
833 833 $ echo b > b
834 834 $ hg ci -Am1
835 835 adding b
836 836 $ hg up br
837 837 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
838 838 $ hg merge tip
839 839 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
840 840 (branch merge, don't forget to commit)
841 841 $ hg ci -m1
842 842 $ hg up 2
843 843 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
844 844 $ echo c > c
845 845 $ hg ci -Am1
846 846 adding c
847 847 $ hg up 3
848 848 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
849 849 $ hg merge 4
850 850 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
851 851 (branch merge, don't forget to commit)
852 852 $ hg ci -m1
853 853
854 854 # main repo layout:
855 855 #
856 856 # * <-- try to merge default into br again
857 857 # .`|
858 858 # . o 5 br --> substate = 5
859 859 # . |
860 860 # o | 4 default --> substate = 4
861 861 # | |
862 862 # | o 3 br --> substate = 2
863 863 # |/|
864 864 # o | 2 default --> substate = 2
865 865 # | |
866 866 # | o 1 br --> substate = 3
867 867 # |/
868 868 # o 0 default --> substate = 2
869 869
870 870 $ cd ..
871 871 $ echo 's = s' > .hgsub
872 872 $ hg -R s up 2
873 873 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
874 874 $ hg ci -Am1
875 875 adding .hgsub
876 876 $ hg branch br
877 877 marked working directory as branch br
878 878 (branches are permanent and global, did you want a bookmark?)
879 879 $ echo b > b
880 880 $ hg -R s up 3
881 881 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
882 882 $ hg ci -Am1
883 883 adding b
884 884 $ hg up default
885 885 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
886 886 $ echo c > c
887 887 $ hg ci -Am1
888 888 adding c
889 889 $ hg up 1
890 890 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
891 891 $ hg merge 2
892 892 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
893 893 (branch merge, don't forget to commit)
894 894 $ hg ci -m1
895 895 $ hg up 2
896 896 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
897 897 $ hg -R s up 4
898 898 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
899 899 $ echo d > d
900 900 $ hg ci -Am1
901 901 adding d
902 902 $ hg up 3
903 903 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
904 904 $ hg -R s up 5
905 905 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
906 906 $ echo e > e
907 907 $ hg ci -Am1
908 908 adding e
909 909
910 910 $ hg up 5
911 911 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
912 912 $ hg merge 4 # try to merge default into br again
913 913 subrepository s diverged (local revision: f8f13b33206e, remote revision: a3f9062a4f88)
914 914 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
915 915 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
916 916 (branch merge, don't forget to commit)
917 917 $ cd ..
918 918
919 919 test subrepo delete from .hgsubstate
920 920
921 921 $ hg init testdelete
922 922 $ mkdir testdelete/nested testdelete/nested2
923 923 $ hg init testdelete/nested
924 924 $ hg init testdelete/nested2
925 925 $ echo test > testdelete/nested/foo
926 926 $ echo test > testdelete/nested2/foo
927 927 $ hg -R testdelete/nested add
928 928 adding testdelete/nested/foo
929 929 $ hg -R testdelete/nested2 add
930 930 adding testdelete/nested2/foo
931 931 $ hg -R testdelete/nested ci -m test
932 932 $ hg -R testdelete/nested2 ci -m test
933 933 $ echo nested = nested > testdelete/.hgsub
934 934 $ echo nested2 = nested2 >> testdelete/.hgsub
935 935 $ hg -R testdelete add
936 936 adding testdelete/.hgsub
937 937 $ hg -R testdelete ci -m "nested 1 & 2 added"
938 938 $ echo nested = nested > testdelete/.hgsub
939 939 $ hg -R testdelete ci -m "nested 2 deleted"
940 940 $ cat testdelete/.hgsubstate
941 941 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
942 942 $ hg -R testdelete remove testdelete/.hgsub
943 943 $ hg -R testdelete ci -m ".hgsub deleted"
944 944 $ cat testdelete/.hgsubstate
945 945 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
946 946
947 947 test repository cloning
948 948
949 949 $ mkdir mercurial mercurial2
950 950 $ hg init nested_absolute
951 951 $ echo test > nested_absolute/foo
952 952 $ hg -R nested_absolute add
953 953 adding nested_absolute/foo
954 954 $ hg -R nested_absolute ci -mtest
955 955 $ cd mercurial
956 956 $ hg init nested_relative
957 957 $ echo test2 > nested_relative/foo2
958 958 $ hg -R nested_relative add
959 959 adding nested_relative/foo2
960 960 $ hg -R nested_relative ci -mtest2
961 961 $ hg init main
962 962 $ echo "nested_relative = ../nested_relative" > main/.hgsub
963 963 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
964 964 $ hg -R main add
965 965 adding main/.hgsub
966 966 $ hg -R main ci -m "add subrepos"
967 967 $ cd ..
968 968 $ hg clone mercurial/main mercurial2/main
969 969 updating to branch default
970 970 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
971 971 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
972 972 > mercurial2/main/nested_relative/.hg/hgrc
973 973 [paths]
974 974 default = $TESTTMP/mercurial/nested_absolute
975 975 [paths]
976 976 default = $TESTTMP/mercurial/nested_relative
977 977 $ rm -rf mercurial mercurial2
978 978
979 979 Issue1977: multirepo push should fail if subrepo push fails
980 980
981 981 $ hg init repo
982 982 $ hg init repo/s
983 983 $ echo a > repo/s/a
984 984 $ hg -R repo/s ci -Am0
985 985 adding a
986 986 $ echo s = s > repo/.hgsub
987 987 $ hg -R repo ci -Am1
988 988 adding .hgsub
989 989 $ hg clone repo repo2
990 990 updating to branch default
991 991 cloning subrepo s from $TESTTMP/repo/s
992 992 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
993 993 $ hg -q -R repo2 pull -u
994 994 $ echo 1 > repo2/s/a
995 995 $ hg -R repo2/s ci -m2
996 996 $ hg -q -R repo2/s push
997 997 $ hg -R repo2/s up -C 0
998 998 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
999 999 $ echo 2 > repo2/s/b
1000 1000 $ hg -R repo2/s ci -m3 -A
1001 1001 adding b
1002 1002 created new head
1003 1003 $ hg -R repo2 ci -m3
1004 1004 $ hg -q -R repo2 push
1005 1005 abort: push creates new remote head cc505f09a8b2! (in subrepository "s")
1006 1006 (merge or see 'hg help push' for details about pushing new heads)
1007 1007 [255]
1008 1008 $ hg -R repo update
1009 1009 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1010 1010
1011 1011 test if untracked file is not overwritten
1012 1012
1013 1013 (this also tests that updated .hgsubstate is treated as "modified",
1014 1014 when 'merge.update()' is aborted before 'merge.recordupdates()', even
1015 1015 if none of mode, size and timestamp of it isn't changed on the
1016 1016 filesystem (see also issue4583))
1017 1017
1018 1018 $ echo issue3276_ok > repo/s/b
1019 1019 $ hg -R repo2 push -f -q
1020 1020 $ touch -t 200001010000 repo/.hgsubstate
1021 1021
1022 1022 $ cat >> repo/.hg/hgrc <<EOF
1023 1023 > [fakedirstatewritetime]
1024 1024 > # emulate invoking dirstate.write() via repo.status()
1025 1025 > # at 2000-01-01 00:00
1026 1026 > fakenow = 200001010000
1027 1027 >
1028 1028 > [extensions]
1029 1029 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
1030 1030 > EOF
1031 1031 $ hg -R repo update
1032 1032 b: untracked file differs
1033 1033 abort: untracked files in working directory differ from files in requested revision (in subrepository "s")
1034 1034 [255]
1035 1035 $ cat >> repo/.hg/hgrc <<EOF
1036 1036 > [extensions]
1037 1037 > fakedirstatewritetime = !
1038 1038 > EOF
1039 1039
1040 1040 $ cat repo/s/b
1041 1041 issue3276_ok
1042 1042 $ rm repo/s/b
1043 1043 $ touch -t 200001010000 repo/.hgsubstate
1044 1044 $ hg -R repo revert --all
1045 1045 reverting repo/.hgsubstate
1046 1046 reverting subrepo s
1047 1047 $ hg -R repo update
1048 1048 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1049 1049 $ cat repo/s/b
1050 1050 2
1051 1051 $ rm -rf repo2 repo
1052 1052
1053 1053
1054 1054 Issue1852 subrepos with relative paths always push/pull relative to default
1055 1055
1056 1056 Prepare a repo with subrepo
1057 1057
1058 1058 $ hg init issue1852a
1059 1059 $ cd issue1852a
1060 1060 $ hg init sub/repo
1061 1061 $ echo test > sub/repo/foo
1062 1062 $ hg -R sub/repo add sub/repo/foo
1063 1063 $ echo sub/repo = sub/repo > .hgsub
1064 1064 $ hg add .hgsub
1065 1065 $ hg ci -mtest
1066 1066 committing subrepository sub/repo
1067 1067 $ echo test >> sub/repo/foo
1068 1068 $ hg ci -mtest
1069 1069 committing subrepository sub/repo
1070 1070 $ hg cat sub/repo/foo
1071 1071 test
1072 1072 test
1073 1073 $ hg cat sub/repo/foo -Tjson | sed 's|\\\\|/|g'
1074 1074 [
1075 1075 {
1076 1076 "data": "test\ntest\n",
1077 1077 "path": "foo"
1078 1078 }
1079 1079 ]
1080 1080
1081 1081 non-exact match:
1082 1082
1083 1083 $ hg cat -T '{path|relpath}\n' 'glob:**'
1084 1084 .hgsub
1085 1085 .hgsubstate
1086 1086 sub/repo/foo
1087 1087 $ hg cat -T '{path|relpath}\n' 're:^sub'
1088 1088 sub/repo/foo
1089 1089
1090 1090 missing subrepos in working directory:
1091 1091
1092 1092 $ mkdir -p tmp/sub/repo
1093 1093 $ hg cat -r 0 --output tmp/%p_p sub/repo/foo
1094 1094 $ cat tmp/sub/repo/foo_p
1095 1095 test
1096 1096 $ mv sub/repo sub_
1097 1097 $ hg cat sub/repo/baz
1098 1098 skipping missing subrepository: sub/repo
1099 1099 [1]
1100 1100 $ rm -rf sub/repo
1101 1101 $ mv sub_ sub/repo
1102 1102 $ cd ..
1103 1103
1104 1104 Create repo without default path, pull top repo, and see what happens on update
1105 1105
1106 1106 $ hg init issue1852b
1107 1107 $ hg -R issue1852b pull issue1852a
1108 1108 pulling from issue1852a
1109 1109 requesting all changes
1110 1110 adding changesets
1111 1111 adding manifests
1112 1112 adding file changes
1113 1113 added 2 changesets with 3 changes to 2 files
1114 1114 new changesets 19487b456929:be5eb94e7215
1115 1115 (run 'hg update' to get a working copy)
1116 1116 $ hg -R issue1852b update
1117 1117 abort: default path for subrepository not found (in subrepository "sub/repo")
1118 1118 [255]
1119 1119
1120 1120 Ensure a full traceback, not just the SubrepoAbort part
1121 1121
1122 1122 $ hg -R issue1852b update --traceback 2>&1 | grep 'raise error\.Abort'
1123 1123 raise error.Abort(_("default path for subrepository not found"))
1124 1124
1125 1125 Pull -u now doesn't help
1126 1126
1127 1127 $ hg -R issue1852b pull -u issue1852a
1128 1128 pulling from issue1852a
1129 1129 searching for changes
1130 1130 no changes found
1131 1131
1132 1132 Try the same, but with pull -u
1133 1133
1134 1134 $ hg init issue1852c
1135 1135 $ hg -R issue1852c pull -r0 -u issue1852a
1136 1136 pulling from issue1852a
1137 1137 adding changesets
1138 1138 adding manifests
1139 1139 adding file changes
1140 1140 added 1 changesets with 2 changes to 2 files
1141 1141 new changesets 19487b456929
1142 1142 cloning subrepo sub/repo from issue1852a/sub/repo
1143 1143 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1144 1144
1145 1145 Try to push from the other side
1146 1146
1147 1147 $ hg -R issue1852a push `pwd`/issue1852c
1148 1148 pushing to $TESTTMP/issue1852c
1149 1149 pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo
1150 1150 searching for changes
1151 1151 no changes found
1152 1152 searching for changes
1153 1153 adding changesets
1154 1154 adding manifests
1155 1155 adding file changes
1156 1156 added 1 changesets with 1 changes to 1 files
1157 1157
1158 1158 Incoming and outgoing should not use the default path:
1159 1159
1160 1160 $ hg clone -q issue1852a issue1852d
1161 1161 $ hg -R issue1852d outgoing --subrepos issue1852c
1162 1162 comparing with issue1852c
1163 1163 searching for changes
1164 1164 no changes found
1165 1165 comparing with issue1852c/sub/repo
1166 1166 searching for changes
1167 1167 no changes found
1168 1168 [1]
1169 1169 $ hg -R issue1852d incoming --subrepos issue1852c
1170 1170 comparing with issue1852c
1171 1171 searching for changes
1172 1172 no changes found
1173 1173 comparing with issue1852c/sub/repo
1174 1174 searching for changes
1175 1175 no changes found
1176 1176 [1]
1177 1177
1178 1178 Check that merge of a new subrepo doesn't write the uncommitted state to
1179 1179 .hgsubstate (issue4622)
1180 1180
1181 1181 $ hg init issue1852a/addedsub
1182 1182 $ echo zzz > issue1852a/addedsub/zz.txt
1183 1183 $ hg -R issue1852a/addedsub ci -Aqm "initial ZZ"
1184 1184
1185 1185 $ hg clone issue1852a/addedsub issue1852d/addedsub
1186 1186 updating to branch default
1187 1187 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1188 1188
1189 1189 $ echo def > issue1852a/sub/repo/foo
1190 1190 $ hg -R issue1852a ci -SAm 'tweaked subrepo'
1191 1191 adding tmp/sub/repo/foo_p
1192 1192 committing subrepository sub/repo
1193 1193
1194 1194 $ echo 'addedsub = addedsub' >> issue1852d/.hgsub
1195 1195 $ echo xyz > issue1852d/sub/repo/foo
1196 1196 $ hg -R issue1852d pull -u
1197 1197 pulling from $TESTTMP/issue1852a
1198 1198 searching for changes
1199 1199 adding changesets
1200 1200 adding manifests
1201 1201 adding file changes
1202 1202 added 1 changesets with 2 changes to 2 files
1203 1203 new changesets c82b79fdcc5b
1204 1204 subrepository sub/repo diverged (local revision: f42d5c7504a8, remote revision: 46cd4aac504c)
1205 1205 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1206 1206 pulling subrepo sub/repo from $TESTTMP/issue1852a/sub/repo
1207 1207 searching for changes
1208 1208 adding changesets
1209 1209 adding manifests
1210 1210 adding file changes
1211 1211 added 1 changesets with 1 changes to 1 files
1212 1212 new changesets 46cd4aac504c
1213 1213 subrepository sources for sub/repo differ
1214 1214 use (l)ocal source (f42d5c7504a8) or (r)emote source (46cd4aac504c)? l
1215 1215 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1216 1216 $ cat issue1852d/.hgsubstate
1217 1217 f42d5c7504a811dda50f5cf3e5e16c3330b87172 sub/repo
1218 1218
1219 1219 Check status of files when none of them belong to the first
1220 1220 subrepository:
1221 1221
1222 1222 $ hg init subrepo-status
1223 1223 $ cd subrepo-status
1224 1224 $ hg init subrepo-1
1225 1225 $ hg init subrepo-2
1226 1226 $ cd subrepo-2
1227 1227 $ touch file
1228 1228 $ hg add file
1229 1229 $ cd ..
1230 1230 $ echo subrepo-1 = subrepo-1 > .hgsub
1231 1231 $ echo subrepo-2 = subrepo-2 >> .hgsub
1232 1232 $ hg add .hgsub
1233 1233 $ hg ci -m 'Added subrepos'
1234 1234 committing subrepository subrepo-2
1235 1235 $ hg st subrepo-2/file
1236 1236
1237 1237 Check that share works with subrepo
1238 1238 $ hg --config extensions.share= share . ../shared
1239 1239 updating working directory
1240 1240 sharing subrepo subrepo-1 from $TESTTMP/subrepo-status/subrepo-1
1241 1241 sharing subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
1242 1242 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1243 1243 $ find ../shared/* | sort
1244 1244 ../shared/subrepo-1
1245 1245 ../shared/subrepo-1/.hg
1246 1246 ../shared/subrepo-1/.hg/cache
1247 1247 ../shared/subrepo-1/.hg/cache/storehash
1248 1248 ../shared/subrepo-1/.hg/cache/storehash/* (glob)
1249 1249 ../shared/subrepo-1/.hg/hgrc
1250 1250 ../shared/subrepo-1/.hg/requires
1251 1251 ../shared/subrepo-1/.hg/sharedpath
1252 1252 ../shared/subrepo-1/.hg/wcache
1253 1253 ../shared/subrepo-2
1254 1254 ../shared/subrepo-2/.hg
1255 1255 ../shared/subrepo-2/.hg/branch
1256 1256 ../shared/subrepo-2/.hg/cache
1257 1257 ../shared/subrepo-2/.hg/cache/storehash
1258 1258 ../shared/subrepo-2/.hg/cache/storehash/* (glob)
1259 1259 ../shared/subrepo-2/.hg/dirstate
1260 1260 ../shared/subrepo-2/.hg/hgrc
1261 1261 ../shared/subrepo-2/.hg/requires
1262 1262 ../shared/subrepo-2/.hg/sharedpath
1263 1263 ../shared/subrepo-2/.hg/wcache
1264 1264 ../shared/subrepo-2/.hg/wcache/checkisexec (execbit !)
1265 1265 ../shared/subrepo-2/.hg/wcache/checklink (symlink !)
1266 1266 ../shared/subrepo-2/.hg/wcache/checklink-target (symlink !)
1267 1267 ../shared/subrepo-2/file
1268 1268 $ hg -R ../shared in
1269 1269 abort: repository default not found!
1270 1270 [255]
1271 1271 $ hg -R ../shared/subrepo-2 showconfig paths
1272 1272 paths.default=$TESTTMP/subrepo-status/subrepo-2
1273 1273 $ hg -R ../shared/subrepo-1 sum --remote
1274 1274 parent: -1:000000000000 tip (empty repository)
1275 1275 branch: default
1276 1276 commit: (clean)
1277 1277 update: (current)
1278 1278 remote: (synced)
1279 1279
1280 1280 Check hg update --clean
1281 1281 $ cd $TESTTMP/t
1282 1282 $ rm -r t/t.orig
1283 1283 $ hg status -S --all
1284 1284 C .hgsub
1285 1285 C .hgsubstate
1286 1286 C a
1287 1287 C s/.hgsub
1288 1288 C s/.hgsubstate
1289 1289 C s/a
1290 1290 C s/ss/a
1291 1291 C t/t
1292 1292 $ echo c1 > s/a
1293 1293 $ cd s
1294 1294 $ echo c1 > b
1295 1295 $ echo c1 > c
1296 1296 $ hg add b
1297 1297 $ cd ..
1298 1298 $ hg status -S
1299 1299 M s/a
1300 1300 A s/b
1301 1301 ? s/c
1302 1302 $ hg update -C
1303 1303 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1304 1304 updated to "925c17564ef8: 13"
1305 1305 2 other heads for branch "default"
1306 1306 $ hg status -S
1307 1307 ? s/b
1308 1308 ? s/c
1309 1309
1310 1310 Sticky subrepositories, no changes
1311 1311 $ cd $TESTTMP/t
1312 1312 $ hg id
1313 1313 925c17564ef8 tip
1314 1314 $ hg -R s id
1315 1315 12a213df6fa9 tip
1316 1316 $ hg -R t id
1317 1317 52c0adc0515a tip
1318 1318 $ hg update 11
1319 1319 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1320 1320 $ hg id
1321 1321 365661e5936a
1322 1322 $ hg -R s id
1323 1323 fc627a69481f
1324 1324 $ hg -R t id
1325 1325 e95bcfa18a35
1326 1326
1327 1327 Sticky subrepositories, file changes
1328 1328 $ touch s/f1
1329 1329 $ touch t/f1
1330 1330 $ hg add -S s/f1
1331 1331 $ hg add -S t/f1
1332 1332 $ hg id
1333 1333 365661e5936a+
1334 1334 $ hg -R s id
1335 1335 fc627a69481f+
1336 1336 $ hg -R t id
1337 1337 e95bcfa18a35+
1338 1338 $ hg update tip
1339 1339 subrepository s diverged (local revision: fc627a69481f, remote revision: 12a213df6fa9)
1340 1340 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1341 1341 subrepository sources for s differ
1342 1342 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)? l
1343 1343 subrepository t diverged (local revision: e95bcfa18a35, remote revision: 52c0adc0515a)
1344 1344 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1345 1345 subrepository sources for t differ
1346 1346 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)? l
1347 1347 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1348 1348 $ hg id
1349 1349 925c17564ef8+ tip
1350 1350 $ hg -R s id
1351 1351 fc627a69481f+
1352 1352 $ hg -R t id
1353 1353 e95bcfa18a35+
1354 1354 $ hg update --clean tip
1355 1355 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1356 1356
1357 1357 Sticky subrepository, revision updates
1358 1358 $ hg id
1359 1359 925c17564ef8 tip
1360 1360 $ hg -R s id
1361 1361 12a213df6fa9 tip
1362 1362 $ hg -R t id
1363 1363 52c0adc0515a tip
1364 1364 $ cd s
1365 1365 $ hg update -r -2
1366 1366 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1367 1367 $ cd ../t
1368 1368 $ hg update -r 2
1369 1369 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1370 1370 $ cd ..
1371 1371 $ hg update 10
1372 1372 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1373 1373 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1374 1374 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 20a0db6fbf6c)
1375 1375 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1376 1376 subrepository sources for t differ (in checked out version)
1377 1377 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)? l
1378 1378 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1379 1379 $ hg id
1380 1380 e45c8b14af55+
1381 1381 $ hg -R s id
1382 1382 02dcf1d70411
1383 1383 $ hg -R t id
1384 1384 7af322bc1198
1385 1385
1386 1386 Sticky subrepository, file changes and revision updates
1387 1387 $ touch s/f1
1388 1388 $ touch t/f1
1389 1389 $ hg add -S s/f1
1390 1390 $ hg add -S t/f1
1391 1391 $ hg id
1392 1392 e45c8b14af55+
1393 1393 $ hg -R s id
1394 1394 02dcf1d70411+
1395 1395 $ hg -R t id
1396 1396 7af322bc1198+
1397 1397 $ hg update tip
1398 1398 subrepository s diverged (local revision: 12a213df6fa9, remote revision: 12a213df6fa9)
1399 1399 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1400 1400 subrepository sources for s differ
1401 1401 use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)? l
1402 1402 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 52c0adc0515a)
1403 1403 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1404 1404 subrepository sources for t differ
1405 1405 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)? l
1406 1406 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1407 1407 $ hg id
1408 1408 925c17564ef8+ tip
1409 1409 $ hg -R s id
1410 1410 02dcf1d70411+
1411 1411 $ hg -R t id
1412 1412 7af322bc1198+
1413 1413
1414 1414 Sticky repository, update --clean
1415 1415 $ hg update --clean tip
1416 1416 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1417 1417 $ hg id
1418 1418 925c17564ef8 tip
1419 1419 $ hg -R s id
1420 1420 12a213df6fa9 tip
1421 1421 $ hg -R t id
1422 1422 52c0adc0515a tip
1423 1423
1424 1424 Test subrepo already at intended revision:
1425 1425 $ cd s
1426 1426 $ hg update fc627a69481f
1427 1427 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1428 1428 $ cd ..
1429 1429 $ hg update 11
1430 1430 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1431 1431 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1432 1432 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1433 1433 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1434 1434 $ hg id -n
1435 1435 11+
1436 1436 $ hg -R s id
1437 1437 fc627a69481f
1438 1438 $ hg -R t id
1439 1439 e95bcfa18a35
1440 1440
1441 1441 Test that removing .hgsubstate doesn't break anything:
1442 1442
1443 1443 $ hg rm -f .hgsubstate
1444 1444 $ hg ci -mrm
1445 1445 nothing changed
1446 1446 [1]
1447 1447 $ hg log -vr tip
1448 1448 changeset: 13:925c17564ef8
1449 1449 tag: tip
1450 1450 user: test
1451 1451 date: Thu Jan 01 00:00:00 1970 +0000
1452 1452 files: .hgsubstate
1453 1453 description:
1454 1454 13
1455 1455
1456 1456
1457 1457
1458 1458 Test that removing .hgsub removes .hgsubstate:
1459 1459
1460 1460 $ hg rm .hgsub
1461 1461 $ hg ci -mrm2
1462 1462 created new head
1463 1463 $ hg log -vr tip
1464 1464 changeset: 14:2400bccd50af
1465 1465 tag: tip
1466 1466 parent: 11:365661e5936a
1467 1467 user: test
1468 1468 date: Thu Jan 01 00:00:00 1970 +0000
1469 1469 files: .hgsub .hgsubstate
1470 1470 description:
1471 1471 rm2
1472 1472
1473 1473
1474 1474 Test issue3153: diff -S with deleted subrepos
1475 1475
1476 1476 $ hg diff --nodates -S -c .
1477 1477 diff -r 365661e5936a -r 2400bccd50af .hgsub
1478 1478 --- a/.hgsub
1479 1479 +++ /dev/null
1480 1480 @@ -1,2 +0,0 @@
1481 1481 -s = s
1482 1482 -t = t
1483 1483 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
1484 1484 --- a/.hgsubstate
1485 1485 +++ /dev/null
1486 1486 @@ -1,2 +0,0 @@
1487 1487 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1488 1488 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
1489 1489
1490 1490 Test behavior of add for explicit path in subrepo:
1491 1491 $ cd ..
1492 1492 $ hg init explicit
1493 1493 $ cd explicit
1494 1494 $ echo s = s > .hgsub
1495 1495 $ hg add .hgsub
1496 1496 $ hg init s
1497 1497 $ hg ci -m0
1498 1498 Adding with an explicit path in a subrepo adds the file
1499 1499 $ echo c1 > f1
1500 1500 $ echo c2 > s/f2
1501 1501 $ hg st -S
1502 1502 ? f1
1503 1503 ? s/f2
1504 1504 $ hg add s/f2
1505 1505 $ hg st -S
1506 1506 A s/f2
1507 1507 ? f1
1508 1508 $ hg ci -R s -m0
1509 1509 $ hg ci -Am1
1510 1510 adding f1
1511 1511 Adding with an explicit path in a subrepo with -S has the same behavior
1512 1512 $ echo c3 > f3
1513 1513 $ echo c4 > s/f4
1514 1514 $ hg st -S
1515 1515 ? f3
1516 1516 ? s/f4
1517 1517 $ hg add -S s/f4
1518 1518 $ hg st -S
1519 1519 A s/f4
1520 1520 ? f3
1521 1521 $ hg ci -R s -m1
1522 1522 $ hg ci -Ama2
1523 1523 adding f3
1524 1524 Adding without a path or pattern silently ignores subrepos
1525 1525 $ echo c5 > f5
1526 1526 $ echo c6 > s/f6
1527 1527 $ echo c7 > s/f7
1528 1528 $ hg st -S
1529 1529 ? f5
1530 1530 ? s/f6
1531 1531 ? s/f7
1532 1532 $ hg add
1533 1533 adding f5
1534 1534 $ hg st -S
1535 1535 A f5
1536 1536 ? s/f6
1537 1537 ? s/f7
1538 1538 $ hg ci -R s -Am2
1539 1539 adding f6
1540 1540 adding f7
1541 1541 $ hg ci -m3
1542 1542 Adding without a path or pattern with -S also adds files in subrepos
1543 1543 $ echo c8 > f8
1544 1544 $ echo c9 > s/f9
1545 1545 $ echo c10 > s/f10
1546 1546 $ hg st -S
1547 1547 ? f8
1548 1548 ? s/f10
1549 1549 ? s/f9
1550 1550 $ hg add -S
1551 1551 adding f8
1552 1552 adding s/f10
1553 1553 adding s/f9
1554 1554 $ hg st -S
1555 1555 A f8
1556 1556 A s/f10
1557 1557 A s/f9
1558 1558 $ hg ci -R s -m3
1559 1559 $ hg ci -m4
1560 1560 Adding with a pattern silently ignores subrepos
1561 1561 $ echo c11 > fm11
1562 1562 $ echo c12 > fn12
1563 1563 $ echo c13 > s/fm13
1564 1564 $ echo c14 > s/fn14
1565 1565 $ hg st -S
1566 1566 ? fm11
1567 1567 ? fn12
1568 1568 ? s/fm13
1569 1569 ? s/fn14
1570 1570 $ hg add 'glob:**fm*'
1571 1571 adding fm11
1572 1572 $ hg st -S
1573 1573 A fm11
1574 1574 ? fn12
1575 1575 ? s/fm13
1576 1576 ? s/fn14
1577 1577 $ hg ci -R s -Am4
1578 1578 adding fm13
1579 1579 adding fn14
1580 1580 $ hg ci -Am5
1581 1581 adding fn12
1582 1582 Adding with a pattern with -S also adds matches in subrepos
1583 1583 $ echo c15 > fm15
1584 1584 $ echo c16 > fn16
1585 1585 $ echo c17 > s/fm17
1586 1586 $ echo c18 > s/fn18
1587 1587 $ hg st -S
1588 1588 ? fm15
1589 1589 ? fn16
1590 1590 ? s/fm17
1591 1591 ? s/fn18
1592 1592 $ hg add -S 'glob:**fm*'
1593 1593 adding fm15
1594 1594 adding s/fm17
1595 1595 $ hg st -S
1596 1596 A fm15
1597 1597 A s/fm17
1598 1598 ? fn16
1599 1599 ? s/fn18
1600 1600 $ hg ci -R s -Am5
1601 1601 adding fn18
1602 1602 $ hg ci -Am6
1603 1603 adding fn16
1604 1604
1605 1605 Test behavior of forget for explicit path in subrepo:
1606 1606 Forgetting an explicit path in a subrepo untracks the file
1607 1607 $ echo c19 > s/f19
1608 1608 $ hg add s/f19
1609 1609 $ hg st -S
1610 1610 A s/f19
1611 1611 $ hg forget s/f19
1612 1612 $ hg st -S
1613 1613 ? s/f19
1614 1614 $ rm s/f19
1615 1615 $ cd ..
1616 1616
1617 1617 Courtesy phases synchronisation to publishing server does not block the push
1618 1618 (issue3781)
1619 1619
1620 1620 $ cp -R main issue3781
1621 1621 $ cp -R main issue3781-dest
1622 1622 $ cd issue3781-dest/s
1623 1623 $ hg phase tip # show we have draft changeset
1624 1624 5: draft
1625 1625 $ chmod a-w .hg/store/phaseroots # prevent phase push
1626 1626 $ cd ../../issue3781
1627 1627 $ cat >> .hg/hgrc << EOF
1628 1628 > [paths]
1629 1629 > default=../issue3781-dest/
1630 1630 > EOF
1631 1631 $ hg push --config devel.legacy.exchange=bundle1
1632 1632 pushing to $TESTTMP/issue3781-dest
1633 1633 pushing subrepo s to $TESTTMP/issue3781-dest/s
1634 1634 searching for changes
1635 1635 no changes found
1636 1636 searching for changes
1637 1637 no changes found
1638 1638 [1]
1639 1639 # clean the push cache
1640 1640 $ rm s/.hg/cache/storehash/*
1641 1641 $ hg push # bundle2+
1642 1642 pushing to $TESTTMP/issue3781-dest
1643 1643 pushing subrepo s to $TESTTMP/issue3781-dest/s
1644 1644 searching for changes
1645 1645 no changes found
1646 1646 searching for changes
1647 1647 no changes found
1648 1648 [1]
1649 1649 $ cd ..
1650 1650
1651 1651 Test phase choice for newly created commit with "phases.subrepochecks"
1652 1652 configuration
1653 1653
1654 1654 $ cd t
1655 1655 $ hg update -q -r 12
1656 1656
1657 1657 $ cat >> s/ss/.hg/hgrc <<EOF
1658 1658 > [phases]
1659 1659 > new-commit = secret
1660 1660 > EOF
1661 1661 $ cat >> s/.hg/hgrc <<EOF
1662 1662 > [phases]
1663 1663 > new-commit = draft
1664 1664 > EOF
1665 1665 $ echo phasecheck1 >> s/ss/a
1666 1666 $ hg -R s commit -S --config phases.checksubrepos=abort -m phasecheck1
1667 1667 committing subrepository ss
1668 1668 transaction abort!
1669 1669 rollback completed
1670 1670 abort: can't commit in draft phase conflicting secret from subrepository ss
1671 1671 [255]
1672 1672 $ echo phasecheck2 >> s/ss/a
1673 1673 $ hg -R s commit -S --config phases.checksubrepos=ignore -m phasecheck2
1674 1674 committing subrepository ss
1675 1675 $ hg -R s/ss phase tip
1676 1676 3: secret
1677 1677 $ hg -R s phase tip
1678 1678 6: draft
1679 1679 $ echo phasecheck3 >> s/ss/a
1680 1680 $ hg -R s commit -S -m phasecheck3
1681 1681 committing subrepository ss
1682 1682 warning: changes are committed in secret phase from subrepository ss
1683 1683 $ hg -R s/ss phase tip
1684 1684 4: secret
1685 1685 $ hg -R s phase tip
1686 1686 7: secret
1687 1687
1688 1688 $ cat >> t/.hg/hgrc <<EOF
1689 1689 > [phases]
1690 1690 > new-commit = draft
1691 1691 > EOF
1692 1692 $ cat >> .hg/hgrc <<EOF
1693 1693 > [phases]
1694 1694 > new-commit = public
1695 1695 > EOF
1696 1696 $ echo phasecheck4 >> s/ss/a
1697 1697 $ echo phasecheck4 >> t/t
1698 1698 $ hg commit -S -m phasecheck4
1699 1699 committing subrepository s
1700 1700 committing subrepository s/ss
1701 1701 warning: changes are committed in secret phase from subrepository ss
1702 1702 committing subrepository t
1703 1703 warning: changes are committed in secret phase from subrepository s
1704 1704 created new head
1705 1705 $ hg -R s/ss phase tip
1706 1706 5: secret
1707 1707 $ hg -R s phase tip
1708 1708 8: secret
1709 1709 $ hg -R t phase tip
1710 1710 6: draft
1711 1711 $ hg phase tip
1712 1712 15: secret
1713 1713
1714 1714 $ cd ..
1715 1715
1716 1716
1717 1717 Test that commit --secret works on both repo and subrepo (issue4182)
1718 1718
1719 1719 $ cd main
1720 1720 $ echo secret >> b
1721 1721 $ echo secret >> s/b
1722 1722 $ hg commit --secret --subrepo -m "secret"
1723 1723 committing subrepository s
1724 1724 $ hg phase -r .
1725 1725 6: secret
1726 1726 $ cd s
1727 1727 $ hg phase -r .
1728 1728 6: secret
1729 1729 $ cd ../../
1730 1730
1731 1731 Test "subrepos" template keyword
1732 1732
1733 1733 $ cd t
1734 1734 $ hg update -q 15
1735 1735 $ cat > .hgsub <<EOF
1736 1736 > s = s
1737 1737 > EOF
1738 1738 $ hg commit -m "16"
1739 1739 warning: changes are committed in secret phase from subrepository s
1740 1740
1741 1741 (addition of ".hgsub" itself)
1742 1742
1743 1743 $ hg diff --nodates -c 1 .hgsubstate
1744 1744 diff -r f7b1eb17ad24 -r 7cf8cfea66e4 .hgsubstate
1745 1745 --- /dev/null
1746 1746 +++ b/.hgsubstate
1747 1747 @@ -0,0 +1,1 @@
1748 1748 +e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1749 1749 $ hg log -r 1 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1750 1750 f7b1eb17ad24 000000000000
1751 1751 s
1752 1752
1753 1753 (modification of existing entry)
1754 1754
1755 1755 $ hg diff --nodates -c 2 .hgsubstate
1756 1756 diff -r 7cf8cfea66e4 -r df30734270ae .hgsubstate
1757 1757 --- a/.hgsubstate
1758 1758 +++ b/.hgsubstate
1759 1759 @@ -1,1 +1,1 @@
1760 1760 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1761 1761 +dc73e2e6d2675eb2e41e33c205f4bdab4ea5111d s
1762 1762 $ hg log -r 2 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1763 1763 7cf8cfea66e4 000000000000
1764 1764 s
1765 1765
1766 1766 (addition of entry)
1767 1767
1768 1768 $ hg diff --nodates -c 5 .hgsubstate
1769 1769 diff -r 7cf8cfea66e4 -r 1f14a2e2d3ec .hgsubstate
1770 1770 --- a/.hgsubstate
1771 1771 +++ b/.hgsubstate
1772 1772 @@ -1,1 +1,2 @@
1773 1773 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1774 1774 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1775 1775 $ hg log -r 5 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1776 1776 7cf8cfea66e4 000000000000
1777 1777 t
1778 1778
1779 1779 (removal of existing entry)
1780 1780
1781 1781 $ hg diff --nodates -c 16 .hgsubstate
1782 1782 diff -r 8bec38d2bd0b -r f2f70bc3d3c9 .hgsubstate
1783 1783 --- a/.hgsubstate
1784 1784 +++ b/.hgsubstate
1785 1785 @@ -1,2 +1,1 @@
1786 1786 0731af8ca9423976d3743119d0865097c07bdc1b s
1787 1787 -e202dc79b04c88a636ea8913d9182a1346d9b3dc t
1788 1788 $ hg log -r 16 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1789 1789 8bec38d2bd0b 000000000000
1790 1790 t
1791 1791
1792 1792 (merging)
1793 1793
1794 1794 $ hg diff --nodates -c 9 .hgsubstate
1795 1795 diff -r f6affe3fbfaa -r f0d2028bf86d .hgsubstate
1796 1796 --- a/.hgsubstate
1797 1797 +++ b/.hgsubstate
1798 1798 @@ -1,1 +1,2 @@
1799 1799 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1800 1800 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1801 1801 $ hg log -r 9 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1802 1802 f6affe3fbfaa 1f14a2e2d3ec
1803 1803 t
1804 1804
1805 1805 (removal of ".hgsub" itself)
1806 1806
1807 1807 $ hg diff --nodates -c 8 .hgsubstate
1808 1808 diff -r f94576341bcf -r 96615c1dad2d .hgsubstate
1809 1809 --- a/.hgsubstate
1810 1810 +++ /dev/null
1811 1811 @@ -1,2 +0,0 @@
1812 1812 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1813 1813 -7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4 t
1814 1814 $ hg log -r 8 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1815 1815 f94576341bcf 000000000000
1816 1816
1817 1817 Test that '[paths]' is configured correctly at subrepo creation
1818 1818
1819 1819 $ cd $TESTTMP/tc
1820 1820 $ cat > .hgsub <<EOF
1821 1821 > # to clear bogus subrepo path 'bogus=[boguspath'
1822 1822 > s = s
1823 1823 > t = t
1824 1824 > EOF
1825 1825 $ hg update -q --clean null
1826 1826 $ rm -rf s t
1827 1827 $ cat >> .hg/hgrc <<EOF
1828 1828 > [paths]
1829 1829 > default-push = /foo/bar
1830 1830 > EOF
1831 1831 $ hg update -q
1832 1832 $ cat s/.hg/hgrc
1833 1833 [paths]
1834 1834 default = $TESTTMP/t/s
1835 1835 default-push = /foo/bar/s
1836 1836 $ cat s/ss/.hg/hgrc
1837 1837 [paths]
1838 1838 default = $TESTTMP/t/s/ss
1839 1839 default-push = /foo/bar/s/ss
1840 1840 $ cat t/.hg/hgrc
1841 1841 [paths]
1842 1842 default = $TESTTMP/t/t
1843 1843 default-push = /foo/bar/t
1844 1844
1845 1845 $ cd $TESTTMP/t
1846 1846 $ hg up -qC 0
1847 1847 $ echo 'bar' > bar.txt
1848 1848 $ hg ci -Am 'branch before subrepo add'
1849 1849 adding bar.txt
1850 1850 created new head
1851 1851 $ hg merge -r "first(subrepo('s'))"
1852 1852 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1853 1853 (branch merge, don't forget to commit)
1854 1854 $ hg status -S -X '.hgsub*'
1855 1855 A s/a
1856 1856 ? s/b
1857 1857 ? s/c
1858 1858 ? s/f1
1859 1859 $ hg status -S --rev 'p2()'
1860 1860 A bar.txt
1861 1861 ? s/b
1862 1862 ? s/c
1863 1863 ? s/f1
1864 1864 $ hg diff -S -X '.hgsub*' --nodates
1865 1865 diff -r 000000000000 s/a
1866 1866 --- /dev/null
1867 1867 +++ b/s/a
1868 1868 @@ -0,0 +1,1 @@
1869 1869 +a
1870 1870 $ hg diff -S --rev 'p2()' --nodates
1871 1871 diff -r 7cf8cfea66e4 bar.txt
1872 1872 --- /dev/null
1873 1873 +++ b/bar.txt
1874 1874 @@ -0,0 +1,1 @@
1875 1875 +bar
1876 1876
1877 1877 $ cd ..
1878 1878
1879 1879 test for ssh exploit 2017-07-25
1880 1880
1881 1881 $ cat >> $HGRCPATH << EOF
1882 1882 > [ui]
1883 1883 > ssh = sh -c "read l; read l; read l"
1884 1884 > EOF
1885 1885
1886 1886 $ hg init malicious-proxycommand
1887 1887 $ cd malicious-proxycommand
1888 1888 $ echo 's = [hg]ssh://-oProxyCommand=touch${IFS}owned/path' > .hgsub
1889 1889 $ hg init s
1890 1890 $ cd s
1891 1891 $ echo init > init
1892 1892 $ hg add
1893 1893 adding init
1894 1894 $ hg commit -m init
1895 1895 $ cd ..
1896 1896 $ hg add .hgsub
1897 1897 $ hg ci -m 'add subrepo'
1898 1898 $ cd ..
1899 1899 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1900 1900 updating to branch default
1901 1901 cloning subrepo s from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
1902 1902 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1903 1903 [255]
1904 1904
1905 1905 also check that a percent encoded '-' (%2D) doesn't work
1906 1906
1907 1907 $ cd malicious-proxycommand
1908 1908 $ echo 's = [hg]ssh://%2DoProxyCommand=touch${IFS}owned/path' > .hgsub
1909 1909 $ hg ci -m 'change url to percent encoded'
1910 1910 $ cd ..
1911 1911 $ rm -r malicious-proxycommand-clone
1912 1912 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1913 1913 updating to branch default
1914 1914 cloning subrepo s from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
1915 1915 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1916 1916 [255]
1917 1917
1918 1918 also check for a pipe
1919 1919
1920 1920 $ cd malicious-proxycommand
1921 1921 $ echo 's = [hg]ssh://fakehost|touch${IFS}owned/path' > .hgsub
1922 1922 $ hg ci -m 'change url to pipe'
1923 1923 $ cd ..
1924 1924 $ rm -r malicious-proxycommand-clone
1925 1925 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1926 1926 updating to branch default
1927 1927 cloning subrepo s from ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path
1928 1928 abort: no suitable response from remote hg!
1929 1929 [255]
1930 1930 $ [ ! -f owned ] || echo 'you got owned'
1931 1931
1932 1932 also check that a percent encoded '|' (%7C) doesn't work
1933 1933
1934 1934 $ cd malicious-proxycommand
1935 1935 $ echo 's = [hg]ssh://fakehost%7Ctouch%20owned/path' > .hgsub
1936 1936 $ hg ci -m 'change url to percent encoded pipe'
1937 1937 $ cd ..
1938 1938 $ rm -r malicious-proxycommand-clone
1939 1939 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1940 1940 updating to branch default
1941 1941 cloning subrepo s from ssh://fakehost%7Ctouch%20owned/path
1942 1942 abort: no suitable response from remote hg!
1943 1943 [255]
1944 1944 $ [ ! -f owned ] || echo 'you got owned'
1945 1945
1946 1946 and bad usernames:
1947 1947 $ cd malicious-proxycommand
1948 1948 $ echo 's = [hg]ssh://-oProxyCommand=touch owned@example.com/path' > .hgsub
1949 1949 $ hg ci -m 'owned username'
1950 1950 $ cd ..
1951 1951 $ rm -r malicious-proxycommand-clone
1952 1952 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1953 1953 updating to branch default
1954 1954 cloning subrepo s from ssh://-oProxyCommand%3Dtouch%20owned@example.com/path
1955 1955 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned@example.com/path' (in subrepository "s")
1956 1956 [255]
1957 1957
1958 1958 Test convert subrepositories including merge (issue5526):
1959 1959
1960 1960 $ hg init tconv
1961 1961 $ hg convert --config extensions.convert= -q t/s tconv/s
1962 1962 $ hg convert --config extensions.convert= -q t/s/ss tconv/s/ss
1963 1963 $ hg convert --config extensions.convert= -q t/t tconv/t
1964 1964
1965 1965 convert shouldn't fail because of pseudo filenode:
1966 1966
1967 1967 $ hg convert --config extensions.convert= t tconv
1968 1968 scanning source...
1969 1969 sorting...
1970 1970 converting...
1971 1971 17 0
1972 1972 16 1
1973 1973 15 2
1974 1974 14 3
1975 1975 13 4
1976 1976 12 5
1977 1977 11 6
1978 1978 10 7
1979 1979 9 8
1980 1980 8 9
1981 1981 7 10
1982 1982 6 11
1983 1983 5 12
1984 1984 4 13
1985 1985 3 rm2
1986 1986 2 phasecheck4
1987 1987 1 16
1988 1988 0 branch before subrepo add
1989 1989
1990 1990 converted .hgsubstate should point to valid nodes:
1991 1991
1992 1992 $ hg up -R tconv 9
1993 1993 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1994 1994 $ cat tconv/.hgsubstate
1995 1995 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1996 1996 60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
General Comments 0
You need to be logged in to leave comments. Login now