##// END OF EJS Templates
cmdutil: fix newandmodified file accounting for --interactive commits...
Daniel Ploch -
r48365:00ae1fb6 default
parent child Browse files
Show More
@@ -1,3932 +1,3925
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Olivia Mackall <olivia@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 copy as copymod
11 11 import errno
12 12 import os
13 13 import re
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 hex,
18 18 nullrev,
19 19 short,
20 20 )
21 21 from .pycompat import (
22 22 getattr,
23 23 open,
24 24 setattr,
25 25 )
26 26 from .thirdparty import attr
27 27
28 28 from . import (
29 29 bookmarks,
30 30 changelog,
31 31 copies,
32 32 crecord as crecordmod,
33 33 dirstateguard,
34 34 encoding,
35 35 error,
36 36 formatter,
37 37 logcmdutil,
38 38 match as matchmod,
39 39 merge as mergemod,
40 40 mergestate as mergestatemod,
41 41 mergeutil,
42 42 obsolete,
43 43 patch,
44 44 pathutil,
45 45 phases,
46 46 pycompat,
47 47 repair,
48 48 revlog,
49 49 rewriteutil,
50 50 scmutil,
51 51 state as statemod,
52 52 subrepoutil,
53 53 templatekw,
54 54 templater,
55 55 util,
56 56 vfs as vfsmod,
57 57 )
58 58
59 59 from .utils import (
60 60 dateutil,
61 61 stringutil,
62 62 )
63 63
64 64 from .revlogutils import (
65 65 constants as revlog_constants,
66 66 )
67 67
68 68 if pycompat.TYPE_CHECKING:
69 69 from typing import (
70 70 Any,
71 71 Dict,
72 72 )
73 73
74 74 for t in (Any, Dict):
75 75 assert t
76 76
77 77 stringio = util.stringio
78 78
79 79 # templates of common command options
80 80
81 81 dryrunopts = [
82 82 (b'n', b'dry-run', None, _(b'do not perform actions, just print output')),
83 83 ]
84 84
85 85 confirmopts = [
86 86 (b'', b'confirm', None, _(b'ask before applying actions')),
87 87 ]
88 88
89 89 remoteopts = [
90 90 (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')),
91 91 (
92 92 b'',
93 93 b'remotecmd',
94 94 b'',
95 95 _(b'specify hg command to run on the remote side'),
96 96 _(b'CMD'),
97 97 ),
98 98 (
99 99 b'',
100 100 b'insecure',
101 101 None,
102 102 _(b'do not verify server certificate (ignoring web.cacerts config)'),
103 103 ),
104 104 ]
105 105
106 106 walkopts = [
107 107 (
108 108 b'I',
109 109 b'include',
110 110 [],
111 111 _(b'include names matching the given patterns'),
112 112 _(b'PATTERN'),
113 113 ),
114 114 (
115 115 b'X',
116 116 b'exclude',
117 117 [],
118 118 _(b'exclude names matching the given patterns'),
119 119 _(b'PATTERN'),
120 120 ),
121 121 ]
122 122
123 123 commitopts = [
124 124 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
125 125 (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')),
126 126 ]
127 127
128 128 commitopts2 = [
129 129 (
130 130 b'd',
131 131 b'date',
132 132 b'',
133 133 _(b'record the specified date as commit date'),
134 134 _(b'DATE'),
135 135 ),
136 136 (
137 137 b'u',
138 138 b'user',
139 139 b'',
140 140 _(b'record the specified user as committer'),
141 141 _(b'USER'),
142 142 ),
143 143 ]
144 144
145 145 commitopts3 = [
146 146 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
147 147 (b'U', b'currentuser', None, _(b'record the current user as committer')),
148 148 ]
149 149
150 150 formatteropts = [
151 151 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
152 152 ]
153 153
154 154 templateopts = [
155 155 (
156 156 b'',
157 157 b'style',
158 158 b'',
159 159 _(b'display using template map file (DEPRECATED)'),
160 160 _(b'STYLE'),
161 161 ),
162 162 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
163 163 ]
164 164
165 165 logopts = [
166 166 (b'p', b'patch', None, _(b'show patch')),
167 167 (b'g', b'git', None, _(b'use git extended diff format')),
168 168 (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')),
169 169 (b'M', b'no-merges', None, _(b'do not show merges')),
170 170 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
171 171 (b'G', b'graph', None, _(b"show the revision DAG")),
172 172 ] + templateopts
173 173
174 174 diffopts = [
175 175 (b'a', b'text', None, _(b'treat all files as text')),
176 176 (
177 177 b'g',
178 178 b'git',
179 179 None,
180 180 _(b'use git extended diff format (DEFAULT: diff.git)'),
181 181 ),
182 182 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
183 183 (b'', b'nodates', None, _(b'omit dates from diff headers')),
184 184 ]
185 185
186 186 diffwsopts = [
187 187 (
188 188 b'w',
189 189 b'ignore-all-space',
190 190 None,
191 191 _(b'ignore white space when comparing lines'),
192 192 ),
193 193 (
194 194 b'b',
195 195 b'ignore-space-change',
196 196 None,
197 197 _(b'ignore changes in the amount of white space'),
198 198 ),
199 199 (
200 200 b'B',
201 201 b'ignore-blank-lines',
202 202 None,
203 203 _(b'ignore changes whose lines are all blank'),
204 204 ),
205 205 (
206 206 b'Z',
207 207 b'ignore-space-at-eol',
208 208 None,
209 209 _(b'ignore changes in whitespace at EOL'),
210 210 ),
211 211 ]
212 212
213 213 diffopts2 = (
214 214 [
215 215 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
216 216 (
217 217 b'p',
218 218 b'show-function',
219 219 None,
220 220 _(
221 221 b'show which function each change is in (DEFAULT: diff.showfunc)'
222 222 ),
223 223 ),
224 224 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
225 225 ]
226 226 + diffwsopts
227 227 + [
228 228 (
229 229 b'U',
230 230 b'unified',
231 231 b'',
232 232 _(b'number of lines of context to show'),
233 233 _(b'NUM'),
234 234 ),
235 235 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
236 236 (
237 237 b'',
238 238 b'root',
239 239 b'',
240 240 _(b'produce diffs relative to subdirectory'),
241 241 _(b'DIR'),
242 242 ),
243 243 ]
244 244 )
245 245
246 246 mergetoolopts = [
247 247 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
248 248 ]
249 249
250 250 similarityopts = [
251 251 (
252 252 b's',
253 253 b'similarity',
254 254 b'',
255 255 _(b'guess renamed files by similarity (0<=s<=100)'),
256 256 _(b'SIMILARITY'),
257 257 )
258 258 ]
259 259
260 260 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
261 261
262 262 debugrevlogopts = [
263 263 (b'c', b'changelog', False, _(b'open changelog')),
264 264 (b'm', b'manifest', False, _(b'open manifest')),
265 265 (b'', b'dir', b'', _(b'open directory manifest')),
266 266 ]
267 267
268 268 # special string such that everything below this line will be ingored in the
269 269 # editor text
270 270 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
271 271
272 272
273 273 def check_at_most_one_arg(opts, *args):
274 274 """abort if more than one of the arguments are in opts
275 275
276 276 Returns the unique argument or None if none of them were specified.
277 277 """
278 278
279 279 def to_display(name):
280 280 return pycompat.sysbytes(name).replace(b'_', b'-')
281 281
282 282 previous = None
283 283 for x in args:
284 284 if opts.get(x):
285 285 if previous:
286 286 raise error.InputError(
287 287 _(b'cannot specify both --%s and --%s')
288 288 % (to_display(previous), to_display(x))
289 289 )
290 290 previous = x
291 291 return previous
292 292
293 293
294 294 def check_incompatible_arguments(opts, first, others):
295 295 """abort if the first argument is given along with any of the others
296 296
297 297 Unlike check_at_most_one_arg(), `others` are not mutually exclusive
298 298 among themselves, and they're passed as a single collection.
299 299 """
300 300 for other in others:
301 301 check_at_most_one_arg(opts, first, other)
302 302
303 303
304 304 def resolve_commit_options(ui, opts):
305 305 """modify commit options dict to handle related options
306 306
307 307 The return value indicates that ``rewrite.update-timestamp`` is the reason
308 308 the ``date`` option is set.
309 309 """
310 310 check_at_most_one_arg(opts, 'date', 'currentdate')
311 311 check_at_most_one_arg(opts, 'user', 'currentuser')
312 312
313 313 datemaydiffer = False # date-only change should be ignored?
314 314
315 315 if opts.get('currentdate'):
316 316 opts['date'] = b'%d %d' % dateutil.makedate()
317 317 elif (
318 318 not opts.get('date')
319 319 and ui.configbool(b'rewrite', b'update-timestamp')
320 320 and opts.get('currentdate') is None
321 321 ):
322 322 opts['date'] = b'%d %d' % dateutil.makedate()
323 323 datemaydiffer = True
324 324
325 325 if opts.get('currentuser'):
326 326 opts['user'] = ui.username()
327 327
328 328 return datemaydiffer
329 329
330 330
331 331 def check_note_size(opts):
332 332 """make sure note is of valid format"""
333 333
334 334 note = opts.get('note')
335 335 if not note:
336 336 return
337 337
338 338 if len(note) > 255:
339 339 raise error.InputError(_(b"cannot store a note of more than 255 bytes"))
340 340 if b'\n' in note:
341 341 raise error.InputError(_(b"note cannot contain a newline"))
342 342
343 343
344 344 def ishunk(x):
345 345 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
346 346 return isinstance(x, hunkclasses)
347 347
348 348
349 def newandmodified(chunks, originalchunks):
349 def isheader(x):
350 headerclasses = (crecordmod.uiheader, patch.header)
351 return isinstance(x, headerclasses)
352
353
354 def newandmodified(chunks):
350 355 newlyaddedandmodifiedfiles = set()
351 356 alsorestore = set()
352 357 for chunk in chunks:
353 if (
354 ishunk(chunk)
355 and chunk.header.isnewfile()
356 and chunk not in originalchunks
357 ):
358 newlyaddedandmodifiedfiles.add(chunk.header.filename())
359 alsorestore.update(
360 set(chunk.header.files()) - {chunk.header.filename()}
361 )
358 if isheader(chunk) and chunk.isnewfile():
359 newlyaddedandmodifiedfiles.add(chunk.filename())
360 alsorestore.update(set(chunk.files()) - {chunk.filename()})
362 361 return newlyaddedandmodifiedfiles, alsorestore
363 362
364 363
365 364 def parsealiases(cmd):
366 365 base_aliases = cmd.split(b"|")
367 366 all_aliases = set(base_aliases)
368 367 extra_aliases = []
369 368 for alias in base_aliases:
370 369 if b'-' in alias:
371 370 folded_alias = alias.replace(b'-', b'')
372 371 if folded_alias not in all_aliases:
373 372 all_aliases.add(folded_alias)
374 373 extra_aliases.append(folded_alias)
375 374 base_aliases.extend(extra_aliases)
376 375 return base_aliases
377 376
378 377
379 378 def setupwrapcolorwrite(ui):
380 379 # wrap ui.write so diff output can be labeled/colorized
381 380 def wrapwrite(orig, *args, **kw):
382 381 label = kw.pop('label', b'')
383 382 for chunk, l in patch.difflabel(lambda: args):
384 383 orig(chunk, label=label + l)
385 384
386 385 oldwrite = ui.write
387 386
388 387 def wrap(*args, **kwargs):
389 388 return wrapwrite(oldwrite, *args, **kwargs)
390 389
391 390 setattr(ui, 'write', wrap)
392 391 return oldwrite
393 392
394 393
395 394 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
396 395 try:
397 396 if usecurses:
398 397 if testfile:
399 398 recordfn = crecordmod.testdecorator(
400 399 testfile, crecordmod.testchunkselector
401 400 )
402 401 else:
403 402 recordfn = crecordmod.chunkselector
404 403
405 404 return crecordmod.filterpatch(
406 405 ui, originalhunks, recordfn, operation
407 406 )
408 407 except crecordmod.fallbackerror as e:
409 408 ui.warn(b'%s\n' % e)
410 409 ui.warn(_(b'falling back to text mode\n'))
411 410
412 411 return patch.filterpatch(ui, originalhunks, match, operation)
413 412
414 413
415 414 def recordfilter(ui, originalhunks, match, operation=None):
416 415 """Prompts the user to filter the originalhunks and return a list of
417 416 selected hunks.
418 417 *operation* is used for to build ui messages to indicate the user what
419 418 kind of filtering they are doing: reverting, committing, shelving, etc.
420 419 (see patch.filterpatch).
421 420 """
422 421 usecurses = crecordmod.checkcurses(ui)
423 422 testfile = ui.config(b'experimental', b'crecordtest')
424 423 oldwrite = setupwrapcolorwrite(ui)
425 424 try:
426 425 newchunks, newopts = filterchunks(
427 426 ui, originalhunks, usecurses, testfile, match, operation
428 427 )
429 428 finally:
430 429 ui.write = oldwrite
431 430 return newchunks, newopts
432 431
433 432
434 433 def dorecord(
435 434 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
436 435 ):
437 436 opts = pycompat.byteskwargs(opts)
438 437 if not ui.interactive():
439 438 if cmdsuggest:
440 439 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
441 440 else:
442 441 msg = _(b'running non-interactively')
443 442 raise error.InputError(msg)
444 443
445 444 # make sure username is set before going interactive
446 445 if not opts.get(b'user'):
447 446 ui.username() # raise exception, username not provided
448 447
449 448 def recordfunc(ui, repo, message, match, opts):
450 449 """This is generic record driver.
451 450
452 451 Its job is to interactively filter local changes, and
453 452 accordingly prepare working directory into a state in which the
454 453 job can be delegated to a non-interactive commit command such as
455 454 'commit' or 'qrefresh'.
456 455
457 456 After the actual job is done by non-interactive command, the
458 457 working directory is restored to its original state.
459 458
460 459 In the end we'll record interesting changes, and everything else
461 460 will be left in place, so the user can continue working.
462 461 """
463 462 if not opts.get(b'interactive-unshelve'):
464 463 checkunfinished(repo, commit=True)
465 464 wctx = repo[None]
466 465 merge = len(wctx.parents()) > 1
467 466 if merge:
468 467 raise error.InputError(
469 468 _(
470 469 b'cannot partially commit a merge '
471 470 b'(use "hg commit" instead)'
472 471 )
473 472 )
474 473
475 474 def fail(f, msg):
476 475 raise error.InputError(b'%s: %s' % (f, msg))
477 476
478 477 force = opts.get(b'force')
479 478 if not force:
480 479 match = matchmod.badmatch(match, fail)
481 480
482 481 status = repo.status(match=match)
483 482
484 483 overrides = {(b'ui', b'commitsubrepos'): True}
485 484
486 485 with repo.ui.configoverride(overrides, b'record'):
487 486 # subrepoutil.precommit() modifies the status
488 487 tmpstatus = scmutil.status(
489 488 copymod.copy(status.modified),
490 489 copymod.copy(status.added),
491 490 copymod.copy(status.removed),
492 491 copymod.copy(status.deleted),
493 492 copymod.copy(status.unknown),
494 493 copymod.copy(status.ignored),
495 494 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
496 495 )
497 496
498 497 # Force allows -X subrepo to skip the subrepo.
499 498 subs, commitsubs, newstate = subrepoutil.precommit(
500 499 repo.ui, wctx, tmpstatus, match, force=True
501 500 )
502 501 for s in subs:
503 502 if s in commitsubs:
504 503 dirtyreason = wctx.sub(s).dirtyreason(True)
505 504 raise error.Abort(dirtyreason)
506 505
507 506 if not force:
508 507 repo.checkcommitpatterns(wctx, match, status, fail)
509 508 diffopts = patch.difffeatureopts(
510 509 ui,
511 510 opts=opts,
512 511 whitespace=True,
513 512 section=b'commands',
514 513 configprefix=b'commit.interactive.',
515 514 )
516 515 diffopts.nodates = True
517 516 diffopts.git = True
518 517 diffopts.showfunc = True
519 518 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
520 originalchunks = patch.parsepatch(originaldiff)
519 original_headers = patch.parsepatch(originaldiff)
521 520 match = scmutil.match(repo[None], pats)
522 521
523 522 # 1. filter patch, since we are intending to apply subset of it
524 523 try:
525 chunks, newopts = filterfn(ui, originalchunks, match)
524 chunks, newopts = filterfn(ui, original_headers, match)
526 525 except error.PatchError as err:
527 526 raise error.InputError(_(b'error parsing patch: %s') % err)
528 527 opts.update(newopts)
529 528
530 529 # We need to keep a backup of files that have been newly added and
531 530 # modified during the recording process because there is a previous
532 531 # version without the edit in the workdir. We also will need to restore
533 532 # files that were the sources of renames so that the patch application
534 533 # works.
535 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
536 chunks, originalchunks
537 )
534 newlyaddedandmodifiedfiles, alsorestore = newandmodified(chunks)
538 535 contenders = set()
539 536 for h in chunks:
540 try:
537 if isheader(h):
541 538 contenders.update(set(h.files()))
542 except AttributeError:
543 pass
544 539
545 540 changed = status.modified + status.added + status.removed
546 541 newfiles = [f for f in changed if f in contenders]
547 542 if not newfiles:
548 543 ui.status(_(b'no changes to record\n'))
549 544 return 0
550 545
551 546 modified = set(status.modified)
552 547
553 548 # 2. backup changed files, so we can restore them in the end
554 549
555 550 if backupall:
556 551 tobackup = changed
557 552 else:
558 553 tobackup = [
559 554 f
560 555 for f in newfiles
561 556 if f in modified or f in newlyaddedandmodifiedfiles
562 557 ]
563 558 backups = {}
564 559 if tobackup:
565 560 backupdir = repo.vfs.join(b'record-backups')
566 561 try:
567 562 os.mkdir(backupdir)
568 563 except OSError as err:
569 564 if err.errno != errno.EEXIST:
570 565 raise
571 566 try:
572 567 # backup continues
573 568 for f in tobackup:
574 569 fd, tmpname = pycompat.mkstemp(
575 570 prefix=os.path.basename(f) + b'.', dir=backupdir
576 571 )
577 572 os.close(fd)
578 573 ui.debug(b'backup %r as %r\n' % (f, tmpname))
579 574 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
580 575 backups[f] = tmpname
581 576
582 577 fp = stringio()
583 578 for c in chunks:
584 579 fname = c.filename()
585 580 if fname in backups:
586 581 c.write(fp)
587 582 dopatch = fp.tell()
588 583 fp.seek(0)
589 584
590 585 # 2.5 optionally review / modify patch in text editor
591 586 if opts.get(b'review', False):
592 587 patchtext = (
593 588 crecordmod.diffhelptext
594 589 + crecordmod.patchhelptext
595 590 + fp.read()
596 591 )
597 592 reviewedpatch = ui.edit(
598 593 patchtext, b"", action=b"diff", repopath=repo.path
599 594 )
600 595 fp.truncate(0)
601 596 fp.write(reviewedpatch)
602 597 fp.seek(0)
603 598
604 599 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
605 600 # 3a. apply filtered patch to clean repo (clean)
606 601 if backups:
607 602 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
608 603 mergemod.revert_to(repo[b'.'], matcher=m)
609 604
610 605 # 3b. (apply)
611 606 if dopatch:
612 607 try:
613 608 ui.debug(b'applying patch\n')
614 609 ui.debug(fp.getvalue())
615 610 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
616 611 except error.PatchError as err:
617 612 raise error.InputError(pycompat.bytestr(err))
618 613 del fp
619 614
620 615 # 4. We prepared working directory according to filtered
621 616 # patch. Now is the time to delegate the job to
622 617 # commit/qrefresh or the like!
623 618
624 619 # Make all of the pathnames absolute.
625 620 newfiles = [repo.wjoin(nf) for nf in newfiles]
626 621 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
627 622 finally:
628 623 # 5. finally restore backed-up files
629 624 try:
630 625 dirstate = repo.dirstate
631 626 for realname, tmpname in pycompat.iteritems(backups):
632 627 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
633 628
634 629 if dirstate[realname] == b'n':
635 630 # without normallookup, restoring timestamp
636 631 # may cause partially committed files
637 632 # to be treated as unmodified
638 633 dirstate.normallookup(realname)
639 634
640 635 # copystat=True here and above are a hack to trick any
641 636 # editors that have f open that we haven't modified them.
642 637 #
643 638 # Also note that this racy as an editor could notice the
644 639 # file's mtime before we've finished writing it.
645 640 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
646 641 os.unlink(tmpname)
647 642 if tobackup:
648 643 os.rmdir(backupdir)
649 644 except OSError:
650 645 pass
651 646
652 647 def recordinwlock(ui, repo, message, match, opts):
653 648 with repo.wlock():
654 649 return recordfunc(ui, repo, message, match, opts)
655 650
656 651 return commit(ui, repo, recordinwlock, pats, opts)
657 652
658 653
659 654 class dirnode(object):
660 655 """
661 656 Represent a directory in user working copy with information required for
662 657 the purpose of tersing its status.
663 658
664 659 path is the path to the directory, without a trailing '/'
665 660
666 661 statuses is a set of statuses of all files in this directory (this includes
667 662 all the files in all the subdirectories too)
668 663
669 664 files is a list of files which are direct child of this directory
670 665
671 666 subdirs is a dictionary of sub-directory name as the key and it's own
672 667 dirnode object as the value
673 668 """
674 669
675 670 def __init__(self, dirpath):
676 671 self.path = dirpath
677 672 self.statuses = set()
678 673 self.files = []
679 674 self.subdirs = {}
680 675
681 676 def _addfileindir(self, filename, status):
682 677 """Add a file in this directory as a direct child."""
683 678 self.files.append((filename, status))
684 679
685 680 def addfile(self, filename, status):
686 681 """
687 682 Add a file to this directory or to its direct parent directory.
688 683
689 684 If the file is not direct child of this directory, we traverse to the
690 685 directory of which this file is a direct child of and add the file
691 686 there.
692 687 """
693 688
694 689 # the filename contains a path separator, it means it's not the direct
695 690 # child of this directory
696 691 if b'/' in filename:
697 692 subdir, filep = filename.split(b'/', 1)
698 693
699 694 # does the dirnode object for subdir exists
700 695 if subdir not in self.subdirs:
701 696 subdirpath = pathutil.join(self.path, subdir)
702 697 self.subdirs[subdir] = dirnode(subdirpath)
703 698
704 699 # try adding the file in subdir
705 700 self.subdirs[subdir].addfile(filep, status)
706 701
707 702 else:
708 703 self._addfileindir(filename, status)
709 704
710 705 if status not in self.statuses:
711 706 self.statuses.add(status)
712 707
713 708 def iterfilepaths(self):
714 709 """Yield (status, path) for files directly under this directory."""
715 710 for f, st in self.files:
716 711 yield st, pathutil.join(self.path, f)
717 712
718 713 def tersewalk(self, terseargs):
719 714 """
720 715 Yield (status, path) obtained by processing the status of this
721 716 dirnode.
722 717
723 718 terseargs is the string of arguments passed by the user with `--terse`
724 719 flag.
725 720
726 721 Following are the cases which can happen:
727 722
728 723 1) All the files in the directory (including all the files in its
729 724 subdirectories) share the same status and the user has asked us to terse
730 725 that status. -> yield (status, dirpath). dirpath will end in '/'.
731 726
732 727 2) Otherwise, we do following:
733 728
734 729 a) Yield (status, filepath) for all the files which are in this
735 730 directory (only the ones in this directory, not the subdirs)
736 731
737 732 b) Recurse the function on all the subdirectories of this
738 733 directory
739 734 """
740 735
741 736 if len(self.statuses) == 1:
742 737 onlyst = self.statuses.pop()
743 738
744 739 # Making sure we terse only when the status abbreviation is
745 740 # passed as terse argument
746 741 if onlyst in terseargs:
747 742 yield onlyst, self.path + b'/'
748 743 return
749 744
750 745 # add the files to status list
751 746 for st, fpath in self.iterfilepaths():
752 747 yield st, fpath
753 748
754 749 # recurse on the subdirs
755 750 for dirobj in self.subdirs.values():
756 751 for st, fpath in dirobj.tersewalk(terseargs):
757 752 yield st, fpath
758 753
759 754
760 755 def tersedir(statuslist, terseargs):
761 756 """
762 757 Terse the status if all the files in a directory shares the same status.
763 758
764 759 statuslist is scmutil.status() object which contains a list of files for
765 760 each status.
766 761 terseargs is string which is passed by the user as the argument to `--terse`
767 762 flag.
768 763
769 764 The function makes a tree of objects of dirnode class, and at each node it
770 765 stores the information required to know whether we can terse a certain
771 766 directory or not.
772 767 """
773 768 # the order matters here as that is used to produce final list
774 769 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
775 770
776 771 # checking the argument validity
777 772 for s in pycompat.bytestr(terseargs):
778 773 if s not in allst:
779 774 raise error.InputError(_(b"'%s' not recognized") % s)
780 775
781 776 # creating a dirnode object for the root of the repo
782 777 rootobj = dirnode(b'')
783 778 pstatus = (
784 779 b'modified',
785 780 b'added',
786 781 b'deleted',
787 782 b'clean',
788 783 b'unknown',
789 784 b'ignored',
790 785 b'removed',
791 786 )
792 787
793 788 tersedict = {}
794 789 for attrname in pstatus:
795 790 statuschar = attrname[0:1]
796 791 for f in getattr(statuslist, attrname):
797 792 rootobj.addfile(f, statuschar)
798 793 tersedict[statuschar] = []
799 794
800 795 # we won't be tersing the root dir, so add files in it
801 796 for st, fpath in rootobj.iterfilepaths():
802 797 tersedict[st].append(fpath)
803 798
804 799 # process each sub-directory and build tersedict
805 800 for subdir in rootobj.subdirs.values():
806 801 for st, f in subdir.tersewalk(terseargs):
807 802 tersedict[st].append(f)
808 803
809 804 tersedlist = []
810 805 for st in allst:
811 806 tersedict[st].sort()
812 807 tersedlist.append(tersedict[st])
813 808
814 809 return scmutil.status(*tersedlist)
815 810
816 811
817 812 def _commentlines(raw):
818 813 '''Surround lineswith a comment char and a new line'''
819 814 lines = raw.splitlines()
820 815 commentedlines = [b'# %s' % line for line in lines]
821 816 return b'\n'.join(commentedlines) + b'\n'
822 817
823 818
824 819 @attr.s(frozen=True)
825 820 class morestatus(object):
826 821 reporoot = attr.ib()
827 822 unfinishedop = attr.ib()
828 823 unfinishedmsg = attr.ib()
829 824 activemerge = attr.ib()
830 825 unresolvedpaths = attr.ib()
831 826 _formattedpaths = attr.ib(init=False, default=set())
832 827 _label = b'status.morestatus'
833 828
834 829 def formatfile(self, path, fm):
835 830 self._formattedpaths.add(path)
836 831 if self.activemerge and path in self.unresolvedpaths:
837 832 fm.data(unresolved=True)
838 833
839 834 def formatfooter(self, fm):
840 835 if self.unfinishedop or self.unfinishedmsg:
841 836 fm.startitem()
842 837 fm.data(itemtype=b'morestatus')
843 838
844 839 if self.unfinishedop:
845 840 fm.data(unfinished=self.unfinishedop)
846 841 statemsg = (
847 842 _(b'The repository is in an unfinished *%s* state.')
848 843 % self.unfinishedop
849 844 )
850 845 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
851 846 if self.unfinishedmsg:
852 847 fm.data(unfinishedmsg=self.unfinishedmsg)
853 848
854 849 # May also start new data items.
855 850 self._formatconflicts(fm)
856 851
857 852 if self.unfinishedmsg:
858 853 fm.plain(
859 854 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
860 855 )
861 856
862 857 def _formatconflicts(self, fm):
863 858 if not self.activemerge:
864 859 return
865 860
866 861 if self.unresolvedpaths:
867 862 mergeliststr = b'\n'.join(
868 863 [
869 864 b' %s'
870 865 % util.pathto(self.reporoot, encoding.getcwd(), path)
871 866 for path in self.unresolvedpaths
872 867 ]
873 868 )
874 869 msg = (
875 870 _(
876 871 b'''Unresolved merge conflicts:
877 872
878 873 %s
879 874
880 875 To mark files as resolved: hg resolve --mark FILE'''
881 876 )
882 877 % mergeliststr
883 878 )
884 879
885 880 # If any paths with unresolved conflicts were not previously
886 881 # formatted, output them now.
887 882 for f in self.unresolvedpaths:
888 883 if f in self._formattedpaths:
889 884 # Already output.
890 885 continue
891 886 fm.startitem()
892 887 # We can't claim to know the status of the file - it may just
893 888 # have been in one of the states that were not requested for
894 889 # display, so it could be anything.
895 890 fm.data(itemtype=b'file', path=f, unresolved=True)
896 891
897 892 else:
898 893 msg = _(b'No unresolved merge conflicts.')
899 894
900 895 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
901 896
902 897
903 898 def readmorestatus(repo):
904 899 """Returns a morestatus object if the repo has unfinished state."""
905 900 statetuple = statemod.getrepostate(repo)
906 901 mergestate = mergestatemod.mergestate.read(repo)
907 902 activemerge = mergestate.active()
908 903 if not statetuple and not activemerge:
909 904 return None
910 905
911 906 unfinishedop = unfinishedmsg = unresolved = None
912 907 if statetuple:
913 908 unfinishedop, unfinishedmsg = statetuple
914 909 if activemerge:
915 910 unresolved = sorted(mergestate.unresolved())
916 911 return morestatus(
917 912 repo.root, unfinishedop, unfinishedmsg, activemerge, unresolved
918 913 )
919 914
920 915
921 916 def findpossible(cmd, table, strict=False):
922 917 """
923 918 Return cmd -> (aliases, command table entry)
924 919 for each matching command.
925 920 Return debug commands (or their aliases) only if no normal command matches.
926 921 """
927 922 choice = {}
928 923 debugchoice = {}
929 924
930 925 if cmd in table:
931 926 # short-circuit exact matches, "log" alias beats "log|history"
932 927 keys = [cmd]
933 928 else:
934 929 keys = table.keys()
935 930
936 931 allcmds = []
937 932 for e in keys:
938 933 aliases = parsealiases(e)
939 934 allcmds.extend(aliases)
940 935 found = None
941 936 if cmd in aliases:
942 937 found = cmd
943 938 elif not strict:
944 939 for a in aliases:
945 940 if a.startswith(cmd):
946 941 found = a
947 942 break
948 943 if found is not None:
949 944 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
950 945 debugchoice[found] = (aliases, table[e])
951 946 else:
952 947 choice[found] = (aliases, table[e])
953 948
954 949 if not choice and debugchoice:
955 950 choice = debugchoice
956 951
957 952 return choice, allcmds
958 953
959 954
960 955 def findcmd(cmd, table, strict=True):
961 956 """Return (aliases, command table entry) for command string."""
962 957 choice, allcmds = findpossible(cmd, table, strict)
963 958
964 959 if cmd in choice:
965 960 return choice[cmd]
966 961
967 962 if len(choice) > 1:
968 963 clist = sorted(choice)
969 964 raise error.AmbiguousCommand(cmd, clist)
970 965
971 966 if choice:
972 967 return list(choice.values())[0]
973 968
974 969 raise error.UnknownCommand(cmd, allcmds)
975 970
976 971
977 972 def changebranch(ui, repo, revs, label, opts):
978 973 """Change the branch name of given revs to label"""
979 974
980 975 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
981 976 # abort in case of uncommitted merge or dirty wdir
982 977 bailifchanged(repo)
983 978 revs = scmutil.revrange(repo, revs)
984 979 if not revs:
985 980 raise error.InputError(b"empty revision set")
986 981 roots = repo.revs(b'roots(%ld)', revs)
987 982 if len(roots) > 1:
988 983 raise error.InputError(
989 984 _(b"cannot change branch of non-linear revisions")
990 985 )
991 986 rewriteutil.precheck(repo, revs, b'change branch of')
992 987
993 988 root = repo[roots.first()]
994 989 rpb = {parent.branch() for parent in root.parents()}
995 990 if (
996 991 not opts.get(b'force')
997 992 and label not in rpb
998 993 and label in repo.branchmap()
999 994 ):
1000 995 raise error.InputError(
1001 996 _(b"a branch of the same name already exists")
1002 997 )
1003 998
1004 999 # make sure only topological heads
1005 1000 if repo.revs(b'heads(%ld) - head()', revs):
1006 1001 raise error.InputError(
1007 1002 _(b"cannot change branch in middle of a stack")
1008 1003 )
1009 1004
1010 1005 replacements = {}
1011 1006 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
1012 1007 # mercurial.subrepo -> mercurial.cmdutil
1013 1008 from . import context
1014 1009
1015 1010 for rev in revs:
1016 1011 ctx = repo[rev]
1017 1012 oldbranch = ctx.branch()
1018 1013 # check if ctx has same branch
1019 1014 if oldbranch == label:
1020 1015 continue
1021 1016
1022 1017 def filectxfn(repo, newctx, path):
1023 1018 try:
1024 1019 return ctx[path]
1025 1020 except error.ManifestLookupError:
1026 1021 return None
1027 1022
1028 1023 ui.debug(
1029 1024 b"changing branch of '%s' from '%s' to '%s'\n"
1030 1025 % (hex(ctx.node()), oldbranch, label)
1031 1026 )
1032 1027 extra = ctx.extra()
1033 1028 extra[b'branch_change'] = hex(ctx.node())
1034 1029 # While changing branch of set of linear commits, make sure that
1035 1030 # we base our commits on new parent rather than old parent which
1036 1031 # was obsoleted while changing the branch
1037 1032 p1 = ctx.p1().node()
1038 1033 p2 = ctx.p2().node()
1039 1034 if p1 in replacements:
1040 1035 p1 = replacements[p1][0]
1041 1036 if p2 in replacements:
1042 1037 p2 = replacements[p2][0]
1043 1038
1044 1039 mc = context.memctx(
1045 1040 repo,
1046 1041 (p1, p2),
1047 1042 ctx.description(),
1048 1043 ctx.files(),
1049 1044 filectxfn,
1050 1045 user=ctx.user(),
1051 1046 date=ctx.date(),
1052 1047 extra=extra,
1053 1048 branch=label,
1054 1049 )
1055 1050
1056 1051 newnode = repo.commitctx(mc)
1057 1052 replacements[ctx.node()] = (newnode,)
1058 1053 ui.debug(b'new node id is %s\n' % hex(newnode))
1059 1054
1060 1055 # create obsmarkers and move bookmarks
1061 1056 scmutil.cleanupnodes(
1062 1057 repo, replacements, b'branch-change', fixphase=True
1063 1058 )
1064 1059
1065 1060 # move the working copy too
1066 1061 wctx = repo[None]
1067 1062 # in-progress merge is a bit too complex for now.
1068 1063 if len(wctx.parents()) == 1:
1069 1064 newid = replacements.get(wctx.p1().node())
1070 1065 if newid is not None:
1071 1066 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1072 1067 # mercurial.cmdutil
1073 1068 from . import hg
1074 1069
1075 1070 hg.update(repo, newid[0], quietempty=True)
1076 1071
1077 1072 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1078 1073
1079 1074
1080 1075 def findrepo(p):
1081 1076 while not os.path.isdir(os.path.join(p, b".hg")):
1082 1077 oldp, p = p, os.path.dirname(p)
1083 1078 if p == oldp:
1084 1079 return None
1085 1080
1086 1081 return p
1087 1082
1088 1083
1089 1084 def bailifchanged(repo, merge=True, hint=None):
1090 1085 """enforce the precondition that working directory must be clean.
1091 1086
1092 1087 'merge' can be set to false if a pending uncommitted merge should be
1093 1088 ignored (such as when 'update --check' runs).
1094 1089
1095 1090 'hint' is the usual hint given to Abort exception.
1096 1091 """
1097 1092
1098 1093 if merge and repo.dirstate.p2() != repo.nullid:
1099 1094 raise error.StateError(_(b'outstanding uncommitted merge'), hint=hint)
1100 1095 st = repo.status()
1101 1096 if st.modified or st.added or st.removed or st.deleted:
1102 1097 raise error.StateError(_(b'uncommitted changes'), hint=hint)
1103 1098 ctx = repo[None]
1104 1099 for s in sorted(ctx.substate):
1105 1100 ctx.sub(s).bailifchanged(hint=hint)
1106 1101
1107 1102
1108 1103 def logmessage(ui, opts):
1109 1104 """get the log message according to -m and -l option"""
1110 1105
1111 1106 check_at_most_one_arg(opts, b'message', b'logfile')
1112 1107
1113 1108 message = opts.get(b'message')
1114 1109 logfile = opts.get(b'logfile')
1115 1110
1116 1111 if not message and logfile:
1117 1112 try:
1118 1113 if isstdiofilename(logfile):
1119 1114 message = ui.fin.read()
1120 1115 else:
1121 1116 message = b'\n'.join(util.readfile(logfile).splitlines())
1122 1117 except IOError as inst:
1123 1118 raise error.Abort(
1124 1119 _(b"can't read commit message '%s': %s")
1125 1120 % (logfile, encoding.strtolocal(inst.strerror))
1126 1121 )
1127 1122 return message
1128 1123
1129 1124
1130 1125 def mergeeditform(ctxorbool, baseformname):
1131 1126 """return appropriate editform name (referencing a committemplate)
1132 1127
1133 1128 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1134 1129 merging is committed.
1135 1130
1136 1131 This returns baseformname with '.merge' appended if it is a merge,
1137 1132 otherwise '.normal' is appended.
1138 1133 """
1139 1134 if isinstance(ctxorbool, bool):
1140 1135 if ctxorbool:
1141 1136 return baseformname + b".merge"
1142 1137 elif len(ctxorbool.parents()) > 1:
1143 1138 return baseformname + b".merge"
1144 1139
1145 1140 return baseformname + b".normal"
1146 1141
1147 1142
1148 1143 def getcommiteditor(
1149 1144 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1150 1145 ):
1151 1146 """get appropriate commit message editor according to '--edit' option
1152 1147
1153 1148 'finishdesc' is a function to be called with edited commit message
1154 1149 (= 'description' of the new changeset) just after editing, but
1155 1150 before checking empty-ness. It should return actual text to be
1156 1151 stored into history. This allows to change description before
1157 1152 storing.
1158 1153
1159 1154 'extramsg' is a extra message to be shown in the editor instead of
1160 1155 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1161 1156 is automatically added.
1162 1157
1163 1158 'editform' is a dot-separated list of names, to distinguish
1164 1159 the purpose of commit text editing.
1165 1160
1166 1161 'getcommiteditor' returns 'commitforceeditor' regardless of
1167 1162 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1168 1163 they are specific for usage in MQ.
1169 1164 """
1170 1165 if edit or finishdesc or extramsg:
1171 1166 return lambda r, c, s: commitforceeditor(
1172 1167 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1173 1168 )
1174 1169 elif editform:
1175 1170 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1176 1171 else:
1177 1172 return commiteditor
1178 1173
1179 1174
1180 1175 def _escapecommandtemplate(tmpl):
1181 1176 parts = []
1182 1177 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1183 1178 if typ == b'string':
1184 1179 parts.append(stringutil.escapestr(tmpl[start:end]))
1185 1180 else:
1186 1181 parts.append(tmpl[start:end])
1187 1182 return b''.join(parts)
1188 1183
1189 1184
1190 1185 def rendercommandtemplate(ui, tmpl, props):
1191 1186 r"""Expand a literal template 'tmpl' in a way suitable for command line
1192 1187
1193 1188 '\' in outermost string is not taken as an escape character because it
1194 1189 is a directory separator on Windows.
1195 1190
1196 1191 >>> from . import ui as uimod
1197 1192 >>> ui = uimod.ui()
1198 1193 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1199 1194 'c:\\foo'
1200 1195 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1201 1196 'c:{path}'
1202 1197 """
1203 1198 if not tmpl:
1204 1199 return tmpl
1205 1200 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1206 1201 return t.renderdefault(props)
1207 1202
1208 1203
1209 1204 def rendertemplate(ctx, tmpl, props=None):
1210 1205 """Expand a literal template 'tmpl' byte-string against one changeset
1211 1206
1212 1207 Each props item must be a stringify-able value or a callable returning
1213 1208 such value, i.e. no bare list nor dict should be passed.
1214 1209 """
1215 1210 repo = ctx.repo()
1216 1211 tres = formatter.templateresources(repo.ui, repo)
1217 1212 t = formatter.maketemplater(
1218 1213 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1219 1214 )
1220 1215 mapping = {b'ctx': ctx}
1221 1216 if props:
1222 1217 mapping.update(props)
1223 1218 return t.renderdefault(mapping)
1224 1219
1225 1220
1226 1221 def format_changeset_summary(ui, ctx, command=None, default_spec=None):
1227 1222 """Format a changeset summary (one line)."""
1228 1223 spec = None
1229 1224 if command:
1230 1225 spec = ui.config(
1231 1226 b'command-templates', b'oneline-summary.%s' % command, None
1232 1227 )
1233 1228 if not spec:
1234 1229 spec = ui.config(b'command-templates', b'oneline-summary')
1235 1230 if not spec:
1236 1231 spec = default_spec
1237 1232 if not spec:
1238 1233 spec = (
1239 1234 b'{separate(" ", '
1240 1235 b'label("oneline-summary.changeset", "{rev}:{node|short}")'
1241 1236 b', '
1242 1237 b'join(filter(namespaces % "{ifeq(namespace, "branches", "", join(names % "{label("oneline-summary.{namespace}", name)}", " "))}"), " ")'
1243 1238 b')} '
1244 1239 b'"{label("oneline-summary.desc", desc|firstline)}"'
1245 1240 )
1246 1241 text = rendertemplate(ctx, spec)
1247 1242 return text.split(b'\n')[0]
1248 1243
1249 1244
1250 1245 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1251 1246 r"""Convert old-style filename format string to template string
1252 1247
1253 1248 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1254 1249 'foo-{reporoot|basename}-{seqno}.patch'
1255 1250 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1256 1251 '{rev}{tags % "{tag}"}{node}'
1257 1252
1258 1253 '\' in outermost strings has to be escaped because it is a directory
1259 1254 separator on Windows:
1260 1255
1261 1256 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1262 1257 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1263 1258 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1264 1259 '\\\\\\\\foo\\\\bar.patch'
1265 1260 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1266 1261 '\\\\{tags % "{tag}"}'
1267 1262
1268 1263 but inner strings follow the template rules (i.e. '\' is taken as an
1269 1264 escape character):
1270 1265
1271 1266 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1272 1267 '{"c:\\tmp"}'
1273 1268 """
1274 1269 expander = {
1275 1270 b'H': b'{node}',
1276 1271 b'R': b'{rev}',
1277 1272 b'h': b'{node|short}',
1278 1273 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1279 1274 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1280 1275 b'%': b'%',
1281 1276 b'b': b'{reporoot|basename}',
1282 1277 }
1283 1278 if total is not None:
1284 1279 expander[b'N'] = b'{total}'
1285 1280 if seqno is not None:
1286 1281 expander[b'n'] = b'{seqno}'
1287 1282 if total is not None and seqno is not None:
1288 1283 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1289 1284 if pathname is not None:
1290 1285 expander[b's'] = b'{pathname|basename}'
1291 1286 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1292 1287 expander[b'p'] = b'{pathname}'
1293 1288
1294 1289 newname = []
1295 1290 for typ, start, end in templater.scantemplate(pat, raw=True):
1296 1291 if typ != b'string':
1297 1292 newname.append(pat[start:end])
1298 1293 continue
1299 1294 i = start
1300 1295 while i < end:
1301 1296 n = pat.find(b'%', i, end)
1302 1297 if n < 0:
1303 1298 newname.append(stringutil.escapestr(pat[i:end]))
1304 1299 break
1305 1300 newname.append(stringutil.escapestr(pat[i:n]))
1306 1301 if n + 2 > end:
1307 1302 raise error.Abort(
1308 1303 _(b"incomplete format spec in output filename")
1309 1304 )
1310 1305 c = pat[n + 1 : n + 2]
1311 1306 i = n + 2
1312 1307 try:
1313 1308 newname.append(expander[c])
1314 1309 except KeyError:
1315 1310 raise error.Abort(
1316 1311 _(b"invalid format spec '%%%s' in output filename") % c
1317 1312 )
1318 1313 return b''.join(newname)
1319 1314
1320 1315
1321 1316 def makefilename(ctx, pat, **props):
1322 1317 if not pat:
1323 1318 return pat
1324 1319 tmpl = _buildfntemplate(pat, **props)
1325 1320 # BUG: alias expansion shouldn't be made against template fragments
1326 1321 # rewritten from %-format strings, but we have no easy way to partially
1327 1322 # disable the expansion.
1328 1323 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1329 1324
1330 1325
1331 1326 def isstdiofilename(pat):
1332 1327 """True if the given pat looks like a filename denoting stdin/stdout"""
1333 1328 return not pat or pat == b'-'
1334 1329
1335 1330
1336 1331 class _unclosablefile(object):
1337 1332 def __init__(self, fp):
1338 1333 self._fp = fp
1339 1334
1340 1335 def close(self):
1341 1336 pass
1342 1337
1343 1338 def __iter__(self):
1344 1339 return iter(self._fp)
1345 1340
1346 1341 def __getattr__(self, attr):
1347 1342 return getattr(self._fp, attr)
1348 1343
1349 1344 def __enter__(self):
1350 1345 return self
1351 1346
1352 1347 def __exit__(self, exc_type, exc_value, exc_tb):
1353 1348 pass
1354 1349
1355 1350
1356 1351 def makefileobj(ctx, pat, mode=b'wb', **props):
1357 1352 writable = mode not in (b'r', b'rb')
1358 1353
1359 1354 if isstdiofilename(pat):
1360 1355 repo = ctx.repo()
1361 1356 if writable:
1362 1357 fp = repo.ui.fout
1363 1358 else:
1364 1359 fp = repo.ui.fin
1365 1360 return _unclosablefile(fp)
1366 1361 fn = makefilename(ctx, pat, **props)
1367 1362 return open(fn, mode)
1368 1363
1369 1364
1370 1365 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1371 1366 """opens the changelog, manifest, a filelog or a given revlog"""
1372 1367 cl = opts[b'changelog']
1373 1368 mf = opts[b'manifest']
1374 1369 dir = opts[b'dir']
1375 1370 msg = None
1376 1371 if cl and mf:
1377 1372 msg = _(b'cannot specify --changelog and --manifest at the same time')
1378 1373 elif cl and dir:
1379 1374 msg = _(b'cannot specify --changelog and --dir at the same time')
1380 1375 elif cl or mf or dir:
1381 1376 if file_:
1382 1377 msg = _(b'cannot specify filename with --changelog or --manifest')
1383 1378 elif not repo:
1384 1379 msg = _(
1385 1380 b'cannot specify --changelog or --manifest or --dir '
1386 1381 b'without a repository'
1387 1382 )
1388 1383 if msg:
1389 1384 raise error.InputError(msg)
1390 1385
1391 1386 r = None
1392 1387 if repo:
1393 1388 if cl:
1394 1389 r = repo.unfiltered().changelog
1395 1390 elif dir:
1396 1391 if not scmutil.istreemanifest(repo):
1397 1392 raise error.InputError(
1398 1393 _(
1399 1394 b"--dir can only be used on repos with "
1400 1395 b"treemanifest enabled"
1401 1396 )
1402 1397 )
1403 1398 if not dir.endswith(b'/'):
1404 1399 dir = dir + b'/'
1405 1400 dirlog = repo.manifestlog.getstorage(dir)
1406 1401 if len(dirlog):
1407 1402 r = dirlog
1408 1403 elif mf:
1409 1404 r = repo.manifestlog.getstorage(b'')
1410 1405 elif file_:
1411 1406 filelog = repo.file(file_)
1412 1407 if len(filelog):
1413 1408 r = filelog
1414 1409
1415 1410 # Not all storage may be revlogs. If requested, try to return an actual
1416 1411 # revlog instance.
1417 1412 if returnrevlog:
1418 1413 if isinstance(r, revlog.revlog):
1419 1414 pass
1420 1415 elif util.safehasattr(r, b'_revlog'):
1421 1416 r = r._revlog # pytype: disable=attribute-error
1422 1417 elif r is not None:
1423 1418 raise error.InputError(
1424 1419 _(b'%r does not appear to be a revlog') % r
1425 1420 )
1426 1421
1427 1422 if not r:
1428 1423 if not returnrevlog:
1429 1424 raise error.InputError(_(b'cannot give path to non-revlog'))
1430 1425
1431 1426 if not file_:
1432 1427 raise error.CommandError(cmd, _(b'invalid arguments'))
1433 1428 if not os.path.isfile(file_):
1434 1429 raise error.InputError(_(b"revlog '%s' not found") % file_)
1435 1430
1436 1431 target = (revlog_constants.KIND_OTHER, b'free-form:%s' % file_)
1437 1432 r = revlog.revlog(
1438 1433 vfsmod.vfs(encoding.getcwd(), audit=False),
1439 1434 target=target,
1440 1435 radix=file_[:-2],
1441 1436 )
1442 1437 return r
1443 1438
1444 1439
1445 1440 def openrevlog(repo, cmd, file_, opts):
1446 1441 """Obtain a revlog backing storage of an item.
1447 1442
1448 1443 This is similar to ``openstorage()`` except it always returns a revlog.
1449 1444
1450 1445 In most cases, a caller cares about the main storage object - not the
1451 1446 revlog backing it. Therefore, this function should only be used by code
1452 1447 that needs to examine low-level revlog implementation details. e.g. debug
1453 1448 commands.
1454 1449 """
1455 1450 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1456 1451
1457 1452
1458 1453 def copy(ui, repo, pats, opts, rename=False):
1459 1454 check_incompatible_arguments(opts, b'forget', [b'dry_run'])
1460 1455
1461 1456 # called with the repo lock held
1462 1457 #
1463 1458 # hgsep => pathname that uses "/" to separate directories
1464 1459 # ossep => pathname that uses os.sep to separate directories
1465 1460 cwd = repo.getcwd()
1466 1461 targets = {}
1467 1462 forget = opts.get(b"forget")
1468 1463 after = opts.get(b"after")
1469 1464 dryrun = opts.get(b"dry_run")
1470 1465 rev = opts.get(b'at_rev')
1471 1466 if rev:
1472 1467 if not forget and not after:
1473 1468 # TODO: Remove this restriction and make it also create the copy
1474 1469 # targets (and remove the rename source if rename==True).
1475 1470 raise error.InputError(_(b'--at-rev requires --after'))
1476 1471 ctx = scmutil.revsingle(repo, rev)
1477 1472 if len(ctx.parents()) > 1:
1478 1473 raise error.InputError(
1479 1474 _(b'cannot mark/unmark copy in merge commit')
1480 1475 )
1481 1476 else:
1482 1477 ctx = repo[None]
1483 1478
1484 1479 pctx = ctx.p1()
1485 1480
1486 1481 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1487 1482
1488 1483 if forget:
1489 1484 if ctx.rev() is None:
1490 1485 new_ctx = ctx
1491 1486 else:
1492 1487 if len(ctx.parents()) > 1:
1493 1488 raise error.InputError(_(b'cannot unmark copy in merge commit'))
1494 1489 # avoid cycle context -> subrepo -> cmdutil
1495 1490 from . import context
1496 1491
1497 1492 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1498 1493 new_ctx = context.overlayworkingctx(repo)
1499 1494 new_ctx.setbase(ctx.p1())
1500 1495 mergemod.graft(repo, ctx, wctx=new_ctx)
1501 1496
1502 1497 match = scmutil.match(ctx, pats, opts)
1503 1498
1504 1499 current_copies = ctx.p1copies()
1505 1500 current_copies.update(ctx.p2copies())
1506 1501
1507 1502 uipathfn = scmutil.getuipathfn(repo)
1508 1503 for f in ctx.walk(match):
1509 1504 if f in current_copies:
1510 1505 new_ctx[f].markcopied(None)
1511 1506 elif match.exact(f):
1512 1507 ui.warn(
1513 1508 _(
1514 1509 b'%s: not unmarking as copy - file is not marked as copied\n'
1515 1510 )
1516 1511 % uipathfn(f)
1517 1512 )
1518 1513
1519 1514 if ctx.rev() is not None:
1520 1515 with repo.lock():
1521 1516 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1522 1517 new_node = mem_ctx.commit()
1523 1518
1524 1519 if repo.dirstate.p1() == ctx.node():
1525 1520 with repo.dirstate.parentchange():
1526 1521 scmutil.movedirstate(repo, repo[new_node])
1527 1522 replacements = {ctx.node(): [new_node]}
1528 1523 scmutil.cleanupnodes(
1529 1524 repo, replacements, b'uncopy', fixphase=True
1530 1525 )
1531 1526
1532 1527 return
1533 1528
1534 1529 pats = scmutil.expandpats(pats)
1535 1530 if not pats:
1536 1531 raise error.InputError(_(b'no source or destination specified'))
1537 1532 if len(pats) == 1:
1538 1533 raise error.InputError(_(b'no destination specified'))
1539 1534 dest = pats.pop()
1540 1535
1541 1536 def walkpat(pat):
1542 1537 srcs = []
1543 1538 # TODO: Inline and simplify the non-working-copy version of this code
1544 1539 # since it shares very little with the working-copy version of it.
1545 1540 ctx_to_walk = ctx if ctx.rev() is None else pctx
1546 1541 m = scmutil.match(ctx_to_walk, [pat], opts, globbed=True)
1547 1542 for abs in ctx_to_walk.walk(m):
1548 1543 rel = uipathfn(abs)
1549 1544 exact = m.exact(abs)
1550 1545 if abs not in ctx:
1551 1546 if abs in pctx:
1552 1547 if not after:
1553 1548 if exact:
1554 1549 ui.warn(
1555 1550 _(
1556 1551 b'%s: not copying - file has been marked '
1557 1552 b'for remove\n'
1558 1553 )
1559 1554 % rel
1560 1555 )
1561 1556 continue
1562 1557 else:
1563 1558 if exact:
1564 1559 ui.warn(
1565 1560 _(b'%s: not copying - file is not managed\n') % rel
1566 1561 )
1567 1562 continue
1568 1563
1569 1564 # abs: hgsep
1570 1565 # rel: ossep
1571 1566 srcs.append((abs, rel, exact))
1572 1567 return srcs
1573 1568
1574 1569 if ctx.rev() is not None:
1575 1570 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1576 1571 absdest = pathutil.canonpath(repo.root, cwd, dest)
1577 1572 if ctx.hasdir(absdest):
1578 1573 raise error.InputError(
1579 1574 _(b'%s: --at-rev does not support a directory as destination')
1580 1575 % uipathfn(absdest)
1581 1576 )
1582 1577 if absdest not in ctx:
1583 1578 raise error.InputError(
1584 1579 _(b'%s: copy destination does not exist in %s')
1585 1580 % (uipathfn(absdest), ctx)
1586 1581 )
1587 1582
1588 1583 # avoid cycle context -> subrepo -> cmdutil
1589 1584 from . import context
1590 1585
1591 1586 copylist = []
1592 1587 for pat in pats:
1593 1588 srcs = walkpat(pat)
1594 1589 if not srcs:
1595 1590 continue
1596 1591 for abs, rel, exact in srcs:
1597 1592 copylist.append(abs)
1598 1593
1599 1594 if not copylist:
1600 1595 raise error.InputError(_(b'no files to copy'))
1601 1596 # TODO: Add support for `hg cp --at-rev . foo bar dir` and
1602 1597 # `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the
1603 1598 # existing functions below.
1604 1599 if len(copylist) != 1:
1605 1600 raise error.InputError(_(b'--at-rev requires a single source'))
1606 1601
1607 1602 new_ctx = context.overlayworkingctx(repo)
1608 1603 new_ctx.setbase(ctx.p1())
1609 1604 mergemod.graft(repo, ctx, wctx=new_ctx)
1610 1605
1611 1606 new_ctx.markcopied(absdest, copylist[0])
1612 1607
1613 1608 with repo.lock():
1614 1609 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1615 1610 new_node = mem_ctx.commit()
1616 1611
1617 1612 if repo.dirstate.p1() == ctx.node():
1618 1613 with repo.dirstate.parentchange():
1619 1614 scmutil.movedirstate(repo, repo[new_node])
1620 1615 replacements = {ctx.node(): [new_node]}
1621 1616 scmutil.cleanupnodes(repo, replacements, b'copy', fixphase=True)
1622 1617
1623 1618 return
1624 1619
1625 1620 # abssrc: hgsep
1626 1621 # relsrc: ossep
1627 1622 # otarget: ossep
1628 1623 def copyfile(abssrc, relsrc, otarget, exact):
1629 1624 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1630 1625 if b'/' in abstarget:
1631 1626 # We cannot normalize abstarget itself, this would prevent
1632 1627 # case only renames, like a => A.
1633 1628 abspath, absname = abstarget.rsplit(b'/', 1)
1634 1629 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1635 1630 reltarget = repo.pathto(abstarget, cwd)
1636 1631 target = repo.wjoin(abstarget)
1637 1632 src = repo.wjoin(abssrc)
1638 1633 state = repo.dirstate[abstarget]
1639 1634
1640 1635 scmutil.checkportable(ui, abstarget)
1641 1636
1642 1637 # check for collisions
1643 1638 prevsrc = targets.get(abstarget)
1644 1639 if prevsrc is not None:
1645 1640 ui.warn(
1646 1641 _(b'%s: not overwriting - %s collides with %s\n')
1647 1642 % (
1648 1643 reltarget,
1649 1644 repo.pathto(abssrc, cwd),
1650 1645 repo.pathto(prevsrc, cwd),
1651 1646 )
1652 1647 )
1653 1648 return True # report a failure
1654 1649
1655 1650 # check for overwrites
1656 1651 exists = os.path.lexists(target)
1657 1652 samefile = False
1658 1653 if exists and abssrc != abstarget:
1659 1654 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1660 1655 abstarget
1661 1656 ):
1662 1657 if not rename:
1663 1658 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1664 1659 return True # report a failure
1665 1660 exists = False
1666 1661 samefile = True
1667 1662
1668 1663 if not after and exists or after and state in b'mn':
1669 1664 if not opts[b'force']:
1670 1665 if state in b'mn':
1671 1666 msg = _(b'%s: not overwriting - file already committed\n')
1672 1667 if after:
1673 1668 flags = b'--after --force'
1674 1669 else:
1675 1670 flags = b'--force'
1676 1671 if rename:
1677 1672 hint = (
1678 1673 _(
1679 1674 b"('hg rename %s' to replace the file by "
1680 1675 b'recording a rename)\n'
1681 1676 )
1682 1677 % flags
1683 1678 )
1684 1679 else:
1685 1680 hint = (
1686 1681 _(
1687 1682 b"('hg copy %s' to replace the file by "
1688 1683 b'recording a copy)\n'
1689 1684 )
1690 1685 % flags
1691 1686 )
1692 1687 else:
1693 1688 msg = _(b'%s: not overwriting - file exists\n')
1694 1689 if rename:
1695 1690 hint = _(
1696 1691 b"('hg rename --after' to record the rename)\n"
1697 1692 )
1698 1693 else:
1699 1694 hint = _(b"('hg copy --after' to record the copy)\n")
1700 1695 ui.warn(msg % reltarget)
1701 1696 ui.warn(hint)
1702 1697 return True # report a failure
1703 1698
1704 1699 if after:
1705 1700 if not exists:
1706 1701 if rename:
1707 1702 ui.warn(
1708 1703 _(b'%s: not recording move - %s does not exist\n')
1709 1704 % (relsrc, reltarget)
1710 1705 )
1711 1706 else:
1712 1707 ui.warn(
1713 1708 _(b'%s: not recording copy - %s does not exist\n')
1714 1709 % (relsrc, reltarget)
1715 1710 )
1716 1711 return True # report a failure
1717 1712 elif not dryrun:
1718 1713 try:
1719 1714 if exists:
1720 1715 os.unlink(target)
1721 1716 targetdir = os.path.dirname(target) or b'.'
1722 1717 if not os.path.isdir(targetdir):
1723 1718 os.makedirs(targetdir)
1724 1719 if samefile:
1725 1720 tmp = target + b"~hgrename"
1726 1721 os.rename(src, tmp)
1727 1722 os.rename(tmp, target)
1728 1723 else:
1729 1724 # Preserve stat info on renames, not on copies; this matches
1730 1725 # Linux CLI behavior.
1731 1726 util.copyfile(src, target, copystat=rename)
1732 1727 srcexists = True
1733 1728 except IOError as inst:
1734 1729 if inst.errno == errno.ENOENT:
1735 1730 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1736 1731 srcexists = False
1737 1732 else:
1738 1733 ui.warn(
1739 1734 _(b'%s: cannot copy - %s\n')
1740 1735 % (relsrc, encoding.strtolocal(inst.strerror))
1741 1736 )
1742 1737 return True # report a failure
1743 1738
1744 1739 if ui.verbose or not exact:
1745 1740 if rename:
1746 1741 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1747 1742 else:
1748 1743 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1749 1744
1750 1745 targets[abstarget] = abssrc
1751 1746
1752 1747 # fix up dirstate
1753 1748 scmutil.dirstatecopy(
1754 1749 ui, repo, ctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1755 1750 )
1756 1751 if rename and not dryrun:
1757 1752 if not after and srcexists and not samefile:
1758 1753 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1759 1754 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1760 1755 ctx.forget([abssrc])
1761 1756
1762 1757 # pat: ossep
1763 1758 # dest ossep
1764 1759 # srcs: list of (hgsep, hgsep, ossep, bool)
1765 1760 # return: function that takes hgsep and returns ossep
1766 1761 def targetpathfn(pat, dest, srcs):
1767 1762 if os.path.isdir(pat):
1768 1763 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1769 1764 abspfx = util.localpath(abspfx)
1770 1765 if destdirexists:
1771 1766 striplen = len(os.path.split(abspfx)[0])
1772 1767 else:
1773 1768 striplen = len(abspfx)
1774 1769 if striplen:
1775 1770 striplen += len(pycompat.ossep)
1776 1771 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1777 1772 elif destdirexists:
1778 1773 res = lambda p: os.path.join(
1779 1774 dest, os.path.basename(util.localpath(p))
1780 1775 )
1781 1776 else:
1782 1777 res = lambda p: dest
1783 1778 return res
1784 1779
1785 1780 # pat: ossep
1786 1781 # dest ossep
1787 1782 # srcs: list of (hgsep, hgsep, ossep, bool)
1788 1783 # return: function that takes hgsep and returns ossep
1789 1784 def targetpathafterfn(pat, dest, srcs):
1790 1785 if matchmod.patkind(pat):
1791 1786 # a mercurial pattern
1792 1787 res = lambda p: os.path.join(
1793 1788 dest, os.path.basename(util.localpath(p))
1794 1789 )
1795 1790 else:
1796 1791 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1797 1792 if len(abspfx) < len(srcs[0][0]):
1798 1793 # A directory. Either the target path contains the last
1799 1794 # component of the source path or it does not.
1800 1795 def evalpath(striplen):
1801 1796 score = 0
1802 1797 for s in srcs:
1803 1798 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1804 1799 if os.path.lexists(t):
1805 1800 score += 1
1806 1801 return score
1807 1802
1808 1803 abspfx = util.localpath(abspfx)
1809 1804 striplen = len(abspfx)
1810 1805 if striplen:
1811 1806 striplen += len(pycompat.ossep)
1812 1807 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1813 1808 score = evalpath(striplen)
1814 1809 striplen1 = len(os.path.split(abspfx)[0])
1815 1810 if striplen1:
1816 1811 striplen1 += len(pycompat.ossep)
1817 1812 if evalpath(striplen1) > score:
1818 1813 striplen = striplen1
1819 1814 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1820 1815 else:
1821 1816 # a file
1822 1817 if destdirexists:
1823 1818 res = lambda p: os.path.join(
1824 1819 dest, os.path.basename(util.localpath(p))
1825 1820 )
1826 1821 else:
1827 1822 res = lambda p: dest
1828 1823 return res
1829 1824
1830 1825 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1831 1826 if not destdirexists:
1832 1827 if len(pats) > 1 or matchmod.patkind(pats[0]):
1833 1828 raise error.InputError(
1834 1829 _(
1835 1830 b'with multiple sources, destination must be an '
1836 1831 b'existing directory'
1837 1832 )
1838 1833 )
1839 1834 if util.endswithsep(dest):
1840 1835 raise error.InputError(
1841 1836 _(b'destination %s is not a directory') % dest
1842 1837 )
1843 1838
1844 1839 tfn = targetpathfn
1845 1840 if after:
1846 1841 tfn = targetpathafterfn
1847 1842 copylist = []
1848 1843 for pat in pats:
1849 1844 srcs = walkpat(pat)
1850 1845 if not srcs:
1851 1846 continue
1852 1847 copylist.append((tfn(pat, dest, srcs), srcs))
1853 1848 if not copylist:
1854 1849 hint = None
1855 1850 if rename:
1856 1851 hint = _(b'maybe you meant to use --after --at-rev=.')
1857 1852 raise error.InputError(_(b'no files to copy'), hint=hint)
1858 1853
1859 1854 errors = 0
1860 1855 for targetpath, srcs in copylist:
1861 1856 for abssrc, relsrc, exact in srcs:
1862 1857 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1863 1858 errors += 1
1864 1859
1865 1860 return errors != 0
1866 1861
1867 1862
1868 1863 ## facility to let extension process additional data into an import patch
1869 1864 # list of identifier to be executed in order
1870 1865 extrapreimport = [] # run before commit
1871 1866 extrapostimport = [] # run after commit
1872 1867 # mapping from identifier to actual import function
1873 1868 #
1874 1869 # 'preimport' are run before the commit is made and are provided the following
1875 1870 # arguments:
1876 1871 # - repo: the localrepository instance,
1877 1872 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1878 1873 # - extra: the future extra dictionary of the changeset, please mutate it,
1879 1874 # - opts: the import options.
1880 1875 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1881 1876 # mutation of in memory commit and more. Feel free to rework the code to get
1882 1877 # there.
1883 1878 extrapreimportmap = {}
1884 1879 # 'postimport' are run after the commit is made and are provided the following
1885 1880 # argument:
1886 1881 # - ctx: the changectx created by import.
1887 1882 extrapostimportmap = {}
1888 1883
1889 1884
1890 1885 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1891 1886 """Utility function used by commands.import to import a single patch
1892 1887
1893 1888 This function is explicitly defined here to help the evolve extension to
1894 1889 wrap this part of the import logic.
1895 1890
1896 1891 The API is currently a bit ugly because it a simple code translation from
1897 1892 the import command. Feel free to make it better.
1898 1893
1899 1894 :patchdata: a dictionary containing parsed patch data (such as from
1900 1895 ``patch.extract()``)
1901 1896 :parents: nodes that will be parent of the created commit
1902 1897 :opts: the full dict of option passed to the import command
1903 1898 :msgs: list to save commit message to.
1904 1899 (used in case we need to save it when failing)
1905 1900 :updatefunc: a function that update a repo to a given node
1906 1901 updatefunc(<repo>, <node>)
1907 1902 """
1908 1903 # avoid cycle context -> subrepo -> cmdutil
1909 1904 from . import context
1910 1905
1911 1906 tmpname = patchdata.get(b'filename')
1912 1907 message = patchdata.get(b'message')
1913 1908 user = opts.get(b'user') or patchdata.get(b'user')
1914 1909 date = opts.get(b'date') or patchdata.get(b'date')
1915 1910 branch = patchdata.get(b'branch')
1916 1911 nodeid = patchdata.get(b'nodeid')
1917 1912 p1 = patchdata.get(b'p1')
1918 1913 p2 = patchdata.get(b'p2')
1919 1914
1920 1915 nocommit = opts.get(b'no_commit')
1921 1916 importbranch = opts.get(b'import_branch')
1922 1917 update = not opts.get(b'bypass')
1923 1918 strip = opts[b"strip"]
1924 1919 prefix = opts[b"prefix"]
1925 1920 sim = float(opts.get(b'similarity') or 0)
1926 1921
1927 1922 if not tmpname:
1928 1923 return None, None, False
1929 1924
1930 1925 rejects = False
1931 1926
1932 1927 cmdline_message = logmessage(ui, opts)
1933 1928 if cmdline_message:
1934 1929 # pickup the cmdline msg
1935 1930 message = cmdline_message
1936 1931 elif message:
1937 1932 # pickup the patch msg
1938 1933 message = message.strip()
1939 1934 else:
1940 1935 # launch the editor
1941 1936 message = None
1942 1937 ui.debug(b'message:\n%s\n' % (message or b''))
1943 1938
1944 1939 if len(parents) == 1:
1945 1940 parents.append(repo[nullrev])
1946 1941 if opts.get(b'exact'):
1947 1942 if not nodeid or not p1:
1948 1943 raise error.InputError(_(b'not a Mercurial patch'))
1949 1944 p1 = repo[p1]
1950 1945 p2 = repo[p2 or nullrev]
1951 1946 elif p2:
1952 1947 try:
1953 1948 p1 = repo[p1]
1954 1949 p2 = repo[p2]
1955 1950 # Without any options, consider p2 only if the
1956 1951 # patch is being applied on top of the recorded
1957 1952 # first parent.
1958 1953 if p1 != parents[0]:
1959 1954 p1 = parents[0]
1960 1955 p2 = repo[nullrev]
1961 1956 except error.RepoError:
1962 1957 p1, p2 = parents
1963 1958 if p2.rev() == nullrev:
1964 1959 ui.warn(
1965 1960 _(
1966 1961 b"warning: import the patch as a normal revision\n"
1967 1962 b"(use --exact to import the patch as a merge)\n"
1968 1963 )
1969 1964 )
1970 1965 else:
1971 1966 p1, p2 = parents
1972 1967
1973 1968 n = None
1974 1969 if update:
1975 1970 if p1 != parents[0]:
1976 1971 updatefunc(repo, p1.node())
1977 1972 if p2 != parents[1]:
1978 1973 repo.setparents(p1.node(), p2.node())
1979 1974
1980 1975 if opts.get(b'exact') or importbranch:
1981 1976 repo.dirstate.setbranch(branch or b'default')
1982 1977
1983 1978 partial = opts.get(b'partial', False)
1984 1979 files = set()
1985 1980 try:
1986 1981 patch.patch(
1987 1982 ui,
1988 1983 repo,
1989 1984 tmpname,
1990 1985 strip=strip,
1991 1986 prefix=prefix,
1992 1987 files=files,
1993 1988 eolmode=None,
1994 1989 similarity=sim / 100.0,
1995 1990 )
1996 1991 except error.PatchError as e:
1997 1992 if not partial:
1998 1993 raise error.Abort(pycompat.bytestr(e))
1999 1994 if partial:
2000 1995 rejects = True
2001 1996
2002 1997 files = list(files)
2003 1998 if nocommit:
2004 1999 if message:
2005 2000 msgs.append(message)
2006 2001 else:
2007 2002 if opts.get(b'exact') or p2:
2008 2003 # If you got here, you either use --force and know what
2009 2004 # you are doing or used --exact or a merge patch while
2010 2005 # being updated to its first parent.
2011 2006 m = None
2012 2007 else:
2013 2008 m = scmutil.matchfiles(repo, files or [])
2014 2009 editform = mergeeditform(repo[None], b'import.normal')
2015 2010 if opts.get(b'exact'):
2016 2011 editor = None
2017 2012 else:
2018 2013 editor = getcommiteditor(
2019 2014 editform=editform, **pycompat.strkwargs(opts)
2020 2015 )
2021 2016 extra = {}
2022 2017 for idfunc in extrapreimport:
2023 2018 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
2024 2019 overrides = {}
2025 2020 if partial:
2026 2021 overrides[(b'ui', b'allowemptycommit')] = True
2027 2022 if opts.get(b'secret'):
2028 2023 overrides[(b'phases', b'new-commit')] = b'secret'
2029 2024 with repo.ui.configoverride(overrides, b'import'):
2030 2025 n = repo.commit(
2031 2026 message, user, date, match=m, editor=editor, extra=extra
2032 2027 )
2033 2028 for idfunc in extrapostimport:
2034 2029 extrapostimportmap[idfunc](repo[n])
2035 2030 else:
2036 2031 if opts.get(b'exact') or importbranch:
2037 2032 branch = branch or b'default'
2038 2033 else:
2039 2034 branch = p1.branch()
2040 2035 store = patch.filestore()
2041 2036 try:
2042 2037 files = set()
2043 2038 try:
2044 2039 patch.patchrepo(
2045 2040 ui,
2046 2041 repo,
2047 2042 p1,
2048 2043 store,
2049 2044 tmpname,
2050 2045 strip,
2051 2046 prefix,
2052 2047 files,
2053 2048 eolmode=None,
2054 2049 )
2055 2050 except error.PatchError as e:
2056 2051 raise error.Abort(stringutil.forcebytestr(e))
2057 2052 if opts.get(b'exact'):
2058 2053 editor = None
2059 2054 else:
2060 2055 editor = getcommiteditor(editform=b'import.bypass')
2061 2056 memctx = context.memctx(
2062 2057 repo,
2063 2058 (p1.node(), p2.node()),
2064 2059 message,
2065 2060 files=files,
2066 2061 filectxfn=store,
2067 2062 user=user,
2068 2063 date=date,
2069 2064 branch=branch,
2070 2065 editor=editor,
2071 2066 )
2072 2067
2073 2068 overrides = {}
2074 2069 if opts.get(b'secret'):
2075 2070 overrides[(b'phases', b'new-commit')] = b'secret'
2076 2071 with repo.ui.configoverride(overrides, b'import'):
2077 2072 n = memctx.commit()
2078 2073 finally:
2079 2074 store.close()
2080 2075 if opts.get(b'exact') and nocommit:
2081 2076 # --exact with --no-commit is still useful in that it does merge
2082 2077 # and branch bits
2083 2078 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
2084 2079 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
2085 2080 raise error.Abort(_(b'patch is damaged or loses information'))
2086 2081 msg = _(b'applied to working directory')
2087 2082 if n:
2088 2083 # i18n: refers to a short changeset id
2089 2084 msg = _(b'created %s') % short(n)
2090 2085 return msg, n, rejects
2091 2086
2092 2087
2093 2088 # facility to let extensions include additional data in an exported patch
2094 2089 # list of identifiers to be executed in order
2095 2090 extraexport = []
2096 2091 # mapping from identifier to actual export function
2097 2092 # function as to return a string to be added to the header or None
2098 2093 # it is given two arguments (sequencenumber, changectx)
2099 2094 extraexportmap = {}
2100 2095
2101 2096
2102 2097 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
2103 2098 node = scmutil.binnode(ctx)
2104 2099 parents = [p.node() for p in ctx.parents() if p]
2105 2100 branch = ctx.branch()
2106 2101 if switch_parent:
2107 2102 parents.reverse()
2108 2103
2109 2104 if parents:
2110 2105 prev = parents[0]
2111 2106 else:
2112 2107 prev = repo.nullid
2113 2108
2114 2109 fm.context(ctx=ctx)
2115 2110 fm.plain(b'# HG changeset patch\n')
2116 2111 fm.write(b'user', b'# User %s\n', ctx.user())
2117 2112 fm.plain(b'# Date %d %d\n' % ctx.date())
2118 2113 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
2119 2114 fm.condwrite(
2120 2115 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
2121 2116 )
2122 2117 fm.write(b'node', b'# Node ID %s\n', hex(node))
2123 2118 fm.plain(b'# Parent %s\n' % hex(prev))
2124 2119 if len(parents) > 1:
2125 2120 fm.plain(b'# Parent %s\n' % hex(parents[1]))
2126 2121 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
2127 2122
2128 2123 # TODO: redesign extraexportmap function to support formatter
2129 2124 for headerid in extraexport:
2130 2125 header = extraexportmap[headerid](seqno, ctx)
2131 2126 if header is not None:
2132 2127 fm.plain(b'# %s\n' % header)
2133 2128
2134 2129 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
2135 2130 fm.plain(b'\n')
2136 2131
2137 2132 if fm.isplain():
2138 2133 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
2139 2134 for chunk, label in chunkiter:
2140 2135 fm.plain(chunk, label=label)
2141 2136 else:
2142 2137 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
2143 2138 # TODO: make it structured?
2144 2139 fm.data(diff=b''.join(chunkiter))
2145 2140
2146 2141
2147 2142 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
2148 2143 """Export changesets to stdout or a single file"""
2149 2144 for seqno, rev in enumerate(revs, 1):
2150 2145 ctx = repo[rev]
2151 2146 if not dest.startswith(b'<'):
2152 2147 repo.ui.note(b"%s\n" % dest)
2153 2148 fm.startitem()
2154 2149 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
2155 2150
2156 2151
2157 2152 def _exportfntemplate(
2158 2153 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
2159 2154 ):
2160 2155 """Export changesets to possibly multiple files"""
2161 2156 total = len(revs)
2162 2157 revwidth = max(len(str(rev)) for rev in revs)
2163 2158 filemap = util.sortdict() # filename: [(seqno, rev), ...]
2164 2159
2165 2160 for seqno, rev in enumerate(revs, 1):
2166 2161 ctx = repo[rev]
2167 2162 dest = makefilename(
2168 2163 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
2169 2164 )
2170 2165 filemap.setdefault(dest, []).append((seqno, rev))
2171 2166
2172 2167 for dest in filemap:
2173 2168 with formatter.maybereopen(basefm, dest) as fm:
2174 2169 repo.ui.note(b"%s\n" % dest)
2175 2170 for seqno, rev in filemap[dest]:
2176 2171 fm.startitem()
2177 2172 ctx = repo[rev]
2178 2173 _exportsingle(
2179 2174 repo, ctx, fm, match, switch_parent, seqno, diffopts
2180 2175 )
2181 2176
2182 2177
2183 2178 def _prefetchchangedfiles(repo, revs, match):
2184 2179 allfiles = set()
2185 2180 for rev in revs:
2186 2181 for file in repo[rev].files():
2187 2182 if not match or match(file):
2188 2183 allfiles.add(file)
2189 2184 match = scmutil.matchfiles(repo, allfiles)
2190 2185 revmatches = [(rev, match) for rev in revs]
2191 2186 scmutil.prefetchfiles(repo, revmatches)
2192 2187
2193 2188
2194 2189 def export(
2195 2190 repo,
2196 2191 revs,
2197 2192 basefm,
2198 2193 fntemplate=b'hg-%h.patch',
2199 2194 switch_parent=False,
2200 2195 opts=None,
2201 2196 match=None,
2202 2197 ):
2203 2198 """export changesets as hg patches
2204 2199
2205 2200 Args:
2206 2201 repo: The repository from which we're exporting revisions.
2207 2202 revs: A list of revisions to export as revision numbers.
2208 2203 basefm: A formatter to which patches should be written.
2209 2204 fntemplate: An optional string to use for generating patch file names.
2210 2205 switch_parent: If True, show diffs against second parent when not nullid.
2211 2206 Default is false, which always shows diff against p1.
2212 2207 opts: diff options to use for generating the patch.
2213 2208 match: If specified, only export changes to files matching this matcher.
2214 2209
2215 2210 Returns:
2216 2211 Nothing.
2217 2212
2218 2213 Side Effect:
2219 2214 "HG Changeset Patch" data is emitted to one of the following
2220 2215 destinations:
2221 2216 fntemplate specified: Each rev is written to a unique file named using
2222 2217 the given template.
2223 2218 Otherwise: All revs will be written to basefm.
2224 2219 """
2225 2220 _prefetchchangedfiles(repo, revs, match)
2226 2221
2227 2222 if not fntemplate:
2228 2223 _exportfile(
2229 2224 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2230 2225 )
2231 2226 else:
2232 2227 _exportfntemplate(
2233 2228 repo, revs, basefm, fntemplate, switch_parent, opts, match
2234 2229 )
2235 2230
2236 2231
2237 2232 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2238 2233 """Export changesets to the given file stream"""
2239 2234 _prefetchchangedfiles(repo, revs, match)
2240 2235
2241 2236 dest = getattr(fp, 'name', b'<unnamed>')
2242 2237 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2243 2238 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2244 2239
2245 2240
2246 2241 def showmarker(fm, marker, index=None):
2247 2242 """utility function to display obsolescence marker in a readable way
2248 2243
2249 2244 To be used by debug function."""
2250 2245 if index is not None:
2251 2246 fm.write(b'index', b'%i ', index)
2252 2247 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2253 2248 succs = marker.succnodes()
2254 2249 fm.condwrite(
2255 2250 succs,
2256 2251 b'succnodes',
2257 2252 b'%s ',
2258 2253 fm.formatlist(map(hex, succs), name=b'node'),
2259 2254 )
2260 2255 fm.write(b'flag', b'%X ', marker.flags())
2261 2256 parents = marker.parentnodes()
2262 2257 if parents is not None:
2263 2258 fm.write(
2264 2259 b'parentnodes',
2265 2260 b'{%s} ',
2266 2261 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2267 2262 )
2268 2263 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2269 2264 meta = marker.metadata().copy()
2270 2265 meta.pop(b'date', None)
2271 2266 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2272 2267 fm.write(
2273 2268 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2274 2269 )
2275 2270 fm.plain(b'\n')
2276 2271
2277 2272
2278 2273 def finddate(ui, repo, date):
2279 2274 """Find the tipmost changeset that matches the given date spec"""
2280 2275 mrevs = repo.revs(b'date(%s)', date)
2281 2276 try:
2282 2277 rev = mrevs.max()
2283 2278 except ValueError:
2284 2279 raise error.InputError(_(b"revision matching date not found"))
2285 2280
2286 2281 ui.status(
2287 2282 _(b"found revision %d from %s\n")
2288 2283 % (rev, dateutil.datestr(repo[rev].date()))
2289 2284 )
2290 2285 return b'%d' % rev
2291 2286
2292 2287
2293 2288 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2294 2289 bad = []
2295 2290
2296 2291 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2297 2292 names = []
2298 2293 wctx = repo[None]
2299 2294 cca = None
2300 2295 abort, warn = scmutil.checkportabilityalert(ui)
2301 2296 if abort or warn:
2302 2297 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2303 2298
2304 2299 match = repo.narrowmatch(match, includeexact=True)
2305 2300 badmatch = matchmod.badmatch(match, badfn)
2306 2301 dirstate = repo.dirstate
2307 2302 # We don't want to just call wctx.walk here, since it would return a lot of
2308 2303 # clean files, which we aren't interested in and takes time.
2309 2304 for f in sorted(
2310 2305 dirstate.walk(
2311 2306 badmatch,
2312 2307 subrepos=sorted(wctx.substate),
2313 2308 unknown=True,
2314 2309 ignored=False,
2315 2310 full=False,
2316 2311 )
2317 2312 ):
2318 2313 exact = match.exact(f)
2319 2314 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2320 2315 if cca:
2321 2316 cca(f)
2322 2317 names.append(f)
2323 2318 if ui.verbose or not exact:
2324 2319 ui.status(
2325 2320 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2326 2321 )
2327 2322
2328 2323 for subpath in sorted(wctx.substate):
2329 2324 sub = wctx.sub(subpath)
2330 2325 try:
2331 2326 submatch = matchmod.subdirmatcher(subpath, match)
2332 2327 subprefix = repo.wvfs.reljoin(prefix, subpath)
2333 2328 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2334 2329 if opts.get('subrepos'):
2335 2330 bad.extend(
2336 2331 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2337 2332 )
2338 2333 else:
2339 2334 bad.extend(
2340 2335 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2341 2336 )
2342 2337 except error.LookupError:
2343 2338 ui.status(
2344 2339 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2345 2340 )
2346 2341
2347 2342 if not opts.get('dry_run'):
2348 2343 rejected = wctx.add(names, prefix)
2349 2344 bad.extend(f for f in rejected if f in match.files())
2350 2345 return bad
2351 2346
2352 2347
2353 2348 def addwebdirpath(repo, serverpath, webconf):
2354 2349 webconf[serverpath] = repo.root
2355 2350 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2356 2351
2357 2352 for r in repo.revs(b'filelog("path:.hgsub")'):
2358 2353 ctx = repo[r]
2359 2354 for subpath in ctx.substate:
2360 2355 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2361 2356
2362 2357
2363 2358 def forget(
2364 2359 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2365 2360 ):
2366 2361 if dryrun and interactive:
2367 2362 raise error.InputError(
2368 2363 _(b"cannot specify both --dry-run and --interactive")
2369 2364 )
2370 2365 bad = []
2371 2366 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2372 2367 wctx = repo[None]
2373 2368 forgot = []
2374 2369
2375 2370 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2376 2371 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2377 2372 if explicitonly:
2378 2373 forget = [f for f in forget if match.exact(f)]
2379 2374
2380 2375 for subpath in sorted(wctx.substate):
2381 2376 sub = wctx.sub(subpath)
2382 2377 submatch = matchmod.subdirmatcher(subpath, match)
2383 2378 subprefix = repo.wvfs.reljoin(prefix, subpath)
2384 2379 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2385 2380 try:
2386 2381 subbad, subforgot = sub.forget(
2387 2382 submatch,
2388 2383 subprefix,
2389 2384 subuipathfn,
2390 2385 dryrun=dryrun,
2391 2386 interactive=interactive,
2392 2387 )
2393 2388 bad.extend([subpath + b'/' + f for f in subbad])
2394 2389 forgot.extend([subpath + b'/' + f for f in subforgot])
2395 2390 except error.LookupError:
2396 2391 ui.status(
2397 2392 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2398 2393 )
2399 2394
2400 2395 if not explicitonly:
2401 2396 for f in match.files():
2402 2397 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2403 2398 if f not in forgot:
2404 2399 if repo.wvfs.exists(f):
2405 2400 # Don't complain if the exact case match wasn't given.
2406 2401 # But don't do this until after checking 'forgot', so
2407 2402 # that subrepo files aren't normalized, and this op is
2408 2403 # purely from data cached by the status walk above.
2409 2404 if repo.dirstate.normalize(f) in repo.dirstate:
2410 2405 continue
2411 2406 ui.warn(
2412 2407 _(
2413 2408 b'not removing %s: '
2414 2409 b'file is already untracked\n'
2415 2410 )
2416 2411 % uipathfn(f)
2417 2412 )
2418 2413 bad.append(f)
2419 2414
2420 2415 if interactive:
2421 2416 responses = _(
2422 2417 b'[Ynsa?]'
2423 2418 b'$$ &Yes, forget this file'
2424 2419 b'$$ &No, skip this file'
2425 2420 b'$$ &Skip remaining files'
2426 2421 b'$$ Include &all remaining files'
2427 2422 b'$$ &? (display help)'
2428 2423 )
2429 2424 for filename in forget[:]:
2430 2425 r = ui.promptchoice(
2431 2426 _(b'forget %s %s') % (uipathfn(filename), responses)
2432 2427 )
2433 2428 if r == 4: # ?
2434 2429 while r == 4:
2435 2430 for c, t in ui.extractchoices(responses)[1]:
2436 2431 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2437 2432 r = ui.promptchoice(
2438 2433 _(b'forget %s %s') % (uipathfn(filename), responses)
2439 2434 )
2440 2435 if r == 0: # yes
2441 2436 continue
2442 2437 elif r == 1: # no
2443 2438 forget.remove(filename)
2444 2439 elif r == 2: # Skip
2445 2440 fnindex = forget.index(filename)
2446 2441 del forget[fnindex:]
2447 2442 break
2448 2443 elif r == 3: # All
2449 2444 break
2450 2445
2451 2446 for f in forget:
2452 2447 if ui.verbose or not match.exact(f) or interactive:
2453 2448 ui.status(
2454 2449 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2455 2450 )
2456 2451
2457 2452 if not dryrun:
2458 2453 rejected = wctx.forget(forget, prefix)
2459 2454 bad.extend(f for f in rejected if f in match.files())
2460 2455 forgot.extend(f for f in forget if f not in rejected)
2461 2456 return bad, forgot
2462 2457
2463 2458
2464 2459 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2465 2460 ret = 1
2466 2461
2467 2462 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2468 2463 if fm.isplain() and not needsfctx:
2469 2464 # Fast path. The speed-up comes from skipping the formatter, and batching
2470 2465 # calls to ui.write.
2471 2466 buf = []
2472 2467 for f in ctx.matches(m):
2473 2468 buf.append(fmt % uipathfn(f))
2474 2469 if len(buf) > 100:
2475 2470 ui.write(b''.join(buf))
2476 2471 del buf[:]
2477 2472 ret = 0
2478 2473 if buf:
2479 2474 ui.write(b''.join(buf))
2480 2475 else:
2481 2476 for f in ctx.matches(m):
2482 2477 fm.startitem()
2483 2478 fm.context(ctx=ctx)
2484 2479 if needsfctx:
2485 2480 fc = ctx[f]
2486 2481 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2487 2482 fm.data(path=f)
2488 2483 fm.plain(fmt % uipathfn(f))
2489 2484 ret = 0
2490 2485
2491 2486 for subpath in sorted(ctx.substate):
2492 2487 submatch = matchmod.subdirmatcher(subpath, m)
2493 2488 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2494 2489 if subrepos or m.exact(subpath) or any(submatch.files()):
2495 2490 sub = ctx.sub(subpath)
2496 2491 try:
2497 2492 recurse = m.exact(subpath) or subrepos
2498 2493 if (
2499 2494 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2500 2495 == 0
2501 2496 ):
2502 2497 ret = 0
2503 2498 except error.LookupError:
2504 2499 ui.status(
2505 2500 _(b"skipping missing subrepository: %s\n")
2506 2501 % uipathfn(subpath)
2507 2502 )
2508 2503
2509 2504 return ret
2510 2505
2511 2506
2512 2507 def remove(
2513 2508 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2514 2509 ):
2515 2510 ret = 0
2516 2511 s = repo.status(match=m, clean=True)
2517 2512 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2518 2513
2519 2514 wctx = repo[None]
2520 2515
2521 2516 if warnings is None:
2522 2517 warnings = []
2523 2518 warn = True
2524 2519 else:
2525 2520 warn = False
2526 2521
2527 2522 subs = sorted(wctx.substate)
2528 2523 progress = ui.makeprogress(
2529 2524 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2530 2525 )
2531 2526 for subpath in subs:
2532 2527 submatch = matchmod.subdirmatcher(subpath, m)
2533 2528 subprefix = repo.wvfs.reljoin(prefix, subpath)
2534 2529 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2535 2530 if subrepos or m.exact(subpath) or any(submatch.files()):
2536 2531 progress.increment()
2537 2532 sub = wctx.sub(subpath)
2538 2533 try:
2539 2534 if sub.removefiles(
2540 2535 submatch,
2541 2536 subprefix,
2542 2537 subuipathfn,
2543 2538 after,
2544 2539 force,
2545 2540 subrepos,
2546 2541 dryrun,
2547 2542 warnings,
2548 2543 ):
2549 2544 ret = 1
2550 2545 except error.LookupError:
2551 2546 warnings.append(
2552 2547 _(b"skipping missing subrepository: %s\n")
2553 2548 % uipathfn(subpath)
2554 2549 )
2555 2550 progress.complete()
2556 2551
2557 2552 # warn about failure to delete explicit files/dirs
2558 2553 deleteddirs = pathutil.dirs(deleted)
2559 2554 files = m.files()
2560 2555 progress = ui.makeprogress(
2561 2556 _(b'deleting'), total=len(files), unit=_(b'files')
2562 2557 )
2563 2558 for f in files:
2564 2559
2565 2560 def insubrepo():
2566 2561 for subpath in wctx.substate:
2567 2562 if f.startswith(subpath + b'/'):
2568 2563 return True
2569 2564 return False
2570 2565
2571 2566 progress.increment()
2572 2567 isdir = f in deleteddirs or wctx.hasdir(f)
2573 2568 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2574 2569 continue
2575 2570
2576 2571 if repo.wvfs.exists(f):
2577 2572 if repo.wvfs.isdir(f):
2578 2573 warnings.append(
2579 2574 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2580 2575 )
2581 2576 else:
2582 2577 warnings.append(
2583 2578 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2584 2579 )
2585 2580 # missing files will generate a warning elsewhere
2586 2581 ret = 1
2587 2582 progress.complete()
2588 2583
2589 2584 if force:
2590 2585 list = modified + deleted + clean + added
2591 2586 elif after:
2592 2587 list = deleted
2593 2588 remaining = modified + added + clean
2594 2589 progress = ui.makeprogress(
2595 2590 _(b'skipping'), total=len(remaining), unit=_(b'files')
2596 2591 )
2597 2592 for f in remaining:
2598 2593 progress.increment()
2599 2594 if ui.verbose or (f in files):
2600 2595 warnings.append(
2601 2596 _(b'not removing %s: file still exists\n') % uipathfn(f)
2602 2597 )
2603 2598 ret = 1
2604 2599 progress.complete()
2605 2600 else:
2606 2601 list = deleted + clean
2607 2602 progress = ui.makeprogress(
2608 2603 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2609 2604 )
2610 2605 for f in modified:
2611 2606 progress.increment()
2612 2607 warnings.append(
2613 2608 _(
2614 2609 b'not removing %s: file is modified (use -f'
2615 2610 b' to force removal)\n'
2616 2611 )
2617 2612 % uipathfn(f)
2618 2613 )
2619 2614 ret = 1
2620 2615 for f in added:
2621 2616 progress.increment()
2622 2617 warnings.append(
2623 2618 _(
2624 2619 b"not removing %s: file has been marked for add"
2625 2620 b" (use 'hg forget' to undo add)\n"
2626 2621 )
2627 2622 % uipathfn(f)
2628 2623 )
2629 2624 ret = 1
2630 2625 progress.complete()
2631 2626
2632 2627 list = sorted(list)
2633 2628 progress = ui.makeprogress(
2634 2629 _(b'deleting'), total=len(list), unit=_(b'files')
2635 2630 )
2636 2631 for f in list:
2637 2632 if ui.verbose or not m.exact(f):
2638 2633 progress.increment()
2639 2634 ui.status(
2640 2635 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2641 2636 )
2642 2637 progress.complete()
2643 2638
2644 2639 if not dryrun:
2645 2640 with repo.wlock():
2646 2641 if not after:
2647 2642 for f in list:
2648 2643 if f in added:
2649 2644 continue # we never unlink added files on remove
2650 2645 rmdir = repo.ui.configbool(
2651 2646 b'experimental', b'removeemptydirs'
2652 2647 )
2653 2648 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2654 2649 repo[None].forget(list)
2655 2650
2656 2651 if warn:
2657 2652 for warning in warnings:
2658 2653 ui.warn(warning)
2659 2654
2660 2655 return ret
2661 2656
2662 2657
2663 2658 def _catfmtneedsdata(fm):
2664 2659 return not fm.datahint() or b'data' in fm.datahint()
2665 2660
2666 2661
2667 2662 def _updatecatformatter(fm, ctx, matcher, path, decode):
2668 2663 """Hook for adding data to the formatter used by ``hg cat``.
2669 2664
2670 2665 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2671 2666 this method first."""
2672 2667
2673 2668 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2674 2669 # wasn't requested.
2675 2670 data = b''
2676 2671 if _catfmtneedsdata(fm):
2677 2672 data = ctx[path].data()
2678 2673 if decode:
2679 2674 data = ctx.repo().wwritedata(path, data)
2680 2675 fm.startitem()
2681 2676 fm.context(ctx=ctx)
2682 2677 fm.write(b'data', b'%s', data)
2683 2678 fm.data(path=path)
2684 2679
2685 2680
2686 2681 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2687 2682 err = 1
2688 2683 opts = pycompat.byteskwargs(opts)
2689 2684
2690 2685 def write(path):
2691 2686 filename = None
2692 2687 if fntemplate:
2693 2688 filename = makefilename(
2694 2689 ctx, fntemplate, pathname=os.path.join(prefix, path)
2695 2690 )
2696 2691 # attempt to create the directory if it does not already exist
2697 2692 try:
2698 2693 os.makedirs(os.path.dirname(filename))
2699 2694 except OSError:
2700 2695 pass
2701 2696 with formatter.maybereopen(basefm, filename) as fm:
2702 2697 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2703 2698
2704 2699 # Automation often uses hg cat on single files, so special case it
2705 2700 # for performance to avoid the cost of parsing the manifest.
2706 2701 if len(matcher.files()) == 1 and not matcher.anypats():
2707 2702 file = matcher.files()[0]
2708 2703 mfl = repo.manifestlog
2709 2704 mfnode = ctx.manifestnode()
2710 2705 try:
2711 2706 if mfnode and mfl[mfnode].find(file)[0]:
2712 2707 if _catfmtneedsdata(basefm):
2713 2708 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2714 2709 write(file)
2715 2710 return 0
2716 2711 except KeyError:
2717 2712 pass
2718 2713
2719 2714 if _catfmtneedsdata(basefm):
2720 2715 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2721 2716
2722 2717 for abs in ctx.walk(matcher):
2723 2718 write(abs)
2724 2719 err = 0
2725 2720
2726 2721 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2727 2722 for subpath in sorted(ctx.substate):
2728 2723 sub = ctx.sub(subpath)
2729 2724 try:
2730 2725 submatch = matchmod.subdirmatcher(subpath, matcher)
2731 2726 subprefix = os.path.join(prefix, subpath)
2732 2727 if not sub.cat(
2733 2728 submatch,
2734 2729 basefm,
2735 2730 fntemplate,
2736 2731 subprefix,
2737 2732 **pycompat.strkwargs(opts)
2738 2733 ):
2739 2734 err = 0
2740 2735 except error.RepoLookupError:
2741 2736 ui.status(
2742 2737 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2743 2738 )
2744 2739
2745 2740 return err
2746 2741
2747 2742
2748 2743 def commit(ui, repo, commitfunc, pats, opts):
2749 2744 '''commit the specified files or all outstanding changes'''
2750 2745 date = opts.get(b'date')
2751 2746 if date:
2752 2747 opts[b'date'] = dateutil.parsedate(date)
2753 2748 message = logmessage(ui, opts)
2754 2749 matcher = scmutil.match(repo[None], pats, opts)
2755 2750
2756 2751 dsguard = None
2757 2752 # extract addremove carefully -- this function can be called from a command
2758 2753 # that doesn't support addremove
2759 2754 if opts.get(b'addremove'):
2760 2755 dsguard = dirstateguard.dirstateguard(repo, b'commit')
2761 2756 with dsguard or util.nullcontextmanager():
2762 2757 if dsguard:
2763 2758 relative = scmutil.anypats(pats, opts)
2764 2759 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2765 2760 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
2766 2761 raise error.Abort(
2767 2762 _(b"failed to mark all new/missing files as added/removed")
2768 2763 )
2769 2764
2770 2765 return commitfunc(ui, repo, message, matcher, opts)
2771 2766
2772 2767
2773 2768 def samefile(f, ctx1, ctx2):
2774 2769 if f in ctx1.manifest():
2775 2770 a = ctx1.filectx(f)
2776 2771 if f in ctx2.manifest():
2777 2772 b = ctx2.filectx(f)
2778 2773 return not a.cmp(b) and a.flags() == b.flags()
2779 2774 else:
2780 2775 return False
2781 2776 else:
2782 2777 return f not in ctx2.manifest()
2783 2778
2784 2779
2785 2780 def amend(ui, repo, old, extra, pats, opts):
2786 2781 # avoid cycle context -> subrepo -> cmdutil
2787 2782 from . import context
2788 2783
2789 2784 # amend will reuse the existing user if not specified, but the obsolete
2790 2785 # marker creation requires that the current user's name is specified.
2791 2786 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2792 2787 ui.username() # raise exception if username not set
2793 2788
2794 2789 ui.note(_(b'amending changeset %s\n') % old)
2795 2790 base = old.p1()
2796 2791
2797 2792 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2798 2793 # Participating changesets:
2799 2794 #
2800 2795 # wctx o - workingctx that contains changes from working copy
2801 2796 # | to go into amending commit
2802 2797 # |
2803 2798 # old o - changeset to amend
2804 2799 # |
2805 2800 # base o - first parent of the changeset to amend
2806 2801 wctx = repo[None]
2807 2802
2808 2803 # Copy to avoid mutating input
2809 2804 extra = extra.copy()
2810 2805 # Update extra dict from amended commit (e.g. to preserve graft
2811 2806 # source)
2812 2807 extra.update(old.extra())
2813 2808
2814 2809 # Also update it from the from the wctx
2815 2810 extra.update(wctx.extra())
2816 2811
2817 2812 # date-only change should be ignored?
2818 2813 datemaydiffer = resolve_commit_options(ui, opts)
2819 2814 opts = pycompat.byteskwargs(opts)
2820 2815
2821 2816 date = old.date()
2822 2817 if opts.get(b'date'):
2823 2818 date = dateutil.parsedate(opts.get(b'date'))
2824 2819 user = opts.get(b'user') or old.user()
2825 2820
2826 2821 if len(old.parents()) > 1:
2827 2822 # ctx.files() isn't reliable for merges, so fall back to the
2828 2823 # slower repo.status() method
2829 2824 st = base.status(old)
2830 2825 files = set(st.modified) | set(st.added) | set(st.removed)
2831 2826 else:
2832 2827 files = set(old.files())
2833 2828
2834 2829 # add/remove the files to the working copy if the "addremove" option
2835 2830 # was specified.
2836 2831 matcher = scmutil.match(wctx, pats, opts)
2837 2832 relative = scmutil.anypats(pats, opts)
2838 2833 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2839 2834 if opts.get(b'addremove') and scmutil.addremove(
2840 2835 repo, matcher, b"", uipathfn, opts
2841 2836 ):
2842 2837 raise error.Abort(
2843 2838 _(b"failed to mark all new/missing files as added/removed")
2844 2839 )
2845 2840
2846 2841 # Check subrepos. This depends on in-place wctx._status update in
2847 2842 # subrepo.precommit(). To minimize the risk of this hack, we do
2848 2843 # nothing if .hgsub does not exist.
2849 2844 if b'.hgsub' in wctx or b'.hgsub' in old:
2850 2845 subs, commitsubs, newsubstate = subrepoutil.precommit(
2851 2846 ui, wctx, wctx._status, matcher
2852 2847 )
2853 2848 # amend should abort if commitsubrepos is enabled
2854 2849 assert not commitsubs
2855 2850 if subs:
2856 2851 subrepoutil.writestate(repo, newsubstate)
2857 2852
2858 2853 ms = mergestatemod.mergestate.read(repo)
2859 2854 mergeutil.checkunresolved(ms)
2860 2855
2861 2856 filestoamend = {f for f in wctx.files() if matcher(f)}
2862 2857
2863 2858 changes = len(filestoamend) > 0
2864 2859 if changes:
2865 2860 # Recompute copies (avoid recording a -> b -> a)
2866 2861 copied = copies.pathcopies(base, wctx, matcher)
2867 2862 if old.p2:
2868 2863 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2869 2864
2870 2865 # Prune files which were reverted by the updates: if old
2871 2866 # introduced file X and the file was renamed in the working
2872 2867 # copy, then those two files are the same and
2873 2868 # we can discard X from our list of files. Likewise if X
2874 2869 # was removed, it's no longer relevant. If X is missing (aka
2875 2870 # deleted), old X must be preserved.
2876 2871 files.update(filestoamend)
2877 2872 files = [
2878 2873 f
2879 2874 for f in files
2880 2875 if (f not in filestoamend or not samefile(f, wctx, base))
2881 2876 ]
2882 2877
2883 2878 def filectxfn(repo, ctx_, path):
2884 2879 try:
2885 2880 # If the file being considered is not amongst the files
2886 2881 # to be amended, we should return the file context from the
2887 2882 # old changeset. This avoids issues when only some files in
2888 2883 # the working copy are being amended but there are also
2889 2884 # changes to other files from the old changeset.
2890 2885 if path not in filestoamend:
2891 2886 return old.filectx(path)
2892 2887
2893 2888 # Return None for removed files.
2894 2889 if path in wctx.removed():
2895 2890 return None
2896 2891
2897 2892 fctx = wctx[path]
2898 2893 flags = fctx.flags()
2899 2894 mctx = context.memfilectx(
2900 2895 repo,
2901 2896 ctx_,
2902 2897 fctx.path(),
2903 2898 fctx.data(),
2904 2899 islink=b'l' in flags,
2905 2900 isexec=b'x' in flags,
2906 2901 copysource=copied.get(path),
2907 2902 )
2908 2903 return mctx
2909 2904 except KeyError:
2910 2905 return None
2911 2906
2912 2907 else:
2913 2908 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
2914 2909
2915 2910 # Use version of files as in the old cset
2916 2911 def filectxfn(repo, ctx_, path):
2917 2912 try:
2918 2913 return old.filectx(path)
2919 2914 except KeyError:
2920 2915 return None
2921 2916
2922 2917 # See if we got a message from -m or -l, if not, open the editor with
2923 2918 # the message of the changeset to amend.
2924 2919 message = logmessage(ui, opts)
2925 2920
2926 2921 editform = mergeeditform(old, b'commit.amend')
2927 2922
2928 2923 if not message:
2929 2924 message = old.description()
2930 2925 # Default if message isn't provided and --edit is not passed is to
2931 2926 # invoke editor, but allow --no-edit. If somehow we don't have any
2932 2927 # description, let's always start the editor.
2933 2928 doedit = not message or opts.get(b'edit') in [True, None]
2934 2929 else:
2935 2930 # Default if message is provided is to not invoke editor, but allow
2936 2931 # --edit.
2937 2932 doedit = opts.get(b'edit') is True
2938 2933 editor = getcommiteditor(edit=doedit, editform=editform)
2939 2934
2940 2935 pureextra = extra.copy()
2941 2936 extra[b'amend_source'] = old.hex()
2942 2937
2943 2938 new = context.memctx(
2944 2939 repo,
2945 2940 parents=[base.node(), old.p2().node()],
2946 2941 text=message,
2947 2942 files=files,
2948 2943 filectxfn=filectxfn,
2949 2944 user=user,
2950 2945 date=date,
2951 2946 extra=extra,
2952 2947 editor=editor,
2953 2948 )
2954 2949
2955 2950 newdesc = changelog.stripdesc(new.description())
2956 2951 if (
2957 2952 (not changes)
2958 2953 and newdesc == old.description()
2959 2954 and user == old.user()
2960 2955 and (date == old.date() or datemaydiffer)
2961 2956 and pureextra == old.extra()
2962 2957 ):
2963 2958 # nothing changed. continuing here would create a new node
2964 2959 # anyway because of the amend_source noise.
2965 2960 #
2966 2961 # This not what we expect from amend.
2967 2962 return old.node()
2968 2963
2969 2964 commitphase = None
2970 2965 if opts.get(b'secret'):
2971 2966 commitphase = phases.secret
2972 2967 newid = repo.commitctx(new)
2973 2968 ms.reset()
2974 2969
2975 2970 # Reroute the working copy parent to the new changeset
2976 2971 repo.setparents(newid, repo.nullid)
2977 2972
2978 2973 # Fixing the dirstate because localrepo.commitctx does not update
2979 2974 # it. This is rather convenient because we did not need to update
2980 2975 # the dirstate for all the files in the new commit which commitctx
2981 2976 # could have done if it updated the dirstate. Now, we can
2982 2977 # selectively update the dirstate only for the amended files.
2983 2978 dirstate = repo.dirstate
2984 2979
2985 2980 # Update the state of the files which were added and modified in the
2986 2981 # amend to "normal" in the dirstate. We need to use "normallookup" since
2987 2982 # the files may have changed since the command started; using "normal"
2988 2983 # would mark them as clean but with uncommitted contents.
2989 2984 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2990 2985 for f in normalfiles:
2991 2986 dirstate.normallookup(f)
2992 2987
2993 2988 # Update the state of files which were removed in the amend
2994 2989 # to "removed" in the dirstate.
2995 2990 removedfiles = set(wctx.removed()) & filestoamend
2996 2991 for f in removedfiles:
2997 2992 dirstate.drop(f)
2998 2993
2999 2994 mapping = {old.node(): (newid,)}
3000 2995 obsmetadata = None
3001 2996 if opts.get(b'note'):
3002 2997 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
3003 2998 backup = ui.configbool(b'rewrite', b'backup-bundle')
3004 2999 scmutil.cleanupnodes(
3005 3000 repo,
3006 3001 mapping,
3007 3002 b'amend',
3008 3003 metadata=obsmetadata,
3009 3004 fixphase=True,
3010 3005 targetphase=commitphase,
3011 3006 backup=backup,
3012 3007 )
3013 3008
3014 3009 return newid
3015 3010
3016 3011
3017 3012 def commiteditor(repo, ctx, subs, editform=b''):
3018 3013 if ctx.description():
3019 3014 return ctx.description()
3020 3015 return commitforceeditor(
3021 3016 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3022 3017 )
3023 3018
3024 3019
3025 3020 def commitforceeditor(
3026 3021 repo,
3027 3022 ctx,
3028 3023 subs,
3029 3024 finishdesc=None,
3030 3025 extramsg=None,
3031 3026 editform=b'',
3032 3027 unchangedmessagedetection=False,
3033 3028 ):
3034 3029 if not extramsg:
3035 3030 extramsg = _(b"Leave message empty to abort commit.")
3036 3031
3037 3032 forms = [e for e in editform.split(b'.') if e]
3038 3033 forms.insert(0, b'changeset')
3039 3034 templatetext = None
3040 3035 while forms:
3041 3036 ref = b'.'.join(forms)
3042 3037 if repo.ui.config(b'committemplate', ref):
3043 3038 templatetext = committext = buildcommittemplate(
3044 3039 repo, ctx, subs, extramsg, ref
3045 3040 )
3046 3041 break
3047 3042 forms.pop()
3048 3043 else:
3049 3044 committext = buildcommittext(repo, ctx, subs, extramsg)
3050 3045
3051 3046 # run editor in the repository root
3052 3047 olddir = encoding.getcwd()
3053 3048 os.chdir(repo.root)
3054 3049
3055 3050 # make in-memory changes visible to external process
3056 3051 tr = repo.currenttransaction()
3057 3052 repo.dirstate.write(tr)
3058 3053 pending = tr and tr.writepending() and repo.root
3059 3054
3060 3055 editortext = repo.ui.edit(
3061 3056 committext,
3062 3057 ctx.user(),
3063 3058 ctx.extra(),
3064 3059 editform=editform,
3065 3060 pending=pending,
3066 3061 repopath=repo.path,
3067 3062 action=b'commit',
3068 3063 )
3069 3064 text = editortext
3070 3065
3071 3066 # strip away anything below this special string (used for editors that want
3072 3067 # to display the diff)
3073 3068 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3074 3069 if stripbelow:
3075 3070 text = text[: stripbelow.start()]
3076 3071
3077 3072 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3078 3073 os.chdir(olddir)
3079 3074
3080 3075 if finishdesc:
3081 3076 text = finishdesc(text)
3082 3077 if not text.strip():
3083 3078 raise error.InputError(_(b"empty commit message"))
3084 3079 if unchangedmessagedetection and editortext == templatetext:
3085 3080 raise error.InputError(_(b"commit message unchanged"))
3086 3081
3087 3082 return text
3088 3083
3089 3084
3090 3085 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3091 3086 ui = repo.ui
3092 3087 spec = formatter.reference_templatespec(ref)
3093 3088 t = logcmdutil.changesettemplater(ui, repo, spec)
3094 3089 t.t.cache.update(
3095 3090 (k, templater.unquotestring(v))
3096 3091 for k, v in repo.ui.configitems(b'committemplate')
3097 3092 )
3098 3093
3099 3094 if not extramsg:
3100 3095 extramsg = b'' # ensure that extramsg is string
3101 3096
3102 3097 ui.pushbuffer()
3103 3098 t.show(ctx, extramsg=extramsg)
3104 3099 return ui.popbuffer()
3105 3100
3106 3101
3107 3102 def hgprefix(msg):
3108 3103 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3109 3104
3110 3105
3111 3106 def buildcommittext(repo, ctx, subs, extramsg):
3112 3107 edittext = []
3113 3108 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3114 3109 if ctx.description():
3115 3110 edittext.append(ctx.description())
3116 3111 edittext.append(b"")
3117 3112 edittext.append(b"") # Empty line between message and comments.
3118 3113 edittext.append(
3119 3114 hgprefix(
3120 3115 _(
3121 3116 b"Enter commit message."
3122 3117 b" Lines beginning with 'HG:' are removed."
3123 3118 )
3124 3119 )
3125 3120 )
3126 3121 edittext.append(hgprefix(extramsg))
3127 3122 edittext.append(b"HG: --")
3128 3123 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3129 3124 if ctx.p2():
3130 3125 edittext.append(hgprefix(_(b"branch merge")))
3131 3126 if ctx.branch():
3132 3127 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3133 3128 if bookmarks.isactivewdirparent(repo):
3134 3129 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3135 3130 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3136 3131 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3137 3132 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3138 3133 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3139 3134 if not added and not modified and not removed:
3140 3135 edittext.append(hgprefix(_(b"no files changed")))
3141 3136 edittext.append(b"")
3142 3137
3143 3138 return b"\n".join(edittext)
3144 3139
3145 3140
3146 3141 def commitstatus(repo, node, branch, bheads=None, tip=None, opts=None):
3147 3142 if opts is None:
3148 3143 opts = {}
3149 3144 ctx = repo[node]
3150 3145 parents = ctx.parents()
3151 3146
3152 3147 if tip is not None and repo.changelog.tip() == tip:
3153 3148 # avoid reporting something like "committed new head" when
3154 3149 # recommitting old changesets, and issue a helpful warning
3155 3150 # for most instances
3156 3151 repo.ui.warn(_(b"warning: commit already existed in the repository!\n"))
3157 3152 elif (
3158 3153 not opts.get(b'amend')
3159 3154 and bheads
3160 3155 and node not in bheads
3161 3156 and not any(
3162 3157 p.node() in bheads and p.branch() == branch for p in parents
3163 3158 )
3164 3159 ):
3165 3160 repo.ui.status(_(b'created new head\n'))
3166 3161 # The message is not printed for initial roots. For the other
3167 3162 # changesets, it is printed in the following situations:
3168 3163 #
3169 3164 # Par column: for the 2 parents with ...
3170 3165 # N: null or no parent
3171 3166 # B: parent is on another named branch
3172 3167 # C: parent is a regular non head changeset
3173 3168 # H: parent was a branch head of the current branch
3174 3169 # Msg column: whether we print "created new head" message
3175 3170 # In the following, it is assumed that there already exists some
3176 3171 # initial branch heads of the current branch, otherwise nothing is
3177 3172 # printed anyway.
3178 3173 #
3179 3174 # Par Msg Comment
3180 3175 # N N y additional topo root
3181 3176 #
3182 3177 # B N y additional branch root
3183 3178 # C N y additional topo head
3184 3179 # H N n usual case
3185 3180 #
3186 3181 # B B y weird additional branch root
3187 3182 # C B y branch merge
3188 3183 # H B n merge with named branch
3189 3184 #
3190 3185 # C C y additional head from merge
3191 3186 # C H n merge with a head
3192 3187 #
3193 3188 # H H n head merge: head count decreases
3194 3189
3195 3190 if not opts.get(b'close_branch'):
3196 3191 for r in parents:
3197 3192 if r.closesbranch() and r.branch() == branch:
3198 3193 repo.ui.status(
3199 3194 _(b'reopening closed branch head %d\n') % r.rev()
3200 3195 )
3201 3196
3202 3197 if repo.ui.debugflag:
3203 3198 repo.ui.write(
3204 3199 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3205 3200 )
3206 3201 elif repo.ui.verbose:
3207 3202 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3208 3203
3209 3204
3210 3205 def postcommitstatus(repo, pats, opts):
3211 3206 return repo.status(match=scmutil.match(repo[None], pats, opts))
3212 3207
3213 3208
3214 3209 def revert(ui, repo, ctx, *pats, **opts):
3215 3210 opts = pycompat.byteskwargs(opts)
3216 3211 parent, p2 = repo.dirstate.parents()
3217 3212 node = ctx.node()
3218 3213
3219 3214 mf = ctx.manifest()
3220 3215 if node == p2:
3221 3216 parent = p2
3222 3217
3223 3218 # need all matching names in dirstate and manifest of target rev,
3224 3219 # so have to walk both. do not print errors if files exist in one
3225 3220 # but not other. in both cases, filesets should be evaluated against
3226 3221 # workingctx to get consistent result (issue4497). this means 'set:**'
3227 3222 # cannot be used to select missing files from target rev.
3228 3223
3229 3224 # `names` is a mapping for all elements in working copy and target revision
3230 3225 # The mapping is in the form:
3231 3226 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3232 3227 names = {}
3233 3228 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3234 3229
3235 3230 with repo.wlock():
3236 3231 ## filling of the `names` mapping
3237 3232 # walk dirstate to fill `names`
3238 3233
3239 3234 interactive = opts.get(b'interactive', False)
3240 3235 wctx = repo[None]
3241 3236 m = scmutil.match(wctx, pats, opts)
3242 3237
3243 3238 # we'll need this later
3244 3239 targetsubs = sorted(s for s in wctx.substate if m(s))
3245 3240
3246 3241 if not m.always():
3247 3242 matcher = matchmod.badmatch(m, lambda x, y: False)
3248 3243 for abs in wctx.walk(matcher):
3249 3244 names[abs] = m.exact(abs)
3250 3245
3251 3246 # walk target manifest to fill `names`
3252 3247
3253 3248 def badfn(path, msg):
3254 3249 if path in names:
3255 3250 return
3256 3251 if path in ctx.substate:
3257 3252 return
3258 3253 path_ = path + b'/'
3259 3254 for f in names:
3260 3255 if f.startswith(path_):
3261 3256 return
3262 3257 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3263 3258
3264 3259 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3265 3260 if abs not in names:
3266 3261 names[abs] = m.exact(abs)
3267 3262
3268 3263 # Find status of all file in `names`.
3269 3264 m = scmutil.matchfiles(repo, names)
3270 3265
3271 3266 changes = repo.status(
3272 3267 node1=node, match=m, unknown=True, ignored=True, clean=True
3273 3268 )
3274 3269 else:
3275 3270 changes = repo.status(node1=node, match=m)
3276 3271 for kind in changes:
3277 3272 for abs in kind:
3278 3273 names[abs] = m.exact(abs)
3279 3274
3280 3275 m = scmutil.matchfiles(repo, names)
3281 3276
3282 3277 modified = set(changes.modified)
3283 3278 added = set(changes.added)
3284 3279 removed = set(changes.removed)
3285 3280 _deleted = set(changes.deleted)
3286 3281 unknown = set(changes.unknown)
3287 3282 unknown.update(changes.ignored)
3288 3283 clean = set(changes.clean)
3289 3284 modadded = set()
3290 3285
3291 3286 # We need to account for the state of the file in the dirstate,
3292 3287 # even when we revert against something else than parent. This will
3293 3288 # slightly alter the behavior of revert (doing back up or not, delete
3294 3289 # or just forget etc).
3295 3290 if parent == node:
3296 3291 dsmodified = modified
3297 3292 dsadded = added
3298 3293 dsremoved = removed
3299 3294 # store all local modifications, useful later for rename detection
3300 3295 localchanges = dsmodified | dsadded
3301 3296 modified, added, removed = set(), set(), set()
3302 3297 else:
3303 3298 changes = repo.status(node1=parent, match=m)
3304 3299 dsmodified = set(changes.modified)
3305 3300 dsadded = set(changes.added)
3306 3301 dsremoved = set(changes.removed)
3307 3302 # store all local modifications, useful later for rename detection
3308 3303 localchanges = dsmodified | dsadded
3309 3304
3310 3305 # only take into account for removes between wc and target
3311 3306 clean |= dsremoved - removed
3312 3307 dsremoved &= removed
3313 3308 # distinct between dirstate remove and other
3314 3309 removed -= dsremoved
3315 3310
3316 3311 modadded = added & dsmodified
3317 3312 added -= modadded
3318 3313
3319 3314 # tell newly modified apart.
3320 3315 dsmodified &= modified
3321 3316 dsmodified |= modified & dsadded # dirstate added may need backup
3322 3317 modified -= dsmodified
3323 3318
3324 3319 # We need to wait for some post-processing to update this set
3325 3320 # before making the distinction. The dirstate will be used for
3326 3321 # that purpose.
3327 3322 dsadded = added
3328 3323
3329 3324 # in case of merge, files that are actually added can be reported as
3330 3325 # modified, we need to post process the result
3331 3326 if p2 != repo.nullid:
3332 3327 mergeadd = set(dsmodified)
3333 3328 for path in dsmodified:
3334 3329 if path in mf:
3335 3330 mergeadd.remove(path)
3336 3331 dsadded |= mergeadd
3337 3332 dsmodified -= mergeadd
3338 3333
3339 3334 # if f is a rename, update `names` to also revert the source
3340 3335 for f in localchanges:
3341 3336 src = repo.dirstate.copied(f)
3342 3337 # XXX should we check for rename down to target node?
3343 3338 if src and src not in names and repo.dirstate[src] == b'r':
3344 3339 dsremoved.add(src)
3345 3340 names[src] = True
3346 3341
3347 3342 # determine the exact nature of the deleted changesets
3348 3343 deladded = set(_deleted)
3349 3344 for path in _deleted:
3350 3345 if path in mf:
3351 3346 deladded.remove(path)
3352 3347 deleted = _deleted - deladded
3353 3348
3354 3349 # distinguish between file to forget and the other
3355 3350 added = set()
3356 3351 for abs in dsadded:
3357 3352 if repo.dirstate[abs] != b'a':
3358 3353 added.add(abs)
3359 3354 dsadded -= added
3360 3355
3361 3356 for abs in deladded:
3362 3357 if repo.dirstate[abs] == b'a':
3363 3358 dsadded.add(abs)
3364 3359 deladded -= dsadded
3365 3360
3366 3361 # For files marked as removed, we check if an unknown file is present at
3367 3362 # the same path. If a such file exists it may need to be backed up.
3368 3363 # Making the distinction at this stage helps have simpler backup
3369 3364 # logic.
3370 3365 removunk = set()
3371 3366 for abs in removed:
3372 3367 target = repo.wjoin(abs)
3373 3368 if os.path.lexists(target):
3374 3369 removunk.add(abs)
3375 3370 removed -= removunk
3376 3371
3377 3372 dsremovunk = set()
3378 3373 for abs in dsremoved:
3379 3374 target = repo.wjoin(abs)
3380 3375 if os.path.lexists(target):
3381 3376 dsremovunk.add(abs)
3382 3377 dsremoved -= dsremovunk
3383 3378
3384 3379 # action to be actually performed by revert
3385 3380 # (<list of file>, message>) tuple
3386 3381 actions = {
3387 3382 b'revert': ([], _(b'reverting %s\n')),
3388 3383 b'add': ([], _(b'adding %s\n')),
3389 3384 b'remove': ([], _(b'removing %s\n')),
3390 3385 b'drop': ([], _(b'removing %s\n')),
3391 3386 b'forget': ([], _(b'forgetting %s\n')),
3392 3387 b'undelete': ([], _(b'undeleting %s\n')),
3393 3388 b'noop': (None, _(b'no changes needed to %s\n')),
3394 3389 b'unknown': (None, _(b'file not managed: %s\n')),
3395 3390 }
3396 3391
3397 3392 # "constant" that convey the backup strategy.
3398 3393 # All set to `discard` if `no-backup` is set do avoid checking
3399 3394 # no_backup lower in the code.
3400 3395 # These values are ordered for comparison purposes
3401 3396 backupinteractive = 3 # do backup if interactively modified
3402 3397 backup = 2 # unconditionally do backup
3403 3398 check = 1 # check if the existing file differs from target
3404 3399 discard = 0 # never do backup
3405 3400 if opts.get(b'no_backup'):
3406 3401 backupinteractive = backup = check = discard
3407 3402 if interactive:
3408 3403 dsmodifiedbackup = backupinteractive
3409 3404 else:
3410 3405 dsmodifiedbackup = backup
3411 3406 tobackup = set()
3412 3407
3413 3408 backupanddel = actions[b'remove']
3414 3409 if not opts.get(b'no_backup'):
3415 3410 backupanddel = actions[b'drop']
3416 3411
3417 3412 disptable = (
3418 3413 # dispatch table:
3419 3414 # file state
3420 3415 # action
3421 3416 # make backup
3422 3417 ## Sets that results that will change file on disk
3423 3418 # Modified compared to target, no local change
3424 3419 (modified, actions[b'revert'], discard),
3425 3420 # Modified compared to target, but local file is deleted
3426 3421 (deleted, actions[b'revert'], discard),
3427 3422 # Modified compared to target, local change
3428 3423 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3429 3424 # Added since target
3430 3425 (added, actions[b'remove'], discard),
3431 3426 # Added in working directory
3432 3427 (dsadded, actions[b'forget'], discard),
3433 3428 # Added since target, have local modification
3434 3429 (modadded, backupanddel, backup),
3435 3430 # Added since target but file is missing in working directory
3436 3431 (deladded, actions[b'drop'], discard),
3437 3432 # Removed since target, before working copy parent
3438 3433 (removed, actions[b'add'], discard),
3439 3434 # Same as `removed` but an unknown file exists at the same path
3440 3435 (removunk, actions[b'add'], check),
3441 3436 # Removed since targe, marked as such in working copy parent
3442 3437 (dsremoved, actions[b'undelete'], discard),
3443 3438 # Same as `dsremoved` but an unknown file exists at the same path
3444 3439 (dsremovunk, actions[b'undelete'], check),
3445 3440 ## the following sets does not result in any file changes
3446 3441 # File with no modification
3447 3442 (clean, actions[b'noop'], discard),
3448 3443 # Existing file, not tracked anywhere
3449 3444 (unknown, actions[b'unknown'], discard),
3450 3445 )
3451 3446
3452 3447 for abs, exact in sorted(names.items()):
3453 3448 # target file to be touch on disk (relative to cwd)
3454 3449 target = repo.wjoin(abs)
3455 3450 # search the entry in the dispatch table.
3456 3451 # if the file is in any of these sets, it was touched in the working
3457 3452 # directory parent and we are sure it needs to be reverted.
3458 3453 for table, (xlist, msg), dobackup in disptable:
3459 3454 if abs not in table:
3460 3455 continue
3461 3456 if xlist is not None:
3462 3457 xlist.append(abs)
3463 3458 if dobackup:
3464 3459 # If in interactive mode, don't automatically create
3465 3460 # .orig files (issue4793)
3466 3461 if dobackup == backupinteractive:
3467 3462 tobackup.add(abs)
3468 3463 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3469 3464 absbakname = scmutil.backuppath(ui, repo, abs)
3470 3465 bakname = os.path.relpath(
3471 3466 absbakname, start=repo.root
3472 3467 )
3473 3468 ui.note(
3474 3469 _(b'saving current version of %s as %s\n')
3475 3470 % (uipathfn(abs), uipathfn(bakname))
3476 3471 )
3477 3472 if not opts.get(b'dry_run'):
3478 3473 if interactive:
3479 3474 util.copyfile(target, absbakname)
3480 3475 else:
3481 3476 util.rename(target, absbakname)
3482 3477 if opts.get(b'dry_run'):
3483 3478 if ui.verbose or not exact:
3484 3479 ui.status(msg % uipathfn(abs))
3485 3480 elif exact:
3486 3481 ui.warn(msg % uipathfn(abs))
3487 3482 break
3488 3483
3489 3484 if not opts.get(b'dry_run'):
3490 3485 needdata = (b'revert', b'add', b'undelete')
3491 3486 oplist = [actions[name][0] for name in needdata]
3492 3487 prefetch = scmutil.prefetchfiles
3493 3488 matchfiles = scmutil.matchfiles(
3494 3489 repo, [f for sublist in oplist for f in sublist]
3495 3490 )
3496 3491 prefetch(
3497 3492 repo,
3498 3493 [(ctx.rev(), matchfiles)],
3499 3494 )
3500 3495 match = scmutil.match(repo[None], pats)
3501 3496 _performrevert(
3502 3497 repo,
3503 3498 ctx,
3504 3499 names,
3505 3500 uipathfn,
3506 3501 actions,
3507 3502 match,
3508 3503 interactive,
3509 3504 tobackup,
3510 3505 )
3511 3506
3512 3507 if targetsubs:
3513 3508 # Revert the subrepos on the revert list
3514 3509 for sub in targetsubs:
3515 3510 try:
3516 3511 wctx.sub(sub).revert(
3517 3512 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3518 3513 )
3519 3514 except KeyError:
3520 3515 raise error.Abort(
3521 3516 b"subrepository '%s' does not exist in %s!"
3522 3517 % (sub, short(ctx.node()))
3523 3518 )
3524 3519
3525 3520
3526 3521 def _performrevert(
3527 3522 repo,
3528 3523 ctx,
3529 3524 names,
3530 3525 uipathfn,
3531 3526 actions,
3532 3527 match,
3533 3528 interactive=False,
3534 3529 tobackup=None,
3535 3530 ):
3536 3531 """function that actually perform all the actions computed for revert
3537 3532
3538 3533 This is an independent function to let extension to plug in and react to
3539 3534 the imminent revert.
3540 3535
3541 3536 Make sure you have the working directory locked when calling this function.
3542 3537 """
3543 3538 parent, p2 = repo.dirstate.parents()
3544 3539 node = ctx.node()
3545 3540 excluded_files = []
3546 3541
3547 3542 def checkout(f):
3548 3543 fc = ctx[f]
3549 3544 repo.wwrite(f, fc.data(), fc.flags())
3550 3545
3551 3546 def doremove(f):
3552 3547 try:
3553 3548 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3554 3549 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3555 3550 except OSError:
3556 3551 pass
3557 3552 repo.dirstate.remove(f)
3558 3553
3559 3554 def prntstatusmsg(action, f):
3560 3555 exact = names[f]
3561 3556 if repo.ui.verbose or not exact:
3562 3557 repo.ui.status(actions[action][1] % uipathfn(f))
3563 3558
3564 3559 audit_path = pathutil.pathauditor(repo.root, cached=True)
3565 3560 for f in actions[b'forget'][0]:
3566 3561 if interactive:
3567 3562 choice = repo.ui.promptchoice(
3568 3563 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3569 3564 )
3570 3565 if choice == 0:
3571 3566 prntstatusmsg(b'forget', f)
3572 3567 repo.dirstate.drop(f)
3573 3568 else:
3574 3569 excluded_files.append(f)
3575 3570 else:
3576 3571 prntstatusmsg(b'forget', f)
3577 3572 repo.dirstate.drop(f)
3578 3573 for f in actions[b'remove'][0]:
3579 3574 audit_path(f)
3580 3575 if interactive:
3581 3576 choice = repo.ui.promptchoice(
3582 3577 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3583 3578 )
3584 3579 if choice == 0:
3585 3580 prntstatusmsg(b'remove', f)
3586 3581 doremove(f)
3587 3582 else:
3588 3583 excluded_files.append(f)
3589 3584 else:
3590 3585 prntstatusmsg(b'remove', f)
3591 3586 doremove(f)
3592 3587 for f in actions[b'drop'][0]:
3593 3588 audit_path(f)
3594 3589 prntstatusmsg(b'drop', f)
3595 3590 repo.dirstate.remove(f)
3596 3591
3597 3592 normal = None
3598 3593 if node == parent:
3599 3594 # We're reverting to our parent. If possible, we'd like status
3600 3595 # to report the file as clean. We have to use normallookup for
3601 3596 # merges to avoid losing information about merged/dirty files.
3602 3597 if p2 != repo.nullid:
3603 3598 normal = repo.dirstate.normallookup
3604 3599 else:
3605 3600 normal = repo.dirstate.normal
3606 3601
3607 3602 newlyaddedandmodifiedfiles = set()
3608 3603 if interactive:
3609 3604 # Prompt the user for changes to revert
3610 3605 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3611 3606 m = scmutil.matchfiles(repo, torevert)
3612 3607 diffopts = patch.difffeatureopts(
3613 3608 repo.ui,
3614 3609 whitespace=True,
3615 3610 section=b'commands',
3616 3611 configprefix=b'revert.interactive.',
3617 3612 )
3618 3613 diffopts.nodates = True
3619 3614 diffopts.git = True
3620 3615 operation = b'apply'
3621 3616 if node == parent:
3622 3617 if repo.ui.configbool(
3623 3618 b'experimental', b'revert.interactive.select-to-keep'
3624 3619 ):
3625 3620 operation = b'keep'
3626 3621 else:
3627 3622 operation = b'discard'
3628 3623
3629 3624 if operation == b'apply':
3630 3625 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3631 3626 else:
3632 3627 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3633 originalchunks = patch.parsepatch(diff)
3628 original_headers = patch.parsepatch(diff)
3634 3629
3635 3630 try:
3636 3631
3637 3632 chunks, opts = recordfilter(
3638 repo.ui, originalchunks, match, operation=operation
3633 repo.ui, original_headers, match, operation=operation
3639 3634 )
3640 3635 if operation == b'discard':
3641 3636 chunks = patch.reversehunks(chunks)
3642 3637
3643 3638 except error.PatchError as err:
3644 3639 raise error.Abort(_(b'error parsing patch: %s') % err)
3645 3640
3646 3641 # FIXME: when doing an interactive revert of a copy, there's no way of
3647 3642 # performing a partial revert of the added file, the only option is
3648 3643 # "remove added file <name> (Yn)?", so we don't need to worry about the
3649 3644 # alsorestore value. Ideally we'd be able to partially revert
3650 3645 # copied/renamed files.
3651 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3652 chunks, originalchunks
3653 )
3646 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(chunks)
3654 3647 if tobackup is None:
3655 3648 tobackup = set()
3656 3649 # Apply changes
3657 3650 fp = stringio()
3658 3651 # chunks are serialized per file, but files aren't sorted
3659 3652 for f in sorted({c.header.filename() for c in chunks if ishunk(c)}):
3660 3653 prntstatusmsg(b'revert', f)
3661 3654 files = set()
3662 3655 for c in chunks:
3663 3656 if ishunk(c):
3664 3657 abs = c.header.filename()
3665 3658 # Create a backup file only if this hunk should be backed up
3666 3659 if c.header.filename() in tobackup:
3667 3660 target = repo.wjoin(abs)
3668 3661 bakname = scmutil.backuppath(repo.ui, repo, abs)
3669 3662 util.copyfile(target, bakname)
3670 3663 tobackup.remove(abs)
3671 3664 if abs not in files:
3672 3665 files.add(abs)
3673 3666 if operation == b'keep':
3674 3667 checkout(abs)
3675 3668 c.write(fp)
3676 3669 dopatch = fp.tell()
3677 3670 fp.seek(0)
3678 3671 if dopatch:
3679 3672 try:
3680 3673 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3681 3674 except error.PatchError as err:
3682 3675 raise error.Abort(pycompat.bytestr(err))
3683 3676 del fp
3684 3677 else:
3685 3678 for f in actions[b'revert'][0]:
3686 3679 prntstatusmsg(b'revert', f)
3687 3680 checkout(f)
3688 3681 if normal:
3689 3682 normal(f)
3690 3683
3691 3684 for f in actions[b'add'][0]:
3692 3685 # Don't checkout modified files, they are already created by the diff
3693 3686 if f not in newlyaddedandmodifiedfiles:
3694 3687 prntstatusmsg(b'add', f)
3695 3688 checkout(f)
3696 3689 repo.dirstate.add(f)
3697 3690
3698 3691 normal = repo.dirstate.normallookup
3699 3692 if node == parent and p2 == repo.nullid:
3700 3693 normal = repo.dirstate.normal
3701 3694 for f in actions[b'undelete'][0]:
3702 3695 if interactive:
3703 3696 choice = repo.ui.promptchoice(
3704 3697 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3705 3698 )
3706 3699 if choice == 0:
3707 3700 prntstatusmsg(b'undelete', f)
3708 3701 checkout(f)
3709 3702 normal(f)
3710 3703 else:
3711 3704 excluded_files.append(f)
3712 3705 else:
3713 3706 prntstatusmsg(b'undelete', f)
3714 3707 checkout(f)
3715 3708 normal(f)
3716 3709
3717 3710 copied = copies.pathcopies(repo[parent], ctx)
3718 3711
3719 3712 for f in (
3720 3713 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3721 3714 ):
3722 3715 if f in copied:
3723 3716 repo.dirstate.copy(copied[f], f)
3724 3717
3725 3718
3726 3719 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3727 3720 # commands.outgoing. "missing" is "missing" of the result of
3728 3721 # "findcommonoutgoing()"
3729 3722 outgoinghooks = util.hooks()
3730 3723
3731 3724 # a list of (ui, repo) functions called by commands.summary
3732 3725 summaryhooks = util.hooks()
3733 3726
3734 3727 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3735 3728 #
3736 3729 # functions should return tuple of booleans below, if 'changes' is None:
3737 3730 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3738 3731 #
3739 3732 # otherwise, 'changes' is a tuple of tuples below:
3740 3733 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3741 3734 # - (desturl, destbranch, destpeer, outgoing)
3742 3735 summaryremotehooks = util.hooks()
3743 3736
3744 3737
3745 3738 def checkunfinished(repo, commit=False, skipmerge=False):
3746 3739 """Look for an unfinished multistep operation, like graft, and abort
3747 3740 if found. It's probably good to check this right before
3748 3741 bailifchanged().
3749 3742 """
3750 3743 # Check for non-clearable states first, so things like rebase will take
3751 3744 # precedence over update.
3752 3745 for state in statemod._unfinishedstates:
3753 3746 if (
3754 3747 state._clearable
3755 3748 or (commit and state._allowcommit)
3756 3749 or state._reportonly
3757 3750 ):
3758 3751 continue
3759 3752 if state.isunfinished(repo):
3760 3753 raise error.StateError(state.msg(), hint=state.hint())
3761 3754
3762 3755 for s in statemod._unfinishedstates:
3763 3756 if (
3764 3757 not s._clearable
3765 3758 or (commit and s._allowcommit)
3766 3759 or (s._opname == b'merge' and skipmerge)
3767 3760 or s._reportonly
3768 3761 ):
3769 3762 continue
3770 3763 if s.isunfinished(repo):
3771 3764 raise error.StateError(s.msg(), hint=s.hint())
3772 3765
3773 3766
3774 3767 def clearunfinished(repo):
3775 3768 """Check for unfinished operations (as above), and clear the ones
3776 3769 that are clearable.
3777 3770 """
3778 3771 for state in statemod._unfinishedstates:
3779 3772 if state._reportonly:
3780 3773 continue
3781 3774 if not state._clearable and state.isunfinished(repo):
3782 3775 raise error.StateError(state.msg(), hint=state.hint())
3783 3776
3784 3777 for s in statemod._unfinishedstates:
3785 3778 if s._opname == b'merge' or s._reportonly:
3786 3779 continue
3787 3780 if s._clearable and s.isunfinished(repo):
3788 3781 util.unlink(repo.vfs.join(s._fname))
3789 3782
3790 3783
3791 3784 def getunfinishedstate(repo):
3792 3785 """Checks for unfinished operations and returns statecheck object
3793 3786 for it"""
3794 3787 for state in statemod._unfinishedstates:
3795 3788 if state.isunfinished(repo):
3796 3789 return state
3797 3790 return None
3798 3791
3799 3792
3800 3793 def howtocontinue(repo):
3801 3794 """Check for an unfinished operation and return the command to finish
3802 3795 it.
3803 3796
3804 3797 statemod._unfinishedstates list is checked for an unfinished operation
3805 3798 and the corresponding message to finish it is generated if a method to
3806 3799 continue is supported by the operation.
3807 3800
3808 3801 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3809 3802 a boolean.
3810 3803 """
3811 3804 contmsg = _(b"continue: %s")
3812 3805 for state in statemod._unfinishedstates:
3813 3806 if not state._continueflag:
3814 3807 continue
3815 3808 if state.isunfinished(repo):
3816 3809 return contmsg % state.continuemsg(), True
3817 3810 if repo[None].dirty(missing=True, merge=False, branch=False):
3818 3811 return contmsg % _(b"hg commit"), False
3819 3812 return None, None
3820 3813
3821 3814
3822 3815 def checkafterresolved(repo):
3823 3816 """Inform the user about the next action after completing hg resolve
3824 3817
3825 3818 If there's a an unfinished operation that supports continue flag,
3826 3819 howtocontinue will yield repo.ui.warn as the reporter.
3827 3820
3828 3821 Otherwise, it will yield repo.ui.note.
3829 3822 """
3830 3823 msg, warning = howtocontinue(repo)
3831 3824 if msg is not None:
3832 3825 if warning:
3833 3826 repo.ui.warn(b"%s\n" % msg)
3834 3827 else:
3835 3828 repo.ui.note(b"%s\n" % msg)
3836 3829
3837 3830
3838 3831 def wrongtooltocontinue(repo, task):
3839 3832 """Raise an abort suggesting how to properly continue if there is an
3840 3833 active task.
3841 3834
3842 3835 Uses howtocontinue() to find the active task.
3843 3836
3844 3837 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3845 3838 a hint.
3846 3839 """
3847 3840 after = howtocontinue(repo)
3848 3841 hint = None
3849 3842 if after[1]:
3850 3843 hint = after[0]
3851 3844 raise error.StateError(_(b'no %s in progress') % task, hint=hint)
3852 3845
3853 3846
3854 3847 def abortgraft(ui, repo, graftstate):
3855 3848 """abort the interrupted graft and rollbacks to the state before interrupted
3856 3849 graft"""
3857 3850 if not graftstate.exists():
3858 3851 raise error.StateError(_(b"no interrupted graft to abort"))
3859 3852 statedata = readgraftstate(repo, graftstate)
3860 3853 newnodes = statedata.get(b'newnodes')
3861 3854 if newnodes is None:
3862 3855 # and old graft state which does not have all the data required to abort
3863 3856 # the graft
3864 3857 raise error.Abort(_(b"cannot abort using an old graftstate"))
3865 3858
3866 3859 # changeset from which graft operation was started
3867 3860 if len(newnodes) > 0:
3868 3861 startctx = repo[newnodes[0]].p1()
3869 3862 else:
3870 3863 startctx = repo[b'.']
3871 3864 # whether to strip or not
3872 3865 cleanup = False
3873 3866
3874 3867 if newnodes:
3875 3868 newnodes = [repo[r].rev() for r in newnodes]
3876 3869 cleanup = True
3877 3870 # checking that none of the newnodes turned public or is public
3878 3871 immutable = [c for c in newnodes if not repo[c].mutable()]
3879 3872 if immutable:
3880 3873 repo.ui.warn(
3881 3874 _(b"cannot clean up public changesets %s\n")
3882 3875 % b', '.join(bytes(repo[r]) for r in immutable),
3883 3876 hint=_(b"see 'hg help phases' for details"),
3884 3877 )
3885 3878 cleanup = False
3886 3879
3887 3880 # checking that no new nodes are created on top of grafted revs
3888 3881 desc = set(repo.changelog.descendants(newnodes))
3889 3882 if desc - set(newnodes):
3890 3883 repo.ui.warn(
3891 3884 _(
3892 3885 b"new changesets detected on destination "
3893 3886 b"branch, can't strip\n"
3894 3887 )
3895 3888 )
3896 3889 cleanup = False
3897 3890
3898 3891 if cleanup:
3899 3892 with repo.wlock(), repo.lock():
3900 3893 mergemod.clean_update(startctx)
3901 3894 # stripping the new nodes created
3902 3895 strippoints = [
3903 3896 c.node() for c in repo.set(b"roots(%ld)", newnodes)
3904 3897 ]
3905 3898 repair.strip(repo.ui, repo, strippoints, backup=False)
3906 3899
3907 3900 if not cleanup:
3908 3901 # we don't update to the startnode if we can't strip
3909 3902 startctx = repo[b'.']
3910 3903 mergemod.clean_update(startctx)
3911 3904
3912 3905 ui.status(_(b"graft aborted\n"))
3913 3906 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
3914 3907 graftstate.delete()
3915 3908 return 0
3916 3909
3917 3910
3918 3911 def readgraftstate(repo, graftstate):
3919 3912 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
3920 3913 """read the graft state file and return a dict of the data stored in it"""
3921 3914 try:
3922 3915 return graftstate.read()
3923 3916 except error.CorruptedState:
3924 3917 nodes = repo.vfs.read(b'graftstate').splitlines()
3925 3918 return {b'nodes': nodes}
3926 3919
3927 3920
3928 3921 def hgabortgraft(ui, repo):
3929 3922 """abort logic for aborting graft using 'hg abort'"""
3930 3923 with repo.wlock():
3931 3924 graftstate = statemod.cmdstate(repo, b'graftstate')
3932 3925 return abortgraft(ui, repo, graftstate)
@@ -1,1984 +1,2022
1 1 Set up a repo
2 2
3 3 $ cat <<EOF >> $HGRCPATH
4 4 > [ui]
5 5 > interactive = true
6 6 > [extensions]
7 7 > record =
8 8 > EOF
9 9
10 10 $ hg init a
11 11 $ cd a
12 12
13 13 Select no files
14 14
15 15 $ touch empty-rw
16 16 $ hg add empty-rw
17 17
18 18 $ hg record --config ui.interactive=false
19 19 abort: running non-interactively, use commit instead
20 20 [255]
21 21 $ hg commit -i --config ui.interactive=false
22 22 abort: running non-interactively
23 23 [10]
24 24 $ hg commit -i empty-rw<<EOF
25 25 > n
26 26 > EOF
27 27 diff --git a/empty-rw b/empty-rw
28 28 new file mode 100644
29 29 abort: empty commit message
30 30 [10]
31 31
32 32 $ hg tip -p
33 33 changeset: -1:000000000000
34 34 tag: tip
35 35 user:
36 36 date: Thu Jan 01 00:00:00 1970 +0000
37 37
38 38
39 39
40 40 Select files but no hunks
41 41
42 42 $ hg commit -i empty-rw<<EOF
43 43 > y
44 44 > n
45 45 > EOF
46 46 diff --git a/empty-rw b/empty-rw
47 47 new file mode 100644
48 48 abort: empty commit message
49 49 [10]
50 50
51 51 $ hg tip -p
52 52 changeset: -1:000000000000
53 53 tag: tip
54 54 user:
55 55 date: Thu Jan 01 00:00:00 1970 +0000
56 56
57 57
58 58
59 59 Abort for untracked
60 60
61 61 $ touch untracked
62 62 $ hg commit -i -m should-fail empty-rw untracked
63 63 abort: untracked: file not tracked!
64 64 [10]
65 65 $ rm untracked
66 66
67 67 Record empty file
68 68
69 69 $ hg commit -i -d '0 0' -m empty empty-rw<<EOF
70 70 > y
71 71 > EOF
72 72 diff --git a/empty-rw b/empty-rw
73 73 new file mode 100644
74 74
75 75 $ hg tip -p
76 76 changeset: 0:c0708cf4e46e
77 77 tag: tip
78 78 user: test
79 79 date: Thu Jan 01 00:00:00 1970 +0000
80 80 summary: empty
81 81
82 82
83 83
84 84 Summary shows we updated to the new cset
85 85
86 86 $ hg summary
87 87 parent: 0:c0708cf4e46e tip
88 88 empty
89 89 branch: default
90 90 commit: (clean)
91 91 update: (current)
92 92 phases: 1 draft
93 93
94 94 Rename empty file
95 95
96 96 $ hg mv empty-rw empty-rename
97 97 $ hg commit -i -d '1 0' -m rename<<EOF
98 98 > y
99 99 > EOF
100 100 diff --git a/empty-rw b/empty-rename
101 101 rename from empty-rw
102 102 rename to empty-rename
103 103 examine changes to 'empty-rw' and 'empty-rename'?
104 104 (enter ? for help) [Ynesfdaq?] y
105 105
106 106
107 107 $ hg tip -p
108 108 changeset: 1:d695e8dcb197
109 109 tag: tip
110 110 user: test
111 111 date: Thu Jan 01 00:00:01 1970 +0000
112 112 summary: rename
113 113
114 114
115 115
116 116 Copy empty file
117 117
118 118 $ hg cp empty-rename empty-copy
119 119 $ hg commit -i -d '2 0' -m copy<<EOF
120 120 > y
121 121 > EOF
122 122 diff --git a/empty-rename b/empty-copy
123 123 copy from empty-rename
124 124 copy to empty-copy
125 125 examine changes to 'empty-rename' and 'empty-copy'?
126 126 (enter ? for help) [Ynesfdaq?] y
127 127
128 128
129 129 $ hg tip -p
130 130 changeset: 2:1d4b90bea524
131 131 tag: tip
132 132 user: test
133 133 date: Thu Jan 01 00:00:02 1970 +0000
134 134 summary: copy
135 135
136 136
137 137
138 138 Delete empty file
139 139
140 140 $ hg rm empty-copy
141 141 $ hg commit -i -d '3 0' -m delete<<EOF
142 142 > y
143 143 > EOF
144 144 diff --git a/empty-copy b/empty-copy
145 145 deleted file mode 100644
146 146 examine changes to 'empty-copy'?
147 147 (enter ? for help) [Ynesfdaq?] y
148 148
149 149
150 150 $ hg tip -p
151 151 changeset: 3:b39a238f01a1
152 152 tag: tip
153 153 user: test
154 154 date: Thu Jan 01 00:00:03 1970 +0000
155 155 summary: delete
156 156
157 157
158 158
159 159 Add binary file
160 160
161 161 $ hg bundle --type v1 --base -2 tip.bundle
162 162 1 changesets found
163 163 $ hg add tip.bundle
164 164 $ hg commit -i -d '4 0' -m binary<<EOF
165 165 > y
166 166 > EOF
167 167 diff --git a/tip.bundle b/tip.bundle
168 168 new file mode 100644
169 169 this is a binary file
170 170 examine changes to 'tip.bundle'?
171 171 (enter ? for help) [Ynesfdaq?] y
172 172
173 173
174 174 $ hg tip -p
175 175 changeset: 4:ad816da3711e
176 176 tag: tip
177 177 user: test
178 178 date: Thu Jan 01 00:00:04 1970 +0000
179 179 summary: binary
180 180
181 181 diff -r b39a238f01a1 -r ad816da3711e tip.bundle
182 182 Binary file tip.bundle has changed
183 183
184 184
185 185 Change binary file
186 186
187 187 $ hg bundle --base -2 --type v1 tip.bundle
188 188 1 changesets found
189 189 $ hg commit -i -d '5 0' -m binary-change<<EOF
190 190 > y
191 191 > EOF
192 192 diff --git a/tip.bundle b/tip.bundle
193 193 this modifies a binary file (all or nothing)
194 194 examine changes to 'tip.bundle'?
195 195 (enter ? for help) [Ynesfdaq?] y
196 196
197 197
198 198 $ hg tip -p
199 199 changeset: 5:dccd6f3eb485
200 200 tag: tip
201 201 user: test
202 202 date: Thu Jan 01 00:00:05 1970 +0000
203 203 summary: binary-change
204 204
205 205 diff -r ad816da3711e -r dccd6f3eb485 tip.bundle
206 206 Binary file tip.bundle has changed
207 207
208 208
209 209 Rename and change binary file
210 210
211 211 $ hg mv tip.bundle top.bundle
212 212 $ hg bundle --base -2 --type v1 top.bundle
213 213 1 changesets found
214 214 $ hg commit -i -d '6 0' -m binary-change-rename<<EOF
215 215 > y
216 216 > EOF
217 217 diff --git a/tip.bundle b/top.bundle
218 218 rename from tip.bundle
219 219 rename to top.bundle
220 220 this modifies a binary file (all or nothing)
221 221 examine changes to 'tip.bundle' and 'top.bundle'?
222 222 (enter ? for help) [Ynesfdaq?] y
223 223
224 224
225 225 $ hg tip -p
226 226 changeset: 6:7fa44105f5b3
227 227 tag: tip
228 228 user: test
229 229 date: Thu Jan 01 00:00:06 1970 +0000
230 230 summary: binary-change-rename
231 231
232 232 diff -r dccd6f3eb485 -r 7fa44105f5b3 tip.bundle
233 233 Binary file tip.bundle has changed
234 234 diff -r dccd6f3eb485 -r 7fa44105f5b3 top.bundle
235 235 Binary file top.bundle has changed
236 236
237 237
238 238 Add plain file
239 239
240 240 $ for i in 1 2 3 4 5 6 7 8 9 10; do
241 241 > echo $i >> plain
242 242 > done
243 243
244 244 $ hg add plain
245 245 $ hg commit -i -d '7 0' -m plain plain<<EOF
246 246 > y
247 247 > y
248 248 > EOF
249 249 diff --git a/plain b/plain
250 250 new file mode 100644
251 251 @@ -0,0 +1,10 @@
252 252 +1
253 253 +2
254 254 +3
255 255 +4
256 256 +5
257 257 +6
258 258 +7
259 259 +8
260 260 +9
261 261 +10
262 262 record this change to 'plain'?
263 263 (enter ? for help) [Ynesfdaq?] y
264 264
265 265 $ hg tip -p
266 266 changeset: 7:11fb457c1be4
267 267 tag: tip
268 268 user: test
269 269 date: Thu Jan 01 00:00:07 1970 +0000
270 270 summary: plain
271 271
272 272 diff -r 7fa44105f5b3 -r 11fb457c1be4 plain
273 273 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
274 274 +++ b/plain Thu Jan 01 00:00:07 1970 +0000
275 275 @@ -0,0 +1,10 @@
276 276 +1
277 277 +2
278 278 +3
279 279 +4
280 280 +5
281 281 +6
282 282 +7
283 283 +8
284 284 +9
285 285 +10
286 286
287 287 Modify end of plain file with username unset
288 288
289 289 $ echo 11 >> plain
290 290 $ unset HGUSER
291 291 $ hg commit -i --config ui.username= -d '8 0' -m end plain
292 292 abort: no username supplied
293 293 (use 'hg config --edit' to set your username)
294 294 [255]
295 295
296 296
297 297 Modify end of plain file, also test that diffopts are accounted for
298 298
299 299 $ HGUSER="test"
300 300 $ export HGUSER
301 301 $ hg commit -i --config diff.showfunc=true -d '8 0' -m end plain <<EOF
302 302 > y
303 303 > y
304 304 > EOF
305 305 diff --git a/plain b/plain
306 306 1 hunks, 1 lines changed
307 307 @@ -8,3 +8,4 @@ 7
308 308 8
309 309 9
310 310 10
311 311 +11
312 312 record this change to 'plain'?
313 313 (enter ? for help) [Ynesfdaq?] y
314 314
315 315
316 316 Modify end of plain file, no EOL
317 317
318 318 $ hg tip --template '{node}' >> plain
319 319 $ hg commit -i -d '9 0' -m noeol plain <<EOF
320 320 > y
321 321 > y
322 322 > EOF
323 323 diff --git a/plain b/plain
324 324 1 hunks, 1 lines changed
325 325 @@ -9,3 +9,4 @@ 8
326 326 9
327 327 10
328 328 11
329 329 +7264f99c5f5ff3261504828afa4fb4d406c3af54
330 330 \ No newline at end of file
331 331 record this change to 'plain'?
332 332 (enter ? for help) [Ynesfdaq?] y
333 333
334 334
335 335 Record showfunc should preserve function across sections
336 336
337 337 $ cat > f1.py <<NO_CHECK_EOF
338 338 > def annotate(ui, repo, *pats, **opts):
339 339 > """show changeset information by line for each file
340 340 >
341 341 > List changes in files, showing the revision id responsible for
342 342 > each line.
343 343 >
344 344 > This command is useful for discovering when a change was made and
345 345 > by whom.
346 346 >
347 347 > If you include -f/-u/-d, the revision number is suppressed unless
348 348 > you also include -the revision number is suppressed unless
349 349 > you also include -n.
350 350 >
351 351 > Without the -a/--text option, annotate will avoid processing files
352 352 > it detects as binary. With -a, annotate will annotate the file
353 353 > anyway, although the results will probably be neither useful
354 354 > nor desirable.
355 355 >
356 356 > Returns 0 on success.
357 357 > """
358 358 > return 0
359 359 > def archive(ui, repo, dest, **opts):
360 360 > '''create an unversioned archive of a repository revision
361 361 >
362 362 > By default, the revision used is the parent of the working
363 363 > directory; use -r/--rev to specify a different revision.
364 364 >
365 365 > The archive type is automatically detected based on file
366 366 > extension (to override, use -t/--type).
367 367 >
368 368 > .. container:: verbose
369 369 >
370 370 > Valid types are:
371 371 > NO_CHECK_EOF
372 372 $ hg add f1.py
373 373 $ hg commit -m funcs
374 374 $ cat > f1.py <<NO_CHECK_EOF
375 375 > def annotate(ui, repo, *pats, **opts):
376 376 > """show changeset information by line for each file
377 377 >
378 378 > List changes in files, showing the revision id responsible for
379 379 > each line
380 380 >
381 381 > This command is useful for discovering when a change was made and
382 382 > by whom.
383 383 >
384 384 > Without the -a/--text option, annotate will avoid processing files
385 385 > it detects as binary. With -a, annotate will annotate the file
386 386 > anyway, although the results will probably be neither useful
387 387 > nor desirable.
388 388 >
389 389 > Returns 0 on success.
390 390 > """
391 391 > return 0
392 392 > def archive(ui, repo, dest, **opts):
393 393 > '''create an unversioned archive of a repository revision
394 394 >
395 395 > By default, the revision used is the parent of the working
396 396 > directory; use -r/--rev to specify a different revision.
397 397 >
398 398 > The archive type is automatically detected based on file
399 399 > extension (or override using -t/--type).
400 400 >
401 401 > .. container:: verbose
402 402 >
403 403 > Valid types are:
404 404 > NO_CHECK_EOF
405 405 $ hg commit -i -m interactive <<EOF
406 406 > y
407 407 > y
408 408 > y
409 409 > y
410 410 > EOF
411 411 diff --git a/f1.py b/f1.py
412 412 3 hunks, 6 lines changed
413 413 examine changes to 'f1.py'?
414 414 (enter ? for help) [Ynesfdaq?] y
415 415
416 416 @@ -2,8 +2,8 @@ def annotate(ui, repo, *pats, **opts):
417 417 """show changeset information by line for each file
418 418
419 419 List changes in files, showing the revision id responsible for
420 420 - each line.
421 421 + each line
422 422
423 423 This command is useful for discovering when a change was made and
424 424 by whom.
425 425
426 426 record change 1/3 to 'f1.py'?
427 427 (enter ? for help) [Ynesfdaq?] y
428 428
429 429 @@ -6,11 +6,7 @@ def annotate(ui, repo, *pats, **opts):
430 430
431 431 This command is useful for discovering when a change was made and
432 432 by whom.
433 433
434 434 - If you include -f/-u/-d, the revision number is suppressed unless
435 435 - you also include -the revision number is suppressed unless
436 436 - you also include -n.
437 437 -
438 438 Without the -a/--text option, annotate will avoid processing files
439 439 it detects as binary. With -a, annotate will annotate the file
440 440 anyway, although the results will probably be neither useful
441 441 record change 2/3 to 'f1.py'?
442 442 (enter ? for help) [Ynesfdaq?] y
443 443
444 444 @@ -26,7 +22,7 @@ def archive(ui, repo, dest, **opts):
445 445 directory; use -r/--rev to specify a different revision.
446 446
447 447 The archive type is automatically detected based on file
448 448 - extension (to override, use -t/--type).
449 449 + extension (or override using -t/--type).
450 450
451 451 .. container:: verbose
452 452
453 453 record change 3/3 to 'f1.py'?
454 454 (enter ? for help) [Ynesfdaq?] y
455 455
456 456
457 457 Modify end of plain file, add EOL
458 458
459 459 $ echo >> plain
460 460 $ echo 1 > plain2
461 461 $ hg add plain2
462 462 $ hg commit -i -d '10 0' -m eol plain plain2 <<EOF
463 463 > y
464 464 > y
465 465 > y
466 466 > y
467 467 > EOF
468 468 diff --git a/plain b/plain
469 469 1 hunks, 1 lines changed
470 470 @@ -9,4 +9,4 @@ 8
471 471 9
472 472 10
473 473 11
474 474 -7264f99c5f5ff3261504828afa4fb4d406c3af54
475 475 \ No newline at end of file
476 476 +7264f99c5f5ff3261504828afa4fb4d406c3af54
477 477 record change 1/2 to 'plain'?
478 478 (enter ? for help) [Ynesfdaq?] y
479 479
480 480 diff --git a/plain2 b/plain2
481 481 new file mode 100644
482 482 @@ -0,0 +1,1 @@
483 483 +1
484 484 record change 2/2 to 'plain2'?
485 485 (enter ? for help) [Ynesfdaq?] y
486 486
487 487 Modify beginning, trim end, record both, add another file to test
488 488 changes numbering
489 489
490 490 $ rm plain
491 491 $ for i in 2 2 3 4 5 6 7 8 9 10; do
492 492 > echo $i >> plain
493 493 > done
494 494 $ echo 2 >> plain2
495 495
496 496 $ hg commit -i -d '10 0' -m begin-and-end plain plain2 <<EOF
497 497 > y
498 498 > y
499 499 > y
500 500 > y
501 501 > y
502 502 > EOF
503 503 diff --git a/plain b/plain
504 504 2 hunks, 3 lines changed
505 505 @@ -1,4 +1,4 @@
506 506 -1
507 507 +2
508 508 2
509 509 3
510 510 4
511 511 record change 1/3 to 'plain'?
512 512 (enter ? for help) [Ynesfdaq?] y
513 513
514 514 @@ -8,5 +8,3 @@ 7
515 515 8
516 516 9
517 517 10
518 518 -11
519 519 -7264f99c5f5ff3261504828afa4fb4d406c3af54
520 520 record change 2/3 to 'plain'?
521 521 (enter ? for help) [Ynesfdaq?] y
522 522
523 523 diff --git a/plain2 b/plain2
524 524 1 hunks, 1 lines changed
525 525 @@ -1,1 +1,2 @@
526 526 1
527 527 +2
528 528 record change 3/3 to 'plain2'?
529 529 (enter ? for help) [Ynesfdaq?] y
530 530
531 531
532 532 $ hg tip -p
533 533 changeset: 13:f941910cff62
534 534 tag: tip
535 535 user: test
536 536 date: Thu Jan 01 00:00:10 1970 +0000
537 537 summary: begin-and-end
538 538
539 539 diff -r 33abe24d946c -r f941910cff62 plain
540 540 --- a/plain Thu Jan 01 00:00:10 1970 +0000
541 541 +++ b/plain Thu Jan 01 00:00:10 1970 +0000
542 542 @@ -1,4 +1,4 @@
543 543 -1
544 544 +2
545 545 2
546 546 3
547 547 4
548 548 @@ -8,5 +8,3 @@
549 549 8
550 550 9
551 551 10
552 552 -11
553 553 -7264f99c5f5ff3261504828afa4fb4d406c3af54
554 554 diff -r 33abe24d946c -r f941910cff62 plain2
555 555 --- a/plain2 Thu Jan 01 00:00:10 1970 +0000
556 556 +++ b/plain2 Thu Jan 01 00:00:10 1970 +0000
557 557 @@ -1,1 +1,2 @@
558 558 1
559 559 +2
560 560
561 561
562 562 Trim beginning, modify end
563 563
564 564 $ rm plain
565 565 > for i in 4 5 6 7 8 9 10.new; do
566 566 > echo $i >> plain
567 567 > done
568 568
569 569 Record end
570 570
571 571 $ hg commit -i -d '11 0' -m end-only plain <<EOF
572 572 > n
573 573 > y
574 574 > EOF
575 575 diff --git a/plain b/plain
576 576 2 hunks, 4 lines changed
577 577 @@ -1,9 +1,6 @@
578 578 -2
579 579 -2
580 580 -3
581 581 4
582 582 5
583 583 6
584 584 7
585 585 8
586 586 9
587 587 record change 1/2 to 'plain'?
588 588 (enter ? for help) [Ynesfdaq?] n
589 589
590 590 @@ -4,7 +1,7 @@
591 591 4
592 592 5
593 593 6
594 594 7
595 595 8
596 596 9
597 597 -10
598 598 +10.new
599 599 record change 2/2 to 'plain'?
600 600 (enter ? for help) [Ynesfdaq?] y
601 601
602 602
603 603 $ hg tip -p
604 604 changeset: 14:4915f538659b
605 605 tag: tip
606 606 user: test
607 607 date: Thu Jan 01 00:00:11 1970 +0000
608 608 summary: end-only
609 609
610 610 diff -r f941910cff62 -r 4915f538659b plain
611 611 --- a/plain Thu Jan 01 00:00:10 1970 +0000
612 612 +++ b/plain Thu Jan 01 00:00:11 1970 +0000
613 613 @@ -7,4 +7,4 @@
614 614 7
615 615 8
616 616 9
617 617 -10
618 618 +10.new
619 619
620 620
621 621 Record beginning
622 622
623 623 $ hg commit -i -d '12 0' -m begin-only plain <<EOF
624 624 > y
625 625 > y
626 626 > EOF
627 627 diff --git a/plain b/plain
628 628 1 hunks, 3 lines changed
629 629 @@ -1,6 +1,3 @@
630 630 -2
631 631 -2
632 632 -3
633 633 4
634 634 5
635 635 6
636 636 record this change to 'plain'?
637 637 (enter ? for help) [Ynesfdaq?] y
638 638
639 639
640 640 $ hg tip -p
641 641 changeset: 15:1b1f93d4b94b
642 642 tag: tip
643 643 user: test
644 644 date: Thu Jan 01 00:00:12 1970 +0000
645 645 summary: begin-only
646 646
647 647 diff -r 4915f538659b -r 1b1f93d4b94b plain
648 648 --- a/plain Thu Jan 01 00:00:11 1970 +0000
649 649 +++ b/plain Thu Jan 01 00:00:12 1970 +0000
650 650 @@ -1,6 +1,3 @@
651 651 -2
652 652 -2
653 653 -3
654 654 4
655 655 5
656 656 6
657 657
658 658
659 659 Add to beginning, trim from end
660 660
661 661 $ rm plain
662 662 $ for i in 1 2 3 4 5 6 7 8 9; do
663 663 > echo $i >> plain
664 664 > done
665 665
666 666 Record end
667 667
668 668 $ hg commit -i --traceback -d '13 0' -m end-again plain<<EOF
669 669 > n
670 670 > y
671 671 > EOF
672 672 diff --git a/plain b/plain
673 673 2 hunks, 4 lines changed
674 674 @@ -1,6 +1,9 @@
675 675 +1
676 676 +2
677 677 +3
678 678 4
679 679 5
680 680 6
681 681 7
682 682 8
683 683 9
684 684 record change 1/2 to 'plain'?
685 685 (enter ? for help) [Ynesfdaq?] n
686 686
687 687 @@ -1,7 +4,6 @@
688 688 4
689 689 5
690 690 6
691 691 7
692 692 8
693 693 9
694 694 -10.new
695 695 record change 2/2 to 'plain'?
696 696 (enter ? for help) [Ynesfdaq?] y
697 697
698 698
699 699 Add to beginning, middle, end
700 700
701 701 $ rm plain
702 702 $ for i in 1 2 3 4 5 5.new 5.reallynew 6 7 8 9 10 11; do
703 703 > echo $i >> plain
704 704 > done
705 705
706 706 Record beginning, middle, and test that format-breaking diffopts are ignored
707 707
708 708 $ hg commit -i --config diff.noprefix=True -d '14 0' -m middle-only plain <<EOF
709 709 > y
710 710 > y
711 711 > n
712 712 > EOF
713 713 diff --git a/plain b/plain
714 714 3 hunks, 7 lines changed
715 715 @@ -1,2 +1,5 @@
716 716 +1
717 717 +2
718 718 +3
719 719 4
720 720 5
721 721 record change 1/3 to 'plain'?
722 722 (enter ? for help) [Ynesfdaq?] y
723 723
724 724 @@ -1,6 +4,8 @@
725 725 4
726 726 5
727 727 +5.new
728 728 +5.reallynew
729 729 6
730 730 7
731 731 8
732 732 9
733 733 record change 2/3 to 'plain'?
734 734 (enter ? for help) [Ynesfdaq?] y
735 735
736 736 @@ -3,4 +8,6 @@
737 737 6
738 738 7
739 739 8
740 740 9
741 741 +10
742 742 +11
743 743 record change 3/3 to 'plain'?
744 744 (enter ? for help) [Ynesfdaq?] n
745 745
746 746
747 747 $ hg tip -p
748 748 changeset: 17:41cf3f5c55ae
749 749 tag: tip
750 750 user: test
751 751 date: Thu Jan 01 00:00:14 1970 +0000
752 752 summary: middle-only
753 753
754 754 diff -r a69d252246e1 -r 41cf3f5c55ae plain
755 755 --- a/plain Thu Jan 01 00:00:13 1970 +0000
756 756 +++ b/plain Thu Jan 01 00:00:14 1970 +0000
757 757 @@ -1,5 +1,10 @@
758 758 +1
759 759 +2
760 760 +3
761 761 4
762 762 5
763 763 +5.new
764 764 +5.reallynew
765 765 6
766 766 7
767 767 8
768 768
769 769
770 770 Record end
771 771
772 772 $ hg commit -i -d '15 0' -m end-only plain <<EOF
773 773 > y
774 774 > y
775 775 > EOF
776 776 diff --git a/plain b/plain
777 777 1 hunks, 2 lines changed
778 778 @@ -9,3 +9,5 @@ 6
779 779 7
780 780 8
781 781 9
782 782 +10
783 783 +11
784 784 record this change to 'plain'?
785 785 (enter ? for help) [Ynesfdaq?] y
786 786
787 787
788 788 $ hg tip -p
789 789 changeset: 18:58a72f46bc24
790 790 tag: tip
791 791 user: test
792 792 date: Thu Jan 01 00:00:15 1970 +0000
793 793 summary: end-only
794 794
795 795 diff -r 41cf3f5c55ae -r 58a72f46bc24 plain
796 796 --- a/plain Thu Jan 01 00:00:14 1970 +0000
797 797 +++ b/plain Thu Jan 01 00:00:15 1970 +0000
798 798 @@ -9,3 +9,5 @@
799 799 7
800 800 8
801 801 9
802 802 +10
803 803 +11
804 804
805 805 Interactive commit can name a directory instead of files (issue6131)
806 806
807 807 $ mkdir subdir
808 808 $ echo a > subdir/a
809 809 $ hg ci -d '16 0' -i subdir -Amsubdir <<EOF
810 810 > y
811 811 > y
812 812 > EOF
813 813 adding subdir/a
814 814 diff --git a/subdir/a b/subdir/a
815 815 new file mode 100644
816 816 examine changes to 'subdir/a'?
817 817 (enter ? for help) [Ynesfdaq?] y
818 818
819 819 @@ -0,0 +1,1 @@
820 820 +a
821 821 record this change to 'subdir/a'?
822 822 (enter ? for help) [Ynesfdaq?] y
823 823
824 824 $ cd subdir
825 825
826 826 $ echo a >> a
827 827 $ hg commit -i -d '16 0' -m subdir-change a <<EOF
828 828 > y
829 829 > y
830 830 > EOF
831 831 diff --git a/subdir/a b/subdir/a
832 832 1 hunks, 1 lines changed
833 833 @@ -1,1 +1,2 @@
834 834 a
835 835 +a
836 836 record this change to 'subdir/a'?
837 837 (enter ? for help) [Ynesfdaq?] y
838 838
839 839
840 840 $ hg tip -p
841 841 changeset: 20:e0f6b99f6c49
842 842 tag: tip
843 843 user: test
844 844 date: Thu Jan 01 00:00:16 1970 +0000
845 845 summary: subdir-change
846 846
847 847 diff -r abd26b51de37 -r e0f6b99f6c49 subdir/a
848 848 --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000
849 849 +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000
850 850 @@ -1,1 +1,2 @@
851 851 a
852 852 +a
853 853
854 854
855 855 $ echo a > f1
856 856 $ echo b > f2
857 857 $ hg add f1 f2
858 858
859 859 $ hg ci -mz -d '17 0'
860 860
861 861 $ echo a >> f1
862 862 $ echo b >> f2
863 863
864 864 Help, quit
865 865
866 866 $ hg commit -i <<EOF
867 867 > ?
868 868 > q
869 869 > EOF
870 870 diff --git a/subdir/f1 b/subdir/f1
871 871 1 hunks, 1 lines changed
872 872 examine changes to 'subdir/f1'?
873 873 (enter ? for help) [Ynesfdaq?] ?
874 874
875 875 y - yes, record this change
876 876 n - no, skip this change
877 877 e - edit this change manually
878 878 s - skip remaining changes to this file
879 879 f - record remaining changes to this file
880 880 d - done, skip remaining changes and files
881 881 a - record all changes to all remaining files
882 882 q - quit, recording no changes
883 883 ? - ? (display help)
884 884 examine changes to 'subdir/f1'?
885 885 (enter ? for help) [Ynesfdaq?] q
886 886
887 887 abort: user quit
888 888 [250]
889 889
890 890 Patterns
891 891
892 892 $ hg commit -i 'glob:f*' << EOF
893 893 > y
894 894 > n
895 895 > y
896 896 > n
897 897 > EOF
898 898 diff --git a/subdir/f1 b/subdir/f1
899 899 1 hunks, 1 lines changed
900 900 examine changes to 'subdir/f1'?
901 901 (enter ? for help) [Ynesfdaq?] y
902 902
903 903 @@ -1,1 +1,2 @@
904 904 a
905 905 +a
906 906 record change 1/2 to 'subdir/f1'?
907 907 (enter ? for help) [Ynesfdaq?] n
908 908
909 909 diff --git a/subdir/f2 b/subdir/f2
910 910 1 hunks, 1 lines changed
911 911 examine changes to 'subdir/f2'?
912 912 (enter ? for help) [Ynesfdaq?] y
913 913
914 914 @@ -1,1 +1,2 @@
915 915 b
916 916 +b
917 917 record change 2/2 to 'subdir/f2'?
918 918 (enter ? for help) [Ynesfdaq?] n
919 919
920 920 no changes to record
921 921 [1]
922 922
923 923 #if gettext
924 924
925 925 Test translated help message
926 926
927 927 str.lower() instead of encoding.lower(str) on translated message might
928 928 make message meaningless, because some encoding uses 0x41(A) - 0x5a(Z)
929 929 as the second or later byte of multi-byte character.
930 930
931 931 For example, "\x8bL\x98^" (translation of "record" in ja_JP.cp932)
932 932 contains 0x4c (L). str.lower() replaces 0x4c(L) by 0x6c(l) and this
933 933 replacement makes message meaningless.
934 934
935 935 This tests that translated help message is lower()-ed correctly.
936 936
937 937 $ LANGUAGE=ja
938 938 $ export LANGUAGE
939 939
940 940 $ cat > $TESTTMP/escape.py <<EOF
941 941 > from __future__ import absolute_import
942 942 > from mercurial import (
943 943 > pycompat,
944 944 > )
945 945 > from mercurial.utils import (
946 946 > procutil,
947 947 > )
948 948 > def escape(c):
949 949 > o = ord(c)
950 950 > if o < 0x80:
951 951 > return c
952 952 > else:
953 953 > return br'\x%02x' % o # escape char setting MSB
954 954 > for l in procutil.stdin:
955 955 > procutil.stdout.write(
956 956 > b''.join(escape(c) for c in pycompat.iterbytestr(l)))
957 957 > EOF
958 958
959 959 $ hg commit -i --encoding cp932 2>&1 <<EOF | "$PYTHON" $TESTTMP/escape.py | grep '^y - '
960 960 > ?
961 961 > q
962 962 > EOF
963 963 y - \x82\xb1\x82\xcc\x95\xcf\x8dX\x82\xf0\x8bL\x98^(yes)
964 964
965 965 $ LANGUAGE=
966 966 #endif
967 967
968 968 Skip
969 969
970 970 $ hg commit -i <<EOF
971 971 > s
972 972 > EOF
973 973 diff --git a/subdir/f1 b/subdir/f1
974 974 1 hunks, 1 lines changed
975 975 examine changes to 'subdir/f1'?
976 976 (enter ? for help) [Ynesfdaq?] s
977 977
978 978 diff --git a/subdir/f2 b/subdir/f2
979 979 1 hunks, 1 lines changed
980 980 examine changes to 'subdir/f2'?
981 981 (enter ? for help) [Ynesfdaq?] abort: response expected
982 982 [255]
983 983
984 984 No
985 985
986 986 $ hg commit -i <<EOF
987 987 > n
988 988 > EOF
989 989 diff --git a/subdir/f1 b/subdir/f1
990 990 1 hunks, 1 lines changed
991 991 examine changes to 'subdir/f1'?
992 992 (enter ? for help) [Ynesfdaq?] n
993 993
994 994 diff --git a/subdir/f2 b/subdir/f2
995 995 1 hunks, 1 lines changed
996 996 examine changes to 'subdir/f2'?
997 997 (enter ? for help) [Ynesfdaq?] abort: response expected
998 998 [255]
999 999
1000 1000 f, quit
1001 1001
1002 1002 $ hg commit -i <<EOF
1003 1003 > f
1004 1004 > q
1005 1005 > EOF
1006 1006 diff --git a/subdir/f1 b/subdir/f1
1007 1007 1 hunks, 1 lines changed
1008 1008 examine changes to 'subdir/f1'?
1009 1009 (enter ? for help) [Ynesfdaq?] f
1010 1010
1011 1011 diff --git a/subdir/f2 b/subdir/f2
1012 1012 1 hunks, 1 lines changed
1013 1013 examine changes to 'subdir/f2'?
1014 1014 (enter ? for help) [Ynesfdaq?] q
1015 1015
1016 1016 abort: user quit
1017 1017 [250]
1018 1018
1019 1019 s, all
1020 1020
1021 1021 $ hg commit -i -d '18 0' -mx <<EOF
1022 1022 > s
1023 1023 > a
1024 1024 > EOF
1025 1025 diff --git a/subdir/f1 b/subdir/f1
1026 1026 1 hunks, 1 lines changed
1027 1027 examine changes to 'subdir/f1'?
1028 1028 (enter ? for help) [Ynesfdaq?] s
1029 1029
1030 1030 diff --git a/subdir/f2 b/subdir/f2
1031 1031 1 hunks, 1 lines changed
1032 1032 examine changes to 'subdir/f2'?
1033 1033 (enter ? for help) [Ynesfdaq?] a
1034 1034
1035 1035
1036 1036 $ hg tip -p
1037 1037 changeset: 22:6afbbefacf35
1038 1038 tag: tip
1039 1039 user: test
1040 1040 date: Thu Jan 01 00:00:18 1970 +0000
1041 1041 summary: x
1042 1042
1043 1043 diff -r b73c401c693c -r 6afbbefacf35 subdir/f2
1044 1044 --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000
1045 1045 +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000
1046 1046 @@ -1,1 +1,2 @@
1047 1047 b
1048 1048 +b
1049 1049
1050 1050
1051 1051 f
1052 1052
1053 1053 $ hg commit -i -d '19 0' -my <<EOF
1054 1054 > f
1055 1055 > EOF
1056 1056 diff --git a/subdir/f1 b/subdir/f1
1057 1057 1 hunks, 1 lines changed
1058 1058 examine changes to 'subdir/f1'?
1059 1059 (enter ? for help) [Ynesfdaq?] f
1060 1060
1061 1061
1062 1062 $ hg tip -p
1063 1063 changeset: 23:715028a33949
1064 1064 tag: tip
1065 1065 user: test
1066 1066 date: Thu Jan 01 00:00:19 1970 +0000
1067 1067 summary: y
1068 1068
1069 1069 diff -r 6afbbefacf35 -r 715028a33949 subdir/f1
1070 1070 --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000
1071 1071 +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000
1072 1072 @@ -1,1 +1,2 @@
1073 1073 a
1074 1074 +a
1075 1075
1076 1076
1077 1077 #if execbit
1078 1078
1079 1079 Preserve chmod +x
1080 1080
1081 1081 $ chmod +x f1
1082 1082 $ echo a >> f1
1083 1083 $ hg commit -i -d '20 0' -mz <<EOF
1084 1084 > y
1085 1085 > y
1086 1086 > y
1087 1087 > EOF
1088 1088 diff --git a/subdir/f1 b/subdir/f1
1089 1089 old mode 100644
1090 1090 new mode 100755
1091 1091 1 hunks, 1 lines changed
1092 1092 examine changes to 'subdir/f1'?
1093 1093 (enter ? for help) [Ynesfdaq?] y
1094 1094
1095 1095 @@ -1,2 +1,3 @@
1096 1096 a
1097 1097 a
1098 1098 +a
1099 1099 record this change to 'subdir/f1'?
1100 1100 (enter ? for help) [Ynesfdaq?] y
1101 1101
1102 1102
1103 1103 $ hg tip --config diff.git=True -p
1104 1104 changeset: 24:db967c1e5884
1105 1105 tag: tip
1106 1106 user: test
1107 1107 date: Thu Jan 01 00:00:20 1970 +0000
1108 1108 summary: z
1109 1109
1110 1110 diff --git a/subdir/f1 b/subdir/f1
1111 1111 old mode 100644
1112 1112 new mode 100755
1113 1113 --- a/subdir/f1
1114 1114 +++ b/subdir/f1
1115 1115 @@ -1,2 +1,3 @@
1116 1116 a
1117 1117 a
1118 1118 +a
1119 1119
1120 1120
1121 1121 Preserve execute permission on original
1122 1122
1123 1123 $ echo b >> f1
1124 1124 $ hg commit -i -d '21 0' -maa <<EOF
1125 1125 > y
1126 1126 > y
1127 1127 > y
1128 1128 > EOF
1129 1129 diff --git a/subdir/f1 b/subdir/f1
1130 1130 1 hunks, 1 lines changed
1131 1131 examine changes to 'subdir/f1'?
1132 1132 (enter ? for help) [Ynesfdaq?] y
1133 1133
1134 1134 @@ -1,3 +1,4 @@
1135 1135 a
1136 1136 a
1137 1137 a
1138 1138 +b
1139 1139 record this change to 'subdir/f1'?
1140 1140 (enter ? for help) [Ynesfdaq?] y
1141 1141
1142 1142
1143 1143 $ hg tip --config diff.git=True -p
1144 1144 changeset: 25:88903aef81c3
1145 1145 tag: tip
1146 1146 user: test
1147 1147 date: Thu Jan 01 00:00:21 1970 +0000
1148 1148 summary: aa
1149 1149
1150 1150 diff --git a/subdir/f1 b/subdir/f1
1151 1151 --- a/subdir/f1
1152 1152 +++ b/subdir/f1
1153 1153 @@ -1,3 +1,4 @@
1154 1154 a
1155 1155 a
1156 1156 a
1157 1157 +b
1158 1158
1159 1159
1160 1160 Preserve chmod -x
1161 1161
1162 1162 $ chmod -x f1
1163 1163 $ echo c >> f1
1164 1164 $ hg commit -i -d '22 0' -mab <<EOF
1165 1165 > y
1166 1166 > y
1167 1167 > y
1168 1168 > EOF
1169 1169 diff --git a/subdir/f1 b/subdir/f1
1170 1170 old mode 100755
1171 1171 new mode 100644
1172 1172 1 hunks, 1 lines changed
1173 1173 examine changes to 'subdir/f1'?
1174 1174 (enter ? for help) [Ynesfdaq?] y
1175 1175
1176 1176 @@ -2,3 +2,4 @@ a
1177 1177 a
1178 1178 a
1179 1179 b
1180 1180 +c
1181 1181 record this change to 'subdir/f1'?
1182 1182 (enter ? for help) [Ynesfdaq?] y
1183 1183
1184 1184
1185 1185 $ hg tip --config diff.git=True -p
1186 1186 changeset: 26:7af84b6cf560
1187 1187 tag: tip
1188 1188 user: test
1189 1189 date: Thu Jan 01 00:00:22 1970 +0000
1190 1190 summary: ab
1191 1191
1192 1192 diff --git a/subdir/f1 b/subdir/f1
1193 1193 old mode 100755
1194 1194 new mode 100644
1195 1195 --- a/subdir/f1
1196 1196 +++ b/subdir/f1
1197 1197 @@ -2,3 +2,4 @@
1198 1198 a
1199 1199 a
1200 1200 b
1201 1201 +c
1202 1202
1203 1203
1204 1204 #else
1205 1205
1206 1206 Slightly bogus tests to get almost same repo structure as when x bit is used
1207 1207 - but with different hashes.
1208 1208
1209 1209 Mock "Preserve chmod +x"
1210 1210
1211 1211 $ echo a >> f1
1212 1212 $ hg commit -i -d '20 0' -mz <<EOF
1213 1213 > y
1214 1214 > y
1215 1215 > y
1216 1216 > EOF
1217 1217 diff --git a/subdir/f1 b/subdir/f1
1218 1218 1 hunks, 1 lines changed
1219 1219 examine changes to 'subdir/f1'?
1220 1220 (enter ? for help) [Ynesfdaq?] y
1221 1221
1222 1222 @@ -1,2 +1,3 @@
1223 1223 a
1224 1224 a
1225 1225 +a
1226 1226 record this change to 'subdir/f1'?
1227 1227 (enter ? for help) [Ynesfdaq?] y
1228 1228
1229 1229
1230 1230 $ hg tip --config diff.git=True -p
1231 1231 changeset: 24:c26cfe2c4eb0
1232 1232 tag: tip
1233 1233 user: test
1234 1234 date: Thu Jan 01 00:00:20 1970 +0000
1235 1235 summary: z
1236 1236
1237 1237 diff --git a/subdir/f1 b/subdir/f1
1238 1238 --- a/subdir/f1
1239 1239 +++ b/subdir/f1
1240 1240 @@ -1,2 +1,3 @@
1241 1241 a
1242 1242 a
1243 1243 +a
1244 1244
1245 1245
1246 1246 Mock "Preserve execute permission on original"
1247 1247
1248 1248 $ echo b >> f1
1249 1249 $ hg commit -i -d '21 0' -maa <<EOF
1250 1250 > y
1251 1251 > y
1252 1252 > y
1253 1253 > EOF
1254 1254 diff --git a/subdir/f1 b/subdir/f1
1255 1255 1 hunks, 1 lines changed
1256 1256 examine changes to 'subdir/f1'?
1257 1257 (enter ? for help) [Ynesfdaq?] y
1258 1258
1259 1259 @@ -1,3 +1,4 @@
1260 1260 a
1261 1261 a
1262 1262 a
1263 1263 +b
1264 1264 record this change to 'subdir/f1'?
1265 1265 (enter ? for help) [Ynesfdaq?] y
1266 1266
1267 1267
1268 1268 $ hg tip --config diff.git=True -p
1269 1269 changeset: 25:a48d2d60adde
1270 1270 tag: tip
1271 1271 user: test
1272 1272 date: Thu Jan 01 00:00:21 1970 +0000
1273 1273 summary: aa
1274 1274
1275 1275 diff --git a/subdir/f1 b/subdir/f1
1276 1276 --- a/subdir/f1
1277 1277 +++ b/subdir/f1
1278 1278 @@ -1,3 +1,4 @@
1279 1279 a
1280 1280 a
1281 1281 a
1282 1282 +b
1283 1283
1284 1284
1285 1285 Mock "Preserve chmod -x"
1286 1286
1287 1287 $ chmod -x f1
1288 1288 $ echo c >> f1
1289 1289 $ hg commit -i -d '22 0' -mab <<EOF
1290 1290 > y
1291 1291 > y
1292 1292 > y
1293 1293 > EOF
1294 1294 diff --git a/subdir/f1 b/subdir/f1
1295 1295 1 hunks, 1 lines changed
1296 1296 examine changes to 'subdir/f1'?
1297 1297 (enter ? for help) [Ynesfdaq?] y
1298 1298
1299 1299 @@ -2,3 +2,4 @@ a
1300 1300 a
1301 1301 a
1302 1302 b
1303 1303 +c
1304 1304 record this change to 'subdir/f1'?
1305 1305 (enter ? for help) [Ynesfdaq?] y
1306 1306
1307 1307
1308 1308 $ hg tip --config diff.git=True -p
1309 1309 changeset: 26:5cc89ae210fa
1310 1310 tag: tip
1311 1311 user: test
1312 1312 date: Thu Jan 01 00:00:22 1970 +0000
1313 1313 summary: ab
1314 1314
1315 1315 diff --git a/subdir/f1 b/subdir/f1
1316 1316 --- a/subdir/f1
1317 1317 +++ b/subdir/f1
1318 1318 @@ -2,3 +2,4 @@
1319 1319 a
1320 1320 a
1321 1321 b
1322 1322 +c
1323 1323
1324 1324
1325 1325 #endif
1326 1326
1327 1327 $ cd ..
1328 1328
1329 1329
1330 1330 Abort early when a merge is in progress
1331 1331
1332 1332 $ hg up 4
1333 1333 1 files updated, 0 files merged, 7 files removed, 0 files unresolved
1334 1334
1335 1335 $ touch iwillmergethat
1336 1336 $ hg add iwillmergethat
1337 1337
1338 1338 $ hg branch thatbranch
1339 1339 marked working directory as branch thatbranch
1340 1340 (branches are permanent and global, did you want a bookmark?)
1341 1341
1342 1342 $ hg ci -m'new head'
1343 1343
1344 1344 $ hg up default
1345 1345 7 files updated, 0 files merged, 2 files removed, 0 files unresolved
1346 1346
1347 1347 $ hg merge thatbranch
1348 1348 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1349 1349 (branch merge, don't forget to commit)
1350 1350
1351 1351 $ hg commit -i -m'will abort'
1352 1352 abort: cannot partially commit a merge (use "hg commit" instead)
1353 1353 [10]
1354 1354
1355 1355 $ hg up -C
1356 1356 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1357 1357
1358 1358 Editing patch (and ignoring trailing text)
1359 1359
1360 1360 $ cat > editor.sh << '__EOF__'
1361 1361 > sed -e 7d -e '5s/^-/ /' -e '/^# ---/i\
1362 1362 > trailing\nditto' "$1" > tmp
1363 1363 > mv tmp "$1"
1364 1364 > __EOF__
1365 1365 $ cat > editedfile << '__EOF__'
1366 1366 > This is the first line
1367 1367 > This is the second line
1368 1368 > This is the third line
1369 1369 > __EOF__
1370 1370 $ hg add editedfile
1371 1371 $ hg commit -medit-patch-1
1372 1372 $ cat > editedfile << '__EOF__'
1373 1373 > This line has changed
1374 1374 > This change will be committed
1375 1375 > This is the third line
1376 1376 > __EOF__
1377 1377 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-2 <<EOF
1378 1378 > y
1379 1379 > e
1380 1380 > EOF
1381 1381 diff --git a/editedfile b/editedfile
1382 1382 1 hunks, 2 lines changed
1383 1383 examine changes to 'editedfile'?
1384 1384 (enter ? for help) [Ynesfdaq?] y
1385 1385
1386 1386 @@ -1,3 +1,3 @@
1387 1387 -This is the first line
1388 1388 -This is the second line
1389 1389 +This line has changed
1390 1390 +This change will be committed
1391 1391 This is the third line
1392 1392 record this change to 'editedfile'?
1393 1393 (enter ? for help) [Ynesfdaq?] e
1394 1394
1395 1395 $ cat editedfile
1396 1396 This line has changed
1397 1397 This change will be committed
1398 1398 This is the third line
1399 1399 $ hg cat -r tip editedfile
1400 1400 This is the first line
1401 1401 This change will be committed
1402 1402 This is the third line
1403 1403 $ hg revert editedfile
1404 1404
1405 1405 Trying to edit patch for whole file
1406 1406
1407 1407 $ echo "This is the fourth line" >> editedfile
1408 1408 $ hg commit -i <<EOF
1409 1409 > e
1410 1410 > q
1411 1411 > EOF
1412 1412 diff --git a/editedfile b/editedfile
1413 1413 1 hunks, 1 lines changed
1414 1414 examine changes to 'editedfile'?
1415 1415 (enter ? for help) [Ynesfdaq?] e
1416 1416
1417 1417 cannot edit patch for whole file
1418 1418 examine changes to 'editedfile'?
1419 1419 (enter ? for help) [Ynesfdaq?] q
1420 1420
1421 1421 abort: user quit
1422 1422 [250]
1423 1423 $ hg revert editedfile
1424 1424
1425 1425 Removing changes from patch
1426 1426
1427 1427 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1428 1428 $ mv tmp editedfile
1429 1429 $ echo "This line has been added" >> editedfile
1430 1430 $ cat > editor.sh << '__EOF__'
1431 1431 > sed -e 's/^[-+]/ /' "$1" > tmp
1432 1432 > mv tmp "$1"
1433 1433 > __EOF__
1434 1434 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1435 1435 > y
1436 1436 > e
1437 1437 > EOF
1438 1438 diff --git a/editedfile b/editedfile
1439 1439 1 hunks, 3 lines changed
1440 1440 examine changes to 'editedfile'?
1441 1441 (enter ? for help) [Ynesfdaq?] y
1442 1442
1443 1443 @@ -1,3 +1,3 @@
1444 1444 -This is the first line
1445 1445 -This change will be committed
1446 1446 -This is the third line
1447 1447 +This change will not be committed
1448 1448 +This is the second line
1449 1449 +This line has been added
1450 1450 record this change to 'editedfile'?
1451 1451 (enter ? for help) [Ynesfdaq?] e
1452 1452
1453 1453 no changes to record
1454 1454 [1]
1455 1455 $ cat editedfile
1456 1456 This change will not be committed
1457 1457 This is the second line
1458 1458 This line has been added
1459 1459 $ hg cat -r tip editedfile
1460 1460 This is the first line
1461 1461 This change will be committed
1462 1462 This is the third line
1463 1463 $ hg revert editedfile
1464 1464
1465 1465 Invalid patch
1466 1466
1467 1467 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1468 1468 $ mv tmp editedfile
1469 1469 $ echo "This line has been added" >> editedfile
1470 1470 $ cat > editor.sh << '__EOF__'
1471 1471 > sed s/This/That/ "$1" > tmp
1472 1472 > mv tmp "$1"
1473 1473 > __EOF__
1474 1474 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1475 1475 > y
1476 1476 > e
1477 1477 > EOF
1478 1478 diff --git a/editedfile b/editedfile
1479 1479 1 hunks, 3 lines changed
1480 1480 examine changes to 'editedfile'?
1481 1481 (enter ? for help) [Ynesfdaq?] y
1482 1482
1483 1483 @@ -1,3 +1,3 @@
1484 1484 -This is the first line
1485 1485 -This change will be committed
1486 1486 -This is the third line
1487 1487 +This change will not be committed
1488 1488 +This is the second line
1489 1489 +This line has been added
1490 1490 record this change to 'editedfile'?
1491 1491 (enter ? for help) [Ynesfdaq?] e
1492 1492
1493 1493 patching file editedfile
1494 1494 Hunk #1 FAILED at 0
1495 1495 1 out of 1 hunks FAILED -- saving rejects to file editedfile.rej
1496 1496 abort: patch failed to apply
1497 1497 [10]
1498 1498 $ cat editedfile
1499 1499 This change will not be committed
1500 1500 This is the second line
1501 1501 This line has been added
1502 1502 $ hg cat -r tip editedfile
1503 1503 This is the first line
1504 1504 This change will be committed
1505 1505 This is the third line
1506 1506 $ cat editedfile.rej
1507 1507 --- editedfile
1508 1508 +++ editedfile
1509 1509 @@ -1,3 +1,3 @@
1510 1510 -That is the first line
1511 1511 -That change will be committed
1512 1512 -That is the third line
1513 1513 +That change will not be committed
1514 1514 +That is the second line
1515 1515 +That line has been added
1516 1516
1517 1517 Malformed patch - error handling
1518 1518
1519 1519 $ cat > editor.sh << '__EOF__'
1520 1520 > sed -e '/^@/p' "$1" > tmp
1521 1521 > mv tmp "$1"
1522 1522 > __EOF__
1523 1523 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1524 1524 > y
1525 1525 > e
1526 1526 > EOF
1527 1527 diff --git a/editedfile b/editedfile
1528 1528 1 hunks, 3 lines changed
1529 1529 examine changes to 'editedfile'?
1530 1530 (enter ? for help) [Ynesfdaq?] y
1531 1531
1532 1532 @@ -1,3 +1,3 @@
1533 1533 -This is the first line
1534 1534 -This change will be committed
1535 1535 -This is the third line
1536 1536 +This change will not be committed
1537 1537 +This is the second line
1538 1538 +This line has been added
1539 1539 record this change to 'editedfile'?
1540 1540 (enter ? for help) [Ynesfdaq?] e
1541 1541
1542 1542 abort: error parsing patch: unhandled transition: range -> range
1543 1543 [10]
1544 1544
1545 1545 Exiting editor with status 1, ignores the edit but does not stop the recording
1546 1546 session
1547 1547
1548 1548 $ HGEDITOR=false hg commit -i <<EOF
1549 1549 > y
1550 1550 > e
1551 1551 > n
1552 1552 > EOF
1553 1553 diff --git a/editedfile b/editedfile
1554 1554 1 hunks, 3 lines changed
1555 1555 examine changes to 'editedfile'?
1556 1556 (enter ? for help) [Ynesfdaq?] y
1557 1557
1558 1558 @@ -1,3 +1,3 @@
1559 1559 -This is the first line
1560 1560 -This change will be committed
1561 1561 -This is the third line
1562 1562 +This change will not be committed
1563 1563 +This is the second line
1564 1564 +This line has been added
1565 1565 record this change to 'editedfile'?
1566 1566 (enter ? for help) [Ynesfdaq?] e
1567 1567
1568 1568 editor exited with exit code 1
1569 1569 record this change to 'editedfile'?
1570 1570 (enter ? for help) [Ynesfdaq?] n
1571 1571
1572 1572 no changes to record
1573 1573 [1]
1574 1574
1575 1575
1576 1576 random text in random positions is still an error
1577 1577
1578 1578 $ cat > editor.sh << '__EOF__'
1579 1579 > sed -e '/^@/i\
1580 1580 > other' "$1" > tmp
1581 1581 > mv tmp "$1"
1582 1582 > __EOF__
1583 1583 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1584 1584 > y
1585 1585 > e
1586 1586 > EOF
1587 1587 diff --git a/editedfile b/editedfile
1588 1588 1 hunks, 3 lines changed
1589 1589 examine changes to 'editedfile'?
1590 1590 (enter ? for help) [Ynesfdaq?] y
1591 1591
1592 1592 @@ -1,3 +1,3 @@
1593 1593 -This is the first line
1594 1594 -This change will be committed
1595 1595 -This is the third line
1596 1596 +This change will not be committed
1597 1597 +This is the second line
1598 1598 +This line has been added
1599 1599 record this change to 'editedfile'?
1600 1600 (enter ? for help) [Ynesfdaq?] e
1601 1601
1602 1602 abort: error parsing patch: unhandled transition: file -> other
1603 1603 [10]
1604 1604
1605 1605 $ hg up -C
1606 1606 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1607 1607
1608 1608 With win32text
1609 1609
1610 1610 $ echo '[extensions]' >> .hg/hgrc
1611 1611 $ echo 'win32text = ' >> .hg/hgrc
1612 1612 $ echo '[decode]' >> .hg/hgrc
1613 1613 $ echo '** = cleverdecode:' >> .hg/hgrc
1614 1614 $ echo '[encode]' >> .hg/hgrc
1615 1615 $ echo '** = cleverencode:' >> .hg/hgrc
1616 1616 $ echo '[patch]' >> .hg/hgrc
1617 1617 $ echo 'eol = crlf' >> .hg/hgrc
1618 1618
1619 1619 Ignore win32text deprecation warning for now:
1620 1620
1621 1621 $ echo '[win32text]' >> .hg/hgrc
1622 1622 $ echo 'warn = no' >> .hg/hgrc
1623 1623
1624 1624 $ echo d >> subdir/f1
1625 1625 $ hg commit -i -d '24 0' -mw1 <<EOF
1626 1626 > y
1627 1627 > y
1628 1628 > EOF
1629 1629 diff --git a/subdir/f1 b/subdir/f1
1630 1630 1 hunks, 1 lines changed
1631 1631 examine changes to 'subdir/f1'?
1632 1632 (enter ? for help) [Ynesfdaq?] y
1633 1633
1634 1634 @@ -3,3 +3,4 @@ a
1635 1635 a
1636 1636 b
1637 1637 c
1638 1638 +d
1639 1639 record this change to 'subdir/f1'?
1640 1640 (enter ? for help) [Ynesfdaq?] y
1641 1641
1642 1642
1643 1643 $ hg status -A subdir/f1
1644 1644 C subdir/f1
1645 1645 $ hg tip -p
1646 1646 changeset: 30:* (glob)
1647 1647 tag: tip
1648 1648 user: test
1649 1649 date: Thu Jan 01 00:00:24 1970 +0000
1650 1650 summary: w1
1651 1651
1652 1652 diff -r ???????????? -r ???????????? subdir/f1 (glob)
1653 1653 --- a/subdir/f1 Thu Jan 01 00:00:23 1970 +0000
1654 1654 +++ b/subdir/f1 Thu Jan 01 00:00:24 1970 +0000
1655 1655 @@ -3,3 +3,4 @@
1656 1656 a
1657 1657 b
1658 1658 c
1659 1659 +d
1660 1660
1661 1661
1662 1662
1663 1663 Test --user when ui.username not set
1664 1664 $ unset HGUSER
1665 1665 $ echo e >> subdir/f1
1666 1666 $ hg commit -i --config ui.username= -d '8 0' --user xyz -m "user flag" <<EOF
1667 1667 > y
1668 1668 > y
1669 1669 > EOF
1670 1670 diff --git a/subdir/f1 b/subdir/f1
1671 1671 1 hunks, 1 lines changed
1672 1672 examine changes to 'subdir/f1'?
1673 1673 (enter ? for help) [Ynesfdaq?] y
1674 1674
1675 1675 @@ -4,3 +4,4 @@ a
1676 1676 b
1677 1677 c
1678 1678 d
1679 1679 +e
1680 1680 record this change to 'subdir/f1'?
1681 1681 (enter ? for help) [Ynesfdaq?] y
1682 1682
1683 1683 $ hg status -A subdir/f1
1684 1684 C subdir/f1
1685 1685 $ hg log --template '{author}\n' -l 1
1686 1686 xyz
1687 1687 $ HGUSER="test"
1688 1688 $ export HGUSER
1689 1689
1690 1690
1691 1691 Moving files
1692 1692
1693 1693 $ hg update -C .
1694 1694 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1695 1695 $ hg mv plain plain3
1696 1696 $ echo somechange >> plain3
1697 1697 $ hg commit -i -d '23 0' -mmoving_files << EOF
1698 1698 > y
1699 1699 > y
1700 1700 > EOF
1701 1701 diff --git a/plain b/plain3
1702 1702 rename from plain
1703 1703 rename to plain3
1704 1704 1 hunks, 1 lines changed
1705 1705 examine changes to 'plain' and 'plain3'?
1706 1706 (enter ? for help) [Ynesfdaq?] y
1707 1707
1708 1708 @@ -11,3 +11,4 @@ 8
1709 1709 9
1710 1710 10
1711 1711 11
1712 1712 +somechange
1713 1713 record this change to 'plain3'?
1714 1714 (enter ? for help) [Ynesfdaq?] y
1715 1715
1716
1717 Rename file but discard edits
1718
1719 $ echo content > new-file
1720 $ hg add -q new-file
1721 $ hg commit -qm 'new file'
1722 $ hg mv new-file renamed-file
1723 $ echo new-content >> renamed-file
1724 $ hg commit -i -d '24 0' -m content-rename<<EOF
1725 > y
1726 > n
1727 > EOF
1728 diff --git a/new-file b/renamed-file
1729 rename from new-file
1730 rename to renamed-file
1731 1 hunks, 1 lines changed
1732 examine changes to 'new-file' and 'renamed-file'?
1733 (enter ? for help) [Ynesfdaq?] y
1734
1735 @@ -1,1 +1,2 @@
1736 content
1737 +new-content
1738 record this change to 'renamed-file'?
1739 (enter ? for help) [Ynesfdaq?] n
1740
1741 $ hg status
1742 M renamed-file
1743 ? editedfile.orig
1744 ? editedfile.rej
1745 ? editor.sh
1746 $ hg diff
1747 diff -r * renamed-file (glob)
1748 --- a/renamed-file Thu Jan 01 00:00:24 1970 +0000
1749 +++ b/renamed-file Thu Jan 01 00:00:00 1970 +0000
1750 @@ -1,1 +1,2 @@
1751 content
1752 +new-content
1753
1716 1754 The #if execbit block above changes the hash here on some systems
1717 1755 $ hg status -A plain3
1718 1756 C plain3
1719 1757 $ hg tip
1720 changeset: 32:* (glob)
1758 changeset: 34:* (glob)
1721 1759 tag: tip
1722 1760 user: test
1723 date: Thu Jan 01 00:00:23 1970 +0000
1724 summary: moving_files
1761 date: Thu Jan 01 00:00:24 1970 +0000
1762 summary: content-rename
1725 1763
1726 1764 Editing patch of newly added file
1727 1765
1728 1766 $ hg update -C .
1729 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1767 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1730 1768 $ cat > editor.sh << '__EOF__'
1731 1769 > cat "$1" | sed "s/first/very/g" > tt
1732 1770 > mv tt "$1"
1733 1771 > __EOF__
1734 1772 $ cat > newfile << '__EOF__'
1735 1773 > This is the first line
1736 1774 > This is the second line
1737 1775 > This is the third line
1738 1776 > __EOF__
1739 1777 $ hg add newfile
1740 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-new <<EOF
1778 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '25 0' -medit-patch-new <<EOF
1741 1779 > y
1742 1780 > e
1743 1781 > EOF
1744 1782 diff --git a/newfile b/newfile
1745 1783 new file mode 100644
1746 1784 examine changes to 'newfile'?
1747 1785 (enter ? for help) [Ynesfdaq?] y
1748 1786
1749 1787 @@ -0,0 +1,3 @@
1750 1788 +This is the first line
1751 1789 +This is the second line
1752 1790 +This is the third line
1753 1791 record this change to 'newfile'?
1754 1792 (enter ? for help) [Ynesfdaq?] e
1755 1793
1756 1794 $ hg cat -r tip newfile
1757 1795 This is the very line
1758 1796 This is the second line
1759 1797 This is the third line
1760 1798
1761 1799 $ cat newfile
1762 1800 This is the first line
1763 1801 This is the second line
1764 1802 This is the third line
1765 1803
1766 1804 Add new file from within a subdirectory
1767 1805 $ hg update -C .
1768 1806 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1769 1807 $ mkdir folder
1770 1808 $ cd folder
1771 1809 $ echo "foo" > bar
1772 1810 $ hg add bar
1773 $ hg commit -i -d '23 0' -mnewfilesubdir <<EOF
1811 $ hg commit -i -d '26 0' -mnewfilesubdir <<EOF
1774 1812 > y
1775 1813 > y
1776 1814 > EOF
1777 1815 diff --git a/folder/bar b/folder/bar
1778 1816 new file mode 100644
1779 1817 examine changes to 'folder/bar'?
1780 1818 (enter ? for help) [Ynesfdaq?] y
1781 1819
1782 1820 @@ -0,0 +1,1 @@
1783 1821 +foo
1784 1822 record this change to 'folder/bar'?
1785 1823 (enter ? for help) [Ynesfdaq?] y
1786 1824
1787 1825 The #if execbit block above changes the hashes here on some systems
1788 1826 $ hg tip -p
1789 changeset: 34:* (glob)
1827 changeset: 36:* (glob)
1790 1828 tag: tip
1791 1829 user: test
1792 date: Thu Jan 01 00:00:23 1970 +0000
1830 date: Thu Jan 01 00:00:26 1970 +0000
1793 1831 summary: newfilesubdir
1794 1832
1795 1833 diff -r * -r * folder/bar (glob)
1796 1834 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1797 +++ b/folder/bar Thu Jan 01 00:00:23 1970 +0000
1835 +++ b/folder/bar Thu Jan 01 00:00:26 1970 +0000
1798 1836 @@ -0,0 +1,1 @@
1799 1837 +foo
1800 1838
1801 1839 $ cd ..
1802 1840
1803 1841 $ hg status -A folder/bar
1804 1842 C folder/bar
1805 1843
1806 1844 Clear win32text configuration before size/timestamp sensitive test
1807 1845
1808 1846 $ cat >> .hg/hgrc <<EOF
1809 1847 > [extensions]
1810 1848 > win32text = !
1811 1849 > [decode]
1812 1850 > ** = !
1813 1851 > [encode]
1814 1852 > ** = !
1815 1853 > [patch]
1816 1854 > eol = strict
1817 1855 > EOF
1818 1856 $ hg update -q -C null
1819 1857 $ hg update -q -C tip
1820 1858
1821 1859 Test that partially committed file is still treated as "modified",
1822 1860 even if none of mode, size and timestamp is changed on the filesystem
1823 1861 (see also issue4583).
1824 1862
1825 1863 $ cat > subdir/f1 <<EOF
1826 1864 > A
1827 1865 > a
1828 1866 > a
1829 1867 > b
1830 1868 > c
1831 1869 > d
1832 1870 > E
1833 1871 > EOF
1834 1872 $ hg diff --git subdir/f1
1835 1873 diff --git a/subdir/f1 b/subdir/f1
1836 1874 --- a/subdir/f1
1837 1875 +++ b/subdir/f1
1838 1876 @@ -1,7 +1,7 @@
1839 1877 -a
1840 1878 +A
1841 1879 a
1842 1880 a
1843 1881 b
1844 1882 c
1845 1883 d
1846 1884 -e
1847 1885 +E
1848 1886
1849 1887 $ touch -t 200001010000 subdir/f1
1850 1888
1851 1889 $ cat >> .hg/hgrc <<EOF
1852 1890 > # emulate invoking patch.internalpatch() at 2000-01-01 00:00
1853 1891 > [fakepatchtime]
1854 1892 > fakenow = 200001010000
1855 1893 >
1856 1894 > [extensions]
1857 1895 > fakepatchtime = $TESTDIR/fakepatchtime.py
1858 1896 > EOF
1859 1897 $ hg commit -i -m 'commit subdir/f1 partially' <<EOF
1860 1898 > y
1861 1899 > y
1862 1900 > n
1863 1901 > EOF
1864 1902 diff --git a/subdir/f1 b/subdir/f1
1865 1903 2 hunks, 2 lines changed
1866 1904 examine changes to 'subdir/f1'?
1867 1905 (enter ? for help) [Ynesfdaq?] y
1868 1906
1869 1907 @@ -1,6 +1,6 @@
1870 1908 -a
1871 1909 +A
1872 1910 a
1873 1911 a
1874 1912 b
1875 1913 c
1876 1914 d
1877 1915 record change 1/2 to 'subdir/f1'?
1878 1916 (enter ? for help) [Ynesfdaq?] y
1879 1917
1880 1918 @@ -2,6 +2,6 @@
1881 1919 a
1882 1920 a
1883 1921 b
1884 1922 c
1885 1923 d
1886 1924 -e
1887 1925 +E
1888 1926 record change 2/2 to 'subdir/f1'?
1889 1927 (enter ? for help) [Ynesfdaq?] n
1890 1928
1891 1929 $ cat >> .hg/hgrc <<EOF
1892 1930 > [extensions]
1893 1931 > fakepatchtime = !
1894 1932 > EOF
1895 1933
1896 1934 $ hg debugstate | grep ' subdir/f1$'
1897 1935 n 0 -1 unset subdir/f1
1898 1936 $ hg status -A subdir/f1
1899 1937 M subdir/f1
1900 1938
1901 1939 Test commands.commit.interactive.unified=0
1902 1940
1903 1941 $ hg init $TESTTMP/b
1904 1942 $ cd $TESTTMP/b
1905 1943 $ cat > foo <<EOF
1906 1944 > 1
1907 1945 > 2
1908 1946 > 3
1909 1947 > 4
1910 1948 > 5
1911 1949 > EOF
1912 1950 $ hg ci -qAm initial
1913 1951 $ cat > foo <<EOF
1914 1952 > 1
1915 1953 > change1
1916 1954 > 2
1917 1955 > 3
1918 1956 > change2
1919 1957 > 4
1920 1958 > 5
1921 1959 > EOF
1922 1960 $ printf 'y\ny\ny\n' | hg ci -im initial --config commands.commit.interactive.unified=0
1923 1961 diff --git a/foo b/foo
1924 1962 2 hunks, 2 lines changed
1925 1963 examine changes to 'foo'?
1926 1964 (enter ? for help) [Ynesfdaq?] y
1927 1965
1928 1966 @@ -1,0 +2,1 @@ 1
1929 1967 +change1
1930 1968 record change 1/2 to 'foo'?
1931 1969 (enter ? for help) [Ynesfdaq?] y
1932 1970
1933 1971 @@ -3,0 +5,1 @@ 3
1934 1972 +change2
1935 1973 record change 2/2 to 'foo'?
1936 1974 (enter ? for help) [Ynesfdaq?] y
1937 1975
1938 1976 $ cd $TESTTMP
1939 1977
1940 1978 Test diff.ignoreblanklines=1
1941 1979
1942 1980 $ hg init c
1943 1981 $ cd c
1944 1982 $ cat > foo <<EOF
1945 1983 > 1
1946 1984 > 2
1947 1985 > 3
1948 1986 > 4
1949 1987 > 5
1950 1988 > EOF
1951 1989 $ hg ci -qAm initial
1952 1990 $ cat > foo <<EOF
1953 1991 > 1
1954 1992 >
1955 1993 > 2
1956 1994 > 3
1957 1995 > change2
1958 1996 > 4
1959 1997 > 5
1960 1998 > EOF
1961 1999 $ printf 'y\ny\ny\n' | hg ci -im initial --config diff.ignoreblanklines=1
1962 2000 diff --git a/foo b/foo
1963 2001 2 hunks, 2 lines changed
1964 2002 examine changes to 'foo'?
1965 2003 (enter ? for help) [Ynesfdaq?] y
1966 2004
1967 2005 @@ -1,3 +1,4 @@
1968 2006 1
1969 2007 +
1970 2008 2
1971 2009 3
1972 2010 record change 1/2 to 'foo'?
1973 2011 (enter ? for help) [Ynesfdaq?] y
1974 2012
1975 2013 @@ -2,4 +3,5 @@
1976 2014 2
1977 2015 3
1978 2016 +change2
1979 2017 4
1980 2018 5
1981 2019 record change 2/2 to 'foo'?
1982 2020 (enter ? for help) [Ynesfdaq?] y
1983 2021
1984 2022
General Comments 0
You need to be logged in to leave comments. Login now