##// END OF EJS Templates
obsmarker: precnode was renamed into prednode...
Boris Feld -
r33856:eae63a9e default
parent child Browse files
Show More
@@ -1,3862 +1,3862 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 itertools
12 12 import os
13 13 import re
14 14 import tempfile
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 hex,
19 19 nullid,
20 20 nullrev,
21 21 short,
22 22 )
23 23
24 24 from . import (
25 25 bookmarks,
26 26 changelog,
27 27 copies,
28 28 crecord as crecordmod,
29 29 dirstateguard,
30 30 encoding,
31 31 error,
32 32 formatter,
33 33 graphmod,
34 34 match as matchmod,
35 35 obsolete,
36 36 patch,
37 37 pathutil,
38 38 phases,
39 39 pycompat,
40 40 registrar,
41 41 revlog,
42 42 revset,
43 43 scmutil,
44 44 smartset,
45 45 templatekw,
46 46 templater,
47 47 util,
48 48 vfs as vfsmod,
49 49 )
50 50 stringio = util.stringio
51 51
52 52 # templates of common command options
53 53
54 54 dryrunopts = [
55 55 ('n', 'dry-run', None,
56 56 _('do not perform actions, just print output')),
57 57 ]
58 58
59 59 remoteopts = [
60 60 ('e', 'ssh', '',
61 61 _('specify ssh command to use'), _('CMD')),
62 62 ('', 'remotecmd', '',
63 63 _('specify hg command to run on the remote side'), _('CMD')),
64 64 ('', 'insecure', None,
65 65 _('do not verify server certificate (ignoring web.cacerts config)')),
66 66 ]
67 67
68 68 walkopts = [
69 69 ('I', 'include', [],
70 70 _('include names matching the given patterns'), _('PATTERN')),
71 71 ('X', 'exclude', [],
72 72 _('exclude names matching the given patterns'), _('PATTERN')),
73 73 ]
74 74
75 75 commitopts = [
76 76 ('m', 'message', '',
77 77 _('use text as commit message'), _('TEXT')),
78 78 ('l', 'logfile', '',
79 79 _('read commit message from file'), _('FILE')),
80 80 ]
81 81
82 82 commitopts2 = [
83 83 ('d', 'date', '',
84 84 _('record the specified date as commit date'), _('DATE')),
85 85 ('u', 'user', '',
86 86 _('record the specified user as committer'), _('USER')),
87 87 ]
88 88
89 89 # hidden for now
90 90 formatteropts = [
91 91 ('T', 'template', '',
92 92 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
93 93 ]
94 94
95 95 templateopts = [
96 96 ('', 'style', '',
97 97 _('display using template map file (DEPRECATED)'), _('STYLE')),
98 98 ('T', 'template', '',
99 99 _('display with template'), _('TEMPLATE')),
100 100 ]
101 101
102 102 logopts = [
103 103 ('p', 'patch', None, _('show patch')),
104 104 ('g', 'git', None, _('use git extended diff format')),
105 105 ('l', 'limit', '',
106 106 _('limit number of changes displayed'), _('NUM')),
107 107 ('M', 'no-merges', None, _('do not show merges')),
108 108 ('', 'stat', None, _('output diffstat-style summary of changes')),
109 109 ('G', 'graph', None, _("show the revision DAG")),
110 110 ] + templateopts
111 111
112 112 diffopts = [
113 113 ('a', 'text', None, _('treat all files as text')),
114 114 ('g', 'git', None, _('use git extended diff format')),
115 115 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
116 116 ('', 'nodates', None, _('omit dates from diff headers'))
117 117 ]
118 118
119 119 diffwsopts = [
120 120 ('w', 'ignore-all-space', None,
121 121 _('ignore white space when comparing lines')),
122 122 ('b', 'ignore-space-change', None,
123 123 _('ignore changes in the amount of white space')),
124 124 ('B', 'ignore-blank-lines', None,
125 125 _('ignore changes whose lines are all blank')),
126 126 ]
127 127
128 128 diffopts2 = [
129 129 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
130 130 ('p', 'show-function', None, _('show which function each change is in')),
131 131 ('', 'reverse', None, _('produce a diff that undoes the changes')),
132 132 ] + diffwsopts + [
133 133 ('U', 'unified', '',
134 134 _('number of lines of context to show'), _('NUM')),
135 135 ('', 'stat', None, _('output diffstat-style summary of changes')),
136 136 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
137 137 ]
138 138
139 139 mergetoolopts = [
140 140 ('t', 'tool', '', _('specify merge tool')),
141 141 ]
142 142
143 143 similarityopts = [
144 144 ('s', 'similarity', '',
145 145 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
146 146 ]
147 147
148 148 subrepoopts = [
149 149 ('S', 'subrepos', None,
150 150 _('recurse into subrepositories'))
151 151 ]
152 152
153 153 debugrevlogopts = [
154 154 ('c', 'changelog', False, _('open changelog')),
155 155 ('m', 'manifest', False, _('open manifest')),
156 156 ('', 'dir', '', _('open directory manifest')),
157 157 ]
158 158
159 159 # special string such that everything below this line will be ingored in the
160 160 # editor text
161 161 _linebelow = "^HG: ------------------------ >8 ------------------------$"
162 162
163 163 def ishunk(x):
164 164 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
165 165 return isinstance(x, hunkclasses)
166 166
167 167 def newandmodified(chunks, originalchunks):
168 168 newlyaddedandmodifiedfiles = set()
169 169 for chunk in chunks:
170 170 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
171 171 originalchunks:
172 172 newlyaddedandmodifiedfiles.add(chunk.header.filename())
173 173 return newlyaddedandmodifiedfiles
174 174
175 175 def parsealiases(cmd):
176 176 return cmd.lstrip("^").split("|")
177 177
178 178 def setupwrapcolorwrite(ui):
179 179 # wrap ui.write so diff output can be labeled/colorized
180 180 def wrapwrite(orig, *args, **kw):
181 181 label = kw.pop('label', '')
182 182 for chunk, l in patch.difflabel(lambda: args):
183 183 orig(chunk, label=label + l)
184 184
185 185 oldwrite = ui.write
186 186 def wrap(*args, **kwargs):
187 187 return wrapwrite(oldwrite, *args, **kwargs)
188 188 setattr(ui, 'write', wrap)
189 189 return oldwrite
190 190
191 191 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
192 192 if usecurses:
193 193 if testfile:
194 194 recordfn = crecordmod.testdecorator(testfile,
195 195 crecordmod.testchunkselector)
196 196 else:
197 197 recordfn = crecordmod.chunkselector
198 198
199 199 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
200 200
201 201 else:
202 202 return patch.filterpatch(ui, originalhunks, operation)
203 203
204 204 def recordfilter(ui, originalhunks, operation=None):
205 205 """ Prompts the user to filter the originalhunks and return a list of
206 206 selected hunks.
207 207 *operation* is used for to build ui messages to indicate the user what
208 208 kind of filtering they are doing: reverting, committing, shelving, etc.
209 209 (see patch.filterpatch).
210 210 """
211 211 usecurses = crecordmod.checkcurses(ui)
212 212 testfile = ui.config('experimental', 'crecordtest')
213 213 oldwrite = setupwrapcolorwrite(ui)
214 214 try:
215 215 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
216 216 testfile, operation)
217 217 finally:
218 218 ui.write = oldwrite
219 219 return newchunks, newopts
220 220
221 221 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
222 222 filterfn, *pats, **opts):
223 223 from . import merge as mergemod
224 224 opts = pycompat.byteskwargs(opts)
225 225 if not ui.interactive():
226 226 if cmdsuggest:
227 227 msg = _('running non-interactively, use %s instead') % cmdsuggest
228 228 else:
229 229 msg = _('running non-interactively')
230 230 raise error.Abort(msg)
231 231
232 232 # make sure username is set before going interactive
233 233 if not opts.get('user'):
234 234 ui.username() # raise exception, username not provided
235 235
236 236 def recordfunc(ui, repo, message, match, opts):
237 237 """This is generic record driver.
238 238
239 239 Its job is to interactively filter local changes, and
240 240 accordingly prepare working directory into a state in which the
241 241 job can be delegated to a non-interactive commit command such as
242 242 'commit' or 'qrefresh'.
243 243
244 244 After the actual job is done by non-interactive command, the
245 245 working directory is restored to its original state.
246 246
247 247 In the end we'll record interesting changes, and everything else
248 248 will be left in place, so the user can continue working.
249 249 """
250 250
251 251 checkunfinished(repo, commit=True)
252 252 wctx = repo[None]
253 253 merge = len(wctx.parents()) > 1
254 254 if merge:
255 255 raise error.Abort(_('cannot partially commit a merge '
256 256 '(use "hg commit" instead)'))
257 257
258 258 def fail(f, msg):
259 259 raise error.Abort('%s: %s' % (f, msg))
260 260
261 261 force = opts.get('force')
262 262 if not force:
263 263 vdirs = []
264 264 match.explicitdir = vdirs.append
265 265 match.bad = fail
266 266
267 267 status = repo.status(match=match)
268 268 if not force:
269 269 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
270 270 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
271 271 diffopts.nodates = True
272 272 diffopts.git = True
273 273 diffopts.showfunc = True
274 274 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
275 275 originalchunks = patch.parsepatch(originaldiff)
276 276
277 277 # 1. filter patch, since we are intending to apply subset of it
278 278 try:
279 279 chunks, newopts = filterfn(ui, originalchunks)
280 280 except patch.PatchError as err:
281 281 raise error.Abort(_('error parsing patch: %s') % err)
282 282 opts.update(newopts)
283 283
284 284 # We need to keep a backup of files that have been newly added and
285 285 # modified during the recording process because there is a previous
286 286 # version without the edit in the workdir
287 287 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
288 288 contenders = set()
289 289 for h in chunks:
290 290 try:
291 291 contenders.update(set(h.files()))
292 292 except AttributeError:
293 293 pass
294 294
295 295 changed = status.modified + status.added + status.removed
296 296 newfiles = [f for f in changed if f in contenders]
297 297 if not newfiles:
298 298 ui.status(_('no changes to record\n'))
299 299 return 0
300 300
301 301 modified = set(status.modified)
302 302
303 303 # 2. backup changed files, so we can restore them in the end
304 304
305 305 if backupall:
306 306 tobackup = changed
307 307 else:
308 308 tobackup = [f for f in newfiles if f in modified or f in \
309 309 newlyaddedandmodifiedfiles]
310 310 backups = {}
311 311 if tobackup:
312 312 backupdir = repo.vfs.join('record-backups')
313 313 try:
314 314 os.mkdir(backupdir)
315 315 except OSError as err:
316 316 if err.errno != errno.EEXIST:
317 317 raise
318 318 try:
319 319 # backup continues
320 320 for f in tobackup:
321 321 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
322 322 dir=backupdir)
323 323 os.close(fd)
324 324 ui.debug('backup %r as %r\n' % (f, tmpname))
325 325 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
326 326 backups[f] = tmpname
327 327
328 328 fp = stringio()
329 329 for c in chunks:
330 330 fname = c.filename()
331 331 if fname in backups:
332 332 c.write(fp)
333 333 dopatch = fp.tell()
334 334 fp.seek(0)
335 335
336 336 # 2.5 optionally review / modify patch in text editor
337 337 if opts.get('review', False):
338 338 patchtext = (crecordmod.diffhelptext
339 339 + crecordmod.patchhelptext
340 340 + fp.read())
341 341 reviewedpatch = ui.edit(patchtext, "",
342 342 extra={"suffix": ".diff"},
343 343 repopath=repo.path)
344 344 fp.truncate(0)
345 345 fp.write(reviewedpatch)
346 346 fp.seek(0)
347 347
348 348 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
349 349 # 3a. apply filtered patch to clean repo (clean)
350 350 if backups:
351 351 # Equivalent to hg.revert
352 352 m = scmutil.matchfiles(repo, backups.keys())
353 353 mergemod.update(repo, repo.dirstate.p1(),
354 354 False, True, matcher=m)
355 355
356 356 # 3b. (apply)
357 357 if dopatch:
358 358 try:
359 359 ui.debug('applying patch\n')
360 360 ui.debug(fp.getvalue())
361 361 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
362 362 except patch.PatchError as err:
363 363 raise error.Abort(str(err))
364 364 del fp
365 365
366 366 # 4. We prepared working directory according to filtered
367 367 # patch. Now is the time to delegate the job to
368 368 # commit/qrefresh or the like!
369 369
370 370 # Make all of the pathnames absolute.
371 371 newfiles = [repo.wjoin(nf) for nf in newfiles]
372 372 return commitfunc(ui, repo, *newfiles, **opts)
373 373 finally:
374 374 # 5. finally restore backed-up files
375 375 try:
376 376 dirstate = repo.dirstate
377 377 for realname, tmpname in backups.iteritems():
378 378 ui.debug('restoring %r to %r\n' % (tmpname, realname))
379 379
380 380 if dirstate[realname] == 'n':
381 381 # without normallookup, restoring timestamp
382 382 # may cause partially committed files
383 383 # to be treated as unmodified
384 384 dirstate.normallookup(realname)
385 385
386 386 # copystat=True here and above are a hack to trick any
387 387 # editors that have f open that we haven't modified them.
388 388 #
389 389 # Also note that this racy as an editor could notice the
390 390 # file's mtime before we've finished writing it.
391 391 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
392 392 os.unlink(tmpname)
393 393 if tobackup:
394 394 os.rmdir(backupdir)
395 395 except OSError:
396 396 pass
397 397
398 398 def recordinwlock(ui, repo, message, match, opts):
399 399 with repo.wlock():
400 400 return recordfunc(ui, repo, message, match, opts)
401 401
402 402 return commit(ui, repo, recordinwlock, pats, opts)
403 403
404 404 def tersestatus(root, statlist, status, ignorefn, ignore):
405 405 """
406 406 Returns a list of statuses with directory collapsed if all the files in the
407 407 directory has the same status.
408 408 """
409 409
410 410 def numfiles(dirname):
411 411 """
412 412 Calculates the number of tracked files in a given directory which also
413 413 includes files which were removed or deleted. Considers ignored files
414 414 if ignore argument is True or 'i' is present in status argument.
415 415 """
416 416 if lencache.get(dirname):
417 417 return lencache[dirname]
418 418 if 'i' in status or ignore:
419 419 def match(localpath):
420 420 absolutepath = os.path.join(root, localpath)
421 421 if os.path.isdir(absolutepath) and isemptydir(absolutepath):
422 422 return True
423 423 return False
424 424 else:
425 425 def match(localpath):
426 426 # there can be directory whose all the files are ignored and
427 427 # hence the drectory should also be ignored while counting
428 428 # number of files or subdirs in it's parent directory. This
429 429 # checks the same.
430 430 # XXX: We need a better logic here.
431 431 if os.path.isdir(os.path.join(root, localpath)):
432 432 return isignoreddir(localpath)
433 433 else:
434 434 # XXX: there can be files which have the ignored pattern but
435 435 # are not ignored. That leads to bug in counting number of
436 436 # tracked files in the directory.
437 437 return ignorefn(localpath)
438 438 lendir = 0
439 439 abspath = os.path.join(root, dirname)
440 440 # There might be cases when a directory does not exists as the whole
441 441 # directory can be removed and/or deleted.
442 442 try:
443 443 for f in os.listdir(abspath):
444 444 localpath = os.path.join(dirname, f)
445 445 if not match(localpath):
446 446 lendir += 1
447 447 except OSError:
448 448 pass
449 449 lendir += len(absentdir.get(dirname, []))
450 450 lencache[dirname] = lendir
451 451 return lendir
452 452
453 453 def isemptydir(abspath):
454 454 """
455 455 Check whether a directory is empty or not, i.e. there is no files in the
456 456 directory and all its subdirectories.
457 457 """
458 458 for f in os.listdir(abspath):
459 459 fullpath = os.path.join(abspath, f)
460 460 if os.path.isdir(fullpath):
461 461 # recursion here
462 462 ret = isemptydir(fullpath)
463 463 if not ret:
464 464 return False
465 465 else:
466 466 return False
467 467 return True
468 468
469 469 def isignoreddir(localpath):
470 470 """Return True if `localpath` directory is ignored or contains only
471 471 ignored files and should hence be considered ignored.
472 472 """
473 473 dirpath = os.path.join(root, localpath)
474 474 if ignorefn(dirpath):
475 475 return True
476 476 for f in os.listdir(dirpath):
477 477 filepath = os.path.join(dirpath, f)
478 478 if os.path.isdir(filepath):
479 479 # recursion here
480 480 ret = isignoreddir(os.path.join(localpath, f))
481 481 if not ret:
482 482 return False
483 483 else:
484 484 if not ignorefn(os.path.join(localpath, f)):
485 485 return False
486 486 return True
487 487
488 488 def absentones(removedfiles, missingfiles):
489 489 """
490 490 Returns a dictionary of directories with files in it which are either
491 491 removed or missing (deleted) in them.
492 492 """
493 493 absentdir = {}
494 494 absentfiles = removedfiles + missingfiles
495 495 while absentfiles:
496 496 f = absentfiles.pop()
497 497 par = os.path.dirname(f)
498 498 if par == '':
499 499 continue
500 500 # we need to store files rather than number of files as some files
501 501 # or subdirectories in a directory can be counted twice. This is
502 502 # also we have used sets here.
503 503 try:
504 504 absentdir[par].add(f)
505 505 except KeyError:
506 506 absentdir[par] = set([f])
507 507 absentfiles.append(par)
508 508 return absentdir
509 509
510 510 indexes = {'m': 0, 'a': 1, 'r': 2, 'd': 3, 'u': 4, 'i': 5, 'c': 6}
511 511 # get a dictonary of directories and files which are missing as os.listdir()
512 512 # won't be able to list them.
513 513 absentdir = absentones(statlist[2], statlist[3])
514 514 finalrs = [[]] * len(indexes)
515 515 didsomethingchanged = False
516 516 # dictionary to store number of files and subdir in a directory so that we
517 517 # don't compute that again.
518 518 lencache = {}
519 519
520 520 for st in pycompat.bytestr(status):
521 521
522 522 try:
523 523 ind = indexes[st]
524 524 except KeyError:
525 525 # TODO: Need a better error message here
526 526 raise error.Abort("'%s' not recognized" % st)
527 527
528 528 sfiles = statlist[ind]
529 529 if not sfiles:
530 530 continue
531 531 pardict = {}
532 532 for a in sfiles:
533 533 par = os.path.dirname(a)
534 534 pardict.setdefault(par, []).append(a)
535 535
536 536 rs = []
537 537 newls = []
538 538 for par, files in pardict.iteritems():
539 539 lenpar = numfiles(par)
540 540 if lenpar == len(files):
541 541 newls.append(par)
542 542
543 543 if not newls:
544 544 continue
545 545
546 546 while newls:
547 547 newel = newls.pop()
548 548 if newel == '':
549 549 continue
550 550 parn = os.path.dirname(newel)
551 551 pardict[newel] = []
552 552 # Adding pycompat.ossep as newel is a directory.
553 553 pardict.setdefault(parn, []).append(newel + pycompat.ossep)
554 554 lenpar = numfiles(parn)
555 555 if lenpar == len(pardict[parn]):
556 556 newls.append(parn)
557 557
558 558 # dict.values() for Py3 compatibility
559 559 for files in pardict.values():
560 560 rs.extend(files)
561 561
562 562 rs.sort()
563 563 finalrs[ind] = rs
564 564 didsomethingchanged = True
565 565
566 566 # If nothing is changed, make sure the order of files is preserved.
567 567 if not didsomethingchanged:
568 568 return statlist
569 569
570 570 for x in xrange(len(indexes)):
571 571 if not finalrs[x]:
572 572 finalrs[x] = statlist[x]
573 573
574 574 return finalrs
575 575
576 576 def _commentlines(raw):
577 577 '''Surround lineswith a comment char and a new line'''
578 578 lines = raw.splitlines()
579 579 commentedlines = ['# %s' % line for line in lines]
580 580 return '\n'.join(commentedlines) + '\n'
581 581
582 582 def _conflictsmsg(repo):
583 583 # avoid merge cycle
584 584 from . import merge as mergemod
585 585 mergestate = mergemod.mergestate.read(repo)
586 586 if not mergestate.active():
587 587 return
588 588
589 589 m = scmutil.match(repo[None])
590 590 unresolvedlist = [f for f in mergestate if m(f) and mergestate[f] == 'u']
591 591 if unresolvedlist:
592 592 mergeliststr = '\n'.join(
593 593 [' %s' % os.path.relpath(
594 594 os.path.join(repo.root, path),
595 595 pycompat.getcwd()) for path in unresolvedlist])
596 596 msg = _('''Unresolved merge conflicts:
597 597
598 598 %s
599 599
600 600 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
601 601 else:
602 602 msg = _('No unresolved merge conflicts.')
603 603
604 604 return _commentlines(msg)
605 605
606 606 def _helpmessage(continuecmd, abortcmd):
607 607 msg = _('To continue: %s\n'
608 608 'To abort: %s') % (continuecmd, abortcmd)
609 609 return _commentlines(msg)
610 610
611 611 def _rebasemsg():
612 612 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
613 613
614 614 def _histeditmsg():
615 615 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
616 616
617 617 def _unshelvemsg():
618 618 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
619 619
620 620 def _updatecleanmsg(dest=None):
621 621 warning = _('warning: this will discard uncommitted changes')
622 622 return 'hg update --clean %s (%s)' % (dest or '.', warning)
623 623
624 624 def _graftmsg():
625 625 # tweakdefaults requires `update` to have a rev hence the `.`
626 626 return _helpmessage('hg graft --continue', _updatecleanmsg())
627 627
628 628 def _mergemsg():
629 629 # tweakdefaults requires `update` to have a rev hence the `.`
630 630 return _helpmessage('hg commit', _updatecleanmsg())
631 631
632 632 def _bisectmsg():
633 633 msg = _('To mark the changeset good: hg bisect --good\n'
634 634 'To mark the changeset bad: hg bisect --bad\n'
635 635 'To abort: hg bisect --reset\n')
636 636 return _commentlines(msg)
637 637
638 638 def fileexistspredicate(filename):
639 639 return lambda repo: repo.vfs.exists(filename)
640 640
641 641 def _mergepredicate(repo):
642 642 return len(repo[None].parents()) > 1
643 643
644 644 STATES = (
645 645 # (state, predicate to detect states, helpful message function)
646 646 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
647 647 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
648 648 ('graft', fileexistspredicate('graftstate'), _graftmsg),
649 649 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
650 650 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
651 651 # The merge state is part of a list that will be iterated over.
652 652 # They need to be last because some of the other unfinished states may also
653 653 # be in a merge or update state (eg. rebase, histedit, graft, etc).
654 654 # We want those to have priority.
655 655 ('merge', _mergepredicate, _mergemsg),
656 656 )
657 657
658 658 def _getrepostate(repo):
659 659 # experimental config: commands.status.skipstates
660 660 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
661 661 for state, statedetectionpredicate, msgfn in STATES:
662 662 if state in skip:
663 663 continue
664 664 if statedetectionpredicate(repo):
665 665 return (state, statedetectionpredicate, msgfn)
666 666
667 667 def morestatus(repo, fm):
668 668 statetuple = _getrepostate(repo)
669 669 label = 'status.morestatus'
670 670 if statetuple:
671 671 fm.startitem()
672 672 state, statedetectionpredicate, helpfulmsg = statetuple
673 673 statemsg = _('The repository is in an unfinished *%s* state.') % state
674 674 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
675 675 conmsg = _conflictsmsg(repo)
676 676 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
677 677 if helpfulmsg:
678 678 helpmsg = helpfulmsg()
679 679 fm.write('helpmsg', '%s\n', helpmsg, label=label)
680 680
681 681 def findpossible(cmd, table, strict=False):
682 682 """
683 683 Return cmd -> (aliases, command table entry)
684 684 for each matching command.
685 685 Return debug commands (or their aliases) only if no normal command matches.
686 686 """
687 687 choice = {}
688 688 debugchoice = {}
689 689
690 690 if cmd in table:
691 691 # short-circuit exact matches, "log" alias beats "^log|history"
692 692 keys = [cmd]
693 693 else:
694 694 keys = table.keys()
695 695
696 696 allcmds = []
697 697 for e in keys:
698 698 aliases = parsealiases(e)
699 699 allcmds.extend(aliases)
700 700 found = None
701 701 if cmd in aliases:
702 702 found = cmd
703 703 elif not strict:
704 704 for a in aliases:
705 705 if a.startswith(cmd):
706 706 found = a
707 707 break
708 708 if found is not None:
709 709 if aliases[0].startswith("debug") or found.startswith("debug"):
710 710 debugchoice[found] = (aliases, table[e])
711 711 else:
712 712 choice[found] = (aliases, table[e])
713 713
714 714 if not choice and debugchoice:
715 715 choice = debugchoice
716 716
717 717 return choice, allcmds
718 718
719 719 def findcmd(cmd, table, strict=True):
720 720 """Return (aliases, command table entry) for command string."""
721 721 choice, allcmds = findpossible(cmd, table, strict)
722 722
723 723 if cmd in choice:
724 724 return choice[cmd]
725 725
726 726 if len(choice) > 1:
727 727 clist = sorted(choice)
728 728 raise error.AmbiguousCommand(cmd, clist)
729 729
730 730 if choice:
731 731 return list(choice.values())[0]
732 732
733 733 raise error.UnknownCommand(cmd, allcmds)
734 734
735 735 def findrepo(p):
736 736 while not os.path.isdir(os.path.join(p, ".hg")):
737 737 oldp, p = p, os.path.dirname(p)
738 738 if p == oldp:
739 739 return None
740 740
741 741 return p
742 742
743 743 def bailifchanged(repo, merge=True, hint=None):
744 744 """ enforce the precondition that working directory must be clean.
745 745
746 746 'merge' can be set to false if a pending uncommitted merge should be
747 747 ignored (such as when 'update --check' runs).
748 748
749 749 'hint' is the usual hint given to Abort exception.
750 750 """
751 751
752 752 if merge and repo.dirstate.p2() != nullid:
753 753 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
754 754 modified, added, removed, deleted = repo.status()[:4]
755 755 if modified or added or removed or deleted:
756 756 raise error.Abort(_('uncommitted changes'), hint=hint)
757 757 ctx = repo[None]
758 758 for s in sorted(ctx.substate):
759 759 ctx.sub(s).bailifchanged(hint=hint)
760 760
761 761 def logmessage(ui, opts):
762 762 """ get the log message according to -m and -l option """
763 763 message = opts.get('message')
764 764 logfile = opts.get('logfile')
765 765
766 766 if message and logfile:
767 767 raise error.Abort(_('options --message and --logfile are mutually '
768 768 'exclusive'))
769 769 if not message and logfile:
770 770 try:
771 771 if isstdiofilename(logfile):
772 772 message = ui.fin.read()
773 773 else:
774 774 message = '\n'.join(util.readfile(logfile).splitlines())
775 775 except IOError as inst:
776 776 raise error.Abort(_("can't read commit message '%s': %s") %
777 777 (logfile, inst.strerror))
778 778 return message
779 779
780 780 def mergeeditform(ctxorbool, baseformname):
781 781 """return appropriate editform name (referencing a committemplate)
782 782
783 783 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
784 784 merging is committed.
785 785
786 786 This returns baseformname with '.merge' appended if it is a merge,
787 787 otherwise '.normal' is appended.
788 788 """
789 789 if isinstance(ctxorbool, bool):
790 790 if ctxorbool:
791 791 return baseformname + ".merge"
792 792 elif 1 < len(ctxorbool.parents()):
793 793 return baseformname + ".merge"
794 794
795 795 return baseformname + ".normal"
796 796
797 797 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
798 798 editform='', **opts):
799 799 """get appropriate commit message editor according to '--edit' option
800 800
801 801 'finishdesc' is a function to be called with edited commit message
802 802 (= 'description' of the new changeset) just after editing, but
803 803 before checking empty-ness. It should return actual text to be
804 804 stored into history. This allows to change description before
805 805 storing.
806 806
807 807 'extramsg' is a extra message to be shown in the editor instead of
808 808 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
809 809 is automatically added.
810 810
811 811 'editform' is a dot-separated list of names, to distinguish
812 812 the purpose of commit text editing.
813 813
814 814 'getcommiteditor' returns 'commitforceeditor' regardless of
815 815 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
816 816 they are specific for usage in MQ.
817 817 """
818 818 if edit or finishdesc or extramsg:
819 819 return lambda r, c, s: commitforceeditor(r, c, s,
820 820 finishdesc=finishdesc,
821 821 extramsg=extramsg,
822 822 editform=editform)
823 823 elif editform:
824 824 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
825 825 else:
826 826 return commiteditor
827 827
828 828 def loglimit(opts):
829 829 """get the log limit according to option -l/--limit"""
830 830 limit = opts.get('limit')
831 831 if limit:
832 832 try:
833 833 limit = int(limit)
834 834 except ValueError:
835 835 raise error.Abort(_('limit must be a positive integer'))
836 836 if limit <= 0:
837 837 raise error.Abort(_('limit must be positive'))
838 838 else:
839 839 limit = None
840 840 return limit
841 841
842 842 def makefilename(repo, pat, node, desc=None,
843 843 total=None, seqno=None, revwidth=None, pathname=None):
844 844 node_expander = {
845 845 'H': lambda: hex(node),
846 846 'R': lambda: str(repo.changelog.rev(node)),
847 847 'h': lambda: short(node),
848 848 'm': lambda: re.sub('[^\w]', '_', str(desc))
849 849 }
850 850 expander = {
851 851 '%': lambda: '%',
852 852 'b': lambda: os.path.basename(repo.root),
853 853 }
854 854
855 855 try:
856 856 if node:
857 857 expander.update(node_expander)
858 858 if node:
859 859 expander['r'] = (lambda:
860 860 str(repo.changelog.rev(node)).zfill(revwidth or 0))
861 861 if total is not None:
862 862 expander['N'] = lambda: str(total)
863 863 if seqno is not None:
864 864 expander['n'] = lambda: str(seqno)
865 865 if total is not None and seqno is not None:
866 866 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
867 867 if pathname is not None:
868 868 expander['s'] = lambda: os.path.basename(pathname)
869 869 expander['d'] = lambda: os.path.dirname(pathname) or '.'
870 870 expander['p'] = lambda: pathname
871 871
872 872 newname = []
873 873 patlen = len(pat)
874 874 i = 0
875 875 while i < patlen:
876 876 c = pat[i:i + 1]
877 877 if c == '%':
878 878 i += 1
879 879 c = pat[i:i + 1]
880 880 c = expander[c]()
881 881 newname.append(c)
882 882 i += 1
883 883 return ''.join(newname)
884 884 except KeyError as inst:
885 885 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
886 886 inst.args[0])
887 887
888 888 def isstdiofilename(pat):
889 889 """True if the given pat looks like a filename denoting stdin/stdout"""
890 890 return not pat or pat == '-'
891 891
892 892 class _unclosablefile(object):
893 893 def __init__(self, fp):
894 894 self._fp = fp
895 895
896 896 def close(self):
897 897 pass
898 898
899 899 def __iter__(self):
900 900 return iter(self._fp)
901 901
902 902 def __getattr__(self, attr):
903 903 return getattr(self._fp, attr)
904 904
905 905 def __enter__(self):
906 906 return self
907 907
908 908 def __exit__(self, exc_type, exc_value, exc_tb):
909 909 pass
910 910
911 911 def makefileobj(repo, pat, node=None, desc=None, total=None,
912 912 seqno=None, revwidth=None, mode='wb', modemap=None,
913 913 pathname=None):
914 914
915 915 writable = mode not in ('r', 'rb')
916 916
917 917 if isstdiofilename(pat):
918 918 if writable:
919 919 fp = repo.ui.fout
920 920 else:
921 921 fp = repo.ui.fin
922 922 return _unclosablefile(fp)
923 923 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
924 924 if modemap is not None:
925 925 mode = modemap.get(fn, mode)
926 926 if mode == 'wb':
927 927 modemap[fn] = 'ab'
928 928 return open(fn, mode)
929 929
930 930 def openrevlog(repo, cmd, file_, opts):
931 931 """opens the changelog, manifest, a filelog or a given revlog"""
932 932 cl = opts['changelog']
933 933 mf = opts['manifest']
934 934 dir = opts['dir']
935 935 msg = None
936 936 if cl and mf:
937 937 msg = _('cannot specify --changelog and --manifest at the same time')
938 938 elif cl and dir:
939 939 msg = _('cannot specify --changelog and --dir at the same time')
940 940 elif cl or mf or dir:
941 941 if file_:
942 942 msg = _('cannot specify filename with --changelog or --manifest')
943 943 elif not repo:
944 944 msg = _('cannot specify --changelog or --manifest or --dir '
945 945 'without a repository')
946 946 if msg:
947 947 raise error.Abort(msg)
948 948
949 949 r = None
950 950 if repo:
951 951 if cl:
952 952 r = repo.unfiltered().changelog
953 953 elif dir:
954 954 if 'treemanifest' not in repo.requirements:
955 955 raise error.Abort(_("--dir can only be used on repos with "
956 956 "treemanifest enabled"))
957 957 dirlog = repo.manifestlog._revlog.dirlog(dir)
958 958 if len(dirlog):
959 959 r = dirlog
960 960 elif mf:
961 961 r = repo.manifestlog._revlog
962 962 elif file_:
963 963 filelog = repo.file(file_)
964 964 if len(filelog):
965 965 r = filelog
966 966 if not r:
967 967 if not file_:
968 968 raise error.CommandError(cmd, _('invalid arguments'))
969 969 if not os.path.isfile(file_):
970 970 raise error.Abort(_("revlog '%s' not found") % file_)
971 971 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
972 972 file_[:-2] + ".i")
973 973 return r
974 974
975 975 def copy(ui, repo, pats, opts, rename=False):
976 976 # called with the repo lock held
977 977 #
978 978 # hgsep => pathname that uses "/" to separate directories
979 979 # ossep => pathname that uses os.sep to separate directories
980 980 cwd = repo.getcwd()
981 981 targets = {}
982 982 after = opts.get("after")
983 983 dryrun = opts.get("dry_run")
984 984 wctx = repo[None]
985 985
986 986 def walkpat(pat):
987 987 srcs = []
988 988 if after:
989 989 badstates = '?'
990 990 else:
991 991 badstates = '?r'
992 992 m = scmutil.match(wctx, [pat], opts, globbed=True)
993 993 for abs in wctx.walk(m):
994 994 state = repo.dirstate[abs]
995 995 rel = m.rel(abs)
996 996 exact = m.exact(abs)
997 997 if state in badstates:
998 998 if exact and state == '?':
999 999 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1000 1000 if exact and state == 'r':
1001 1001 ui.warn(_('%s: not copying - file has been marked for'
1002 1002 ' remove\n') % rel)
1003 1003 continue
1004 1004 # abs: hgsep
1005 1005 # rel: ossep
1006 1006 srcs.append((abs, rel, exact))
1007 1007 return srcs
1008 1008
1009 1009 # abssrc: hgsep
1010 1010 # relsrc: ossep
1011 1011 # otarget: ossep
1012 1012 def copyfile(abssrc, relsrc, otarget, exact):
1013 1013 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1014 1014 if '/' in abstarget:
1015 1015 # We cannot normalize abstarget itself, this would prevent
1016 1016 # case only renames, like a => A.
1017 1017 abspath, absname = abstarget.rsplit('/', 1)
1018 1018 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1019 1019 reltarget = repo.pathto(abstarget, cwd)
1020 1020 target = repo.wjoin(abstarget)
1021 1021 src = repo.wjoin(abssrc)
1022 1022 state = repo.dirstate[abstarget]
1023 1023
1024 1024 scmutil.checkportable(ui, abstarget)
1025 1025
1026 1026 # check for collisions
1027 1027 prevsrc = targets.get(abstarget)
1028 1028 if prevsrc is not None:
1029 1029 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1030 1030 (reltarget, repo.pathto(abssrc, cwd),
1031 1031 repo.pathto(prevsrc, cwd)))
1032 1032 return
1033 1033
1034 1034 # check for overwrites
1035 1035 exists = os.path.lexists(target)
1036 1036 samefile = False
1037 1037 if exists and abssrc != abstarget:
1038 1038 if (repo.dirstate.normalize(abssrc) ==
1039 1039 repo.dirstate.normalize(abstarget)):
1040 1040 if not rename:
1041 1041 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1042 1042 return
1043 1043 exists = False
1044 1044 samefile = True
1045 1045
1046 1046 if not after and exists or after and state in 'mn':
1047 1047 if not opts['force']:
1048 1048 if state in 'mn':
1049 1049 msg = _('%s: not overwriting - file already committed\n')
1050 1050 if after:
1051 1051 flags = '--after --force'
1052 1052 else:
1053 1053 flags = '--force'
1054 1054 if rename:
1055 1055 hint = _('(hg rename %s to replace the file by '
1056 1056 'recording a rename)\n') % flags
1057 1057 else:
1058 1058 hint = _('(hg copy %s to replace the file by '
1059 1059 'recording a copy)\n') % flags
1060 1060 else:
1061 1061 msg = _('%s: not overwriting - file exists\n')
1062 1062 if rename:
1063 1063 hint = _('(hg rename --after to record the rename)\n')
1064 1064 else:
1065 1065 hint = _('(hg copy --after to record the copy)\n')
1066 1066 ui.warn(msg % reltarget)
1067 1067 ui.warn(hint)
1068 1068 return
1069 1069
1070 1070 if after:
1071 1071 if not exists:
1072 1072 if rename:
1073 1073 ui.warn(_('%s: not recording move - %s does not exist\n') %
1074 1074 (relsrc, reltarget))
1075 1075 else:
1076 1076 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1077 1077 (relsrc, reltarget))
1078 1078 return
1079 1079 elif not dryrun:
1080 1080 try:
1081 1081 if exists:
1082 1082 os.unlink(target)
1083 1083 targetdir = os.path.dirname(target) or '.'
1084 1084 if not os.path.isdir(targetdir):
1085 1085 os.makedirs(targetdir)
1086 1086 if samefile:
1087 1087 tmp = target + "~hgrename"
1088 1088 os.rename(src, tmp)
1089 1089 os.rename(tmp, target)
1090 1090 else:
1091 1091 util.copyfile(src, target)
1092 1092 srcexists = True
1093 1093 except IOError as inst:
1094 1094 if inst.errno == errno.ENOENT:
1095 1095 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1096 1096 srcexists = False
1097 1097 else:
1098 1098 ui.warn(_('%s: cannot copy - %s\n') %
1099 1099 (relsrc, inst.strerror))
1100 1100 return True # report a failure
1101 1101
1102 1102 if ui.verbose or not exact:
1103 1103 if rename:
1104 1104 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1105 1105 else:
1106 1106 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1107 1107
1108 1108 targets[abstarget] = abssrc
1109 1109
1110 1110 # fix up dirstate
1111 1111 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1112 1112 dryrun=dryrun, cwd=cwd)
1113 1113 if rename and not dryrun:
1114 1114 if not after and srcexists and not samefile:
1115 1115 repo.wvfs.unlinkpath(abssrc)
1116 1116 wctx.forget([abssrc])
1117 1117
1118 1118 # pat: ossep
1119 1119 # dest ossep
1120 1120 # srcs: list of (hgsep, hgsep, ossep, bool)
1121 1121 # return: function that takes hgsep and returns ossep
1122 1122 def targetpathfn(pat, dest, srcs):
1123 1123 if os.path.isdir(pat):
1124 1124 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1125 1125 abspfx = util.localpath(abspfx)
1126 1126 if destdirexists:
1127 1127 striplen = len(os.path.split(abspfx)[0])
1128 1128 else:
1129 1129 striplen = len(abspfx)
1130 1130 if striplen:
1131 1131 striplen += len(pycompat.ossep)
1132 1132 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1133 1133 elif destdirexists:
1134 1134 res = lambda p: os.path.join(dest,
1135 1135 os.path.basename(util.localpath(p)))
1136 1136 else:
1137 1137 res = lambda p: dest
1138 1138 return res
1139 1139
1140 1140 # pat: ossep
1141 1141 # dest ossep
1142 1142 # srcs: list of (hgsep, hgsep, ossep, bool)
1143 1143 # return: function that takes hgsep and returns ossep
1144 1144 def targetpathafterfn(pat, dest, srcs):
1145 1145 if matchmod.patkind(pat):
1146 1146 # a mercurial pattern
1147 1147 res = lambda p: os.path.join(dest,
1148 1148 os.path.basename(util.localpath(p)))
1149 1149 else:
1150 1150 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1151 1151 if len(abspfx) < len(srcs[0][0]):
1152 1152 # A directory. Either the target path contains the last
1153 1153 # component of the source path or it does not.
1154 1154 def evalpath(striplen):
1155 1155 score = 0
1156 1156 for s in srcs:
1157 1157 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1158 1158 if os.path.lexists(t):
1159 1159 score += 1
1160 1160 return score
1161 1161
1162 1162 abspfx = util.localpath(abspfx)
1163 1163 striplen = len(abspfx)
1164 1164 if striplen:
1165 1165 striplen += len(pycompat.ossep)
1166 1166 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1167 1167 score = evalpath(striplen)
1168 1168 striplen1 = len(os.path.split(abspfx)[0])
1169 1169 if striplen1:
1170 1170 striplen1 += len(pycompat.ossep)
1171 1171 if evalpath(striplen1) > score:
1172 1172 striplen = striplen1
1173 1173 res = lambda p: os.path.join(dest,
1174 1174 util.localpath(p)[striplen:])
1175 1175 else:
1176 1176 # a file
1177 1177 if destdirexists:
1178 1178 res = lambda p: os.path.join(dest,
1179 1179 os.path.basename(util.localpath(p)))
1180 1180 else:
1181 1181 res = lambda p: dest
1182 1182 return res
1183 1183
1184 1184 pats = scmutil.expandpats(pats)
1185 1185 if not pats:
1186 1186 raise error.Abort(_('no source or destination specified'))
1187 1187 if len(pats) == 1:
1188 1188 raise error.Abort(_('no destination specified'))
1189 1189 dest = pats.pop()
1190 1190 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1191 1191 if not destdirexists:
1192 1192 if len(pats) > 1 or matchmod.patkind(pats[0]):
1193 1193 raise error.Abort(_('with multiple sources, destination must be an '
1194 1194 'existing directory'))
1195 1195 if util.endswithsep(dest):
1196 1196 raise error.Abort(_('destination %s is not a directory') % dest)
1197 1197
1198 1198 tfn = targetpathfn
1199 1199 if after:
1200 1200 tfn = targetpathafterfn
1201 1201 copylist = []
1202 1202 for pat in pats:
1203 1203 srcs = walkpat(pat)
1204 1204 if not srcs:
1205 1205 continue
1206 1206 copylist.append((tfn(pat, dest, srcs), srcs))
1207 1207 if not copylist:
1208 1208 raise error.Abort(_('no files to copy'))
1209 1209
1210 1210 errors = 0
1211 1211 for targetpath, srcs in copylist:
1212 1212 for abssrc, relsrc, exact in srcs:
1213 1213 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1214 1214 errors += 1
1215 1215
1216 1216 if errors:
1217 1217 ui.warn(_('(consider using --after)\n'))
1218 1218
1219 1219 return errors != 0
1220 1220
1221 1221 ## facility to let extension process additional data into an import patch
1222 1222 # list of identifier to be executed in order
1223 1223 extrapreimport = [] # run before commit
1224 1224 extrapostimport = [] # run after commit
1225 1225 # mapping from identifier to actual import function
1226 1226 #
1227 1227 # 'preimport' are run before the commit is made and are provided the following
1228 1228 # arguments:
1229 1229 # - repo: the localrepository instance,
1230 1230 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1231 1231 # - extra: the future extra dictionary of the changeset, please mutate it,
1232 1232 # - opts: the import options.
1233 1233 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1234 1234 # mutation of in memory commit and more. Feel free to rework the code to get
1235 1235 # there.
1236 1236 extrapreimportmap = {}
1237 1237 # 'postimport' are run after the commit is made and are provided the following
1238 1238 # argument:
1239 1239 # - ctx: the changectx created by import.
1240 1240 extrapostimportmap = {}
1241 1241
1242 1242 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1243 1243 """Utility function used by commands.import to import a single patch
1244 1244
1245 1245 This function is explicitly defined here to help the evolve extension to
1246 1246 wrap this part of the import logic.
1247 1247
1248 1248 The API is currently a bit ugly because it a simple code translation from
1249 1249 the import command. Feel free to make it better.
1250 1250
1251 1251 :hunk: a patch (as a binary string)
1252 1252 :parents: nodes that will be parent of the created commit
1253 1253 :opts: the full dict of option passed to the import command
1254 1254 :msgs: list to save commit message to.
1255 1255 (used in case we need to save it when failing)
1256 1256 :updatefunc: a function that update a repo to a given node
1257 1257 updatefunc(<repo>, <node>)
1258 1258 """
1259 1259 # avoid cycle context -> subrepo -> cmdutil
1260 1260 from . import context
1261 1261 extractdata = patch.extract(ui, hunk)
1262 1262 tmpname = extractdata.get('filename')
1263 1263 message = extractdata.get('message')
1264 1264 user = opts.get('user') or extractdata.get('user')
1265 1265 date = opts.get('date') or extractdata.get('date')
1266 1266 branch = extractdata.get('branch')
1267 1267 nodeid = extractdata.get('nodeid')
1268 1268 p1 = extractdata.get('p1')
1269 1269 p2 = extractdata.get('p2')
1270 1270
1271 1271 nocommit = opts.get('no_commit')
1272 1272 importbranch = opts.get('import_branch')
1273 1273 update = not opts.get('bypass')
1274 1274 strip = opts["strip"]
1275 1275 prefix = opts["prefix"]
1276 1276 sim = float(opts.get('similarity') or 0)
1277 1277 if not tmpname:
1278 1278 return (None, None, False)
1279 1279
1280 1280 rejects = False
1281 1281
1282 1282 try:
1283 1283 cmdline_message = logmessage(ui, opts)
1284 1284 if cmdline_message:
1285 1285 # pickup the cmdline msg
1286 1286 message = cmdline_message
1287 1287 elif message:
1288 1288 # pickup the patch msg
1289 1289 message = message.strip()
1290 1290 else:
1291 1291 # launch the editor
1292 1292 message = None
1293 1293 ui.debug('message:\n%s\n' % message)
1294 1294
1295 1295 if len(parents) == 1:
1296 1296 parents.append(repo[nullid])
1297 1297 if opts.get('exact'):
1298 1298 if not nodeid or not p1:
1299 1299 raise error.Abort(_('not a Mercurial patch'))
1300 1300 p1 = repo[p1]
1301 1301 p2 = repo[p2 or nullid]
1302 1302 elif p2:
1303 1303 try:
1304 1304 p1 = repo[p1]
1305 1305 p2 = repo[p2]
1306 1306 # Without any options, consider p2 only if the
1307 1307 # patch is being applied on top of the recorded
1308 1308 # first parent.
1309 1309 if p1 != parents[0]:
1310 1310 p1 = parents[0]
1311 1311 p2 = repo[nullid]
1312 1312 except error.RepoError:
1313 1313 p1, p2 = parents
1314 1314 if p2.node() == nullid:
1315 1315 ui.warn(_("warning: import the patch as a normal revision\n"
1316 1316 "(use --exact to import the patch as a merge)\n"))
1317 1317 else:
1318 1318 p1, p2 = parents
1319 1319
1320 1320 n = None
1321 1321 if update:
1322 1322 if p1 != parents[0]:
1323 1323 updatefunc(repo, p1.node())
1324 1324 if p2 != parents[1]:
1325 1325 repo.setparents(p1.node(), p2.node())
1326 1326
1327 1327 if opts.get('exact') or importbranch:
1328 1328 repo.dirstate.setbranch(branch or 'default')
1329 1329
1330 1330 partial = opts.get('partial', False)
1331 1331 files = set()
1332 1332 try:
1333 1333 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1334 1334 files=files, eolmode=None, similarity=sim / 100.0)
1335 1335 except patch.PatchError as e:
1336 1336 if not partial:
1337 1337 raise error.Abort(str(e))
1338 1338 if partial:
1339 1339 rejects = True
1340 1340
1341 1341 files = list(files)
1342 1342 if nocommit:
1343 1343 if message:
1344 1344 msgs.append(message)
1345 1345 else:
1346 1346 if opts.get('exact') or p2:
1347 1347 # If you got here, you either use --force and know what
1348 1348 # you are doing or used --exact or a merge patch while
1349 1349 # being updated to its first parent.
1350 1350 m = None
1351 1351 else:
1352 1352 m = scmutil.matchfiles(repo, files or [])
1353 1353 editform = mergeeditform(repo[None], 'import.normal')
1354 1354 if opts.get('exact'):
1355 1355 editor = None
1356 1356 else:
1357 1357 editor = getcommiteditor(editform=editform, **opts)
1358 1358 extra = {}
1359 1359 for idfunc in extrapreimport:
1360 1360 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1361 1361 overrides = {}
1362 1362 if partial:
1363 1363 overrides[('ui', 'allowemptycommit')] = True
1364 1364 with repo.ui.configoverride(overrides, 'import'):
1365 1365 n = repo.commit(message, user,
1366 1366 date, match=m,
1367 1367 editor=editor, extra=extra)
1368 1368 for idfunc in extrapostimport:
1369 1369 extrapostimportmap[idfunc](repo[n])
1370 1370 else:
1371 1371 if opts.get('exact') or importbranch:
1372 1372 branch = branch or 'default'
1373 1373 else:
1374 1374 branch = p1.branch()
1375 1375 store = patch.filestore()
1376 1376 try:
1377 1377 files = set()
1378 1378 try:
1379 1379 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1380 1380 files, eolmode=None)
1381 1381 except patch.PatchError as e:
1382 1382 raise error.Abort(str(e))
1383 1383 if opts.get('exact'):
1384 1384 editor = None
1385 1385 else:
1386 1386 editor = getcommiteditor(editform='import.bypass')
1387 1387 memctx = context.memctx(repo, (p1.node(), p2.node()),
1388 1388 message,
1389 1389 files=files,
1390 1390 filectxfn=store,
1391 1391 user=user,
1392 1392 date=date,
1393 1393 branch=branch,
1394 1394 editor=editor)
1395 1395 n = memctx.commit()
1396 1396 finally:
1397 1397 store.close()
1398 1398 if opts.get('exact') and nocommit:
1399 1399 # --exact with --no-commit is still useful in that it does merge
1400 1400 # and branch bits
1401 1401 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1402 1402 elif opts.get('exact') and hex(n) != nodeid:
1403 1403 raise error.Abort(_('patch is damaged or loses information'))
1404 1404 msg = _('applied to working directory')
1405 1405 if n:
1406 1406 # i18n: refers to a short changeset id
1407 1407 msg = _('created %s') % short(n)
1408 1408 return (msg, n, rejects)
1409 1409 finally:
1410 1410 os.unlink(tmpname)
1411 1411
1412 1412 # facility to let extensions include additional data in an exported patch
1413 1413 # list of identifiers to be executed in order
1414 1414 extraexport = []
1415 1415 # mapping from identifier to actual export function
1416 1416 # function as to return a string to be added to the header or None
1417 1417 # it is given two arguments (sequencenumber, changectx)
1418 1418 extraexportmap = {}
1419 1419
1420 1420 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1421 1421 node = scmutil.binnode(ctx)
1422 1422 parents = [p.node() for p in ctx.parents() if p]
1423 1423 branch = ctx.branch()
1424 1424 if switch_parent:
1425 1425 parents.reverse()
1426 1426
1427 1427 if parents:
1428 1428 prev = parents[0]
1429 1429 else:
1430 1430 prev = nullid
1431 1431
1432 1432 write("# HG changeset patch\n")
1433 1433 write("# User %s\n" % ctx.user())
1434 1434 write("# Date %d %d\n" % ctx.date())
1435 1435 write("# %s\n" % util.datestr(ctx.date()))
1436 1436 if branch and branch != 'default':
1437 1437 write("# Branch %s\n" % branch)
1438 1438 write("# Node ID %s\n" % hex(node))
1439 1439 write("# Parent %s\n" % hex(prev))
1440 1440 if len(parents) > 1:
1441 1441 write("# Parent %s\n" % hex(parents[1]))
1442 1442
1443 1443 for headerid in extraexport:
1444 1444 header = extraexportmap[headerid](seqno, ctx)
1445 1445 if header is not None:
1446 1446 write('# %s\n' % header)
1447 1447 write(ctx.description().rstrip())
1448 1448 write("\n\n")
1449 1449
1450 1450 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1451 1451 write(chunk, label=label)
1452 1452
1453 1453 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1454 1454 opts=None, match=None):
1455 1455 '''export changesets as hg patches
1456 1456
1457 1457 Args:
1458 1458 repo: The repository from which we're exporting revisions.
1459 1459 revs: A list of revisions to export as revision numbers.
1460 1460 fntemplate: An optional string to use for generating patch file names.
1461 1461 fp: An optional file-like object to which patches should be written.
1462 1462 switch_parent: If True, show diffs against second parent when not nullid.
1463 1463 Default is false, which always shows diff against p1.
1464 1464 opts: diff options to use for generating the patch.
1465 1465 match: If specified, only export changes to files matching this matcher.
1466 1466
1467 1467 Returns:
1468 1468 Nothing.
1469 1469
1470 1470 Side Effect:
1471 1471 "HG Changeset Patch" data is emitted to one of the following
1472 1472 destinations:
1473 1473 fp is specified: All revs are written to the specified
1474 1474 file-like object.
1475 1475 fntemplate specified: Each rev is written to a unique file named using
1476 1476 the given template.
1477 1477 Neither fp nor template specified: All revs written to repo.ui.write()
1478 1478 '''
1479 1479
1480 1480 total = len(revs)
1481 1481 revwidth = max(len(str(rev)) for rev in revs)
1482 1482 filemode = {}
1483 1483
1484 1484 write = None
1485 1485 dest = '<unnamed>'
1486 1486 if fp:
1487 1487 dest = getattr(fp, 'name', dest)
1488 1488 def write(s, **kw):
1489 1489 fp.write(s)
1490 1490 elif not fntemplate:
1491 1491 write = repo.ui.write
1492 1492
1493 1493 for seqno, rev in enumerate(revs, 1):
1494 1494 ctx = repo[rev]
1495 1495 fo = None
1496 1496 if not fp and fntemplate:
1497 1497 desc_lines = ctx.description().rstrip().split('\n')
1498 1498 desc = desc_lines[0] #Commit always has a first line.
1499 1499 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1500 1500 total=total, seqno=seqno, revwidth=revwidth,
1501 1501 mode='wb', modemap=filemode)
1502 1502 dest = fo.name
1503 1503 def write(s, **kw):
1504 1504 fo.write(s)
1505 1505 if not dest.startswith('<'):
1506 1506 repo.ui.note("%s\n" % dest)
1507 1507 _exportsingle(
1508 1508 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1509 1509 if fo is not None:
1510 1510 fo.close()
1511 1511
1512 1512 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1513 1513 changes=None, stat=False, fp=None, prefix='',
1514 1514 root='', listsubrepos=False):
1515 1515 '''show diff or diffstat.'''
1516 1516 if fp is None:
1517 1517 write = ui.write
1518 1518 else:
1519 1519 def write(s, **kw):
1520 1520 fp.write(s)
1521 1521
1522 1522 if root:
1523 1523 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1524 1524 else:
1525 1525 relroot = ''
1526 1526 if relroot != '':
1527 1527 # XXX relative roots currently don't work if the root is within a
1528 1528 # subrepo
1529 1529 uirelroot = match.uipath(relroot)
1530 1530 relroot += '/'
1531 1531 for matchroot in match.files():
1532 1532 if not matchroot.startswith(relroot):
1533 1533 ui.warn(_('warning: %s not inside relative root %s\n') % (
1534 1534 match.uipath(matchroot), uirelroot))
1535 1535
1536 1536 if stat:
1537 1537 diffopts = diffopts.copy(context=0)
1538 1538 width = 80
1539 1539 if not ui.plain():
1540 1540 width = ui.termwidth()
1541 1541 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1542 1542 prefix=prefix, relroot=relroot)
1543 1543 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1544 1544 width=width):
1545 1545 write(chunk, label=label)
1546 1546 else:
1547 1547 for chunk, label in patch.diffui(repo, node1, node2, match,
1548 1548 changes, diffopts, prefix=prefix,
1549 1549 relroot=relroot):
1550 1550 write(chunk, label=label)
1551 1551
1552 1552 if listsubrepos:
1553 1553 ctx1 = repo[node1]
1554 1554 ctx2 = repo[node2]
1555 1555 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1556 1556 tempnode2 = node2
1557 1557 try:
1558 1558 if node2 is not None:
1559 1559 tempnode2 = ctx2.substate[subpath][1]
1560 1560 except KeyError:
1561 1561 # A subrepo that existed in node1 was deleted between node1 and
1562 1562 # node2 (inclusive). Thus, ctx2's substate won't contain that
1563 1563 # subpath. The best we can do is to ignore it.
1564 1564 tempnode2 = None
1565 1565 submatch = matchmod.subdirmatcher(subpath, match)
1566 1566 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1567 1567 stat=stat, fp=fp, prefix=prefix)
1568 1568
1569 1569 def _changesetlabels(ctx):
1570 1570 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1571 1571 if ctx.obsolete():
1572 1572 labels.append('changeset.obsolete')
1573 1573 if ctx.isunstable():
1574 1574 labels.append('changeset.unstable')
1575 1575 for instability in ctx.instabilities():
1576 1576 labels.append('instability.%s' % instability)
1577 1577 return ' '.join(labels)
1578 1578
1579 1579 class changeset_printer(object):
1580 1580 '''show changeset information when templating not requested.'''
1581 1581
1582 1582 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1583 1583 self.ui = ui
1584 1584 self.repo = repo
1585 1585 self.buffered = buffered
1586 1586 self.matchfn = matchfn
1587 1587 self.diffopts = diffopts
1588 1588 self.header = {}
1589 1589 self.hunk = {}
1590 1590 self.lastheader = None
1591 1591 self.footer = None
1592 1592
1593 1593 def flush(self, ctx):
1594 1594 rev = ctx.rev()
1595 1595 if rev in self.header:
1596 1596 h = self.header[rev]
1597 1597 if h != self.lastheader:
1598 1598 self.lastheader = h
1599 1599 self.ui.write(h)
1600 1600 del self.header[rev]
1601 1601 if rev in self.hunk:
1602 1602 self.ui.write(self.hunk[rev])
1603 1603 del self.hunk[rev]
1604 1604 return 1
1605 1605 return 0
1606 1606
1607 1607 def close(self):
1608 1608 if self.footer:
1609 1609 self.ui.write(self.footer)
1610 1610
1611 1611 def show(self, ctx, copies=None, matchfn=None, **props):
1612 1612 props = pycompat.byteskwargs(props)
1613 1613 if self.buffered:
1614 1614 self.ui.pushbuffer(labeled=True)
1615 1615 self._show(ctx, copies, matchfn, props)
1616 1616 self.hunk[ctx.rev()] = self.ui.popbuffer()
1617 1617 else:
1618 1618 self._show(ctx, copies, matchfn, props)
1619 1619
1620 1620 def _show(self, ctx, copies, matchfn, props):
1621 1621 '''show a single changeset or file revision'''
1622 1622 changenode = ctx.node()
1623 1623 rev = ctx.rev()
1624 1624 if self.ui.debugflag:
1625 1625 hexfunc = hex
1626 1626 else:
1627 1627 hexfunc = short
1628 1628 # as of now, wctx.node() and wctx.rev() return None, but we want to
1629 1629 # show the same values as {node} and {rev} templatekw
1630 1630 revnode = (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
1631 1631
1632 1632 if self.ui.quiet:
1633 1633 self.ui.write("%d:%s\n" % revnode, label='log.node')
1634 1634 return
1635 1635
1636 1636 date = util.datestr(ctx.date())
1637 1637
1638 1638 # i18n: column positioning for "hg log"
1639 1639 self.ui.write(_("changeset: %d:%s\n") % revnode,
1640 1640 label=_changesetlabels(ctx))
1641 1641
1642 1642 # branches are shown first before any other names due to backwards
1643 1643 # compatibility
1644 1644 branch = ctx.branch()
1645 1645 # don't show the default branch name
1646 1646 if branch != 'default':
1647 1647 # i18n: column positioning for "hg log"
1648 1648 self.ui.write(_("branch: %s\n") % branch,
1649 1649 label='log.branch')
1650 1650
1651 1651 for nsname, ns in self.repo.names.iteritems():
1652 1652 # branches has special logic already handled above, so here we just
1653 1653 # skip it
1654 1654 if nsname == 'branches':
1655 1655 continue
1656 1656 # we will use the templatename as the color name since those two
1657 1657 # should be the same
1658 1658 for name in ns.names(self.repo, changenode):
1659 1659 self.ui.write(ns.logfmt % name,
1660 1660 label='log.%s' % ns.colorname)
1661 1661 if self.ui.debugflag:
1662 1662 # i18n: column positioning for "hg log"
1663 1663 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1664 1664 label='log.phase')
1665 1665 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1666 1666 label = 'log.parent changeset.%s' % pctx.phasestr()
1667 1667 # i18n: column positioning for "hg log"
1668 1668 self.ui.write(_("parent: %d:%s\n")
1669 1669 % (pctx.rev(), hexfunc(pctx.node())),
1670 1670 label=label)
1671 1671
1672 1672 if self.ui.debugflag and rev is not None:
1673 1673 mnode = ctx.manifestnode()
1674 1674 # i18n: column positioning for "hg log"
1675 1675 self.ui.write(_("manifest: %d:%s\n") %
1676 1676 (self.repo.manifestlog._revlog.rev(mnode),
1677 1677 hex(mnode)),
1678 1678 label='ui.debug log.manifest')
1679 1679 # i18n: column positioning for "hg log"
1680 1680 self.ui.write(_("user: %s\n") % ctx.user(),
1681 1681 label='log.user')
1682 1682 # i18n: column positioning for "hg log"
1683 1683 self.ui.write(_("date: %s\n") % date,
1684 1684 label='log.date')
1685 1685
1686 1686 if ctx.isunstable():
1687 1687 # i18n: column positioning for "hg log"
1688 1688 instabilities = ctx.instabilities()
1689 1689 self.ui.write(_("instability: %s\n") % ', '.join(instabilities),
1690 1690 label='log.instability')
1691 1691
1692 1692 self._exthook(ctx)
1693 1693
1694 1694 if self.ui.debugflag:
1695 1695 files = ctx.p1().status(ctx)[:3]
1696 1696 for key, value in zip([# i18n: column positioning for "hg log"
1697 1697 _("files:"),
1698 1698 # i18n: column positioning for "hg log"
1699 1699 _("files+:"),
1700 1700 # i18n: column positioning for "hg log"
1701 1701 _("files-:")], files):
1702 1702 if value:
1703 1703 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1704 1704 label='ui.debug log.files')
1705 1705 elif ctx.files() and self.ui.verbose:
1706 1706 # i18n: column positioning for "hg log"
1707 1707 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1708 1708 label='ui.note log.files')
1709 1709 if copies and self.ui.verbose:
1710 1710 copies = ['%s (%s)' % c for c in copies]
1711 1711 # i18n: column positioning for "hg log"
1712 1712 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1713 1713 label='ui.note log.copies')
1714 1714
1715 1715 extra = ctx.extra()
1716 1716 if extra and self.ui.debugflag:
1717 1717 for key, value in sorted(extra.items()):
1718 1718 # i18n: column positioning for "hg log"
1719 1719 self.ui.write(_("extra: %s=%s\n")
1720 1720 % (key, util.escapestr(value)),
1721 1721 label='ui.debug log.extra')
1722 1722
1723 1723 description = ctx.description().strip()
1724 1724 if description:
1725 1725 if self.ui.verbose:
1726 1726 self.ui.write(_("description:\n"),
1727 1727 label='ui.note log.description')
1728 1728 self.ui.write(description,
1729 1729 label='ui.note log.description')
1730 1730 self.ui.write("\n\n")
1731 1731 else:
1732 1732 # i18n: column positioning for "hg log"
1733 1733 self.ui.write(_("summary: %s\n") %
1734 1734 description.splitlines()[0],
1735 1735 label='log.summary')
1736 1736 self.ui.write("\n")
1737 1737
1738 1738 self.showpatch(ctx, matchfn)
1739 1739
1740 1740 def _exthook(self, ctx):
1741 1741 '''empty method used by extension as a hook point
1742 1742 '''
1743 1743 pass
1744 1744
1745 1745 def showpatch(self, ctx, matchfn):
1746 1746 if not matchfn:
1747 1747 matchfn = self.matchfn
1748 1748 if matchfn:
1749 1749 stat = self.diffopts.get('stat')
1750 1750 diff = self.diffopts.get('patch')
1751 1751 diffopts = patch.diffallopts(self.ui, self.diffopts)
1752 1752 node = ctx.node()
1753 1753 prev = ctx.p1().node()
1754 1754 if stat:
1755 1755 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1756 1756 match=matchfn, stat=True)
1757 1757 if diff:
1758 1758 if stat:
1759 1759 self.ui.write("\n")
1760 1760 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1761 1761 match=matchfn, stat=False)
1762 1762 self.ui.write("\n")
1763 1763
1764 1764 class jsonchangeset(changeset_printer):
1765 1765 '''format changeset information.'''
1766 1766
1767 1767 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1768 1768 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1769 1769 self.cache = {}
1770 1770 self._first = True
1771 1771
1772 1772 def close(self):
1773 1773 if not self._first:
1774 1774 self.ui.write("\n]\n")
1775 1775 else:
1776 1776 self.ui.write("[]\n")
1777 1777
1778 1778 def _show(self, ctx, copies, matchfn, props):
1779 1779 '''show a single changeset or file revision'''
1780 1780 rev = ctx.rev()
1781 1781 if rev is None:
1782 1782 jrev = jnode = 'null'
1783 1783 else:
1784 1784 jrev = '%d' % rev
1785 1785 jnode = '"%s"' % hex(ctx.node())
1786 1786 j = encoding.jsonescape
1787 1787
1788 1788 if self._first:
1789 1789 self.ui.write("[\n {")
1790 1790 self._first = False
1791 1791 else:
1792 1792 self.ui.write(",\n {")
1793 1793
1794 1794 if self.ui.quiet:
1795 1795 self.ui.write(('\n "rev": %s') % jrev)
1796 1796 self.ui.write((',\n "node": %s') % jnode)
1797 1797 self.ui.write('\n }')
1798 1798 return
1799 1799
1800 1800 self.ui.write(('\n "rev": %s') % jrev)
1801 1801 self.ui.write((',\n "node": %s') % jnode)
1802 1802 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1803 1803 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1804 1804 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1805 1805 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1806 1806 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1807 1807
1808 1808 self.ui.write((',\n "bookmarks": [%s]') %
1809 1809 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1810 1810 self.ui.write((',\n "tags": [%s]') %
1811 1811 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1812 1812 self.ui.write((',\n "parents": [%s]') %
1813 1813 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1814 1814
1815 1815 if self.ui.debugflag:
1816 1816 if rev is None:
1817 1817 jmanifestnode = 'null'
1818 1818 else:
1819 1819 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1820 1820 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1821 1821
1822 1822 self.ui.write((',\n "extra": {%s}') %
1823 1823 ", ".join('"%s": "%s"' % (j(k), j(v))
1824 1824 for k, v in ctx.extra().items()))
1825 1825
1826 1826 files = ctx.p1().status(ctx)
1827 1827 self.ui.write((',\n "modified": [%s]') %
1828 1828 ", ".join('"%s"' % j(f) for f in files[0]))
1829 1829 self.ui.write((',\n "added": [%s]') %
1830 1830 ", ".join('"%s"' % j(f) for f in files[1]))
1831 1831 self.ui.write((',\n "removed": [%s]') %
1832 1832 ", ".join('"%s"' % j(f) for f in files[2]))
1833 1833
1834 1834 elif self.ui.verbose:
1835 1835 self.ui.write((',\n "files": [%s]') %
1836 1836 ", ".join('"%s"' % j(f) for f in ctx.files()))
1837 1837
1838 1838 if copies:
1839 1839 self.ui.write((',\n "copies": {%s}') %
1840 1840 ", ".join('"%s": "%s"' % (j(k), j(v))
1841 1841 for k, v in copies))
1842 1842
1843 1843 matchfn = self.matchfn
1844 1844 if matchfn:
1845 1845 stat = self.diffopts.get('stat')
1846 1846 diff = self.diffopts.get('patch')
1847 1847 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1848 1848 node, prev = ctx.node(), ctx.p1().node()
1849 1849 if stat:
1850 1850 self.ui.pushbuffer()
1851 1851 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1852 1852 match=matchfn, stat=True)
1853 1853 self.ui.write((',\n "diffstat": "%s"')
1854 1854 % j(self.ui.popbuffer()))
1855 1855 if diff:
1856 1856 self.ui.pushbuffer()
1857 1857 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1858 1858 match=matchfn, stat=False)
1859 1859 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1860 1860
1861 1861 self.ui.write("\n }")
1862 1862
1863 1863 class changeset_templater(changeset_printer):
1864 1864 '''format changeset information.'''
1865 1865
1866 1866 # Arguments before "buffered" used to be positional. Consider not
1867 1867 # adding/removing arguments before "buffered" to not break callers.
1868 1868 def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
1869 1869 buffered=False):
1870 1870 diffopts = diffopts or {}
1871 1871
1872 1872 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1873 1873 self.t = formatter.loadtemplater(ui, tmplspec,
1874 1874 cache=templatekw.defaulttempl)
1875 1875 self._counter = itertools.count()
1876 1876 self.cache = {}
1877 1877
1878 1878 self._tref = tmplspec.ref
1879 1879 self._parts = {'header': '', 'footer': '',
1880 1880 tmplspec.ref: tmplspec.ref,
1881 1881 'docheader': '', 'docfooter': '',
1882 1882 'separator': ''}
1883 1883 if tmplspec.mapfile:
1884 1884 # find correct templates for current mode, for backward
1885 1885 # compatibility with 'log -v/-q/--debug' using a mapfile
1886 1886 tmplmodes = [
1887 1887 (True, ''),
1888 1888 (self.ui.verbose, '_verbose'),
1889 1889 (self.ui.quiet, '_quiet'),
1890 1890 (self.ui.debugflag, '_debug'),
1891 1891 ]
1892 1892 for mode, postfix in tmplmodes:
1893 1893 for t in self._parts:
1894 1894 cur = t + postfix
1895 1895 if mode and cur in self.t:
1896 1896 self._parts[t] = cur
1897 1897 else:
1898 1898 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
1899 1899 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
1900 1900 self._parts.update(m)
1901 1901
1902 1902 if self._parts['docheader']:
1903 1903 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1904 1904
1905 1905 def close(self):
1906 1906 if self._parts['docfooter']:
1907 1907 if not self.footer:
1908 1908 self.footer = ""
1909 1909 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1910 1910 return super(changeset_templater, self).close()
1911 1911
1912 1912 def _show(self, ctx, copies, matchfn, props):
1913 1913 '''show a single changeset or file revision'''
1914 1914 props = props.copy()
1915 1915 props.update(templatekw.keywords)
1916 1916 props['templ'] = self.t
1917 1917 props['ctx'] = ctx
1918 1918 props['repo'] = self.repo
1919 1919 props['ui'] = self.repo.ui
1920 1920 props['index'] = index = next(self._counter)
1921 1921 props['revcache'] = {'copies': copies}
1922 1922 props['cache'] = self.cache
1923 1923 props = pycompat.strkwargs(props)
1924 1924
1925 1925 # write separator, which wouldn't work well with the header part below
1926 1926 # since there's inherently a conflict between header (across items) and
1927 1927 # separator (per item)
1928 1928 if self._parts['separator'] and index > 0:
1929 1929 self.ui.write(templater.stringify(self.t(self._parts['separator'])))
1930 1930
1931 1931 # write header
1932 1932 if self._parts['header']:
1933 1933 h = templater.stringify(self.t(self._parts['header'], **props))
1934 1934 if self.buffered:
1935 1935 self.header[ctx.rev()] = h
1936 1936 else:
1937 1937 if self.lastheader != h:
1938 1938 self.lastheader = h
1939 1939 self.ui.write(h)
1940 1940
1941 1941 # write changeset metadata, then patch if requested
1942 1942 key = self._parts[self._tref]
1943 1943 self.ui.write(templater.stringify(self.t(key, **props)))
1944 1944 self.showpatch(ctx, matchfn)
1945 1945
1946 1946 if self._parts['footer']:
1947 1947 if not self.footer:
1948 1948 self.footer = templater.stringify(
1949 1949 self.t(self._parts['footer'], **props))
1950 1950
1951 1951 def logtemplatespec(tmpl, mapfile):
1952 1952 if mapfile:
1953 1953 return formatter.templatespec('changeset', tmpl, mapfile)
1954 1954 else:
1955 1955 return formatter.templatespec('', tmpl, None)
1956 1956
1957 1957 def _lookuplogtemplate(ui, tmpl, style):
1958 1958 """Find the template matching the given template spec or style
1959 1959
1960 1960 See formatter.lookuptemplate() for details.
1961 1961 """
1962 1962
1963 1963 # ui settings
1964 1964 if not tmpl and not style: # template are stronger than style
1965 1965 tmpl = ui.config('ui', 'logtemplate')
1966 1966 if tmpl:
1967 1967 return logtemplatespec(templater.unquotestring(tmpl), None)
1968 1968 else:
1969 1969 style = util.expandpath(ui.config('ui', 'style'))
1970 1970
1971 1971 if not tmpl and style:
1972 1972 mapfile = style
1973 1973 if not os.path.split(mapfile)[0]:
1974 1974 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1975 1975 or templater.templatepath(mapfile))
1976 1976 if mapname:
1977 1977 mapfile = mapname
1978 1978 return logtemplatespec(None, mapfile)
1979 1979
1980 1980 if not tmpl:
1981 1981 return logtemplatespec(None, None)
1982 1982
1983 1983 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1984 1984
1985 1985 def makelogtemplater(ui, repo, tmpl, buffered=False):
1986 1986 """Create a changeset_templater from a literal template 'tmpl'"""
1987 1987 spec = logtemplatespec(tmpl, None)
1988 1988 return changeset_templater(ui, repo, spec, buffered=buffered)
1989 1989
1990 1990 def show_changeset(ui, repo, opts, buffered=False):
1991 1991 """show one changeset using template or regular display.
1992 1992
1993 1993 Display format will be the first non-empty hit of:
1994 1994 1. option 'template'
1995 1995 2. option 'style'
1996 1996 3. [ui] setting 'logtemplate'
1997 1997 4. [ui] setting 'style'
1998 1998 If all of these values are either the unset or the empty string,
1999 1999 regular display via changeset_printer() is done.
2000 2000 """
2001 2001 # options
2002 2002 matchfn = None
2003 2003 if opts.get('patch') or opts.get('stat'):
2004 2004 matchfn = scmutil.matchall(repo)
2005 2005
2006 2006 if opts.get('template') == 'json':
2007 2007 return jsonchangeset(ui, repo, matchfn, opts, buffered)
2008 2008
2009 2009 spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
2010 2010
2011 2011 if not spec.ref and not spec.tmpl and not spec.mapfile:
2012 2012 return changeset_printer(ui, repo, matchfn, opts, buffered)
2013 2013
2014 2014 return changeset_templater(ui, repo, spec, matchfn, opts, buffered)
2015 2015
2016 2016 def showmarker(fm, marker, index=None):
2017 2017 """utility function to display obsolescence marker in a readable way
2018 2018
2019 2019 To be used by debug function."""
2020 2020 if index is not None:
2021 2021 fm.write('index', '%i ', index)
2022 fm.write('precnode', '%s ', hex(marker.prednode()))
2022 fm.write('prednode', '%s ', hex(marker.prednode()))
2023 2023 succs = marker.succnodes()
2024 2024 fm.condwrite(succs, 'succnodes', '%s ',
2025 2025 fm.formatlist(map(hex, succs), name='node'))
2026 2026 fm.write('flag', '%X ', marker.flags())
2027 2027 parents = marker.parentnodes()
2028 2028 if parents is not None:
2029 2029 fm.write('parentnodes', '{%s} ',
2030 2030 fm.formatlist(map(hex, parents), name='node', sep=', '))
2031 2031 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
2032 2032 meta = marker.metadata().copy()
2033 2033 meta.pop('date', None)
2034 2034 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
2035 2035 fm.plain('\n')
2036 2036
2037 2037 def finddate(ui, repo, date):
2038 2038 """Find the tipmost changeset that matches the given date spec"""
2039 2039
2040 2040 df = util.matchdate(date)
2041 2041 m = scmutil.matchall(repo)
2042 2042 results = {}
2043 2043
2044 2044 def prep(ctx, fns):
2045 2045 d = ctx.date()
2046 2046 if df(d[0]):
2047 2047 results[ctx.rev()] = d
2048 2048
2049 2049 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
2050 2050 rev = ctx.rev()
2051 2051 if rev in results:
2052 2052 ui.status(_("found revision %s from %s\n") %
2053 2053 (rev, util.datestr(results[rev])))
2054 2054 return '%d' % rev
2055 2055
2056 2056 raise error.Abort(_("revision matching date not found"))
2057 2057
2058 2058 def increasingwindows(windowsize=8, sizelimit=512):
2059 2059 while True:
2060 2060 yield windowsize
2061 2061 if windowsize < sizelimit:
2062 2062 windowsize *= 2
2063 2063
2064 2064 class FileWalkError(Exception):
2065 2065 pass
2066 2066
2067 2067 def walkfilerevs(repo, match, follow, revs, fncache):
2068 2068 '''Walks the file history for the matched files.
2069 2069
2070 2070 Returns the changeset revs that are involved in the file history.
2071 2071
2072 2072 Throws FileWalkError if the file history can't be walked using
2073 2073 filelogs alone.
2074 2074 '''
2075 2075 wanted = set()
2076 2076 copies = []
2077 2077 minrev, maxrev = min(revs), max(revs)
2078 2078 def filerevgen(filelog, last):
2079 2079 """
2080 2080 Only files, no patterns. Check the history of each file.
2081 2081
2082 2082 Examines filelog entries within minrev, maxrev linkrev range
2083 2083 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2084 2084 tuples in backwards order
2085 2085 """
2086 2086 cl_count = len(repo)
2087 2087 revs = []
2088 2088 for j in xrange(0, last + 1):
2089 2089 linkrev = filelog.linkrev(j)
2090 2090 if linkrev < minrev:
2091 2091 continue
2092 2092 # only yield rev for which we have the changelog, it can
2093 2093 # happen while doing "hg log" during a pull or commit
2094 2094 if linkrev >= cl_count:
2095 2095 break
2096 2096
2097 2097 parentlinkrevs = []
2098 2098 for p in filelog.parentrevs(j):
2099 2099 if p != nullrev:
2100 2100 parentlinkrevs.append(filelog.linkrev(p))
2101 2101 n = filelog.node(j)
2102 2102 revs.append((linkrev, parentlinkrevs,
2103 2103 follow and filelog.renamed(n)))
2104 2104
2105 2105 return reversed(revs)
2106 2106 def iterfiles():
2107 2107 pctx = repo['.']
2108 2108 for filename in match.files():
2109 2109 if follow:
2110 2110 if filename not in pctx:
2111 2111 raise error.Abort(_('cannot follow file not in parent '
2112 2112 'revision: "%s"') % filename)
2113 2113 yield filename, pctx[filename].filenode()
2114 2114 else:
2115 2115 yield filename, None
2116 2116 for filename_node in copies:
2117 2117 yield filename_node
2118 2118
2119 2119 for file_, node in iterfiles():
2120 2120 filelog = repo.file(file_)
2121 2121 if not len(filelog):
2122 2122 if node is None:
2123 2123 # A zero count may be a directory or deleted file, so
2124 2124 # try to find matching entries on the slow path.
2125 2125 if follow:
2126 2126 raise error.Abort(
2127 2127 _('cannot follow nonexistent file: "%s"') % file_)
2128 2128 raise FileWalkError("Cannot walk via filelog")
2129 2129 else:
2130 2130 continue
2131 2131
2132 2132 if node is None:
2133 2133 last = len(filelog) - 1
2134 2134 else:
2135 2135 last = filelog.rev(node)
2136 2136
2137 2137 # keep track of all ancestors of the file
2138 2138 ancestors = {filelog.linkrev(last)}
2139 2139
2140 2140 # iterate from latest to oldest revision
2141 2141 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
2142 2142 if not follow:
2143 2143 if rev > maxrev:
2144 2144 continue
2145 2145 else:
2146 2146 # Note that last might not be the first interesting
2147 2147 # rev to us:
2148 2148 # if the file has been changed after maxrev, we'll
2149 2149 # have linkrev(last) > maxrev, and we still need
2150 2150 # to explore the file graph
2151 2151 if rev not in ancestors:
2152 2152 continue
2153 2153 # XXX insert 1327 fix here
2154 2154 if flparentlinkrevs:
2155 2155 ancestors.update(flparentlinkrevs)
2156 2156
2157 2157 fncache.setdefault(rev, []).append(file_)
2158 2158 wanted.add(rev)
2159 2159 if copied:
2160 2160 copies.append(copied)
2161 2161
2162 2162 return wanted
2163 2163
2164 2164 class _followfilter(object):
2165 2165 def __init__(self, repo, onlyfirst=False):
2166 2166 self.repo = repo
2167 2167 self.startrev = nullrev
2168 2168 self.roots = set()
2169 2169 self.onlyfirst = onlyfirst
2170 2170
2171 2171 def match(self, rev):
2172 2172 def realparents(rev):
2173 2173 if self.onlyfirst:
2174 2174 return self.repo.changelog.parentrevs(rev)[0:1]
2175 2175 else:
2176 2176 return filter(lambda x: x != nullrev,
2177 2177 self.repo.changelog.parentrevs(rev))
2178 2178
2179 2179 if self.startrev == nullrev:
2180 2180 self.startrev = rev
2181 2181 return True
2182 2182
2183 2183 if rev > self.startrev:
2184 2184 # forward: all descendants
2185 2185 if not self.roots:
2186 2186 self.roots.add(self.startrev)
2187 2187 for parent in realparents(rev):
2188 2188 if parent in self.roots:
2189 2189 self.roots.add(rev)
2190 2190 return True
2191 2191 else:
2192 2192 # backwards: all parents
2193 2193 if not self.roots:
2194 2194 self.roots.update(realparents(self.startrev))
2195 2195 if rev in self.roots:
2196 2196 self.roots.remove(rev)
2197 2197 self.roots.update(realparents(rev))
2198 2198 return True
2199 2199
2200 2200 return False
2201 2201
2202 2202 def walkchangerevs(repo, match, opts, prepare):
2203 2203 '''Iterate over files and the revs in which they changed.
2204 2204
2205 2205 Callers most commonly need to iterate backwards over the history
2206 2206 in which they are interested. Doing so has awful (quadratic-looking)
2207 2207 performance, so we use iterators in a "windowed" way.
2208 2208
2209 2209 We walk a window of revisions in the desired order. Within the
2210 2210 window, we first walk forwards to gather data, then in the desired
2211 2211 order (usually backwards) to display it.
2212 2212
2213 2213 This function returns an iterator yielding contexts. Before
2214 2214 yielding each context, the iterator will first call the prepare
2215 2215 function on each context in the window in forward order.'''
2216 2216
2217 2217 follow = opts.get('follow') or opts.get('follow_first')
2218 2218 revs = _logrevs(repo, opts)
2219 2219 if not revs:
2220 2220 return []
2221 2221 wanted = set()
2222 2222 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2223 2223 opts.get('removed'))
2224 2224 fncache = {}
2225 2225 change = repo.changectx
2226 2226
2227 2227 # First step is to fill wanted, the set of revisions that we want to yield.
2228 2228 # When it does not induce extra cost, we also fill fncache for revisions in
2229 2229 # wanted: a cache of filenames that were changed (ctx.files()) and that
2230 2230 # match the file filtering conditions.
2231 2231
2232 2232 if match.always():
2233 2233 # No files, no patterns. Display all revs.
2234 2234 wanted = revs
2235 2235 elif not slowpath:
2236 2236 # We only have to read through the filelog to find wanted revisions
2237 2237
2238 2238 try:
2239 2239 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2240 2240 except FileWalkError:
2241 2241 slowpath = True
2242 2242
2243 2243 # We decided to fall back to the slowpath because at least one
2244 2244 # of the paths was not a file. Check to see if at least one of them
2245 2245 # existed in history, otherwise simply return
2246 2246 for path in match.files():
2247 2247 if path == '.' or path in repo.store:
2248 2248 break
2249 2249 else:
2250 2250 return []
2251 2251
2252 2252 if slowpath:
2253 2253 # We have to read the changelog to match filenames against
2254 2254 # changed files
2255 2255
2256 2256 if follow:
2257 2257 raise error.Abort(_('can only follow copies/renames for explicit '
2258 2258 'filenames'))
2259 2259
2260 2260 # The slow path checks files modified in every changeset.
2261 2261 # This is really slow on large repos, so compute the set lazily.
2262 2262 class lazywantedset(object):
2263 2263 def __init__(self):
2264 2264 self.set = set()
2265 2265 self.revs = set(revs)
2266 2266
2267 2267 # No need to worry about locality here because it will be accessed
2268 2268 # in the same order as the increasing window below.
2269 2269 def __contains__(self, value):
2270 2270 if value in self.set:
2271 2271 return True
2272 2272 elif not value in self.revs:
2273 2273 return False
2274 2274 else:
2275 2275 self.revs.discard(value)
2276 2276 ctx = change(value)
2277 2277 matches = filter(match, ctx.files())
2278 2278 if matches:
2279 2279 fncache[value] = matches
2280 2280 self.set.add(value)
2281 2281 return True
2282 2282 return False
2283 2283
2284 2284 def discard(self, value):
2285 2285 self.revs.discard(value)
2286 2286 self.set.discard(value)
2287 2287
2288 2288 wanted = lazywantedset()
2289 2289
2290 2290 # it might be worthwhile to do this in the iterator if the rev range
2291 2291 # is descending and the prune args are all within that range
2292 2292 for rev in opts.get('prune', ()):
2293 2293 rev = repo[rev].rev()
2294 2294 ff = _followfilter(repo)
2295 2295 stop = min(revs[0], revs[-1])
2296 2296 for x in xrange(rev, stop - 1, -1):
2297 2297 if ff.match(x):
2298 2298 wanted = wanted - [x]
2299 2299
2300 2300 # Now that wanted is correctly initialized, we can iterate over the
2301 2301 # revision range, yielding only revisions in wanted.
2302 2302 def iterate():
2303 2303 if follow and match.always():
2304 2304 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2305 2305 def want(rev):
2306 2306 return ff.match(rev) and rev in wanted
2307 2307 else:
2308 2308 def want(rev):
2309 2309 return rev in wanted
2310 2310
2311 2311 it = iter(revs)
2312 2312 stopiteration = False
2313 2313 for windowsize in increasingwindows():
2314 2314 nrevs = []
2315 2315 for i in xrange(windowsize):
2316 2316 rev = next(it, None)
2317 2317 if rev is None:
2318 2318 stopiteration = True
2319 2319 break
2320 2320 elif want(rev):
2321 2321 nrevs.append(rev)
2322 2322 for rev in sorted(nrevs):
2323 2323 fns = fncache.get(rev)
2324 2324 ctx = change(rev)
2325 2325 if not fns:
2326 2326 def fns_generator():
2327 2327 for f in ctx.files():
2328 2328 if match(f):
2329 2329 yield f
2330 2330 fns = fns_generator()
2331 2331 prepare(ctx, fns)
2332 2332 for rev in nrevs:
2333 2333 yield change(rev)
2334 2334
2335 2335 if stopiteration:
2336 2336 break
2337 2337
2338 2338 return iterate()
2339 2339
2340 2340 def _makefollowlogfilematcher(repo, files, followfirst):
2341 2341 # When displaying a revision with --patch --follow FILE, we have
2342 2342 # to know which file of the revision must be diffed. With
2343 2343 # --follow, we want the names of the ancestors of FILE in the
2344 2344 # revision, stored in "fcache". "fcache" is populated by
2345 2345 # reproducing the graph traversal already done by --follow revset
2346 2346 # and relating revs to file names (which is not "correct" but
2347 2347 # good enough).
2348 2348 fcache = {}
2349 2349 fcacheready = [False]
2350 2350 pctx = repo['.']
2351 2351
2352 2352 def populate():
2353 2353 for fn in files:
2354 2354 fctx = pctx[fn]
2355 2355 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
2356 2356 for c in fctx.ancestors(followfirst=followfirst):
2357 2357 fcache.setdefault(c.rev(), set()).add(c.path())
2358 2358
2359 2359 def filematcher(rev):
2360 2360 if not fcacheready[0]:
2361 2361 # Lazy initialization
2362 2362 fcacheready[0] = True
2363 2363 populate()
2364 2364 return scmutil.matchfiles(repo, fcache.get(rev, []))
2365 2365
2366 2366 return filematcher
2367 2367
2368 2368 def _makenofollowlogfilematcher(repo, pats, opts):
2369 2369 '''hook for extensions to override the filematcher for non-follow cases'''
2370 2370 return None
2371 2371
2372 2372 def _makelogrevset(repo, pats, opts, revs):
2373 2373 """Return (expr, filematcher) where expr is a revset string built
2374 2374 from log options and file patterns or None. If --stat or --patch
2375 2375 are not passed filematcher is None. Otherwise it is a callable
2376 2376 taking a revision number and returning a match objects filtering
2377 2377 the files to be detailed when displaying the revision.
2378 2378 """
2379 2379 opt2revset = {
2380 2380 'no_merges': ('not merge()', None),
2381 2381 'only_merges': ('merge()', None),
2382 2382 '_ancestors': ('ancestors(%(val)s)', None),
2383 2383 '_fancestors': ('_firstancestors(%(val)s)', None),
2384 2384 '_descendants': ('descendants(%(val)s)', None),
2385 2385 '_fdescendants': ('_firstdescendants(%(val)s)', None),
2386 2386 '_matchfiles': ('_matchfiles(%(val)s)', None),
2387 2387 'date': ('date(%(val)r)', None),
2388 2388 'branch': ('branch(%(val)r)', ' or '),
2389 2389 '_patslog': ('filelog(%(val)r)', ' or '),
2390 2390 '_patsfollow': ('follow(%(val)r)', ' or '),
2391 2391 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
2392 2392 'keyword': ('keyword(%(val)r)', ' or '),
2393 2393 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
2394 2394 'user': ('user(%(val)r)', ' or '),
2395 2395 }
2396 2396
2397 2397 opts = dict(opts)
2398 2398 # follow or not follow?
2399 2399 follow = opts.get('follow') or opts.get('follow_first')
2400 2400 if opts.get('follow_first'):
2401 2401 followfirst = 1
2402 2402 else:
2403 2403 followfirst = 0
2404 2404 # --follow with FILE behavior depends on revs...
2405 2405 it = iter(revs)
2406 2406 startrev = next(it)
2407 2407 followdescendants = startrev < next(it, startrev)
2408 2408
2409 2409 # branch and only_branch are really aliases and must be handled at
2410 2410 # the same time
2411 2411 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2412 2412 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2413 2413 # pats/include/exclude are passed to match.match() directly in
2414 2414 # _matchfiles() revset but walkchangerevs() builds its matcher with
2415 2415 # scmutil.match(). The difference is input pats are globbed on
2416 2416 # platforms without shell expansion (windows).
2417 2417 wctx = repo[None]
2418 2418 match, pats = scmutil.matchandpats(wctx, pats, opts)
2419 2419 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2420 2420 opts.get('removed'))
2421 2421 if not slowpath:
2422 2422 for f in match.files():
2423 2423 if follow and f not in wctx:
2424 2424 # If the file exists, it may be a directory, so let it
2425 2425 # take the slow path.
2426 2426 if os.path.exists(repo.wjoin(f)):
2427 2427 slowpath = True
2428 2428 continue
2429 2429 else:
2430 2430 raise error.Abort(_('cannot follow file not in parent '
2431 2431 'revision: "%s"') % f)
2432 2432 filelog = repo.file(f)
2433 2433 if not filelog:
2434 2434 # A zero count may be a directory or deleted file, so
2435 2435 # try to find matching entries on the slow path.
2436 2436 if follow:
2437 2437 raise error.Abort(
2438 2438 _('cannot follow nonexistent file: "%s"') % f)
2439 2439 slowpath = True
2440 2440
2441 2441 # We decided to fall back to the slowpath because at least one
2442 2442 # of the paths was not a file. Check to see if at least one of them
2443 2443 # existed in history - in that case, we'll continue down the
2444 2444 # slowpath; otherwise, we can turn off the slowpath
2445 2445 if slowpath:
2446 2446 for path in match.files():
2447 2447 if path == '.' or path in repo.store:
2448 2448 break
2449 2449 else:
2450 2450 slowpath = False
2451 2451
2452 2452 fpats = ('_patsfollow', '_patsfollowfirst')
2453 2453 fnopats = (('_ancestors', '_fancestors'),
2454 2454 ('_descendants', '_fdescendants'))
2455 2455 if slowpath:
2456 2456 # See walkchangerevs() slow path.
2457 2457 #
2458 2458 # pats/include/exclude cannot be represented as separate
2459 2459 # revset expressions as their filtering logic applies at file
2460 2460 # level. For instance "-I a -X a" matches a revision touching
2461 2461 # "a" and "b" while "file(a) and not file(b)" does
2462 2462 # not. Besides, filesets are evaluated against the working
2463 2463 # directory.
2464 2464 matchargs = ['r:', 'd:relpath']
2465 2465 for p in pats:
2466 2466 matchargs.append('p:' + p)
2467 2467 for p in opts.get('include', []):
2468 2468 matchargs.append('i:' + p)
2469 2469 for p in opts.get('exclude', []):
2470 2470 matchargs.append('x:' + p)
2471 2471 matchargs = ','.join(('%r' % p) for p in matchargs)
2472 2472 opts['_matchfiles'] = matchargs
2473 2473 if follow:
2474 2474 opts[fnopats[0][followfirst]] = '.'
2475 2475 else:
2476 2476 if follow:
2477 2477 if pats:
2478 2478 # follow() revset interprets its file argument as a
2479 2479 # manifest entry, so use match.files(), not pats.
2480 2480 opts[fpats[followfirst]] = list(match.files())
2481 2481 else:
2482 2482 op = fnopats[followdescendants][followfirst]
2483 2483 opts[op] = 'rev(%d)' % startrev
2484 2484 else:
2485 2485 opts['_patslog'] = list(pats)
2486 2486
2487 2487 filematcher = None
2488 2488 if opts.get('patch') or opts.get('stat'):
2489 2489 # When following files, track renames via a special matcher.
2490 2490 # If we're forced to take the slowpath it means we're following
2491 2491 # at least one pattern/directory, so don't bother with rename tracking.
2492 2492 if follow and not match.always() and not slowpath:
2493 2493 # _makefollowlogfilematcher expects its files argument to be
2494 2494 # relative to the repo root, so use match.files(), not pats.
2495 2495 filematcher = _makefollowlogfilematcher(repo, match.files(),
2496 2496 followfirst)
2497 2497 else:
2498 2498 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2499 2499 if filematcher is None:
2500 2500 filematcher = lambda rev: match
2501 2501
2502 2502 expr = []
2503 2503 for op, val in sorted(opts.iteritems()):
2504 2504 if not val:
2505 2505 continue
2506 2506 if op not in opt2revset:
2507 2507 continue
2508 2508 revop, andor = opt2revset[op]
2509 2509 if '%(val)' not in revop:
2510 2510 expr.append(revop)
2511 2511 else:
2512 2512 if not isinstance(val, list):
2513 2513 e = revop % {'val': val}
2514 2514 else:
2515 2515 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2516 2516 expr.append(e)
2517 2517
2518 2518 if expr:
2519 2519 expr = '(' + ' and '.join(expr) + ')'
2520 2520 else:
2521 2521 expr = None
2522 2522 return expr, filematcher
2523 2523
2524 2524 def _logrevs(repo, opts):
2525 2525 # Default --rev value depends on --follow but --follow behavior
2526 2526 # depends on revisions resolved from --rev...
2527 2527 follow = opts.get('follow') or opts.get('follow_first')
2528 2528 if opts.get('rev'):
2529 2529 revs = scmutil.revrange(repo, opts['rev'])
2530 2530 elif follow and repo.dirstate.p1() == nullid:
2531 2531 revs = smartset.baseset()
2532 2532 elif follow:
2533 2533 revs = repo.revs('reverse(:.)')
2534 2534 else:
2535 2535 revs = smartset.spanset(repo)
2536 2536 revs.reverse()
2537 2537 return revs
2538 2538
2539 2539 def getgraphlogrevs(repo, pats, opts):
2540 2540 """Return (revs, expr, filematcher) where revs is an iterable of
2541 2541 revision numbers, expr is a revset string built from log options
2542 2542 and file patterns or None, and used to filter 'revs'. If --stat or
2543 2543 --patch are not passed filematcher is None. Otherwise it is a
2544 2544 callable taking a revision number and returning a match objects
2545 2545 filtering the files to be detailed when displaying the revision.
2546 2546 """
2547 2547 limit = loglimit(opts)
2548 2548 revs = _logrevs(repo, opts)
2549 2549 if not revs:
2550 2550 return smartset.baseset(), None, None
2551 2551 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2552 2552 if opts.get('rev'):
2553 2553 # User-specified revs might be unsorted, but don't sort before
2554 2554 # _makelogrevset because it might depend on the order of revs
2555 2555 if not (revs.isdescending() or revs.istopo()):
2556 2556 revs.sort(reverse=True)
2557 2557 if expr:
2558 2558 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2559 2559 revs = matcher(repo, revs)
2560 2560 if limit is not None:
2561 2561 limitedrevs = []
2562 2562 for idx, rev in enumerate(revs):
2563 2563 if idx >= limit:
2564 2564 break
2565 2565 limitedrevs.append(rev)
2566 2566 revs = smartset.baseset(limitedrevs)
2567 2567
2568 2568 return revs, expr, filematcher
2569 2569
2570 2570 def getlogrevs(repo, pats, opts):
2571 2571 """Return (revs, expr, filematcher) where revs is an iterable of
2572 2572 revision numbers, expr is a revset string built from log options
2573 2573 and file patterns or None, and used to filter 'revs'. If --stat or
2574 2574 --patch are not passed filematcher is None. Otherwise it is a
2575 2575 callable taking a revision number and returning a match objects
2576 2576 filtering the files to be detailed when displaying the revision.
2577 2577 """
2578 2578 limit = loglimit(opts)
2579 2579 revs = _logrevs(repo, opts)
2580 2580 if not revs:
2581 2581 return smartset.baseset([]), None, None
2582 2582 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2583 2583 if expr:
2584 2584 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2585 2585 revs = matcher(repo, revs)
2586 2586 if limit is not None:
2587 2587 limitedrevs = []
2588 2588 for idx, r in enumerate(revs):
2589 2589 if limit <= idx:
2590 2590 break
2591 2591 limitedrevs.append(r)
2592 2592 revs = smartset.baseset(limitedrevs)
2593 2593
2594 2594 return revs, expr, filematcher
2595 2595
2596 2596 def _graphnodeformatter(ui, displayer):
2597 2597 spec = ui.config('ui', 'graphnodetemplate')
2598 2598 if not spec:
2599 2599 return templatekw.showgraphnode # fast path for "{graphnode}"
2600 2600
2601 2601 spec = templater.unquotestring(spec)
2602 2602 templ = formatter.maketemplater(ui, spec)
2603 2603 cache = {}
2604 2604 if isinstance(displayer, changeset_templater):
2605 2605 cache = displayer.cache # reuse cache of slow templates
2606 2606 props = templatekw.keywords.copy()
2607 2607 props['templ'] = templ
2608 2608 props['cache'] = cache
2609 2609 def formatnode(repo, ctx):
2610 2610 props['ctx'] = ctx
2611 2611 props['repo'] = repo
2612 2612 props['ui'] = repo.ui
2613 2613 props['revcache'] = {}
2614 2614 return templ.render(props)
2615 2615 return formatnode
2616 2616
2617 2617 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2618 2618 filematcher=None):
2619 2619 formatnode = _graphnodeformatter(ui, displayer)
2620 2620 state = graphmod.asciistate()
2621 2621 styles = state['styles']
2622 2622
2623 2623 # only set graph styling if HGPLAIN is not set.
2624 2624 if ui.plain('graph'):
2625 2625 # set all edge styles to |, the default pre-3.8 behaviour
2626 2626 styles.update(dict.fromkeys(styles, '|'))
2627 2627 else:
2628 2628 edgetypes = {
2629 2629 'parent': graphmod.PARENT,
2630 2630 'grandparent': graphmod.GRANDPARENT,
2631 2631 'missing': graphmod.MISSINGPARENT
2632 2632 }
2633 2633 for name, key in edgetypes.items():
2634 2634 # experimental config: experimental.graphstyle.*
2635 2635 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2636 2636 styles[key])
2637 2637 if not styles[key]:
2638 2638 styles[key] = None
2639 2639
2640 2640 # experimental config: experimental.graphshorten
2641 2641 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2642 2642
2643 2643 for rev, type, ctx, parents in dag:
2644 2644 char = formatnode(repo, ctx)
2645 2645 copies = None
2646 2646 if getrenamed and ctx.rev():
2647 2647 copies = []
2648 2648 for fn in ctx.files():
2649 2649 rename = getrenamed(fn, ctx.rev())
2650 2650 if rename:
2651 2651 copies.append((fn, rename[0]))
2652 2652 revmatchfn = None
2653 2653 if filematcher is not None:
2654 2654 revmatchfn = filematcher(ctx.rev())
2655 2655 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2656 2656 lines = displayer.hunk.pop(rev).split('\n')
2657 2657 if not lines[-1]:
2658 2658 del lines[-1]
2659 2659 displayer.flush(ctx)
2660 2660 edges = edgefn(type, char, lines, state, rev, parents)
2661 2661 for type, char, lines, coldata in edges:
2662 2662 graphmod.ascii(ui, state, type, char, lines, coldata)
2663 2663 displayer.close()
2664 2664
2665 2665 def graphlog(ui, repo, pats, opts):
2666 2666 # Parameters are identical to log command ones
2667 2667 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2668 2668 revdag = graphmod.dagwalker(repo, revs)
2669 2669
2670 2670 getrenamed = None
2671 2671 if opts.get('copies'):
2672 2672 endrev = None
2673 2673 if opts.get('rev'):
2674 2674 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2675 2675 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2676 2676
2677 2677 ui.pager('log')
2678 2678 displayer = show_changeset(ui, repo, opts, buffered=True)
2679 2679 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2680 2680 filematcher)
2681 2681
2682 2682 def checkunsupportedgraphflags(pats, opts):
2683 2683 for op in ["newest_first"]:
2684 2684 if op in opts and opts[op]:
2685 2685 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2686 2686 % op.replace("_", "-"))
2687 2687
2688 2688 def graphrevs(repo, nodes, opts):
2689 2689 limit = loglimit(opts)
2690 2690 nodes.reverse()
2691 2691 if limit is not None:
2692 2692 nodes = nodes[:limit]
2693 2693 return graphmod.nodes(repo, nodes)
2694 2694
2695 2695 def add(ui, repo, match, prefix, explicitonly, **opts):
2696 2696 join = lambda f: os.path.join(prefix, f)
2697 2697 bad = []
2698 2698
2699 2699 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2700 2700 names = []
2701 2701 wctx = repo[None]
2702 2702 cca = None
2703 2703 abort, warn = scmutil.checkportabilityalert(ui)
2704 2704 if abort or warn:
2705 2705 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2706 2706
2707 2707 badmatch = matchmod.badmatch(match, badfn)
2708 2708 dirstate = repo.dirstate
2709 2709 # We don't want to just call wctx.walk here, since it would return a lot of
2710 2710 # clean files, which we aren't interested in and takes time.
2711 2711 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2712 2712 True, False, full=False)):
2713 2713 exact = match.exact(f)
2714 2714 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2715 2715 if cca:
2716 2716 cca(f)
2717 2717 names.append(f)
2718 2718 if ui.verbose or not exact:
2719 2719 ui.status(_('adding %s\n') % match.rel(f))
2720 2720
2721 2721 for subpath in sorted(wctx.substate):
2722 2722 sub = wctx.sub(subpath)
2723 2723 try:
2724 2724 submatch = matchmod.subdirmatcher(subpath, match)
2725 2725 if opts.get(r'subrepos'):
2726 2726 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2727 2727 else:
2728 2728 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2729 2729 except error.LookupError:
2730 2730 ui.status(_("skipping missing subrepository: %s\n")
2731 2731 % join(subpath))
2732 2732
2733 2733 if not opts.get(r'dry_run'):
2734 2734 rejected = wctx.add(names, prefix)
2735 2735 bad.extend(f for f in rejected if f in match.files())
2736 2736 return bad
2737 2737
2738 2738 def addwebdirpath(repo, serverpath, webconf):
2739 2739 webconf[serverpath] = repo.root
2740 2740 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2741 2741
2742 2742 for r in repo.revs('filelog("path:.hgsub")'):
2743 2743 ctx = repo[r]
2744 2744 for subpath in ctx.substate:
2745 2745 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2746 2746
2747 2747 def forget(ui, repo, match, prefix, explicitonly):
2748 2748 join = lambda f: os.path.join(prefix, f)
2749 2749 bad = []
2750 2750 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2751 2751 wctx = repo[None]
2752 2752 forgot = []
2753 2753
2754 2754 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2755 2755 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2756 2756 if explicitonly:
2757 2757 forget = [f for f in forget if match.exact(f)]
2758 2758
2759 2759 for subpath in sorted(wctx.substate):
2760 2760 sub = wctx.sub(subpath)
2761 2761 try:
2762 2762 submatch = matchmod.subdirmatcher(subpath, match)
2763 2763 subbad, subforgot = sub.forget(submatch, prefix)
2764 2764 bad.extend([subpath + '/' + f for f in subbad])
2765 2765 forgot.extend([subpath + '/' + f for f in subforgot])
2766 2766 except error.LookupError:
2767 2767 ui.status(_("skipping missing subrepository: %s\n")
2768 2768 % join(subpath))
2769 2769
2770 2770 if not explicitonly:
2771 2771 for f in match.files():
2772 2772 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2773 2773 if f not in forgot:
2774 2774 if repo.wvfs.exists(f):
2775 2775 # Don't complain if the exact case match wasn't given.
2776 2776 # But don't do this until after checking 'forgot', so
2777 2777 # that subrepo files aren't normalized, and this op is
2778 2778 # purely from data cached by the status walk above.
2779 2779 if repo.dirstate.normalize(f) in repo.dirstate:
2780 2780 continue
2781 2781 ui.warn(_('not removing %s: '
2782 2782 'file is already untracked\n')
2783 2783 % match.rel(f))
2784 2784 bad.append(f)
2785 2785
2786 2786 for f in forget:
2787 2787 if ui.verbose or not match.exact(f):
2788 2788 ui.status(_('removing %s\n') % match.rel(f))
2789 2789
2790 2790 rejected = wctx.forget(forget, prefix)
2791 2791 bad.extend(f for f in rejected if f in match.files())
2792 2792 forgot.extend(f for f in forget if f not in rejected)
2793 2793 return bad, forgot
2794 2794
2795 2795 def files(ui, ctx, m, fm, fmt, subrepos):
2796 2796 rev = ctx.rev()
2797 2797 ret = 1
2798 2798 ds = ctx.repo().dirstate
2799 2799
2800 2800 for f in ctx.matches(m):
2801 2801 if rev is None and ds[f] == 'r':
2802 2802 continue
2803 2803 fm.startitem()
2804 2804 if ui.verbose:
2805 2805 fc = ctx[f]
2806 2806 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2807 2807 fm.data(abspath=f)
2808 2808 fm.write('path', fmt, m.rel(f))
2809 2809 ret = 0
2810 2810
2811 2811 for subpath in sorted(ctx.substate):
2812 2812 submatch = matchmod.subdirmatcher(subpath, m)
2813 2813 if (subrepos or m.exact(subpath) or any(submatch.files())):
2814 2814 sub = ctx.sub(subpath)
2815 2815 try:
2816 2816 recurse = m.exact(subpath) or subrepos
2817 2817 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2818 2818 ret = 0
2819 2819 except error.LookupError:
2820 2820 ui.status(_("skipping missing subrepository: %s\n")
2821 2821 % m.abs(subpath))
2822 2822
2823 2823 return ret
2824 2824
2825 2825 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2826 2826 join = lambda f: os.path.join(prefix, f)
2827 2827 ret = 0
2828 2828 s = repo.status(match=m, clean=True)
2829 2829 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2830 2830
2831 2831 wctx = repo[None]
2832 2832
2833 2833 if warnings is None:
2834 2834 warnings = []
2835 2835 warn = True
2836 2836 else:
2837 2837 warn = False
2838 2838
2839 2839 subs = sorted(wctx.substate)
2840 2840 total = len(subs)
2841 2841 count = 0
2842 2842 for subpath in subs:
2843 2843 count += 1
2844 2844 submatch = matchmod.subdirmatcher(subpath, m)
2845 2845 if subrepos or m.exact(subpath) or any(submatch.files()):
2846 2846 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2847 2847 sub = wctx.sub(subpath)
2848 2848 try:
2849 2849 if sub.removefiles(submatch, prefix, after, force, subrepos,
2850 2850 warnings):
2851 2851 ret = 1
2852 2852 except error.LookupError:
2853 2853 warnings.append(_("skipping missing subrepository: %s\n")
2854 2854 % join(subpath))
2855 2855 ui.progress(_('searching'), None)
2856 2856
2857 2857 # warn about failure to delete explicit files/dirs
2858 2858 deleteddirs = util.dirs(deleted)
2859 2859 files = m.files()
2860 2860 total = len(files)
2861 2861 count = 0
2862 2862 for f in files:
2863 2863 def insubrepo():
2864 2864 for subpath in wctx.substate:
2865 2865 if f.startswith(subpath + '/'):
2866 2866 return True
2867 2867 return False
2868 2868
2869 2869 count += 1
2870 2870 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2871 2871 isdir = f in deleteddirs or wctx.hasdir(f)
2872 2872 if (f in repo.dirstate or isdir or f == '.'
2873 2873 or insubrepo() or f in subs):
2874 2874 continue
2875 2875
2876 2876 if repo.wvfs.exists(f):
2877 2877 if repo.wvfs.isdir(f):
2878 2878 warnings.append(_('not removing %s: no tracked files\n')
2879 2879 % m.rel(f))
2880 2880 else:
2881 2881 warnings.append(_('not removing %s: file is untracked\n')
2882 2882 % m.rel(f))
2883 2883 # missing files will generate a warning elsewhere
2884 2884 ret = 1
2885 2885 ui.progress(_('deleting'), None)
2886 2886
2887 2887 if force:
2888 2888 list = modified + deleted + clean + added
2889 2889 elif after:
2890 2890 list = deleted
2891 2891 remaining = modified + added + clean
2892 2892 total = len(remaining)
2893 2893 count = 0
2894 2894 for f in remaining:
2895 2895 count += 1
2896 2896 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2897 2897 warnings.append(_('not removing %s: file still exists\n')
2898 2898 % m.rel(f))
2899 2899 ret = 1
2900 2900 ui.progress(_('skipping'), None)
2901 2901 else:
2902 2902 list = deleted + clean
2903 2903 total = len(modified) + len(added)
2904 2904 count = 0
2905 2905 for f in modified:
2906 2906 count += 1
2907 2907 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2908 2908 warnings.append(_('not removing %s: file is modified (use -f'
2909 2909 ' to force removal)\n') % m.rel(f))
2910 2910 ret = 1
2911 2911 for f in added:
2912 2912 count += 1
2913 2913 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2914 2914 warnings.append(_("not removing %s: file has been marked for add"
2915 2915 " (use 'hg forget' to undo add)\n") % m.rel(f))
2916 2916 ret = 1
2917 2917 ui.progress(_('skipping'), None)
2918 2918
2919 2919 list = sorted(list)
2920 2920 total = len(list)
2921 2921 count = 0
2922 2922 for f in list:
2923 2923 count += 1
2924 2924 if ui.verbose or not m.exact(f):
2925 2925 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2926 2926 ui.status(_('removing %s\n') % m.rel(f))
2927 2927 ui.progress(_('deleting'), None)
2928 2928
2929 2929 with repo.wlock():
2930 2930 if not after:
2931 2931 for f in list:
2932 2932 if f in added:
2933 2933 continue # we never unlink added files on remove
2934 2934 repo.wvfs.unlinkpath(f, ignoremissing=True)
2935 2935 repo[None].forget(list)
2936 2936
2937 2937 if warn:
2938 2938 for warning in warnings:
2939 2939 ui.warn(warning)
2940 2940
2941 2941 return ret
2942 2942
2943 2943 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2944 2944 err = 1
2945 2945
2946 2946 def write(path):
2947 2947 filename = None
2948 2948 if fntemplate:
2949 2949 filename = makefilename(repo, fntemplate, ctx.node(),
2950 2950 pathname=os.path.join(prefix, path))
2951 2951 with formatter.maybereopen(basefm, filename, opts) as fm:
2952 2952 data = ctx[path].data()
2953 2953 if opts.get('decode'):
2954 2954 data = repo.wwritedata(path, data)
2955 2955 fm.startitem()
2956 2956 fm.write('data', '%s', data)
2957 2957 fm.data(abspath=path, path=matcher.rel(path))
2958 2958
2959 2959 # Automation often uses hg cat on single files, so special case it
2960 2960 # for performance to avoid the cost of parsing the manifest.
2961 2961 if len(matcher.files()) == 1 and not matcher.anypats():
2962 2962 file = matcher.files()[0]
2963 2963 mfl = repo.manifestlog
2964 2964 mfnode = ctx.manifestnode()
2965 2965 try:
2966 2966 if mfnode and mfl[mfnode].find(file)[0]:
2967 2967 write(file)
2968 2968 return 0
2969 2969 except KeyError:
2970 2970 pass
2971 2971
2972 2972 for abs in ctx.walk(matcher):
2973 2973 write(abs)
2974 2974 err = 0
2975 2975
2976 2976 for subpath in sorted(ctx.substate):
2977 2977 sub = ctx.sub(subpath)
2978 2978 try:
2979 2979 submatch = matchmod.subdirmatcher(subpath, matcher)
2980 2980
2981 2981 if not sub.cat(submatch, basefm, fntemplate,
2982 2982 os.path.join(prefix, sub._path), **opts):
2983 2983 err = 0
2984 2984 except error.RepoLookupError:
2985 2985 ui.status(_("skipping missing subrepository: %s\n")
2986 2986 % os.path.join(prefix, subpath))
2987 2987
2988 2988 return err
2989 2989
2990 2990 def commit(ui, repo, commitfunc, pats, opts):
2991 2991 '''commit the specified files or all outstanding changes'''
2992 2992 date = opts.get('date')
2993 2993 if date:
2994 2994 opts['date'] = util.parsedate(date)
2995 2995 message = logmessage(ui, opts)
2996 2996 matcher = scmutil.match(repo[None], pats, opts)
2997 2997
2998 2998 dsguard = None
2999 2999 # extract addremove carefully -- this function can be called from a command
3000 3000 # that doesn't support addremove
3001 3001 if opts.get('addremove'):
3002 3002 dsguard = dirstateguard.dirstateguard(repo, 'commit')
3003 3003 with dsguard or util.nullcontextmanager():
3004 3004 if dsguard:
3005 3005 if scmutil.addremove(repo, matcher, "", opts) != 0:
3006 3006 raise error.Abort(
3007 3007 _("failed to mark all new/missing files as added/removed"))
3008 3008
3009 3009 return commitfunc(ui, repo, message, matcher, opts)
3010 3010
3011 3011 def samefile(f, ctx1, ctx2):
3012 3012 if f in ctx1.manifest():
3013 3013 a = ctx1.filectx(f)
3014 3014 if f in ctx2.manifest():
3015 3015 b = ctx2.filectx(f)
3016 3016 return (not a.cmp(b)
3017 3017 and a.flags() == b.flags())
3018 3018 else:
3019 3019 return False
3020 3020 else:
3021 3021 return f not in ctx2.manifest()
3022 3022
3023 3023 def amend(ui, repo, commitfunc, old, extra, pats, opts):
3024 3024 # avoid cycle context -> subrepo -> cmdutil
3025 3025 from . import context
3026 3026
3027 3027 # amend will reuse the existing user if not specified, but the obsolete
3028 3028 # marker creation requires that the current user's name is specified.
3029 3029 if obsolete.isenabled(repo, obsolete.createmarkersopt):
3030 3030 ui.username() # raise exception if username not set
3031 3031
3032 3032 ui.note(_('amending changeset %s\n') % old)
3033 3033 base = old.p1()
3034 3034
3035 3035 newid = None
3036 3036 with repo.wlock(), repo.lock(), repo.transaction('amend'):
3037 3037 # See if we got a message from -m or -l, if not, open the editor
3038 3038 # with the message of the changeset to amend
3039 3039 message = logmessage(ui, opts)
3040 3040 # ensure logfile does not conflict with later enforcement of the
3041 3041 # message. potential logfile content has been processed by
3042 3042 # `logmessage` anyway.
3043 3043 opts.pop('logfile')
3044 3044 # First, do a regular commit to record all changes in the working
3045 3045 # directory (if there are any)
3046 3046 ui.callhooks = False
3047 3047 activebookmark = repo._bookmarks.active
3048 3048 try:
3049 3049 repo._bookmarks.active = None
3050 3050 opts['message'] = 'temporary amend commit for %s' % old
3051 3051 node = commit(ui, repo, commitfunc, pats, opts)
3052 3052 finally:
3053 3053 repo._bookmarks.active = activebookmark
3054 3054 ui.callhooks = True
3055 3055 ctx = repo[node]
3056 3056
3057 3057 # Participating changesets:
3058 3058 #
3059 3059 # node/ctx o - new (intermediate) commit that contains changes
3060 3060 # | from working dir to go into amending commit
3061 3061 # | (or a workingctx if there were no changes)
3062 3062 # |
3063 3063 # old o - changeset to amend
3064 3064 # |
3065 3065 # base o - parent of amending changeset
3066 3066
3067 3067 # Update extra dict from amended commit (e.g. to preserve graft
3068 3068 # source)
3069 3069 extra.update(old.extra())
3070 3070
3071 3071 # Also update it from the intermediate commit or from the wctx
3072 3072 extra.update(ctx.extra())
3073 3073
3074 3074 if len(old.parents()) > 1:
3075 3075 # ctx.files() isn't reliable for merges, so fall back to the
3076 3076 # slower repo.status() method
3077 3077 files = set([fn for st in repo.status(base, old)[:3]
3078 3078 for fn in st])
3079 3079 else:
3080 3080 files = set(old.files())
3081 3081
3082 3082 # Second, we use either the commit we just did, or if there were no
3083 3083 # changes the parent of the working directory as the version of the
3084 3084 # files in the final amend commit
3085 3085 if node:
3086 3086 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
3087 3087
3088 3088 user = ctx.user()
3089 3089 date = ctx.date()
3090 3090 # Recompute copies (avoid recording a -> b -> a)
3091 3091 copied = copies.pathcopies(base, ctx)
3092 3092 if old.p2:
3093 3093 copied.update(copies.pathcopies(old.p2(), ctx))
3094 3094
3095 3095 # Prune files which were reverted by the updates: if old
3096 3096 # introduced file X and our intermediate commit, node,
3097 3097 # renamed that file, then those two files are the same and
3098 3098 # we can discard X from our list of files. Likewise if X
3099 3099 # was deleted, it's no longer relevant
3100 3100 files.update(ctx.files())
3101 3101 files = [f for f in files if not samefile(f, ctx, base)]
3102 3102
3103 3103 def filectxfn(repo, ctx_, path):
3104 3104 try:
3105 3105 fctx = ctx[path]
3106 3106 flags = fctx.flags()
3107 3107 mctx = context.memfilectx(repo,
3108 3108 fctx.path(), fctx.data(),
3109 3109 islink='l' in flags,
3110 3110 isexec='x' in flags,
3111 3111 copied=copied.get(path))
3112 3112 return mctx
3113 3113 except KeyError:
3114 3114 return None
3115 3115 else:
3116 3116 ui.note(_('copying changeset %s to %s\n') % (old, base))
3117 3117
3118 3118 # Use version of files as in the old cset
3119 3119 def filectxfn(repo, ctx_, path):
3120 3120 try:
3121 3121 return old.filectx(path)
3122 3122 except KeyError:
3123 3123 return None
3124 3124
3125 3125 user = opts.get('user') or old.user()
3126 3126 date = opts.get('date') or old.date()
3127 3127 editform = mergeeditform(old, 'commit.amend')
3128 3128 editor = getcommiteditor(editform=editform,
3129 3129 **pycompat.strkwargs(opts))
3130 3130 if not message:
3131 3131 editor = getcommiteditor(edit=True, editform=editform)
3132 3132 message = old.description()
3133 3133
3134 3134 pureextra = extra.copy()
3135 3135 extra['amend_source'] = old.hex()
3136 3136
3137 3137 new = context.memctx(repo,
3138 3138 parents=[base.node(), old.p2().node()],
3139 3139 text=message,
3140 3140 files=files,
3141 3141 filectxfn=filectxfn,
3142 3142 user=user,
3143 3143 date=date,
3144 3144 extra=extra,
3145 3145 editor=editor)
3146 3146
3147 3147 newdesc = changelog.stripdesc(new.description())
3148 3148 if ((not node)
3149 3149 and newdesc == old.description()
3150 3150 and user == old.user()
3151 3151 and date == old.date()
3152 3152 and pureextra == old.extra()):
3153 3153 # nothing changed. continuing here would create a new node
3154 3154 # anyway because of the amend_source noise.
3155 3155 #
3156 3156 # This not what we expect from amend.
3157 3157 return old.node()
3158 3158
3159 3159 ph = repo.ui.config('phases', 'new-commit', phases.draft)
3160 3160 try:
3161 3161 if opts.get('secret'):
3162 3162 commitphase = 'secret'
3163 3163 else:
3164 3164 commitphase = old.phase()
3165 3165 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
3166 3166 newid = repo.commitctx(new)
3167 3167 finally:
3168 3168 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
3169 3169 if newid != old.node():
3170 3170 # Reroute the working copy parent to the new changeset
3171 3171 repo.setparents(newid, nullid)
3172 3172 mapping = {old.node(): (newid,)}
3173 3173 if node:
3174 3174 mapping[node] = ()
3175 3175 scmutil.cleanupnodes(repo, mapping, 'amend')
3176 3176 return newid
3177 3177
3178 3178 def commiteditor(repo, ctx, subs, editform=''):
3179 3179 if ctx.description():
3180 3180 return ctx.description()
3181 3181 return commitforceeditor(repo, ctx, subs, editform=editform,
3182 3182 unchangedmessagedetection=True)
3183 3183
3184 3184 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
3185 3185 editform='', unchangedmessagedetection=False):
3186 3186 if not extramsg:
3187 3187 extramsg = _("Leave message empty to abort commit.")
3188 3188
3189 3189 forms = [e for e in editform.split('.') if e]
3190 3190 forms.insert(0, 'changeset')
3191 3191 templatetext = None
3192 3192 while forms:
3193 3193 ref = '.'.join(forms)
3194 3194 if repo.ui.config('committemplate', ref):
3195 3195 templatetext = committext = buildcommittemplate(
3196 3196 repo, ctx, subs, extramsg, ref)
3197 3197 break
3198 3198 forms.pop()
3199 3199 else:
3200 3200 committext = buildcommittext(repo, ctx, subs, extramsg)
3201 3201
3202 3202 # run editor in the repository root
3203 3203 olddir = pycompat.getcwd()
3204 3204 os.chdir(repo.root)
3205 3205
3206 3206 # make in-memory changes visible to external process
3207 3207 tr = repo.currenttransaction()
3208 3208 repo.dirstate.write(tr)
3209 3209 pending = tr and tr.writepending() and repo.root
3210 3210
3211 3211 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
3212 3212 editform=editform, pending=pending,
3213 3213 repopath=repo.path)
3214 3214 text = editortext
3215 3215
3216 3216 # strip away anything below this special string (used for editors that want
3217 3217 # to display the diff)
3218 3218 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3219 3219 if stripbelow:
3220 3220 text = text[:stripbelow.start()]
3221 3221
3222 3222 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
3223 3223 os.chdir(olddir)
3224 3224
3225 3225 if finishdesc:
3226 3226 text = finishdesc(text)
3227 3227 if not text.strip():
3228 3228 raise error.Abort(_("empty commit message"))
3229 3229 if unchangedmessagedetection and editortext == templatetext:
3230 3230 raise error.Abort(_("commit message unchanged"))
3231 3231
3232 3232 return text
3233 3233
3234 3234 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3235 3235 ui = repo.ui
3236 3236 spec = formatter.templatespec(ref, None, None)
3237 3237 t = changeset_templater(ui, repo, spec, None, {}, False)
3238 3238 t.t.cache.update((k, templater.unquotestring(v))
3239 3239 for k, v in repo.ui.configitems('committemplate'))
3240 3240
3241 3241 if not extramsg:
3242 3242 extramsg = '' # ensure that extramsg is string
3243 3243
3244 3244 ui.pushbuffer()
3245 3245 t.show(ctx, extramsg=extramsg)
3246 3246 return ui.popbuffer()
3247 3247
3248 3248 def hgprefix(msg):
3249 3249 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
3250 3250
3251 3251 def buildcommittext(repo, ctx, subs, extramsg):
3252 3252 edittext = []
3253 3253 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3254 3254 if ctx.description():
3255 3255 edittext.append(ctx.description())
3256 3256 edittext.append("")
3257 3257 edittext.append("") # Empty line between message and comments.
3258 3258 edittext.append(hgprefix(_("Enter commit message."
3259 3259 " Lines beginning with 'HG:' are removed.")))
3260 3260 edittext.append(hgprefix(extramsg))
3261 3261 edittext.append("HG: --")
3262 3262 edittext.append(hgprefix(_("user: %s") % ctx.user()))
3263 3263 if ctx.p2():
3264 3264 edittext.append(hgprefix(_("branch merge")))
3265 3265 if ctx.branch():
3266 3266 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
3267 3267 if bookmarks.isactivewdirparent(repo):
3268 3268 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
3269 3269 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
3270 3270 edittext.extend([hgprefix(_("added %s") % f) for f in added])
3271 3271 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
3272 3272 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
3273 3273 if not added and not modified and not removed:
3274 3274 edittext.append(hgprefix(_("no files changed")))
3275 3275 edittext.append("")
3276 3276
3277 3277 return "\n".join(edittext)
3278 3278
3279 3279 def commitstatus(repo, node, branch, bheads=None, opts=None):
3280 3280 if opts is None:
3281 3281 opts = {}
3282 3282 ctx = repo[node]
3283 3283 parents = ctx.parents()
3284 3284
3285 3285 if (not opts.get('amend') and bheads and node not in bheads and not
3286 3286 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3287 3287 repo.ui.status(_('created new head\n'))
3288 3288 # The message is not printed for initial roots. For the other
3289 3289 # changesets, it is printed in the following situations:
3290 3290 #
3291 3291 # Par column: for the 2 parents with ...
3292 3292 # N: null or no parent
3293 3293 # B: parent is on another named branch
3294 3294 # C: parent is a regular non head changeset
3295 3295 # H: parent was a branch head of the current branch
3296 3296 # Msg column: whether we print "created new head" message
3297 3297 # In the following, it is assumed that there already exists some
3298 3298 # initial branch heads of the current branch, otherwise nothing is
3299 3299 # printed anyway.
3300 3300 #
3301 3301 # Par Msg Comment
3302 3302 # N N y additional topo root
3303 3303 #
3304 3304 # B N y additional branch root
3305 3305 # C N y additional topo head
3306 3306 # H N n usual case
3307 3307 #
3308 3308 # B B y weird additional branch root
3309 3309 # C B y branch merge
3310 3310 # H B n merge with named branch
3311 3311 #
3312 3312 # C C y additional head from merge
3313 3313 # C H n merge with a head
3314 3314 #
3315 3315 # H H n head merge: head count decreases
3316 3316
3317 3317 if not opts.get('close_branch'):
3318 3318 for r in parents:
3319 3319 if r.closesbranch() and r.branch() == branch:
3320 3320 repo.ui.status(_('reopening closed branch head %d\n') % r)
3321 3321
3322 3322 if repo.ui.debugflag:
3323 3323 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3324 3324 elif repo.ui.verbose:
3325 3325 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3326 3326
3327 3327 def postcommitstatus(repo, pats, opts):
3328 3328 return repo.status(match=scmutil.match(repo[None], pats, opts))
3329 3329
3330 3330 def revert(ui, repo, ctx, parents, *pats, **opts):
3331 3331 parent, p2 = parents
3332 3332 node = ctx.node()
3333 3333
3334 3334 mf = ctx.manifest()
3335 3335 if node == p2:
3336 3336 parent = p2
3337 3337
3338 3338 # need all matching names in dirstate and manifest of target rev,
3339 3339 # so have to walk both. do not print errors if files exist in one
3340 3340 # but not other. in both cases, filesets should be evaluated against
3341 3341 # workingctx to get consistent result (issue4497). this means 'set:**'
3342 3342 # cannot be used to select missing files from target rev.
3343 3343
3344 3344 # `names` is a mapping for all elements in working copy and target revision
3345 3345 # The mapping is in the form:
3346 3346 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3347 3347 names = {}
3348 3348
3349 3349 with repo.wlock():
3350 3350 ## filling of the `names` mapping
3351 3351 # walk dirstate to fill `names`
3352 3352
3353 3353 interactive = opts.get('interactive', False)
3354 3354 wctx = repo[None]
3355 3355 m = scmutil.match(wctx, pats, opts)
3356 3356
3357 3357 # we'll need this later
3358 3358 targetsubs = sorted(s for s in wctx.substate if m(s))
3359 3359
3360 3360 if not m.always():
3361 3361 matcher = matchmod.badmatch(m, lambda x, y: False)
3362 3362 for abs in wctx.walk(matcher):
3363 3363 names[abs] = m.rel(abs), m.exact(abs)
3364 3364
3365 3365 # walk target manifest to fill `names`
3366 3366
3367 3367 def badfn(path, msg):
3368 3368 if path in names:
3369 3369 return
3370 3370 if path in ctx.substate:
3371 3371 return
3372 3372 path_ = path + '/'
3373 3373 for f in names:
3374 3374 if f.startswith(path_):
3375 3375 return
3376 3376 ui.warn("%s: %s\n" % (m.rel(path), msg))
3377 3377
3378 3378 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3379 3379 if abs not in names:
3380 3380 names[abs] = m.rel(abs), m.exact(abs)
3381 3381
3382 3382 # Find status of all file in `names`.
3383 3383 m = scmutil.matchfiles(repo, names)
3384 3384
3385 3385 changes = repo.status(node1=node, match=m,
3386 3386 unknown=True, ignored=True, clean=True)
3387 3387 else:
3388 3388 changes = repo.status(node1=node, match=m)
3389 3389 for kind in changes:
3390 3390 for abs in kind:
3391 3391 names[abs] = m.rel(abs), m.exact(abs)
3392 3392
3393 3393 m = scmutil.matchfiles(repo, names)
3394 3394
3395 3395 modified = set(changes.modified)
3396 3396 added = set(changes.added)
3397 3397 removed = set(changes.removed)
3398 3398 _deleted = set(changes.deleted)
3399 3399 unknown = set(changes.unknown)
3400 3400 unknown.update(changes.ignored)
3401 3401 clean = set(changes.clean)
3402 3402 modadded = set()
3403 3403
3404 3404 # We need to account for the state of the file in the dirstate,
3405 3405 # even when we revert against something else than parent. This will
3406 3406 # slightly alter the behavior of revert (doing back up or not, delete
3407 3407 # or just forget etc).
3408 3408 if parent == node:
3409 3409 dsmodified = modified
3410 3410 dsadded = added
3411 3411 dsremoved = removed
3412 3412 # store all local modifications, useful later for rename detection
3413 3413 localchanges = dsmodified | dsadded
3414 3414 modified, added, removed = set(), set(), set()
3415 3415 else:
3416 3416 changes = repo.status(node1=parent, match=m)
3417 3417 dsmodified = set(changes.modified)
3418 3418 dsadded = set(changes.added)
3419 3419 dsremoved = set(changes.removed)
3420 3420 # store all local modifications, useful later for rename detection
3421 3421 localchanges = dsmodified | dsadded
3422 3422
3423 3423 # only take into account for removes between wc and target
3424 3424 clean |= dsremoved - removed
3425 3425 dsremoved &= removed
3426 3426 # distinct between dirstate remove and other
3427 3427 removed -= dsremoved
3428 3428
3429 3429 modadded = added & dsmodified
3430 3430 added -= modadded
3431 3431
3432 3432 # tell newly modified apart.
3433 3433 dsmodified &= modified
3434 3434 dsmodified |= modified & dsadded # dirstate added may need backup
3435 3435 modified -= dsmodified
3436 3436
3437 3437 # We need to wait for some post-processing to update this set
3438 3438 # before making the distinction. The dirstate will be used for
3439 3439 # that purpose.
3440 3440 dsadded = added
3441 3441
3442 3442 # in case of merge, files that are actually added can be reported as
3443 3443 # modified, we need to post process the result
3444 3444 if p2 != nullid:
3445 3445 mergeadd = set(dsmodified)
3446 3446 for path in dsmodified:
3447 3447 if path in mf:
3448 3448 mergeadd.remove(path)
3449 3449 dsadded |= mergeadd
3450 3450 dsmodified -= mergeadd
3451 3451
3452 3452 # if f is a rename, update `names` to also revert the source
3453 3453 cwd = repo.getcwd()
3454 3454 for f in localchanges:
3455 3455 src = repo.dirstate.copied(f)
3456 3456 # XXX should we check for rename down to target node?
3457 3457 if src and src not in names and repo.dirstate[src] == 'r':
3458 3458 dsremoved.add(src)
3459 3459 names[src] = (repo.pathto(src, cwd), True)
3460 3460
3461 3461 # determine the exact nature of the deleted changesets
3462 3462 deladded = set(_deleted)
3463 3463 for path in _deleted:
3464 3464 if path in mf:
3465 3465 deladded.remove(path)
3466 3466 deleted = _deleted - deladded
3467 3467
3468 3468 # distinguish between file to forget and the other
3469 3469 added = set()
3470 3470 for abs in dsadded:
3471 3471 if repo.dirstate[abs] != 'a':
3472 3472 added.add(abs)
3473 3473 dsadded -= added
3474 3474
3475 3475 for abs in deladded:
3476 3476 if repo.dirstate[abs] == 'a':
3477 3477 dsadded.add(abs)
3478 3478 deladded -= dsadded
3479 3479
3480 3480 # For files marked as removed, we check if an unknown file is present at
3481 3481 # the same path. If a such file exists it may need to be backed up.
3482 3482 # Making the distinction at this stage helps have simpler backup
3483 3483 # logic.
3484 3484 removunk = set()
3485 3485 for abs in removed:
3486 3486 target = repo.wjoin(abs)
3487 3487 if os.path.lexists(target):
3488 3488 removunk.add(abs)
3489 3489 removed -= removunk
3490 3490
3491 3491 dsremovunk = set()
3492 3492 for abs in dsremoved:
3493 3493 target = repo.wjoin(abs)
3494 3494 if os.path.lexists(target):
3495 3495 dsremovunk.add(abs)
3496 3496 dsremoved -= dsremovunk
3497 3497
3498 3498 # action to be actually performed by revert
3499 3499 # (<list of file>, message>) tuple
3500 3500 actions = {'revert': ([], _('reverting %s\n')),
3501 3501 'add': ([], _('adding %s\n')),
3502 3502 'remove': ([], _('removing %s\n')),
3503 3503 'drop': ([], _('removing %s\n')),
3504 3504 'forget': ([], _('forgetting %s\n')),
3505 3505 'undelete': ([], _('undeleting %s\n')),
3506 3506 'noop': (None, _('no changes needed to %s\n')),
3507 3507 'unknown': (None, _('file not managed: %s\n')),
3508 3508 }
3509 3509
3510 3510 # "constant" that convey the backup strategy.
3511 3511 # All set to `discard` if `no-backup` is set do avoid checking
3512 3512 # no_backup lower in the code.
3513 3513 # These values are ordered for comparison purposes
3514 3514 backupinteractive = 3 # do backup if interactively modified
3515 3515 backup = 2 # unconditionally do backup
3516 3516 check = 1 # check if the existing file differs from target
3517 3517 discard = 0 # never do backup
3518 3518 if opts.get('no_backup'):
3519 3519 backupinteractive = backup = check = discard
3520 3520 if interactive:
3521 3521 dsmodifiedbackup = backupinteractive
3522 3522 else:
3523 3523 dsmodifiedbackup = backup
3524 3524 tobackup = set()
3525 3525
3526 3526 backupanddel = actions['remove']
3527 3527 if not opts.get('no_backup'):
3528 3528 backupanddel = actions['drop']
3529 3529
3530 3530 disptable = (
3531 3531 # dispatch table:
3532 3532 # file state
3533 3533 # action
3534 3534 # make backup
3535 3535
3536 3536 ## Sets that results that will change file on disk
3537 3537 # Modified compared to target, no local change
3538 3538 (modified, actions['revert'], discard),
3539 3539 # Modified compared to target, but local file is deleted
3540 3540 (deleted, actions['revert'], discard),
3541 3541 # Modified compared to target, local change
3542 3542 (dsmodified, actions['revert'], dsmodifiedbackup),
3543 3543 # Added since target
3544 3544 (added, actions['remove'], discard),
3545 3545 # Added in working directory
3546 3546 (dsadded, actions['forget'], discard),
3547 3547 # Added since target, have local modification
3548 3548 (modadded, backupanddel, backup),
3549 3549 # Added since target but file is missing in working directory
3550 3550 (deladded, actions['drop'], discard),
3551 3551 # Removed since target, before working copy parent
3552 3552 (removed, actions['add'], discard),
3553 3553 # Same as `removed` but an unknown file exists at the same path
3554 3554 (removunk, actions['add'], check),
3555 3555 # Removed since targe, marked as such in working copy parent
3556 3556 (dsremoved, actions['undelete'], discard),
3557 3557 # Same as `dsremoved` but an unknown file exists at the same path
3558 3558 (dsremovunk, actions['undelete'], check),
3559 3559 ## the following sets does not result in any file changes
3560 3560 # File with no modification
3561 3561 (clean, actions['noop'], discard),
3562 3562 # Existing file, not tracked anywhere
3563 3563 (unknown, actions['unknown'], discard),
3564 3564 )
3565 3565
3566 3566 for abs, (rel, exact) in sorted(names.items()):
3567 3567 # target file to be touch on disk (relative to cwd)
3568 3568 target = repo.wjoin(abs)
3569 3569 # search the entry in the dispatch table.
3570 3570 # if the file is in any of these sets, it was touched in the working
3571 3571 # directory parent and we are sure it needs to be reverted.
3572 3572 for table, (xlist, msg), dobackup in disptable:
3573 3573 if abs not in table:
3574 3574 continue
3575 3575 if xlist is not None:
3576 3576 xlist.append(abs)
3577 3577 if dobackup:
3578 3578 # If in interactive mode, don't automatically create
3579 3579 # .orig files (issue4793)
3580 3580 if dobackup == backupinteractive:
3581 3581 tobackup.add(abs)
3582 3582 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3583 3583 bakname = scmutil.origpath(ui, repo, rel)
3584 3584 ui.note(_('saving current version of %s as %s\n') %
3585 3585 (rel, bakname))
3586 3586 if not opts.get('dry_run'):
3587 3587 if interactive:
3588 3588 util.copyfile(target, bakname)
3589 3589 else:
3590 3590 util.rename(target, bakname)
3591 3591 if ui.verbose or not exact:
3592 3592 if not isinstance(msg, basestring):
3593 3593 msg = msg(abs)
3594 3594 ui.status(msg % rel)
3595 3595 elif exact:
3596 3596 ui.warn(msg % rel)
3597 3597 break
3598 3598
3599 3599 if not opts.get('dry_run'):
3600 3600 needdata = ('revert', 'add', 'undelete')
3601 3601 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3602 3602 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3603 3603
3604 3604 if targetsubs:
3605 3605 # Revert the subrepos on the revert list
3606 3606 for sub in targetsubs:
3607 3607 try:
3608 3608 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3609 3609 except KeyError:
3610 3610 raise error.Abort("subrepository '%s' does not exist in %s!"
3611 3611 % (sub, short(ctx.node())))
3612 3612
3613 3613 def _revertprefetch(repo, ctx, *files):
3614 3614 """Let extension changing the storage layer prefetch content"""
3615 3615 pass
3616 3616
3617 3617 def _performrevert(repo, parents, ctx, actions, interactive=False,
3618 3618 tobackup=None):
3619 3619 """function that actually perform all the actions computed for revert
3620 3620
3621 3621 This is an independent function to let extension to plug in and react to
3622 3622 the imminent revert.
3623 3623
3624 3624 Make sure you have the working directory locked when calling this function.
3625 3625 """
3626 3626 parent, p2 = parents
3627 3627 node = ctx.node()
3628 3628 excluded_files = []
3629 3629 matcher_opts = {"exclude": excluded_files}
3630 3630
3631 3631 def checkout(f):
3632 3632 fc = ctx[f]
3633 3633 repo.wwrite(f, fc.data(), fc.flags())
3634 3634
3635 3635 def doremove(f):
3636 3636 try:
3637 3637 repo.wvfs.unlinkpath(f)
3638 3638 except OSError:
3639 3639 pass
3640 3640 repo.dirstate.remove(f)
3641 3641
3642 3642 audit_path = pathutil.pathauditor(repo.root, cached=True)
3643 3643 for f in actions['forget'][0]:
3644 3644 if interactive:
3645 3645 choice = repo.ui.promptchoice(
3646 3646 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3647 3647 if choice == 0:
3648 3648 repo.dirstate.drop(f)
3649 3649 else:
3650 3650 excluded_files.append(repo.wjoin(f))
3651 3651 else:
3652 3652 repo.dirstate.drop(f)
3653 3653 for f in actions['remove'][0]:
3654 3654 audit_path(f)
3655 3655 if interactive:
3656 3656 choice = repo.ui.promptchoice(
3657 3657 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3658 3658 if choice == 0:
3659 3659 doremove(f)
3660 3660 else:
3661 3661 excluded_files.append(repo.wjoin(f))
3662 3662 else:
3663 3663 doremove(f)
3664 3664 for f in actions['drop'][0]:
3665 3665 audit_path(f)
3666 3666 repo.dirstate.remove(f)
3667 3667
3668 3668 normal = None
3669 3669 if node == parent:
3670 3670 # We're reverting to our parent. If possible, we'd like status
3671 3671 # to report the file as clean. We have to use normallookup for
3672 3672 # merges to avoid losing information about merged/dirty files.
3673 3673 if p2 != nullid:
3674 3674 normal = repo.dirstate.normallookup
3675 3675 else:
3676 3676 normal = repo.dirstate.normal
3677 3677
3678 3678 newlyaddedandmodifiedfiles = set()
3679 3679 if interactive:
3680 3680 # Prompt the user for changes to revert
3681 3681 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3682 3682 m = scmutil.match(ctx, torevert, matcher_opts)
3683 3683 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3684 3684 diffopts.nodates = True
3685 3685 diffopts.git = True
3686 3686 operation = 'discard'
3687 3687 reversehunks = True
3688 3688 if node != parent:
3689 3689 operation = 'revert'
3690 3690 reversehunks = repo.ui.configbool('experimental',
3691 3691 'revertalternateinteractivemode')
3692 3692 if reversehunks:
3693 3693 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3694 3694 else:
3695 3695 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3696 3696 originalchunks = patch.parsepatch(diff)
3697 3697
3698 3698 try:
3699 3699
3700 3700 chunks, opts = recordfilter(repo.ui, originalchunks,
3701 3701 operation=operation)
3702 3702 if reversehunks:
3703 3703 chunks = patch.reversehunks(chunks)
3704 3704
3705 3705 except patch.PatchError as err:
3706 3706 raise error.Abort(_('error parsing patch: %s') % err)
3707 3707
3708 3708 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3709 3709 if tobackup is None:
3710 3710 tobackup = set()
3711 3711 # Apply changes
3712 3712 fp = stringio()
3713 3713 for c in chunks:
3714 3714 # Create a backup file only if this hunk should be backed up
3715 3715 if ishunk(c) and c.header.filename() in tobackup:
3716 3716 abs = c.header.filename()
3717 3717 target = repo.wjoin(abs)
3718 3718 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3719 3719 util.copyfile(target, bakname)
3720 3720 tobackup.remove(abs)
3721 3721 c.write(fp)
3722 3722 dopatch = fp.tell()
3723 3723 fp.seek(0)
3724 3724 if dopatch:
3725 3725 try:
3726 3726 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3727 3727 except patch.PatchError as err:
3728 3728 raise error.Abort(str(err))
3729 3729 del fp
3730 3730 else:
3731 3731 for f in actions['revert'][0]:
3732 3732 checkout(f)
3733 3733 if normal:
3734 3734 normal(f)
3735 3735
3736 3736 for f in actions['add'][0]:
3737 3737 # Don't checkout modified files, they are already created by the diff
3738 3738 if f not in newlyaddedandmodifiedfiles:
3739 3739 checkout(f)
3740 3740 repo.dirstate.add(f)
3741 3741
3742 3742 normal = repo.dirstate.normallookup
3743 3743 if node == parent and p2 == nullid:
3744 3744 normal = repo.dirstate.normal
3745 3745 for f in actions['undelete'][0]:
3746 3746 checkout(f)
3747 3747 normal(f)
3748 3748
3749 3749 copied = copies.pathcopies(repo[parent], ctx)
3750 3750
3751 3751 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3752 3752 if f in copied:
3753 3753 repo.dirstate.copy(copied[f], f)
3754 3754
3755 3755 class command(registrar.command):
3756 3756 def _doregister(self, func, name, *args, **kwargs):
3757 3757 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3758 3758 return super(command, self)._doregister(func, name, *args, **kwargs)
3759 3759
3760 3760 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3761 3761 # commands.outgoing. "missing" is "missing" of the result of
3762 3762 # "findcommonoutgoing()"
3763 3763 outgoinghooks = util.hooks()
3764 3764
3765 3765 # a list of (ui, repo) functions called by commands.summary
3766 3766 summaryhooks = util.hooks()
3767 3767
3768 3768 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3769 3769 #
3770 3770 # functions should return tuple of booleans below, if 'changes' is None:
3771 3771 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3772 3772 #
3773 3773 # otherwise, 'changes' is a tuple of tuples below:
3774 3774 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3775 3775 # - (desturl, destbranch, destpeer, outgoing)
3776 3776 summaryremotehooks = util.hooks()
3777 3777
3778 3778 # A list of state files kept by multistep operations like graft.
3779 3779 # Since graft cannot be aborted, it is considered 'clearable' by update.
3780 3780 # note: bisect is intentionally excluded
3781 3781 # (state file, clearable, allowcommit, error, hint)
3782 3782 unfinishedstates = [
3783 3783 ('graftstate', True, False, _('graft in progress'),
3784 3784 _("use 'hg graft --continue' or 'hg update' to abort")),
3785 3785 ('updatestate', True, False, _('last update was interrupted'),
3786 3786 _("use 'hg update' to get a consistent checkout"))
3787 3787 ]
3788 3788
3789 3789 def checkunfinished(repo, commit=False):
3790 3790 '''Look for an unfinished multistep operation, like graft, and abort
3791 3791 if found. It's probably good to check this right before
3792 3792 bailifchanged().
3793 3793 '''
3794 3794 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3795 3795 if commit and allowcommit:
3796 3796 continue
3797 3797 if repo.vfs.exists(f):
3798 3798 raise error.Abort(msg, hint=hint)
3799 3799
3800 3800 def clearunfinished(repo):
3801 3801 '''Check for unfinished operations (as above), and clear the ones
3802 3802 that are clearable.
3803 3803 '''
3804 3804 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3805 3805 if not clearable and repo.vfs.exists(f):
3806 3806 raise error.Abort(msg, hint=hint)
3807 3807 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3808 3808 if clearable and repo.vfs.exists(f):
3809 3809 util.unlink(repo.vfs.join(f))
3810 3810
3811 3811 afterresolvedstates = [
3812 3812 ('graftstate',
3813 3813 _('hg graft --continue')),
3814 3814 ]
3815 3815
3816 3816 def howtocontinue(repo):
3817 3817 '''Check for an unfinished operation and return the command to finish
3818 3818 it.
3819 3819
3820 3820 afterresolvedstates tuples define a .hg/{file} and the corresponding
3821 3821 command needed to finish it.
3822 3822
3823 3823 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3824 3824 a boolean.
3825 3825 '''
3826 3826 contmsg = _("continue: %s")
3827 3827 for f, msg in afterresolvedstates:
3828 3828 if repo.vfs.exists(f):
3829 3829 return contmsg % msg, True
3830 3830 if repo[None].dirty(missing=True, merge=False, branch=False):
3831 3831 return contmsg % _("hg commit"), False
3832 3832 return None, None
3833 3833
3834 3834 def checkafterresolved(repo):
3835 3835 '''Inform the user about the next action after completing hg resolve
3836 3836
3837 3837 If there's a matching afterresolvedstates, howtocontinue will yield
3838 3838 repo.ui.warn as the reporter.
3839 3839
3840 3840 Otherwise, it will yield repo.ui.note.
3841 3841 '''
3842 3842 msg, warning = howtocontinue(repo)
3843 3843 if msg is not None:
3844 3844 if warning:
3845 3845 repo.ui.warn("%s\n" % msg)
3846 3846 else:
3847 3847 repo.ui.note("%s\n" % msg)
3848 3848
3849 3849 def wrongtooltocontinue(repo, task):
3850 3850 '''Raise an abort suggesting how to properly continue if there is an
3851 3851 active task.
3852 3852
3853 3853 Uses howtocontinue() to find the active task.
3854 3854
3855 3855 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3856 3856 a hint.
3857 3857 '''
3858 3858 after = howtocontinue(repo)
3859 3859 hint = None
3860 3860 if after[1]:
3861 3861 hint = after[0]
3862 3862 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,1506 +1,1506 b''
1 1 $ cat >> $HGRCPATH << EOF
2 2 > [phases]
3 3 > # public changeset are not obsolete
4 4 > publish=false
5 5 > [ui]
6 6 > logtemplate="{rev}:{node|short} ({phase}{if(obsolete, ' *{obsolete}*')}{if(instabilities, ' {instabilities}')}) [{tags} {bookmarks}] {desc|firstline}\n"
7 7 > EOF
8 8 $ mkcommit() {
9 9 > echo "$1" > "$1"
10 10 > hg add "$1"
11 11 > hg ci -m "add $1"
12 12 > }
13 13 $ getid() {
14 14 > hg log -T "{node}\n" --hidden -r "desc('$1')"
15 15 > }
16 16
17 17 $ cat > debugkeys.py <<EOF
18 18 > def reposetup(ui, repo):
19 19 > class debugkeysrepo(repo.__class__):
20 20 > def listkeys(self, namespace):
21 21 > ui.write('listkeys %s\n' % (namespace,))
22 22 > return super(debugkeysrepo, self).listkeys(namespace)
23 23 >
24 24 > if repo.local():
25 25 > repo.__class__ = debugkeysrepo
26 26 > EOF
27 27
28 28 $ hg init tmpa
29 29 $ cd tmpa
30 30 $ mkcommit kill_me
31 31
32 32 Checking that the feature is properly disabled
33 33
34 34 $ hg debugobsolete -d '0 0' `getid kill_me` -u babar
35 35 abort: creating obsolete markers is not enabled on this repo
36 36 [255]
37 37
38 38 Enabling it
39 39
40 40 $ cat >> $HGRCPATH << EOF
41 41 > [experimental]
42 42 > stabilization=createmarkers,exchange
43 43 > EOF
44 44
45 45 Killing a single changeset without replacement
46 46
47 47 $ hg debugobsolete 0
48 48 abort: changeset references must be full hexadecimal node identifiers
49 49 [255]
50 50 $ hg debugobsolete '00'
51 51 abort: changeset references must be full hexadecimal node identifiers
52 52 [255]
53 53 $ hg debugobsolete -d '0 0' `getid kill_me` -u babar
54 54 obsoleted 1 changesets
55 55 $ hg debugobsolete
56 56 97b7c2d76b1845ed3eb988cd612611e72406cef0 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'babar'}
57 57
58 58 (test that mercurial is not confused)
59 59
60 60 $ hg up null --quiet # having 0 as parent prevents it to be hidden
61 61 $ hg tip
62 62 -1:000000000000 (public) [tip ]
63 63 $ hg up --hidden tip --quiet
64 64
65 65 Killing a single changeset with itself should fail
66 66 (simple local safeguard)
67 67
68 68 $ hg debugobsolete `getid kill_me` `getid kill_me`
69 69 abort: bad obsmarker input: in-marker cycle with 97b7c2d76b1845ed3eb988cd612611e72406cef0
70 70 [255]
71 71
72 72 $ cd ..
73 73
74 74 Killing a single changeset with replacement
75 75 (and testing the format option)
76 76
77 77 $ hg init tmpb
78 78 $ cd tmpb
79 79 $ mkcommit a
80 80 $ mkcommit b
81 81 $ mkcommit original_c
82 82 $ hg up "desc('b')"
83 83 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
84 84 $ mkcommit new_c
85 85 created new head
86 86 $ hg log -r 'hidden()' --template '{rev}:{node|short} {desc}\n' --hidden
87 87 $ hg debugobsolete --config format.obsstore-version=0 --flag 12 `getid original_c` `getid new_c` -d '121 120'
88 88 obsoleted 1 changesets
89 89 $ hg log -r 'hidden()' --template '{rev}:{node|short} {desc}\n' --hidden
90 90 2:245bde4270cd add original_c
91 91 $ hg debugrevlog -cd
92 92 # rev p1rev p2rev start end deltastart base p1 p2 rawsize totalsize compression heads chainlen
93 93 0 -1 -1 0 59 0 0 0 0 58 58 0 1 0
94 94 1 0 -1 59 118 59 59 0 0 58 116 0 1 0
95 95 2 1 -1 118 193 118 118 59 0 76 192 0 1 0
96 96 3 1 -1 193 260 193 193 59 0 66 258 0 2 0
97 97 $ hg debugobsolete
98 98 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
99 99
100 100 (check for version number of the obsstore)
101 101
102 102 $ dd bs=1 count=1 if=.hg/store/obsstore 2>/dev/null
103 103 \x00 (no-eol) (esc)
104 104
105 105 do it again (it read the obsstore before adding new changeset)
106 106
107 107 $ hg up '.^'
108 108 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
109 109 $ mkcommit new_2_c
110 110 created new head
111 111 $ hg debugobsolete -d '1337 0' `getid new_c` `getid new_2_c`
112 112 obsoleted 1 changesets
113 113 $ hg debugobsolete
114 114 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
115 115 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
116 116
117 117 Register two markers with a missing node
118 118
119 119 $ hg up '.^'
120 120 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
121 121 $ mkcommit new_3_c
122 122 created new head
123 123 $ hg debugobsolete -d '1338 0' `getid new_2_c` 1337133713371337133713371337133713371337
124 124 obsoleted 1 changesets
125 125 $ hg debugobsolete -d '1339 0' 1337133713371337133713371337133713371337 `getid new_3_c`
126 126 $ hg debugobsolete
127 127 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
128 128 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
129 129 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
130 130 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
131 131
132 132 Test the --index option of debugobsolete command
133 133 $ hg debugobsolete --index
134 134 0 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
135 135 1 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
136 136 2 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
137 137 3 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
138 138
139 139 Refuse pathological nullid successors
140 140 $ hg debugobsolete -d '9001 0' 1337133713371337133713371337133713371337 0000000000000000000000000000000000000000
141 141 transaction abort!
142 142 rollback completed
143 143 abort: bad obsolescence marker detected: invalid successors nullid
144 144 [255]
145 145
146 146 Check that graphlog detect that a changeset is obsolete:
147 147
148 148 $ hg log -G
149 149 @ 5:5601fb93a350 (draft) [tip ] add new_3_c
150 150 |
151 151 o 1:7c3bad9141dc (draft) [ ] add b
152 152 |
153 153 o 0:1f0dee641bb7 (draft) [ ] add a
154 154
155 155
156 156 check that heads does not report them
157 157
158 158 $ hg heads
159 159 5:5601fb93a350 (draft) [tip ] add new_3_c
160 160 $ hg heads --hidden
161 161 5:5601fb93a350 (draft) [tip ] add new_3_c
162 162 4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c
163 163 3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c
164 164 2:245bde4270cd (draft *obsolete*) [ ] add original_c
165 165
166 166
167 167 check that summary does not report them
168 168
169 169 $ hg init ../sink
170 170 $ echo '[paths]' >> .hg/hgrc
171 171 $ echo 'default=../sink' >> .hg/hgrc
172 172 $ hg summary --remote
173 173 parent: 5:5601fb93a350 tip
174 174 add new_3_c
175 175 branch: default
176 176 commit: (clean)
177 177 update: (current)
178 178 phases: 3 draft
179 179 remote: 3 outgoing
180 180
181 181 $ hg summary --remote --hidden
182 182 parent: 5:5601fb93a350 tip
183 183 add new_3_c
184 184 branch: default
185 185 commit: (clean)
186 186 update: 3 new changesets, 4 branch heads (merge)
187 187 phases: 6 draft
188 188 remote: 3 outgoing
189 189
190 190 check that various commands work well with filtering
191 191
192 192 $ hg tip
193 193 5:5601fb93a350 (draft) [tip ] add new_3_c
194 194 $ hg log -r 6
195 195 abort: unknown revision '6'!
196 196 [255]
197 197 $ hg log -r 4
198 198 abort: hidden revision '4'!
199 199 (use --hidden to access hidden revisions)
200 200 [255]
201 201 $ hg debugrevspec 'rev(6)'
202 202 $ hg debugrevspec 'rev(4)'
203 203 $ hg debugrevspec 'null'
204 204 -1
205 205
206 206 Check that public changeset are not accounted as obsolete:
207 207
208 208 $ hg --hidden phase --public 2
209 209 $ hg log -G
210 210 @ 5:5601fb93a350 (draft phase-divergent) [tip ] add new_3_c
211 211 |
212 212 | o 2:245bde4270cd (public) [ ] add original_c
213 213 |/
214 214 o 1:7c3bad9141dc (public) [ ] add b
215 215 |
216 216 o 0:1f0dee641bb7 (public) [ ] add a
217 217
218 218
219 219 And that bumped changeset are detected
220 220 --------------------------------------
221 221
222 222 If we didn't filtered obsolete changesets out, 3 and 4 would show up too. Also
223 223 note that the bumped changeset (5:5601fb93a350) is not a direct successor of
224 224 the public changeset
225 225
226 226 $ hg log --hidden -r 'phasedivergent()'
227 227 5:5601fb93a350 (draft phase-divergent) [tip ] add new_3_c
228 228
229 229 And that we can't push bumped changeset
230 230
231 231 $ hg push ../tmpa -r 0 --force #(make repo related)
232 232 pushing to ../tmpa
233 233 searching for changes
234 234 warning: repository is unrelated
235 235 adding changesets
236 236 adding manifests
237 237 adding file changes
238 238 added 1 changesets with 1 changes to 1 files (+1 heads)
239 239 $ hg push ../tmpa
240 240 pushing to ../tmpa
241 241 searching for changes
242 242 abort: push includes phase-divergent changeset: 5601fb93a350!
243 243 [255]
244 244
245 245 Fixing "bumped" situation
246 246 We need to create a clone of 5 and add a special marker with a flag
247 247
248 248 $ hg summary
249 249 parent: 5:5601fb93a350 tip (phase-divergent)
250 250 add new_3_c
251 251 branch: default
252 252 commit: (clean)
253 253 update: 1 new changesets, 2 branch heads (merge)
254 254 phases: 1 draft
255 255 phase-divergent: 1 changesets
256 256 $ hg up '5^'
257 257 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
258 258 $ hg revert -ar 5
259 259 adding new_3_c
260 260 $ hg ci -m 'add n3w_3_c'
261 261 created new head
262 262 $ hg debugobsolete -d '1338 0' --flags 1 `getid new_3_c` `getid n3w_3_c`
263 263 obsoleted 1 changesets
264 264 $ hg log -r 'phasedivergent()'
265 265 $ hg log -G
266 266 @ 6:6f9641995072 (draft) [tip ] add n3w_3_c
267 267 |
268 268 | o 2:245bde4270cd (public) [ ] add original_c
269 269 |/
270 270 o 1:7c3bad9141dc (public) [ ] add b
271 271 |
272 272 o 0:1f0dee641bb7 (public) [ ] add a
273 273
274 274
275 275 Basic exclusive testing
276 276
277 277 $ hg log -G --hidden
278 278 @ 6:6f9641995072 (draft) [tip ] add n3w_3_c
279 279 |
280 280 | x 5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c
281 281 |/
282 282 | x 4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c
283 283 |/
284 284 | x 3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c
285 285 |/
286 286 | o 2:245bde4270cd (public) [ ] add original_c
287 287 |/
288 288 o 1:7c3bad9141dc (public) [ ] add b
289 289 |
290 290 o 0:1f0dee641bb7 (public) [ ] add a
291 291
292 292 $ hg debugobsolete --rev 6f9641995072
293 293 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
294 294 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
295 295 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
296 296 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
297 297 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
298 298 $ hg debugobsolete --rev 6f9641995072 --exclusive
299 299 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
300 300 $ hg debugobsolete --rev 5601fb93a350 --hidden
301 301 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
302 302 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
303 303 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
304 304 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
305 305 $ hg debugobsolete --rev 5601fb93a350 --hidden --exclusive
306 306 $ hg debugobsolete --rev 5601fb93a350+6f9641995072 --hidden --exclusive
307 307 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
308 308 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
309 309 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
310 310
311 311 $ cd ..
312 312
313 313 Revision 0 is hidden
314 314 --------------------
315 315
316 316 $ hg init rev0hidden
317 317 $ cd rev0hidden
318 318
319 319 $ mkcommit kill0
320 320 $ hg up -q null
321 321 $ hg debugobsolete `getid kill0`
322 322 obsoleted 1 changesets
323 323 $ mkcommit a
324 324 $ mkcommit b
325 325
326 326 Should pick the first visible revision as "repo" node
327 327
328 328 $ hg archive ../archive-null
329 329 $ cat ../archive-null/.hg_archival.txt
330 330 repo: 1f0dee641bb7258c56bd60e93edfa2405381c41e
331 331 node: 7c3bad9141dcb46ff89abf5f61856facd56e476c
332 332 branch: default
333 333 latesttag: null
334 334 latesttagdistance: 2
335 335 changessincelatesttag: 2
336 336
337 337
338 338 $ cd ..
339 339
340 340 Exchange Test
341 341 ============================
342 342
343 343 Destination repo does not have any data
344 344 ---------------------------------------
345 345
346 346 Simple incoming test
347 347
348 348 $ hg init tmpc
349 349 $ cd tmpc
350 350 $ hg incoming ../tmpb
351 351 comparing with ../tmpb
352 352 0:1f0dee641bb7 (public) [ ] add a
353 353 1:7c3bad9141dc (public) [ ] add b
354 354 2:245bde4270cd (public) [ ] add original_c
355 355 6:6f9641995072 (draft) [tip ] add n3w_3_c
356 356
357 357 Try to pull markers
358 358 (extinct changeset are excluded but marker are pushed)
359 359
360 360 $ hg pull ../tmpb
361 361 pulling from ../tmpb
362 362 requesting all changes
363 363 adding changesets
364 364 adding manifests
365 365 adding file changes
366 366 added 4 changesets with 4 changes to 4 files (+1 heads)
367 367 5 new obsolescence markers
368 368 (run 'hg heads' to see heads, 'hg merge' to merge)
369 369 $ hg debugobsolete
370 370 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
371 371 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
372 372 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
373 373 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
374 374 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
375 375
376 376 Rollback//Transaction support
377 377
378 378 $ hg debugobsolete -d '1340 0' aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
379 379 $ hg debugobsolete
380 380 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
381 381 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
382 382 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
383 383 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
384 384 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
385 385 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 0 (Thu Jan 01 00:22:20 1970 +0000) {'user': 'test'}
386 386 $ hg rollback -n
387 387 repository tip rolled back to revision 3 (undo debugobsolete)
388 388 $ hg rollback
389 389 repository tip rolled back to revision 3 (undo debugobsolete)
390 390 $ hg debugobsolete
391 391 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
392 392 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
393 393 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
394 394 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
395 395 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
396 396
397 397 $ cd ..
398 398
399 399 Try to push markers
400 400
401 401 $ hg init tmpd
402 402 $ hg -R tmpb push tmpd
403 403 pushing to tmpd
404 404 searching for changes
405 405 adding changesets
406 406 adding manifests
407 407 adding file changes
408 408 added 4 changesets with 4 changes to 4 files (+1 heads)
409 409 5 new obsolescence markers
410 410 $ hg -R tmpd debugobsolete | sort
411 411 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
412 412 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
413 413 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
414 414 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
415 415 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
416 416
417 417 Check obsolete keys are exchanged only if source has an obsolete store
418 418
419 419 $ hg init empty
420 420 $ hg --config extensions.debugkeys=debugkeys.py -R empty push tmpd
421 421 pushing to tmpd
422 422 listkeys phases
423 423 listkeys bookmarks
424 424 no changes found
425 425 listkeys phases
426 426 [1]
427 427
428 428 clone support
429 429 (markers are copied and extinct changesets are included to allow hardlinks)
430 430
431 431 $ hg clone tmpb clone-dest
432 432 updating to branch default
433 433 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
434 434 $ hg -R clone-dest log -G --hidden
435 435 @ 6:6f9641995072 (draft) [tip ] add n3w_3_c
436 436 |
437 437 | x 5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c
438 438 |/
439 439 | x 4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c
440 440 |/
441 441 | x 3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c
442 442 |/
443 443 | o 2:245bde4270cd (public) [ ] add original_c
444 444 |/
445 445 o 1:7c3bad9141dc (public) [ ] add b
446 446 |
447 447 o 0:1f0dee641bb7 (public) [ ] add a
448 448
449 449 $ hg -R clone-dest debugobsolete
450 450 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
451 451 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
452 452 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
453 453 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
454 454 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
455 455
456 456
457 457 Destination repo have existing data
458 458 ---------------------------------------
459 459
460 460 On pull
461 461
462 462 $ hg init tmpe
463 463 $ cd tmpe
464 464 $ hg debugobsolete -d '1339 0' 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00
465 465 $ hg pull ../tmpb
466 466 pulling from ../tmpb
467 467 requesting all changes
468 468 adding changesets
469 469 adding manifests
470 470 adding file changes
471 471 added 4 changesets with 4 changes to 4 files (+1 heads)
472 472 5 new obsolescence markers
473 473 (run 'hg heads' to see heads, 'hg merge' to merge)
474 474 $ hg debugobsolete
475 475 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
476 476 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
477 477 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
478 478 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
479 479 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
480 480 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
481 481
482 482
483 483 On push
484 484
485 485 $ hg push ../tmpc
486 486 pushing to ../tmpc
487 487 searching for changes
488 488 no changes found
489 489 1 new obsolescence markers
490 490 [1]
491 491 $ hg -R ../tmpc debugobsolete
492 492 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
493 493 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
494 494 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
495 495 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
496 496 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
497 497 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
498 498
499 499 detect outgoing obsolete and unstable
500 500 ---------------------------------------
501 501
502 502
503 503 $ hg log -G
504 504 o 3:6f9641995072 (draft) [tip ] add n3w_3_c
505 505 |
506 506 | o 2:245bde4270cd (public) [ ] add original_c
507 507 |/
508 508 o 1:7c3bad9141dc (public) [ ] add b
509 509 |
510 510 o 0:1f0dee641bb7 (public) [ ] add a
511 511
512 512 $ hg up 'desc("n3w_3_c")'
513 513 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
514 514 $ mkcommit original_d
515 515 $ mkcommit original_e
516 516 $ hg debugobsolete --record-parents `getid original_d` -d '0 0'
517 517 obsoleted 1 changesets
518 518 $ hg debugobsolete | grep `getid original_d`
519 519 94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
520 520 $ hg log -r 'obsolete()'
521 521 4:94b33453f93b (draft *obsolete*) [ ] add original_d
522 522 $ hg summary
523 523 parent: 5:cda648ca50f5 tip (orphan)
524 524 add original_e
525 525 branch: default
526 526 commit: (clean)
527 527 update: 1 new changesets, 2 branch heads (merge)
528 528 phases: 3 draft
529 529 orphan: 1 changesets
530 530 $ hg log -G -r '::orphan()'
531 531 @ 5:cda648ca50f5 (draft orphan) [tip ] add original_e
532 532 |
533 533 x 4:94b33453f93b (draft *obsolete*) [ ] add original_d
534 534 |
535 535 o 3:6f9641995072 (draft) [ ] add n3w_3_c
536 536 |
537 537 o 1:7c3bad9141dc (public) [ ] add b
538 538 |
539 539 o 0:1f0dee641bb7 (public) [ ] add a
540 540
541 541
542 542 refuse to push obsolete changeset
543 543
544 544 $ hg push ../tmpc/ -r 'desc("original_d")'
545 545 pushing to ../tmpc/
546 546 searching for changes
547 547 abort: push includes obsolete changeset: 94b33453f93b!
548 548 [255]
549 549
550 550 refuse to push unstable changeset
551 551
552 552 $ hg push ../tmpc/
553 553 pushing to ../tmpc/
554 554 searching for changes
555 555 abort: push includes orphan changeset: cda648ca50f5!
556 556 [255]
557 557
558 558 Test that extinct changeset are properly detected
559 559
560 560 $ hg log -r 'extinct()'
561 561
562 562 Don't try to push extinct changeset
563 563
564 564 $ hg init ../tmpf
565 565 $ hg out ../tmpf
566 566 comparing with ../tmpf
567 567 searching for changes
568 568 0:1f0dee641bb7 (public) [ ] add a
569 569 1:7c3bad9141dc (public) [ ] add b
570 570 2:245bde4270cd (public) [ ] add original_c
571 571 3:6f9641995072 (draft) [ ] add n3w_3_c
572 572 4:94b33453f93b (draft *obsolete*) [ ] add original_d
573 573 5:cda648ca50f5 (draft orphan) [tip ] add original_e
574 574 $ hg push ../tmpf -f # -f because be push unstable too
575 575 pushing to ../tmpf
576 576 searching for changes
577 577 adding changesets
578 578 adding manifests
579 579 adding file changes
580 580 added 6 changesets with 6 changes to 6 files (+1 heads)
581 581 7 new obsolescence markers
582 582
583 583 no warning displayed
584 584
585 585 $ hg push ../tmpf
586 586 pushing to ../tmpf
587 587 searching for changes
588 588 no changes found
589 589 [1]
590 590
591 591 Do not warn about new head when the new head is a successors of a remote one
592 592
593 593 $ hg log -G
594 594 @ 5:cda648ca50f5 (draft orphan) [tip ] add original_e
595 595 |
596 596 x 4:94b33453f93b (draft *obsolete*) [ ] add original_d
597 597 |
598 598 o 3:6f9641995072 (draft) [ ] add n3w_3_c
599 599 |
600 600 | o 2:245bde4270cd (public) [ ] add original_c
601 601 |/
602 602 o 1:7c3bad9141dc (public) [ ] add b
603 603 |
604 604 o 0:1f0dee641bb7 (public) [ ] add a
605 605
606 606 $ hg up -q 'desc(n3w_3_c)'
607 607 $ mkcommit obsolete_e
608 608 created new head
609 609 $ hg debugobsolete `getid 'original_e'` `getid 'obsolete_e'` \
610 610 > -u 'test <test@example.net>'
611 611 obsoleted 1 changesets
612 612 $ hg outgoing ../tmpf # parasite hg outgoing testin
613 613 comparing with ../tmpf
614 614 searching for changes
615 615 6:3de5eca88c00 (draft) [tip ] add obsolete_e
616 616 $ hg push ../tmpf
617 617 pushing to ../tmpf
618 618 searching for changes
619 619 adding changesets
620 620 adding manifests
621 621 adding file changes
622 622 added 1 changesets with 1 changes to 1 files (+1 heads)
623 623 1 new obsolescence markers
624 624 obsoleted 1 changesets
625 625
626 626 test relevance computation
627 627 ---------------------------------------
628 628
629 629 Checking simple case of "marker relevance".
630 630
631 631
632 632 Reminder of the repo situation
633 633
634 634 $ hg log --hidden --graph
635 635 @ 6:3de5eca88c00 (draft) [tip ] add obsolete_e
636 636 |
637 637 | x 5:cda648ca50f5 (draft *obsolete*) [ ] add original_e
638 638 | |
639 639 | x 4:94b33453f93b (draft *obsolete*) [ ] add original_d
640 640 |/
641 641 o 3:6f9641995072 (draft) [ ] add n3w_3_c
642 642 |
643 643 | o 2:245bde4270cd (public) [ ] add original_c
644 644 |/
645 645 o 1:7c3bad9141dc (public) [ ] add b
646 646 |
647 647 o 0:1f0dee641bb7 (public) [ ] add a
648 648
649 649
650 650 List of all markers
651 651
652 652 $ hg debugobsolete
653 653 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
654 654 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
655 655 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
656 656 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
657 657 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
658 658 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
659 659 94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
660 660 cda648ca50f50482b7055c0b0c4c117bba6733d9 3de5eca88c00aa039da7399a220f4a5221faa585 0 (*) {'user': 'test <test@example.net>'} (glob)
661 661
662 662 List of changesets with no chain
663 663
664 664 $ hg debugobsolete --hidden --rev ::2
665 665
666 666 List of changesets that are included on marker chain
667 667
668 668 $ hg debugobsolete --hidden --rev 6
669 669 cda648ca50f50482b7055c0b0c4c117bba6733d9 3de5eca88c00aa039da7399a220f4a5221faa585 0 (*) {'user': 'test <test@example.net>'} (glob)
670 670
671 671 List of changesets with a longer chain, (including a pruned children)
672 672
673 673 $ hg debugobsolete --hidden --rev 3
674 674 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
675 675 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
676 676 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
677 677 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
678 678 94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
679 679 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
680 680 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
681 681
682 682 List of both
683 683
684 684 $ hg debugobsolete --hidden --rev 3::6
685 685 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
686 686 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
687 687 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
688 688 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
689 689 94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
690 690 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
691 691 cda648ca50f50482b7055c0b0c4c117bba6733d9 3de5eca88c00aa039da7399a220f4a5221faa585 0 (*) {'user': 'test <test@example.net>'} (glob)
692 692 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
693 693
694 694 List of all markers in JSON
695 695
696 696 $ hg debugobsolete -Tjson
697 697 [
698 698 {
699 699 "date": [1339.0, 0],
700 700 "flag": 0,
701 701 "metadata": {"user": "test"},
702 "precnode": "1339133913391339133913391339133913391339",
702 "prednode": "1339133913391339133913391339133913391339",
703 703 "succnodes": ["ca819180edb99ed25ceafb3e9584ac287e240b00"]
704 704 },
705 705 {
706 706 "date": [1339.0, 0],
707 707 "flag": 0,
708 708 "metadata": {"user": "test"},
709 "precnode": "1337133713371337133713371337133713371337",
709 "prednode": "1337133713371337133713371337133713371337",
710 710 "succnodes": ["5601fb93a350734d935195fee37f4054c529ff39"]
711 711 },
712 712 {
713 713 "date": [121.0, 120],
714 714 "flag": 12,
715 715 "metadata": {"user": "test"},
716 "precnode": "245bde4270cd1072a27757984f9cda8ba26f08ca",
716 "prednode": "245bde4270cd1072a27757984f9cda8ba26f08ca",
717 717 "succnodes": ["cdbce2fbb16313928851e97e0d85413f3f7eb77f"]
718 718 },
719 719 {
720 720 "date": [1338.0, 0],
721 721 "flag": 1,
722 722 "metadata": {"user": "test"},
723 "precnode": "5601fb93a350734d935195fee37f4054c529ff39",
723 "prednode": "5601fb93a350734d935195fee37f4054c529ff39",
724 724 "succnodes": ["6f96419950729f3671185b847352890f074f7557"]
725 725 },
726 726 {
727 727 "date": [1338.0, 0],
728 728 "flag": 0,
729 729 "metadata": {"user": "test"},
730 "precnode": "ca819180edb99ed25ceafb3e9584ac287e240b00",
730 "prednode": "ca819180edb99ed25ceafb3e9584ac287e240b00",
731 731 "succnodes": ["1337133713371337133713371337133713371337"]
732 732 },
733 733 {
734 734 "date": [1337.0, 0],
735 735 "flag": 0,
736 736 "metadata": {"user": "test"},
737 "precnode": "cdbce2fbb16313928851e97e0d85413f3f7eb77f",
737 "prednode": "cdbce2fbb16313928851e97e0d85413f3f7eb77f",
738 738 "succnodes": ["ca819180edb99ed25ceafb3e9584ac287e240b00"]
739 739 },
740 740 {
741 741 "date": [0.0, 0],
742 742 "flag": 0,
743 743 "metadata": {"user": "test"},
744 744 "parentnodes": ["6f96419950729f3671185b847352890f074f7557"],
745 "precnode": "94b33453f93bdb8d457ef9b770851a618bf413e1",
745 "prednode": "94b33453f93bdb8d457ef9b770851a618bf413e1",
746 746 "succnodes": []
747 747 },
748 748 {
749 749 "date": *, (glob)
750 750 "flag": 0,
751 751 "metadata": {"user": "test <test@example.net>"},
752 "precnode": "cda648ca50f50482b7055c0b0c4c117bba6733d9",
752 "prednode": "cda648ca50f50482b7055c0b0c4c117bba6733d9",
753 753 "succnodes": ["3de5eca88c00aa039da7399a220f4a5221faa585"]
754 754 }
755 755 ]
756 756
757 757 Template keywords
758 758
759 759 $ hg debugobsolete -r6 -T '{succnodes % "{node|short}"} {date|shortdate}\n'
760 760 3de5eca88c00 ????-??-?? (glob)
761 761 $ hg debugobsolete -r6 -T '{join(metadata % "{key}={value}", " ")}\n'
762 762 user=test <test@example.net>
763 763 $ hg debugobsolete -r6 -T '{metadata}\n'
764 764 'user': 'test <test@example.net>'
765 765 $ hg debugobsolete -r6 -T '{flag} {get(metadata, "user")}\n'
766 766 0 test <test@example.net>
767 767
768 768 Test the debug output for exchange
769 769 ----------------------------------
770 770
771 771 $ hg pull ../tmpb --config 'experimental.obsmarkers-exchange-debug=True' # bundle2
772 772 pulling from ../tmpb
773 773 searching for changes
774 774 no changes found
775 775 obsmarker-exchange: 346 bytes received
776 776
777 777 check hgweb does not explode
778 778 ====================================
779 779
780 780 $ hg unbundle $TESTDIR/bundles/hgweb+obs.hg
781 781 adding changesets
782 782 adding manifests
783 783 adding file changes
784 784 added 62 changesets with 63 changes to 9 files (+60 heads)
785 785 (run 'hg heads .' to see heads, 'hg merge' to merge)
786 786 $ for node in `hg log -r 'desc(babar_)' --template '{node}\n'`;
787 787 > do
788 788 > hg debugobsolete $node
789 789 > done
790 790 obsoleted 1 changesets
791 791 obsoleted 1 changesets
792 792 obsoleted 1 changesets
793 793 obsoleted 1 changesets
794 794 obsoleted 1 changesets
795 795 obsoleted 1 changesets
796 796 obsoleted 1 changesets
797 797 obsoleted 1 changesets
798 798 obsoleted 1 changesets
799 799 obsoleted 1 changesets
800 800 obsoleted 1 changesets
801 801 obsoleted 1 changesets
802 802 obsoleted 1 changesets
803 803 obsoleted 1 changesets
804 804 obsoleted 1 changesets
805 805 obsoleted 1 changesets
806 806 obsoleted 1 changesets
807 807 obsoleted 1 changesets
808 808 obsoleted 1 changesets
809 809 obsoleted 1 changesets
810 810 obsoleted 1 changesets
811 811 obsoleted 1 changesets
812 812 obsoleted 1 changesets
813 813 obsoleted 1 changesets
814 814 obsoleted 1 changesets
815 815 obsoleted 1 changesets
816 816 obsoleted 1 changesets
817 817 obsoleted 1 changesets
818 818 obsoleted 1 changesets
819 819 obsoleted 1 changesets
820 820 obsoleted 1 changesets
821 821 obsoleted 1 changesets
822 822 obsoleted 1 changesets
823 823 obsoleted 1 changesets
824 824 obsoleted 1 changesets
825 825 obsoleted 1 changesets
826 826 obsoleted 1 changesets
827 827 obsoleted 1 changesets
828 828 obsoleted 1 changesets
829 829 obsoleted 1 changesets
830 830 obsoleted 1 changesets
831 831 obsoleted 1 changesets
832 832 obsoleted 1 changesets
833 833 obsoleted 1 changesets
834 834 obsoleted 1 changesets
835 835 obsoleted 1 changesets
836 836 obsoleted 1 changesets
837 837 obsoleted 1 changesets
838 838 obsoleted 1 changesets
839 839 obsoleted 1 changesets
840 840 obsoleted 1 changesets
841 841 obsoleted 1 changesets
842 842 obsoleted 1 changesets
843 843 obsoleted 1 changesets
844 844 obsoleted 1 changesets
845 845 obsoleted 1 changesets
846 846 obsoleted 1 changesets
847 847 obsoleted 1 changesets
848 848 obsoleted 1 changesets
849 849 obsoleted 1 changesets
850 850 $ hg up tip
851 851 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
852 852
853 853 #if serve
854 854
855 855 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
856 856 $ cat hg.pid >> $DAEMON_PIDS
857 857
858 858 check changelog view
859 859
860 860 $ get-with-headers.py --headeronly localhost:$HGPORT 'shortlog/'
861 861 200 Script output follows
862 862
863 863 check graph view
864 864
865 865 $ get-with-headers.py --headeronly localhost:$HGPORT 'graph'
866 866 200 Script output follows
867 867
868 868 check filelog view
869 869
870 870 $ get-with-headers.py --headeronly localhost:$HGPORT 'log/'`hg log -r . -T "{node}"`/'babar'
871 871 200 Script output follows
872 872
873 873 $ get-with-headers.py --headeronly localhost:$HGPORT 'rev/68'
874 874 200 Script output follows
875 875 $ get-with-headers.py --headeronly localhost:$HGPORT 'rev/67'
876 876 404 Not Found
877 877 [1]
878 878
879 879 check that web.view config option:
880 880
881 881 $ killdaemons.py hg.pid
882 882 $ cat >> .hg/hgrc << EOF
883 883 > [web]
884 884 > view=all
885 885 > EOF
886 886 $ wait
887 887 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
888 888 $ get-with-headers.py --headeronly localhost:$HGPORT 'rev/67'
889 889 200 Script output follows
890 890 $ killdaemons.py hg.pid
891 891
892 892 Checking _enable=False warning if obsolete marker exists
893 893
894 894 $ echo '[experimental]' >> $HGRCPATH
895 895 $ echo "stabilization=" >> $HGRCPATH
896 896 $ hg log -r tip
897 897 obsolete feature not enabled but 68 markers found!
898 898 68:c15e9edfca13 (draft) [tip ] add celestine
899 899
900 900 reenable for later test
901 901
902 902 $ echo '[experimental]' >> $HGRCPATH
903 903 $ echo "stabilization=createmarkers,exchange" >> $HGRCPATH
904 904
905 905 $ rm hg.pid access.log errors.log
906 906 #endif
907 907
908 908 Several troubles on the same changeset (create an unstable and bumped changeset)
909 909
910 910 $ hg debugobsolete `getid obsolete_e`
911 911 obsoleted 1 changesets
912 912 $ hg debugobsolete `getid original_c` `getid babar`
913 913 $ hg log --config ui.logtemplate= -r 'phasedivergent() and orphan()'
914 914 changeset: 7:50c51b361e60
915 915 user: test
916 916 date: Thu Jan 01 00:00:00 1970 +0000
917 917 instability: orphan, phase-divergent
918 918 summary: add babar
919 919
920 920
921 921 test the "obsolete" templatekw
922 922
923 923 $ hg log -r 'obsolete()'
924 924 6:3de5eca88c00 (draft *obsolete*) [ ] add obsolete_e
925 925
926 926 test the "troubles" templatekw
927 927
928 928 $ hg log -r 'phasedivergent() and orphan()'
929 929 7:50c51b361e60 (draft orphan phase-divergent) [ ] add babar
930 930
931 931 test the default cmdline template
932 932
933 933 $ hg log -T default -r 'phasedivergent()'
934 934 changeset: 7:50c51b361e60
935 935 user: test
936 936 date: Thu Jan 01 00:00:00 1970 +0000
937 937 instability: orphan, phase-divergent
938 938 summary: add babar
939 939
940 940 $ hg log -T default -r 'obsolete()'
941 941 changeset: 6:3de5eca88c00
942 942 parent: 3:6f9641995072
943 943 user: test
944 944 date: Thu Jan 01 00:00:00 1970 +0000
945 945 summary: add obsolete_e
946 946
947 947
948 948 test the obsolete labels
949 949
950 950 $ hg log --config ui.logtemplate= --color=debug -r 'phasedivergent()'
951 951 [log.changeset changeset.draft changeset.unstable instability.orphan instability.phase-divergent|changeset: 7:50c51b361e60]
952 952 [log.user|user: test]
953 953 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
954 954 [log.instability|instability: orphan, phase-divergent]
955 955 [log.summary|summary: add babar]
956 956
957 957
958 958 $ hg log -T default -r 'phasedivergent()' --color=debug
959 959 [log.changeset changeset.draft changeset.unstable instability.orphaninstability.phase-divergent|changeset: 7:50c51b361e60]
960 960 [log.user|user: test]
961 961 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
962 962 [log.instability|instability: orphan, phase-divergent]
963 963 [log.summary|summary: add babar]
964 964
965 965
966 966 $ hg log --config ui.logtemplate= --color=debug -r "obsolete()"
967 967 [log.changeset changeset.draft changeset.obsolete|changeset: 6:3de5eca88c00]
968 968 [log.parent changeset.draft|parent: 3:6f9641995072]
969 969 [log.user|user: test]
970 970 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
971 971 [log.summary|summary: add obsolete_e]
972 972
973 973
974 974 $ hg log -T default -r 'obsolete()' --color=debug
975 975 [log.changeset changeset.draft changeset.obsolete|changeset: 6:3de5eca88c00]
976 976 [log.parent changeset.draft|parent: 3:6f9641995072]
977 977 [log.user|user: test]
978 978 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
979 979 [log.summary|summary: add obsolete_e]
980 980
981 981
982 982 test summary output
983 983
984 984 $ hg up -r 'phasedivergent() and orphan()'
985 985 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
986 986 $ hg summary
987 987 parent: 7:50c51b361e60 (orphan, phase-divergent)
988 988 add babar
989 989 branch: default
990 990 commit: (clean)
991 991 update: 2 new changesets (update)
992 992 phases: 4 draft
993 993 orphan: 2 changesets
994 994 phase-divergent: 1 changesets
995 995 $ hg up -r 'obsolete()'
996 996 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
997 997 $ hg summary
998 998 parent: 6:3de5eca88c00 (obsolete)
999 999 add obsolete_e
1000 1000 branch: default
1001 1001 commit: (clean)
1002 1002 update: 3 new changesets (update)
1003 1003 phases: 4 draft
1004 1004 orphan: 2 changesets
1005 1005 phase-divergent: 1 changesets
1006 1006
1007 1007 Test incoming/outcoming with changesets obsoleted remotely, known locally
1008 1008 ===============================================================================
1009 1009
1010 1010 This test issue 3805
1011 1011
1012 1012 $ hg init repo-issue3805
1013 1013 $ cd repo-issue3805
1014 1014 $ echo "base" > base
1015 1015 $ hg ci -Am "base"
1016 1016 adding base
1017 1017 $ echo "foo" > foo
1018 1018 $ hg ci -Am "A"
1019 1019 adding foo
1020 1020 $ hg clone . ../other-issue3805
1021 1021 updating to branch default
1022 1022 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1023 1023 $ echo "bar" >> foo
1024 1024 $ hg ci --amend
1025 1025 $ cd ../other-issue3805
1026 1026 $ hg log -G
1027 1027 @ 1:29f0c6921ddd (draft) [tip ] A
1028 1028 |
1029 1029 o 0:d20a80d4def3 (draft) [ ] base
1030 1030
1031 1031 $ hg log -G -R ../repo-issue3805
1032 1032 @ 3:323a9c3ddd91 (draft) [tip ] A
1033 1033 |
1034 1034 o 0:d20a80d4def3 (draft) [ ] base
1035 1035
1036 1036 $ hg incoming
1037 1037 comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
1038 1038 searching for changes
1039 1039 3:323a9c3ddd91 (draft) [tip ] A
1040 1040 $ hg incoming --bundle ../issue3805.hg
1041 1041 comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
1042 1042 searching for changes
1043 1043 3:323a9c3ddd91 (draft) [tip ] A
1044 1044 $ hg outgoing
1045 1045 comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
1046 1046 searching for changes
1047 1047 1:29f0c6921ddd (draft) [tip ] A
1048 1048
1049 1049 #if serve
1050 1050
1051 1051 $ hg serve -R ../repo-issue3805 -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
1052 1052 $ cat hg.pid >> $DAEMON_PIDS
1053 1053
1054 1054 $ hg incoming http://localhost:$HGPORT
1055 1055 comparing with http://localhost:$HGPORT/
1056 1056 searching for changes
1057 1057 2:323a9c3ddd91 (draft) [tip ] A
1058 1058 $ hg outgoing http://localhost:$HGPORT
1059 1059 comparing with http://localhost:$HGPORT/
1060 1060 searching for changes
1061 1061 1:29f0c6921ddd (draft) [tip ] A
1062 1062
1063 1063 $ killdaemons.py
1064 1064
1065 1065 #endif
1066 1066
1067 1067 This test issue 3814
1068 1068
1069 1069 (nothing to push but locally hidden changeset)
1070 1070
1071 1071 $ cd ..
1072 1072 $ hg init repo-issue3814
1073 1073 $ cd repo-issue3805
1074 1074 $ hg push -r 323a9c3ddd91 ../repo-issue3814
1075 1075 pushing to ../repo-issue3814
1076 1076 searching for changes
1077 1077 adding changesets
1078 1078 adding manifests
1079 1079 adding file changes
1080 1080 added 2 changesets with 2 changes to 2 files
1081 1081 2 new obsolescence markers
1082 1082 $ hg out ../repo-issue3814
1083 1083 comparing with ../repo-issue3814
1084 1084 searching for changes
1085 1085 no changes found
1086 1086 [1]
1087 1087
1088 1088 Test that a local tag blocks a changeset from being hidden
1089 1089
1090 1090 $ hg tag -l visible -r 1 --hidden
1091 1091 $ hg log -G
1092 1092 @ 3:323a9c3ddd91 (draft) [tip ] A
1093 1093 |
1094 1094 | x 1:29f0c6921ddd (draft *obsolete*) [visible ] A
1095 1095 |/
1096 1096 o 0:d20a80d4def3 (draft) [ ] base
1097 1097
1098 1098 Test that removing a local tag does not cause some commands to fail
1099 1099
1100 1100 $ hg tag -l -r tip tiptag
1101 1101 $ hg tags
1102 1102 tiptag 3:323a9c3ddd91
1103 1103 tip 3:323a9c3ddd91
1104 1104 visible 1:29f0c6921ddd
1105 1105 $ hg --config extensions.strip= strip -r tip --no-backup
1106 1106 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1107 1107 $ hg tags
1108 1108 visible 1:29f0c6921ddd
1109 1109 tip 1:29f0c6921ddd
1110 1110
1111 1111 Test bundle overlay onto hidden revision
1112 1112
1113 1113 $ cd ..
1114 1114 $ hg init repo-bundleoverlay
1115 1115 $ cd repo-bundleoverlay
1116 1116 $ echo "A" > foo
1117 1117 $ hg ci -Am "A"
1118 1118 adding foo
1119 1119 $ echo "B" >> foo
1120 1120 $ hg ci -m "B"
1121 1121 $ hg up 0
1122 1122 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1123 1123 $ echo "C" >> foo
1124 1124 $ hg ci -m "C"
1125 1125 created new head
1126 1126 $ hg log -G
1127 1127 @ 2:c186d7714947 (draft) [tip ] C
1128 1128 |
1129 1129 | o 1:44526ebb0f98 (draft) [ ] B
1130 1130 |/
1131 1131 o 0:4b34ecfb0d56 (draft) [ ] A
1132 1132
1133 1133
1134 1134 $ hg clone -r1 . ../other-bundleoverlay
1135 1135 adding changesets
1136 1136 adding manifests
1137 1137 adding file changes
1138 1138 added 2 changesets with 2 changes to 1 files
1139 1139 updating to branch default
1140 1140 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1141 1141 $ cd ../other-bundleoverlay
1142 1142 $ echo "B+" >> foo
1143 1143 $ hg ci --amend -m "B+"
1144 1144 $ hg log -G --hidden
1145 1145 @ 3:b7d587542d40 (draft) [tip ] B+
1146 1146 |
1147 1147 | x 2:eb95e9297e18 (draft *obsolete*) [ ] temporary amend commit for 44526ebb0f98
1148 1148 | |
1149 1149 | x 1:44526ebb0f98 (draft *obsolete*) [ ] B
1150 1150 |/
1151 1151 o 0:4b34ecfb0d56 (draft) [ ] A
1152 1152
1153 1153
1154 1154 $ hg incoming ../repo-bundleoverlay --bundle ../bundleoverlay.hg
1155 1155 comparing with ../repo-bundleoverlay
1156 1156 searching for changes
1157 1157 1:44526ebb0f98 (draft) [ ] B
1158 1158 2:c186d7714947 (draft) [tip ] C
1159 1159 $ hg log -G -R ../bundleoverlay.hg
1160 1160 o 4:c186d7714947 (draft) [tip ] C
1161 1161 |
1162 1162 | @ 3:b7d587542d40 (draft) [ ] B+
1163 1163 |/
1164 1164 o 0:4b34ecfb0d56 (draft) [ ] A
1165 1165
1166 1166
1167 1167 #if serve
1168 1168
1169 1169 Test issue 4506
1170 1170
1171 1171 $ cd ..
1172 1172 $ hg init repo-issue4506
1173 1173 $ cd repo-issue4506
1174 1174 $ echo "0" > foo
1175 1175 $ hg add foo
1176 1176 $ hg ci -m "content-0"
1177 1177
1178 1178 $ hg up null
1179 1179 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1180 1180 $ echo "1" > bar
1181 1181 $ hg add bar
1182 1182 $ hg ci -m "content-1"
1183 1183 created new head
1184 1184 $ hg up 0
1185 1185 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1186 1186 $ hg graft 1
1187 1187 grafting 1:1c9eddb02162 "content-1" (tip)
1188 1188
1189 1189 $ hg debugobsolete `hg log -r1 -T'{node}'` `hg log -r2 -T'{node}'`
1190 1190 obsoleted 1 changesets
1191 1191
1192 1192 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
1193 1193 $ cat hg.pid >> $DAEMON_PIDS
1194 1194
1195 1195 $ get-with-headers.py --headeronly localhost:$HGPORT 'rev/1'
1196 1196 404 Not Found
1197 1197 [1]
1198 1198 $ get-with-headers.py --headeronly localhost:$HGPORT 'file/tip/bar'
1199 1199 200 Script output follows
1200 1200 $ get-with-headers.py --headeronly localhost:$HGPORT 'annotate/tip/bar'
1201 1201 200 Script output follows
1202 1202
1203 1203 $ killdaemons.py
1204 1204
1205 1205 #endif
1206 1206
1207 1207 Test heads computation on pending index changes with obsolescence markers
1208 1208 $ cd ..
1209 1209 $ cat >$TESTTMP/test_extension.py << EOF
1210 1210 > from mercurial import cmdutil, registrar
1211 1211 > from mercurial.i18n import _
1212 1212 >
1213 1213 > cmdtable = {}
1214 1214 > command = registrar.command(cmdtable)
1215 1215 > @command(b"amendtransient",[], _('hg amendtransient [rev]'))
1216 1216 > def amend(ui, repo, *pats, **opts):
1217 1217 > def commitfunc(ui, repo, message, match, opts):
1218 1218 > return repo.commit(message, repo['.'].user(), repo['.'].date(), match)
1219 1219 > opts['message'] = 'Test'
1220 1220 > opts['logfile'] = None
1221 1221 > cmdutil.amend(ui, repo, commitfunc, repo['.'], {}, pats, opts)
1222 1222 > ui.write('%s\n' % repo.changelog.headrevs())
1223 1223 > EOF
1224 1224 $ cat >> $HGRCPATH << EOF
1225 1225 > [extensions]
1226 1226 > testextension=$TESTTMP/test_extension.py
1227 1227 > EOF
1228 1228 $ hg init repo-issue-nativerevs-pending-changes
1229 1229 $ cd repo-issue-nativerevs-pending-changes
1230 1230 $ mkcommit a
1231 1231 $ mkcommit b
1232 1232 $ hg up ".^"
1233 1233 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1234 1234 $ echo aa > a
1235 1235 $ hg amendtransient
1236 1236 [1, 3]
1237 1237
1238 1238 Test cache consistency for the visible filter
1239 1239 1) We want to make sure that the cached filtered revs are invalidated when
1240 1240 bookmarks change
1241 1241 $ cd ..
1242 1242 $ cat >$TESTTMP/test_extension.py << EOF
1243 1243 > import weakref
1244 1244 > from mercurial import cmdutil, extensions, bookmarks, repoview
1245 1245 > def _bookmarkchanged(orig, bkmstoreinst, *args, **kwargs):
1246 1246 > reporef = weakref.ref(bkmstoreinst._repo)
1247 1247 > def trhook(tr):
1248 1248 > repo = reporef()
1249 1249 > hidden1 = repoview.computehidden(repo)
1250 1250 > hidden = repoview.filterrevs(repo, 'visible')
1251 1251 > if sorted(hidden1) != sorted(hidden):
1252 1252 > print "cache inconsistency"
1253 1253 > bkmstoreinst._repo.currenttransaction().addpostclose('test_extension', trhook)
1254 1254 > orig(bkmstoreinst, *args, **kwargs)
1255 1255 > def extsetup(ui):
1256 1256 > extensions.wrapfunction(bookmarks.bmstore, '_recordchange',
1257 1257 > _bookmarkchanged)
1258 1258 > EOF
1259 1259
1260 1260 $ hg init repo-cache-inconsistency
1261 1261 $ cd repo-issue-nativerevs-pending-changes
1262 1262 $ mkcommit a
1263 1263 a already tracked!
1264 1264 $ mkcommit b
1265 1265 $ hg id
1266 1266 13bedc178fce tip
1267 1267 $ echo "hello" > b
1268 1268 $ hg commit --amend -m "message"
1269 1269 $ hg book bookb -r 13bedc178fce --hidden
1270 1270 $ hg log -r 13bedc178fce
1271 1271 5:13bedc178fce (draft *obsolete*) [ bookb] add b
1272 1272 $ hg book -d bookb
1273 1273 $ hg log -r 13bedc178fce
1274 1274 abort: hidden revision '13bedc178fce'!
1275 1275 (use --hidden to access hidden revisions)
1276 1276 [255]
1277 1277
1278 1278 Empty out the test extension, as it isn't compatible with later parts
1279 1279 of the test.
1280 1280 $ echo > $TESTTMP/test_extension.py
1281 1281
1282 1282 Test ability to pull changeset with locally applying obsolescence markers
1283 1283 (issue4945)
1284 1284
1285 1285 $ cd ..
1286 1286 $ hg init issue4845
1287 1287 $ cd issue4845
1288 1288
1289 1289 $ echo foo > f0
1290 1290 $ hg add f0
1291 1291 $ hg ci -m '0'
1292 1292 $ echo foo > f1
1293 1293 $ hg add f1
1294 1294 $ hg ci -m '1'
1295 1295 $ echo foo > f2
1296 1296 $ hg add f2
1297 1297 $ hg ci -m '2'
1298 1298
1299 1299 $ echo bar > f2
1300 1300 $ hg commit --amend --config experimetnal.stabilization=createmarkers
1301 1301 $ hg log -G
1302 1302 @ 4:b0551702f918 (draft) [tip ] 2
1303 1303 |
1304 1304 o 1:e016b03fd86f (draft) [ ] 1
1305 1305 |
1306 1306 o 0:a78f55e5508c (draft) [ ] 0
1307 1307
1308 1308 $ hg log -G --hidden
1309 1309 @ 4:b0551702f918 (draft) [tip ] 2
1310 1310 |
1311 1311 | x 3:f27abbcc1f77 (draft *obsolete*) [ ] temporary amend commit for e008cf283490
1312 1312 | |
1313 1313 | x 2:e008cf283490 (draft *obsolete*) [ ] 2
1314 1314 |/
1315 1315 o 1:e016b03fd86f (draft) [ ] 1
1316 1316 |
1317 1317 o 0:a78f55e5508c (draft) [ ] 0
1318 1318
1319 1319
1320 1320 $ hg strip --hidden -r 2 --config extensions.strip= --config devel.strip-obsmarkers=no
1321 1321 saved backup bundle to $TESTTMP/tmpe/issue4845/.hg/strip-backup/e008cf283490-39c978dc-backup.hg (glob)
1322 1322 $ hg debugobsolete
1323 1323 e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (*) {'user': 'test'} (glob)
1324 1324 f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (*) {'user': 'test'} (glob)
1325 1325 $ hg log -G
1326 1326 @ 2:b0551702f918 (draft) [tip ] 2
1327 1327 |
1328 1328 o 1:e016b03fd86f (draft) [ ] 1
1329 1329 |
1330 1330 o 0:a78f55e5508c (draft) [ ] 0
1331 1331
1332 1332 $ hg log -G --hidden
1333 1333 @ 2:b0551702f918 (draft) [tip ] 2
1334 1334 |
1335 1335 o 1:e016b03fd86f (draft) [ ] 1
1336 1336 |
1337 1337 o 0:a78f55e5508c (draft) [ ] 0
1338 1338
1339 1339 $ hg debugbundle .hg/strip-backup/e008cf283490-*-backup.hg
1340 1340 Stream params: sortdict([('Compression', 'BZ')])
1341 1341 changegroup -- "sortdict([('version', '02'), ('nbchanges', '2')])"
1342 1342 e008cf2834908e5d6b0f792a9d4b0e2272260fb8
1343 1343 f27abbcc1f77fb409cf9160482fe619541e2d605
1344 1344 obsmarkers -- 'sortdict()'
1345 1345 version: 1 (70 bytes)
1346 1346 f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
1347 1347 phase-heads -- 'sortdict()'
1348 1348 f27abbcc1f77fb409cf9160482fe619541e2d605 draft
1349 1349
1350 1350 $ hg pull .hg/strip-backup/e008cf283490-*-backup.hg
1351 1351 pulling from .hg/strip-backup/e008cf283490-39c978dc-backup.hg
1352 1352 searching for changes
1353 1353 no changes found
1354 1354 $ hg debugobsolete
1355 1355 e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (*) {'user': 'test'} (glob)
1356 1356 f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (*) {'user': 'test'} (glob)
1357 1357 $ hg log -G
1358 1358 @ 2:b0551702f918 (draft) [tip ] 2
1359 1359 |
1360 1360 o 1:e016b03fd86f (draft) [ ] 1
1361 1361 |
1362 1362 o 0:a78f55e5508c (draft) [ ] 0
1363 1363
1364 1364 $ hg log -G --hidden
1365 1365 @ 2:b0551702f918 (draft) [tip ] 2
1366 1366 |
1367 1367 o 1:e016b03fd86f (draft) [ ] 1
1368 1368 |
1369 1369 o 0:a78f55e5508c (draft) [ ] 0
1370 1370
1371 1371
1372 1372 Testing that strip remove markers:
1373 1373
1374 1374 $ hg strip -r 1 --config extensions.strip=
1375 1375 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
1376 1376 saved backup bundle to $TESTTMP/tmpe/issue4845/.hg/strip-backup/e016b03fd86f-65ede734-backup.hg (glob)
1377 1377 $ hg debugobsolete
1378 1378 $ hg log -G
1379 1379 @ 0:a78f55e5508c (draft) [tip ] 0
1380 1380
1381 1381 $ hg log -G --hidden
1382 1382 @ 0:a78f55e5508c (draft) [tip ] 0
1383 1383
1384 1384 $ hg debugbundle .hg/strip-backup/e016b03fd86f-*-backup.hg
1385 1385 Stream params: sortdict([('Compression', 'BZ')])
1386 1386 changegroup -- "sortdict([('version', '02'), ('nbchanges', '2')])"
1387 1387 e016b03fd86fcccc54817d120b90b751aaf367d6
1388 1388 b0551702f918510f01ae838ab03a463054c67b46
1389 1389 obsmarkers -- 'sortdict()'
1390 1390 version: 1 (139 bytes)
1391 1391 e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
1392 1392 f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
1393 1393 phase-heads -- 'sortdict()'
1394 1394 b0551702f918510f01ae838ab03a463054c67b46 draft
1395 1395
1396 1396 $ hg unbundle .hg/strip-backup/e016b03fd86f-*-backup.hg
1397 1397 adding changesets
1398 1398 adding manifests
1399 1399 adding file changes
1400 1400 added 2 changesets with 2 changes to 2 files
1401 1401 2 new obsolescence markers
1402 1402 (run 'hg update' to get a working copy)
1403 1403 $ hg debugobsolete | sort
1404 1404 e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (*) {'user': 'test'} (glob)
1405 1405 f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (*) {'user': 'test'} (glob)
1406 1406 $ hg log -G
1407 1407 o 2:b0551702f918 (draft) [tip ] 2
1408 1408 |
1409 1409 o 1:e016b03fd86f (draft) [ ] 1
1410 1410 |
1411 1411 @ 0:a78f55e5508c (draft) [ ] 0
1412 1412
1413 1413 $ hg log -G --hidden
1414 1414 o 2:b0551702f918 (draft) [tip ] 2
1415 1415 |
1416 1416 o 1:e016b03fd86f (draft) [ ] 1
1417 1417 |
1418 1418 @ 0:a78f55e5508c (draft) [ ] 0
1419 1419
1420 1420 Test that 'hg debugobsolete --index --rev' can show indices of obsmarkers when
1421 1421 only a subset of those are displayed (because of --rev option)
1422 1422 $ hg init doindexrev
1423 1423 $ cd doindexrev
1424 1424 $ echo a > a
1425 1425 $ hg ci -Am a
1426 1426 adding a
1427 1427 $ hg ci --amend -m aa
1428 1428 $ echo b > b
1429 1429 $ hg ci -Am b
1430 1430 adding b
1431 1431 $ hg ci --amend -m bb
1432 1432 $ echo c > c
1433 1433 $ hg ci -Am c
1434 1434 adding c
1435 1435 $ hg ci --amend -m cc
1436 1436 $ echo d > d
1437 1437 $ hg ci -Am d
1438 1438 adding d
1439 1439 $ hg ci --amend -m dd --config experimental.stabilization.track-operation=1
1440 1440 $ hg debugobsolete --index --rev "3+7"
1441 1441 1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 \(.*\) {'user': 'test'} (re)
1442 1442 3 4715cf767440ed891755448016c2b8cf70760c30 7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d 0 \(.*\) {'operation': 'amend', 'user': 'test'} (re)
1443 1443 $ hg debugobsolete --index --rev "3+7" -Tjson
1444 1444 [
1445 1445 {
1446 1446 "date": [0.0, 0],
1447 1447 "flag": 0,
1448 1448 "index": 1,
1449 1449 "metadata": {"user": "test"},
1450 "precnode": "6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1",
1450 "prednode": "6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1",
1451 1451 "succnodes": ["d27fb9b066076fd921277a4b9e8b9cb48c95bc6a"]
1452 1452 },
1453 1453 {
1454 1454 "date": [0.0, 0],
1455 1455 "flag": 0,
1456 1456 "index": 3,
1457 1457 "metadata": {"operation": "amend", "user": "test"},
1458 "precnode": "4715cf767440ed891755448016c2b8cf70760c30",
1458 "prednode": "4715cf767440ed891755448016c2b8cf70760c30",
1459 1459 "succnodes": ["7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d"]
1460 1460 }
1461 1461 ]
1462 1462
1463 1463 Test the --delete option of debugobsolete command
1464 1464 $ hg debugobsolete --index
1465 1465 0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
1466 1466 1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
1467 1467 2 1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
1468 1468 3 4715cf767440ed891755448016c2b8cf70760c30 7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
1469 1469 $ hg debugobsolete --delete 1 --delete 3
1470 1470 deleted 2 obsolescence markers
1471 1471 $ hg debugobsolete
1472 1472 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
1473 1473 1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
1474 1474
1475 1475 Test adding changeset after obsmarkers affecting it
1476 1476 (eg: during pull, or unbundle)
1477 1477
1478 1478 $ mkcommit e
1479 1479 $ hg bundle -r . --base .~1 ../bundle-2.hg
1480 1480 1 changesets found
1481 1481 $ getid .
1482 1482 $ hg --config extensions.strip= strip -r .
1483 1483 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1484 1484 saved backup bundle to $TESTTMP/tmpe/issue4845/doindexrev/.hg/strip-backup/9bc153528424-ee80edd4-backup.hg (glob)
1485 1485 $ hg debugobsolete 9bc153528424ea266d13e57f9ff0d799dfe61e4b
1486 1486 $ hg unbundle ../bundle-2.hg
1487 1487 adding changesets
1488 1488 adding manifests
1489 1489 adding file changes
1490 1490 added 1 changesets with 1 changes to 1 files
1491 1491 (run 'hg update' to get a working copy)
1492 1492 $ hg log -G
1493 1493 @ 7:7ae79c5d60f0 (draft) [tip ] dd
1494 1494 |
1495 1495 | o 6:4715cf767440 (draft) [ ] d
1496 1496 |/
1497 1497 o 5:29346082e4a9 (draft) [ ] cc
1498 1498 |
1499 1499 o 3:d27fb9b06607 (draft) [ ] bb
1500 1500 |
1501 1501 | o 2:6fdef60fcbab (draft) [ ] b
1502 1502 |/
1503 1503 o 1:f9bd49731b0b (draft) [ ] aa
1504 1504
1505 1505
1506 1506 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now