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