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