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