##// END OF EJS Templates
revert: always show relative path to .orig backup...
Martin von Zweigbergk -
r41731:4d484244 default
parent child Browse files
Show More
@@ -1,3332 +1,3333 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import os
12 12 import re
13 13
14 14 from .i18n import _
15 15 from .node import (
16 16 hex,
17 17 nullid,
18 18 nullrev,
19 19 short,
20 20 )
21 21
22 22 from . import (
23 23 bookmarks,
24 24 changelog,
25 25 copies,
26 26 crecord as crecordmod,
27 27 dirstateguard,
28 28 encoding,
29 29 error,
30 30 formatter,
31 31 logcmdutil,
32 32 match as matchmod,
33 33 merge as mergemod,
34 34 mergeutil,
35 35 obsolete,
36 36 patch,
37 37 pathutil,
38 38 phases,
39 39 pycompat,
40 40 revlog,
41 41 rewriteutil,
42 42 scmutil,
43 43 smartset,
44 44 subrepoutil,
45 45 templatekw,
46 46 templater,
47 47 util,
48 48 vfs as vfsmod,
49 49 )
50 50
51 51 from .utils import (
52 52 dateutil,
53 53 stringutil,
54 54 )
55 55
56 56 stringio = util.stringio
57 57
58 58 # templates of common command options
59 59
60 60 dryrunopts = [
61 61 ('n', 'dry-run', None,
62 62 _('do not perform actions, just print output')),
63 63 ]
64 64
65 65 confirmopts = [
66 66 ('', 'confirm', None,
67 67 _('ask before applying actions')),
68 68 ]
69 69
70 70 remoteopts = [
71 71 ('e', 'ssh', '',
72 72 _('specify ssh command to use'), _('CMD')),
73 73 ('', 'remotecmd', '',
74 74 _('specify hg command to run on the remote side'), _('CMD')),
75 75 ('', 'insecure', None,
76 76 _('do not verify server certificate (ignoring web.cacerts config)')),
77 77 ]
78 78
79 79 walkopts = [
80 80 ('I', 'include', [],
81 81 _('include names matching the given patterns'), _('PATTERN')),
82 82 ('X', 'exclude', [],
83 83 _('exclude names matching the given patterns'), _('PATTERN')),
84 84 ]
85 85
86 86 commitopts = [
87 87 ('m', 'message', '',
88 88 _('use text as commit message'), _('TEXT')),
89 89 ('l', 'logfile', '',
90 90 _('read commit message from file'), _('FILE')),
91 91 ]
92 92
93 93 commitopts2 = [
94 94 ('d', 'date', '',
95 95 _('record the specified date as commit date'), _('DATE')),
96 96 ('u', 'user', '',
97 97 _('record the specified user as committer'), _('USER')),
98 98 ]
99 99
100 100 formatteropts = [
101 101 ('T', 'template', '',
102 102 _('display with template'), _('TEMPLATE')),
103 103 ]
104 104
105 105 templateopts = [
106 106 ('', 'style', '',
107 107 _('display using template map file (DEPRECATED)'), _('STYLE')),
108 108 ('T', 'template', '',
109 109 _('display with template'), _('TEMPLATE')),
110 110 ]
111 111
112 112 logopts = [
113 113 ('p', 'patch', None, _('show patch')),
114 114 ('g', 'git', None, _('use git extended diff format')),
115 115 ('l', 'limit', '',
116 116 _('limit number of changes displayed'), _('NUM')),
117 117 ('M', 'no-merges', None, _('do not show merges')),
118 118 ('', 'stat', None, _('output diffstat-style summary of changes')),
119 119 ('G', 'graph', None, _("show the revision DAG")),
120 120 ] + templateopts
121 121
122 122 diffopts = [
123 123 ('a', 'text', None, _('treat all files as text')),
124 124 ('g', 'git', None, _('use git extended diff format')),
125 125 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
126 126 ('', 'nodates', None, _('omit dates from diff headers'))
127 127 ]
128 128
129 129 diffwsopts = [
130 130 ('w', 'ignore-all-space', None,
131 131 _('ignore white space when comparing lines')),
132 132 ('b', 'ignore-space-change', None,
133 133 _('ignore changes in the amount of white space')),
134 134 ('B', 'ignore-blank-lines', None,
135 135 _('ignore changes whose lines are all blank')),
136 136 ('Z', 'ignore-space-at-eol', None,
137 137 _('ignore changes in whitespace at EOL')),
138 138 ]
139 139
140 140 diffopts2 = [
141 141 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
142 142 ('p', 'show-function', None, _('show which function each change is in')),
143 143 ('', 'reverse', None, _('produce a diff that undoes the changes')),
144 144 ] + diffwsopts + [
145 145 ('U', 'unified', '',
146 146 _('number of lines of context to show'), _('NUM')),
147 147 ('', 'stat', None, _('output diffstat-style summary of changes')),
148 148 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
149 149 ]
150 150
151 151 mergetoolopts = [
152 152 ('t', 'tool', '', _('specify merge tool'), _('TOOL')),
153 153 ]
154 154
155 155 similarityopts = [
156 156 ('s', 'similarity', '',
157 157 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
158 158 ]
159 159
160 160 subrepoopts = [
161 161 ('S', 'subrepos', None,
162 162 _('recurse into subrepositories'))
163 163 ]
164 164
165 165 debugrevlogopts = [
166 166 ('c', 'changelog', False, _('open changelog')),
167 167 ('m', 'manifest', False, _('open manifest')),
168 168 ('', 'dir', '', _('open directory manifest')),
169 169 ]
170 170
171 171 # special string such that everything below this line will be ingored in the
172 172 # editor text
173 173 _linebelow = "^HG: ------------------------ >8 ------------------------$"
174 174
175 175 def ishunk(x):
176 176 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
177 177 return isinstance(x, hunkclasses)
178 178
179 179 def newandmodified(chunks, originalchunks):
180 180 newlyaddedandmodifiedfiles = set()
181 181 for chunk in chunks:
182 182 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
183 183 originalchunks:
184 184 newlyaddedandmodifiedfiles.add(chunk.header.filename())
185 185 return newlyaddedandmodifiedfiles
186 186
187 187 def parsealiases(cmd):
188 188 return cmd.split("|")
189 189
190 190 def setupwrapcolorwrite(ui):
191 191 # wrap ui.write so diff output can be labeled/colorized
192 192 def wrapwrite(orig, *args, **kw):
193 193 label = kw.pop(r'label', '')
194 194 for chunk, l in patch.difflabel(lambda: args):
195 195 orig(chunk, label=label + l)
196 196
197 197 oldwrite = ui.write
198 198 def wrap(*args, **kwargs):
199 199 return wrapwrite(oldwrite, *args, **kwargs)
200 200 setattr(ui, 'write', wrap)
201 201 return oldwrite
202 202
203 203 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
204 204 try:
205 205 if usecurses:
206 206 if testfile:
207 207 recordfn = crecordmod.testdecorator(
208 208 testfile, crecordmod.testchunkselector)
209 209 else:
210 210 recordfn = crecordmod.chunkselector
211 211
212 212 return crecordmod.filterpatch(ui, originalhunks, recordfn,
213 213 operation)
214 214 except crecordmod.fallbackerror as e:
215 215 ui.warn('%s\n' % e.message)
216 216 ui.warn(_('falling back to text mode\n'))
217 217
218 218 return patch.filterpatch(ui, originalhunks, operation)
219 219
220 220 def recordfilter(ui, originalhunks, operation=None):
221 221 """ Prompts the user to filter the originalhunks and return a list of
222 222 selected hunks.
223 223 *operation* is used for to build ui messages to indicate the user what
224 224 kind of filtering they are doing: reverting, committing, shelving, etc.
225 225 (see patch.filterpatch).
226 226 """
227 227 usecurses = crecordmod.checkcurses(ui)
228 228 testfile = ui.config('experimental', 'crecordtest')
229 229 oldwrite = setupwrapcolorwrite(ui)
230 230 try:
231 231 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
232 232 testfile, operation)
233 233 finally:
234 234 ui.write = oldwrite
235 235 return newchunks, newopts
236 236
237 237 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
238 238 filterfn, *pats, **opts):
239 239 opts = pycompat.byteskwargs(opts)
240 240 if not ui.interactive():
241 241 if cmdsuggest:
242 242 msg = _('running non-interactively, use %s instead') % cmdsuggest
243 243 else:
244 244 msg = _('running non-interactively')
245 245 raise error.Abort(msg)
246 246
247 247 # make sure username is set before going interactive
248 248 if not opts.get('user'):
249 249 ui.username() # raise exception, username not provided
250 250
251 251 def recordfunc(ui, repo, message, match, opts):
252 252 """This is generic record driver.
253 253
254 254 Its job is to interactively filter local changes, and
255 255 accordingly prepare working directory into a state in which the
256 256 job can be delegated to a non-interactive commit command such as
257 257 'commit' or 'qrefresh'.
258 258
259 259 After the actual job is done by non-interactive command, the
260 260 working directory is restored to its original state.
261 261
262 262 In the end we'll record interesting changes, and everything else
263 263 will be left in place, so the user can continue working.
264 264 """
265 265
266 266 checkunfinished(repo, commit=True)
267 267 wctx = repo[None]
268 268 merge = len(wctx.parents()) > 1
269 269 if merge:
270 270 raise error.Abort(_('cannot partially commit a merge '
271 271 '(use "hg commit" instead)'))
272 272
273 273 def fail(f, msg):
274 274 raise error.Abort('%s: %s' % (f, msg))
275 275
276 276 force = opts.get('force')
277 277 if not force:
278 278 vdirs = []
279 279 match.explicitdir = vdirs.append
280 280 match.bad = fail
281 281
282 282 status = repo.status(match=match)
283 283 if not force:
284 284 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
285 285 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True,
286 286 section='commands',
287 287 configprefix='commit.interactive.')
288 288 diffopts.nodates = True
289 289 diffopts.git = True
290 290 diffopts.showfunc = True
291 291 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
292 292 originalchunks = patch.parsepatch(originaldiff)
293 293
294 294 # 1. filter patch, since we are intending to apply subset of it
295 295 try:
296 296 chunks, newopts = filterfn(ui, originalchunks)
297 297 except error.PatchError as err:
298 298 raise error.Abort(_('error parsing patch: %s') % err)
299 299 opts.update(newopts)
300 300
301 301 # We need to keep a backup of files that have been newly added and
302 302 # modified during the recording process because there is a previous
303 303 # version without the edit in the workdir
304 304 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
305 305 contenders = set()
306 306 for h in chunks:
307 307 try:
308 308 contenders.update(set(h.files()))
309 309 except AttributeError:
310 310 pass
311 311
312 312 changed = status.modified + status.added + status.removed
313 313 newfiles = [f for f in changed if f in contenders]
314 314 if not newfiles:
315 315 ui.status(_('no changes to record\n'))
316 316 return 0
317 317
318 318 modified = set(status.modified)
319 319
320 320 # 2. backup changed files, so we can restore them in the end
321 321
322 322 if backupall:
323 323 tobackup = changed
324 324 else:
325 325 tobackup = [f for f in newfiles if f in modified or f in \
326 326 newlyaddedandmodifiedfiles]
327 327 backups = {}
328 328 if tobackup:
329 329 backupdir = repo.vfs.join('record-backups')
330 330 try:
331 331 os.mkdir(backupdir)
332 332 except OSError as err:
333 333 if err.errno != errno.EEXIST:
334 334 raise
335 335 try:
336 336 # backup continues
337 337 for f in tobackup:
338 338 fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.',
339 339 dir=backupdir)
340 340 os.close(fd)
341 341 ui.debug('backup %r as %r\n' % (f, tmpname))
342 342 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
343 343 backups[f] = tmpname
344 344
345 345 fp = stringio()
346 346 for c in chunks:
347 347 fname = c.filename()
348 348 if fname in backups:
349 349 c.write(fp)
350 350 dopatch = fp.tell()
351 351 fp.seek(0)
352 352
353 353 # 2.5 optionally review / modify patch in text editor
354 354 if opts.get('review', False):
355 355 patchtext = (crecordmod.diffhelptext
356 356 + crecordmod.patchhelptext
357 357 + fp.read())
358 358 reviewedpatch = ui.edit(patchtext, "",
359 359 action="diff",
360 360 repopath=repo.path)
361 361 fp.truncate(0)
362 362 fp.write(reviewedpatch)
363 363 fp.seek(0)
364 364
365 365 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
366 366 # 3a. apply filtered patch to clean repo (clean)
367 367 if backups:
368 368 # Equivalent to hg.revert
369 369 m = scmutil.matchfiles(repo, backups.keys())
370 370 mergemod.update(repo, repo.dirstate.p1(), branchmerge=False,
371 371 force=True, matcher=m)
372 372
373 373 # 3b. (apply)
374 374 if dopatch:
375 375 try:
376 376 ui.debug('applying patch\n')
377 377 ui.debug(fp.getvalue())
378 378 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
379 379 except error.PatchError as err:
380 380 raise error.Abort(pycompat.bytestr(err))
381 381 del fp
382 382
383 383 # 4. We prepared working directory according to filtered
384 384 # patch. Now is the time to delegate the job to
385 385 # commit/qrefresh or the like!
386 386
387 387 # Make all of the pathnames absolute.
388 388 newfiles = [repo.wjoin(nf) for nf in newfiles]
389 389 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
390 390 finally:
391 391 # 5. finally restore backed-up files
392 392 try:
393 393 dirstate = repo.dirstate
394 394 for realname, tmpname in backups.iteritems():
395 395 ui.debug('restoring %r to %r\n' % (tmpname, realname))
396 396
397 397 if dirstate[realname] == 'n':
398 398 # without normallookup, restoring timestamp
399 399 # may cause partially committed files
400 400 # to be treated as unmodified
401 401 dirstate.normallookup(realname)
402 402
403 403 # copystat=True here and above are a hack to trick any
404 404 # editors that have f open that we haven't modified them.
405 405 #
406 406 # Also note that this racy as an editor could notice the
407 407 # file's mtime before we've finished writing it.
408 408 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
409 409 os.unlink(tmpname)
410 410 if tobackup:
411 411 os.rmdir(backupdir)
412 412 except OSError:
413 413 pass
414 414
415 415 def recordinwlock(ui, repo, message, match, opts):
416 416 with repo.wlock():
417 417 return recordfunc(ui, repo, message, match, opts)
418 418
419 419 return commit(ui, repo, recordinwlock, pats, opts)
420 420
421 421 class dirnode(object):
422 422 """
423 423 Represent a directory in user working copy with information required for
424 424 the purpose of tersing its status.
425 425
426 426 path is the path to the directory, without a trailing '/'
427 427
428 428 statuses is a set of statuses of all files in this directory (this includes
429 429 all the files in all the subdirectories too)
430 430
431 431 files is a list of files which are direct child of this directory
432 432
433 433 subdirs is a dictionary of sub-directory name as the key and it's own
434 434 dirnode object as the value
435 435 """
436 436
437 437 def __init__(self, dirpath):
438 438 self.path = dirpath
439 439 self.statuses = set([])
440 440 self.files = []
441 441 self.subdirs = {}
442 442
443 443 def _addfileindir(self, filename, status):
444 444 """Add a file in this directory as a direct child."""
445 445 self.files.append((filename, status))
446 446
447 447 def addfile(self, filename, status):
448 448 """
449 449 Add a file to this directory or to its direct parent directory.
450 450
451 451 If the file is not direct child of this directory, we traverse to the
452 452 directory of which this file is a direct child of and add the file
453 453 there.
454 454 """
455 455
456 456 # the filename contains a path separator, it means it's not the direct
457 457 # child of this directory
458 458 if '/' in filename:
459 459 subdir, filep = filename.split('/', 1)
460 460
461 461 # does the dirnode object for subdir exists
462 462 if subdir not in self.subdirs:
463 463 subdirpath = pathutil.join(self.path, subdir)
464 464 self.subdirs[subdir] = dirnode(subdirpath)
465 465
466 466 # try adding the file in subdir
467 467 self.subdirs[subdir].addfile(filep, status)
468 468
469 469 else:
470 470 self._addfileindir(filename, status)
471 471
472 472 if status not in self.statuses:
473 473 self.statuses.add(status)
474 474
475 475 def iterfilepaths(self):
476 476 """Yield (status, path) for files directly under this directory."""
477 477 for f, st in self.files:
478 478 yield st, pathutil.join(self.path, f)
479 479
480 480 def tersewalk(self, terseargs):
481 481 """
482 482 Yield (status, path) obtained by processing the status of this
483 483 dirnode.
484 484
485 485 terseargs is the string of arguments passed by the user with `--terse`
486 486 flag.
487 487
488 488 Following are the cases which can happen:
489 489
490 490 1) All the files in the directory (including all the files in its
491 491 subdirectories) share the same status and the user has asked us to terse
492 492 that status. -> yield (status, dirpath). dirpath will end in '/'.
493 493
494 494 2) Otherwise, we do following:
495 495
496 496 a) Yield (status, filepath) for all the files which are in this
497 497 directory (only the ones in this directory, not the subdirs)
498 498
499 499 b) Recurse the function on all the subdirectories of this
500 500 directory
501 501 """
502 502
503 503 if len(self.statuses) == 1:
504 504 onlyst = self.statuses.pop()
505 505
506 506 # Making sure we terse only when the status abbreviation is
507 507 # passed as terse argument
508 508 if onlyst in terseargs:
509 509 yield onlyst, self.path + '/'
510 510 return
511 511
512 512 # add the files to status list
513 513 for st, fpath in self.iterfilepaths():
514 514 yield st, fpath
515 515
516 516 #recurse on the subdirs
517 517 for dirobj in self.subdirs.values():
518 518 for st, fpath in dirobj.tersewalk(terseargs):
519 519 yield st, fpath
520 520
521 521 def tersedir(statuslist, terseargs):
522 522 """
523 523 Terse the status if all the files in a directory shares the same status.
524 524
525 525 statuslist is scmutil.status() object which contains a list of files for
526 526 each status.
527 527 terseargs is string which is passed by the user as the argument to `--terse`
528 528 flag.
529 529
530 530 The function makes a tree of objects of dirnode class, and at each node it
531 531 stores the information required to know whether we can terse a certain
532 532 directory or not.
533 533 """
534 534 # the order matters here as that is used to produce final list
535 535 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
536 536
537 537 # checking the argument validity
538 538 for s in pycompat.bytestr(terseargs):
539 539 if s not in allst:
540 540 raise error.Abort(_("'%s' not recognized") % s)
541 541
542 542 # creating a dirnode object for the root of the repo
543 543 rootobj = dirnode('')
544 544 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
545 545 'ignored', 'removed')
546 546
547 547 tersedict = {}
548 548 for attrname in pstatus:
549 549 statuschar = attrname[0:1]
550 550 for f in getattr(statuslist, attrname):
551 551 rootobj.addfile(f, statuschar)
552 552 tersedict[statuschar] = []
553 553
554 554 # we won't be tersing the root dir, so add files in it
555 555 for st, fpath in rootobj.iterfilepaths():
556 556 tersedict[st].append(fpath)
557 557
558 558 # process each sub-directory and build tersedict
559 559 for subdir in rootobj.subdirs.values():
560 560 for st, f in subdir.tersewalk(terseargs):
561 561 tersedict[st].append(f)
562 562
563 563 tersedlist = []
564 564 for st in allst:
565 565 tersedict[st].sort()
566 566 tersedlist.append(tersedict[st])
567 567
568 568 return tersedlist
569 569
570 570 def _commentlines(raw):
571 571 '''Surround lineswith a comment char and a new line'''
572 572 lines = raw.splitlines()
573 573 commentedlines = ['# %s' % line for line in lines]
574 574 return '\n'.join(commentedlines) + '\n'
575 575
576 576 def _conflictsmsg(repo):
577 577 mergestate = mergemod.mergestate.read(repo)
578 578 if not mergestate.active():
579 579 return
580 580
581 581 m = scmutil.match(repo[None])
582 582 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
583 583 if unresolvedlist:
584 584 mergeliststr = '\n'.join(
585 585 [' %s' % util.pathto(repo.root, encoding.getcwd(), path)
586 586 for path in sorted(unresolvedlist)])
587 587 msg = _('''Unresolved merge conflicts:
588 588
589 589 %s
590 590
591 591 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
592 592 else:
593 593 msg = _('No unresolved merge conflicts.')
594 594
595 595 return _commentlines(msg)
596 596
597 597 def _helpmessage(continuecmd, abortcmd):
598 598 msg = _('To continue: %s\n'
599 599 'To abort: %s') % (continuecmd, abortcmd)
600 600 return _commentlines(msg)
601 601
602 602 def _rebasemsg():
603 603 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
604 604
605 605 def _histeditmsg():
606 606 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
607 607
608 608 def _unshelvemsg():
609 609 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
610 610
611 611 def _graftmsg():
612 612 return _helpmessage('hg graft --continue', 'hg graft --abort')
613 613
614 614 def _mergemsg():
615 615 return _helpmessage('hg commit', 'hg merge --abort')
616 616
617 617 def _bisectmsg():
618 618 msg = _('To mark the changeset good: hg bisect --good\n'
619 619 'To mark the changeset bad: hg bisect --bad\n'
620 620 'To abort: hg bisect --reset\n')
621 621 return _commentlines(msg)
622 622
623 623 def fileexistspredicate(filename):
624 624 return lambda repo: repo.vfs.exists(filename)
625 625
626 626 def _mergepredicate(repo):
627 627 return len(repo[None].parents()) > 1
628 628
629 629 STATES = (
630 630 # (state, predicate to detect states, helpful message function)
631 631 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
632 632 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
633 633 ('graft', fileexistspredicate('graftstate'), _graftmsg),
634 634 ('unshelve', fileexistspredicate('shelvedstate'), _unshelvemsg),
635 635 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
636 636 # The merge state is part of a list that will be iterated over.
637 637 # They need to be last because some of the other unfinished states may also
638 638 # be in a merge or update state (eg. rebase, histedit, graft, etc).
639 639 # We want those to have priority.
640 640 ('merge', _mergepredicate, _mergemsg),
641 641 )
642 642
643 643 def _getrepostate(repo):
644 644 # experimental config: commands.status.skipstates
645 645 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
646 646 for state, statedetectionpredicate, msgfn in STATES:
647 647 if state in skip:
648 648 continue
649 649 if statedetectionpredicate(repo):
650 650 return (state, statedetectionpredicate, msgfn)
651 651
652 652 def morestatus(repo, fm):
653 653 statetuple = _getrepostate(repo)
654 654 label = 'status.morestatus'
655 655 if statetuple:
656 656 state, statedetectionpredicate, helpfulmsg = statetuple
657 657 statemsg = _('The repository is in an unfinished *%s* state.') % state
658 658 fm.plain('%s\n' % _commentlines(statemsg), label=label)
659 659 conmsg = _conflictsmsg(repo)
660 660 if conmsg:
661 661 fm.plain('%s\n' % conmsg, label=label)
662 662 if helpfulmsg:
663 663 helpmsg = helpfulmsg()
664 664 fm.plain('%s\n' % helpmsg, label=label)
665 665
666 666 def findpossible(cmd, table, strict=False):
667 667 """
668 668 Return cmd -> (aliases, command table entry)
669 669 for each matching command.
670 670 Return debug commands (or their aliases) only if no normal command matches.
671 671 """
672 672 choice = {}
673 673 debugchoice = {}
674 674
675 675 if cmd in table:
676 676 # short-circuit exact matches, "log" alias beats "log|history"
677 677 keys = [cmd]
678 678 else:
679 679 keys = table.keys()
680 680
681 681 allcmds = []
682 682 for e in keys:
683 683 aliases = parsealiases(e)
684 684 allcmds.extend(aliases)
685 685 found = None
686 686 if cmd in aliases:
687 687 found = cmd
688 688 elif not strict:
689 689 for a in aliases:
690 690 if a.startswith(cmd):
691 691 found = a
692 692 break
693 693 if found is not None:
694 694 if aliases[0].startswith("debug") or found.startswith("debug"):
695 695 debugchoice[found] = (aliases, table[e])
696 696 else:
697 697 choice[found] = (aliases, table[e])
698 698
699 699 if not choice and debugchoice:
700 700 choice = debugchoice
701 701
702 702 return choice, allcmds
703 703
704 704 def findcmd(cmd, table, strict=True):
705 705 """Return (aliases, command table entry) for command string."""
706 706 choice, allcmds = findpossible(cmd, table, strict)
707 707
708 708 if cmd in choice:
709 709 return choice[cmd]
710 710
711 711 if len(choice) > 1:
712 712 clist = sorted(choice)
713 713 raise error.AmbiguousCommand(cmd, clist)
714 714
715 715 if choice:
716 716 return list(choice.values())[0]
717 717
718 718 raise error.UnknownCommand(cmd, allcmds)
719 719
720 720 def changebranch(ui, repo, revs, label):
721 721 """ Change the branch name of given revs to label """
722 722
723 723 with repo.wlock(), repo.lock(), repo.transaction('branches'):
724 724 # abort in case of uncommitted merge or dirty wdir
725 725 bailifchanged(repo)
726 726 revs = scmutil.revrange(repo, revs)
727 727 if not revs:
728 728 raise error.Abort("empty revision set")
729 729 roots = repo.revs('roots(%ld)', revs)
730 730 if len(roots) > 1:
731 731 raise error.Abort(_("cannot change branch of non-linear revisions"))
732 732 rewriteutil.precheck(repo, revs, 'change branch of')
733 733
734 734 root = repo[roots.first()]
735 735 rpb = {parent.branch() for parent in root.parents()}
736 736 if label not in rpb and label in repo.branchmap():
737 737 raise error.Abort(_("a branch of the same name already exists"))
738 738
739 739 if repo.revs('obsolete() and %ld', revs):
740 740 raise error.Abort(_("cannot change branch of a obsolete changeset"))
741 741
742 742 # make sure only topological heads
743 743 if repo.revs('heads(%ld) - head()', revs):
744 744 raise error.Abort(_("cannot change branch in middle of a stack"))
745 745
746 746 replacements = {}
747 747 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
748 748 # mercurial.subrepo -> mercurial.cmdutil
749 749 from . import context
750 750 for rev in revs:
751 751 ctx = repo[rev]
752 752 oldbranch = ctx.branch()
753 753 # check if ctx has same branch
754 754 if oldbranch == label:
755 755 continue
756 756
757 757 def filectxfn(repo, newctx, path):
758 758 try:
759 759 return ctx[path]
760 760 except error.ManifestLookupError:
761 761 return None
762 762
763 763 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
764 764 % (hex(ctx.node()), oldbranch, label))
765 765 extra = ctx.extra()
766 766 extra['branch_change'] = hex(ctx.node())
767 767 # While changing branch of set of linear commits, make sure that
768 768 # we base our commits on new parent rather than old parent which
769 769 # was obsoleted while changing the branch
770 770 p1 = ctx.p1().node()
771 771 p2 = ctx.p2().node()
772 772 if p1 in replacements:
773 773 p1 = replacements[p1][0]
774 774 if p2 in replacements:
775 775 p2 = replacements[p2][0]
776 776
777 777 mc = context.memctx(repo, (p1, p2),
778 778 ctx.description(),
779 779 ctx.files(),
780 780 filectxfn,
781 781 user=ctx.user(),
782 782 date=ctx.date(),
783 783 extra=extra,
784 784 branch=label)
785 785
786 786 newnode = repo.commitctx(mc)
787 787 replacements[ctx.node()] = (newnode,)
788 788 ui.debug('new node id is %s\n' % hex(newnode))
789 789
790 790 # create obsmarkers and move bookmarks
791 791 scmutil.cleanupnodes(repo, replacements, 'branch-change', fixphase=True)
792 792
793 793 # move the working copy too
794 794 wctx = repo[None]
795 795 # in-progress merge is a bit too complex for now.
796 796 if len(wctx.parents()) == 1:
797 797 newid = replacements.get(wctx.p1().node())
798 798 if newid is not None:
799 799 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
800 800 # mercurial.cmdutil
801 801 from . import hg
802 802 hg.update(repo, newid[0], quietempty=True)
803 803
804 804 ui.status(_("changed branch on %d changesets\n") % len(replacements))
805 805
806 806 def findrepo(p):
807 807 while not os.path.isdir(os.path.join(p, ".hg")):
808 808 oldp, p = p, os.path.dirname(p)
809 809 if p == oldp:
810 810 return None
811 811
812 812 return p
813 813
814 814 def bailifchanged(repo, merge=True, hint=None):
815 815 """ enforce the precondition that working directory must be clean.
816 816
817 817 'merge' can be set to false if a pending uncommitted merge should be
818 818 ignored (such as when 'update --check' runs).
819 819
820 820 'hint' is the usual hint given to Abort exception.
821 821 """
822 822
823 823 if merge and repo.dirstate.p2() != nullid:
824 824 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
825 825 modified, added, removed, deleted = repo.status()[:4]
826 826 if modified or added or removed or deleted:
827 827 raise error.Abort(_('uncommitted changes'), hint=hint)
828 828 ctx = repo[None]
829 829 for s in sorted(ctx.substate):
830 830 ctx.sub(s).bailifchanged(hint=hint)
831 831
832 832 def logmessage(ui, opts):
833 833 """ get the log message according to -m and -l option """
834 834 message = opts.get('message')
835 835 logfile = opts.get('logfile')
836 836
837 837 if message and logfile:
838 838 raise error.Abort(_('options --message and --logfile are mutually '
839 839 'exclusive'))
840 840 if not message and logfile:
841 841 try:
842 842 if isstdiofilename(logfile):
843 843 message = ui.fin.read()
844 844 else:
845 845 message = '\n'.join(util.readfile(logfile).splitlines())
846 846 except IOError as inst:
847 847 raise error.Abort(_("can't read commit message '%s': %s") %
848 848 (logfile, encoding.strtolocal(inst.strerror)))
849 849 return message
850 850
851 851 def mergeeditform(ctxorbool, baseformname):
852 852 """return appropriate editform name (referencing a committemplate)
853 853
854 854 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
855 855 merging is committed.
856 856
857 857 This returns baseformname with '.merge' appended if it is a merge,
858 858 otherwise '.normal' is appended.
859 859 """
860 860 if isinstance(ctxorbool, bool):
861 861 if ctxorbool:
862 862 return baseformname + ".merge"
863 863 elif len(ctxorbool.parents()) > 1:
864 864 return baseformname + ".merge"
865 865
866 866 return baseformname + ".normal"
867 867
868 868 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
869 869 editform='', **opts):
870 870 """get appropriate commit message editor according to '--edit' option
871 871
872 872 'finishdesc' is a function to be called with edited commit message
873 873 (= 'description' of the new changeset) just after editing, but
874 874 before checking empty-ness. It should return actual text to be
875 875 stored into history. This allows to change description before
876 876 storing.
877 877
878 878 'extramsg' is a extra message to be shown in the editor instead of
879 879 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
880 880 is automatically added.
881 881
882 882 'editform' is a dot-separated list of names, to distinguish
883 883 the purpose of commit text editing.
884 884
885 885 'getcommiteditor' returns 'commitforceeditor' regardless of
886 886 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
887 887 they are specific for usage in MQ.
888 888 """
889 889 if edit or finishdesc or extramsg:
890 890 return lambda r, c, s: commitforceeditor(r, c, s,
891 891 finishdesc=finishdesc,
892 892 extramsg=extramsg,
893 893 editform=editform)
894 894 elif editform:
895 895 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
896 896 else:
897 897 return commiteditor
898 898
899 899 def _escapecommandtemplate(tmpl):
900 900 parts = []
901 901 for typ, start, end in templater.scantemplate(tmpl, raw=True):
902 902 if typ == b'string':
903 903 parts.append(stringutil.escapestr(tmpl[start:end]))
904 904 else:
905 905 parts.append(tmpl[start:end])
906 906 return b''.join(parts)
907 907
908 908 def rendercommandtemplate(ui, tmpl, props):
909 909 r"""Expand a literal template 'tmpl' in a way suitable for command line
910 910
911 911 '\' in outermost string is not taken as an escape character because it
912 912 is a directory separator on Windows.
913 913
914 914 >>> from . import ui as uimod
915 915 >>> ui = uimod.ui()
916 916 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
917 917 'c:\\foo'
918 918 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
919 919 'c:{path}'
920 920 """
921 921 if not tmpl:
922 922 return tmpl
923 923 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
924 924 return t.renderdefault(props)
925 925
926 926 def rendertemplate(ctx, tmpl, props=None):
927 927 """Expand a literal template 'tmpl' byte-string against one changeset
928 928
929 929 Each props item must be a stringify-able value or a callable returning
930 930 such value, i.e. no bare list nor dict should be passed.
931 931 """
932 932 repo = ctx.repo()
933 933 tres = formatter.templateresources(repo.ui, repo)
934 934 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
935 935 resources=tres)
936 936 mapping = {'ctx': ctx}
937 937 if props:
938 938 mapping.update(props)
939 939 return t.renderdefault(mapping)
940 940
941 941 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
942 942 r"""Convert old-style filename format string to template string
943 943
944 944 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
945 945 'foo-{reporoot|basename}-{seqno}.patch'
946 946 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
947 947 '{rev}{tags % "{tag}"}{node}'
948 948
949 949 '\' in outermost strings has to be escaped because it is a directory
950 950 separator on Windows:
951 951
952 952 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
953 953 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
954 954 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
955 955 '\\\\\\\\foo\\\\bar.patch'
956 956 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
957 957 '\\\\{tags % "{tag}"}'
958 958
959 959 but inner strings follow the template rules (i.e. '\' is taken as an
960 960 escape character):
961 961
962 962 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
963 963 '{"c:\\tmp"}'
964 964 """
965 965 expander = {
966 966 b'H': b'{node}',
967 967 b'R': b'{rev}',
968 968 b'h': b'{node|short}',
969 969 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
970 970 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
971 971 b'%': b'%',
972 972 b'b': b'{reporoot|basename}',
973 973 }
974 974 if total is not None:
975 975 expander[b'N'] = b'{total}'
976 976 if seqno is not None:
977 977 expander[b'n'] = b'{seqno}'
978 978 if total is not None and seqno is not None:
979 979 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
980 980 if pathname is not None:
981 981 expander[b's'] = b'{pathname|basename}'
982 982 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
983 983 expander[b'p'] = b'{pathname}'
984 984
985 985 newname = []
986 986 for typ, start, end in templater.scantemplate(pat, raw=True):
987 987 if typ != b'string':
988 988 newname.append(pat[start:end])
989 989 continue
990 990 i = start
991 991 while i < end:
992 992 n = pat.find(b'%', i, end)
993 993 if n < 0:
994 994 newname.append(stringutil.escapestr(pat[i:end]))
995 995 break
996 996 newname.append(stringutil.escapestr(pat[i:n]))
997 997 if n + 2 > end:
998 998 raise error.Abort(_("incomplete format spec in output "
999 999 "filename"))
1000 1000 c = pat[n + 1:n + 2]
1001 1001 i = n + 2
1002 1002 try:
1003 1003 newname.append(expander[c])
1004 1004 except KeyError:
1005 1005 raise error.Abort(_("invalid format spec '%%%s' in output "
1006 1006 "filename") % c)
1007 1007 return ''.join(newname)
1008 1008
1009 1009 def makefilename(ctx, pat, **props):
1010 1010 if not pat:
1011 1011 return pat
1012 1012 tmpl = _buildfntemplate(pat, **props)
1013 1013 # BUG: alias expansion shouldn't be made against template fragments
1014 1014 # rewritten from %-format strings, but we have no easy way to partially
1015 1015 # disable the expansion.
1016 1016 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1017 1017
1018 1018 def isstdiofilename(pat):
1019 1019 """True if the given pat looks like a filename denoting stdin/stdout"""
1020 1020 return not pat or pat == '-'
1021 1021
1022 1022 class _unclosablefile(object):
1023 1023 def __init__(self, fp):
1024 1024 self._fp = fp
1025 1025
1026 1026 def close(self):
1027 1027 pass
1028 1028
1029 1029 def __iter__(self):
1030 1030 return iter(self._fp)
1031 1031
1032 1032 def __getattr__(self, attr):
1033 1033 return getattr(self._fp, attr)
1034 1034
1035 1035 def __enter__(self):
1036 1036 return self
1037 1037
1038 1038 def __exit__(self, exc_type, exc_value, exc_tb):
1039 1039 pass
1040 1040
1041 1041 def makefileobj(ctx, pat, mode='wb', **props):
1042 1042 writable = mode not in ('r', 'rb')
1043 1043
1044 1044 if isstdiofilename(pat):
1045 1045 repo = ctx.repo()
1046 1046 if writable:
1047 1047 fp = repo.ui.fout
1048 1048 else:
1049 1049 fp = repo.ui.fin
1050 1050 return _unclosablefile(fp)
1051 1051 fn = makefilename(ctx, pat, **props)
1052 1052 return open(fn, mode)
1053 1053
1054 1054 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1055 1055 """opens the changelog, manifest, a filelog or a given revlog"""
1056 1056 cl = opts['changelog']
1057 1057 mf = opts['manifest']
1058 1058 dir = opts['dir']
1059 1059 msg = None
1060 1060 if cl and mf:
1061 1061 msg = _('cannot specify --changelog and --manifest at the same time')
1062 1062 elif cl and dir:
1063 1063 msg = _('cannot specify --changelog and --dir at the same time')
1064 1064 elif cl or mf or dir:
1065 1065 if file_:
1066 1066 msg = _('cannot specify filename with --changelog or --manifest')
1067 1067 elif not repo:
1068 1068 msg = _('cannot specify --changelog or --manifest or --dir '
1069 1069 'without a repository')
1070 1070 if msg:
1071 1071 raise error.Abort(msg)
1072 1072
1073 1073 r = None
1074 1074 if repo:
1075 1075 if cl:
1076 1076 r = repo.unfiltered().changelog
1077 1077 elif dir:
1078 1078 if 'treemanifest' not in repo.requirements:
1079 1079 raise error.Abort(_("--dir can only be used on repos with "
1080 1080 "treemanifest enabled"))
1081 1081 if not dir.endswith('/'):
1082 1082 dir = dir + '/'
1083 1083 dirlog = repo.manifestlog.getstorage(dir)
1084 1084 if len(dirlog):
1085 1085 r = dirlog
1086 1086 elif mf:
1087 1087 r = repo.manifestlog.getstorage(b'')
1088 1088 elif file_:
1089 1089 filelog = repo.file(file_)
1090 1090 if len(filelog):
1091 1091 r = filelog
1092 1092
1093 1093 # Not all storage may be revlogs. If requested, try to return an actual
1094 1094 # revlog instance.
1095 1095 if returnrevlog:
1096 1096 if isinstance(r, revlog.revlog):
1097 1097 pass
1098 1098 elif util.safehasattr(r, '_revlog'):
1099 1099 r = r._revlog
1100 1100 elif r is not None:
1101 1101 raise error.Abort(_('%r does not appear to be a revlog') % r)
1102 1102
1103 1103 if not r:
1104 1104 if not returnrevlog:
1105 1105 raise error.Abort(_('cannot give path to non-revlog'))
1106 1106
1107 1107 if not file_:
1108 1108 raise error.CommandError(cmd, _('invalid arguments'))
1109 1109 if not os.path.isfile(file_):
1110 1110 raise error.Abort(_("revlog '%s' not found") % file_)
1111 1111 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False),
1112 1112 file_[:-2] + ".i")
1113 1113 return r
1114 1114
1115 1115 def openrevlog(repo, cmd, file_, opts):
1116 1116 """Obtain a revlog backing storage of an item.
1117 1117
1118 1118 This is similar to ``openstorage()`` except it always returns a revlog.
1119 1119
1120 1120 In most cases, a caller cares about the main storage object - not the
1121 1121 revlog backing it. Therefore, this function should only be used by code
1122 1122 that needs to examine low-level revlog implementation details. e.g. debug
1123 1123 commands.
1124 1124 """
1125 1125 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1126 1126
1127 1127 def copy(ui, repo, pats, opts, rename=False):
1128 1128 # called with the repo lock held
1129 1129 #
1130 1130 # hgsep => pathname that uses "/" to separate directories
1131 1131 # ossep => pathname that uses os.sep to separate directories
1132 1132 cwd = repo.getcwd()
1133 1133 targets = {}
1134 1134 after = opts.get("after")
1135 1135 dryrun = opts.get("dry_run")
1136 1136 wctx = repo[None]
1137 1137
1138 1138 def walkpat(pat):
1139 1139 srcs = []
1140 1140 if after:
1141 1141 badstates = '?'
1142 1142 else:
1143 1143 badstates = '?r'
1144 1144 m = scmutil.match(wctx, [pat], opts, globbed=True)
1145 1145 for abs in wctx.walk(m):
1146 1146 state = repo.dirstate[abs]
1147 1147 rel = m.rel(abs)
1148 1148 exact = m.exact(abs)
1149 1149 if state in badstates:
1150 1150 if exact and state == '?':
1151 1151 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1152 1152 if exact and state == 'r':
1153 1153 ui.warn(_('%s: not copying - file has been marked for'
1154 1154 ' remove\n') % rel)
1155 1155 continue
1156 1156 # abs: hgsep
1157 1157 # rel: ossep
1158 1158 srcs.append((abs, rel, exact))
1159 1159 return srcs
1160 1160
1161 1161 # abssrc: hgsep
1162 1162 # relsrc: ossep
1163 1163 # otarget: ossep
1164 1164 def copyfile(abssrc, relsrc, otarget, exact):
1165 1165 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1166 1166 if '/' in abstarget:
1167 1167 # We cannot normalize abstarget itself, this would prevent
1168 1168 # case only renames, like a => A.
1169 1169 abspath, absname = abstarget.rsplit('/', 1)
1170 1170 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1171 1171 reltarget = repo.pathto(abstarget, cwd)
1172 1172 target = repo.wjoin(abstarget)
1173 1173 src = repo.wjoin(abssrc)
1174 1174 state = repo.dirstate[abstarget]
1175 1175
1176 1176 scmutil.checkportable(ui, abstarget)
1177 1177
1178 1178 # check for collisions
1179 1179 prevsrc = targets.get(abstarget)
1180 1180 if prevsrc is not None:
1181 1181 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1182 1182 (reltarget, repo.pathto(abssrc, cwd),
1183 1183 repo.pathto(prevsrc, cwd)))
1184 1184 return True # report a failure
1185 1185
1186 1186 # check for overwrites
1187 1187 exists = os.path.lexists(target)
1188 1188 samefile = False
1189 1189 if exists and abssrc != abstarget:
1190 1190 if (repo.dirstate.normalize(abssrc) ==
1191 1191 repo.dirstate.normalize(abstarget)):
1192 1192 if not rename:
1193 1193 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1194 1194 return True # report a failure
1195 1195 exists = False
1196 1196 samefile = True
1197 1197
1198 1198 if not after and exists or after and state in 'mn':
1199 1199 if not opts['force']:
1200 1200 if state in 'mn':
1201 1201 msg = _('%s: not overwriting - file already committed\n')
1202 1202 if after:
1203 1203 flags = '--after --force'
1204 1204 else:
1205 1205 flags = '--force'
1206 1206 if rename:
1207 1207 hint = _("('hg rename %s' to replace the file by "
1208 1208 'recording a rename)\n') % flags
1209 1209 else:
1210 1210 hint = _("('hg copy %s' to replace the file by "
1211 1211 'recording a copy)\n') % flags
1212 1212 else:
1213 1213 msg = _('%s: not overwriting - file exists\n')
1214 1214 if rename:
1215 1215 hint = _("('hg rename --after' to record the rename)\n")
1216 1216 else:
1217 1217 hint = _("('hg copy --after' to record the copy)\n")
1218 1218 ui.warn(msg % reltarget)
1219 1219 ui.warn(hint)
1220 1220 return True # report a failure
1221 1221
1222 1222 if after:
1223 1223 if not exists:
1224 1224 if rename:
1225 1225 ui.warn(_('%s: not recording move - %s does not exist\n') %
1226 1226 (relsrc, reltarget))
1227 1227 else:
1228 1228 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1229 1229 (relsrc, reltarget))
1230 1230 return True # report a failure
1231 1231 elif not dryrun:
1232 1232 try:
1233 1233 if exists:
1234 1234 os.unlink(target)
1235 1235 targetdir = os.path.dirname(target) or '.'
1236 1236 if not os.path.isdir(targetdir):
1237 1237 os.makedirs(targetdir)
1238 1238 if samefile:
1239 1239 tmp = target + "~hgrename"
1240 1240 os.rename(src, tmp)
1241 1241 os.rename(tmp, target)
1242 1242 else:
1243 1243 # Preserve stat info on renames, not on copies; this matches
1244 1244 # Linux CLI behavior.
1245 1245 util.copyfile(src, target, copystat=rename)
1246 1246 srcexists = True
1247 1247 except IOError as inst:
1248 1248 if inst.errno == errno.ENOENT:
1249 1249 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1250 1250 srcexists = False
1251 1251 else:
1252 1252 ui.warn(_('%s: cannot copy - %s\n') %
1253 1253 (relsrc, encoding.strtolocal(inst.strerror)))
1254 1254 return True # report a failure
1255 1255
1256 1256 if ui.verbose or not exact:
1257 1257 if rename:
1258 1258 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1259 1259 else:
1260 1260 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1261 1261
1262 1262 targets[abstarget] = abssrc
1263 1263
1264 1264 # fix up dirstate
1265 1265 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1266 1266 dryrun=dryrun, cwd=cwd)
1267 1267 if rename and not dryrun:
1268 1268 if not after and srcexists and not samefile:
1269 1269 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
1270 1270 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1271 1271 wctx.forget([abssrc])
1272 1272
1273 1273 # pat: ossep
1274 1274 # dest ossep
1275 1275 # srcs: list of (hgsep, hgsep, ossep, bool)
1276 1276 # return: function that takes hgsep and returns ossep
1277 1277 def targetpathfn(pat, dest, srcs):
1278 1278 if os.path.isdir(pat):
1279 1279 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1280 1280 abspfx = util.localpath(abspfx)
1281 1281 if destdirexists:
1282 1282 striplen = len(os.path.split(abspfx)[0])
1283 1283 else:
1284 1284 striplen = len(abspfx)
1285 1285 if striplen:
1286 1286 striplen += len(pycompat.ossep)
1287 1287 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1288 1288 elif destdirexists:
1289 1289 res = lambda p: os.path.join(dest,
1290 1290 os.path.basename(util.localpath(p)))
1291 1291 else:
1292 1292 res = lambda p: dest
1293 1293 return res
1294 1294
1295 1295 # pat: ossep
1296 1296 # dest ossep
1297 1297 # srcs: list of (hgsep, hgsep, ossep, bool)
1298 1298 # return: function that takes hgsep and returns ossep
1299 1299 def targetpathafterfn(pat, dest, srcs):
1300 1300 if matchmod.patkind(pat):
1301 1301 # a mercurial pattern
1302 1302 res = lambda p: os.path.join(dest,
1303 1303 os.path.basename(util.localpath(p)))
1304 1304 else:
1305 1305 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1306 1306 if len(abspfx) < len(srcs[0][0]):
1307 1307 # A directory. Either the target path contains the last
1308 1308 # component of the source path or it does not.
1309 1309 def evalpath(striplen):
1310 1310 score = 0
1311 1311 for s in srcs:
1312 1312 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1313 1313 if os.path.lexists(t):
1314 1314 score += 1
1315 1315 return score
1316 1316
1317 1317 abspfx = util.localpath(abspfx)
1318 1318 striplen = len(abspfx)
1319 1319 if striplen:
1320 1320 striplen += len(pycompat.ossep)
1321 1321 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1322 1322 score = evalpath(striplen)
1323 1323 striplen1 = len(os.path.split(abspfx)[0])
1324 1324 if striplen1:
1325 1325 striplen1 += len(pycompat.ossep)
1326 1326 if evalpath(striplen1) > score:
1327 1327 striplen = striplen1
1328 1328 res = lambda p: os.path.join(dest,
1329 1329 util.localpath(p)[striplen:])
1330 1330 else:
1331 1331 # a file
1332 1332 if destdirexists:
1333 1333 res = lambda p: os.path.join(dest,
1334 1334 os.path.basename(util.localpath(p)))
1335 1335 else:
1336 1336 res = lambda p: dest
1337 1337 return res
1338 1338
1339 1339 pats = scmutil.expandpats(pats)
1340 1340 if not pats:
1341 1341 raise error.Abort(_('no source or destination specified'))
1342 1342 if len(pats) == 1:
1343 1343 raise error.Abort(_('no destination specified'))
1344 1344 dest = pats.pop()
1345 1345 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1346 1346 if not destdirexists:
1347 1347 if len(pats) > 1 or matchmod.patkind(pats[0]):
1348 1348 raise error.Abort(_('with multiple sources, destination must be an '
1349 1349 'existing directory'))
1350 1350 if util.endswithsep(dest):
1351 1351 raise error.Abort(_('destination %s is not a directory') % dest)
1352 1352
1353 1353 tfn = targetpathfn
1354 1354 if after:
1355 1355 tfn = targetpathafterfn
1356 1356 copylist = []
1357 1357 for pat in pats:
1358 1358 srcs = walkpat(pat)
1359 1359 if not srcs:
1360 1360 continue
1361 1361 copylist.append((tfn(pat, dest, srcs), srcs))
1362 1362 if not copylist:
1363 1363 raise error.Abort(_('no files to copy'))
1364 1364
1365 1365 errors = 0
1366 1366 for targetpath, srcs in copylist:
1367 1367 for abssrc, relsrc, exact in srcs:
1368 1368 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1369 1369 errors += 1
1370 1370
1371 1371 return errors != 0
1372 1372
1373 1373 ## facility to let extension process additional data into an import patch
1374 1374 # list of identifier to be executed in order
1375 1375 extrapreimport = [] # run before commit
1376 1376 extrapostimport = [] # run after commit
1377 1377 # mapping from identifier to actual import function
1378 1378 #
1379 1379 # 'preimport' are run before the commit is made and are provided the following
1380 1380 # arguments:
1381 1381 # - repo: the localrepository instance,
1382 1382 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1383 1383 # - extra: the future extra dictionary of the changeset, please mutate it,
1384 1384 # - opts: the import options.
1385 1385 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1386 1386 # mutation of in memory commit and more. Feel free to rework the code to get
1387 1387 # there.
1388 1388 extrapreimportmap = {}
1389 1389 # 'postimport' are run after the commit is made and are provided the following
1390 1390 # argument:
1391 1391 # - ctx: the changectx created by import.
1392 1392 extrapostimportmap = {}
1393 1393
1394 1394 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1395 1395 """Utility function used by commands.import to import a single patch
1396 1396
1397 1397 This function is explicitly defined here to help the evolve extension to
1398 1398 wrap this part of the import logic.
1399 1399
1400 1400 The API is currently a bit ugly because it a simple code translation from
1401 1401 the import command. Feel free to make it better.
1402 1402
1403 1403 :patchdata: a dictionary containing parsed patch data (such as from
1404 1404 ``patch.extract()``)
1405 1405 :parents: nodes that will be parent of the created commit
1406 1406 :opts: the full dict of option passed to the import command
1407 1407 :msgs: list to save commit message to.
1408 1408 (used in case we need to save it when failing)
1409 1409 :updatefunc: a function that update a repo to a given node
1410 1410 updatefunc(<repo>, <node>)
1411 1411 """
1412 1412 # avoid cycle context -> subrepo -> cmdutil
1413 1413 from . import context
1414 1414
1415 1415 tmpname = patchdata.get('filename')
1416 1416 message = patchdata.get('message')
1417 1417 user = opts.get('user') or patchdata.get('user')
1418 1418 date = opts.get('date') or patchdata.get('date')
1419 1419 branch = patchdata.get('branch')
1420 1420 nodeid = patchdata.get('nodeid')
1421 1421 p1 = patchdata.get('p1')
1422 1422 p2 = patchdata.get('p2')
1423 1423
1424 1424 nocommit = opts.get('no_commit')
1425 1425 importbranch = opts.get('import_branch')
1426 1426 update = not opts.get('bypass')
1427 1427 strip = opts["strip"]
1428 1428 prefix = opts["prefix"]
1429 1429 sim = float(opts.get('similarity') or 0)
1430 1430
1431 1431 if not tmpname:
1432 1432 return None, None, False
1433 1433
1434 1434 rejects = False
1435 1435
1436 1436 cmdline_message = logmessage(ui, opts)
1437 1437 if cmdline_message:
1438 1438 # pickup the cmdline msg
1439 1439 message = cmdline_message
1440 1440 elif message:
1441 1441 # pickup the patch msg
1442 1442 message = message.strip()
1443 1443 else:
1444 1444 # launch the editor
1445 1445 message = None
1446 1446 ui.debug('message:\n%s\n' % (message or ''))
1447 1447
1448 1448 if len(parents) == 1:
1449 1449 parents.append(repo[nullid])
1450 1450 if opts.get('exact'):
1451 1451 if not nodeid or not p1:
1452 1452 raise error.Abort(_('not a Mercurial patch'))
1453 1453 p1 = repo[p1]
1454 1454 p2 = repo[p2 or nullid]
1455 1455 elif p2:
1456 1456 try:
1457 1457 p1 = repo[p1]
1458 1458 p2 = repo[p2]
1459 1459 # Without any options, consider p2 only if the
1460 1460 # patch is being applied on top of the recorded
1461 1461 # first parent.
1462 1462 if p1 != parents[0]:
1463 1463 p1 = parents[0]
1464 1464 p2 = repo[nullid]
1465 1465 except error.RepoError:
1466 1466 p1, p2 = parents
1467 1467 if p2.node() == nullid:
1468 1468 ui.warn(_("warning: import the patch as a normal revision\n"
1469 1469 "(use --exact to import the patch as a merge)\n"))
1470 1470 else:
1471 1471 p1, p2 = parents
1472 1472
1473 1473 n = None
1474 1474 if update:
1475 1475 if p1 != parents[0]:
1476 1476 updatefunc(repo, p1.node())
1477 1477 if p2 != parents[1]:
1478 1478 repo.setparents(p1.node(), p2.node())
1479 1479
1480 1480 if opts.get('exact') or importbranch:
1481 1481 repo.dirstate.setbranch(branch or 'default')
1482 1482
1483 1483 partial = opts.get('partial', False)
1484 1484 files = set()
1485 1485 try:
1486 1486 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1487 1487 files=files, eolmode=None, similarity=sim / 100.0)
1488 1488 except error.PatchError as e:
1489 1489 if not partial:
1490 1490 raise error.Abort(pycompat.bytestr(e))
1491 1491 if partial:
1492 1492 rejects = True
1493 1493
1494 1494 files = list(files)
1495 1495 if nocommit:
1496 1496 if message:
1497 1497 msgs.append(message)
1498 1498 else:
1499 1499 if opts.get('exact') or p2:
1500 1500 # If you got here, you either use --force and know what
1501 1501 # you are doing or used --exact or a merge patch while
1502 1502 # being updated to its first parent.
1503 1503 m = None
1504 1504 else:
1505 1505 m = scmutil.matchfiles(repo, files or [])
1506 1506 editform = mergeeditform(repo[None], 'import.normal')
1507 1507 if opts.get('exact'):
1508 1508 editor = None
1509 1509 else:
1510 1510 editor = getcommiteditor(editform=editform,
1511 1511 **pycompat.strkwargs(opts))
1512 1512 extra = {}
1513 1513 for idfunc in extrapreimport:
1514 1514 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1515 1515 overrides = {}
1516 1516 if partial:
1517 1517 overrides[('ui', 'allowemptycommit')] = True
1518 1518 with repo.ui.configoverride(overrides, 'import'):
1519 1519 n = repo.commit(message, user,
1520 1520 date, match=m,
1521 1521 editor=editor, extra=extra)
1522 1522 for idfunc in extrapostimport:
1523 1523 extrapostimportmap[idfunc](repo[n])
1524 1524 else:
1525 1525 if opts.get('exact') or importbranch:
1526 1526 branch = branch or 'default'
1527 1527 else:
1528 1528 branch = p1.branch()
1529 1529 store = patch.filestore()
1530 1530 try:
1531 1531 files = set()
1532 1532 try:
1533 1533 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1534 1534 files, eolmode=None)
1535 1535 except error.PatchError as e:
1536 1536 raise error.Abort(stringutil.forcebytestr(e))
1537 1537 if opts.get('exact'):
1538 1538 editor = None
1539 1539 else:
1540 1540 editor = getcommiteditor(editform='import.bypass')
1541 1541 memctx = context.memctx(repo, (p1.node(), p2.node()),
1542 1542 message,
1543 1543 files=files,
1544 1544 filectxfn=store,
1545 1545 user=user,
1546 1546 date=date,
1547 1547 branch=branch,
1548 1548 editor=editor)
1549 1549 n = memctx.commit()
1550 1550 finally:
1551 1551 store.close()
1552 1552 if opts.get('exact') and nocommit:
1553 1553 # --exact with --no-commit is still useful in that it does merge
1554 1554 # and branch bits
1555 1555 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1556 1556 elif opts.get('exact') and (not n or hex(n) != nodeid):
1557 1557 raise error.Abort(_('patch is damaged or loses information'))
1558 1558 msg = _('applied to working directory')
1559 1559 if n:
1560 1560 # i18n: refers to a short changeset id
1561 1561 msg = _('created %s') % short(n)
1562 1562 return msg, n, rejects
1563 1563
1564 1564 # facility to let extensions include additional data in an exported patch
1565 1565 # list of identifiers to be executed in order
1566 1566 extraexport = []
1567 1567 # mapping from identifier to actual export function
1568 1568 # function as to return a string to be added to the header or None
1569 1569 # it is given two arguments (sequencenumber, changectx)
1570 1570 extraexportmap = {}
1571 1571
1572 1572 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1573 1573 node = scmutil.binnode(ctx)
1574 1574 parents = [p.node() for p in ctx.parents() if p]
1575 1575 branch = ctx.branch()
1576 1576 if switch_parent:
1577 1577 parents.reverse()
1578 1578
1579 1579 if parents:
1580 1580 prev = parents[0]
1581 1581 else:
1582 1582 prev = nullid
1583 1583
1584 1584 fm.context(ctx=ctx)
1585 1585 fm.plain('# HG changeset patch\n')
1586 1586 fm.write('user', '# User %s\n', ctx.user())
1587 1587 fm.plain('# Date %d %d\n' % ctx.date())
1588 1588 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1589 1589 fm.condwrite(branch and branch != 'default',
1590 1590 'branch', '# Branch %s\n', branch)
1591 1591 fm.write('node', '# Node ID %s\n', hex(node))
1592 1592 fm.plain('# Parent %s\n' % hex(prev))
1593 1593 if len(parents) > 1:
1594 1594 fm.plain('# Parent %s\n' % hex(parents[1]))
1595 1595 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1596 1596
1597 1597 # TODO: redesign extraexportmap function to support formatter
1598 1598 for headerid in extraexport:
1599 1599 header = extraexportmap[headerid](seqno, ctx)
1600 1600 if header is not None:
1601 1601 fm.plain('# %s\n' % header)
1602 1602
1603 1603 fm.write('desc', '%s\n', ctx.description().rstrip())
1604 1604 fm.plain('\n')
1605 1605
1606 1606 if fm.isplain():
1607 1607 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1608 1608 for chunk, label in chunkiter:
1609 1609 fm.plain(chunk, label=label)
1610 1610 else:
1611 1611 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1612 1612 # TODO: make it structured?
1613 1613 fm.data(diff=b''.join(chunkiter))
1614 1614
1615 1615 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1616 1616 """Export changesets to stdout or a single file"""
1617 1617 for seqno, rev in enumerate(revs, 1):
1618 1618 ctx = repo[rev]
1619 1619 if not dest.startswith('<'):
1620 1620 repo.ui.note("%s\n" % dest)
1621 1621 fm.startitem()
1622 1622 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1623 1623
1624 1624 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1625 1625 match):
1626 1626 """Export changesets to possibly multiple files"""
1627 1627 total = len(revs)
1628 1628 revwidth = max(len(str(rev)) for rev in revs)
1629 1629 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1630 1630
1631 1631 for seqno, rev in enumerate(revs, 1):
1632 1632 ctx = repo[rev]
1633 1633 dest = makefilename(ctx, fntemplate,
1634 1634 total=total, seqno=seqno, revwidth=revwidth)
1635 1635 filemap.setdefault(dest, []).append((seqno, rev))
1636 1636
1637 1637 for dest in filemap:
1638 1638 with formatter.maybereopen(basefm, dest) as fm:
1639 1639 repo.ui.note("%s\n" % dest)
1640 1640 for seqno, rev in filemap[dest]:
1641 1641 fm.startitem()
1642 1642 ctx = repo[rev]
1643 1643 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1644 1644 diffopts)
1645 1645
1646 1646 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1647 1647 opts=None, match=None):
1648 1648 '''export changesets as hg patches
1649 1649
1650 1650 Args:
1651 1651 repo: The repository from which we're exporting revisions.
1652 1652 revs: A list of revisions to export as revision numbers.
1653 1653 basefm: A formatter to which patches should be written.
1654 1654 fntemplate: An optional string to use for generating patch file names.
1655 1655 switch_parent: If True, show diffs against second parent when not nullid.
1656 1656 Default is false, which always shows diff against p1.
1657 1657 opts: diff options to use for generating the patch.
1658 1658 match: If specified, only export changes to files matching this matcher.
1659 1659
1660 1660 Returns:
1661 1661 Nothing.
1662 1662
1663 1663 Side Effect:
1664 1664 "HG Changeset Patch" data is emitted to one of the following
1665 1665 destinations:
1666 1666 fntemplate specified: Each rev is written to a unique file named using
1667 1667 the given template.
1668 1668 Otherwise: All revs will be written to basefm.
1669 1669 '''
1670 1670 scmutil.prefetchfiles(repo, revs, match)
1671 1671
1672 1672 if not fntemplate:
1673 1673 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1674 1674 else:
1675 1675 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1676 1676 match)
1677 1677
1678 1678 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1679 1679 """Export changesets to the given file stream"""
1680 1680 scmutil.prefetchfiles(repo, revs, match)
1681 1681
1682 1682 dest = getattr(fp, 'name', '<unnamed>')
1683 1683 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1684 1684 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1685 1685
1686 1686 def showmarker(fm, marker, index=None):
1687 1687 """utility function to display obsolescence marker in a readable way
1688 1688
1689 1689 To be used by debug function."""
1690 1690 if index is not None:
1691 1691 fm.write('index', '%i ', index)
1692 1692 fm.write('prednode', '%s ', hex(marker.prednode()))
1693 1693 succs = marker.succnodes()
1694 1694 fm.condwrite(succs, 'succnodes', '%s ',
1695 1695 fm.formatlist(map(hex, succs), name='node'))
1696 1696 fm.write('flag', '%X ', marker.flags())
1697 1697 parents = marker.parentnodes()
1698 1698 if parents is not None:
1699 1699 fm.write('parentnodes', '{%s} ',
1700 1700 fm.formatlist(map(hex, parents), name='node', sep=', '))
1701 1701 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1702 1702 meta = marker.metadata().copy()
1703 1703 meta.pop('date', None)
1704 1704 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
1705 1705 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1706 1706 fm.plain('\n')
1707 1707
1708 1708 def finddate(ui, repo, date):
1709 1709 """Find the tipmost changeset that matches the given date spec"""
1710 1710
1711 1711 df = dateutil.matchdate(date)
1712 1712 m = scmutil.matchall(repo)
1713 1713 results = {}
1714 1714
1715 1715 def prep(ctx, fns):
1716 1716 d = ctx.date()
1717 1717 if df(d[0]):
1718 1718 results[ctx.rev()] = d
1719 1719
1720 1720 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1721 1721 rev = ctx.rev()
1722 1722 if rev in results:
1723 1723 ui.status(_("found revision %s from %s\n") %
1724 1724 (rev, dateutil.datestr(results[rev])))
1725 1725 return '%d' % rev
1726 1726
1727 1727 raise error.Abort(_("revision matching date not found"))
1728 1728
1729 1729 def increasingwindows(windowsize=8, sizelimit=512):
1730 1730 while True:
1731 1731 yield windowsize
1732 1732 if windowsize < sizelimit:
1733 1733 windowsize *= 2
1734 1734
1735 1735 def _walkrevs(repo, opts):
1736 1736 # Default --rev value depends on --follow but --follow behavior
1737 1737 # depends on revisions resolved from --rev...
1738 1738 follow = opts.get('follow') or opts.get('follow_first')
1739 1739 if opts.get('rev'):
1740 1740 revs = scmutil.revrange(repo, opts['rev'])
1741 1741 elif follow and repo.dirstate.p1() == nullid:
1742 1742 revs = smartset.baseset()
1743 1743 elif follow:
1744 1744 revs = repo.revs('reverse(:.)')
1745 1745 else:
1746 1746 revs = smartset.spanset(repo)
1747 1747 revs.reverse()
1748 1748 return revs
1749 1749
1750 1750 class FileWalkError(Exception):
1751 1751 pass
1752 1752
1753 1753 def walkfilerevs(repo, match, follow, revs, fncache):
1754 1754 '''Walks the file history for the matched files.
1755 1755
1756 1756 Returns the changeset revs that are involved in the file history.
1757 1757
1758 1758 Throws FileWalkError if the file history can't be walked using
1759 1759 filelogs alone.
1760 1760 '''
1761 1761 wanted = set()
1762 1762 copies = []
1763 1763 minrev, maxrev = min(revs), max(revs)
1764 1764 def filerevgen(filelog, last):
1765 1765 """
1766 1766 Only files, no patterns. Check the history of each file.
1767 1767
1768 1768 Examines filelog entries within minrev, maxrev linkrev range
1769 1769 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1770 1770 tuples in backwards order
1771 1771 """
1772 1772 cl_count = len(repo)
1773 1773 revs = []
1774 1774 for j in pycompat.xrange(0, last + 1):
1775 1775 linkrev = filelog.linkrev(j)
1776 1776 if linkrev < minrev:
1777 1777 continue
1778 1778 # only yield rev for which we have the changelog, it can
1779 1779 # happen while doing "hg log" during a pull or commit
1780 1780 if linkrev >= cl_count:
1781 1781 break
1782 1782
1783 1783 parentlinkrevs = []
1784 1784 for p in filelog.parentrevs(j):
1785 1785 if p != nullrev:
1786 1786 parentlinkrevs.append(filelog.linkrev(p))
1787 1787 n = filelog.node(j)
1788 1788 revs.append((linkrev, parentlinkrevs,
1789 1789 follow and filelog.renamed(n)))
1790 1790
1791 1791 return reversed(revs)
1792 1792 def iterfiles():
1793 1793 pctx = repo['.']
1794 1794 for filename in match.files():
1795 1795 if follow:
1796 1796 if filename not in pctx:
1797 1797 raise error.Abort(_('cannot follow file not in parent '
1798 1798 'revision: "%s"') % filename)
1799 1799 yield filename, pctx[filename].filenode()
1800 1800 else:
1801 1801 yield filename, None
1802 1802 for filename_node in copies:
1803 1803 yield filename_node
1804 1804
1805 1805 for file_, node in iterfiles():
1806 1806 filelog = repo.file(file_)
1807 1807 if not len(filelog):
1808 1808 if node is None:
1809 1809 # A zero count may be a directory or deleted file, so
1810 1810 # try to find matching entries on the slow path.
1811 1811 if follow:
1812 1812 raise error.Abort(
1813 1813 _('cannot follow nonexistent file: "%s"') % file_)
1814 1814 raise FileWalkError("Cannot walk via filelog")
1815 1815 else:
1816 1816 continue
1817 1817
1818 1818 if node is None:
1819 1819 last = len(filelog) - 1
1820 1820 else:
1821 1821 last = filelog.rev(node)
1822 1822
1823 1823 # keep track of all ancestors of the file
1824 1824 ancestors = {filelog.linkrev(last)}
1825 1825
1826 1826 # iterate from latest to oldest revision
1827 1827 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1828 1828 if not follow:
1829 1829 if rev > maxrev:
1830 1830 continue
1831 1831 else:
1832 1832 # Note that last might not be the first interesting
1833 1833 # rev to us:
1834 1834 # if the file has been changed after maxrev, we'll
1835 1835 # have linkrev(last) > maxrev, and we still need
1836 1836 # to explore the file graph
1837 1837 if rev not in ancestors:
1838 1838 continue
1839 1839 # XXX insert 1327 fix here
1840 1840 if flparentlinkrevs:
1841 1841 ancestors.update(flparentlinkrevs)
1842 1842
1843 1843 fncache.setdefault(rev, []).append(file_)
1844 1844 wanted.add(rev)
1845 1845 if copied:
1846 1846 copies.append(copied)
1847 1847
1848 1848 return wanted
1849 1849
1850 1850 class _followfilter(object):
1851 1851 def __init__(self, repo, onlyfirst=False):
1852 1852 self.repo = repo
1853 1853 self.startrev = nullrev
1854 1854 self.roots = set()
1855 1855 self.onlyfirst = onlyfirst
1856 1856
1857 1857 def match(self, rev):
1858 1858 def realparents(rev):
1859 1859 if self.onlyfirst:
1860 1860 return self.repo.changelog.parentrevs(rev)[0:1]
1861 1861 else:
1862 1862 return filter(lambda x: x != nullrev,
1863 1863 self.repo.changelog.parentrevs(rev))
1864 1864
1865 1865 if self.startrev == nullrev:
1866 1866 self.startrev = rev
1867 1867 return True
1868 1868
1869 1869 if rev > self.startrev:
1870 1870 # forward: all descendants
1871 1871 if not self.roots:
1872 1872 self.roots.add(self.startrev)
1873 1873 for parent in realparents(rev):
1874 1874 if parent in self.roots:
1875 1875 self.roots.add(rev)
1876 1876 return True
1877 1877 else:
1878 1878 # backwards: all parents
1879 1879 if not self.roots:
1880 1880 self.roots.update(realparents(self.startrev))
1881 1881 if rev in self.roots:
1882 1882 self.roots.remove(rev)
1883 1883 self.roots.update(realparents(rev))
1884 1884 return True
1885 1885
1886 1886 return False
1887 1887
1888 1888 def walkchangerevs(repo, match, opts, prepare):
1889 1889 '''Iterate over files and the revs in which they changed.
1890 1890
1891 1891 Callers most commonly need to iterate backwards over the history
1892 1892 in which they are interested. Doing so has awful (quadratic-looking)
1893 1893 performance, so we use iterators in a "windowed" way.
1894 1894
1895 1895 We walk a window of revisions in the desired order. Within the
1896 1896 window, we first walk forwards to gather data, then in the desired
1897 1897 order (usually backwards) to display it.
1898 1898
1899 1899 This function returns an iterator yielding contexts. Before
1900 1900 yielding each context, the iterator will first call the prepare
1901 1901 function on each context in the window in forward order.'''
1902 1902
1903 1903 allfiles = opts.get('all_files')
1904 1904 follow = opts.get('follow') or opts.get('follow_first')
1905 1905 revs = _walkrevs(repo, opts)
1906 1906 if not revs:
1907 1907 return []
1908 1908 wanted = set()
1909 1909 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1910 1910 fncache = {}
1911 1911 change = repo.__getitem__
1912 1912
1913 1913 # First step is to fill wanted, the set of revisions that we want to yield.
1914 1914 # When it does not induce extra cost, we also fill fncache for revisions in
1915 1915 # wanted: a cache of filenames that were changed (ctx.files()) and that
1916 1916 # match the file filtering conditions.
1917 1917
1918 1918 if match.always() or allfiles:
1919 1919 # No files, no patterns. Display all revs.
1920 1920 wanted = revs
1921 1921 elif not slowpath:
1922 1922 # We only have to read through the filelog to find wanted revisions
1923 1923
1924 1924 try:
1925 1925 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1926 1926 except FileWalkError:
1927 1927 slowpath = True
1928 1928
1929 1929 # We decided to fall back to the slowpath because at least one
1930 1930 # of the paths was not a file. Check to see if at least one of them
1931 1931 # existed in history, otherwise simply return
1932 1932 for path in match.files():
1933 1933 if path == '.' or path in repo.store:
1934 1934 break
1935 1935 else:
1936 1936 return []
1937 1937
1938 1938 if slowpath:
1939 1939 # We have to read the changelog to match filenames against
1940 1940 # changed files
1941 1941
1942 1942 if follow:
1943 1943 raise error.Abort(_('can only follow copies/renames for explicit '
1944 1944 'filenames'))
1945 1945
1946 1946 # The slow path checks files modified in every changeset.
1947 1947 # This is really slow on large repos, so compute the set lazily.
1948 1948 class lazywantedset(object):
1949 1949 def __init__(self):
1950 1950 self.set = set()
1951 1951 self.revs = set(revs)
1952 1952
1953 1953 # No need to worry about locality here because it will be accessed
1954 1954 # in the same order as the increasing window below.
1955 1955 def __contains__(self, value):
1956 1956 if value in self.set:
1957 1957 return True
1958 1958 elif not value in self.revs:
1959 1959 return False
1960 1960 else:
1961 1961 self.revs.discard(value)
1962 1962 ctx = change(value)
1963 1963 matches = [f for f in ctx.files() if match(f)]
1964 1964 if matches:
1965 1965 fncache[value] = matches
1966 1966 self.set.add(value)
1967 1967 return True
1968 1968 return False
1969 1969
1970 1970 def discard(self, value):
1971 1971 self.revs.discard(value)
1972 1972 self.set.discard(value)
1973 1973
1974 1974 wanted = lazywantedset()
1975 1975
1976 1976 # it might be worthwhile to do this in the iterator if the rev range
1977 1977 # is descending and the prune args are all within that range
1978 1978 for rev in opts.get('prune', ()):
1979 1979 rev = repo[rev].rev()
1980 1980 ff = _followfilter(repo)
1981 1981 stop = min(revs[0], revs[-1])
1982 1982 for x in pycompat.xrange(rev, stop - 1, -1):
1983 1983 if ff.match(x):
1984 1984 wanted = wanted - [x]
1985 1985
1986 1986 # Now that wanted is correctly initialized, we can iterate over the
1987 1987 # revision range, yielding only revisions in wanted.
1988 1988 def iterate():
1989 1989 if follow and match.always():
1990 1990 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1991 1991 def want(rev):
1992 1992 return ff.match(rev) and rev in wanted
1993 1993 else:
1994 1994 def want(rev):
1995 1995 return rev in wanted
1996 1996
1997 1997 it = iter(revs)
1998 1998 stopiteration = False
1999 1999 for windowsize in increasingwindows():
2000 2000 nrevs = []
2001 2001 for i in pycompat.xrange(windowsize):
2002 2002 rev = next(it, None)
2003 2003 if rev is None:
2004 2004 stopiteration = True
2005 2005 break
2006 2006 elif want(rev):
2007 2007 nrevs.append(rev)
2008 2008 for rev in sorted(nrevs):
2009 2009 fns = fncache.get(rev)
2010 2010 ctx = change(rev)
2011 2011 if not fns:
2012 2012 def fns_generator():
2013 2013 if allfiles:
2014 2014 fiter = iter(ctx)
2015 2015 else:
2016 2016 fiter = ctx.files()
2017 2017 for f in fiter:
2018 2018 if match(f):
2019 2019 yield f
2020 2020 fns = fns_generator()
2021 2021 prepare(ctx, fns)
2022 2022 for rev in nrevs:
2023 2023 yield change(rev)
2024 2024
2025 2025 if stopiteration:
2026 2026 break
2027 2027
2028 2028 return iterate()
2029 2029
2030 2030 def add(ui, repo, match, prefix, explicitonly, **opts):
2031 2031 join = lambda f: os.path.join(prefix, f)
2032 2032 bad = []
2033 2033
2034 2034 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2035 2035 names = []
2036 2036 wctx = repo[None]
2037 2037 cca = None
2038 2038 abort, warn = scmutil.checkportabilityalert(ui)
2039 2039 if abort or warn:
2040 2040 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2041 2041
2042 2042 match = repo.narrowmatch(match, includeexact=True)
2043 2043 badmatch = matchmod.badmatch(match, badfn)
2044 2044 dirstate = repo.dirstate
2045 2045 # We don't want to just call wctx.walk here, since it would return a lot of
2046 2046 # clean files, which we aren't interested in and takes time.
2047 2047 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2048 2048 unknown=True, ignored=False, full=False)):
2049 2049 exact = match.exact(f)
2050 2050 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2051 2051 if cca:
2052 2052 cca(f)
2053 2053 names.append(f)
2054 2054 if ui.verbose or not exact:
2055 2055 ui.status(_('adding %s\n') % match.rel(f),
2056 2056 label='ui.addremove.added')
2057 2057
2058 2058 for subpath in sorted(wctx.substate):
2059 2059 sub = wctx.sub(subpath)
2060 2060 try:
2061 2061 submatch = matchmod.subdirmatcher(subpath, match)
2062 2062 if opts.get(r'subrepos'):
2063 2063 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2064 2064 else:
2065 2065 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2066 2066 except error.LookupError:
2067 2067 ui.status(_("skipping missing subrepository: %s\n")
2068 2068 % join(subpath))
2069 2069
2070 2070 if not opts.get(r'dry_run'):
2071 2071 rejected = wctx.add(names, prefix)
2072 2072 bad.extend(f for f in rejected if f in match.files())
2073 2073 return bad
2074 2074
2075 2075 def addwebdirpath(repo, serverpath, webconf):
2076 2076 webconf[serverpath] = repo.root
2077 2077 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2078 2078
2079 2079 for r in repo.revs('filelog("path:.hgsub")'):
2080 2080 ctx = repo[r]
2081 2081 for subpath in ctx.substate:
2082 2082 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2083 2083
2084 2084 def forget(ui, repo, match, prefix, explicitonly, dryrun, interactive):
2085 2085 if dryrun and interactive:
2086 2086 raise error.Abort(_("cannot specify both --dry-run and --interactive"))
2087 2087 join = lambda f: os.path.join(prefix, f)
2088 2088 bad = []
2089 2089 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2090 2090 wctx = repo[None]
2091 2091 forgot = []
2092 2092
2093 2093 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2094 2094 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2095 2095 if explicitonly:
2096 2096 forget = [f for f in forget if match.exact(f)]
2097 2097
2098 2098 for subpath in sorted(wctx.substate):
2099 2099 sub = wctx.sub(subpath)
2100 2100 try:
2101 2101 submatch = matchmod.subdirmatcher(subpath, match)
2102 2102 subbad, subforgot = sub.forget(submatch, prefix, dryrun=dryrun,
2103 2103 interactive=interactive)
2104 2104 bad.extend([subpath + '/' + f for f in subbad])
2105 2105 forgot.extend([subpath + '/' + f for f in subforgot])
2106 2106 except error.LookupError:
2107 2107 ui.status(_("skipping missing subrepository: %s\n")
2108 2108 % join(subpath))
2109 2109
2110 2110 if not explicitonly:
2111 2111 for f in match.files():
2112 2112 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2113 2113 if f not in forgot:
2114 2114 if repo.wvfs.exists(f):
2115 2115 # Don't complain if the exact case match wasn't given.
2116 2116 # But don't do this until after checking 'forgot', so
2117 2117 # that subrepo files aren't normalized, and this op is
2118 2118 # purely from data cached by the status walk above.
2119 2119 if repo.dirstate.normalize(f) in repo.dirstate:
2120 2120 continue
2121 2121 ui.warn(_('not removing %s: '
2122 2122 'file is already untracked\n')
2123 2123 % match.rel(f))
2124 2124 bad.append(f)
2125 2125
2126 2126 if interactive:
2127 2127 responses = _('[Ynsa?]'
2128 2128 '$$ &Yes, forget this file'
2129 2129 '$$ &No, skip this file'
2130 2130 '$$ &Skip remaining files'
2131 2131 '$$ Include &all remaining files'
2132 2132 '$$ &? (display help)')
2133 2133 for filename in forget[:]:
2134 2134 r = ui.promptchoice(_('forget %s %s') % (filename, responses))
2135 2135 if r == 4: # ?
2136 2136 while r == 4:
2137 2137 for c, t in ui.extractchoices(responses)[1]:
2138 2138 ui.write('%s - %s\n' % (c, encoding.lower(t)))
2139 2139 r = ui.promptchoice(_('forget %s %s') % (filename,
2140 2140 responses))
2141 2141 if r == 0: # yes
2142 2142 continue
2143 2143 elif r == 1: # no
2144 2144 forget.remove(filename)
2145 2145 elif r == 2: # Skip
2146 2146 fnindex = forget.index(filename)
2147 2147 del forget[fnindex:]
2148 2148 break
2149 2149 elif r == 3: # All
2150 2150 break
2151 2151
2152 2152 for f in forget:
2153 2153 if ui.verbose or not match.exact(f) or interactive:
2154 2154 ui.status(_('removing %s\n') % match.rel(f),
2155 2155 label='ui.addremove.removed')
2156 2156
2157 2157 if not dryrun:
2158 2158 rejected = wctx.forget(forget, prefix)
2159 2159 bad.extend(f for f in rejected if f in match.files())
2160 2160 forgot.extend(f for f in forget if f not in rejected)
2161 2161 return bad, forgot
2162 2162
2163 2163 def files(ui, ctx, m, fm, fmt, subrepos):
2164 2164 ret = 1
2165 2165
2166 2166 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint()
2167 2167 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2168 2168 for f in ctx.matches(m):
2169 2169 fm.startitem()
2170 2170 fm.context(ctx=ctx)
2171 2171 if needsfctx:
2172 2172 fc = ctx[f]
2173 2173 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2174 2174 fm.data(path=f)
2175 2175 fm.plain(fmt % uipathfn(f))
2176 2176 ret = 0
2177 2177
2178 2178 for subpath in sorted(ctx.substate):
2179 2179 submatch = matchmod.subdirmatcher(subpath, m)
2180 2180 if (subrepos or m.exact(subpath) or any(submatch.files())):
2181 2181 sub = ctx.sub(subpath)
2182 2182 try:
2183 2183 recurse = m.exact(subpath) or subrepos
2184 2184 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2185 2185 ret = 0
2186 2186 except error.LookupError:
2187 2187 ui.status(_("skipping missing subrepository: %s\n")
2188 2188 % m.abs(subpath))
2189 2189
2190 2190 return ret
2191 2191
2192 2192 def remove(ui, repo, m, prefix, after, force, subrepos, dryrun, warnings=None):
2193 2193 join = lambda f: os.path.join(prefix, f)
2194 2194 ret = 0
2195 2195 s = repo.status(match=m, clean=True)
2196 2196 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2197 2197
2198 2198 wctx = repo[None]
2199 2199
2200 2200 if warnings is None:
2201 2201 warnings = []
2202 2202 warn = True
2203 2203 else:
2204 2204 warn = False
2205 2205
2206 2206 subs = sorted(wctx.substate)
2207 2207 progress = ui.makeprogress(_('searching'), total=len(subs),
2208 2208 unit=_('subrepos'))
2209 2209 for subpath in subs:
2210 2210 submatch = matchmod.subdirmatcher(subpath, m)
2211 2211 if subrepos or m.exact(subpath) or any(submatch.files()):
2212 2212 progress.increment()
2213 2213 sub = wctx.sub(subpath)
2214 2214 try:
2215 2215 if sub.removefiles(submatch, prefix, after, force, subrepos,
2216 2216 dryrun, warnings):
2217 2217 ret = 1
2218 2218 except error.LookupError:
2219 2219 warnings.append(_("skipping missing subrepository: %s\n")
2220 2220 % join(subpath))
2221 2221 progress.complete()
2222 2222
2223 2223 # warn about failure to delete explicit files/dirs
2224 2224 deleteddirs = util.dirs(deleted)
2225 2225 files = m.files()
2226 2226 progress = ui.makeprogress(_('deleting'), total=len(files),
2227 2227 unit=_('files'))
2228 2228 for f in files:
2229 2229 def insubrepo():
2230 2230 for subpath in wctx.substate:
2231 2231 if f.startswith(subpath + '/'):
2232 2232 return True
2233 2233 return False
2234 2234
2235 2235 progress.increment()
2236 2236 isdir = f in deleteddirs or wctx.hasdir(f)
2237 2237 if (f in repo.dirstate or isdir or f == '.'
2238 2238 or insubrepo() or f in subs):
2239 2239 continue
2240 2240
2241 2241 if repo.wvfs.exists(f):
2242 2242 if repo.wvfs.isdir(f):
2243 2243 warnings.append(_('not removing %s: no tracked files\n')
2244 2244 % m.rel(f))
2245 2245 else:
2246 2246 warnings.append(_('not removing %s: file is untracked\n')
2247 2247 % m.rel(f))
2248 2248 # missing files will generate a warning elsewhere
2249 2249 ret = 1
2250 2250 progress.complete()
2251 2251
2252 2252 if force:
2253 2253 list = modified + deleted + clean + added
2254 2254 elif after:
2255 2255 list = deleted
2256 2256 remaining = modified + added + clean
2257 2257 progress = ui.makeprogress(_('skipping'), total=len(remaining),
2258 2258 unit=_('files'))
2259 2259 for f in remaining:
2260 2260 progress.increment()
2261 2261 if ui.verbose or (f in files):
2262 2262 warnings.append(_('not removing %s: file still exists\n')
2263 2263 % m.rel(f))
2264 2264 ret = 1
2265 2265 progress.complete()
2266 2266 else:
2267 2267 list = deleted + clean
2268 2268 progress = ui.makeprogress(_('skipping'),
2269 2269 total=(len(modified) + len(added)),
2270 2270 unit=_('files'))
2271 2271 for f in modified:
2272 2272 progress.increment()
2273 2273 warnings.append(_('not removing %s: file is modified (use -f'
2274 2274 ' to force removal)\n') % m.rel(f))
2275 2275 ret = 1
2276 2276 for f in added:
2277 2277 progress.increment()
2278 2278 warnings.append(_("not removing %s: file has been marked for add"
2279 2279 " (use 'hg forget' to undo add)\n") % m.rel(f))
2280 2280 ret = 1
2281 2281 progress.complete()
2282 2282
2283 2283 list = sorted(list)
2284 2284 progress = ui.makeprogress(_('deleting'), total=len(list),
2285 2285 unit=_('files'))
2286 2286 for f in list:
2287 2287 if ui.verbose or not m.exact(f):
2288 2288 progress.increment()
2289 2289 ui.status(_('removing %s\n') % m.rel(f),
2290 2290 label='ui.addremove.removed')
2291 2291 progress.complete()
2292 2292
2293 2293 if not dryrun:
2294 2294 with repo.wlock():
2295 2295 if not after:
2296 2296 for f in list:
2297 2297 if f in added:
2298 2298 continue # we never unlink added files on remove
2299 2299 rmdir = repo.ui.configbool('experimental',
2300 2300 'removeemptydirs')
2301 2301 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2302 2302 repo[None].forget(list)
2303 2303
2304 2304 if warn:
2305 2305 for warning in warnings:
2306 2306 ui.warn(warning)
2307 2307
2308 2308 return ret
2309 2309
2310 2310 def _updatecatformatter(fm, ctx, matcher, path, decode):
2311 2311 """Hook for adding data to the formatter used by ``hg cat``.
2312 2312
2313 2313 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2314 2314 this method first."""
2315 2315 data = ctx[path].data()
2316 2316 if decode:
2317 2317 data = ctx.repo().wwritedata(path, data)
2318 2318 fm.startitem()
2319 2319 fm.context(ctx=ctx)
2320 2320 fm.write('data', '%s', data)
2321 2321 fm.data(path=path)
2322 2322
2323 2323 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2324 2324 err = 1
2325 2325 opts = pycompat.byteskwargs(opts)
2326 2326
2327 2327 def write(path):
2328 2328 filename = None
2329 2329 if fntemplate:
2330 2330 filename = makefilename(ctx, fntemplate,
2331 2331 pathname=os.path.join(prefix, path))
2332 2332 # attempt to create the directory if it does not already exist
2333 2333 try:
2334 2334 os.makedirs(os.path.dirname(filename))
2335 2335 except OSError:
2336 2336 pass
2337 2337 with formatter.maybereopen(basefm, filename) as fm:
2338 2338 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2339 2339
2340 2340 # Automation often uses hg cat on single files, so special case it
2341 2341 # for performance to avoid the cost of parsing the manifest.
2342 2342 if len(matcher.files()) == 1 and not matcher.anypats():
2343 2343 file = matcher.files()[0]
2344 2344 mfl = repo.manifestlog
2345 2345 mfnode = ctx.manifestnode()
2346 2346 try:
2347 2347 if mfnode and mfl[mfnode].find(file)[0]:
2348 2348 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2349 2349 write(file)
2350 2350 return 0
2351 2351 except KeyError:
2352 2352 pass
2353 2353
2354 2354 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2355 2355
2356 2356 for abs in ctx.walk(matcher):
2357 2357 write(abs)
2358 2358 err = 0
2359 2359
2360 2360 for subpath in sorted(ctx.substate):
2361 2361 sub = ctx.sub(subpath)
2362 2362 try:
2363 2363 submatch = matchmod.subdirmatcher(subpath, matcher)
2364 2364
2365 2365 if not sub.cat(submatch, basefm, fntemplate,
2366 2366 os.path.join(prefix, sub._path),
2367 2367 **pycompat.strkwargs(opts)):
2368 2368 err = 0
2369 2369 except error.RepoLookupError:
2370 2370 ui.status(_("skipping missing subrepository: %s\n")
2371 2371 % os.path.join(prefix, subpath))
2372 2372
2373 2373 return err
2374 2374
2375 2375 def commit(ui, repo, commitfunc, pats, opts):
2376 2376 '''commit the specified files or all outstanding changes'''
2377 2377 date = opts.get('date')
2378 2378 if date:
2379 2379 opts['date'] = dateutil.parsedate(date)
2380 2380 message = logmessage(ui, opts)
2381 2381 matcher = scmutil.match(repo[None], pats, opts)
2382 2382
2383 2383 dsguard = None
2384 2384 # extract addremove carefully -- this function can be called from a command
2385 2385 # that doesn't support addremove
2386 2386 if opts.get('addremove'):
2387 2387 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2388 2388 with dsguard or util.nullcontextmanager():
2389 2389 if dsguard:
2390 2390 if scmutil.addremove(repo, matcher, "", opts) != 0:
2391 2391 raise error.Abort(
2392 2392 _("failed to mark all new/missing files as added/removed"))
2393 2393
2394 2394 return commitfunc(ui, repo, message, matcher, opts)
2395 2395
2396 2396 def samefile(f, ctx1, ctx2):
2397 2397 if f in ctx1.manifest():
2398 2398 a = ctx1.filectx(f)
2399 2399 if f in ctx2.manifest():
2400 2400 b = ctx2.filectx(f)
2401 2401 return (not a.cmp(b)
2402 2402 and a.flags() == b.flags())
2403 2403 else:
2404 2404 return False
2405 2405 else:
2406 2406 return f not in ctx2.manifest()
2407 2407
2408 2408 def amend(ui, repo, old, extra, pats, opts):
2409 2409 # avoid cycle context -> subrepo -> cmdutil
2410 2410 from . import context
2411 2411
2412 2412 # amend will reuse the existing user if not specified, but the obsolete
2413 2413 # marker creation requires that the current user's name is specified.
2414 2414 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2415 2415 ui.username() # raise exception if username not set
2416 2416
2417 2417 ui.note(_('amending changeset %s\n') % old)
2418 2418 base = old.p1()
2419 2419
2420 2420 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2421 2421 # Participating changesets:
2422 2422 #
2423 2423 # wctx o - workingctx that contains changes from working copy
2424 2424 # | to go into amending commit
2425 2425 # |
2426 2426 # old o - changeset to amend
2427 2427 # |
2428 2428 # base o - first parent of the changeset to amend
2429 2429 wctx = repo[None]
2430 2430
2431 2431 # Copy to avoid mutating input
2432 2432 extra = extra.copy()
2433 2433 # Update extra dict from amended commit (e.g. to preserve graft
2434 2434 # source)
2435 2435 extra.update(old.extra())
2436 2436
2437 2437 # Also update it from the from the wctx
2438 2438 extra.update(wctx.extra())
2439 2439
2440 2440 user = opts.get('user') or old.user()
2441 2441
2442 2442 datemaydiffer = False # date-only change should be ignored?
2443 2443 if opts.get('date') and opts.get('currentdate'):
2444 2444 raise error.Abort(_('--date and --currentdate are mutually '
2445 2445 'exclusive'))
2446 2446 if opts.get('date'):
2447 2447 date = dateutil.parsedate(opts.get('date'))
2448 2448 elif opts.get('currentdate'):
2449 2449 date = dateutil.makedate()
2450 2450 elif (ui.configbool('rewrite', 'update-timestamp')
2451 2451 and opts.get('currentdate') is None):
2452 2452 date = dateutil.makedate()
2453 2453 datemaydiffer = True
2454 2454 else:
2455 2455 date = old.date()
2456 2456
2457 2457 if len(old.parents()) > 1:
2458 2458 # ctx.files() isn't reliable for merges, so fall back to the
2459 2459 # slower repo.status() method
2460 2460 files = set([fn for st in base.status(old)[:3]
2461 2461 for fn in st])
2462 2462 else:
2463 2463 files = set(old.files())
2464 2464
2465 2465 # add/remove the files to the working copy if the "addremove" option
2466 2466 # was specified.
2467 2467 matcher = scmutil.match(wctx, pats, opts)
2468 2468 if (opts.get('addremove')
2469 2469 and scmutil.addremove(repo, matcher, "", opts)):
2470 2470 raise error.Abort(
2471 2471 _("failed to mark all new/missing files as added/removed"))
2472 2472
2473 2473 # Check subrepos. This depends on in-place wctx._status update in
2474 2474 # subrepo.precommit(). To minimize the risk of this hack, we do
2475 2475 # nothing if .hgsub does not exist.
2476 2476 if '.hgsub' in wctx or '.hgsub' in old:
2477 2477 subs, commitsubs, newsubstate = subrepoutil.precommit(
2478 2478 ui, wctx, wctx._status, matcher)
2479 2479 # amend should abort if commitsubrepos is enabled
2480 2480 assert not commitsubs
2481 2481 if subs:
2482 2482 subrepoutil.writestate(repo, newsubstate)
2483 2483
2484 2484 ms = mergemod.mergestate.read(repo)
2485 2485 mergeutil.checkunresolved(ms)
2486 2486
2487 2487 filestoamend = set(f for f in wctx.files() if matcher(f))
2488 2488
2489 2489 changes = (len(filestoamend) > 0)
2490 2490 if changes:
2491 2491 # Recompute copies (avoid recording a -> b -> a)
2492 2492 copied = copies.pathcopies(base, wctx, matcher)
2493 2493 if old.p2:
2494 2494 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2495 2495
2496 2496 # Prune files which were reverted by the updates: if old
2497 2497 # introduced file X and the file was renamed in the working
2498 2498 # copy, then those two files are the same and
2499 2499 # we can discard X from our list of files. Likewise if X
2500 2500 # was removed, it's no longer relevant. If X is missing (aka
2501 2501 # deleted), old X must be preserved.
2502 2502 files.update(filestoamend)
2503 2503 files = [f for f in files if (not samefile(f, wctx, base)
2504 2504 or f in wctx.deleted())]
2505 2505
2506 2506 def filectxfn(repo, ctx_, path):
2507 2507 try:
2508 2508 # If the file being considered is not amongst the files
2509 2509 # to be amended, we should return the file context from the
2510 2510 # old changeset. This avoids issues when only some files in
2511 2511 # the working copy are being amended but there are also
2512 2512 # changes to other files from the old changeset.
2513 2513 if path not in filestoamend:
2514 2514 return old.filectx(path)
2515 2515
2516 2516 # Return None for removed files.
2517 2517 if path in wctx.removed():
2518 2518 return None
2519 2519
2520 2520 fctx = wctx[path]
2521 2521 flags = fctx.flags()
2522 2522 mctx = context.memfilectx(repo, ctx_,
2523 2523 fctx.path(), fctx.data(),
2524 2524 islink='l' in flags,
2525 2525 isexec='x' in flags,
2526 2526 copied=copied.get(path))
2527 2527 return mctx
2528 2528 except KeyError:
2529 2529 return None
2530 2530 else:
2531 2531 ui.note(_('copying changeset %s to %s\n') % (old, base))
2532 2532
2533 2533 # Use version of files as in the old cset
2534 2534 def filectxfn(repo, ctx_, path):
2535 2535 try:
2536 2536 return old.filectx(path)
2537 2537 except KeyError:
2538 2538 return None
2539 2539
2540 2540 # See if we got a message from -m or -l, if not, open the editor with
2541 2541 # the message of the changeset to amend.
2542 2542 message = logmessage(ui, opts)
2543 2543
2544 2544 editform = mergeeditform(old, 'commit.amend')
2545 2545 editor = getcommiteditor(editform=editform,
2546 2546 **pycompat.strkwargs(opts))
2547 2547
2548 2548 if not message:
2549 2549 editor = getcommiteditor(edit=True, editform=editform)
2550 2550 message = old.description()
2551 2551
2552 2552 pureextra = extra.copy()
2553 2553 extra['amend_source'] = old.hex()
2554 2554
2555 2555 new = context.memctx(repo,
2556 2556 parents=[base.node(), old.p2().node()],
2557 2557 text=message,
2558 2558 files=files,
2559 2559 filectxfn=filectxfn,
2560 2560 user=user,
2561 2561 date=date,
2562 2562 extra=extra,
2563 2563 editor=editor)
2564 2564
2565 2565 newdesc = changelog.stripdesc(new.description())
2566 2566 if ((not changes)
2567 2567 and newdesc == old.description()
2568 2568 and user == old.user()
2569 2569 and (date == old.date() or datemaydiffer)
2570 2570 and pureextra == old.extra()):
2571 2571 # nothing changed. continuing here would create a new node
2572 2572 # anyway because of the amend_source noise.
2573 2573 #
2574 2574 # This not what we expect from amend.
2575 2575 return old.node()
2576 2576
2577 2577 commitphase = None
2578 2578 if opts.get('secret'):
2579 2579 commitphase = phases.secret
2580 2580 newid = repo.commitctx(new)
2581 2581
2582 2582 # Reroute the working copy parent to the new changeset
2583 2583 repo.setparents(newid, nullid)
2584 2584 mapping = {old.node(): (newid,)}
2585 2585 obsmetadata = None
2586 2586 if opts.get('note'):
2587 2587 obsmetadata = {'note': encoding.fromlocal(opts['note'])}
2588 2588 backup = ui.configbool('rewrite', 'backup-bundle')
2589 2589 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata,
2590 2590 fixphase=True, targetphase=commitphase,
2591 2591 backup=backup)
2592 2592
2593 2593 # Fixing the dirstate because localrepo.commitctx does not update
2594 2594 # it. This is rather convenient because we did not need to update
2595 2595 # the dirstate for all the files in the new commit which commitctx
2596 2596 # could have done if it updated the dirstate. Now, we can
2597 2597 # selectively update the dirstate only for the amended files.
2598 2598 dirstate = repo.dirstate
2599 2599
2600 2600 # Update the state of the files which were added and
2601 2601 # and modified in the amend to "normal" in the dirstate.
2602 2602 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2603 2603 for f in normalfiles:
2604 2604 dirstate.normal(f)
2605 2605
2606 2606 # Update the state of files which were removed in the amend
2607 2607 # to "removed" in the dirstate.
2608 2608 removedfiles = set(wctx.removed()) & filestoamend
2609 2609 for f in removedfiles:
2610 2610 dirstate.drop(f)
2611 2611
2612 2612 return newid
2613 2613
2614 2614 def commiteditor(repo, ctx, subs, editform=''):
2615 2615 if ctx.description():
2616 2616 return ctx.description()
2617 2617 return commitforceeditor(repo, ctx, subs, editform=editform,
2618 2618 unchangedmessagedetection=True)
2619 2619
2620 2620 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2621 2621 editform='', unchangedmessagedetection=False):
2622 2622 if not extramsg:
2623 2623 extramsg = _("Leave message empty to abort commit.")
2624 2624
2625 2625 forms = [e for e in editform.split('.') if e]
2626 2626 forms.insert(0, 'changeset')
2627 2627 templatetext = None
2628 2628 while forms:
2629 2629 ref = '.'.join(forms)
2630 2630 if repo.ui.config('committemplate', ref):
2631 2631 templatetext = committext = buildcommittemplate(
2632 2632 repo, ctx, subs, extramsg, ref)
2633 2633 break
2634 2634 forms.pop()
2635 2635 else:
2636 2636 committext = buildcommittext(repo, ctx, subs, extramsg)
2637 2637
2638 2638 # run editor in the repository root
2639 2639 olddir = encoding.getcwd()
2640 2640 os.chdir(repo.root)
2641 2641
2642 2642 # make in-memory changes visible to external process
2643 2643 tr = repo.currenttransaction()
2644 2644 repo.dirstate.write(tr)
2645 2645 pending = tr and tr.writepending() and repo.root
2646 2646
2647 2647 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2648 2648 editform=editform, pending=pending,
2649 2649 repopath=repo.path, action='commit')
2650 2650 text = editortext
2651 2651
2652 2652 # strip away anything below this special string (used for editors that want
2653 2653 # to display the diff)
2654 2654 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2655 2655 if stripbelow:
2656 2656 text = text[:stripbelow.start()]
2657 2657
2658 2658 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2659 2659 os.chdir(olddir)
2660 2660
2661 2661 if finishdesc:
2662 2662 text = finishdesc(text)
2663 2663 if not text.strip():
2664 2664 raise error.Abort(_("empty commit message"))
2665 2665 if unchangedmessagedetection and editortext == templatetext:
2666 2666 raise error.Abort(_("commit message unchanged"))
2667 2667
2668 2668 return text
2669 2669
2670 2670 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2671 2671 ui = repo.ui
2672 2672 spec = formatter.templatespec(ref, None, None)
2673 2673 t = logcmdutil.changesettemplater(ui, repo, spec)
2674 2674 t.t.cache.update((k, templater.unquotestring(v))
2675 2675 for k, v in repo.ui.configitems('committemplate'))
2676 2676
2677 2677 if not extramsg:
2678 2678 extramsg = '' # ensure that extramsg is string
2679 2679
2680 2680 ui.pushbuffer()
2681 2681 t.show(ctx, extramsg=extramsg)
2682 2682 return ui.popbuffer()
2683 2683
2684 2684 def hgprefix(msg):
2685 2685 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2686 2686
2687 2687 def buildcommittext(repo, ctx, subs, extramsg):
2688 2688 edittext = []
2689 2689 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2690 2690 if ctx.description():
2691 2691 edittext.append(ctx.description())
2692 2692 edittext.append("")
2693 2693 edittext.append("") # Empty line between message and comments.
2694 2694 edittext.append(hgprefix(_("Enter commit message."
2695 2695 " Lines beginning with 'HG:' are removed.")))
2696 2696 edittext.append(hgprefix(extramsg))
2697 2697 edittext.append("HG: --")
2698 2698 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2699 2699 if ctx.p2():
2700 2700 edittext.append(hgprefix(_("branch merge")))
2701 2701 if ctx.branch():
2702 2702 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2703 2703 if bookmarks.isactivewdirparent(repo):
2704 2704 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2705 2705 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2706 2706 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2707 2707 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2708 2708 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2709 2709 if not added and not modified and not removed:
2710 2710 edittext.append(hgprefix(_("no files changed")))
2711 2711 edittext.append("")
2712 2712
2713 2713 return "\n".join(edittext)
2714 2714
2715 2715 def commitstatus(repo, node, branch, bheads=None, opts=None):
2716 2716 if opts is None:
2717 2717 opts = {}
2718 2718 ctx = repo[node]
2719 2719 parents = ctx.parents()
2720 2720
2721 2721 if (not opts.get('amend') and bheads and node not in bheads and not
2722 2722 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2723 2723 repo.ui.status(_('created new head\n'))
2724 2724 # The message is not printed for initial roots. For the other
2725 2725 # changesets, it is printed in the following situations:
2726 2726 #
2727 2727 # Par column: for the 2 parents with ...
2728 2728 # N: null or no parent
2729 2729 # B: parent is on another named branch
2730 2730 # C: parent is a regular non head changeset
2731 2731 # H: parent was a branch head of the current branch
2732 2732 # Msg column: whether we print "created new head" message
2733 2733 # In the following, it is assumed that there already exists some
2734 2734 # initial branch heads of the current branch, otherwise nothing is
2735 2735 # printed anyway.
2736 2736 #
2737 2737 # Par Msg Comment
2738 2738 # N N y additional topo root
2739 2739 #
2740 2740 # B N y additional branch root
2741 2741 # C N y additional topo head
2742 2742 # H N n usual case
2743 2743 #
2744 2744 # B B y weird additional branch root
2745 2745 # C B y branch merge
2746 2746 # H B n merge with named branch
2747 2747 #
2748 2748 # C C y additional head from merge
2749 2749 # C H n merge with a head
2750 2750 #
2751 2751 # H H n head merge: head count decreases
2752 2752
2753 2753 if not opts.get('close_branch'):
2754 2754 for r in parents:
2755 2755 if r.closesbranch() and r.branch() == branch:
2756 2756 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2757 2757
2758 2758 if repo.ui.debugflag:
2759 2759 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2760 2760 elif repo.ui.verbose:
2761 2761 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2762 2762
2763 2763 def postcommitstatus(repo, pats, opts):
2764 2764 return repo.status(match=scmutil.match(repo[None], pats, opts))
2765 2765
2766 2766 def revert(ui, repo, ctx, parents, *pats, **opts):
2767 2767 opts = pycompat.byteskwargs(opts)
2768 2768 parent, p2 = parents
2769 2769 node = ctx.node()
2770 2770
2771 2771 mf = ctx.manifest()
2772 2772 if node == p2:
2773 2773 parent = p2
2774 2774
2775 2775 # need all matching names in dirstate and manifest of target rev,
2776 2776 # so have to walk both. do not print errors if files exist in one
2777 2777 # but not other. in both cases, filesets should be evaluated against
2778 2778 # workingctx to get consistent result (issue4497). this means 'set:**'
2779 2779 # cannot be used to select missing files from target rev.
2780 2780
2781 2781 # `names` is a mapping for all elements in working copy and target revision
2782 2782 # The mapping is in the form:
2783 2783 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2784 2784 names = {}
2785 2785
2786 2786 with repo.wlock():
2787 2787 ## filling of the `names` mapping
2788 2788 # walk dirstate to fill `names`
2789 2789
2790 2790 interactive = opts.get('interactive', False)
2791 2791 wctx = repo[None]
2792 2792 m = scmutil.match(wctx, pats, opts)
2793 2793
2794 2794 # we'll need this later
2795 2795 targetsubs = sorted(s for s in wctx.substate if m(s))
2796 2796
2797 2797 if not m.always():
2798 2798 matcher = matchmod.badmatch(m, lambda x, y: False)
2799 2799 for abs in wctx.walk(matcher):
2800 2800 names[abs] = m.rel(abs), m.exact(abs)
2801 2801
2802 2802 # walk target manifest to fill `names`
2803 2803
2804 2804 def badfn(path, msg):
2805 2805 if path in names:
2806 2806 return
2807 2807 if path in ctx.substate:
2808 2808 return
2809 2809 path_ = path + '/'
2810 2810 for f in names:
2811 2811 if f.startswith(path_):
2812 2812 return
2813 2813 ui.warn("%s: %s\n" % (m.rel(path), msg))
2814 2814
2815 2815 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2816 2816 if abs not in names:
2817 2817 names[abs] = m.rel(abs), m.exact(abs)
2818 2818
2819 2819 # Find status of all file in `names`.
2820 2820 m = scmutil.matchfiles(repo, names)
2821 2821
2822 2822 changes = repo.status(node1=node, match=m,
2823 2823 unknown=True, ignored=True, clean=True)
2824 2824 else:
2825 2825 changes = repo.status(node1=node, match=m)
2826 2826 for kind in changes:
2827 2827 for abs in kind:
2828 2828 names[abs] = m.rel(abs), m.exact(abs)
2829 2829
2830 2830 m = scmutil.matchfiles(repo, names)
2831 2831
2832 2832 modified = set(changes.modified)
2833 2833 added = set(changes.added)
2834 2834 removed = set(changes.removed)
2835 2835 _deleted = set(changes.deleted)
2836 2836 unknown = set(changes.unknown)
2837 2837 unknown.update(changes.ignored)
2838 2838 clean = set(changes.clean)
2839 2839 modadded = set()
2840 2840
2841 2841 # We need to account for the state of the file in the dirstate,
2842 2842 # even when we revert against something else than parent. This will
2843 2843 # slightly alter the behavior of revert (doing back up or not, delete
2844 2844 # or just forget etc).
2845 2845 if parent == node:
2846 2846 dsmodified = modified
2847 2847 dsadded = added
2848 2848 dsremoved = removed
2849 2849 # store all local modifications, useful later for rename detection
2850 2850 localchanges = dsmodified | dsadded
2851 2851 modified, added, removed = set(), set(), set()
2852 2852 else:
2853 2853 changes = repo.status(node1=parent, match=m)
2854 2854 dsmodified = set(changes.modified)
2855 2855 dsadded = set(changes.added)
2856 2856 dsremoved = set(changes.removed)
2857 2857 # store all local modifications, useful later for rename detection
2858 2858 localchanges = dsmodified | dsadded
2859 2859
2860 2860 # only take into account for removes between wc and target
2861 2861 clean |= dsremoved - removed
2862 2862 dsremoved &= removed
2863 2863 # distinct between dirstate remove and other
2864 2864 removed -= dsremoved
2865 2865
2866 2866 modadded = added & dsmodified
2867 2867 added -= modadded
2868 2868
2869 2869 # tell newly modified apart.
2870 2870 dsmodified &= modified
2871 2871 dsmodified |= modified & dsadded # dirstate added may need backup
2872 2872 modified -= dsmodified
2873 2873
2874 2874 # We need to wait for some post-processing to update this set
2875 2875 # before making the distinction. The dirstate will be used for
2876 2876 # that purpose.
2877 2877 dsadded = added
2878 2878
2879 2879 # in case of merge, files that are actually added can be reported as
2880 2880 # modified, we need to post process the result
2881 2881 if p2 != nullid:
2882 2882 mergeadd = set(dsmodified)
2883 2883 for path in dsmodified:
2884 2884 if path in mf:
2885 2885 mergeadd.remove(path)
2886 2886 dsadded |= mergeadd
2887 2887 dsmodified -= mergeadd
2888 2888
2889 2889 # if f is a rename, update `names` to also revert the source
2890 2890 cwd = repo.getcwd()
2891 2891 for f in localchanges:
2892 2892 src = repo.dirstate.copied(f)
2893 2893 # XXX should we check for rename down to target node?
2894 2894 if src and src not in names and repo.dirstate[src] == 'r':
2895 2895 dsremoved.add(src)
2896 2896 names[src] = (repo.pathto(src, cwd), True)
2897 2897
2898 2898 # determine the exact nature of the deleted changesets
2899 2899 deladded = set(_deleted)
2900 2900 for path in _deleted:
2901 2901 if path in mf:
2902 2902 deladded.remove(path)
2903 2903 deleted = _deleted - deladded
2904 2904
2905 2905 # distinguish between file to forget and the other
2906 2906 added = set()
2907 2907 for abs in dsadded:
2908 2908 if repo.dirstate[abs] != 'a':
2909 2909 added.add(abs)
2910 2910 dsadded -= added
2911 2911
2912 2912 for abs in deladded:
2913 2913 if repo.dirstate[abs] == 'a':
2914 2914 dsadded.add(abs)
2915 2915 deladded -= dsadded
2916 2916
2917 2917 # For files marked as removed, we check if an unknown file is present at
2918 2918 # the same path. If a such file exists it may need to be backed up.
2919 2919 # Making the distinction at this stage helps have simpler backup
2920 2920 # logic.
2921 2921 removunk = set()
2922 2922 for abs in removed:
2923 2923 target = repo.wjoin(abs)
2924 2924 if os.path.lexists(target):
2925 2925 removunk.add(abs)
2926 2926 removed -= removunk
2927 2927
2928 2928 dsremovunk = set()
2929 2929 for abs in dsremoved:
2930 2930 target = repo.wjoin(abs)
2931 2931 if os.path.lexists(target):
2932 2932 dsremovunk.add(abs)
2933 2933 dsremoved -= dsremovunk
2934 2934
2935 2935 # action to be actually performed by revert
2936 2936 # (<list of file>, message>) tuple
2937 2937 actions = {'revert': ([], _('reverting %s\n')),
2938 2938 'add': ([], _('adding %s\n')),
2939 2939 'remove': ([], _('removing %s\n')),
2940 2940 'drop': ([], _('removing %s\n')),
2941 2941 'forget': ([], _('forgetting %s\n')),
2942 2942 'undelete': ([], _('undeleting %s\n')),
2943 2943 'noop': (None, _('no changes needed to %s\n')),
2944 2944 'unknown': (None, _('file not managed: %s\n')),
2945 2945 }
2946 2946
2947 2947 # "constant" that convey the backup strategy.
2948 2948 # All set to `discard` if `no-backup` is set do avoid checking
2949 2949 # no_backup lower in the code.
2950 2950 # These values are ordered for comparison purposes
2951 2951 backupinteractive = 3 # do backup if interactively modified
2952 2952 backup = 2 # unconditionally do backup
2953 2953 check = 1 # check if the existing file differs from target
2954 2954 discard = 0 # never do backup
2955 2955 if opts.get('no_backup'):
2956 2956 backupinteractive = backup = check = discard
2957 2957 if interactive:
2958 2958 dsmodifiedbackup = backupinteractive
2959 2959 else:
2960 2960 dsmodifiedbackup = backup
2961 2961 tobackup = set()
2962 2962
2963 2963 backupanddel = actions['remove']
2964 2964 if not opts.get('no_backup'):
2965 2965 backupanddel = actions['drop']
2966 2966
2967 2967 disptable = (
2968 2968 # dispatch table:
2969 2969 # file state
2970 2970 # action
2971 2971 # make backup
2972 2972
2973 2973 ## Sets that results that will change file on disk
2974 2974 # Modified compared to target, no local change
2975 2975 (modified, actions['revert'], discard),
2976 2976 # Modified compared to target, but local file is deleted
2977 2977 (deleted, actions['revert'], discard),
2978 2978 # Modified compared to target, local change
2979 2979 (dsmodified, actions['revert'], dsmodifiedbackup),
2980 2980 # Added since target
2981 2981 (added, actions['remove'], discard),
2982 2982 # Added in working directory
2983 2983 (dsadded, actions['forget'], discard),
2984 2984 # Added since target, have local modification
2985 2985 (modadded, backupanddel, backup),
2986 2986 # Added since target but file is missing in working directory
2987 2987 (deladded, actions['drop'], discard),
2988 2988 # Removed since target, before working copy parent
2989 2989 (removed, actions['add'], discard),
2990 2990 # Same as `removed` but an unknown file exists at the same path
2991 2991 (removunk, actions['add'], check),
2992 2992 # Removed since targe, marked as such in working copy parent
2993 2993 (dsremoved, actions['undelete'], discard),
2994 2994 # Same as `dsremoved` but an unknown file exists at the same path
2995 2995 (dsremovunk, actions['undelete'], check),
2996 2996 ## the following sets does not result in any file changes
2997 2997 # File with no modification
2998 2998 (clean, actions['noop'], discard),
2999 2999 # Existing file, not tracked anywhere
3000 3000 (unknown, actions['unknown'], discard),
3001 3001 )
3002 3002
3003 3003 for abs, (rel, exact) in sorted(names.items()):
3004 3004 # target file to be touch on disk (relative to cwd)
3005 3005 target = repo.wjoin(abs)
3006 3006 # search the entry in the dispatch table.
3007 3007 # if the file is in any of these sets, it was touched in the working
3008 3008 # directory parent and we are sure it needs to be reverted.
3009 3009 for table, (xlist, msg), dobackup in disptable:
3010 3010 if abs not in table:
3011 3011 continue
3012 3012 if xlist is not None:
3013 3013 xlist.append(abs)
3014 3014 if dobackup:
3015 3015 # If in interactive mode, don't automatically create
3016 3016 # .orig files (issue4793)
3017 3017 if dobackup == backupinteractive:
3018 3018 tobackup.add(abs)
3019 3019 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3020 3020 bakname = scmutil.origpath(ui, repo, rel)
3021 relbakname = os.path.relpath(bakname)
3021 3022 ui.note(_('saving current version of %s as %s\n') %
3022 (rel, bakname))
3023 (rel, relbakname))
3023 3024 if not opts.get('dry_run'):
3024 3025 if interactive:
3025 3026 util.copyfile(target, bakname)
3026 3027 else:
3027 3028 util.rename(target, bakname)
3028 3029 if opts.get('dry_run'):
3029 3030 if ui.verbose or not exact:
3030 3031 ui.status(msg % rel)
3031 3032 elif exact:
3032 3033 ui.warn(msg % rel)
3033 3034 break
3034 3035
3035 3036 if not opts.get('dry_run'):
3036 3037 needdata = ('revert', 'add', 'undelete')
3037 3038 oplist = [actions[name][0] for name in needdata]
3038 3039 prefetch = scmutil.prefetchfiles
3039 3040 matchfiles = scmutil.matchfiles
3040 3041 prefetch(repo, [ctx.rev()],
3041 3042 matchfiles(repo,
3042 3043 [f for sublist in oplist for f in sublist]))
3043 3044 _performrevert(repo, parents, ctx, names, actions, interactive,
3044 3045 tobackup)
3045 3046
3046 3047 if targetsubs:
3047 3048 # Revert the subrepos on the revert list
3048 3049 for sub in targetsubs:
3049 3050 try:
3050 3051 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3051 3052 **pycompat.strkwargs(opts))
3052 3053 except KeyError:
3053 3054 raise error.Abort("subrepository '%s' does not exist in %s!"
3054 3055 % (sub, short(ctx.node())))
3055 3056
3056 3057 def _performrevert(repo, parents, ctx, names, actions, interactive=False,
3057 3058 tobackup=None):
3058 3059 """function that actually perform all the actions computed for revert
3059 3060
3060 3061 This is an independent function to let extension to plug in and react to
3061 3062 the imminent revert.
3062 3063
3063 3064 Make sure you have the working directory locked when calling this function.
3064 3065 """
3065 3066 parent, p2 = parents
3066 3067 node = ctx.node()
3067 3068 excluded_files = []
3068 3069
3069 3070 def checkout(f):
3070 3071 fc = ctx[f]
3071 3072 repo.wwrite(f, fc.data(), fc.flags())
3072 3073
3073 3074 def doremove(f):
3074 3075 try:
3075 3076 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
3076 3077 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3077 3078 except OSError:
3078 3079 pass
3079 3080 repo.dirstate.remove(f)
3080 3081
3081 3082 def prntstatusmsg(action, f):
3082 3083 rel, exact = names[f]
3083 3084 if repo.ui.verbose or not exact:
3084 3085 repo.ui.status(actions[action][1] % rel)
3085 3086
3086 3087 audit_path = pathutil.pathauditor(repo.root, cached=True)
3087 3088 for f in actions['forget'][0]:
3088 3089 if interactive:
3089 3090 choice = repo.ui.promptchoice(
3090 3091 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3091 3092 if choice == 0:
3092 3093 prntstatusmsg('forget', f)
3093 3094 repo.dirstate.drop(f)
3094 3095 else:
3095 3096 excluded_files.append(f)
3096 3097 else:
3097 3098 prntstatusmsg('forget', f)
3098 3099 repo.dirstate.drop(f)
3099 3100 for f in actions['remove'][0]:
3100 3101 audit_path(f)
3101 3102 if interactive:
3102 3103 choice = repo.ui.promptchoice(
3103 3104 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3104 3105 if choice == 0:
3105 3106 prntstatusmsg('remove', f)
3106 3107 doremove(f)
3107 3108 else:
3108 3109 excluded_files.append(f)
3109 3110 else:
3110 3111 prntstatusmsg('remove', f)
3111 3112 doremove(f)
3112 3113 for f in actions['drop'][0]:
3113 3114 audit_path(f)
3114 3115 prntstatusmsg('drop', f)
3115 3116 repo.dirstate.remove(f)
3116 3117
3117 3118 normal = None
3118 3119 if node == parent:
3119 3120 # We're reverting to our parent. If possible, we'd like status
3120 3121 # to report the file as clean. We have to use normallookup for
3121 3122 # merges to avoid losing information about merged/dirty files.
3122 3123 if p2 != nullid:
3123 3124 normal = repo.dirstate.normallookup
3124 3125 else:
3125 3126 normal = repo.dirstate.normal
3126 3127
3127 3128 newlyaddedandmodifiedfiles = set()
3128 3129 if interactive:
3129 3130 # Prompt the user for changes to revert
3130 3131 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3131 3132 m = scmutil.matchfiles(repo, torevert)
3132 3133 diffopts = patch.difffeatureopts(repo.ui, whitespace=True,
3133 3134 section='commands',
3134 3135 configprefix='revert.interactive.')
3135 3136 diffopts.nodates = True
3136 3137 diffopts.git = True
3137 3138 operation = 'discard'
3138 3139 reversehunks = True
3139 3140 if node != parent:
3140 3141 operation = 'apply'
3141 3142 reversehunks = False
3142 3143 if reversehunks:
3143 3144 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3144 3145 else:
3145 3146 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3146 3147 originalchunks = patch.parsepatch(diff)
3147 3148
3148 3149 try:
3149 3150
3150 3151 chunks, opts = recordfilter(repo.ui, originalchunks,
3151 3152 operation=operation)
3152 3153 if reversehunks:
3153 3154 chunks = patch.reversehunks(chunks)
3154 3155
3155 3156 except error.PatchError as err:
3156 3157 raise error.Abort(_('error parsing patch: %s') % err)
3157 3158
3158 3159 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3159 3160 if tobackup is None:
3160 3161 tobackup = set()
3161 3162 # Apply changes
3162 3163 fp = stringio()
3163 3164 # chunks are serialized per file, but files aren't sorted
3164 3165 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3165 3166 prntstatusmsg('revert', f)
3166 3167 for c in chunks:
3167 3168 if ishunk(c):
3168 3169 abs = c.header.filename()
3169 3170 # Create a backup file only if this hunk should be backed up
3170 3171 if c.header.filename() in tobackup:
3171 3172 target = repo.wjoin(abs)
3172 3173 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3173 3174 util.copyfile(target, bakname)
3174 3175 tobackup.remove(abs)
3175 3176 c.write(fp)
3176 3177 dopatch = fp.tell()
3177 3178 fp.seek(0)
3178 3179 if dopatch:
3179 3180 try:
3180 3181 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3181 3182 except error.PatchError as err:
3182 3183 raise error.Abort(pycompat.bytestr(err))
3183 3184 del fp
3184 3185 else:
3185 3186 for f in actions['revert'][0]:
3186 3187 prntstatusmsg('revert', f)
3187 3188 checkout(f)
3188 3189 if normal:
3189 3190 normal(f)
3190 3191
3191 3192 for f in actions['add'][0]:
3192 3193 # Don't checkout modified files, they are already created by the diff
3193 3194 if f not in newlyaddedandmodifiedfiles:
3194 3195 prntstatusmsg('add', f)
3195 3196 checkout(f)
3196 3197 repo.dirstate.add(f)
3197 3198
3198 3199 normal = repo.dirstate.normallookup
3199 3200 if node == parent and p2 == nullid:
3200 3201 normal = repo.dirstate.normal
3201 3202 for f in actions['undelete'][0]:
3202 3203 if interactive:
3203 3204 choice = repo.ui.promptchoice(
3204 3205 _("add back removed file %s (Yn)?$$ &Yes $$ &No") % f)
3205 3206 if choice == 0:
3206 3207 prntstatusmsg('undelete', f)
3207 3208 checkout(f)
3208 3209 normal(f)
3209 3210 else:
3210 3211 excluded_files.append(f)
3211 3212 else:
3212 3213 prntstatusmsg('undelete', f)
3213 3214 checkout(f)
3214 3215 normal(f)
3215 3216
3216 3217 copied = copies.pathcopies(repo[parent], ctx)
3217 3218
3218 3219 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3219 3220 if f in copied:
3220 3221 repo.dirstate.copy(copied[f], f)
3221 3222
3222 3223 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3223 3224 # commands.outgoing. "missing" is "missing" of the result of
3224 3225 # "findcommonoutgoing()"
3225 3226 outgoinghooks = util.hooks()
3226 3227
3227 3228 # a list of (ui, repo) functions called by commands.summary
3228 3229 summaryhooks = util.hooks()
3229 3230
3230 3231 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3231 3232 #
3232 3233 # functions should return tuple of booleans below, if 'changes' is None:
3233 3234 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3234 3235 #
3235 3236 # otherwise, 'changes' is a tuple of tuples below:
3236 3237 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3237 3238 # - (desturl, destbranch, destpeer, outgoing)
3238 3239 summaryremotehooks = util.hooks()
3239 3240
3240 3241 # A list of state files kept by multistep operations like graft.
3241 3242 # Since graft cannot be aborted, it is considered 'clearable' by update.
3242 3243 # note: bisect is intentionally excluded
3243 3244 # (state file, clearable, allowcommit, error, hint)
3244 3245 unfinishedstates = [
3245 3246 ('graftstate', True, False, _('graft in progress'),
3246 3247 _("use 'hg graft --continue' or 'hg graft --stop' to stop")),
3247 3248 ('updatestate', True, False, _('last update was interrupted'),
3248 3249 _("use 'hg update' to get a consistent checkout"))
3249 3250 ]
3250 3251
3251 3252 def checkunfinished(repo, commit=False):
3252 3253 '''Look for an unfinished multistep operation, like graft, and abort
3253 3254 if found. It's probably good to check this right before
3254 3255 bailifchanged().
3255 3256 '''
3256 3257 # Check for non-clearable states first, so things like rebase will take
3257 3258 # precedence over update.
3258 3259 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3259 3260 if clearable or (commit and allowcommit):
3260 3261 continue
3261 3262 if repo.vfs.exists(f):
3262 3263 raise error.Abort(msg, hint=hint)
3263 3264
3264 3265 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3265 3266 if not clearable or (commit and allowcommit):
3266 3267 continue
3267 3268 if repo.vfs.exists(f):
3268 3269 raise error.Abort(msg, hint=hint)
3269 3270
3270 3271 def clearunfinished(repo):
3271 3272 '''Check for unfinished operations (as above), and clear the ones
3272 3273 that are clearable.
3273 3274 '''
3274 3275 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3275 3276 if not clearable and repo.vfs.exists(f):
3276 3277 raise error.Abort(msg, hint=hint)
3277 3278 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3278 3279 if clearable and repo.vfs.exists(f):
3279 3280 util.unlink(repo.vfs.join(f))
3280 3281
3281 3282 afterresolvedstates = [
3282 3283 ('graftstate',
3283 3284 _('hg graft --continue')),
3284 3285 ]
3285 3286
3286 3287 def howtocontinue(repo):
3287 3288 '''Check for an unfinished operation and return the command to finish
3288 3289 it.
3289 3290
3290 3291 afterresolvedstates tuples define a .hg/{file} and the corresponding
3291 3292 command needed to finish it.
3292 3293
3293 3294 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3294 3295 a boolean.
3295 3296 '''
3296 3297 contmsg = _("continue: %s")
3297 3298 for f, msg in afterresolvedstates:
3298 3299 if repo.vfs.exists(f):
3299 3300 return contmsg % msg, True
3300 3301 if repo[None].dirty(missing=True, merge=False, branch=False):
3301 3302 return contmsg % _("hg commit"), False
3302 3303 return None, None
3303 3304
3304 3305 def checkafterresolved(repo):
3305 3306 '''Inform the user about the next action after completing hg resolve
3306 3307
3307 3308 If there's a matching afterresolvedstates, howtocontinue will yield
3308 3309 repo.ui.warn as the reporter.
3309 3310
3310 3311 Otherwise, it will yield repo.ui.note.
3311 3312 '''
3312 3313 msg, warning = howtocontinue(repo)
3313 3314 if msg is not None:
3314 3315 if warning:
3315 3316 repo.ui.warn("%s\n" % msg)
3316 3317 else:
3317 3318 repo.ui.note("%s\n" % msg)
3318 3319
3319 3320 def wrongtooltocontinue(repo, task):
3320 3321 '''Raise an abort suggesting how to properly continue if there is an
3321 3322 active task.
3322 3323
3323 3324 Uses howtocontinue() to find the active task.
3324 3325
3325 3326 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3326 3327 a hint.
3327 3328 '''
3328 3329 after = howtocontinue(repo)
3329 3330 hint = None
3330 3331 if after[1]:
3331 3332 hint = after[0]
3332 3333 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,1315 +1,1315 b''
1 1 This file contains testcases that tend to be related to special cases or less
2 2 common commands affecting largefile.
3 3
4 4 $ hg init requirements
5 5 $ cd requirements
6 6
7 7 # largefiles not loaded by default.
8 8
9 9 $ hg config extensions
10 10 [1]
11 11
12 12 # Adding largefiles to requires file will auto-load largefiles extension.
13 13
14 14 $ echo largefiles >> .hg/requires
15 15 $ hg config extensions
16 16 extensions.largefiles=
17 17
18 18 # But only if there is no config entry for the extension already.
19 19
20 20 $ cat > .hg/hgrc << EOF
21 21 > [extensions]
22 22 > largefiles=!
23 23 > EOF
24 24
25 25 $ hg config extensions
26 26 abort: repository requires features unknown to this Mercurial: largefiles!
27 27 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
28 28 [255]
29 29
30 30 $ cat > .hg/hgrc << EOF
31 31 > [extensions]
32 32 > largefiles=
33 33 > EOF
34 34
35 35 $ hg config extensions
36 36 extensions.largefiles=
37 37
38 38 $ cat > .hg/hgrc << EOF
39 39 > [extensions]
40 40 > largefiles = missing.py
41 41 > EOF
42 42
43 43 $ hg config extensions
44 44 *** failed to import extension largefiles from missing.py: [Errno 2] $ENOENT$: 'missing.py'
45 45 abort: repository requires features unknown to this Mercurial: largefiles!
46 46 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
47 47 [255]
48 48
49 49 $ cd ..
50 50
51 51 Each sections should be independent of each others.
52 52
53 53 $ USERCACHE="$TESTTMP/cache"; export USERCACHE
54 54 $ mkdir "${USERCACHE}"
55 55 $ cat >> $HGRCPATH <<EOF
56 56 > [extensions]
57 57 > largefiles=
58 58 > purge=
59 59 > rebase=
60 60 > transplant=
61 61 > [phases]
62 62 > publish=False
63 63 > [largefiles]
64 64 > minsize=2
65 65 > patterns=glob:**.dat
66 66 > usercache=${USERCACHE}
67 67 > [hooks]
68 68 > precommit=sh -c "echo \\"Invoking status precommit hook\\"; hg status"
69 69 > EOF
70 70
71 71
72 72
73 73 Test copies and moves from a directory other than root (issue3516)
74 74 =========================================================================
75 75
76 76 $ hg init lf_cpmv
77 77 $ cd lf_cpmv
78 78 $ mkdir dira
79 79 $ mkdir dira/dirb
80 80 $ touch dira/dirb/largefile
81 81 $ hg add --large dira/dirb/largefile
82 82 $ hg commit -m "added"
83 83 Invoking status precommit hook
84 84 A dira/dirb/largefile
85 85 $ cd dira
86 86 $ hg cp dirb/largefile foo/largefile
87 87
88 88 TODO: Ideally, this should mention the largefile, not the standin
89 89 $ hg log -T '{rev}\n' --stat 'set:clean()'
90 90 0
91 91 .hglf/dira/dirb/largefile | 1 +
92 92 1 files changed, 1 insertions(+), 0 deletions(-)
93 93
94 94 $ hg ci -m "deep copy"
95 95 Invoking status precommit hook
96 96 A dira/foo/largefile
97 97 $ find . | sort
98 98 .
99 99 ./dirb
100 100 ./dirb/largefile
101 101 ./foo
102 102 ./foo/largefile
103 103 $ hg mv foo/largefile baz/largefile
104 104 $ hg ci -m "moved"
105 105 Invoking status precommit hook
106 106 A dira/baz/largefile
107 107 R dira/foo/largefile
108 108 $ find . | sort
109 109 .
110 110 ./baz
111 111 ./baz/largefile
112 112 ./dirb
113 113 ./dirb/largefile
114 114 $ cd ..
115 115 $ hg mv dira dirc
116 116 moving .hglf/dira/baz/largefile to .hglf/dirc/baz/largefile
117 117 moving .hglf/dira/dirb/largefile to .hglf/dirc/dirb/largefile
118 118 $ find * | sort
119 119 dirc
120 120 dirc/baz
121 121 dirc/baz/largefile
122 122 dirc/dirb
123 123 dirc/dirb/largefile
124 124
125 125 $ hg clone -q . ../fetch
126 126 $ hg --config extensions.fetch= fetch ../fetch
127 127 abort: uncommitted changes
128 128 [255]
129 129 $ hg up -qC
130 130 $ cd ..
131 131
132 132 Clone a local repository owned by another user
133 133 ===================================================
134 134
135 135 #if unix-permissions
136 136
137 137 We have to simulate that here by setting $HOME and removing write permissions
138 138 $ ORIGHOME="$HOME"
139 139 $ mkdir alice
140 140 $ HOME="`pwd`/alice"
141 141 $ cd alice
142 142 $ hg init pubrepo
143 143 $ cd pubrepo
144 144 $ dd if=/dev/zero bs=1k count=11k > a-large-file 2> /dev/null
145 145 $ hg add --large a-large-file
146 146 $ hg commit -m "Add a large file"
147 147 Invoking status precommit hook
148 148 A a-large-file
149 149 $ cd ..
150 150 $ chmod -R a-w pubrepo
151 151 $ cd ..
152 152 $ mkdir bob
153 153 $ HOME="`pwd`/bob"
154 154 $ cd bob
155 155 $ hg clone --pull ../alice/pubrepo pubrepo
156 156 requesting all changes
157 157 adding changesets
158 158 adding manifests
159 159 adding file changes
160 160 added 1 changesets with 1 changes to 1 files
161 161 new changesets 09a186cfa6da (1 drafts)
162 162 updating to branch default
163 163 getting changed largefiles
164 164 1 largefiles updated, 0 removed
165 165 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
166 166 $ cd ..
167 167 $ chmod -R u+w alice/pubrepo
168 168 $ HOME="$ORIGHOME"
169 169
170 170 #endif
171 171
172 172
173 173 Symlink to a large largefile should behave the same as a symlink to a normal file
174 174 =====================================================================================
175 175
176 176 #if symlink
177 177
178 178 $ hg init largesymlink
179 179 $ cd largesymlink
180 180 $ dd if=/dev/zero bs=1k count=10k of=largefile 2>/dev/null
181 181 $ hg add --large largefile
182 182 $ hg commit -m "commit a large file"
183 183 Invoking status precommit hook
184 184 A largefile
185 185 $ ln -s largefile largelink
186 186 $ hg add largelink
187 187 $ hg commit -m "commit a large symlink"
188 188 Invoking status precommit hook
189 189 A largelink
190 190 $ rm -f largelink
191 191 $ hg up >/dev/null
192 192 $ test -f largelink
193 193 [1]
194 194 $ test -L largelink
195 195 [1]
196 196 $ rm -f largelink # make next part of the test independent of the previous
197 197 $ hg up -C >/dev/null
198 198 $ test -f largelink
199 199 $ test -L largelink
200 200 $ cd ..
201 201
202 202 #endif
203 203
204 204
205 205 test for pattern matching on 'hg status':
206 206 ==============================================
207 207
208 208
209 209 to boost performance, largefiles checks whether specified patterns are
210 210 related to largefiles in working directory (NOT to STANDIN) or not.
211 211
212 212 $ hg init statusmatch
213 213 $ cd statusmatch
214 214
215 215 $ mkdir -p a/b/c/d
216 216 $ echo normal > a/b/c/d/e.normal.txt
217 217 $ hg add a/b/c/d/e.normal.txt
218 218 $ echo large > a/b/c/d/e.large.txt
219 219 $ hg add --large a/b/c/d/e.large.txt
220 220 $ mkdir -p a/b/c/x
221 221 $ echo normal > a/b/c/x/y.normal.txt
222 222 $ hg add a/b/c/x/y.normal.txt
223 223 $ hg commit -m 'add files'
224 224 Invoking status precommit hook
225 225 A a/b/c/d/e.large.txt
226 226 A a/b/c/d/e.normal.txt
227 227 A a/b/c/x/y.normal.txt
228 228
229 229 (1) no pattern: no performance boost
230 230 $ hg status -A
231 231 C a/b/c/d/e.large.txt
232 232 C a/b/c/d/e.normal.txt
233 233 C a/b/c/x/y.normal.txt
234 234
235 235 (2) pattern not related to largefiles: performance boost
236 236 $ hg status -A a/b/c/x
237 237 C a/b/c/x/y.normal.txt
238 238
239 239 (3) pattern related to largefiles: no performance boost
240 240 $ hg status -A a/b/c/d
241 241 C a/b/c/d/e.large.txt
242 242 C a/b/c/d/e.normal.txt
243 243
244 244 (4) pattern related to STANDIN (not to largefiles): performance boost
245 245 $ hg status -A .hglf/a
246 246 C .hglf/a/b/c/d/e.large.txt
247 247
248 248 (5) mixed case: no performance boost
249 249 $ hg status -A a/b/c/x a/b/c/d
250 250 C a/b/c/d/e.large.txt
251 251 C a/b/c/d/e.normal.txt
252 252 C a/b/c/x/y.normal.txt
253 253
254 254 verify that largefiles doesn't break filesets
255 255
256 256 $ hg log --rev . --exclude "set:binary()"
257 257 changeset: 0:41bd42f10efa
258 258 tag: tip
259 259 user: test
260 260 date: Thu Jan 01 00:00:00 1970 +0000
261 261 summary: add files
262 262
263 263 sharing a largefile repo automatically enables largefiles on the share
264 264
265 265 $ hg share --config extensions.share= . ../shared_lfrepo
266 266 updating working directory
267 267 getting changed largefiles
268 268 1 largefiles updated, 0 removed
269 269 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
270 270 $ grep largefiles ../shared_lfrepo/.hg/requires
271 271 largefiles
272 272
273 273 verify that large files in subrepos handled properly
274 274 $ hg init subrepo
275 275 $ echo "subrepo = subrepo" > .hgsub
276 276 $ hg add .hgsub
277 277 $ hg ci -m "add subrepo"
278 278 Invoking status precommit hook
279 279 A .hgsub
280 280 ? .hgsubstate
281 281 $ echo "rev 1" > subrepo/large.txt
282 282 $ hg add --large subrepo/large.txt
283 283 $ hg sum
284 284 parent: 1:8ee150ea2e9c tip
285 285 add subrepo
286 286 branch: default
287 287 commit: 1 subrepos
288 288 update: (current)
289 289 phases: 2 draft
290 290 $ hg st
291 291 $ hg st -S
292 292 A subrepo/large.txt
293 293 $ hg ci -S -m "commit top repo"
294 294 committing subrepository subrepo
295 295 Invoking status precommit hook
296 296 A large.txt
297 297 Invoking status precommit hook
298 298 M .hgsubstate
299 299 # No differences
300 300 $ hg st -S
301 301 $ hg sum
302 302 parent: 2:ce4cd0c527a6 tip
303 303 commit top repo
304 304 branch: default
305 305 commit: (clean)
306 306 update: (current)
307 307 phases: 3 draft
308 308 $ echo "rev 2" > subrepo/large.txt
309 309 $ hg st -S
310 310 M subrepo/large.txt
311 311 $ hg sum
312 312 parent: 2:ce4cd0c527a6 tip
313 313 commit top repo
314 314 branch: default
315 315 commit: 1 subrepos
316 316 update: (current)
317 317 phases: 3 draft
318 318 $ hg ci -m "this commit should fail without -S"
319 319 abort: uncommitted changes in subrepository "subrepo"
320 320 (use --subrepos for recursive commit)
321 321 [255]
322 322
323 323 Add a normal file to the subrepo, then test archiving
324 324
325 325 $ echo 'normal file' > subrepo/normal.txt
326 326 $ touch large.dat
327 327 $ mv subrepo/large.txt subrepo/renamed-large.txt
328 328 $ hg addremove -S --dry-run
329 329 adding large.dat as a largefile
330 330 removing subrepo/large.txt
331 331 adding subrepo/normal.txt
332 332 adding subrepo/renamed-large.txt
333 333 $ hg status -S
334 334 ! subrepo/large.txt
335 335 ? large.dat
336 336 ? subrepo/normal.txt
337 337 ? subrepo/renamed-large.txt
338 338
339 339 $ hg addremove --dry-run subrepo
340 340 removing subrepo/large.txt
341 341 adding subrepo/normal.txt
342 342 adding subrepo/renamed-large.txt
343 343 $ hg status -S
344 344 ! subrepo/large.txt
345 345 ? large.dat
346 346 ? subrepo/normal.txt
347 347 ? subrepo/renamed-large.txt
348 348 $ cd ..
349 349
350 350 $ hg -R statusmatch addremove --dry-run statusmatch/subrepo
351 351 removing statusmatch/subrepo/large.txt
352 352 adding statusmatch/subrepo/normal.txt
353 353 adding statusmatch/subrepo/renamed-large.txt
354 354 $ hg -R statusmatch status -S
355 355 ! subrepo/large.txt
356 356 ? large.dat
357 357 ? subrepo/normal.txt
358 358 ? subrepo/renamed-large.txt
359 359
360 360 $ hg -R statusmatch addremove --dry-run -S
361 361 adding large.dat as a largefile
362 362 removing subrepo/large.txt
363 363 adding subrepo/normal.txt
364 364 adding subrepo/renamed-large.txt
365 365 $ cd statusmatch
366 366
367 367 $ mv subrepo/renamed-large.txt subrepo/large.txt
368 368 $ hg addremove subrepo
369 369 adding subrepo/normal.txt
370 370 $ hg forget subrepo/normal.txt
371 371
372 372 $ hg addremove -S
373 373 adding large.dat as a largefile
374 374 adding subrepo/normal.txt
375 375 $ rm large.dat
376 376
377 377 $ hg addremove subrepo
378 378 $ hg addremove -S
379 379 removing large.dat
380 380
381 381 Lock in subrepo, otherwise the change isn't archived
382 382
383 383 $ hg ci -S -m "add normal file to top level"
384 384 committing subrepository subrepo
385 385 Invoking status precommit hook
386 386 M large.txt
387 387 A normal.txt
388 388 Invoking status precommit hook
389 389 M .hgsubstate
390 390 $ hg archive -S ../lf_subrepo_archive
391 391 $ find ../lf_subrepo_archive | sort
392 392 ../lf_subrepo_archive
393 393 ../lf_subrepo_archive/.hg_archival.txt
394 394 ../lf_subrepo_archive/.hgsub
395 395 ../lf_subrepo_archive/.hgsubstate
396 396 ../lf_subrepo_archive/a
397 397 ../lf_subrepo_archive/a/b
398 398 ../lf_subrepo_archive/a/b/c
399 399 ../lf_subrepo_archive/a/b/c/d
400 400 ../lf_subrepo_archive/a/b/c/d/e.large.txt
401 401 ../lf_subrepo_archive/a/b/c/d/e.normal.txt
402 402 ../lf_subrepo_archive/a/b/c/x
403 403 ../lf_subrepo_archive/a/b/c/x/y.normal.txt
404 404 ../lf_subrepo_archive/subrepo
405 405 ../lf_subrepo_archive/subrepo/large.txt
406 406 ../lf_subrepo_archive/subrepo/normal.txt
407 407 $ cat ../lf_subrepo_archive/.hg_archival.txt
408 408 repo: 41bd42f10efa43698cc02052ea0977771cba506d
409 409 node: d56a95e6522858bc08a724c4fe2bdee066d1c30b
410 410 branch: default
411 411 latesttag: null
412 412 latesttagdistance: 4
413 413 changessincelatesttag: 4
414 414
415 415 Test update with subrepos.
416 416
417 417 $ hg update 0
418 418 getting changed largefiles
419 419 0 largefiles updated, 1 removed
420 420 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
421 421 $ hg status -S
422 422 $ hg update tip
423 423 getting changed largefiles
424 424 1 largefiles updated, 0 removed
425 425 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
426 426 $ hg status -S
427 427 # modify a large file
428 428 $ echo "modified" > subrepo/large.txt
429 429 $ hg st -S
430 430 M subrepo/large.txt
431 431 # update -C should revert the change.
432 432 $ hg update -C
433 433 getting changed largefiles
434 434 1 largefiles updated, 0 removed
435 435 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
436 436 $ hg status -S
437 437
438 438 Forget doesn't change the content of the file
439 439 $ echo 'pre-forget content' > subrepo/large.txt
440 440 $ hg forget -v subrepo/large.txt
441 441 removing subrepo/large.txt
442 442 $ cat subrepo/large.txt
443 443 pre-forget content
444 444
445 445 Test reverting a forgotten file
446 446 $ hg revert -R subrepo subrepo/large.txt
447 447 $ hg status -SA subrepo/large.txt
448 448 C subrepo/large.txt
449 449
450 450 $ hg rm -v subrepo/large.txt
451 451 removing subrepo/large.txt
452 452 $ hg revert -R subrepo subrepo/large.txt
453 453 $ rm subrepo/large.txt
454 454 $ hg addremove -S
455 455 removing subrepo/large.txt
456 456 $ hg st -S
457 457 R subrepo/large.txt
458 458
459 459 Test archiving a revision that references a subrepo that is not yet
460 460 cloned (see test-subrepo-recursion.t):
461 461
462 462 $ hg clone -U . ../empty
463 463 $ cd ../empty
464 464 $ hg archive --subrepos -r tip ../archive.tar.gz
465 465 cloning subrepo subrepo from $TESTTMP/statusmatch/subrepo
466 466 $ cd ..
467 467
468 468
469 469
470 470
471 471
472 472
473 473 Test addremove, forget and others
474 474 ==============================================
475 475
476 476 Test that addremove picks up largefiles prior to the initial commit (issue3541)
477 477
478 478 $ hg init addrm2
479 479 $ cd addrm2
480 480 $ touch large.dat
481 481 $ touch large2.dat
482 482 $ touch normal
483 483 $ hg add --large large.dat
484 484 $ hg addremove -v
485 485 adding large2.dat as a largefile
486 486 adding normal
487 487
488 488 Test that forgetting all largefiles reverts to islfilesrepo() == False
489 489 (addremove will add *.dat as normal files now)
490 490 $ hg forget large.dat
491 491 $ hg forget large2.dat
492 492 $ hg addremove -v
493 493 adding large.dat
494 494 adding large2.dat
495 495
496 496 Test commit's addremove option prior to the first commit
497 497 $ hg forget large.dat
498 498 $ hg forget large2.dat
499 499 $ hg add --large large.dat
500 500 $ hg ci -Am "commit"
501 501 adding large2.dat as a largefile
502 502 Invoking status precommit hook
503 503 A large.dat
504 504 A large2.dat
505 505 A normal
506 506 $ find .hglf | sort
507 507 .hglf
508 508 .hglf/large.dat
509 509 .hglf/large2.dat
510 510
511 511 Test actions on largefiles using relative paths from subdir
512 512
513 513 $ mkdir sub
514 514 $ cd sub
515 515 $ echo anotherlarge > anotherlarge
516 516 $ hg add --large anotherlarge
517 517 $ hg st
518 518 A sub/anotherlarge
519 519 $ hg st anotherlarge
520 520 A anotherlarge
521 521 $ hg commit -m anotherlarge anotherlarge
522 522 Invoking status precommit hook
523 523 A sub/anotherlarge
524 524 $ hg log anotherlarge
525 525 changeset: 1:9627a577c5e9
526 526 tag: tip
527 527 user: test
528 528 date: Thu Jan 01 00:00:00 1970 +0000
529 529 summary: anotherlarge
530 530
531 531 $ hg --debug log -T '{rev}: {desc}\n' ../sub/anotherlarge
532 532 updated patterns: ../.hglf/sub/../sub/anotherlarge, ../sub/anotherlarge
533 533 1: anotherlarge
534 534
535 535 $ hg log -G anotherlarge
536 536 @ changeset: 1:9627a577c5e9
537 537 | tag: tip
538 538 ~ user: test
539 539 date: Thu Jan 01 00:00:00 1970 +0000
540 540 summary: anotherlarge
541 541
542 542
543 543 $ hg log glob:another*
544 544 changeset: 1:9627a577c5e9
545 545 tag: tip
546 546 user: test
547 547 date: Thu Jan 01 00:00:00 1970 +0000
548 548 summary: anotherlarge
549 549
550 550 $ hg --debug log -T '{rev}: {desc}\n' -G glob:another*
551 551 updated patterns: glob:../.hglf/sub/another*, glob:another*
552 552 @ 1: anotherlarge
553 553 |
554 554 ~
555 555
556 556 #if no-msys
557 557 $ hg --debug log -T '{rev}: {desc}\n' 'glob:../.hglf/sub/another*' # no-msys
558 558 updated patterns: glob:../.hglf/sub/another*
559 559 1: anotherlarge
560 560
561 561 $ hg --debug log -G -T '{rev}: {desc}\n' 'glob:../.hglf/sub/another*' # no-msys
562 562 updated patterns: glob:../.hglf/sub/another*
563 563 @ 1: anotherlarge
564 564 |
565 565 ~
566 566 #endif
567 567
568 568 $ echo more >> anotherlarge
569 569 $ hg st .
570 570 M anotherlarge
571 571 $ hg cat anotherlarge
572 572 anotherlarge
573 573 $ hg revert anotherlarge
574 574 $ hg st
575 575 ? sub/anotherlarge.orig
576 576
577 577 Test orig files go where we want them
578 578 $ echo moremore >> anotherlarge
579 579 $ hg revert anotherlarge -v --config 'ui.origbackuppath=.hg/origbackups'
580 580 creating directory: $TESTTMP/addrm2/.hg/origbackups/.hglf/sub
581 saving current version of ../.hglf/sub/anotherlarge as $TESTTMP/addrm2/.hg/origbackups/.hglf/sub/anotherlarge
581 saving current version of ../.hglf/sub/anotherlarge as ../.hg/origbackups/.hglf/sub/anotherlarge
582 582 reverting ../.hglf/sub/anotherlarge
583 583 creating directory: $TESTTMP/addrm2/.hg/origbackups/sub
584 584 found 90c622cf65cebe75c5842f9136c459333faf392e in store
585 585 found 90c622cf65cebe75c5842f9136c459333faf392e in store
586 586 $ ls ../.hg/origbackups/sub
587 587 anotherlarge
588 588 $ cd ..
589 589
590 590 Test glob logging from the root dir
591 591 $ hg log glob:**another*
592 592 changeset: 1:9627a577c5e9
593 593 tag: tip
594 594 user: test
595 595 date: Thu Jan 01 00:00:00 1970 +0000
596 596 summary: anotherlarge
597 597
598 598 $ hg log -G glob:**another*
599 599 @ changeset: 1:9627a577c5e9
600 600 | tag: tip
601 601 ~ user: test
602 602 date: Thu Jan 01 00:00:00 1970 +0000
603 603 summary: anotherlarge
604 604
605 605
606 606 $ cd ..
607 607
608 608 Log from outer space
609 609 $ hg --debug log -R addrm2 -T '{rev}: {desc}\n' 'addrm2/sub/anotherlarge'
610 610 updated patterns: addrm2/.hglf/sub/anotherlarge, addrm2/sub/anotherlarge
611 611 1: anotherlarge
612 612 $ hg --debug log -R addrm2 -T '{rev}: {desc}\n' 'addrm2/.hglf/sub/anotherlarge'
613 613 updated patterns: addrm2/.hglf/sub/anotherlarge
614 614 1: anotherlarge
615 615
616 616
617 617 Check error message while exchange
618 618 =========================================================
619 619
620 620 issue3651: summary/outgoing with largefiles shows "no remote repo"
621 621 unexpectedly
622 622
623 623 $ mkdir issue3651
624 624 $ cd issue3651
625 625
626 626 $ hg init src
627 627 $ echo a > src/a
628 628 $ hg -R src add --large src/a
629 629 $ hg -R src commit -m '#0'
630 630 Invoking status precommit hook
631 631 A a
632 632
633 633 check messages when no remote repository is specified:
634 634 "no remote repo" route for "hg outgoing --large" is not tested here,
635 635 because it can't be reproduced easily.
636 636
637 637 $ hg init clone1
638 638 $ hg -R clone1 -q pull src
639 639 $ hg -R clone1 -q update
640 640 $ hg -R clone1 paths | grep default
641 641 [1]
642 642
643 643 $ hg -R clone1 summary --large
644 644 parent: 0:fc0bd45326d3 tip
645 645 #0
646 646 branch: default
647 647 commit: (clean)
648 648 update: (current)
649 649 phases: 1 draft
650 650 largefiles: (no remote repo)
651 651
652 652 check messages when there is no files to upload:
653 653
654 654 $ hg -q clone src clone2
655 655 $ hg -R clone2 paths | grep default
656 656 default = $TESTTMP/issue3651/src
657 657
658 658 $ hg -R clone2 summary --large
659 659 parent: 0:fc0bd45326d3 tip
660 660 #0
661 661 branch: default
662 662 commit: (clean)
663 663 update: (current)
664 664 phases: 1 draft
665 665 largefiles: (no files to upload)
666 666 $ hg -R clone2 outgoing --large
667 667 comparing with $TESTTMP/issue3651/src
668 668 searching for changes
669 669 no changes found
670 670 largefiles: no files to upload
671 671 [1]
672 672
673 673 $ hg -R clone2 outgoing --large --graph --template "{rev}"
674 674 comparing with $TESTTMP/issue3651/src
675 675 searching for changes
676 676 no changes found
677 677 largefiles: no files to upload
678 678
679 679 check messages when there are files to upload:
680 680
681 681 $ echo b > clone2/b
682 682 $ hg -R clone2 add --large clone2/b
683 683 $ hg -R clone2 commit -m '#1'
684 684 Invoking status precommit hook
685 685 A b
686 686 $ hg -R clone2 summary --large
687 687 parent: 1:1acbe71ce432 tip
688 688 #1
689 689 branch: default
690 690 commit: (clean)
691 691 update: (current)
692 692 phases: 2 draft
693 693 largefiles: 1 entities for 1 files to upload
694 694 $ hg -R clone2 outgoing --large
695 695 comparing with $TESTTMP/issue3651/src
696 696 searching for changes
697 697 changeset: 1:1acbe71ce432
698 698 tag: tip
699 699 user: test
700 700 date: Thu Jan 01 00:00:00 1970 +0000
701 701 summary: #1
702 702
703 703 largefiles to upload (1 entities):
704 704 b
705 705
706 706 $ hg -R clone2 outgoing --large --graph --template "{rev}"
707 707 comparing with $TESTTMP/issue3651/src
708 708 searching for changes
709 709 @ 1
710 710
711 711 largefiles to upload (1 entities):
712 712 b
713 713
714 714
715 715 $ cp clone2/b clone2/b1
716 716 $ cp clone2/b clone2/b2
717 717 $ hg -R clone2 add --large clone2/b1 clone2/b2
718 718 $ hg -R clone2 commit -m '#2: add largefiles referring same entity'
719 719 Invoking status precommit hook
720 720 A b1
721 721 A b2
722 722 $ hg -R clone2 summary --large
723 723 parent: 2:6095d0695d70 tip
724 724 #2: add largefiles referring same entity
725 725 branch: default
726 726 commit: (clean)
727 727 update: (current)
728 728 phases: 3 draft
729 729 largefiles: 1 entities for 3 files to upload
730 730 $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n"
731 731 comparing with $TESTTMP/issue3651/src
732 732 searching for changes
733 733 1:1acbe71ce432
734 734 2:6095d0695d70
735 735 largefiles to upload (1 entities):
736 736 b
737 737 b1
738 738 b2
739 739
740 740 $ hg -R clone2 cat -r 1 clone2/.hglf/b
741 741 89e6c98d92887913cadf06b2adb97f26cde4849b
742 742 $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug --config progress.debug=true
743 743 comparing with $TESTTMP/issue3651/src
744 744 query 1; heads
745 745 searching for changes
746 746 all remote heads known locally
747 747 1:1acbe71ce432
748 748 2:6095d0695d70
749 749 finding outgoing largefiles: 0/2 revisions (0.00%)
750 750 finding outgoing largefiles: 1/2 revisions (50.00%)
751 751 largefiles to upload (1 entities):
752 752 b
753 753 89e6c98d92887913cadf06b2adb97f26cde4849b
754 754 b1
755 755 89e6c98d92887913cadf06b2adb97f26cde4849b
756 756 b2
757 757 89e6c98d92887913cadf06b2adb97f26cde4849b
758 758
759 759
760 760 $ echo bbb > clone2/b
761 761 $ hg -R clone2 commit -m '#3: add new largefile entity as existing file'
762 762 Invoking status precommit hook
763 763 M b
764 764 $ echo bbbb > clone2/b
765 765 $ hg -R clone2 commit -m '#4: add new largefile entity as existing file'
766 766 Invoking status precommit hook
767 767 M b
768 768 $ cp clone2/b1 clone2/b
769 769 $ hg -R clone2 commit -m '#5: refer existing largefile entity again'
770 770 Invoking status precommit hook
771 771 M b
772 772 $ hg -R clone2 summary --large
773 773 parent: 5:036794ea641c tip
774 774 #5: refer existing largefile entity again
775 775 branch: default
776 776 commit: (clean)
777 777 update: (current)
778 778 phases: 6 draft
779 779 largefiles: 3 entities for 3 files to upload
780 780 $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n"
781 781 comparing with $TESTTMP/issue3651/src
782 782 searching for changes
783 783 1:1acbe71ce432
784 784 2:6095d0695d70
785 785 3:7983dce246cc
786 786 4:233f12ada4ae
787 787 5:036794ea641c
788 788 largefiles to upload (3 entities):
789 789 b
790 790 b1
791 791 b2
792 792
793 793 $ hg -R clone2 cat -r 3 clone2/.hglf/b
794 794 c801c9cfe94400963fcb683246217d5db77f9a9a
795 795 $ hg -R clone2 cat -r 4 clone2/.hglf/b
796 796 13f9ed0898e315bf59dc2973fec52037b6f441a2
797 797 $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug --config progress.debug=true
798 798 comparing with $TESTTMP/issue3651/src
799 799 query 1; heads
800 800 searching for changes
801 801 all remote heads known locally
802 802 1:1acbe71ce432
803 803 2:6095d0695d70
804 804 3:7983dce246cc
805 805 4:233f12ada4ae
806 806 5:036794ea641c
807 807 finding outgoing largefiles: 0/5 revisions (0.00%)
808 808 finding outgoing largefiles: 1/5 revisions (20.00%)
809 809 finding outgoing largefiles: 2/5 revisions (40.00%)
810 810 finding outgoing largefiles: 3/5 revisions (60.00%)
811 811 finding outgoing largefiles: 4/5 revisions (80.00%)
812 812 largefiles to upload (3 entities):
813 813 b
814 814 13f9ed0898e315bf59dc2973fec52037b6f441a2
815 815 89e6c98d92887913cadf06b2adb97f26cde4849b
816 816 c801c9cfe94400963fcb683246217d5db77f9a9a
817 817 b1
818 818 89e6c98d92887913cadf06b2adb97f26cde4849b
819 819 b2
820 820 89e6c98d92887913cadf06b2adb97f26cde4849b
821 821
822 822
823 823 Pushing revision #1 causes uploading entity 89e6c98d9288, which is
824 824 shared also by largefiles b1, b2 in revision #2 and b in revision #5.
825 825
826 826 Then, entity 89e6c98d9288 is not treated as "outgoing entity" at "hg
827 827 summary" and "hg outgoing", even though files in outgoing revision #2
828 828 and #5 refer it.
829 829
830 830 $ hg -R clone2 push -r 1 -q
831 831 $ hg -R clone2 summary --large
832 832 parent: 5:036794ea641c tip
833 833 #5: refer existing largefile entity again
834 834 branch: default
835 835 commit: (clean)
836 836 update: (current)
837 837 phases: 6 draft
838 838 largefiles: 2 entities for 1 files to upload
839 839 $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n"
840 840 comparing with $TESTTMP/issue3651/src
841 841 searching for changes
842 842 2:6095d0695d70
843 843 3:7983dce246cc
844 844 4:233f12ada4ae
845 845 5:036794ea641c
846 846 largefiles to upload (2 entities):
847 847 b
848 848
849 849 $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug --config progress.debug=true
850 850 comparing with $TESTTMP/issue3651/src
851 851 query 1; heads
852 852 searching for changes
853 853 all remote heads known locally
854 854 2:6095d0695d70
855 855 3:7983dce246cc
856 856 4:233f12ada4ae
857 857 5:036794ea641c
858 858 finding outgoing largefiles: 0/4 revisions (0.00%)
859 859 finding outgoing largefiles: 1/4 revisions (25.00%)
860 860 finding outgoing largefiles: 2/4 revisions (50.00%)
861 861 finding outgoing largefiles: 3/4 revisions (75.00%)
862 862 largefiles to upload (2 entities):
863 863 b
864 864 13f9ed0898e315bf59dc2973fec52037b6f441a2
865 865 c801c9cfe94400963fcb683246217d5db77f9a9a
866 866
867 867
868 868 $ cd ..
869 869
870 870 merge action 'd' for 'local renamed directory to d2/g' which has no filename
871 871 ==================================================================================
872 872
873 873 $ hg init merge-action
874 874 $ cd merge-action
875 875 $ touch l
876 876 $ hg add --large l
877 877 $ mkdir d1
878 878 $ touch d1/f
879 879 $ hg ci -Aqm0
880 880 Invoking status precommit hook
881 881 A d1/f
882 882 A l
883 883 $ echo > d1/f
884 884 $ touch d1/g
885 885 $ hg ci -Aqm1
886 886 Invoking status precommit hook
887 887 M d1/f
888 888 A d1/g
889 889 $ hg up -qr0
890 890 $ hg mv d1 d2
891 891 moving d1/f to d2/f
892 892 $ hg ci -qm2
893 893 Invoking status precommit hook
894 894 A d2/f
895 895 R d1/f
896 896 $ hg merge
897 897 merging d2/f and d1/f to d2/f
898 898 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
899 899 (branch merge, don't forget to commit)
900 900 $ cd ..
901 901
902 902
903 903 Merge conflicts:
904 904 =====================
905 905
906 906 $ hg init merge
907 907 $ cd merge
908 908 $ echo 0 > f-different
909 909 $ echo 0 > f-same
910 910 $ echo 0 > f-unchanged-1
911 911 $ echo 0 > f-unchanged-2
912 912 $ hg add --large *
913 913 $ hg ci -m0
914 914 Invoking status precommit hook
915 915 A f-different
916 916 A f-same
917 917 A f-unchanged-1
918 918 A f-unchanged-2
919 919 $ echo tmp1 > f-unchanged-1
920 920 $ echo tmp1 > f-unchanged-2
921 921 $ echo tmp1 > f-same
922 922 $ hg ci -m1
923 923 Invoking status precommit hook
924 924 M f-same
925 925 M f-unchanged-1
926 926 M f-unchanged-2
927 927 $ echo 2 > f-different
928 928 $ echo 0 > f-unchanged-1
929 929 $ echo 1 > f-unchanged-2
930 930 $ echo 1 > f-same
931 931 $ hg ci -m2
932 932 Invoking status precommit hook
933 933 M f-different
934 934 M f-same
935 935 M f-unchanged-1
936 936 M f-unchanged-2
937 937 $ hg up -qr0
938 938 $ echo tmp2 > f-unchanged-1
939 939 $ echo tmp2 > f-unchanged-2
940 940 $ echo tmp2 > f-same
941 941 $ hg ci -m3
942 942 Invoking status precommit hook
943 943 M f-same
944 944 M f-unchanged-1
945 945 M f-unchanged-2
946 946 created new head
947 947 $ echo 1 > f-different
948 948 $ echo 1 > f-unchanged-1
949 949 $ echo 0 > f-unchanged-2
950 950 $ echo 1 > f-same
951 951 $ hg ci -m4
952 952 Invoking status precommit hook
953 953 M f-different
954 954 M f-same
955 955 M f-unchanged-1
956 956 M f-unchanged-2
957 957 $ hg merge
958 958 largefile f-different has a merge conflict
959 959 ancestor was 09d2af8dd22201dd8d48e5dcfcaed281ff9422c7
960 960 keep (l)ocal e5fa44f2b31c1fb553b6021e7360d07d5d91ff5e or
961 961 take (o)ther 7448d8798a4380162d4b56f9b452e2f6f9e24e7a? l
962 962 getting changed largefiles
963 963 1 largefiles updated, 0 removed
964 964 0 files updated, 4 files merged, 0 files removed, 0 files unresolved
965 965 (branch merge, don't forget to commit)
966 966 $ cat f-different
967 967 1
968 968 $ cat f-same
969 969 1
970 970 $ cat f-unchanged-1
971 971 1
972 972 $ cat f-unchanged-2
973 973 1
974 974 $ cd ..
975 975
976 976 Test largefile insulation (do not enabled a side effect
977 977 ========================================================
978 978
979 979 Check whether "largefiles" feature is supported only in repositories
980 980 enabling largefiles extension.
981 981
982 982 $ mkdir individualenabling
983 983 $ cd individualenabling
984 984
985 985 $ hg init enabledlocally
986 986 $ echo large > enabledlocally/large
987 987 $ hg -R enabledlocally add --large enabledlocally/large
988 988 $ hg -R enabledlocally commit -m '#0'
989 989 Invoking status precommit hook
990 990 A large
991 991
992 992 $ hg init notenabledlocally
993 993 $ echo large > notenabledlocally/large
994 994 $ hg -R notenabledlocally add --large notenabledlocally/large
995 995 $ hg -R notenabledlocally commit -m '#0'
996 996 Invoking status precommit hook
997 997 A large
998 998
999 999 $ cat >> $HGRCPATH <<EOF
1000 1000 > [extensions]
1001 1001 > # disable globally
1002 1002 > largefiles=!
1003 1003 > EOF
1004 1004 $ cat >> enabledlocally/.hg/hgrc <<EOF
1005 1005 > [extensions]
1006 1006 > # enable locally
1007 1007 > largefiles=
1008 1008 > EOF
1009 1009 $ hg -R enabledlocally root
1010 1010 $TESTTMP/individualenabling/enabledlocally
1011 1011 $ hg -R notenabledlocally root
1012 1012 abort: repository requires features unknown to this Mercurial: largefiles!
1013 1013 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
1014 1014 [255]
1015 1015
1016 1016 $ hg init push-dst
1017 1017 $ hg -R enabledlocally push push-dst
1018 1018 pushing to push-dst
1019 1019 abort: required features are not supported in the destination: largefiles
1020 1020 [255]
1021 1021
1022 1022 $ hg init pull-src
1023 1023 $ hg -R pull-src pull enabledlocally
1024 1024 pulling from enabledlocally
1025 1025 abort: required features are not supported in the destination: largefiles
1026 1026 [255]
1027 1027
1028 1028 $ hg clone enabledlocally clone-dst
1029 1029 abort: repository requires features unknown to this Mercurial: largefiles!
1030 1030 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
1031 1031 [255]
1032 1032 $ test -d clone-dst
1033 1033 [1]
1034 1034 $ hg clone --pull enabledlocally clone-pull-dst
1035 1035 abort: required features are not supported in the destination: largefiles
1036 1036 [255]
1037 1037 $ test -d clone-pull-dst
1038 1038 [1]
1039 1039
1040 1040 #if serve
1041 1041
1042 1042 Test largefiles specific peer setup, when largefiles is enabled
1043 1043 locally (issue4109)
1044 1044
1045 1045 $ hg showconfig extensions | grep largefiles
1046 1046 extensions.largefiles=!
1047 1047 $ mkdir -p $TESTTMP/individualenabling/usercache
1048 1048
1049 1049 $ hg serve -R enabledlocally -d -p $HGPORT --pid-file hg.pid
1050 1050 $ cat hg.pid >> $DAEMON_PIDS
1051 1051
1052 1052 $ hg init pull-dst
1053 1053 $ cat > pull-dst/.hg/hgrc <<EOF
1054 1054 > [extensions]
1055 1055 > # enable locally
1056 1056 > largefiles=
1057 1057 > [largefiles]
1058 1058 > # ignore system cache to force largefiles specific wire proto access
1059 1059 > usercache=$TESTTMP/individualenabling/usercache
1060 1060 > EOF
1061 1061 $ hg -R pull-dst -q pull -u http://localhost:$HGPORT
1062 1062
1063 1063 $ killdaemons.py
1064 1064 #endif
1065 1065
1066 1066 Test overridden functions work correctly even for repos disabling
1067 1067 largefiles (issue4547)
1068 1068
1069 1069 $ hg showconfig extensions | grep largefiles
1070 1070 extensions.largefiles=!
1071 1071
1072 1072 (test updating implied by clone)
1073 1073
1074 1074 $ hg init enabled-but-no-largefiles
1075 1075 $ echo normal1 > enabled-but-no-largefiles/normal1
1076 1076 $ hg -R enabled-but-no-largefiles add enabled-but-no-largefiles/normal1
1077 1077 $ hg -R enabled-but-no-largefiles commit -m '#0@enabled-but-no-largefiles'
1078 1078 Invoking status precommit hook
1079 1079 A normal1
1080 1080 $ cat >> enabled-but-no-largefiles/.hg/hgrc <<EOF
1081 1081 > [extensions]
1082 1082 > # enable locally
1083 1083 > largefiles=
1084 1084 > EOF
1085 1085 $ hg clone -q enabled-but-no-largefiles no-largefiles
1086 1086
1087 1087 $ echo normal2 > enabled-but-no-largefiles/normal2
1088 1088 $ hg -R enabled-but-no-largefiles add enabled-but-no-largefiles/normal2
1089 1089 $ hg -R enabled-but-no-largefiles commit -m '#1@enabled-but-no-largefiles'
1090 1090 Invoking status precommit hook
1091 1091 A normal2
1092 1092
1093 1093 $ echo normal3 > no-largefiles/normal3
1094 1094 $ hg -R no-largefiles add no-largefiles/normal3
1095 1095 $ hg -R no-largefiles commit -m '#1@no-largefiles'
1096 1096 Invoking status precommit hook
1097 1097 A normal3
1098 1098
1099 1099 $ hg -R no-largefiles -q pull --rebase
1100 1100 Invoking status precommit hook
1101 1101 A normal3
1102 1102
1103 1103 (test reverting)
1104 1104
1105 1105 $ hg init subrepo-root
1106 1106 $ cat >> subrepo-root/.hg/hgrc <<EOF
1107 1107 > [extensions]
1108 1108 > # enable locally
1109 1109 > largefiles=
1110 1110 > EOF
1111 1111 $ echo large > subrepo-root/large
1112 1112 $ mkdir -p subrepo-root/dir/subdir
1113 1113 $ echo large2 > subrepo-root/dir/subdir/large.bin
1114 1114 $ hg -R subrepo-root add --large subrepo-root/large subrepo-root/dir/subdir/large.bin
1115 1115 $ hg clone -q no-largefiles subrepo-root/no-largefiles
1116 1116 $ cat > subrepo-root/.hgsub <<EOF
1117 1117 > no-largefiles = no-largefiles
1118 1118 > EOF
1119 1119 $ hg -R subrepo-root add subrepo-root/.hgsub
1120 1120 $ hg -R subrepo-root commit -m '#0'
1121 1121 Invoking status precommit hook
1122 1122 A .hgsub
1123 1123 A dir/subdir/large.bin
1124 1124 A large
1125 1125 ? .hgsubstate
1126 1126 $ echo dirty >> subrepo-root/large
1127 1127 $ echo dirty >> subrepo-root/no-largefiles/normal1
1128 1128 $ hg -R subrepo-root status -S
1129 1129 M large
1130 1130 M no-largefiles/normal1
1131 1131 $ hg -R subrepo-root extdiff -p echo -S --config extensions.extdiff=
1132 1132 "*\\no-largefiles\\normal1" "*\\no-largefiles\\normal1" (glob) (windows !)
1133 1133 */no-largefiles/normal1 */no-largefiles/normal1 (glob) (no-windows !)
1134 1134 [1]
1135 1135 $ hg -R subrepo-root revert --all
1136 1136 reverting subrepo-root/.hglf/large
1137 1137 reverting subrepo no-largefiles
1138 1138 reverting subrepo-root/no-largefiles/normal1
1139 1139
1140 1140 Move (and then undo) a directory move with only largefiles.
1141 1141
1142 1142 $ cd subrepo-root
1143 1143 $ "$PYTHON" $TESTDIR/list-tree.py .hglf dir* large*
1144 1144 .hglf/
1145 1145 .hglf/dir/
1146 1146 .hglf/dir/subdir/
1147 1147 .hglf/dir/subdir/large.bin
1148 1148 .hglf/large
1149 1149 dir/
1150 1150 dir/subdir/
1151 1151 dir/subdir/large.bin
1152 1152 large
1153 1153 large.orig
1154 1154
1155 1155 $ hg mv dir/subdir dir/subdir2
1156 1156 moving .hglf/dir/subdir/large.bin to .hglf/dir/subdir2/large.bin
1157 1157
1158 1158 $ "$PYTHON" $TESTDIR/list-tree.py .hglf dir* large*
1159 1159 .hglf/
1160 1160 .hglf/dir/
1161 1161 .hglf/dir/subdir2/
1162 1162 .hglf/dir/subdir2/large.bin
1163 1163 .hglf/large
1164 1164 dir/
1165 1165 dir/subdir2/
1166 1166 dir/subdir2/large.bin
1167 1167 large
1168 1168 large.orig
1169 1169 $ hg status -C
1170 1170 A dir/subdir2/large.bin
1171 1171 dir/subdir/large.bin
1172 1172 R dir/subdir/large.bin
1173 1173 ? large.orig
1174 1174
1175 1175 $ echo 'modified' > dir/subdir2/large.bin
1176 1176 $ hg status -C
1177 1177 A dir/subdir2/large.bin
1178 1178 dir/subdir/large.bin
1179 1179 R dir/subdir/large.bin
1180 1180 ? large.orig
1181 1181
1182 1182 $ hg revert --all
1183 1183 forgetting .hglf/dir/subdir2/large.bin
1184 1184 undeleting .hglf/dir/subdir/large.bin
1185 1185 reverting subrepo no-largefiles
1186 1186
1187 1187 $ hg status -C
1188 1188 ? dir/subdir2/large.bin
1189 1189 ? large.orig
1190 1190
1191 1191 The content of the forgotten file shouldn't be clobbered
1192 1192
1193 1193 $ cat dir/subdir2/large.bin
1194 1194 modified
1195 1195
1196 1196 The standin for subdir2 should be deleted, not just dropped
1197 1197
1198 1198 $ "$PYTHON" $TESTDIR/list-tree.py .hglf dir* large*
1199 1199 .hglf/
1200 1200 .hglf/dir/
1201 1201 .hglf/dir/subdir/
1202 1202 .hglf/dir/subdir/large.bin
1203 1203 .hglf/large
1204 1204 dir/
1205 1205 dir/subdir/
1206 1206 dir/subdir/large.bin
1207 1207 dir/subdir2/
1208 1208 dir/subdir2/large.bin
1209 1209 large
1210 1210 large.orig
1211 1211
1212 1212 $ rm -r dir/subdir2
1213 1213
1214 1214 'subdir' should not be in the destination. It would be if the subdir2 directory
1215 1215 existed under .hglf/.
1216 1216 $ hg mv dir/subdir dir/subdir2
1217 1217 moving .hglf/dir/subdir/large.bin to .hglf/dir/subdir2/large.bin
1218 1218
1219 1219 $ hg status -C
1220 1220 A dir/subdir2/large.bin
1221 1221 dir/subdir/large.bin
1222 1222 R dir/subdir/large.bin
1223 1223 ? large.orig
1224 1224
1225 1225 $ "$PYTHON" $TESTDIR/list-tree.py .hglf dir* large*
1226 1226 .hglf/
1227 1227 .hglf/dir/
1228 1228 .hglf/dir/subdir2/
1229 1229 .hglf/dir/subdir2/large.bin
1230 1230 .hglf/large
1231 1231 dir/
1232 1232 dir/subdir2/
1233 1233 dir/subdir2/large.bin
1234 1234 large
1235 1235 large.orig
1236 1236
1237 1237 Start from scratch, and rename something other than the final path component.
1238 1238
1239 1239 $ hg up -qC .
1240 1240 $ hg --config extensions.purge= purge
1241 1241
1242 1242 $ hg mv dir/subdir dir2/subdir
1243 1243 moving .hglf/dir/subdir/large.bin to .hglf/dir2/subdir/large.bin
1244 1244
1245 1245 $ hg status -C
1246 1246 A dir2/subdir/large.bin
1247 1247 dir/subdir/large.bin
1248 1248 R dir/subdir/large.bin
1249 1249
1250 1250 $ "$PYTHON" $TESTDIR/list-tree.py .hglf dir* large*
1251 1251 .hglf/
1252 1252 .hglf/dir2/
1253 1253 .hglf/dir2/subdir/
1254 1254 .hglf/dir2/subdir/large.bin
1255 1255 .hglf/large
1256 1256 dir2/
1257 1257 dir2/subdir/
1258 1258 dir2/subdir/large.bin
1259 1259 large
1260 1260
1261 1261 $ hg revert --all
1262 1262 forgetting .hglf/dir2/subdir/large.bin
1263 1263 undeleting .hglf/dir/subdir/large.bin
1264 1264 reverting subrepo no-largefiles
1265 1265
1266 1266 $ hg status -C
1267 1267 ? dir2/subdir/large.bin
1268 1268
1269 1269 $ "$PYTHON" $TESTDIR/list-tree.py .hglf dir* large*
1270 1270 .hglf/
1271 1271 .hglf/dir/
1272 1272 .hglf/dir/subdir/
1273 1273 .hglf/dir/subdir/large.bin
1274 1274 .hglf/large
1275 1275 dir/
1276 1276 dir/subdir/
1277 1277 dir/subdir/large.bin
1278 1278 dir2/
1279 1279 dir2/subdir/
1280 1280 dir2/subdir/large.bin
1281 1281 large
1282 1282
1283 1283 $ cd ../..
1284 1284
1285 1285 Test "pull --rebase" when rebase is enabled before largefiles (issue3861)
1286 1286 =========================================================================
1287 1287
1288 1288 $ hg showconfig extensions | grep largefiles
1289 1289 extensions.largefiles=!
1290 1290
1291 1291 $ mkdir issue3861
1292 1292 $ cd issue3861
1293 1293 $ hg init src
1294 1294 $ hg clone -q src dst
1295 1295 $ echo a > src/a
1296 1296 $ hg -R src commit -Aqm "#0"
1297 1297 Invoking status precommit hook
1298 1298 A a
1299 1299
1300 1300 $ cat >> dst/.hg/hgrc <<EOF
1301 1301 > [extensions]
1302 1302 > largefiles=
1303 1303 > EOF
1304 1304 $ hg -R dst pull --rebase
1305 1305 pulling from $TESTTMP/issue3861/src
1306 1306 requesting all changes
1307 1307 adding changesets
1308 1308 adding manifests
1309 1309 adding file changes
1310 1310 added 1 changesets with 1 changes to 1 files
1311 1311 new changesets bf5e395ced2c (1 drafts)
1312 1312 nothing to rebase - updating instead
1313 1313 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1314 1314
1315 1315 $ cd ..
@@ -1,1148 +1,1148 b''
1 1 $ hg init repo
2 2 $ cd repo
3 3 $ echo 123 > a
4 4 $ echo 123 > c
5 5 $ echo 123 > e
6 6 $ hg add a c e
7 7 $ hg commit -m "first" a c e
8 8
9 9 nothing changed
10 10
11 11 $ hg revert
12 12 abort: no files or directories specified
13 13 (use --all to revert all files)
14 14 [255]
15 15 $ hg revert --all
16 16
17 17 Introduce some changes and revert them
18 18 --------------------------------------
19 19
20 20 $ echo 123 > b
21 21
22 22 $ hg status
23 23 ? b
24 24 $ echo 12 > c
25 25
26 26 $ hg status
27 27 M c
28 28 ? b
29 29 $ hg add b
30 30
31 31 $ hg status
32 32 M c
33 33 A b
34 34 $ hg rm a
35 35
36 36 $ hg status
37 37 M c
38 38 A b
39 39 R a
40 40
41 41 revert removal of a file
42 42
43 43 $ hg revert a
44 44 $ hg status
45 45 M c
46 46 A b
47 47
48 48 revert addition of a file
49 49
50 50 $ hg revert b
51 51 $ hg status
52 52 M c
53 53 ? b
54 54
55 55 revert modification of a file (--no-backup)
56 56
57 57 $ hg revert --no-backup c
58 58 $ hg status
59 59 ? b
60 60
61 61 revert deletion (! status) of a added file
62 62 ------------------------------------------
63 63
64 64 $ hg add b
65 65
66 66 $ hg status b
67 67 A b
68 68 $ rm b
69 69 $ hg status b
70 70 ! b
71 71 $ hg revert -v b
72 72 forgetting b
73 73 $ hg status b
74 74 b: * (glob)
75 75
76 76 $ ls
77 77 a
78 78 c
79 79 e
80 80
81 81 Test creation of backup (.orig) files
82 82 -------------------------------------
83 83
84 84 $ echo z > e
85 85 $ hg revert --all -v
86 86 saving current version of e as e.orig
87 87 reverting e
88 88
89 89 Test creation of backup (.orig) file in configured file location
90 90 ----------------------------------------------------------------
91 91
92 92 $ echo z > e
93 93 $ hg revert --all -v --config 'ui.origbackuppath=.hg/origbackups'
94 94 creating directory: $TESTTMP/repo/.hg/origbackups
95 saving current version of e as $TESTTMP/repo/.hg/origbackups/e
95 saving current version of e as .hg/origbackups/e
96 96 reverting e
97 97 $ rm -rf .hg/origbackups
98 98
99 99 revert on clean file (no change)
100 100 --------------------------------
101 101
102 102 $ hg revert a
103 103 no changes needed to a
104 104
105 105 revert on an untracked file
106 106 ---------------------------
107 107
108 108 $ echo q > q
109 109 $ hg revert q
110 110 file not managed: q
111 111 $ rm q
112 112
113 113 revert on file that does not exists
114 114 -----------------------------------
115 115
116 116 $ hg revert notfound
117 117 notfound: no such file in rev 334a9e57682c
118 118 $ touch d
119 119 $ hg add d
120 120 $ hg rm a
121 121 $ hg commit -m "second"
122 122 $ echo z > z
123 123 $ hg add z
124 124 $ hg st
125 125 A z
126 126 ? e.orig
127 127
128 128 revert to another revision (--rev)
129 129 ----------------------------------
130 130
131 131 $ hg revert --all -r0
132 132 forgetting z
133 133 removing d
134 134 adding a
135 135
136 136 revert explicitly to parent (--rev)
137 137 -----------------------------------
138 138
139 139 $ hg revert --all -rtip
140 140 forgetting a
141 141 undeleting d
142 142 $ rm a *.orig
143 143
144 144 revert to another revision (--rev) and exact match
145 145 --------------------------------------------------
146 146
147 147 exact match are more silent
148 148
149 149 $ hg revert -r0 a
150 150 $ hg st a
151 151 A a
152 152 $ hg rm d
153 153 $ hg st d
154 154 R d
155 155
156 156 should keep d removed
157 157
158 158 $ hg revert -r0 d
159 159 no changes needed to d
160 160 $ hg st d
161 161 R d
162 162
163 163 $ hg update -C
164 164 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
165 165
166 166 revert of exec bit
167 167 ------------------
168 168
169 169 #if execbit
170 170 $ chmod +x c
171 171 $ hg revert --all
172 172 reverting c
173 173
174 174 $ test -x c || echo non-executable
175 175 non-executable
176 176
177 177 $ chmod +x c
178 178 $ hg commit -m exe
179 179
180 180 $ chmod -x c
181 181 $ hg revert --all
182 182 reverting c
183 183
184 184 $ test -x c && echo executable
185 185 executable
186 186 #endif
187 187
188 188 Test that files reverted to other than the parent are treated as
189 189 "modified", even if none of mode, size and timestamp of it isn't
190 190 changed on the filesystem (see also issue4583).
191 191
192 192 $ echo 321 > e
193 193 $ hg diff --git
194 194 diff --git a/e b/e
195 195 --- a/e
196 196 +++ b/e
197 197 @@ -1,1 +1,1 @@
198 198 -123
199 199 +321
200 200 $ hg commit -m 'ambiguity from size'
201 201
202 202 $ cat e
203 203 321
204 204 $ touch -t 200001010000 e
205 205 $ hg debugrebuildstate
206 206
207 207 $ cat >> .hg/hgrc <<EOF
208 208 > [fakedirstatewritetime]
209 209 > # emulate invoking dirstate.write() via repo.status()
210 210 > # at 2000-01-01 00:00
211 211 > fakenow = 200001010000
212 212 >
213 213 > [extensions]
214 214 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
215 215 > EOF
216 216 $ hg revert -r 0 e
217 217 $ cat >> .hg/hgrc <<EOF
218 218 > [extensions]
219 219 > fakedirstatewritetime = !
220 220 > EOF
221 221
222 222 $ cat e
223 223 123
224 224 $ touch -t 200001010000 e
225 225 $ hg status -A e
226 226 M e
227 227
228 228 $ cd ..
229 229
230 230
231 231 Issue241: update and revert produces inconsistent repositories
232 232 --------------------------------------------------------------
233 233
234 234 $ hg init a
235 235 $ cd a
236 236 $ echo a >> a
237 237 $ hg commit -A -d '1 0' -m a
238 238 adding a
239 239 $ echo a >> a
240 240 $ hg commit -d '2 0' -m a
241 241 $ hg update 0
242 242 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
243 243 $ mkdir b
244 244 $ echo b > b/b
245 245
246 246 call `hg revert` with no file specified
247 247 ---------------------------------------
248 248
249 249 $ hg revert -rtip
250 250 abort: no files or directories specified
251 251 (use --all to revert all files, or 'hg update 1' to update)
252 252 [255]
253 253
254 254 call `hg revert` with -I
255 255 ---------------------------
256 256
257 257 $ echo a >> a
258 258 $ hg revert -I a
259 259 reverting a
260 260
261 261 call `hg revert` with -X
262 262 ---------------------------
263 263
264 264 $ echo a >> a
265 265 $ hg revert -X d
266 266 reverting a
267 267
268 268 call `hg revert` with --all
269 269 ---------------------------
270 270
271 271 $ hg revert --all -rtip
272 272 reverting a
273 273 $ rm *.orig
274 274
275 275 Issue332: confusing message when reverting directory
276 276 ----------------------------------------------------
277 277
278 278 $ hg ci -A -m b
279 279 adding b/b
280 280 created new head
281 281 $ echo foobar > b/b
282 282 $ mkdir newdir
283 283 $ echo foo > newdir/newfile
284 284 $ hg add newdir/newfile
285 285 $ hg revert b newdir
286 286 forgetting newdir/newfile
287 287 reverting b/b
288 288 $ echo foobar > b/b
289 289 $ hg revert .
290 290 reverting b/b
291 291
292 292
293 293 reverting a rename target should revert the source
294 294 --------------------------------------------------
295 295
296 296 $ hg mv a newa
297 297 $ hg revert newa
298 298 $ hg st a newa
299 299 ? newa
300 300
301 301 Also true for move overwriting an existing file
302 302
303 303 $ hg mv --force a b/b
304 304 $ hg revert b/b
305 305 $ hg status a b/b
306 306
307 307 $ cd ..
308 308
309 309 $ hg init ignored
310 310 $ cd ignored
311 311 $ echo '^ignored$' > .hgignore
312 312 $ echo '^ignoreddir$' >> .hgignore
313 313 $ echo '^removed$' >> .hgignore
314 314
315 315 $ mkdir ignoreddir
316 316 $ touch ignoreddir/file
317 317 $ touch ignoreddir/removed
318 318 $ touch ignored
319 319 $ touch removed
320 320
321 321 4 ignored files (we will add/commit everything)
322 322
323 323 $ hg st -A -X .hgignore
324 324 I ignored
325 325 I ignoreddir/file
326 326 I ignoreddir/removed
327 327 I removed
328 328 $ hg ci -qAm 'add files' ignored ignoreddir/file ignoreddir/removed removed
329 329
330 330 $ echo >> ignored
331 331 $ echo >> ignoreddir/file
332 332 $ hg rm removed ignoreddir/removed
333 333
334 334 should revert ignored* and undelete *removed
335 335 --------------------------------------------
336 336
337 337 $ hg revert -a --no-backup
338 338 reverting ignored
339 339 reverting ignoreddir/file
340 340 undeleting ignoreddir/removed
341 341 undeleting removed
342 342 $ hg st -mardi
343 343
344 344 $ hg up -qC
345 345 $ echo >> ignored
346 346 $ hg rm removed
347 347
348 348 should silently revert the named files
349 349 --------------------------------------
350 350
351 351 $ hg revert --no-backup ignored removed
352 352 $ hg st -mardi
353 353
354 354 Reverting copy (issue3920)
355 355 --------------------------
356 356
357 357 someone set up us the copies
358 358
359 359 $ rm .hgignore
360 360 $ hg update -C
361 361 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
362 362 $ hg mv ignored allyour
363 363 $ hg copy removed base
364 364 $ hg commit -m rename
365 365
366 366 copies and renames, you have no chance to survive make your time (issue3920)
367 367
368 368 $ hg update '.^'
369 369 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
370 370 $ hg revert -rtip -a
371 371 removing ignored
372 372 adding allyour
373 373 adding base
374 374 $ hg status -C
375 375 A allyour
376 376 ignored
377 377 A base
378 378 removed
379 379 R ignored
380 380
381 381 Test revert of a file added by one side of the merge
382 382 ====================================================
383 383
384 384 remove any pending change
385 385
386 386 $ hg revert --all
387 387 forgetting allyour
388 388 forgetting base
389 389 undeleting ignored
390 390 $ hg purge --all --config extensions.purge=
391 391
392 392 Adds a new commit
393 393
394 394 $ echo foo > newadd
395 395 $ hg add newadd
396 396 $ hg commit -m 'other adds'
397 397 created new head
398 398
399 399
400 400 merge it with the other head
401 401
402 402 $ hg merge # merge 1 into 2
403 403 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
404 404 (branch merge, don't forget to commit)
405 405 $ hg summary
406 406 parent: 2:b8ec310b2d4e tip
407 407 other adds
408 408 parent: 1:f6180deb8fbe
409 409 rename
410 410 branch: default
411 411 commit: 2 modified, 1 removed (merge)
412 412 update: (current)
413 413 phases: 3 draft
414 414
415 415 clarifies who added what
416 416
417 417 $ hg status
418 418 M allyour
419 419 M base
420 420 R ignored
421 421 $ hg status --change 'p1()'
422 422 A newadd
423 423 $ hg status --change 'p2()'
424 424 A allyour
425 425 A base
426 426 R ignored
427 427
428 428 revert file added by p1() to p1() state
429 429 -----------------------------------------
430 430
431 431 $ hg revert -r 'p1()' 'glob:newad?'
432 432 $ hg status
433 433 M allyour
434 434 M base
435 435 R ignored
436 436
437 437 revert file added by p1() to p2() state
438 438 ------------------------------------------
439 439
440 440 $ hg revert -r 'p2()' 'glob:newad?'
441 441 removing newadd
442 442 $ hg status
443 443 M allyour
444 444 M base
445 445 R ignored
446 446 R newadd
447 447
448 448 revert file added by p2() to p2() state
449 449 ------------------------------------------
450 450
451 451 $ hg revert -r 'p2()' 'glob:allyou?'
452 452 $ hg status
453 453 M allyour
454 454 M base
455 455 R ignored
456 456 R newadd
457 457
458 458 revert file added by p2() to p1() state
459 459 ------------------------------------------
460 460
461 461 $ hg revert -r 'p1()' 'glob:allyou?'
462 462 removing allyour
463 463 $ hg status
464 464 M base
465 465 R allyour
466 466 R ignored
467 467 R newadd
468 468
469 469 Systematic behavior validation of most possible cases
470 470 =====================================================
471 471
472 472 This section tests most of the possible combinations of revision states and
473 473 working directory states. The number of possible cases is significant but they
474 474 but they all have a slightly different handling. So this section commits to
475 475 and testing all of them to allow safe refactoring of the revert code.
476 476
477 477 A python script is used to generate a file history for each combination of
478 478 states, on one side the content (or lack thereof) in two revisions, and
479 479 on the other side, the content and "tracked-ness" of the working directory. The
480 480 three states generated are:
481 481
482 482 - a "base" revision
483 483 - a "parent" revision
484 484 - the working directory (based on "parent")
485 485
486 486 The files generated have names of the form:
487 487
488 488 <rev1-content>_<rev2-content>_<working-copy-content>-<tracked-ness>
489 489
490 490 All known states are not tested yet. See inline documentation for details.
491 491 Special cases from merge and rename are not tested by this section.
492 492
493 493 Write the python script to disk
494 494 -------------------------------
495 495
496 496 check list of planned files
497 497
498 498 $ "$PYTHON" $TESTDIR/generate-working-copy-states.py filelist 2
499 499 content1_content1_content1-tracked
500 500 content1_content1_content1-untracked
501 501 content1_content1_content3-tracked
502 502 content1_content1_content3-untracked
503 503 content1_content1_missing-tracked
504 504 content1_content1_missing-untracked
505 505 content1_content2_content1-tracked
506 506 content1_content2_content1-untracked
507 507 content1_content2_content2-tracked
508 508 content1_content2_content2-untracked
509 509 content1_content2_content3-tracked
510 510 content1_content2_content3-untracked
511 511 content1_content2_missing-tracked
512 512 content1_content2_missing-untracked
513 513 content1_missing_content1-tracked
514 514 content1_missing_content1-untracked
515 515 content1_missing_content3-tracked
516 516 content1_missing_content3-untracked
517 517 content1_missing_missing-tracked
518 518 content1_missing_missing-untracked
519 519 missing_content2_content2-tracked
520 520 missing_content2_content2-untracked
521 521 missing_content2_content3-tracked
522 522 missing_content2_content3-untracked
523 523 missing_content2_missing-tracked
524 524 missing_content2_missing-untracked
525 525 missing_missing_content3-tracked
526 526 missing_missing_content3-untracked
527 527 missing_missing_missing-tracked
528 528 missing_missing_missing-untracked
529 529
530 530 Script to make a simple text version of the content
531 531 ---------------------------------------------------
532 532
533 533 $ cat << EOF >> dircontent.py
534 534 > # generate a simple text view of the directory for easy comparison
535 535 > from __future__ import print_function
536 536 > import os
537 537 > files = os.listdir('.')
538 538 > files.sort()
539 539 > for filename in files:
540 540 > if os.path.isdir(filename):
541 541 > continue
542 542 > content = open(filename).read()
543 543 > print('%-6s %s' % (content.strip(), filename))
544 544 > EOF
545 545
546 546 Generate appropriate repo state
547 547 -------------------------------
548 548
549 549 $ hg init revert-ref
550 550 $ cd revert-ref
551 551
552 552 Generate base changeset
553 553
554 554 $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 2 1
555 555 $ hg addremove --similarity 0
556 556 adding content1_content1_content1-tracked
557 557 adding content1_content1_content1-untracked
558 558 adding content1_content1_content3-tracked
559 559 adding content1_content1_content3-untracked
560 560 adding content1_content1_missing-tracked
561 561 adding content1_content1_missing-untracked
562 562 adding content1_content2_content1-tracked
563 563 adding content1_content2_content1-untracked
564 564 adding content1_content2_content2-tracked
565 565 adding content1_content2_content2-untracked
566 566 adding content1_content2_content3-tracked
567 567 adding content1_content2_content3-untracked
568 568 adding content1_content2_missing-tracked
569 569 adding content1_content2_missing-untracked
570 570 adding content1_missing_content1-tracked
571 571 adding content1_missing_content1-untracked
572 572 adding content1_missing_content3-tracked
573 573 adding content1_missing_content3-untracked
574 574 adding content1_missing_missing-tracked
575 575 adding content1_missing_missing-untracked
576 576 $ hg status
577 577 A content1_content1_content1-tracked
578 578 A content1_content1_content1-untracked
579 579 A content1_content1_content3-tracked
580 580 A content1_content1_content3-untracked
581 581 A content1_content1_missing-tracked
582 582 A content1_content1_missing-untracked
583 583 A content1_content2_content1-tracked
584 584 A content1_content2_content1-untracked
585 585 A content1_content2_content2-tracked
586 586 A content1_content2_content2-untracked
587 587 A content1_content2_content3-tracked
588 588 A content1_content2_content3-untracked
589 589 A content1_content2_missing-tracked
590 590 A content1_content2_missing-untracked
591 591 A content1_missing_content1-tracked
592 592 A content1_missing_content1-untracked
593 593 A content1_missing_content3-tracked
594 594 A content1_missing_content3-untracked
595 595 A content1_missing_missing-tracked
596 596 A content1_missing_missing-untracked
597 597 $ hg commit -m 'base'
598 598
599 599 (create a simple text version of the content)
600 600
601 601 $ "$PYTHON" ../dircontent.py > ../content-base.txt
602 602 $ cat ../content-base.txt
603 603 content1 content1_content1_content1-tracked
604 604 content1 content1_content1_content1-untracked
605 605 content1 content1_content1_content3-tracked
606 606 content1 content1_content1_content3-untracked
607 607 content1 content1_content1_missing-tracked
608 608 content1 content1_content1_missing-untracked
609 609 content1 content1_content2_content1-tracked
610 610 content1 content1_content2_content1-untracked
611 611 content1 content1_content2_content2-tracked
612 612 content1 content1_content2_content2-untracked
613 613 content1 content1_content2_content3-tracked
614 614 content1 content1_content2_content3-untracked
615 615 content1 content1_content2_missing-tracked
616 616 content1 content1_content2_missing-untracked
617 617 content1 content1_missing_content1-tracked
618 618 content1 content1_missing_content1-untracked
619 619 content1 content1_missing_content3-tracked
620 620 content1 content1_missing_content3-untracked
621 621 content1 content1_missing_missing-tracked
622 622 content1 content1_missing_missing-untracked
623 623
624 624 Create parent changeset
625 625
626 626 $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 2 2
627 627 $ hg addremove --similarity 0
628 628 removing content1_missing_content1-tracked
629 629 removing content1_missing_content1-untracked
630 630 removing content1_missing_content3-tracked
631 631 removing content1_missing_content3-untracked
632 632 removing content1_missing_missing-tracked
633 633 removing content1_missing_missing-untracked
634 634 adding missing_content2_content2-tracked
635 635 adding missing_content2_content2-untracked
636 636 adding missing_content2_content3-tracked
637 637 adding missing_content2_content3-untracked
638 638 adding missing_content2_missing-tracked
639 639 adding missing_content2_missing-untracked
640 640 $ hg status
641 641 M content1_content2_content1-tracked
642 642 M content1_content2_content1-untracked
643 643 M content1_content2_content2-tracked
644 644 M content1_content2_content2-untracked
645 645 M content1_content2_content3-tracked
646 646 M content1_content2_content3-untracked
647 647 M content1_content2_missing-tracked
648 648 M content1_content2_missing-untracked
649 649 A missing_content2_content2-tracked
650 650 A missing_content2_content2-untracked
651 651 A missing_content2_content3-tracked
652 652 A missing_content2_content3-untracked
653 653 A missing_content2_missing-tracked
654 654 A missing_content2_missing-untracked
655 655 R content1_missing_content1-tracked
656 656 R content1_missing_content1-untracked
657 657 R content1_missing_content3-tracked
658 658 R content1_missing_content3-untracked
659 659 R content1_missing_missing-tracked
660 660 R content1_missing_missing-untracked
661 661 $ hg commit -m 'parent'
662 662
663 663 (create a simple text version of the content)
664 664
665 665 $ "$PYTHON" ../dircontent.py > ../content-parent.txt
666 666 $ cat ../content-parent.txt
667 667 content1 content1_content1_content1-tracked
668 668 content1 content1_content1_content1-untracked
669 669 content1 content1_content1_content3-tracked
670 670 content1 content1_content1_content3-untracked
671 671 content1 content1_content1_missing-tracked
672 672 content1 content1_content1_missing-untracked
673 673 content2 content1_content2_content1-tracked
674 674 content2 content1_content2_content1-untracked
675 675 content2 content1_content2_content2-tracked
676 676 content2 content1_content2_content2-untracked
677 677 content2 content1_content2_content3-tracked
678 678 content2 content1_content2_content3-untracked
679 679 content2 content1_content2_missing-tracked
680 680 content2 content1_content2_missing-untracked
681 681 content2 missing_content2_content2-tracked
682 682 content2 missing_content2_content2-untracked
683 683 content2 missing_content2_content3-tracked
684 684 content2 missing_content2_content3-untracked
685 685 content2 missing_content2_missing-tracked
686 686 content2 missing_content2_missing-untracked
687 687
688 688 Setup working directory
689 689
690 690 $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 2 wc
691 691 $ hg addremove --similarity 0
692 692 adding content1_missing_content1-tracked
693 693 adding content1_missing_content1-untracked
694 694 adding content1_missing_content3-tracked
695 695 adding content1_missing_content3-untracked
696 696 adding content1_missing_missing-tracked
697 697 adding content1_missing_missing-untracked
698 698 adding missing_missing_content3-tracked
699 699 adding missing_missing_content3-untracked
700 700 adding missing_missing_missing-tracked
701 701 adding missing_missing_missing-untracked
702 702 $ hg forget *_*_*-untracked
703 703 $ rm *_*_missing-*
704 704 $ hg status
705 705 M content1_content1_content3-tracked
706 706 M content1_content2_content1-tracked
707 707 M content1_content2_content3-tracked
708 708 M missing_content2_content3-tracked
709 709 A content1_missing_content1-tracked
710 710 A content1_missing_content3-tracked
711 711 A missing_missing_content3-tracked
712 712 R content1_content1_content1-untracked
713 713 R content1_content1_content3-untracked
714 714 R content1_content1_missing-untracked
715 715 R content1_content2_content1-untracked
716 716 R content1_content2_content2-untracked
717 717 R content1_content2_content3-untracked
718 718 R content1_content2_missing-untracked
719 719 R missing_content2_content2-untracked
720 720 R missing_content2_content3-untracked
721 721 R missing_content2_missing-untracked
722 722 ! content1_content1_missing-tracked
723 723 ! content1_content2_missing-tracked
724 724 ! content1_missing_missing-tracked
725 725 ! missing_content2_missing-tracked
726 726 ! missing_missing_missing-tracked
727 727 ? content1_missing_content1-untracked
728 728 ? content1_missing_content3-untracked
729 729 ? missing_missing_content3-untracked
730 730
731 731 $ hg status --rev 'desc("base")'
732 732 M content1_content1_content3-tracked
733 733 M content1_content2_content2-tracked
734 734 M content1_content2_content3-tracked
735 735 M content1_missing_content3-tracked
736 736 A missing_content2_content2-tracked
737 737 A missing_content2_content3-tracked
738 738 A missing_missing_content3-tracked
739 739 R content1_content1_content1-untracked
740 740 R content1_content1_content3-untracked
741 741 R content1_content1_missing-untracked
742 742 R content1_content2_content1-untracked
743 743 R content1_content2_content2-untracked
744 744 R content1_content2_content3-untracked
745 745 R content1_content2_missing-untracked
746 746 R content1_missing_content1-untracked
747 747 R content1_missing_content3-untracked
748 748 R content1_missing_missing-untracked
749 749 ! content1_content1_missing-tracked
750 750 ! content1_content2_missing-tracked
751 751 ! content1_missing_missing-tracked
752 752 ! missing_content2_missing-tracked
753 753 ! missing_missing_missing-tracked
754 754 ? missing_missing_content3-untracked
755 755
756 756 (create a simple text version of the content)
757 757
758 758 $ "$PYTHON" ../dircontent.py > ../content-wc.txt
759 759 $ cat ../content-wc.txt
760 760 content1 content1_content1_content1-tracked
761 761 content1 content1_content1_content1-untracked
762 762 content3 content1_content1_content3-tracked
763 763 content3 content1_content1_content3-untracked
764 764 content1 content1_content2_content1-tracked
765 765 content1 content1_content2_content1-untracked
766 766 content2 content1_content2_content2-tracked
767 767 content2 content1_content2_content2-untracked
768 768 content3 content1_content2_content3-tracked
769 769 content3 content1_content2_content3-untracked
770 770 content1 content1_missing_content1-tracked
771 771 content1 content1_missing_content1-untracked
772 772 content3 content1_missing_content3-tracked
773 773 content3 content1_missing_content3-untracked
774 774 content2 missing_content2_content2-tracked
775 775 content2 missing_content2_content2-untracked
776 776 content3 missing_content2_content3-tracked
777 777 content3 missing_content2_content3-untracked
778 778 content3 missing_missing_content3-tracked
779 779 content3 missing_missing_content3-untracked
780 780
781 781 $ cd ..
782 782
783 783 Test revert --all to parent content
784 784 -----------------------------------
785 785
786 786 (setup from reference repo)
787 787
788 788 $ cp -R revert-ref revert-parent-all
789 789 $ cd revert-parent-all
790 790
791 791 check revert output
792 792
793 793 $ hg revert --all
794 794 forgetting content1_missing_content1-tracked
795 795 forgetting content1_missing_content3-tracked
796 796 forgetting content1_missing_missing-tracked
797 797 forgetting missing_missing_content3-tracked
798 798 forgetting missing_missing_missing-tracked
799 799 reverting content1_content1_content3-tracked
800 800 reverting content1_content1_missing-tracked
801 801 reverting content1_content2_content1-tracked
802 802 reverting content1_content2_content3-tracked
803 803 reverting content1_content2_missing-tracked
804 804 reverting missing_content2_content3-tracked
805 805 reverting missing_content2_missing-tracked
806 806 undeleting content1_content1_content1-untracked
807 807 undeleting content1_content1_content3-untracked
808 808 undeleting content1_content1_missing-untracked
809 809 undeleting content1_content2_content1-untracked
810 810 undeleting content1_content2_content2-untracked
811 811 undeleting content1_content2_content3-untracked
812 812 undeleting content1_content2_missing-untracked
813 813 undeleting missing_content2_content2-untracked
814 814 undeleting missing_content2_content3-untracked
815 815 undeleting missing_content2_missing-untracked
816 816
817 817 Compare resulting directory with revert target.
818 818
819 819 The diff is filtered to include change only. The only difference should be
820 820 additional `.orig` backup file when applicable.
821 821
822 822 $ "$PYTHON" ../dircontent.py > ../content-parent-all.txt
823 823 $ cd ..
824 824 $ diff -U 0 -- content-parent.txt content-parent-all.txt | grep _
825 825 +content3 content1_content1_content3-tracked.orig
826 826 +content3 content1_content1_content3-untracked.orig
827 827 +content1 content1_content2_content1-tracked.orig
828 828 +content1 content1_content2_content1-untracked.orig
829 829 +content3 content1_content2_content3-tracked.orig
830 830 +content3 content1_content2_content3-untracked.orig
831 831 +content1 content1_missing_content1-tracked
832 832 +content1 content1_missing_content1-untracked
833 833 +content3 content1_missing_content3-tracked
834 834 +content3 content1_missing_content3-untracked
835 835 +content3 missing_content2_content3-tracked.orig
836 836 +content3 missing_content2_content3-untracked.orig
837 837 +content3 missing_missing_content3-tracked
838 838 +content3 missing_missing_content3-untracked
839 839
840 840 Test revert --all to "base" content
841 841 -----------------------------------
842 842
843 843 (setup from reference repo)
844 844
845 845 $ cp -R revert-ref revert-base-all
846 846 $ cd revert-base-all
847 847
848 848 check revert output
849 849
850 850 $ hg revert --all --rev 'desc(base)'
851 851 forgetting missing_missing_content3-tracked
852 852 forgetting missing_missing_missing-tracked
853 853 removing missing_content2_content2-tracked
854 854 removing missing_content2_content3-tracked
855 855 removing missing_content2_missing-tracked
856 856 reverting content1_content1_content3-tracked
857 857 reverting content1_content1_missing-tracked
858 858 reverting content1_content2_content2-tracked
859 859 reverting content1_content2_content3-tracked
860 860 reverting content1_content2_missing-tracked
861 861 reverting content1_missing_content3-tracked
862 862 reverting content1_missing_missing-tracked
863 863 adding content1_missing_content1-untracked
864 864 adding content1_missing_content3-untracked
865 865 adding content1_missing_missing-untracked
866 866 undeleting content1_content1_content1-untracked
867 867 undeleting content1_content1_content3-untracked
868 868 undeleting content1_content1_missing-untracked
869 869 undeleting content1_content2_content1-untracked
870 870 undeleting content1_content2_content2-untracked
871 871 undeleting content1_content2_content3-untracked
872 872 undeleting content1_content2_missing-untracked
873 873
874 874 Compare resulting directory with revert target.
875 875
876 876 The diff is filtered to include change only. The only difference should be
877 877 additional `.orig` backup file when applicable.
878 878
879 879 $ "$PYTHON" ../dircontent.py > ../content-base-all.txt
880 880 $ cd ..
881 881 $ diff -U 0 -- content-base.txt content-base-all.txt | grep _
882 882 +content3 content1_content1_content3-tracked.orig
883 883 +content3 content1_content1_content3-untracked.orig
884 884 +content2 content1_content2_content2-untracked.orig
885 885 +content3 content1_content2_content3-tracked.orig
886 886 +content3 content1_content2_content3-untracked.orig
887 887 +content3 content1_missing_content3-tracked.orig
888 888 +content3 content1_missing_content3-untracked.orig
889 889 +content2 missing_content2_content2-untracked
890 890 +content3 missing_content2_content3-tracked.orig
891 891 +content3 missing_content2_content3-untracked
892 892 +content3 missing_missing_content3-tracked
893 893 +content3 missing_missing_content3-untracked
894 894
895 895 Test revert to parent content with explicit file name
896 896 -----------------------------------------------------
897 897
898 898 (setup from reference repo)
899 899
900 900 $ cp -R revert-ref revert-parent-explicit
901 901 $ cd revert-parent-explicit
902 902
903 903 revert all files individually and check the output
904 904 (output is expected to be different than in the --all case)
905 905
906 906 $ for file in `"$PYTHON" $TESTDIR/generate-working-copy-states.py filelist 2`; do
907 907 > echo '### revert for:' $file;
908 908 > hg revert $file;
909 909 > echo
910 910 > done
911 911 ### revert for: content1_content1_content1-tracked
912 912 no changes needed to content1_content1_content1-tracked
913 913
914 914 ### revert for: content1_content1_content1-untracked
915 915
916 916 ### revert for: content1_content1_content3-tracked
917 917
918 918 ### revert for: content1_content1_content3-untracked
919 919
920 920 ### revert for: content1_content1_missing-tracked
921 921
922 922 ### revert for: content1_content1_missing-untracked
923 923
924 924 ### revert for: content1_content2_content1-tracked
925 925
926 926 ### revert for: content1_content2_content1-untracked
927 927
928 928 ### revert for: content1_content2_content2-tracked
929 929 no changes needed to content1_content2_content2-tracked
930 930
931 931 ### revert for: content1_content2_content2-untracked
932 932
933 933 ### revert for: content1_content2_content3-tracked
934 934
935 935 ### revert for: content1_content2_content3-untracked
936 936
937 937 ### revert for: content1_content2_missing-tracked
938 938
939 939 ### revert for: content1_content2_missing-untracked
940 940
941 941 ### revert for: content1_missing_content1-tracked
942 942
943 943 ### revert for: content1_missing_content1-untracked
944 944 file not managed: content1_missing_content1-untracked
945 945
946 946 ### revert for: content1_missing_content3-tracked
947 947
948 948 ### revert for: content1_missing_content3-untracked
949 949 file not managed: content1_missing_content3-untracked
950 950
951 951 ### revert for: content1_missing_missing-tracked
952 952
953 953 ### revert for: content1_missing_missing-untracked
954 954 content1_missing_missing-untracked: no such file in rev * (glob)
955 955
956 956 ### revert for: missing_content2_content2-tracked
957 957 no changes needed to missing_content2_content2-tracked
958 958
959 959 ### revert for: missing_content2_content2-untracked
960 960
961 961 ### revert for: missing_content2_content3-tracked
962 962
963 963 ### revert for: missing_content2_content3-untracked
964 964
965 965 ### revert for: missing_content2_missing-tracked
966 966
967 967 ### revert for: missing_content2_missing-untracked
968 968
969 969 ### revert for: missing_missing_content3-tracked
970 970
971 971 ### revert for: missing_missing_content3-untracked
972 972 file not managed: missing_missing_content3-untracked
973 973
974 974 ### revert for: missing_missing_missing-tracked
975 975
976 976 ### revert for: missing_missing_missing-untracked
977 977 missing_missing_missing-untracked: no such file in rev * (glob)
978 978
979 979
980 980 check resulting directory against the --all run
981 981 (There should be no difference)
982 982
983 983 $ "$PYTHON" ../dircontent.py > ../content-parent-explicit.txt
984 984 $ cd ..
985 985 $ diff -U 0 -- content-parent-all.txt content-parent-explicit.txt | grep _
986 986 [1]
987 987
988 988 Test revert to "base" content with explicit file name
989 989 -----------------------------------------------------
990 990
991 991 (setup from reference repo)
992 992
993 993 $ cp -R revert-ref revert-base-explicit
994 994 $ cd revert-base-explicit
995 995
996 996 revert all files individually and check the output
997 997 (output is expected to be different than in the --all case)
998 998
999 999 $ for file in `"$PYTHON" $TESTDIR/generate-working-copy-states.py filelist 2`; do
1000 1000 > echo '### revert for:' $file;
1001 1001 > hg revert $file --rev 'desc(base)';
1002 1002 > echo
1003 1003 > done
1004 1004 ### revert for: content1_content1_content1-tracked
1005 1005 no changes needed to content1_content1_content1-tracked
1006 1006
1007 1007 ### revert for: content1_content1_content1-untracked
1008 1008
1009 1009 ### revert for: content1_content1_content3-tracked
1010 1010
1011 1011 ### revert for: content1_content1_content3-untracked
1012 1012
1013 1013 ### revert for: content1_content1_missing-tracked
1014 1014
1015 1015 ### revert for: content1_content1_missing-untracked
1016 1016
1017 1017 ### revert for: content1_content2_content1-tracked
1018 1018 no changes needed to content1_content2_content1-tracked
1019 1019
1020 1020 ### revert for: content1_content2_content1-untracked
1021 1021
1022 1022 ### revert for: content1_content2_content2-tracked
1023 1023
1024 1024 ### revert for: content1_content2_content2-untracked
1025 1025
1026 1026 ### revert for: content1_content2_content3-tracked
1027 1027
1028 1028 ### revert for: content1_content2_content3-untracked
1029 1029
1030 1030 ### revert for: content1_content2_missing-tracked
1031 1031
1032 1032 ### revert for: content1_content2_missing-untracked
1033 1033
1034 1034 ### revert for: content1_missing_content1-tracked
1035 1035 no changes needed to content1_missing_content1-tracked
1036 1036
1037 1037 ### revert for: content1_missing_content1-untracked
1038 1038
1039 1039 ### revert for: content1_missing_content3-tracked
1040 1040
1041 1041 ### revert for: content1_missing_content3-untracked
1042 1042
1043 1043 ### revert for: content1_missing_missing-tracked
1044 1044
1045 1045 ### revert for: content1_missing_missing-untracked
1046 1046
1047 1047 ### revert for: missing_content2_content2-tracked
1048 1048
1049 1049 ### revert for: missing_content2_content2-untracked
1050 1050 no changes needed to missing_content2_content2-untracked
1051 1051
1052 1052 ### revert for: missing_content2_content3-tracked
1053 1053
1054 1054 ### revert for: missing_content2_content3-untracked
1055 1055 no changes needed to missing_content2_content3-untracked
1056 1056
1057 1057 ### revert for: missing_content2_missing-tracked
1058 1058
1059 1059 ### revert for: missing_content2_missing-untracked
1060 1060 no changes needed to missing_content2_missing-untracked
1061 1061
1062 1062 ### revert for: missing_missing_content3-tracked
1063 1063
1064 1064 ### revert for: missing_missing_content3-untracked
1065 1065 file not managed: missing_missing_content3-untracked
1066 1066
1067 1067 ### revert for: missing_missing_missing-tracked
1068 1068
1069 1069 ### revert for: missing_missing_missing-untracked
1070 1070 missing_missing_missing-untracked: no such file in rev * (glob)
1071 1071
1072 1072
1073 1073 check resulting directory against the --all run
1074 1074 (There should be no difference)
1075 1075
1076 1076 $ "$PYTHON" ../dircontent.py > ../content-base-explicit.txt
1077 1077 $ cd ..
1078 1078 $ diff -U 0 -- content-base-all.txt content-base-explicit.txt | grep _
1079 1079 [1]
1080 1080
1081 1081 Revert to an ancestor of P2 during a merge (issue5052)
1082 1082 -----------------------------------------------------
1083 1083
1084 1084 (prepare the repository)
1085 1085
1086 1086 $ hg init issue5052
1087 1087 $ cd issue5052
1088 1088 $ echo '.\.orig' > .hgignore
1089 1089 $ echo 0 > root
1090 1090 $ hg ci -qAm C0
1091 1091 $ echo 0 > A
1092 1092 $ hg ci -qAm C1
1093 1093 $ echo 1 >> A
1094 1094 $ hg ci -qm C2
1095 1095 $ hg up -q 0
1096 1096 $ echo 1 > B
1097 1097 $ hg ci -qAm C3
1098 1098 $ hg status --rev 'ancestor(.,2)' --rev 2
1099 1099 A A
1100 1100 $ hg log -G -T '{rev} ({files})\n'
1101 1101 @ 3 (B)
1102 1102 |
1103 1103 | o 2 (A)
1104 1104 | |
1105 1105 | o 1 (A)
1106 1106 |/
1107 1107 o 0 (.hgignore root)
1108 1108
1109 1109
1110 1110 actual tests: reverting to something else than a merge parent
1111 1111
1112 1112 $ hg merge
1113 1113 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1114 1114 (branch merge, don't forget to commit)
1115 1115
1116 1116 $ hg status --rev 'p1()'
1117 1117 M A
1118 1118 $ hg status --rev 'p2()'
1119 1119 A B
1120 1120 $ hg status --rev '1'
1121 1121 M A
1122 1122 A B
1123 1123 $ hg revert --rev 1 --all
1124 1124 removing B
1125 1125 reverting A
1126 1126 $ hg status --rev 1
1127 1127
1128 1128 From the other parents
1129 1129
1130 1130 $ hg up -C 'p2()'
1131 1131 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1132 1132 $ hg merge
1133 1133 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1134 1134 (branch merge, don't forget to commit)
1135 1135
1136 1136 $ hg status --rev 'p1()'
1137 1137 M B
1138 1138 $ hg status --rev 'p2()'
1139 1139 A A
1140 1140 $ hg status --rev '1'
1141 1141 M A
1142 1142 A B
1143 1143 $ hg revert --rev 1 --all
1144 1144 removing B
1145 1145 reverting A
1146 1146 $ hg status --rev 1
1147 1147
1148 1148 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now