##// END OF EJS Templates
errors: raise InputError when given non-existent paths etc...
Martin von Zweigbergk -
r46450:96ca817e default
parent child Browse files
Show More
@@ -1,3913 +1,3913 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import 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 nullid,
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 if pycompat.TYPE_CHECKING:
65 65 from typing import (
66 66 Any,
67 67 Dict,
68 68 )
69 69
70 70 for t in (Any, Dict):
71 71 assert t
72 72
73 73 stringio = util.stringio
74 74
75 75 # templates of common command options
76 76
77 77 dryrunopts = [
78 78 (b'n', b'dry-run', None, _(b'do not perform actions, just print output')),
79 79 ]
80 80
81 81 confirmopts = [
82 82 (b'', b'confirm', None, _(b'ask before applying actions')),
83 83 ]
84 84
85 85 remoteopts = [
86 86 (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')),
87 87 (
88 88 b'',
89 89 b'remotecmd',
90 90 b'',
91 91 _(b'specify hg command to run on the remote side'),
92 92 _(b'CMD'),
93 93 ),
94 94 (
95 95 b'',
96 96 b'insecure',
97 97 None,
98 98 _(b'do not verify server certificate (ignoring web.cacerts config)'),
99 99 ),
100 100 ]
101 101
102 102 walkopts = [
103 103 (
104 104 b'I',
105 105 b'include',
106 106 [],
107 107 _(b'include names matching the given patterns'),
108 108 _(b'PATTERN'),
109 109 ),
110 110 (
111 111 b'X',
112 112 b'exclude',
113 113 [],
114 114 _(b'exclude names matching the given patterns'),
115 115 _(b'PATTERN'),
116 116 ),
117 117 ]
118 118
119 119 commitopts = [
120 120 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
121 121 (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')),
122 122 ]
123 123
124 124 commitopts2 = [
125 125 (
126 126 b'd',
127 127 b'date',
128 128 b'',
129 129 _(b'record the specified date as commit date'),
130 130 _(b'DATE'),
131 131 ),
132 132 (
133 133 b'u',
134 134 b'user',
135 135 b'',
136 136 _(b'record the specified user as committer'),
137 137 _(b'USER'),
138 138 ),
139 139 ]
140 140
141 141 commitopts3 = [
142 142 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
143 143 (b'U', b'currentuser', None, _(b'record the current user as committer')),
144 144 ]
145 145
146 146 formatteropts = [
147 147 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
148 148 ]
149 149
150 150 templateopts = [
151 151 (
152 152 b'',
153 153 b'style',
154 154 b'',
155 155 _(b'display using template map file (DEPRECATED)'),
156 156 _(b'STYLE'),
157 157 ),
158 158 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
159 159 ]
160 160
161 161 logopts = [
162 162 (b'p', b'patch', None, _(b'show patch')),
163 163 (b'g', b'git', None, _(b'use git extended diff format')),
164 164 (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')),
165 165 (b'M', b'no-merges', None, _(b'do not show merges')),
166 166 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
167 167 (b'G', b'graph', None, _(b"show the revision DAG")),
168 168 ] + templateopts
169 169
170 170 diffopts = [
171 171 (b'a', b'text', None, _(b'treat all files as text')),
172 172 (
173 173 b'g',
174 174 b'git',
175 175 None,
176 176 _(b'use git extended diff format (DEFAULT: diff.git)'),
177 177 ),
178 178 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
179 179 (b'', b'nodates', None, _(b'omit dates from diff headers')),
180 180 ]
181 181
182 182 diffwsopts = [
183 183 (
184 184 b'w',
185 185 b'ignore-all-space',
186 186 None,
187 187 _(b'ignore white space when comparing lines'),
188 188 ),
189 189 (
190 190 b'b',
191 191 b'ignore-space-change',
192 192 None,
193 193 _(b'ignore changes in the amount of white space'),
194 194 ),
195 195 (
196 196 b'B',
197 197 b'ignore-blank-lines',
198 198 None,
199 199 _(b'ignore changes whose lines are all blank'),
200 200 ),
201 201 (
202 202 b'Z',
203 203 b'ignore-space-at-eol',
204 204 None,
205 205 _(b'ignore changes in whitespace at EOL'),
206 206 ),
207 207 ]
208 208
209 209 diffopts2 = (
210 210 [
211 211 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
212 212 (
213 213 b'p',
214 214 b'show-function',
215 215 None,
216 216 _(
217 217 b'show which function each change is in (DEFAULT: diff.showfunc)'
218 218 ),
219 219 ),
220 220 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
221 221 ]
222 222 + diffwsopts
223 223 + [
224 224 (
225 225 b'U',
226 226 b'unified',
227 227 b'',
228 228 _(b'number of lines of context to show'),
229 229 _(b'NUM'),
230 230 ),
231 231 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
232 232 (
233 233 b'',
234 234 b'root',
235 235 b'',
236 236 _(b'produce diffs relative to subdirectory'),
237 237 _(b'DIR'),
238 238 ),
239 239 ]
240 240 )
241 241
242 242 mergetoolopts = [
243 243 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
244 244 ]
245 245
246 246 similarityopts = [
247 247 (
248 248 b's',
249 249 b'similarity',
250 250 b'',
251 251 _(b'guess renamed files by similarity (0<=s<=100)'),
252 252 _(b'SIMILARITY'),
253 253 )
254 254 ]
255 255
256 256 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
257 257
258 258 debugrevlogopts = [
259 259 (b'c', b'changelog', False, _(b'open changelog')),
260 260 (b'm', b'manifest', False, _(b'open manifest')),
261 261 (b'', b'dir', b'', _(b'open directory manifest')),
262 262 ]
263 263
264 264 # special string such that everything below this line will be ingored in the
265 265 # editor text
266 266 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
267 267
268 268
269 269 def check_at_most_one_arg(opts, *args):
270 270 """abort if more than one of the arguments are in opts
271 271
272 272 Returns the unique argument or None if none of them were specified.
273 273 """
274 274
275 275 def to_display(name):
276 276 return pycompat.sysbytes(name).replace(b'_', b'-')
277 277
278 278 previous = None
279 279 for x in args:
280 280 if opts.get(x):
281 281 if previous:
282 282 raise error.InputError(
283 283 _(b'cannot specify both --%s and --%s')
284 284 % (to_display(previous), to_display(x))
285 285 )
286 286 previous = x
287 287 return previous
288 288
289 289
290 290 def check_incompatible_arguments(opts, first, others):
291 291 """abort if the first argument is given along with any of the others
292 292
293 293 Unlike check_at_most_one_arg(), `others` are not mutually exclusive
294 294 among themselves, and they're passed as a single collection.
295 295 """
296 296 for other in others:
297 297 check_at_most_one_arg(opts, first, other)
298 298
299 299
300 300 def resolvecommitoptions(ui, opts):
301 301 """modify commit options dict to handle related options
302 302
303 303 The return value indicates that ``rewrite.update-timestamp`` is the reason
304 304 the ``date`` option is set.
305 305 """
306 306 check_at_most_one_arg(opts, b'date', b'currentdate')
307 307 check_at_most_one_arg(opts, b'user', b'currentuser')
308 308
309 309 datemaydiffer = False # date-only change should be ignored?
310 310
311 311 if opts.get(b'currentdate'):
312 312 opts[b'date'] = b'%d %d' % dateutil.makedate()
313 313 elif (
314 314 not opts.get(b'date')
315 315 and ui.configbool(b'rewrite', b'update-timestamp')
316 316 and opts.get(b'currentdate') is None
317 317 ):
318 318 opts[b'date'] = b'%d %d' % dateutil.makedate()
319 319 datemaydiffer = True
320 320
321 321 if opts.get(b'currentuser'):
322 322 opts[b'user'] = ui.username()
323 323
324 324 return datemaydiffer
325 325
326 326
327 327 def checknotesize(ui, opts):
328 328 """ make sure note is of valid format """
329 329
330 330 note = opts.get(b'note')
331 331 if not note:
332 332 return
333 333
334 334 if len(note) > 255:
335 335 raise error.InputError(_(b"cannot store a note of more than 255 bytes"))
336 336 if b'\n' in note:
337 337 raise error.InputError(_(b"note cannot contain a newline"))
338 338
339 339
340 340 def ishunk(x):
341 341 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
342 342 return isinstance(x, hunkclasses)
343 343
344 344
345 345 def newandmodified(chunks, originalchunks):
346 346 newlyaddedandmodifiedfiles = set()
347 347 alsorestore = set()
348 348 for chunk in chunks:
349 349 if (
350 350 ishunk(chunk)
351 351 and chunk.header.isnewfile()
352 352 and chunk not in originalchunks
353 353 ):
354 354 newlyaddedandmodifiedfiles.add(chunk.header.filename())
355 355 alsorestore.update(
356 356 set(chunk.header.files()) - {chunk.header.filename()}
357 357 )
358 358 return newlyaddedandmodifiedfiles, alsorestore
359 359
360 360
361 361 def parsealiases(cmd):
362 362 return cmd.split(b"|")
363 363
364 364
365 365 def setupwrapcolorwrite(ui):
366 366 # wrap ui.write so diff output can be labeled/colorized
367 367 def wrapwrite(orig, *args, **kw):
368 368 label = kw.pop('label', b'')
369 369 for chunk, l in patch.difflabel(lambda: args):
370 370 orig(chunk, label=label + l)
371 371
372 372 oldwrite = ui.write
373 373
374 374 def wrap(*args, **kwargs):
375 375 return wrapwrite(oldwrite, *args, **kwargs)
376 376
377 377 setattr(ui, 'write', wrap)
378 378 return oldwrite
379 379
380 380
381 381 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
382 382 try:
383 383 if usecurses:
384 384 if testfile:
385 385 recordfn = crecordmod.testdecorator(
386 386 testfile, crecordmod.testchunkselector
387 387 )
388 388 else:
389 389 recordfn = crecordmod.chunkselector
390 390
391 391 return crecordmod.filterpatch(
392 392 ui, originalhunks, recordfn, operation
393 393 )
394 394 except crecordmod.fallbackerror as e:
395 395 ui.warn(b'%s\n' % e)
396 396 ui.warn(_(b'falling back to text mode\n'))
397 397
398 398 return patch.filterpatch(ui, originalhunks, match, operation)
399 399
400 400
401 401 def recordfilter(ui, originalhunks, match, operation=None):
402 402 """ Prompts the user to filter the originalhunks and return a list of
403 403 selected hunks.
404 404 *operation* is used for to build ui messages to indicate the user what
405 405 kind of filtering they are doing: reverting, committing, shelving, etc.
406 406 (see patch.filterpatch).
407 407 """
408 408 usecurses = crecordmod.checkcurses(ui)
409 409 testfile = ui.config(b'experimental', b'crecordtest')
410 410 oldwrite = setupwrapcolorwrite(ui)
411 411 try:
412 412 newchunks, newopts = filterchunks(
413 413 ui, originalhunks, usecurses, testfile, match, operation
414 414 )
415 415 finally:
416 416 ui.write = oldwrite
417 417 return newchunks, newopts
418 418
419 419
420 420 def dorecord(
421 421 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
422 422 ):
423 423 opts = pycompat.byteskwargs(opts)
424 424 if not ui.interactive():
425 425 if cmdsuggest:
426 426 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
427 427 else:
428 428 msg = _(b'running non-interactively')
429 429 raise error.InputError(msg)
430 430
431 431 # make sure username is set before going interactive
432 432 if not opts.get(b'user'):
433 433 ui.username() # raise exception, username not provided
434 434
435 435 def recordfunc(ui, repo, message, match, opts):
436 436 """This is generic record driver.
437 437
438 438 Its job is to interactively filter local changes, and
439 439 accordingly prepare working directory into a state in which the
440 440 job can be delegated to a non-interactive commit command such as
441 441 'commit' or 'qrefresh'.
442 442
443 443 After the actual job is done by non-interactive command, the
444 444 working directory is restored to its original state.
445 445
446 446 In the end we'll record interesting changes, and everything else
447 447 will be left in place, so the user can continue working.
448 448 """
449 449 if not opts.get(b'interactive-unshelve'):
450 450 checkunfinished(repo, commit=True)
451 451 wctx = repo[None]
452 452 merge = len(wctx.parents()) > 1
453 453 if merge:
454 454 raise error.InputError(
455 455 _(
456 456 b'cannot partially commit a merge '
457 457 b'(use "hg commit" instead)'
458 458 )
459 459 )
460 460
461 461 def fail(f, msg):
462 raise error.Abort(b'%s: %s' % (f, msg))
462 raise error.InputError(b'%s: %s' % (f, msg))
463 463
464 464 force = opts.get(b'force')
465 465 if not force:
466 466 match = matchmod.badmatch(match, fail)
467 467
468 468 status = repo.status(match=match)
469 469
470 470 overrides = {(b'ui', b'commitsubrepos'): True}
471 471
472 472 with repo.ui.configoverride(overrides, b'record'):
473 473 # subrepoutil.precommit() modifies the status
474 474 tmpstatus = scmutil.status(
475 475 copymod.copy(status.modified),
476 476 copymod.copy(status.added),
477 477 copymod.copy(status.removed),
478 478 copymod.copy(status.deleted),
479 479 copymod.copy(status.unknown),
480 480 copymod.copy(status.ignored),
481 481 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
482 482 )
483 483
484 484 # Force allows -X subrepo to skip the subrepo.
485 485 subs, commitsubs, newstate = subrepoutil.precommit(
486 486 repo.ui, wctx, tmpstatus, match, force=True
487 487 )
488 488 for s in subs:
489 489 if s in commitsubs:
490 490 dirtyreason = wctx.sub(s).dirtyreason(True)
491 491 raise error.Abort(dirtyreason)
492 492
493 493 if not force:
494 494 repo.checkcommitpatterns(wctx, match, status, fail)
495 495 diffopts = patch.difffeatureopts(
496 496 ui,
497 497 opts=opts,
498 498 whitespace=True,
499 499 section=b'commands',
500 500 configprefix=b'commit.interactive.',
501 501 )
502 502 diffopts.nodates = True
503 503 diffopts.git = True
504 504 diffopts.showfunc = True
505 505 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
506 506 originalchunks = patch.parsepatch(originaldiff)
507 507 match = scmutil.match(repo[None], pats)
508 508
509 509 # 1. filter patch, since we are intending to apply subset of it
510 510 try:
511 511 chunks, newopts = filterfn(ui, originalchunks, match)
512 512 except error.PatchError as err:
513 513 raise error.InputError(_(b'error parsing patch: %s') % err)
514 514 opts.update(newopts)
515 515
516 516 # We need to keep a backup of files that have been newly added and
517 517 # modified during the recording process because there is a previous
518 518 # version without the edit in the workdir. We also will need to restore
519 519 # files that were the sources of renames so that the patch application
520 520 # works.
521 521 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
522 522 chunks, originalchunks
523 523 )
524 524 contenders = set()
525 525 for h in chunks:
526 526 try:
527 527 contenders.update(set(h.files()))
528 528 except AttributeError:
529 529 pass
530 530
531 531 changed = status.modified + status.added + status.removed
532 532 newfiles = [f for f in changed if f in contenders]
533 533 if not newfiles:
534 534 ui.status(_(b'no changes to record\n'))
535 535 return 0
536 536
537 537 modified = set(status.modified)
538 538
539 539 # 2. backup changed files, so we can restore them in the end
540 540
541 541 if backupall:
542 542 tobackup = changed
543 543 else:
544 544 tobackup = [
545 545 f
546 546 for f in newfiles
547 547 if f in modified or f in newlyaddedandmodifiedfiles
548 548 ]
549 549 backups = {}
550 550 if tobackup:
551 551 backupdir = repo.vfs.join(b'record-backups')
552 552 try:
553 553 os.mkdir(backupdir)
554 554 except OSError as err:
555 555 if err.errno != errno.EEXIST:
556 556 raise
557 557 try:
558 558 # backup continues
559 559 for f in tobackup:
560 560 fd, tmpname = pycompat.mkstemp(
561 561 prefix=os.path.basename(f) + b'.', dir=backupdir
562 562 )
563 563 os.close(fd)
564 564 ui.debug(b'backup %r as %r\n' % (f, tmpname))
565 565 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
566 566 backups[f] = tmpname
567 567
568 568 fp = stringio()
569 569 for c in chunks:
570 570 fname = c.filename()
571 571 if fname in backups:
572 572 c.write(fp)
573 573 dopatch = fp.tell()
574 574 fp.seek(0)
575 575
576 576 # 2.5 optionally review / modify patch in text editor
577 577 if opts.get(b'review', False):
578 578 patchtext = (
579 579 crecordmod.diffhelptext
580 580 + crecordmod.patchhelptext
581 581 + fp.read()
582 582 )
583 583 reviewedpatch = ui.edit(
584 584 patchtext, b"", action=b"diff", repopath=repo.path
585 585 )
586 586 fp.truncate(0)
587 587 fp.write(reviewedpatch)
588 588 fp.seek(0)
589 589
590 590 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
591 591 # 3a. apply filtered patch to clean repo (clean)
592 592 if backups:
593 593 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
594 594 mergemod.revert_to(repo[b'.'], matcher=m)
595 595
596 596 # 3b. (apply)
597 597 if dopatch:
598 598 try:
599 599 ui.debug(b'applying patch\n')
600 600 ui.debug(fp.getvalue())
601 601 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
602 602 except error.PatchError as err:
603 603 raise error.InputError(pycompat.bytestr(err))
604 604 del fp
605 605
606 606 # 4. We prepared working directory according to filtered
607 607 # patch. Now is the time to delegate the job to
608 608 # commit/qrefresh or the like!
609 609
610 610 # Make all of the pathnames absolute.
611 611 newfiles = [repo.wjoin(nf) for nf in newfiles]
612 612 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
613 613 finally:
614 614 # 5. finally restore backed-up files
615 615 try:
616 616 dirstate = repo.dirstate
617 617 for realname, tmpname in pycompat.iteritems(backups):
618 618 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
619 619
620 620 if dirstate[realname] == b'n':
621 621 # without normallookup, restoring timestamp
622 622 # may cause partially committed files
623 623 # to be treated as unmodified
624 624 dirstate.normallookup(realname)
625 625
626 626 # copystat=True here and above are a hack to trick any
627 627 # editors that have f open that we haven't modified them.
628 628 #
629 629 # Also note that this racy as an editor could notice the
630 630 # file's mtime before we've finished writing it.
631 631 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
632 632 os.unlink(tmpname)
633 633 if tobackup:
634 634 os.rmdir(backupdir)
635 635 except OSError:
636 636 pass
637 637
638 638 def recordinwlock(ui, repo, message, match, opts):
639 639 with repo.wlock():
640 640 return recordfunc(ui, repo, message, match, opts)
641 641
642 642 return commit(ui, repo, recordinwlock, pats, opts)
643 643
644 644
645 645 class dirnode(object):
646 646 """
647 647 Represent a directory in user working copy with information required for
648 648 the purpose of tersing its status.
649 649
650 650 path is the path to the directory, without a trailing '/'
651 651
652 652 statuses is a set of statuses of all files in this directory (this includes
653 653 all the files in all the subdirectories too)
654 654
655 655 files is a list of files which are direct child of this directory
656 656
657 657 subdirs is a dictionary of sub-directory name as the key and it's own
658 658 dirnode object as the value
659 659 """
660 660
661 661 def __init__(self, dirpath):
662 662 self.path = dirpath
663 663 self.statuses = set()
664 664 self.files = []
665 665 self.subdirs = {}
666 666
667 667 def _addfileindir(self, filename, status):
668 668 """Add a file in this directory as a direct child."""
669 669 self.files.append((filename, status))
670 670
671 671 def addfile(self, filename, status):
672 672 """
673 673 Add a file to this directory or to its direct parent directory.
674 674
675 675 If the file is not direct child of this directory, we traverse to the
676 676 directory of which this file is a direct child of and add the file
677 677 there.
678 678 """
679 679
680 680 # the filename contains a path separator, it means it's not the direct
681 681 # child of this directory
682 682 if b'/' in filename:
683 683 subdir, filep = filename.split(b'/', 1)
684 684
685 685 # does the dirnode object for subdir exists
686 686 if subdir not in self.subdirs:
687 687 subdirpath = pathutil.join(self.path, subdir)
688 688 self.subdirs[subdir] = dirnode(subdirpath)
689 689
690 690 # try adding the file in subdir
691 691 self.subdirs[subdir].addfile(filep, status)
692 692
693 693 else:
694 694 self._addfileindir(filename, status)
695 695
696 696 if status not in self.statuses:
697 697 self.statuses.add(status)
698 698
699 699 def iterfilepaths(self):
700 700 """Yield (status, path) for files directly under this directory."""
701 701 for f, st in self.files:
702 702 yield st, pathutil.join(self.path, f)
703 703
704 704 def tersewalk(self, terseargs):
705 705 """
706 706 Yield (status, path) obtained by processing the status of this
707 707 dirnode.
708 708
709 709 terseargs is the string of arguments passed by the user with `--terse`
710 710 flag.
711 711
712 712 Following are the cases which can happen:
713 713
714 714 1) All the files in the directory (including all the files in its
715 715 subdirectories) share the same status and the user has asked us to terse
716 716 that status. -> yield (status, dirpath). dirpath will end in '/'.
717 717
718 718 2) Otherwise, we do following:
719 719
720 720 a) Yield (status, filepath) for all the files which are in this
721 721 directory (only the ones in this directory, not the subdirs)
722 722
723 723 b) Recurse the function on all the subdirectories of this
724 724 directory
725 725 """
726 726
727 727 if len(self.statuses) == 1:
728 728 onlyst = self.statuses.pop()
729 729
730 730 # Making sure we terse only when the status abbreviation is
731 731 # passed as terse argument
732 732 if onlyst in terseargs:
733 733 yield onlyst, self.path + b'/'
734 734 return
735 735
736 736 # add the files to status list
737 737 for st, fpath in self.iterfilepaths():
738 738 yield st, fpath
739 739
740 740 # recurse on the subdirs
741 741 for dirobj in self.subdirs.values():
742 742 for st, fpath in dirobj.tersewalk(terseargs):
743 743 yield st, fpath
744 744
745 745
746 746 def tersedir(statuslist, terseargs):
747 747 """
748 748 Terse the status if all the files in a directory shares the same status.
749 749
750 750 statuslist is scmutil.status() object which contains a list of files for
751 751 each status.
752 752 terseargs is string which is passed by the user as the argument to `--terse`
753 753 flag.
754 754
755 755 The function makes a tree of objects of dirnode class, and at each node it
756 756 stores the information required to know whether we can terse a certain
757 757 directory or not.
758 758 """
759 759 # the order matters here as that is used to produce final list
760 760 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
761 761
762 762 # checking the argument validity
763 763 for s in pycompat.bytestr(terseargs):
764 764 if s not in allst:
765 765 raise error.InputError(_(b"'%s' not recognized") % s)
766 766
767 767 # creating a dirnode object for the root of the repo
768 768 rootobj = dirnode(b'')
769 769 pstatus = (
770 770 b'modified',
771 771 b'added',
772 772 b'deleted',
773 773 b'clean',
774 774 b'unknown',
775 775 b'ignored',
776 776 b'removed',
777 777 )
778 778
779 779 tersedict = {}
780 780 for attrname in pstatus:
781 781 statuschar = attrname[0:1]
782 782 for f in getattr(statuslist, attrname):
783 783 rootobj.addfile(f, statuschar)
784 784 tersedict[statuschar] = []
785 785
786 786 # we won't be tersing the root dir, so add files in it
787 787 for st, fpath in rootobj.iterfilepaths():
788 788 tersedict[st].append(fpath)
789 789
790 790 # process each sub-directory and build tersedict
791 791 for subdir in rootobj.subdirs.values():
792 792 for st, f in subdir.tersewalk(terseargs):
793 793 tersedict[st].append(f)
794 794
795 795 tersedlist = []
796 796 for st in allst:
797 797 tersedict[st].sort()
798 798 tersedlist.append(tersedict[st])
799 799
800 800 return scmutil.status(*tersedlist)
801 801
802 802
803 803 def _commentlines(raw):
804 804 '''Surround lineswith a comment char and a new line'''
805 805 lines = raw.splitlines()
806 806 commentedlines = [b'# %s' % line for line in lines]
807 807 return b'\n'.join(commentedlines) + b'\n'
808 808
809 809
810 810 @attr.s(frozen=True)
811 811 class morestatus(object):
812 812 reporoot = attr.ib()
813 813 unfinishedop = attr.ib()
814 814 unfinishedmsg = attr.ib()
815 815 activemerge = attr.ib()
816 816 unresolvedpaths = attr.ib()
817 817 _formattedpaths = attr.ib(init=False, default=set())
818 818 _label = b'status.morestatus'
819 819
820 820 def formatfile(self, path, fm):
821 821 self._formattedpaths.add(path)
822 822 if self.activemerge and path in self.unresolvedpaths:
823 823 fm.data(unresolved=True)
824 824
825 825 def formatfooter(self, fm):
826 826 if self.unfinishedop or self.unfinishedmsg:
827 827 fm.startitem()
828 828 fm.data(itemtype=b'morestatus')
829 829
830 830 if self.unfinishedop:
831 831 fm.data(unfinished=self.unfinishedop)
832 832 statemsg = (
833 833 _(b'The repository is in an unfinished *%s* state.')
834 834 % self.unfinishedop
835 835 )
836 836 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
837 837 if self.unfinishedmsg:
838 838 fm.data(unfinishedmsg=self.unfinishedmsg)
839 839
840 840 # May also start new data items.
841 841 self._formatconflicts(fm)
842 842
843 843 if self.unfinishedmsg:
844 844 fm.plain(
845 845 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
846 846 )
847 847
848 848 def _formatconflicts(self, fm):
849 849 if not self.activemerge:
850 850 return
851 851
852 852 if self.unresolvedpaths:
853 853 mergeliststr = b'\n'.join(
854 854 [
855 855 b' %s'
856 856 % util.pathto(self.reporoot, encoding.getcwd(), path)
857 857 for path in self.unresolvedpaths
858 858 ]
859 859 )
860 860 msg = (
861 861 _(
862 862 '''Unresolved merge conflicts:
863 863
864 864 %s
865 865
866 866 To mark files as resolved: hg resolve --mark FILE'''
867 867 )
868 868 % mergeliststr
869 869 )
870 870
871 871 # If any paths with unresolved conflicts were not previously
872 872 # formatted, output them now.
873 873 for f in self.unresolvedpaths:
874 874 if f in self._formattedpaths:
875 875 # Already output.
876 876 continue
877 877 fm.startitem()
878 878 # We can't claim to know the status of the file - it may just
879 879 # have been in one of the states that were not requested for
880 880 # display, so it could be anything.
881 881 fm.data(itemtype=b'file', path=f, unresolved=True)
882 882
883 883 else:
884 884 msg = _(b'No unresolved merge conflicts.')
885 885
886 886 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
887 887
888 888
889 889 def readmorestatus(repo):
890 890 """Returns a morestatus object if the repo has unfinished state."""
891 891 statetuple = statemod.getrepostate(repo)
892 892 mergestate = mergestatemod.mergestate.read(repo)
893 893 activemerge = mergestate.active()
894 894 if not statetuple and not activemerge:
895 895 return None
896 896
897 897 unfinishedop = unfinishedmsg = unresolved = None
898 898 if statetuple:
899 899 unfinishedop, unfinishedmsg = statetuple
900 900 if activemerge:
901 901 unresolved = sorted(mergestate.unresolved())
902 902 return morestatus(
903 903 repo.root, unfinishedop, unfinishedmsg, activemerge, unresolved
904 904 )
905 905
906 906
907 907 def findpossible(cmd, table, strict=False):
908 908 """
909 909 Return cmd -> (aliases, command table entry)
910 910 for each matching command.
911 911 Return debug commands (or their aliases) only if no normal command matches.
912 912 """
913 913 choice = {}
914 914 debugchoice = {}
915 915
916 916 if cmd in table:
917 917 # short-circuit exact matches, "log" alias beats "log|history"
918 918 keys = [cmd]
919 919 else:
920 920 keys = table.keys()
921 921
922 922 allcmds = []
923 923 for e in keys:
924 924 aliases = parsealiases(e)
925 925 allcmds.extend(aliases)
926 926 found = None
927 927 if cmd in aliases:
928 928 found = cmd
929 929 elif not strict:
930 930 for a in aliases:
931 931 if a.startswith(cmd):
932 932 found = a
933 933 break
934 934 if found is not None:
935 935 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
936 936 debugchoice[found] = (aliases, table[e])
937 937 else:
938 938 choice[found] = (aliases, table[e])
939 939
940 940 if not choice and debugchoice:
941 941 choice = debugchoice
942 942
943 943 return choice, allcmds
944 944
945 945
946 946 def findcmd(cmd, table, strict=True):
947 947 """Return (aliases, command table entry) for command string."""
948 948 choice, allcmds = findpossible(cmd, table, strict)
949 949
950 950 if cmd in choice:
951 951 return choice[cmd]
952 952
953 953 if len(choice) > 1:
954 954 clist = sorted(choice)
955 955 raise error.AmbiguousCommand(cmd, clist)
956 956
957 957 if choice:
958 958 return list(choice.values())[0]
959 959
960 960 raise error.UnknownCommand(cmd, allcmds)
961 961
962 962
963 963 def changebranch(ui, repo, revs, label, opts):
964 964 """ Change the branch name of given revs to label """
965 965
966 966 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
967 967 # abort in case of uncommitted merge or dirty wdir
968 968 bailifchanged(repo)
969 969 revs = scmutil.revrange(repo, revs)
970 970 if not revs:
971 971 raise error.InputError(b"empty revision set")
972 972 roots = repo.revs(b'roots(%ld)', revs)
973 973 if len(roots) > 1:
974 974 raise error.InputError(
975 975 _(b"cannot change branch of non-linear revisions")
976 976 )
977 977 rewriteutil.precheck(repo, revs, b'change branch of')
978 978
979 979 root = repo[roots.first()]
980 980 rpb = {parent.branch() for parent in root.parents()}
981 981 if (
982 982 not opts.get(b'force')
983 983 and label not in rpb
984 984 and label in repo.branchmap()
985 985 ):
986 986 raise error.InputError(
987 987 _(b"a branch of the same name already exists")
988 988 )
989 989
990 990 if repo.revs(b'obsolete() and %ld', revs):
991 991 raise error.InputError(
992 992 _(b"cannot change branch of a obsolete changeset")
993 993 )
994 994
995 995 # make sure only topological heads
996 996 if repo.revs(b'heads(%ld) - head()', revs):
997 997 raise error.InputError(
998 998 _(b"cannot change branch in middle of a stack")
999 999 )
1000 1000
1001 1001 replacements = {}
1002 1002 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
1003 1003 # mercurial.subrepo -> mercurial.cmdutil
1004 1004 from . import context
1005 1005
1006 1006 for rev in revs:
1007 1007 ctx = repo[rev]
1008 1008 oldbranch = ctx.branch()
1009 1009 # check if ctx has same branch
1010 1010 if oldbranch == label:
1011 1011 continue
1012 1012
1013 1013 def filectxfn(repo, newctx, path):
1014 1014 try:
1015 1015 return ctx[path]
1016 1016 except error.ManifestLookupError:
1017 1017 return None
1018 1018
1019 1019 ui.debug(
1020 1020 b"changing branch of '%s' from '%s' to '%s'\n"
1021 1021 % (hex(ctx.node()), oldbranch, label)
1022 1022 )
1023 1023 extra = ctx.extra()
1024 1024 extra[b'branch_change'] = hex(ctx.node())
1025 1025 # While changing branch of set of linear commits, make sure that
1026 1026 # we base our commits on new parent rather than old parent which
1027 1027 # was obsoleted while changing the branch
1028 1028 p1 = ctx.p1().node()
1029 1029 p2 = ctx.p2().node()
1030 1030 if p1 in replacements:
1031 1031 p1 = replacements[p1][0]
1032 1032 if p2 in replacements:
1033 1033 p2 = replacements[p2][0]
1034 1034
1035 1035 mc = context.memctx(
1036 1036 repo,
1037 1037 (p1, p2),
1038 1038 ctx.description(),
1039 1039 ctx.files(),
1040 1040 filectxfn,
1041 1041 user=ctx.user(),
1042 1042 date=ctx.date(),
1043 1043 extra=extra,
1044 1044 branch=label,
1045 1045 )
1046 1046
1047 1047 newnode = repo.commitctx(mc)
1048 1048 replacements[ctx.node()] = (newnode,)
1049 1049 ui.debug(b'new node id is %s\n' % hex(newnode))
1050 1050
1051 1051 # create obsmarkers and move bookmarks
1052 1052 scmutil.cleanupnodes(
1053 1053 repo, replacements, b'branch-change', fixphase=True
1054 1054 )
1055 1055
1056 1056 # move the working copy too
1057 1057 wctx = repo[None]
1058 1058 # in-progress merge is a bit too complex for now.
1059 1059 if len(wctx.parents()) == 1:
1060 1060 newid = replacements.get(wctx.p1().node())
1061 1061 if newid is not None:
1062 1062 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1063 1063 # mercurial.cmdutil
1064 1064 from . import hg
1065 1065
1066 1066 hg.update(repo, newid[0], quietempty=True)
1067 1067
1068 1068 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1069 1069
1070 1070
1071 1071 def findrepo(p):
1072 1072 while not os.path.isdir(os.path.join(p, b".hg")):
1073 1073 oldp, p = p, os.path.dirname(p)
1074 1074 if p == oldp:
1075 1075 return None
1076 1076
1077 1077 return p
1078 1078
1079 1079
1080 1080 def bailifchanged(repo, merge=True, hint=None):
1081 1081 """ enforce the precondition that working directory must be clean.
1082 1082
1083 1083 'merge' can be set to false if a pending uncommitted merge should be
1084 1084 ignored (such as when 'update --check' runs).
1085 1085
1086 1086 'hint' is the usual hint given to Abort exception.
1087 1087 """
1088 1088
1089 1089 if merge and repo.dirstate.p2() != nullid:
1090 1090 raise error.StateError(_(b'outstanding uncommitted merge'), hint=hint)
1091 1091 st = repo.status()
1092 1092 if st.modified or st.added or st.removed or st.deleted:
1093 1093 raise error.StateError(_(b'uncommitted changes'), hint=hint)
1094 1094 ctx = repo[None]
1095 1095 for s in sorted(ctx.substate):
1096 1096 ctx.sub(s).bailifchanged(hint=hint)
1097 1097
1098 1098
1099 1099 def logmessage(ui, opts):
1100 1100 """ get the log message according to -m and -l option """
1101 1101
1102 1102 check_at_most_one_arg(opts, b'message', b'logfile')
1103 1103
1104 1104 message = opts.get(b'message')
1105 1105 logfile = opts.get(b'logfile')
1106 1106
1107 1107 if not message and logfile:
1108 1108 try:
1109 1109 if isstdiofilename(logfile):
1110 1110 message = ui.fin.read()
1111 1111 else:
1112 1112 message = b'\n'.join(util.readfile(logfile).splitlines())
1113 1113 except IOError as inst:
1114 1114 raise error.Abort(
1115 1115 _(b"can't read commit message '%s': %s")
1116 1116 % (logfile, encoding.strtolocal(inst.strerror))
1117 1117 )
1118 1118 return message
1119 1119
1120 1120
1121 1121 def mergeeditform(ctxorbool, baseformname):
1122 1122 """return appropriate editform name (referencing a committemplate)
1123 1123
1124 1124 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1125 1125 merging is committed.
1126 1126
1127 1127 This returns baseformname with '.merge' appended if it is a merge,
1128 1128 otherwise '.normal' is appended.
1129 1129 """
1130 1130 if isinstance(ctxorbool, bool):
1131 1131 if ctxorbool:
1132 1132 return baseformname + b".merge"
1133 1133 elif len(ctxorbool.parents()) > 1:
1134 1134 return baseformname + b".merge"
1135 1135
1136 1136 return baseformname + b".normal"
1137 1137
1138 1138
1139 1139 def getcommiteditor(
1140 1140 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1141 1141 ):
1142 1142 """get appropriate commit message editor according to '--edit' option
1143 1143
1144 1144 'finishdesc' is a function to be called with edited commit message
1145 1145 (= 'description' of the new changeset) just after editing, but
1146 1146 before checking empty-ness. It should return actual text to be
1147 1147 stored into history. This allows to change description before
1148 1148 storing.
1149 1149
1150 1150 'extramsg' is a extra message to be shown in the editor instead of
1151 1151 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1152 1152 is automatically added.
1153 1153
1154 1154 'editform' is a dot-separated list of names, to distinguish
1155 1155 the purpose of commit text editing.
1156 1156
1157 1157 'getcommiteditor' returns 'commitforceeditor' regardless of
1158 1158 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1159 1159 they are specific for usage in MQ.
1160 1160 """
1161 1161 if edit or finishdesc or extramsg:
1162 1162 return lambda r, c, s: commitforceeditor(
1163 1163 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1164 1164 )
1165 1165 elif editform:
1166 1166 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1167 1167 else:
1168 1168 return commiteditor
1169 1169
1170 1170
1171 1171 def _escapecommandtemplate(tmpl):
1172 1172 parts = []
1173 1173 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1174 1174 if typ == b'string':
1175 1175 parts.append(stringutil.escapestr(tmpl[start:end]))
1176 1176 else:
1177 1177 parts.append(tmpl[start:end])
1178 1178 return b''.join(parts)
1179 1179
1180 1180
1181 1181 def rendercommandtemplate(ui, tmpl, props):
1182 1182 r"""Expand a literal template 'tmpl' in a way suitable for command line
1183 1183
1184 1184 '\' in outermost string is not taken as an escape character because it
1185 1185 is a directory separator on Windows.
1186 1186
1187 1187 >>> from . import ui as uimod
1188 1188 >>> ui = uimod.ui()
1189 1189 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1190 1190 'c:\\foo'
1191 1191 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1192 1192 'c:{path}'
1193 1193 """
1194 1194 if not tmpl:
1195 1195 return tmpl
1196 1196 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1197 1197 return t.renderdefault(props)
1198 1198
1199 1199
1200 1200 def rendertemplate(ctx, tmpl, props=None):
1201 1201 """Expand a literal template 'tmpl' byte-string against one changeset
1202 1202
1203 1203 Each props item must be a stringify-able value or a callable returning
1204 1204 such value, i.e. no bare list nor dict should be passed.
1205 1205 """
1206 1206 repo = ctx.repo()
1207 1207 tres = formatter.templateresources(repo.ui, repo)
1208 1208 t = formatter.maketemplater(
1209 1209 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1210 1210 )
1211 1211 mapping = {b'ctx': ctx}
1212 1212 if props:
1213 1213 mapping.update(props)
1214 1214 return t.renderdefault(mapping)
1215 1215
1216 1216
1217 1217 def format_changeset_summary(ui, ctx, command=None, default_spec=None):
1218 1218 """Format a changeset summary (one line)."""
1219 1219 spec = None
1220 1220 if command:
1221 1221 spec = ui.config(
1222 1222 b'command-templates', b'oneline-summary.%s' % command, None
1223 1223 )
1224 1224 if not spec:
1225 1225 spec = ui.config(b'command-templates', b'oneline-summary')
1226 1226 if not spec:
1227 1227 spec = default_spec
1228 1228 if not spec:
1229 1229 spec = (
1230 1230 b'{separate(" ", '
1231 1231 b'label("oneline-summary.changeset", "{rev}:{node|short}")'
1232 1232 b', '
1233 1233 b'join(filter(namespaces % "{ifeq(namespace, "branches", "", join(names % "{label("oneline-summary.{namespace}", name)}", " "))}"), " ")'
1234 1234 b')} '
1235 1235 b'"{label("oneline-summary.desc", desc|firstline)}"'
1236 1236 )
1237 1237 text = rendertemplate(ctx, spec)
1238 1238 return text.split(b'\n')[0]
1239 1239
1240 1240
1241 1241 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1242 1242 r"""Convert old-style filename format string to template string
1243 1243
1244 1244 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1245 1245 'foo-{reporoot|basename}-{seqno}.patch'
1246 1246 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1247 1247 '{rev}{tags % "{tag}"}{node}'
1248 1248
1249 1249 '\' in outermost strings has to be escaped because it is a directory
1250 1250 separator on Windows:
1251 1251
1252 1252 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1253 1253 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1254 1254 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1255 1255 '\\\\\\\\foo\\\\bar.patch'
1256 1256 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1257 1257 '\\\\{tags % "{tag}"}'
1258 1258
1259 1259 but inner strings follow the template rules (i.e. '\' is taken as an
1260 1260 escape character):
1261 1261
1262 1262 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1263 1263 '{"c:\\tmp"}'
1264 1264 """
1265 1265 expander = {
1266 1266 b'H': b'{node}',
1267 1267 b'R': b'{rev}',
1268 1268 b'h': b'{node|short}',
1269 1269 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1270 1270 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1271 1271 b'%': b'%',
1272 1272 b'b': b'{reporoot|basename}',
1273 1273 }
1274 1274 if total is not None:
1275 1275 expander[b'N'] = b'{total}'
1276 1276 if seqno is not None:
1277 1277 expander[b'n'] = b'{seqno}'
1278 1278 if total is not None and seqno is not None:
1279 1279 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1280 1280 if pathname is not None:
1281 1281 expander[b's'] = b'{pathname|basename}'
1282 1282 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1283 1283 expander[b'p'] = b'{pathname}'
1284 1284
1285 1285 newname = []
1286 1286 for typ, start, end in templater.scantemplate(pat, raw=True):
1287 1287 if typ != b'string':
1288 1288 newname.append(pat[start:end])
1289 1289 continue
1290 1290 i = start
1291 1291 while i < end:
1292 1292 n = pat.find(b'%', i, end)
1293 1293 if n < 0:
1294 1294 newname.append(stringutil.escapestr(pat[i:end]))
1295 1295 break
1296 1296 newname.append(stringutil.escapestr(pat[i:n]))
1297 1297 if n + 2 > end:
1298 1298 raise error.Abort(
1299 1299 _(b"incomplete format spec in output filename")
1300 1300 )
1301 1301 c = pat[n + 1 : n + 2]
1302 1302 i = n + 2
1303 1303 try:
1304 1304 newname.append(expander[c])
1305 1305 except KeyError:
1306 1306 raise error.Abort(
1307 1307 _(b"invalid format spec '%%%s' in output filename") % c
1308 1308 )
1309 1309 return b''.join(newname)
1310 1310
1311 1311
1312 1312 def makefilename(ctx, pat, **props):
1313 1313 if not pat:
1314 1314 return pat
1315 1315 tmpl = _buildfntemplate(pat, **props)
1316 1316 # BUG: alias expansion shouldn't be made against template fragments
1317 1317 # rewritten from %-format strings, but we have no easy way to partially
1318 1318 # disable the expansion.
1319 1319 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1320 1320
1321 1321
1322 1322 def isstdiofilename(pat):
1323 1323 """True if the given pat looks like a filename denoting stdin/stdout"""
1324 1324 return not pat or pat == b'-'
1325 1325
1326 1326
1327 1327 class _unclosablefile(object):
1328 1328 def __init__(self, fp):
1329 1329 self._fp = fp
1330 1330
1331 1331 def close(self):
1332 1332 pass
1333 1333
1334 1334 def __iter__(self):
1335 1335 return iter(self._fp)
1336 1336
1337 1337 def __getattr__(self, attr):
1338 1338 return getattr(self._fp, attr)
1339 1339
1340 1340 def __enter__(self):
1341 1341 return self
1342 1342
1343 1343 def __exit__(self, exc_type, exc_value, exc_tb):
1344 1344 pass
1345 1345
1346 1346
1347 1347 def makefileobj(ctx, pat, mode=b'wb', **props):
1348 1348 writable = mode not in (b'r', b'rb')
1349 1349
1350 1350 if isstdiofilename(pat):
1351 1351 repo = ctx.repo()
1352 1352 if writable:
1353 1353 fp = repo.ui.fout
1354 1354 else:
1355 1355 fp = repo.ui.fin
1356 1356 return _unclosablefile(fp)
1357 1357 fn = makefilename(ctx, pat, **props)
1358 1358 return open(fn, mode)
1359 1359
1360 1360
1361 1361 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1362 1362 """opens the changelog, manifest, a filelog or a given revlog"""
1363 1363 cl = opts[b'changelog']
1364 1364 mf = opts[b'manifest']
1365 1365 dir = opts[b'dir']
1366 1366 msg = None
1367 1367 if cl and mf:
1368 1368 msg = _(b'cannot specify --changelog and --manifest at the same time')
1369 1369 elif cl and dir:
1370 1370 msg = _(b'cannot specify --changelog and --dir at the same time')
1371 1371 elif cl or mf or dir:
1372 1372 if file_:
1373 1373 msg = _(b'cannot specify filename with --changelog or --manifest')
1374 1374 elif not repo:
1375 1375 msg = _(
1376 1376 b'cannot specify --changelog or --manifest or --dir '
1377 1377 b'without a repository'
1378 1378 )
1379 1379 if msg:
1380 1380 raise error.InputError(msg)
1381 1381
1382 1382 r = None
1383 1383 if repo:
1384 1384 if cl:
1385 1385 r = repo.unfiltered().changelog
1386 1386 elif dir:
1387 1387 if not scmutil.istreemanifest(repo):
1388 1388 raise error.InputError(
1389 1389 _(
1390 1390 b"--dir can only be used on repos with "
1391 1391 b"treemanifest enabled"
1392 1392 )
1393 1393 )
1394 1394 if not dir.endswith(b'/'):
1395 1395 dir = dir + b'/'
1396 1396 dirlog = repo.manifestlog.getstorage(dir)
1397 1397 if len(dirlog):
1398 1398 r = dirlog
1399 1399 elif mf:
1400 1400 r = repo.manifestlog.getstorage(b'')
1401 1401 elif file_:
1402 1402 filelog = repo.file(file_)
1403 1403 if len(filelog):
1404 1404 r = filelog
1405 1405
1406 1406 # Not all storage may be revlogs. If requested, try to return an actual
1407 1407 # revlog instance.
1408 1408 if returnrevlog:
1409 1409 if isinstance(r, revlog.revlog):
1410 1410 pass
1411 1411 elif util.safehasattr(r, b'_revlog'):
1412 1412 r = r._revlog # pytype: disable=attribute-error
1413 1413 elif r is not None:
1414 1414 raise error.InputError(
1415 1415 _(b'%r does not appear to be a revlog') % r
1416 1416 )
1417 1417
1418 1418 if not r:
1419 1419 if not returnrevlog:
1420 1420 raise error.InputError(_(b'cannot give path to non-revlog'))
1421 1421
1422 1422 if not file_:
1423 1423 raise error.CommandError(cmd, _(b'invalid arguments'))
1424 1424 if not os.path.isfile(file_):
1425 1425 raise error.InputError(_(b"revlog '%s' not found") % file_)
1426 1426 r = revlog.revlog(
1427 1427 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1428 1428 )
1429 1429 return r
1430 1430
1431 1431
1432 1432 def openrevlog(repo, cmd, file_, opts):
1433 1433 """Obtain a revlog backing storage of an item.
1434 1434
1435 1435 This is similar to ``openstorage()`` except it always returns a revlog.
1436 1436
1437 1437 In most cases, a caller cares about the main storage object - not the
1438 1438 revlog backing it. Therefore, this function should only be used by code
1439 1439 that needs to examine low-level revlog implementation details. e.g. debug
1440 1440 commands.
1441 1441 """
1442 1442 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1443 1443
1444 1444
1445 1445 def copy(ui, repo, pats, opts, rename=False):
1446 1446 check_incompatible_arguments(opts, b'forget', [b'dry_run'])
1447 1447
1448 1448 # called with the repo lock held
1449 1449 #
1450 1450 # hgsep => pathname that uses "/" to separate directories
1451 1451 # ossep => pathname that uses os.sep to separate directories
1452 1452 cwd = repo.getcwd()
1453 1453 targets = {}
1454 1454 forget = opts.get(b"forget")
1455 1455 after = opts.get(b"after")
1456 1456 dryrun = opts.get(b"dry_run")
1457 1457 rev = opts.get(b'at_rev')
1458 1458 if rev:
1459 1459 if not forget and not after:
1460 1460 # TODO: Remove this restriction and make it also create the copy
1461 1461 # targets (and remove the rename source if rename==True).
1462 1462 raise error.InputError(_(b'--at-rev requires --after'))
1463 1463 ctx = scmutil.revsingle(repo, rev)
1464 1464 if len(ctx.parents()) > 1:
1465 1465 raise error.InputError(
1466 1466 _(b'cannot mark/unmark copy in merge commit')
1467 1467 )
1468 1468 else:
1469 1469 ctx = repo[None]
1470 1470
1471 1471 pctx = ctx.p1()
1472 1472
1473 1473 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1474 1474
1475 1475 if forget:
1476 1476 if ctx.rev() is None:
1477 1477 new_ctx = ctx
1478 1478 else:
1479 1479 if len(ctx.parents()) > 1:
1480 1480 raise error.InputError(_(b'cannot unmark copy in merge commit'))
1481 1481 # avoid cycle context -> subrepo -> cmdutil
1482 1482 from . import context
1483 1483
1484 1484 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1485 1485 new_ctx = context.overlayworkingctx(repo)
1486 1486 new_ctx.setbase(ctx.p1())
1487 1487 mergemod.graft(repo, ctx, wctx=new_ctx)
1488 1488
1489 1489 match = scmutil.match(ctx, pats, opts)
1490 1490
1491 1491 current_copies = ctx.p1copies()
1492 1492 current_copies.update(ctx.p2copies())
1493 1493
1494 1494 uipathfn = scmutil.getuipathfn(repo)
1495 1495 for f in ctx.walk(match):
1496 1496 if f in current_copies:
1497 1497 new_ctx[f].markcopied(None)
1498 1498 elif match.exact(f):
1499 1499 ui.warn(
1500 1500 _(
1501 1501 b'%s: not unmarking as copy - file is not marked as copied\n'
1502 1502 )
1503 1503 % uipathfn(f)
1504 1504 )
1505 1505
1506 1506 if ctx.rev() is not None:
1507 1507 with repo.lock():
1508 1508 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1509 1509 new_node = mem_ctx.commit()
1510 1510
1511 1511 if repo.dirstate.p1() == ctx.node():
1512 1512 with repo.dirstate.parentchange():
1513 1513 scmutil.movedirstate(repo, repo[new_node])
1514 1514 replacements = {ctx.node(): [new_node]}
1515 1515 scmutil.cleanupnodes(
1516 1516 repo, replacements, b'uncopy', fixphase=True
1517 1517 )
1518 1518
1519 1519 return
1520 1520
1521 1521 pats = scmutil.expandpats(pats)
1522 1522 if not pats:
1523 1523 raise error.InputError(_(b'no source or destination specified'))
1524 1524 if len(pats) == 1:
1525 1525 raise error.InputError(_(b'no destination specified'))
1526 1526 dest = pats.pop()
1527 1527
1528 1528 def walkpat(pat):
1529 1529 srcs = []
1530 1530 # TODO: Inline and simplify the non-working-copy version of this code
1531 1531 # since it shares very little with the working-copy version of it.
1532 1532 ctx_to_walk = ctx if ctx.rev() is None else pctx
1533 1533 m = scmutil.match(ctx_to_walk, [pat], opts, globbed=True)
1534 1534 for abs in ctx_to_walk.walk(m):
1535 1535 rel = uipathfn(abs)
1536 1536 exact = m.exact(abs)
1537 1537 if abs not in ctx:
1538 1538 if abs in pctx:
1539 1539 if not after:
1540 1540 if exact:
1541 1541 ui.warn(
1542 1542 _(
1543 1543 b'%s: not copying - file has been marked '
1544 1544 b'for remove\n'
1545 1545 )
1546 1546 % rel
1547 1547 )
1548 1548 continue
1549 1549 else:
1550 1550 if exact:
1551 1551 ui.warn(
1552 1552 _(b'%s: not copying - file is not managed\n') % rel
1553 1553 )
1554 1554 continue
1555 1555
1556 1556 # abs: hgsep
1557 1557 # rel: ossep
1558 1558 srcs.append((abs, rel, exact))
1559 1559 return srcs
1560 1560
1561 1561 if ctx.rev() is not None:
1562 1562 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1563 1563 absdest = pathutil.canonpath(repo.root, cwd, dest)
1564 1564 if ctx.hasdir(absdest):
1565 1565 raise error.InputError(
1566 1566 _(b'%s: --at-rev does not support a directory as destination')
1567 1567 % uipathfn(absdest)
1568 1568 )
1569 1569 if absdest not in ctx:
1570 1570 raise error.InputError(
1571 1571 _(b'%s: copy destination does not exist in %s')
1572 1572 % (uipathfn(absdest), ctx)
1573 1573 )
1574 1574
1575 1575 # avoid cycle context -> subrepo -> cmdutil
1576 1576 from . import context
1577 1577
1578 1578 copylist = []
1579 1579 for pat in pats:
1580 1580 srcs = walkpat(pat)
1581 1581 if not srcs:
1582 1582 continue
1583 1583 for abs, rel, exact in srcs:
1584 1584 copylist.append(abs)
1585 1585
1586 1586 if not copylist:
1587 1587 raise error.InputError(_(b'no files to copy'))
1588 1588 # TODO: Add support for `hg cp --at-rev . foo bar dir` and
1589 1589 # `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the
1590 1590 # existing functions below.
1591 1591 if len(copylist) != 1:
1592 1592 raise error.InputError(_(b'--at-rev requires a single source'))
1593 1593
1594 1594 new_ctx = context.overlayworkingctx(repo)
1595 1595 new_ctx.setbase(ctx.p1())
1596 1596 mergemod.graft(repo, ctx, wctx=new_ctx)
1597 1597
1598 1598 new_ctx.markcopied(absdest, copylist[0])
1599 1599
1600 1600 with repo.lock():
1601 1601 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1602 1602 new_node = mem_ctx.commit()
1603 1603
1604 1604 if repo.dirstate.p1() == ctx.node():
1605 1605 with repo.dirstate.parentchange():
1606 1606 scmutil.movedirstate(repo, repo[new_node])
1607 1607 replacements = {ctx.node(): [new_node]}
1608 1608 scmutil.cleanupnodes(repo, replacements, b'copy', fixphase=True)
1609 1609
1610 1610 return
1611 1611
1612 1612 # abssrc: hgsep
1613 1613 # relsrc: ossep
1614 1614 # otarget: ossep
1615 1615 def copyfile(abssrc, relsrc, otarget, exact):
1616 1616 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1617 1617 if b'/' in abstarget:
1618 1618 # We cannot normalize abstarget itself, this would prevent
1619 1619 # case only renames, like a => A.
1620 1620 abspath, absname = abstarget.rsplit(b'/', 1)
1621 1621 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1622 1622 reltarget = repo.pathto(abstarget, cwd)
1623 1623 target = repo.wjoin(abstarget)
1624 1624 src = repo.wjoin(abssrc)
1625 1625 state = repo.dirstate[abstarget]
1626 1626
1627 1627 scmutil.checkportable(ui, abstarget)
1628 1628
1629 1629 # check for collisions
1630 1630 prevsrc = targets.get(abstarget)
1631 1631 if prevsrc is not None:
1632 1632 ui.warn(
1633 1633 _(b'%s: not overwriting - %s collides with %s\n')
1634 1634 % (
1635 1635 reltarget,
1636 1636 repo.pathto(abssrc, cwd),
1637 1637 repo.pathto(prevsrc, cwd),
1638 1638 )
1639 1639 )
1640 1640 return True # report a failure
1641 1641
1642 1642 # check for overwrites
1643 1643 exists = os.path.lexists(target)
1644 1644 samefile = False
1645 1645 if exists and abssrc != abstarget:
1646 1646 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1647 1647 abstarget
1648 1648 ):
1649 1649 if not rename:
1650 1650 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1651 1651 return True # report a failure
1652 1652 exists = False
1653 1653 samefile = True
1654 1654
1655 1655 if not after and exists or after and state in b'mn':
1656 1656 if not opts[b'force']:
1657 1657 if state in b'mn':
1658 1658 msg = _(b'%s: not overwriting - file already committed\n')
1659 1659 if after:
1660 1660 flags = b'--after --force'
1661 1661 else:
1662 1662 flags = b'--force'
1663 1663 if rename:
1664 1664 hint = (
1665 1665 _(
1666 1666 b"('hg rename %s' to replace the file by "
1667 1667 b'recording a rename)\n'
1668 1668 )
1669 1669 % flags
1670 1670 )
1671 1671 else:
1672 1672 hint = (
1673 1673 _(
1674 1674 b"('hg copy %s' to replace the file by "
1675 1675 b'recording a copy)\n'
1676 1676 )
1677 1677 % flags
1678 1678 )
1679 1679 else:
1680 1680 msg = _(b'%s: not overwriting - file exists\n')
1681 1681 if rename:
1682 1682 hint = _(
1683 1683 b"('hg rename --after' to record the rename)\n"
1684 1684 )
1685 1685 else:
1686 1686 hint = _(b"('hg copy --after' to record the copy)\n")
1687 1687 ui.warn(msg % reltarget)
1688 1688 ui.warn(hint)
1689 1689 return True # report a failure
1690 1690
1691 1691 if after:
1692 1692 if not exists:
1693 1693 if rename:
1694 1694 ui.warn(
1695 1695 _(b'%s: not recording move - %s does not exist\n')
1696 1696 % (relsrc, reltarget)
1697 1697 )
1698 1698 else:
1699 1699 ui.warn(
1700 1700 _(b'%s: not recording copy - %s does not exist\n')
1701 1701 % (relsrc, reltarget)
1702 1702 )
1703 1703 return True # report a failure
1704 1704 elif not dryrun:
1705 1705 try:
1706 1706 if exists:
1707 1707 os.unlink(target)
1708 1708 targetdir = os.path.dirname(target) or b'.'
1709 1709 if not os.path.isdir(targetdir):
1710 1710 os.makedirs(targetdir)
1711 1711 if samefile:
1712 1712 tmp = target + b"~hgrename"
1713 1713 os.rename(src, tmp)
1714 1714 os.rename(tmp, target)
1715 1715 else:
1716 1716 # Preserve stat info on renames, not on copies; this matches
1717 1717 # Linux CLI behavior.
1718 1718 util.copyfile(src, target, copystat=rename)
1719 1719 srcexists = True
1720 1720 except IOError as inst:
1721 1721 if inst.errno == errno.ENOENT:
1722 1722 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1723 1723 srcexists = False
1724 1724 else:
1725 1725 ui.warn(
1726 1726 _(b'%s: cannot copy - %s\n')
1727 1727 % (relsrc, encoding.strtolocal(inst.strerror))
1728 1728 )
1729 1729 return True # report a failure
1730 1730
1731 1731 if ui.verbose or not exact:
1732 1732 if rename:
1733 1733 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1734 1734 else:
1735 1735 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1736 1736
1737 1737 targets[abstarget] = abssrc
1738 1738
1739 1739 # fix up dirstate
1740 1740 scmutil.dirstatecopy(
1741 1741 ui, repo, ctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1742 1742 )
1743 1743 if rename and not dryrun:
1744 1744 if not after and srcexists and not samefile:
1745 1745 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1746 1746 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1747 1747 ctx.forget([abssrc])
1748 1748
1749 1749 # pat: ossep
1750 1750 # dest ossep
1751 1751 # srcs: list of (hgsep, hgsep, ossep, bool)
1752 1752 # return: function that takes hgsep and returns ossep
1753 1753 def targetpathfn(pat, dest, srcs):
1754 1754 if os.path.isdir(pat):
1755 1755 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1756 1756 abspfx = util.localpath(abspfx)
1757 1757 if destdirexists:
1758 1758 striplen = len(os.path.split(abspfx)[0])
1759 1759 else:
1760 1760 striplen = len(abspfx)
1761 1761 if striplen:
1762 1762 striplen += len(pycompat.ossep)
1763 1763 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1764 1764 elif destdirexists:
1765 1765 res = lambda p: os.path.join(
1766 1766 dest, os.path.basename(util.localpath(p))
1767 1767 )
1768 1768 else:
1769 1769 res = lambda p: dest
1770 1770 return res
1771 1771
1772 1772 # pat: ossep
1773 1773 # dest ossep
1774 1774 # srcs: list of (hgsep, hgsep, ossep, bool)
1775 1775 # return: function that takes hgsep and returns ossep
1776 1776 def targetpathafterfn(pat, dest, srcs):
1777 1777 if matchmod.patkind(pat):
1778 1778 # a mercurial pattern
1779 1779 res = lambda p: os.path.join(
1780 1780 dest, os.path.basename(util.localpath(p))
1781 1781 )
1782 1782 else:
1783 1783 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1784 1784 if len(abspfx) < len(srcs[0][0]):
1785 1785 # A directory. Either the target path contains the last
1786 1786 # component of the source path or it does not.
1787 1787 def evalpath(striplen):
1788 1788 score = 0
1789 1789 for s in srcs:
1790 1790 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1791 1791 if os.path.lexists(t):
1792 1792 score += 1
1793 1793 return score
1794 1794
1795 1795 abspfx = util.localpath(abspfx)
1796 1796 striplen = len(abspfx)
1797 1797 if striplen:
1798 1798 striplen += len(pycompat.ossep)
1799 1799 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1800 1800 score = evalpath(striplen)
1801 1801 striplen1 = len(os.path.split(abspfx)[0])
1802 1802 if striplen1:
1803 1803 striplen1 += len(pycompat.ossep)
1804 1804 if evalpath(striplen1) > score:
1805 1805 striplen = striplen1
1806 1806 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1807 1807 else:
1808 1808 # a file
1809 1809 if destdirexists:
1810 1810 res = lambda p: os.path.join(
1811 1811 dest, os.path.basename(util.localpath(p))
1812 1812 )
1813 1813 else:
1814 1814 res = lambda p: dest
1815 1815 return res
1816 1816
1817 1817 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1818 1818 if not destdirexists:
1819 1819 if len(pats) > 1 or matchmod.patkind(pats[0]):
1820 1820 raise error.InputError(
1821 1821 _(
1822 1822 b'with multiple sources, destination must be an '
1823 1823 b'existing directory'
1824 1824 )
1825 1825 )
1826 1826 if util.endswithsep(dest):
1827 1827 raise error.InputError(
1828 1828 _(b'destination %s is not a directory') % dest
1829 1829 )
1830 1830
1831 1831 tfn = targetpathfn
1832 1832 if after:
1833 1833 tfn = targetpathafterfn
1834 1834 copylist = []
1835 1835 for pat in pats:
1836 1836 srcs = walkpat(pat)
1837 1837 if not srcs:
1838 1838 continue
1839 1839 copylist.append((tfn(pat, dest, srcs), srcs))
1840 1840 if not copylist:
1841 1841 raise error.InputError(_(b'no files to copy'))
1842 1842
1843 1843 errors = 0
1844 1844 for targetpath, srcs in copylist:
1845 1845 for abssrc, relsrc, exact in srcs:
1846 1846 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1847 1847 errors += 1
1848 1848
1849 1849 return errors != 0
1850 1850
1851 1851
1852 1852 ## facility to let extension process additional data into an import patch
1853 1853 # list of identifier to be executed in order
1854 1854 extrapreimport = [] # run before commit
1855 1855 extrapostimport = [] # run after commit
1856 1856 # mapping from identifier to actual import function
1857 1857 #
1858 1858 # 'preimport' are run before the commit is made and are provided the following
1859 1859 # arguments:
1860 1860 # - repo: the localrepository instance,
1861 1861 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1862 1862 # - extra: the future extra dictionary of the changeset, please mutate it,
1863 1863 # - opts: the import options.
1864 1864 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1865 1865 # mutation of in memory commit and more. Feel free to rework the code to get
1866 1866 # there.
1867 1867 extrapreimportmap = {}
1868 1868 # 'postimport' are run after the commit is made and are provided the following
1869 1869 # argument:
1870 1870 # - ctx: the changectx created by import.
1871 1871 extrapostimportmap = {}
1872 1872
1873 1873
1874 1874 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1875 1875 """Utility function used by commands.import to import a single patch
1876 1876
1877 1877 This function is explicitly defined here to help the evolve extension to
1878 1878 wrap this part of the import logic.
1879 1879
1880 1880 The API is currently a bit ugly because it a simple code translation from
1881 1881 the import command. Feel free to make it better.
1882 1882
1883 1883 :patchdata: a dictionary containing parsed patch data (such as from
1884 1884 ``patch.extract()``)
1885 1885 :parents: nodes that will be parent of the created commit
1886 1886 :opts: the full dict of option passed to the import command
1887 1887 :msgs: list to save commit message to.
1888 1888 (used in case we need to save it when failing)
1889 1889 :updatefunc: a function that update a repo to a given node
1890 1890 updatefunc(<repo>, <node>)
1891 1891 """
1892 1892 # avoid cycle context -> subrepo -> cmdutil
1893 1893 from . import context
1894 1894
1895 1895 tmpname = patchdata.get(b'filename')
1896 1896 message = patchdata.get(b'message')
1897 1897 user = opts.get(b'user') or patchdata.get(b'user')
1898 1898 date = opts.get(b'date') or patchdata.get(b'date')
1899 1899 branch = patchdata.get(b'branch')
1900 1900 nodeid = patchdata.get(b'nodeid')
1901 1901 p1 = patchdata.get(b'p1')
1902 1902 p2 = patchdata.get(b'p2')
1903 1903
1904 1904 nocommit = opts.get(b'no_commit')
1905 1905 importbranch = opts.get(b'import_branch')
1906 1906 update = not opts.get(b'bypass')
1907 1907 strip = opts[b"strip"]
1908 1908 prefix = opts[b"prefix"]
1909 1909 sim = float(opts.get(b'similarity') or 0)
1910 1910
1911 1911 if not tmpname:
1912 1912 return None, None, False
1913 1913
1914 1914 rejects = False
1915 1915
1916 1916 cmdline_message = logmessage(ui, opts)
1917 1917 if cmdline_message:
1918 1918 # pickup the cmdline msg
1919 1919 message = cmdline_message
1920 1920 elif message:
1921 1921 # pickup the patch msg
1922 1922 message = message.strip()
1923 1923 else:
1924 1924 # launch the editor
1925 1925 message = None
1926 1926 ui.debug(b'message:\n%s\n' % (message or b''))
1927 1927
1928 1928 if len(parents) == 1:
1929 1929 parents.append(repo[nullid])
1930 1930 if opts.get(b'exact'):
1931 1931 if not nodeid or not p1:
1932 1932 raise error.InputError(_(b'not a Mercurial patch'))
1933 1933 p1 = repo[p1]
1934 1934 p2 = repo[p2 or nullid]
1935 1935 elif p2:
1936 1936 try:
1937 1937 p1 = repo[p1]
1938 1938 p2 = repo[p2]
1939 1939 # Without any options, consider p2 only if the
1940 1940 # patch is being applied on top of the recorded
1941 1941 # first parent.
1942 1942 if p1 != parents[0]:
1943 1943 p1 = parents[0]
1944 1944 p2 = repo[nullid]
1945 1945 except error.RepoError:
1946 1946 p1, p2 = parents
1947 1947 if p2.node() == nullid:
1948 1948 ui.warn(
1949 1949 _(
1950 1950 b"warning: import the patch as a normal revision\n"
1951 1951 b"(use --exact to import the patch as a merge)\n"
1952 1952 )
1953 1953 )
1954 1954 else:
1955 1955 p1, p2 = parents
1956 1956
1957 1957 n = None
1958 1958 if update:
1959 1959 if p1 != parents[0]:
1960 1960 updatefunc(repo, p1.node())
1961 1961 if p2 != parents[1]:
1962 1962 repo.setparents(p1.node(), p2.node())
1963 1963
1964 1964 if opts.get(b'exact') or importbranch:
1965 1965 repo.dirstate.setbranch(branch or b'default')
1966 1966
1967 1967 partial = opts.get(b'partial', False)
1968 1968 files = set()
1969 1969 try:
1970 1970 patch.patch(
1971 1971 ui,
1972 1972 repo,
1973 1973 tmpname,
1974 1974 strip=strip,
1975 1975 prefix=prefix,
1976 1976 files=files,
1977 1977 eolmode=None,
1978 1978 similarity=sim / 100.0,
1979 1979 )
1980 1980 except error.PatchError as e:
1981 1981 if not partial:
1982 1982 raise error.Abort(pycompat.bytestr(e))
1983 1983 if partial:
1984 1984 rejects = True
1985 1985
1986 1986 files = list(files)
1987 1987 if nocommit:
1988 1988 if message:
1989 1989 msgs.append(message)
1990 1990 else:
1991 1991 if opts.get(b'exact') or p2:
1992 1992 # If you got here, you either use --force and know what
1993 1993 # you are doing or used --exact or a merge patch while
1994 1994 # being updated to its first parent.
1995 1995 m = None
1996 1996 else:
1997 1997 m = scmutil.matchfiles(repo, files or [])
1998 1998 editform = mergeeditform(repo[None], b'import.normal')
1999 1999 if opts.get(b'exact'):
2000 2000 editor = None
2001 2001 else:
2002 2002 editor = getcommiteditor(
2003 2003 editform=editform, **pycompat.strkwargs(opts)
2004 2004 )
2005 2005 extra = {}
2006 2006 for idfunc in extrapreimport:
2007 2007 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
2008 2008 overrides = {}
2009 2009 if partial:
2010 2010 overrides[(b'ui', b'allowemptycommit')] = True
2011 2011 if opts.get(b'secret'):
2012 2012 overrides[(b'phases', b'new-commit')] = b'secret'
2013 2013 with repo.ui.configoverride(overrides, b'import'):
2014 2014 n = repo.commit(
2015 2015 message, user, date, match=m, editor=editor, extra=extra
2016 2016 )
2017 2017 for idfunc in extrapostimport:
2018 2018 extrapostimportmap[idfunc](repo[n])
2019 2019 else:
2020 2020 if opts.get(b'exact') or importbranch:
2021 2021 branch = branch or b'default'
2022 2022 else:
2023 2023 branch = p1.branch()
2024 2024 store = patch.filestore()
2025 2025 try:
2026 2026 files = set()
2027 2027 try:
2028 2028 patch.patchrepo(
2029 2029 ui,
2030 2030 repo,
2031 2031 p1,
2032 2032 store,
2033 2033 tmpname,
2034 2034 strip,
2035 2035 prefix,
2036 2036 files,
2037 2037 eolmode=None,
2038 2038 )
2039 2039 except error.PatchError as e:
2040 2040 raise error.Abort(stringutil.forcebytestr(e))
2041 2041 if opts.get(b'exact'):
2042 2042 editor = None
2043 2043 else:
2044 2044 editor = getcommiteditor(editform=b'import.bypass')
2045 2045 memctx = context.memctx(
2046 2046 repo,
2047 2047 (p1.node(), p2.node()),
2048 2048 message,
2049 2049 files=files,
2050 2050 filectxfn=store,
2051 2051 user=user,
2052 2052 date=date,
2053 2053 branch=branch,
2054 2054 editor=editor,
2055 2055 )
2056 2056
2057 2057 overrides = {}
2058 2058 if opts.get(b'secret'):
2059 2059 overrides[(b'phases', b'new-commit')] = b'secret'
2060 2060 with repo.ui.configoverride(overrides, b'import'):
2061 2061 n = memctx.commit()
2062 2062 finally:
2063 2063 store.close()
2064 2064 if opts.get(b'exact') and nocommit:
2065 2065 # --exact with --no-commit is still useful in that it does merge
2066 2066 # and branch bits
2067 2067 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
2068 2068 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
2069 2069 raise error.Abort(_(b'patch is damaged or loses information'))
2070 2070 msg = _(b'applied to working directory')
2071 2071 if n:
2072 2072 # i18n: refers to a short changeset id
2073 2073 msg = _(b'created %s') % short(n)
2074 2074 return msg, n, rejects
2075 2075
2076 2076
2077 2077 # facility to let extensions include additional data in an exported patch
2078 2078 # list of identifiers to be executed in order
2079 2079 extraexport = []
2080 2080 # mapping from identifier to actual export function
2081 2081 # function as to return a string to be added to the header or None
2082 2082 # it is given two arguments (sequencenumber, changectx)
2083 2083 extraexportmap = {}
2084 2084
2085 2085
2086 2086 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
2087 2087 node = scmutil.binnode(ctx)
2088 2088 parents = [p.node() for p in ctx.parents() if p]
2089 2089 branch = ctx.branch()
2090 2090 if switch_parent:
2091 2091 parents.reverse()
2092 2092
2093 2093 if parents:
2094 2094 prev = parents[0]
2095 2095 else:
2096 2096 prev = nullid
2097 2097
2098 2098 fm.context(ctx=ctx)
2099 2099 fm.plain(b'# HG changeset patch\n')
2100 2100 fm.write(b'user', b'# User %s\n', ctx.user())
2101 2101 fm.plain(b'# Date %d %d\n' % ctx.date())
2102 2102 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
2103 2103 fm.condwrite(
2104 2104 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
2105 2105 )
2106 2106 fm.write(b'node', b'# Node ID %s\n', hex(node))
2107 2107 fm.plain(b'# Parent %s\n' % hex(prev))
2108 2108 if len(parents) > 1:
2109 2109 fm.plain(b'# Parent %s\n' % hex(parents[1]))
2110 2110 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
2111 2111
2112 2112 # TODO: redesign extraexportmap function to support formatter
2113 2113 for headerid in extraexport:
2114 2114 header = extraexportmap[headerid](seqno, ctx)
2115 2115 if header is not None:
2116 2116 fm.plain(b'# %s\n' % header)
2117 2117
2118 2118 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
2119 2119 fm.plain(b'\n')
2120 2120
2121 2121 if fm.isplain():
2122 2122 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
2123 2123 for chunk, label in chunkiter:
2124 2124 fm.plain(chunk, label=label)
2125 2125 else:
2126 2126 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
2127 2127 # TODO: make it structured?
2128 2128 fm.data(diff=b''.join(chunkiter))
2129 2129
2130 2130
2131 2131 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
2132 2132 """Export changesets to stdout or a single file"""
2133 2133 for seqno, rev in enumerate(revs, 1):
2134 2134 ctx = repo[rev]
2135 2135 if not dest.startswith(b'<'):
2136 2136 repo.ui.note(b"%s\n" % dest)
2137 2137 fm.startitem()
2138 2138 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
2139 2139
2140 2140
2141 2141 def _exportfntemplate(
2142 2142 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
2143 2143 ):
2144 2144 """Export changesets to possibly multiple files"""
2145 2145 total = len(revs)
2146 2146 revwidth = max(len(str(rev)) for rev in revs)
2147 2147 filemap = util.sortdict() # filename: [(seqno, rev), ...]
2148 2148
2149 2149 for seqno, rev in enumerate(revs, 1):
2150 2150 ctx = repo[rev]
2151 2151 dest = makefilename(
2152 2152 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
2153 2153 )
2154 2154 filemap.setdefault(dest, []).append((seqno, rev))
2155 2155
2156 2156 for dest in filemap:
2157 2157 with formatter.maybereopen(basefm, dest) as fm:
2158 2158 repo.ui.note(b"%s\n" % dest)
2159 2159 for seqno, rev in filemap[dest]:
2160 2160 fm.startitem()
2161 2161 ctx = repo[rev]
2162 2162 _exportsingle(
2163 2163 repo, ctx, fm, match, switch_parent, seqno, diffopts
2164 2164 )
2165 2165
2166 2166
2167 2167 def _prefetchchangedfiles(repo, revs, match):
2168 2168 allfiles = set()
2169 2169 for rev in revs:
2170 2170 for file in repo[rev].files():
2171 2171 if not match or match(file):
2172 2172 allfiles.add(file)
2173 2173 match = scmutil.matchfiles(repo, allfiles)
2174 2174 revmatches = [(rev, match) for rev in revs]
2175 2175 scmutil.prefetchfiles(repo, revmatches)
2176 2176
2177 2177
2178 2178 def export(
2179 2179 repo,
2180 2180 revs,
2181 2181 basefm,
2182 2182 fntemplate=b'hg-%h.patch',
2183 2183 switch_parent=False,
2184 2184 opts=None,
2185 2185 match=None,
2186 2186 ):
2187 2187 '''export changesets as hg patches
2188 2188
2189 2189 Args:
2190 2190 repo: The repository from which we're exporting revisions.
2191 2191 revs: A list of revisions to export as revision numbers.
2192 2192 basefm: A formatter to which patches should be written.
2193 2193 fntemplate: An optional string to use for generating patch file names.
2194 2194 switch_parent: If True, show diffs against second parent when not nullid.
2195 2195 Default is false, which always shows diff against p1.
2196 2196 opts: diff options to use for generating the patch.
2197 2197 match: If specified, only export changes to files matching this matcher.
2198 2198
2199 2199 Returns:
2200 2200 Nothing.
2201 2201
2202 2202 Side Effect:
2203 2203 "HG Changeset Patch" data is emitted to one of the following
2204 2204 destinations:
2205 2205 fntemplate specified: Each rev is written to a unique file named using
2206 2206 the given template.
2207 2207 Otherwise: All revs will be written to basefm.
2208 2208 '''
2209 2209 _prefetchchangedfiles(repo, revs, match)
2210 2210
2211 2211 if not fntemplate:
2212 2212 _exportfile(
2213 2213 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2214 2214 )
2215 2215 else:
2216 2216 _exportfntemplate(
2217 2217 repo, revs, basefm, fntemplate, switch_parent, opts, match
2218 2218 )
2219 2219
2220 2220
2221 2221 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2222 2222 """Export changesets to the given file stream"""
2223 2223 _prefetchchangedfiles(repo, revs, match)
2224 2224
2225 2225 dest = getattr(fp, 'name', b'<unnamed>')
2226 2226 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2227 2227 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2228 2228
2229 2229
2230 2230 def showmarker(fm, marker, index=None):
2231 2231 """utility function to display obsolescence marker in a readable way
2232 2232
2233 2233 To be used by debug function."""
2234 2234 if index is not None:
2235 2235 fm.write(b'index', b'%i ', index)
2236 2236 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2237 2237 succs = marker.succnodes()
2238 2238 fm.condwrite(
2239 2239 succs,
2240 2240 b'succnodes',
2241 2241 b'%s ',
2242 2242 fm.formatlist(map(hex, succs), name=b'node'),
2243 2243 )
2244 2244 fm.write(b'flag', b'%X ', marker.flags())
2245 2245 parents = marker.parentnodes()
2246 2246 if parents is not None:
2247 2247 fm.write(
2248 2248 b'parentnodes',
2249 2249 b'{%s} ',
2250 2250 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2251 2251 )
2252 2252 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2253 2253 meta = marker.metadata().copy()
2254 2254 meta.pop(b'date', None)
2255 2255 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2256 2256 fm.write(
2257 2257 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2258 2258 )
2259 2259 fm.plain(b'\n')
2260 2260
2261 2261
2262 2262 def finddate(ui, repo, date):
2263 2263 """Find the tipmost changeset that matches the given date spec"""
2264 2264 mrevs = repo.revs(b'date(%s)', date)
2265 2265 try:
2266 2266 rev = mrevs.max()
2267 2267 except ValueError:
2268 2268 raise error.InputError(_(b"revision matching date not found"))
2269 2269
2270 2270 ui.status(
2271 2271 _(b"found revision %d from %s\n")
2272 2272 % (rev, dateutil.datestr(repo[rev].date()))
2273 2273 )
2274 2274 return b'%d' % rev
2275 2275
2276 2276
2277 2277 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2278 2278 bad = []
2279 2279
2280 2280 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2281 2281 names = []
2282 2282 wctx = repo[None]
2283 2283 cca = None
2284 2284 abort, warn = scmutil.checkportabilityalert(ui)
2285 2285 if abort or warn:
2286 2286 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2287 2287
2288 2288 match = repo.narrowmatch(match, includeexact=True)
2289 2289 badmatch = matchmod.badmatch(match, badfn)
2290 2290 dirstate = repo.dirstate
2291 2291 # We don't want to just call wctx.walk here, since it would return a lot of
2292 2292 # clean files, which we aren't interested in and takes time.
2293 2293 for f in sorted(
2294 2294 dirstate.walk(
2295 2295 badmatch,
2296 2296 subrepos=sorted(wctx.substate),
2297 2297 unknown=True,
2298 2298 ignored=False,
2299 2299 full=False,
2300 2300 )
2301 2301 ):
2302 2302 exact = match.exact(f)
2303 2303 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2304 2304 if cca:
2305 2305 cca(f)
2306 2306 names.append(f)
2307 2307 if ui.verbose or not exact:
2308 2308 ui.status(
2309 2309 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2310 2310 )
2311 2311
2312 2312 for subpath in sorted(wctx.substate):
2313 2313 sub = wctx.sub(subpath)
2314 2314 try:
2315 2315 submatch = matchmod.subdirmatcher(subpath, match)
2316 2316 subprefix = repo.wvfs.reljoin(prefix, subpath)
2317 2317 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2318 2318 if opts.get('subrepos'):
2319 2319 bad.extend(
2320 2320 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2321 2321 )
2322 2322 else:
2323 2323 bad.extend(
2324 2324 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2325 2325 )
2326 2326 except error.LookupError:
2327 2327 ui.status(
2328 2328 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2329 2329 )
2330 2330
2331 2331 if not opts.get('dry_run'):
2332 2332 rejected = wctx.add(names, prefix)
2333 2333 bad.extend(f for f in rejected if f in match.files())
2334 2334 return bad
2335 2335
2336 2336
2337 2337 def addwebdirpath(repo, serverpath, webconf):
2338 2338 webconf[serverpath] = repo.root
2339 2339 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2340 2340
2341 2341 for r in repo.revs(b'filelog("path:.hgsub")'):
2342 2342 ctx = repo[r]
2343 2343 for subpath in ctx.substate:
2344 2344 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2345 2345
2346 2346
2347 2347 def forget(
2348 2348 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2349 2349 ):
2350 2350 if dryrun and interactive:
2351 2351 raise error.InputError(
2352 2352 _(b"cannot specify both --dry-run and --interactive")
2353 2353 )
2354 2354 bad = []
2355 2355 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2356 2356 wctx = repo[None]
2357 2357 forgot = []
2358 2358
2359 2359 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2360 2360 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2361 2361 if explicitonly:
2362 2362 forget = [f for f in forget if match.exact(f)]
2363 2363
2364 2364 for subpath in sorted(wctx.substate):
2365 2365 sub = wctx.sub(subpath)
2366 2366 submatch = matchmod.subdirmatcher(subpath, match)
2367 2367 subprefix = repo.wvfs.reljoin(prefix, subpath)
2368 2368 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2369 2369 try:
2370 2370 subbad, subforgot = sub.forget(
2371 2371 submatch,
2372 2372 subprefix,
2373 2373 subuipathfn,
2374 2374 dryrun=dryrun,
2375 2375 interactive=interactive,
2376 2376 )
2377 2377 bad.extend([subpath + b'/' + f for f in subbad])
2378 2378 forgot.extend([subpath + b'/' + f for f in subforgot])
2379 2379 except error.LookupError:
2380 2380 ui.status(
2381 2381 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2382 2382 )
2383 2383
2384 2384 if not explicitonly:
2385 2385 for f in match.files():
2386 2386 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2387 2387 if f not in forgot:
2388 2388 if repo.wvfs.exists(f):
2389 2389 # Don't complain if the exact case match wasn't given.
2390 2390 # But don't do this until after checking 'forgot', so
2391 2391 # that subrepo files aren't normalized, and this op is
2392 2392 # purely from data cached by the status walk above.
2393 2393 if repo.dirstate.normalize(f) in repo.dirstate:
2394 2394 continue
2395 2395 ui.warn(
2396 2396 _(
2397 2397 b'not removing %s: '
2398 2398 b'file is already untracked\n'
2399 2399 )
2400 2400 % uipathfn(f)
2401 2401 )
2402 2402 bad.append(f)
2403 2403
2404 2404 if interactive:
2405 2405 responses = _(
2406 2406 b'[Ynsa?]'
2407 2407 b'$$ &Yes, forget this file'
2408 2408 b'$$ &No, skip this file'
2409 2409 b'$$ &Skip remaining files'
2410 2410 b'$$ Include &all remaining files'
2411 2411 b'$$ &? (display help)'
2412 2412 )
2413 2413 for filename in forget[:]:
2414 2414 r = ui.promptchoice(
2415 2415 _(b'forget %s %s') % (uipathfn(filename), responses)
2416 2416 )
2417 2417 if r == 4: # ?
2418 2418 while r == 4:
2419 2419 for c, t in ui.extractchoices(responses)[1]:
2420 2420 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2421 2421 r = ui.promptchoice(
2422 2422 _(b'forget %s %s') % (uipathfn(filename), responses)
2423 2423 )
2424 2424 if r == 0: # yes
2425 2425 continue
2426 2426 elif r == 1: # no
2427 2427 forget.remove(filename)
2428 2428 elif r == 2: # Skip
2429 2429 fnindex = forget.index(filename)
2430 2430 del forget[fnindex:]
2431 2431 break
2432 2432 elif r == 3: # All
2433 2433 break
2434 2434
2435 2435 for f in forget:
2436 2436 if ui.verbose or not match.exact(f) or interactive:
2437 2437 ui.status(
2438 2438 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2439 2439 )
2440 2440
2441 2441 if not dryrun:
2442 2442 rejected = wctx.forget(forget, prefix)
2443 2443 bad.extend(f for f in rejected if f in match.files())
2444 2444 forgot.extend(f for f in forget if f not in rejected)
2445 2445 return bad, forgot
2446 2446
2447 2447
2448 2448 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2449 2449 ret = 1
2450 2450
2451 2451 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2452 2452 if fm.isplain() and not needsfctx:
2453 2453 # Fast path. The speed-up comes from skipping the formatter, and batching
2454 2454 # calls to ui.write.
2455 2455 buf = []
2456 2456 for f in ctx.matches(m):
2457 2457 buf.append(fmt % uipathfn(f))
2458 2458 if len(buf) > 100:
2459 2459 ui.write(b''.join(buf))
2460 2460 del buf[:]
2461 2461 ret = 0
2462 2462 if buf:
2463 2463 ui.write(b''.join(buf))
2464 2464 else:
2465 2465 for f in ctx.matches(m):
2466 2466 fm.startitem()
2467 2467 fm.context(ctx=ctx)
2468 2468 if needsfctx:
2469 2469 fc = ctx[f]
2470 2470 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2471 2471 fm.data(path=f)
2472 2472 fm.plain(fmt % uipathfn(f))
2473 2473 ret = 0
2474 2474
2475 2475 for subpath in sorted(ctx.substate):
2476 2476 submatch = matchmod.subdirmatcher(subpath, m)
2477 2477 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2478 2478 if subrepos or m.exact(subpath) or any(submatch.files()):
2479 2479 sub = ctx.sub(subpath)
2480 2480 try:
2481 2481 recurse = m.exact(subpath) or subrepos
2482 2482 if (
2483 2483 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2484 2484 == 0
2485 2485 ):
2486 2486 ret = 0
2487 2487 except error.LookupError:
2488 2488 ui.status(
2489 2489 _(b"skipping missing subrepository: %s\n")
2490 2490 % uipathfn(subpath)
2491 2491 )
2492 2492
2493 2493 return ret
2494 2494
2495 2495
2496 2496 def remove(
2497 2497 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2498 2498 ):
2499 2499 ret = 0
2500 2500 s = repo.status(match=m, clean=True)
2501 2501 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2502 2502
2503 2503 wctx = repo[None]
2504 2504
2505 2505 if warnings is None:
2506 2506 warnings = []
2507 2507 warn = True
2508 2508 else:
2509 2509 warn = False
2510 2510
2511 2511 subs = sorted(wctx.substate)
2512 2512 progress = ui.makeprogress(
2513 2513 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2514 2514 )
2515 2515 for subpath in subs:
2516 2516 submatch = matchmod.subdirmatcher(subpath, m)
2517 2517 subprefix = repo.wvfs.reljoin(prefix, subpath)
2518 2518 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2519 2519 if subrepos or m.exact(subpath) or any(submatch.files()):
2520 2520 progress.increment()
2521 2521 sub = wctx.sub(subpath)
2522 2522 try:
2523 2523 if sub.removefiles(
2524 2524 submatch,
2525 2525 subprefix,
2526 2526 subuipathfn,
2527 2527 after,
2528 2528 force,
2529 2529 subrepos,
2530 2530 dryrun,
2531 2531 warnings,
2532 2532 ):
2533 2533 ret = 1
2534 2534 except error.LookupError:
2535 2535 warnings.append(
2536 2536 _(b"skipping missing subrepository: %s\n")
2537 2537 % uipathfn(subpath)
2538 2538 )
2539 2539 progress.complete()
2540 2540
2541 2541 # warn about failure to delete explicit files/dirs
2542 2542 deleteddirs = pathutil.dirs(deleted)
2543 2543 files = m.files()
2544 2544 progress = ui.makeprogress(
2545 2545 _(b'deleting'), total=len(files), unit=_(b'files')
2546 2546 )
2547 2547 for f in files:
2548 2548
2549 2549 def insubrepo():
2550 2550 for subpath in wctx.substate:
2551 2551 if f.startswith(subpath + b'/'):
2552 2552 return True
2553 2553 return False
2554 2554
2555 2555 progress.increment()
2556 2556 isdir = f in deleteddirs or wctx.hasdir(f)
2557 2557 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2558 2558 continue
2559 2559
2560 2560 if repo.wvfs.exists(f):
2561 2561 if repo.wvfs.isdir(f):
2562 2562 warnings.append(
2563 2563 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2564 2564 )
2565 2565 else:
2566 2566 warnings.append(
2567 2567 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2568 2568 )
2569 2569 # missing files will generate a warning elsewhere
2570 2570 ret = 1
2571 2571 progress.complete()
2572 2572
2573 2573 if force:
2574 2574 list = modified + deleted + clean + added
2575 2575 elif after:
2576 2576 list = deleted
2577 2577 remaining = modified + added + clean
2578 2578 progress = ui.makeprogress(
2579 2579 _(b'skipping'), total=len(remaining), unit=_(b'files')
2580 2580 )
2581 2581 for f in remaining:
2582 2582 progress.increment()
2583 2583 if ui.verbose or (f in files):
2584 2584 warnings.append(
2585 2585 _(b'not removing %s: file still exists\n') % uipathfn(f)
2586 2586 )
2587 2587 ret = 1
2588 2588 progress.complete()
2589 2589 else:
2590 2590 list = deleted + clean
2591 2591 progress = ui.makeprogress(
2592 2592 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2593 2593 )
2594 2594 for f in modified:
2595 2595 progress.increment()
2596 2596 warnings.append(
2597 2597 _(
2598 2598 b'not removing %s: file is modified (use -f'
2599 2599 b' to force removal)\n'
2600 2600 )
2601 2601 % uipathfn(f)
2602 2602 )
2603 2603 ret = 1
2604 2604 for f in added:
2605 2605 progress.increment()
2606 2606 warnings.append(
2607 2607 _(
2608 2608 b"not removing %s: file has been marked for add"
2609 2609 b" (use 'hg forget' to undo add)\n"
2610 2610 )
2611 2611 % uipathfn(f)
2612 2612 )
2613 2613 ret = 1
2614 2614 progress.complete()
2615 2615
2616 2616 list = sorted(list)
2617 2617 progress = ui.makeprogress(
2618 2618 _(b'deleting'), total=len(list), unit=_(b'files')
2619 2619 )
2620 2620 for f in list:
2621 2621 if ui.verbose or not m.exact(f):
2622 2622 progress.increment()
2623 2623 ui.status(
2624 2624 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2625 2625 )
2626 2626 progress.complete()
2627 2627
2628 2628 if not dryrun:
2629 2629 with repo.wlock():
2630 2630 if not after:
2631 2631 for f in list:
2632 2632 if f in added:
2633 2633 continue # we never unlink added files on remove
2634 2634 rmdir = repo.ui.configbool(
2635 2635 b'experimental', b'removeemptydirs'
2636 2636 )
2637 2637 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2638 2638 repo[None].forget(list)
2639 2639
2640 2640 if warn:
2641 2641 for warning in warnings:
2642 2642 ui.warn(warning)
2643 2643
2644 2644 return ret
2645 2645
2646 2646
2647 2647 def _catfmtneedsdata(fm):
2648 2648 return not fm.datahint() or b'data' in fm.datahint()
2649 2649
2650 2650
2651 2651 def _updatecatformatter(fm, ctx, matcher, path, decode):
2652 2652 """Hook for adding data to the formatter used by ``hg cat``.
2653 2653
2654 2654 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2655 2655 this method first."""
2656 2656
2657 2657 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2658 2658 # wasn't requested.
2659 2659 data = b''
2660 2660 if _catfmtneedsdata(fm):
2661 2661 data = ctx[path].data()
2662 2662 if decode:
2663 2663 data = ctx.repo().wwritedata(path, data)
2664 2664 fm.startitem()
2665 2665 fm.context(ctx=ctx)
2666 2666 fm.write(b'data', b'%s', data)
2667 2667 fm.data(path=path)
2668 2668
2669 2669
2670 2670 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2671 2671 err = 1
2672 2672 opts = pycompat.byteskwargs(opts)
2673 2673
2674 2674 def write(path):
2675 2675 filename = None
2676 2676 if fntemplate:
2677 2677 filename = makefilename(
2678 2678 ctx, fntemplate, pathname=os.path.join(prefix, path)
2679 2679 )
2680 2680 # attempt to create the directory if it does not already exist
2681 2681 try:
2682 2682 os.makedirs(os.path.dirname(filename))
2683 2683 except OSError:
2684 2684 pass
2685 2685 with formatter.maybereopen(basefm, filename) as fm:
2686 2686 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2687 2687
2688 2688 # Automation often uses hg cat on single files, so special case it
2689 2689 # for performance to avoid the cost of parsing the manifest.
2690 2690 if len(matcher.files()) == 1 and not matcher.anypats():
2691 2691 file = matcher.files()[0]
2692 2692 mfl = repo.manifestlog
2693 2693 mfnode = ctx.manifestnode()
2694 2694 try:
2695 2695 if mfnode and mfl[mfnode].find(file)[0]:
2696 2696 if _catfmtneedsdata(basefm):
2697 2697 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2698 2698 write(file)
2699 2699 return 0
2700 2700 except KeyError:
2701 2701 pass
2702 2702
2703 2703 if _catfmtneedsdata(basefm):
2704 2704 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2705 2705
2706 2706 for abs in ctx.walk(matcher):
2707 2707 write(abs)
2708 2708 err = 0
2709 2709
2710 2710 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2711 2711 for subpath in sorted(ctx.substate):
2712 2712 sub = ctx.sub(subpath)
2713 2713 try:
2714 2714 submatch = matchmod.subdirmatcher(subpath, matcher)
2715 2715 subprefix = os.path.join(prefix, subpath)
2716 2716 if not sub.cat(
2717 2717 submatch,
2718 2718 basefm,
2719 2719 fntemplate,
2720 2720 subprefix,
2721 2721 **pycompat.strkwargs(opts)
2722 2722 ):
2723 2723 err = 0
2724 2724 except error.RepoLookupError:
2725 2725 ui.status(
2726 2726 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2727 2727 )
2728 2728
2729 2729 return err
2730 2730
2731 2731
2732 2732 def commit(ui, repo, commitfunc, pats, opts):
2733 2733 '''commit the specified files or all outstanding changes'''
2734 2734 date = opts.get(b'date')
2735 2735 if date:
2736 2736 opts[b'date'] = dateutil.parsedate(date)
2737 2737 message = logmessage(ui, opts)
2738 2738 matcher = scmutil.match(repo[None], pats, opts)
2739 2739
2740 2740 dsguard = None
2741 2741 # extract addremove carefully -- this function can be called from a command
2742 2742 # that doesn't support addremove
2743 2743 if opts.get(b'addremove'):
2744 2744 dsguard = dirstateguard.dirstateguard(repo, b'commit')
2745 2745 with dsguard or util.nullcontextmanager():
2746 2746 if dsguard:
2747 2747 relative = scmutil.anypats(pats, opts)
2748 2748 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2749 2749 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
2750 2750 raise error.Abort(
2751 2751 _(b"failed to mark all new/missing files as added/removed")
2752 2752 )
2753 2753
2754 2754 return commitfunc(ui, repo, message, matcher, opts)
2755 2755
2756 2756
2757 2757 def samefile(f, ctx1, ctx2):
2758 2758 if f in ctx1.manifest():
2759 2759 a = ctx1.filectx(f)
2760 2760 if f in ctx2.manifest():
2761 2761 b = ctx2.filectx(f)
2762 2762 return not a.cmp(b) and a.flags() == b.flags()
2763 2763 else:
2764 2764 return False
2765 2765 else:
2766 2766 return f not in ctx2.manifest()
2767 2767
2768 2768
2769 2769 def amend(ui, repo, old, extra, pats, opts):
2770 2770 # avoid cycle context -> subrepo -> cmdutil
2771 2771 from . import context
2772 2772
2773 2773 # amend will reuse the existing user if not specified, but the obsolete
2774 2774 # marker creation requires that the current user's name is specified.
2775 2775 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2776 2776 ui.username() # raise exception if username not set
2777 2777
2778 2778 ui.note(_(b'amending changeset %s\n') % old)
2779 2779 base = old.p1()
2780 2780
2781 2781 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2782 2782 # Participating changesets:
2783 2783 #
2784 2784 # wctx o - workingctx that contains changes from working copy
2785 2785 # | to go into amending commit
2786 2786 # |
2787 2787 # old o - changeset to amend
2788 2788 # |
2789 2789 # base o - first parent of the changeset to amend
2790 2790 wctx = repo[None]
2791 2791
2792 2792 # Copy to avoid mutating input
2793 2793 extra = extra.copy()
2794 2794 # Update extra dict from amended commit (e.g. to preserve graft
2795 2795 # source)
2796 2796 extra.update(old.extra())
2797 2797
2798 2798 # Also update it from the from the wctx
2799 2799 extra.update(wctx.extra())
2800 2800
2801 2801 # date-only change should be ignored?
2802 2802 datemaydiffer = resolvecommitoptions(ui, opts)
2803 2803
2804 2804 date = old.date()
2805 2805 if opts.get(b'date'):
2806 2806 date = dateutil.parsedate(opts.get(b'date'))
2807 2807 user = opts.get(b'user') or old.user()
2808 2808
2809 2809 if len(old.parents()) > 1:
2810 2810 # ctx.files() isn't reliable for merges, so fall back to the
2811 2811 # slower repo.status() method
2812 2812 st = base.status(old)
2813 2813 files = set(st.modified) | set(st.added) | set(st.removed)
2814 2814 else:
2815 2815 files = set(old.files())
2816 2816
2817 2817 # add/remove the files to the working copy if the "addremove" option
2818 2818 # was specified.
2819 2819 matcher = scmutil.match(wctx, pats, opts)
2820 2820 relative = scmutil.anypats(pats, opts)
2821 2821 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2822 2822 if opts.get(b'addremove') and scmutil.addremove(
2823 2823 repo, matcher, b"", uipathfn, opts
2824 2824 ):
2825 2825 raise error.Abort(
2826 2826 _(b"failed to mark all new/missing files as added/removed")
2827 2827 )
2828 2828
2829 2829 # Check subrepos. This depends on in-place wctx._status update in
2830 2830 # subrepo.precommit(). To minimize the risk of this hack, we do
2831 2831 # nothing if .hgsub does not exist.
2832 2832 if b'.hgsub' in wctx or b'.hgsub' in old:
2833 2833 subs, commitsubs, newsubstate = subrepoutil.precommit(
2834 2834 ui, wctx, wctx._status, matcher
2835 2835 )
2836 2836 # amend should abort if commitsubrepos is enabled
2837 2837 assert not commitsubs
2838 2838 if subs:
2839 2839 subrepoutil.writestate(repo, newsubstate)
2840 2840
2841 2841 ms = mergestatemod.mergestate.read(repo)
2842 2842 mergeutil.checkunresolved(ms)
2843 2843
2844 2844 filestoamend = {f for f in wctx.files() if matcher(f)}
2845 2845
2846 2846 changes = len(filestoamend) > 0
2847 2847 if changes:
2848 2848 # Recompute copies (avoid recording a -> b -> a)
2849 2849 copied = copies.pathcopies(base, wctx, matcher)
2850 2850 if old.p2:
2851 2851 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2852 2852
2853 2853 # Prune files which were reverted by the updates: if old
2854 2854 # introduced file X and the file was renamed in the working
2855 2855 # copy, then those two files are the same and
2856 2856 # we can discard X from our list of files. Likewise if X
2857 2857 # was removed, it's no longer relevant. If X is missing (aka
2858 2858 # deleted), old X must be preserved.
2859 2859 files.update(filestoamend)
2860 2860 files = [
2861 2861 f
2862 2862 for f in files
2863 2863 if (f not in filestoamend or not samefile(f, wctx, base))
2864 2864 ]
2865 2865
2866 2866 def filectxfn(repo, ctx_, path):
2867 2867 try:
2868 2868 # If the file being considered is not amongst the files
2869 2869 # to be amended, we should return the file context from the
2870 2870 # old changeset. This avoids issues when only some files in
2871 2871 # the working copy are being amended but there are also
2872 2872 # changes to other files from the old changeset.
2873 2873 if path not in filestoamend:
2874 2874 return old.filectx(path)
2875 2875
2876 2876 # Return None for removed files.
2877 2877 if path in wctx.removed():
2878 2878 return None
2879 2879
2880 2880 fctx = wctx[path]
2881 2881 flags = fctx.flags()
2882 2882 mctx = context.memfilectx(
2883 2883 repo,
2884 2884 ctx_,
2885 2885 fctx.path(),
2886 2886 fctx.data(),
2887 2887 islink=b'l' in flags,
2888 2888 isexec=b'x' in flags,
2889 2889 copysource=copied.get(path),
2890 2890 )
2891 2891 return mctx
2892 2892 except KeyError:
2893 2893 return None
2894 2894
2895 2895 else:
2896 2896 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
2897 2897
2898 2898 # Use version of files as in the old cset
2899 2899 def filectxfn(repo, ctx_, path):
2900 2900 try:
2901 2901 return old.filectx(path)
2902 2902 except KeyError:
2903 2903 return None
2904 2904
2905 2905 # See if we got a message from -m or -l, if not, open the editor with
2906 2906 # the message of the changeset to amend.
2907 2907 message = logmessage(ui, opts)
2908 2908
2909 2909 editform = mergeeditform(old, b'commit.amend')
2910 2910
2911 2911 if not message:
2912 2912 message = old.description()
2913 2913 # Default if message isn't provided and --edit is not passed is to
2914 2914 # invoke editor, but allow --no-edit. If somehow we don't have any
2915 2915 # description, let's always start the editor.
2916 2916 doedit = not message or opts.get(b'edit') in [True, None]
2917 2917 else:
2918 2918 # Default if message is provided is to not invoke editor, but allow
2919 2919 # --edit.
2920 2920 doedit = opts.get(b'edit') is True
2921 2921 editor = getcommiteditor(edit=doedit, editform=editform)
2922 2922
2923 2923 pureextra = extra.copy()
2924 2924 extra[b'amend_source'] = old.hex()
2925 2925
2926 2926 new = context.memctx(
2927 2927 repo,
2928 2928 parents=[base.node(), old.p2().node()],
2929 2929 text=message,
2930 2930 files=files,
2931 2931 filectxfn=filectxfn,
2932 2932 user=user,
2933 2933 date=date,
2934 2934 extra=extra,
2935 2935 editor=editor,
2936 2936 )
2937 2937
2938 2938 newdesc = changelog.stripdesc(new.description())
2939 2939 if (
2940 2940 (not changes)
2941 2941 and newdesc == old.description()
2942 2942 and user == old.user()
2943 2943 and (date == old.date() or datemaydiffer)
2944 2944 and pureextra == old.extra()
2945 2945 ):
2946 2946 # nothing changed. continuing here would create a new node
2947 2947 # anyway because of the amend_source noise.
2948 2948 #
2949 2949 # This not what we expect from amend.
2950 2950 return old.node()
2951 2951
2952 2952 commitphase = None
2953 2953 if opts.get(b'secret'):
2954 2954 commitphase = phases.secret
2955 2955 newid = repo.commitctx(new)
2956 2956 ms.reset()
2957 2957
2958 2958 # Reroute the working copy parent to the new changeset
2959 2959 repo.setparents(newid, nullid)
2960 2960 mapping = {old.node(): (newid,)}
2961 2961 obsmetadata = None
2962 2962 if opts.get(b'note'):
2963 2963 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
2964 2964 backup = ui.configbool(b'rewrite', b'backup-bundle')
2965 2965 scmutil.cleanupnodes(
2966 2966 repo,
2967 2967 mapping,
2968 2968 b'amend',
2969 2969 metadata=obsmetadata,
2970 2970 fixphase=True,
2971 2971 targetphase=commitphase,
2972 2972 backup=backup,
2973 2973 )
2974 2974
2975 2975 # Fixing the dirstate because localrepo.commitctx does not update
2976 2976 # it. This is rather convenient because we did not need to update
2977 2977 # the dirstate for all the files in the new commit which commitctx
2978 2978 # could have done if it updated the dirstate. Now, we can
2979 2979 # selectively update the dirstate only for the amended files.
2980 2980 dirstate = repo.dirstate
2981 2981
2982 2982 # Update the state of the files which were added and modified in the
2983 2983 # amend to "normal" in the dirstate. We need to use "normallookup" since
2984 2984 # the files may have changed since the command started; using "normal"
2985 2985 # would mark them as clean but with uncommitted contents.
2986 2986 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2987 2987 for f in normalfiles:
2988 2988 dirstate.normallookup(f)
2989 2989
2990 2990 # Update the state of files which were removed in the amend
2991 2991 # to "removed" in the dirstate.
2992 2992 removedfiles = set(wctx.removed()) & filestoamend
2993 2993 for f in removedfiles:
2994 2994 dirstate.drop(f)
2995 2995
2996 2996 return newid
2997 2997
2998 2998
2999 2999 def commiteditor(repo, ctx, subs, editform=b''):
3000 3000 if ctx.description():
3001 3001 return ctx.description()
3002 3002 return commitforceeditor(
3003 3003 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3004 3004 )
3005 3005
3006 3006
3007 3007 def commitforceeditor(
3008 3008 repo,
3009 3009 ctx,
3010 3010 subs,
3011 3011 finishdesc=None,
3012 3012 extramsg=None,
3013 3013 editform=b'',
3014 3014 unchangedmessagedetection=False,
3015 3015 ):
3016 3016 if not extramsg:
3017 3017 extramsg = _(b"Leave message empty to abort commit.")
3018 3018
3019 3019 forms = [e for e in editform.split(b'.') if e]
3020 3020 forms.insert(0, b'changeset')
3021 3021 templatetext = None
3022 3022 while forms:
3023 3023 ref = b'.'.join(forms)
3024 3024 if repo.ui.config(b'committemplate', ref):
3025 3025 templatetext = committext = buildcommittemplate(
3026 3026 repo, ctx, subs, extramsg, ref
3027 3027 )
3028 3028 break
3029 3029 forms.pop()
3030 3030 else:
3031 3031 committext = buildcommittext(repo, ctx, subs, extramsg)
3032 3032
3033 3033 # run editor in the repository root
3034 3034 olddir = encoding.getcwd()
3035 3035 os.chdir(repo.root)
3036 3036
3037 3037 # make in-memory changes visible to external process
3038 3038 tr = repo.currenttransaction()
3039 3039 repo.dirstate.write(tr)
3040 3040 pending = tr and tr.writepending() and repo.root
3041 3041
3042 3042 editortext = repo.ui.edit(
3043 3043 committext,
3044 3044 ctx.user(),
3045 3045 ctx.extra(),
3046 3046 editform=editform,
3047 3047 pending=pending,
3048 3048 repopath=repo.path,
3049 3049 action=b'commit',
3050 3050 )
3051 3051 text = editortext
3052 3052
3053 3053 # strip away anything below this special string (used for editors that want
3054 3054 # to display the diff)
3055 3055 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3056 3056 if stripbelow:
3057 3057 text = text[: stripbelow.start()]
3058 3058
3059 3059 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3060 3060 os.chdir(olddir)
3061 3061
3062 3062 if finishdesc:
3063 3063 text = finishdesc(text)
3064 3064 if not text.strip():
3065 3065 raise error.InputError(_(b"empty commit message"))
3066 3066 if unchangedmessagedetection and editortext == templatetext:
3067 3067 raise error.InputError(_(b"commit message unchanged"))
3068 3068
3069 3069 return text
3070 3070
3071 3071
3072 3072 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3073 3073 ui = repo.ui
3074 3074 spec = formatter.reference_templatespec(ref)
3075 3075 t = logcmdutil.changesettemplater(ui, repo, spec)
3076 3076 t.t.cache.update(
3077 3077 (k, templater.unquotestring(v))
3078 3078 for k, v in repo.ui.configitems(b'committemplate')
3079 3079 )
3080 3080
3081 3081 if not extramsg:
3082 3082 extramsg = b'' # ensure that extramsg is string
3083 3083
3084 3084 ui.pushbuffer()
3085 3085 t.show(ctx, extramsg=extramsg)
3086 3086 return ui.popbuffer()
3087 3087
3088 3088
3089 3089 def hgprefix(msg):
3090 3090 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3091 3091
3092 3092
3093 3093 def buildcommittext(repo, ctx, subs, extramsg):
3094 3094 edittext = []
3095 3095 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3096 3096 if ctx.description():
3097 3097 edittext.append(ctx.description())
3098 3098 edittext.append(b"")
3099 3099 edittext.append(b"") # Empty line between message and comments.
3100 3100 edittext.append(
3101 3101 hgprefix(
3102 3102 _(
3103 3103 b"Enter commit message."
3104 3104 b" Lines beginning with 'HG:' are removed."
3105 3105 )
3106 3106 )
3107 3107 )
3108 3108 edittext.append(hgprefix(extramsg))
3109 3109 edittext.append(b"HG: --")
3110 3110 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3111 3111 if ctx.p2():
3112 3112 edittext.append(hgprefix(_(b"branch merge")))
3113 3113 if ctx.branch():
3114 3114 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3115 3115 if bookmarks.isactivewdirparent(repo):
3116 3116 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3117 3117 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3118 3118 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3119 3119 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3120 3120 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3121 3121 if not added and not modified and not removed:
3122 3122 edittext.append(hgprefix(_(b"no files changed")))
3123 3123 edittext.append(b"")
3124 3124
3125 3125 return b"\n".join(edittext)
3126 3126
3127 3127
3128 3128 def commitstatus(repo, node, branch, bheads=None, tip=None, opts=None):
3129 3129 if opts is None:
3130 3130 opts = {}
3131 3131 ctx = repo[node]
3132 3132 parents = ctx.parents()
3133 3133
3134 3134 if tip is not None and repo.changelog.tip() == tip:
3135 3135 # avoid reporting something like "committed new head" when
3136 3136 # recommitting old changesets, and issue a helpful warning
3137 3137 # for most instances
3138 3138 repo.ui.warn(_("warning: commit already existed in the repository!\n"))
3139 3139 elif (
3140 3140 not opts.get(b'amend')
3141 3141 and bheads
3142 3142 and node not in bheads
3143 3143 and not any(
3144 3144 p.node() in bheads and p.branch() == branch for p in parents
3145 3145 )
3146 3146 ):
3147 3147 repo.ui.status(_(b'created new head\n'))
3148 3148 # The message is not printed for initial roots. For the other
3149 3149 # changesets, it is printed in the following situations:
3150 3150 #
3151 3151 # Par column: for the 2 parents with ...
3152 3152 # N: null or no parent
3153 3153 # B: parent is on another named branch
3154 3154 # C: parent is a regular non head changeset
3155 3155 # H: parent was a branch head of the current branch
3156 3156 # Msg column: whether we print "created new head" message
3157 3157 # In the following, it is assumed that there already exists some
3158 3158 # initial branch heads of the current branch, otherwise nothing is
3159 3159 # printed anyway.
3160 3160 #
3161 3161 # Par Msg Comment
3162 3162 # N N y additional topo root
3163 3163 #
3164 3164 # B N y additional branch root
3165 3165 # C N y additional topo head
3166 3166 # H N n usual case
3167 3167 #
3168 3168 # B B y weird additional branch root
3169 3169 # C B y branch merge
3170 3170 # H B n merge with named branch
3171 3171 #
3172 3172 # C C y additional head from merge
3173 3173 # C H n merge with a head
3174 3174 #
3175 3175 # H H n head merge: head count decreases
3176 3176
3177 3177 if not opts.get(b'close_branch'):
3178 3178 for r in parents:
3179 3179 if r.closesbranch() and r.branch() == branch:
3180 3180 repo.ui.status(
3181 3181 _(b'reopening closed branch head %d\n') % r.rev()
3182 3182 )
3183 3183
3184 3184 if repo.ui.debugflag:
3185 3185 repo.ui.write(
3186 3186 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3187 3187 )
3188 3188 elif repo.ui.verbose:
3189 3189 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3190 3190
3191 3191
3192 3192 def postcommitstatus(repo, pats, opts):
3193 3193 return repo.status(match=scmutil.match(repo[None], pats, opts))
3194 3194
3195 3195
3196 3196 def revert(ui, repo, ctx, *pats, **opts):
3197 3197 opts = pycompat.byteskwargs(opts)
3198 3198 parent, p2 = repo.dirstate.parents()
3199 3199 node = ctx.node()
3200 3200
3201 3201 mf = ctx.manifest()
3202 3202 if node == p2:
3203 3203 parent = p2
3204 3204
3205 3205 # need all matching names in dirstate and manifest of target rev,
3206 3206 # so have to walk both. do not print errors if files exist in one
3207 3207 # but not other. in both cases, filesets should be evaluated against
3208 3208 # workingctx to get consistent result (issue4497). this means 'set:**'
3209 3209 # cannot be used to select missing files from target rev.
3210 3210
3211 3211 # `names` is a mapping for all elements in working copy and target revision
3212 3212 # The mapping is in the form:
3213 3213 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3214 3214 names = {}
3215 3215 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3216 3216
3217 3217 with repo.wlock():
3218 3218 ## filling of the `names` mapping
3219 3219 # walk dirstate to fill `names`
3220 3220
3221 3221 interactive = opts.get(b'interactive', False)
3222 3222 wctx = repo[None]
3223 3223 m = scmutil.match(wctx, pats, opts)
3224 3224
3225 3225 # we'll need this later
3226 3226 targetsubs = sorted(s for s in wctx.substate if m(s))
3227 3227
3228 3228 if not m.always():
3229 3229 matcher = matchmod.badmatch(m, lambda x, y: False)
3230 3230 for abs in wctx.walk(matcher):
3231 3231 names[abs] = m.exact(abs)
3232 3232
3233 3233 # walk target manifest to fill `names`
3234 3234
3235 3235 def badfn(path, msg):
3236 3236 if path in names:
3237 3237 return
3238 3238 if path in ctx.substate:
3239 3239 return
3240 3240 path_ = path + b'/'
3241 3241 for f in names:
3242 3242 if f.startswith(path_):
3243 3243 return
3244 3244 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3245 3245
3246 3246 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3247 3247 if abs not in names:
3248 3248 names[abs] = m.exact(abs)
3249 3249
3250 3250 # Find status of all file in `names`.
3251 3251 m = scmutil.matchfiles(repo, names)
3252 3252
3253 3253 changes = repo.status(
3254 3254 node1=node, match=m, unknown=True, ignored=True, clean=True
3255 3255 )
3256 3256 else:
3257 3257 changes = repo.status(node1=node, match=m)
3258 3258 for kind in changes:
3259 3259 for abs in kind:
3260 3260 names[abs] = m.exact(abs)
3261 3261
3262 3262 m = scmutil.matchfiles(repo, names)
3263 3263
3264 3264 modified = set(changes.modified)
3265 3265 added = set(changes.added)
3266 3266 removed = set(changes.removed)
3267 3267 _deleted = set(changes.deleted)
3268 3268 unknown = set(changes.unknown)
3269 3269 unknown.update(changes.ignored)
3270 3270 clean = set(changes.clean)
3271 3271 modadded = set()
3272 3272
3273 3273 # We need to account for the state of the file in the dirstate,
3274 3274 # even when we revert against something else than parent. This will
3275 3275 # slightly alter the behavior of revert (doing back up or not, delete
3276 3276 # or just forget etc).
3277 3277 if parent == node:
3278 3278 dsmodified = modified
3279 3279 dsadded = added
3280 3280 dsremoved = removed
3281 3281 # store all local modifications, useful later for rename detection
3282 3282 localchanges = dsmodified | dsadded
3283 3283 modified, added, removed = set(), set(), set()
3284 3284 else:
3285 3285 changes = repo.status(node1=parent, match=m)
3286 3286 dsmodified = set(changes.modified)
3287 3287 dsadded = set(changes.added)
3288 3288 dsremoved = set(changes.removed)
3289 3289 # store all local modifications, useful later for rename detection
3290 3290 localchanges = dsmodified | dsadded
3291 3291
3292 3292 # only take into account for removes between wc and target
3293 3293 clean |= dsremoved - removed
3294 3294 dsremoved &= removed
3295 3295 # distinct between dirstate remove and other
3296 3296 removed -= dsremoved
3297 3297
3298 3298 modadded = added & dsmodified
3299 3299 added -= modadded
3300 3300
3301 3301 # tell newly modified apart.
3302 3302 dsmodified &= modified
3303 3303 dsmodified |= modified & dsadded # dirstate added may need backup
3304 3304 modified -= dsmodified
3305 3305
3306 3306 # We need to wait for some post-processing to update this set
3307 3307 # before making the distinction. The dirstate will be used for
3308 3308 # that purpose.
3309 3309 dsadded = added
3310 3310
3311 3311 # in case of merge, files that are actually added can be reported as
3312 3312 # modified, we need to post process the result
3313 3313 if p2 != nullid:
3314 3314 mergeadd = set(dsmodified)
3315 3315 for path in dsmodified:
3316 3316 if path in mf:
3317 3317 mergeadd.remove(path)
3318 3318 dsadded |= mergeadd
3319 3319 dsmodified -= mergeadd
3320 3320
3321 3321 # if f is a rename, update `names` to also revert the source
3322 3322 for f in localchanges:
3323 3323 src = repo.dirstate.copied(f)
3324 3324 # XXX should we check for rename down to target node?
3325 3325 if src and src not in names and repo.dirstate[src] == b'r':
3326 3326 dsremoved.add(src)
3327 3327 names[src] = True
3328 3328
3329 3329 # determine the exact nature of the deleted changesets
3330 3330 deladded = set(_deleted)
3331 3331 for path in _deleted:
3332 3332 if path in mf:
3333 3333 deladded.remove(path)
3334 3334 deleted = _deleted - deladded
3335 3335
3336 3336 # distinguish between file to forget and the other
3337 3337 added = set()
3338 3338 for abs in dsadded:
3339 3339 if repo.dirstate[abs] != b'a':
3340 3340 added.add(abs)
3341 3341 dsadded -= added
3342 3342
3343 3343 for abs in deladded:
3344 3344 if repo.dirstate[abs] == b'a':
3345 3345 dsadded.add(abs)
3346 3346 deladded -= dsadded
3347 3347
3348 3348 # For files marked as removed, we check if an unknown file is present at
3349 3349 # the same path. If a such file exists it may need to be backed up.
3350 3350 # Making the distinction at this stage helps have simpler backup
3351 3351 # logic.
3352 3352 removunk = set()
3353 3353 for abs in removed:
3354 3354 target = repo.wjoin(abs)
3355 3355 if os.path.lexists(target):
3356 3356 removunk.add(abs)
3357 3357 removed -= removunk
3358 3358
3359 3359 dsremovunk = set()
3360 3360 for abs in dsremoved:
3361 3361 target = repo.wjoin(abs)
3362 3362 if os.path.lexists(target):
3363 3363 dsremovunk.add(abs)
3364 3364 dsremoved -= dsremovunk
3365 3365
3366 3366 # action to be actually performed by revert
3367 3367 # (<list of file>, message>) tuple
3368 3368 actions = {
3369 3369 b'revert': ([], _(b'reverting %s\n')),
3370 3370 b'add': ([], _(b'adding %s\n')),
3371 3371 b'remove': ([], _(b'removing %s\n')),
3372 3372 b'drop': ([], _(b'removing %s\n')),
3373 3373 b'forget': ([], _(b'forgetting %s\n')),
3374 3374 b'undelete': ([], _(b'undeleting %s\n')),
3375 3375 b'noop': (None, _(b'no changes needed to %s\n')),
3376 3376 b'unknown': (None, _(b'file not managed: %s\n')),
3377 3377 }
3378 3378
3379 3379 # "constant" that convey the backup strategy.
3380 3380 # All set to `discard` if `no-backup` is set do avoid checking
3381 3381 # no_backup lower in the code.
3382 3382 # These values are ordered for comparison purposes
3383 3383 backupinteractive = 3 # do backup if interactively modified
3384 3384 backup = 2 # unconditionally do backup
3385 3385 check = 1 # check if the existing file differs from target
3386 3386 discard = 0 # never do backup
3387 3387 if opts.get(b'no_backup'):
3388 3388 backupinteractive = backup = check = discard
3389 3389 if interactive:
3390 3390 dsmodifiedbackup = backupinteractive
3391 3391 else:
3392 3392 dsmodifiedbackup = backup
3393 3393 tobackup = set()
3394 3394
3395 3395 backupanddel = actions[b'remove']
3396 3396 if not opts.get(b'no_backup'):
3397 3397 backupanddel = actions[b'drop']
3398 3398
3399 3399 disptable = (
3400 3400 # dispatch table:
3401 3401 # file state
3402 3402 # action
3403 3403 # make backup
3404 3404 ## Sets that results that will change file on disk
3405 3405 # Modified compared to target, no local change
3406 3406 (modified, actions[b'revert'], discard),
3407 3407 # Modified compared to target, but local file is deleted
3408 3408 (deleted, actions[b'revert'], discard),
3409 3409 # Modified compared to target, local change
3410 3410 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3411 3411 # Added since target
3412 3412 (added, actions[b'remove'], discard),
3413 3413 # Added in working directory
3414 3414 (dsadded, actions[b'forget'], discard),
3415 3415 # Added since target, have local modification
3416 3416 (modadded, backupanddel, backup),
3417 3417 # Added since target but file is missing in working directory
3418 3418 (deladded, actions[b'drop'], discard),
3419 3419 # Removed since target, before working copy parent
3420 3420 (removed, actions[b'add'], discard),
3421 3421 # Same as `removed` but an unknown file exists at the same path
3422 3422 (removunk, actions[b'add'], check),
3423 3423 # Removed since targe, marked as such in working copy parent
3424 3424 (dsremoved, actions[b'undelete'], discard),
3425 3425 # Same as `dsremoved` but an unknown file exists at the same path
3426 3426 (dsremovunk, actions[b'undelete'], check),
3427 3427 ## the following sets does not result in any file changes
3428 3428 # File with no modification
3429 3429 (clean, actions[b'noop'], discard),
3430 3430 # Existing file, not tracked anywhere
3431 3431 (unknown, actions[b'unknown'], discard),
3432 3432 )
3433 3433
3434 3434 for abs, exact in sorted(names.items()):
3435 3435 # target file to be touch on disk (relative to cwd)
3436 3436 target = repo.wjoin(abs)
3437 3437 # search the entry in the dispatch table.
3438 3438 # if the file is in any of these sets, it was touched in the working
3439 3439 # directory parent and we are sure it needs to be reverted.
3440 3440 for table, (xlist, msg), dobackup in disptable:
3441 3441 if abs not in table:
3442 3442 continue
3443 3443 if xlist is not None:
3444 3444 xlist.append(abs)
3445 3445 if dobackup:
3446 3446 # If in interactive mode, don't automatically create
3447 3447 # .orig files (issue4793)
3448 3448 if dobackup == backupinteractive:
3449 3449 tobackup.add(abs)
3450 3450 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3451 3451 absbakname = scmutil.backuppath(ui, repo, abs)
3452 3452 bakname = os.path.relpath(
3453 3453 absbakname, start=repo.root
3454 3454 )
3455 3455 ui.note(
3456 3456 _(b'saving current version of %s as %s\n')
3457 3457 % (uipathfn(abs), uipathfn(bakname))
3458 3458 )
3459 3459 if not opts.get(b'dry_run'):
3460 3460 if interactive:
3461 3461 util.copyfile(target, absbakname)
3462 3462 else:
3463 3463 util.rename(target, absbakname)
3464 3464 if opts.get(b'dry_run'):
3465 3465 if ui.verbose or not exact:
3466 3466 ui.status(msg % uipathfn(abs))
3467 3467 elif exact:
3468 3468 ui.warn(msg % uipathfn(abs))
3469 3469 break
3470 3470
3471 3471 if not opts.get(b'dry_run'):
3472 3472 needdata = (b'revert', b'add', b'undelete')
3473 3473 oplist = [actions[name][0] for name in needdata]
3474 3474 prefetch = scmutil.prefetchfiles
3475 3475 matchfiles = scmutil.matchfiles(
3476 3476 repo, [f for sublist in oplist for f in sublist]
3477 3477 )
3478 3478 prefetch(
3479 3479 repo, [(ctx.rev(), matchfiles)],
3480 3480 )
3481 3481 match = scmutil.match(repo[None], pats)
3482 3482 _performrevert(
3483 3483 repo,
3484 3484 ctx,
3485 3485 names,
3486 3486 uipathfn,
3487 3487 actions,
3488 3488 match,
3489 3489 interactive,
3490 3490 tobackup,
3491 3491 )
3492 3492
3493 3493 if targetsubs:
3494 3494 # Revert the subrepos on the revert list
3495 3495 for sub in targetsubs:
3496 3496 try:
3497 3497 wctx.sub(sub).revert(
3498 3498 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3499 3499 )
3500 3500 except KeyError:
3501 3501 raise error.Abort(
3502 3502 b"subrepository '%s' does not exist in %s!"
3503 3503 % (sub, short(ctx.node()))
3504 3504 )
3505 3505
3506 3506
3507 3507 def _performrevert(
3508 3508 repo,
3509 3509 ctx,
3510 3510 names,
3511 3511 uipathfn,
3512 3512 actions,
3513 3513 match,
3514 3514 interactive=False,
3515 3515 tobackup=None,
3516 3516 ):
3517 3517 """function that actually perform all the actions computed for revert
3518 3518
3519 3519 This is an independent function to let extension to plug in and react to
3520 3520 the imminent revert.
3521 3521
3522 3522 Make sure you have the working directory locked when calling this function.
3523 3523 """
3524 3524 parent, p2 = repo.dirstate.parents()
3525 3525 node = ctx.node()
3526 3526 excluded_files = []
3527 3527
3528 3528 def checkout(f):
3529 3529 fc = ctx[f]
3530 3530 repo.wwrite(f, fc.data(), fc.flags())
3531 3531
3532 3532 def doremove(f):
3533 3533 try:
3534 3534 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3535 3535 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3536 3536 except OSError:
3537 3537 pass
3538 3538 repo.dirstate.remove(f)
3539 3539
3540 3540 def prntstatusmsg(action, f):
3541 3541 exact = names[f]
3542 3542 if repo.ui.verbose or not exact:
3543 3543 repo.ui.status(actions[action][1] % uipathfn(f))
3544 3544
3545 3545 audit_path = pathutil.pathauditor(repo.root, cached=True)
3546 3546 for f in actions[b'forget'][0]:
3547 3547 if interactive:
3548 3548 choice = repo.ui.promptchoice(
3549 3549 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3550 3550 )
3551 3551 if choice == 0:
3552 3552 prntstatusmsg(b'forget', f)
3553 3553 repo.dirstate.drop(f)
3554 3554 else:
3555 3555 excluded_files.append(f)
3556 3556 else:
3557 3557 prntstatusmsg(b'forget', f)
3558 3558 repo.dirstate.drop(f)
3559 3559 for f in actions[b'remove'][0]:
3560 3560 audit_path(f)
3561 3561 if interactive:
3562 3562 choice = repo.ui.promptchoice(
3563 3563 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3564 3564 )
3565 3565 if choice == 0:
3566 3566 prntstatusmsg(b'remove', f)
3567 3567 doremove(f)
3568 3568 else:
3569 3569 excluded_files.append(f)
3570 3570 else:
3571 3571 prntstatusmsg(b'remove', f)
3572 3572 doremove(f)
3573 3573 for f in actions[b'drop'][0]:
3574 3574 audit_path(f)
3575 3575 prntstatusmsg(b'drop', f)
3576 3576 repo.dirstate.remove(f)
3577 3577
3578 3578 normal = None
3579 3579 if node == parent:
3580 3580 # We're reverting to our parent. If possible, we'd like status
3581 3581 # to report the file as clean. We have to use normallookup for
3582 3582 # merges to avoid losing information about merged/dirty files.
3583 3583 if p2 != nullid:
3584 3584 normal = repo.dirstate.normallookup
3585 3585 else:
3586 3586 normal = repo.dirstate.normal
3587 3587
3588 3588 newlyaddedandmodifiedfiles = set()
3589 3589 if interactive:
3590 3590 # Prompt the user for changes to revert
3591 3591 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3592 3592 m = scmutil.matchfiles(repo, torevert)
3593 3593 diffopts = patch.difffeatureopts(
3594 3594 repo.ui,
3595 3595 whitespace=True,
3596 3596 section=b'commands',
3597 3597 configprefix=b'revert.interactive.',
3598 3598 )
3599 3599 diffopts.nodates = True
3600 3600 diffopts.git = True
3601 3601 operation = b'apply'
3602 3602 if node == parent:
3603 3603 if repo.ui.configbool(
3604 3604 b'experimental', b'revert.interactive.select-to-keep'
3605 3605 ):
3606 3606 operation = b'keep'
3607 3607 else:
3608 3608 operation = b'discard'
3609 3609
3610 3610 if operation == b'apply':
3611 3611 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3612 3612 else:
3613 3613 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3614 3614 originalchunks = patch.parsepatch(diff)
3615 3615
3616 3616 try:
3617 3617
3618 3618 chunks, opts = recordfilter(
3619 3619 repo.ui, originalchunks, match, operation=operation
3620 3620 )
3621 3621 if operation == b'discard':
3622 3622 chunks = patch.reversehunks(chunks)
3623 3623
3624 3624 except error.PatchError as err:
3625 3625 raise error.Abort(_(b'error parsing patch: %s') % err)
3626 3626
3627 3627 # FIXME: when doing an interactive revert of a copy, there's no way of
3628 3628 # performing a partial revert of the added file, the only option is
3629 3629 # "remove added file <name> (Yn)?", so we don't need to worry about the
3630 3630 # alsorestore value. Ideally we'd be able to partially revert
3631 3631 # copied/renamed files.
3632 3632 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3633 3633 chunks, originalchunks
3634 3634 )
3635 3635 if tobackup is None:
3636 3636 tobackup = set()
3637 3637 # Apply changes
3638 3638 fp = stringio()
3639 3639 # chunks are serialized per file, but files aren't sorted
3640 3640 for f in sorted({c.header.filename() for c in chunks if ishunk(c)}):
3641 3641 prntstatusmsg(b'revert', f)
3642 3642 files = set()
3643 3643 for c in chunks:
3644 3644 if ishunk(c):
3645 3645 abs = c.header.filename()
3646 3646 # Create a backup file only if this hunk should be backed up
3647 3647 if c.header.filename() in tobackup:
3648 3648 target = repo.wjoin(abs)
3649 3649 bakname = scmutil.backuppath(repo.ui, repo, abs)
3650 3650 util.copyfile(target, bakname)
3651 3651 tobackup.remove(abs)
3652 3652 if abs not in files:
3653 3653 files.add(abs)
3654 3654 if operation == b'keep':
3655 3655 checkout(abs)
3656 3656 c.write(fp)
3657 3657 dopatch = fp.tell()
3658 3658 fp.seek(0)
3659 3659 if dopatch:
3660 3660 try:
3661 3661 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3662 3662 except error.PatchError as err:
3663 3663 raise error.Abort(pycompat.bytestr(err))
3664 3664 del fp
3665 3665 else:
3666 3666 for f in actions[b'revert'][0]:
3667 3667 prntstatusmsg(b'revert', f)
3668 3668 checkout(f)
3669 3669 if normal:
3670 3670 normal(f)
3671 3671
3672 3672 for f in actions[b'add'][0]:
3673 3673 # Don't checkout modified files, they are already created by the diff
3674 3674 if f not in newlyaddedandmodifiedfiles:
3675 3675 prntstatusmsg(b'add', f)
3676 3676 checkout(f)
3677 3677 repo.dirstate.add(f)
3678 3678
3679 3679 normal = repo.dirstate.normallookup
3680 3680 if node == parent and p2 == nullid:
3681 3681 normal = repo.dirstate.normal
3682 3682 for f in actions[b'undelete'][0]:
3683 3683 if interactive:
3684 3684 choice = repo.ui.promptchoice(
3685 3685 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3686 3686 )
3687 3687 if choice == 0:
3688 3688 prntstatusmsg(b'undelete', f)
3689 3689 checkout(f)
3690 3690 normal(f)
3691 3691 else:
3692 3692 excluded_files.append(f)
3693 3693 else:
3694 3694 prntstatusmsg(b'undelete', f)
3695 3695 checkout(f)
3696 3696 normal(f)
3697 3697
3698 3698 copied = copies.pathcopies(repo[parent], ctx)
3699 3699
3700 3700 for f in (
3701 3701 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3702 3702 ):
3703 3703 if f in copied:
3704 3704 repo.dirstate.copy(copied[f], f)
3705 3705
3706 3706
3707 3707 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3708 3708 # commands.outgoing. "missing" is "missing" of the result of
3709 3709 # "findcommonoutgoing()"
3710 3710 outgoinghooks = util.hooks()
3711 3711
3712 3712 # a list of (ui, repo) functions called by commands.summary
3713 3713 summaryhooks = util.hooks()
3714 3714
3715 3715 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3716 3716 #
3717 3717 # functions should return tuple of booleans below, if 'changes' is None:
3718 3718 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3719 3719 #
3720 3720 # otherwise, 'changes' is a tuple of tuples below:
3721 3721 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3722 3722 # - (desturl, destbranch, destpeer, outgoing)
3723 3723 summaryremotehooks = util.hooks()
3724 3724
3725 3725
3726 3726 def checkunfinished(repo, commit=False, skipmerge=False):
3727 3727 '''Look for an unfinished multistep operation, like graft, and abort
3728 3728 if found. It's probably good to check this right before
3729 3729 bailifchanged().
3730 3730 '''
3731 3731 # Check for non-clearable states first, so things like rebase will take
3732 3732 # precedence over update.
3733 3733 for state in statemod._unfinishedstates:
3734 3734 if (
3735 3735 state._clearable
3736 3736 or (commit and state._allowcommit)
3737 3737 or state._reportonly
3738 3738 ):
3739 3739 continue
3740 3740 if state.isunfinished(repo):
3741 3741 raise error.StateError(state.msg(), hint=state.hint())
3742 3742
3743 3743 for s in statemod._unfinishedstates:
3744 3744 if (
3745 3745 not s._clearable
3746 3746 or (commit and s._allowcommit)
3747 3747 or (s._opname == b'merge' and skipmerge)
3748 3748 or s._reportonly
3749 3749 ):
3750 3750 continue
3751 3751 if s.isunfinished(repo):
3752 3752 raise error.StateError(s.msg(), hint=s.hint())
3753 3753
3754 3754
3755 3755 def clearunfinished(repo):
3756 3756 '''Check for unfinished operations (as above), and clear the ones
3757 3757 that are clearable.
3758 3758 '''
3759 3759 for state in statemod._unfinishedstates:
3760 3760 if state._reportonly:
3761 3761 continue
3762 3762 if not state._clearable and state.isunfinished(repo):
3763 3763 raise error.StateError(state.msg(), hint=state.hint())
3764 3764
3765 3765 for s in statemod._unfinishedstates:
3766 3766 if s._opname == b'merge' or state._reportonly:
3767 3767 continue
3768 3768 if s._clearable and s.isunfinished(repo):
3769 3769 util.unlink(repo.vfs.join(s._fname))
3770 3770
3771 3771
3772 3772 def getunfinishedstate(repo):
3773 3773 ''' Checks for unfinished operations and returns statecheck object
3774 3774 for it'''
3775 3775 for state in statemod._unfinishedstates:
3776 3776 if state.isunfinished(repo):
3777 3777 return state
3778 3778 return None
3779 3779
3780 3780
3781 3781 def howtocontinue(repo):
3782 3782 '''Check for an unfinished operation and return the command to finish
3783 3783 it.
3784 3784
3785 3785 statemod._unfinishedstates list is checked for an unfinished operation
3786 3786 and the corresponding message to finish it is generated if a method to
3787 3787 continue is supported by the operation.
3788 3788
3789 3789 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3790 3790 a boolean.
3791 3791 '''
3792 3792 contmsg = _(b"continue: %s")
3793 3793 for state in statemod._unfinishedstates:
3794 3794 if not state._continueflag:
3795 3795 continue
3796 3796 if state.isunfinished(repo):
3797 3797 return contmsg % state.continuemsg(), True
3798 3798 if repo[None].dirty(missing=True, merge=False, branch=False):
3799 3799 return contmsg % _(b"hg commit"), False
3800 3800 return None, None
3801 3801
3802 3802
3803 3803 def checkafterresolved(repo):
3804 3804 '''Inform the user about the next action after completing hg resolve
3805 3805
3806 3806 If there's a an unfinished operation that supports continue flag,
3807 3807 howtocontinue will yield repo.ui.warn as the reporter.
3808 3808
3809 3809 Otherwise, it will yield repo.ui.note.
3810 3810 '''
3811 3811 msg, warning = howtocontinue(repo)
3812 3812 if msg is not None:
3813 3813 if warning:
3814 3814 repo.ui.warn(b"%s\n" % msg)
3815 3815 else:
3816 3816 repo.ui.note(b"%s\n" % msg)
3817 3817
3818 3818
3819 3819 def wrongtooltocontinue(repo, task):
3820 3820 '''Raise an abort suggesting how to properly continue if there is an
3821 3821 active task.
3822 3822
3823 3823 Uses howtocontinue() to find the active task.
3824 3824
3825 3825 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3826 3826 a hint.
3827 3827 '''
3828 3828 after = howtocontinue(repo)
3829 3829 hint = None
3830 3830 if after[1]:
3831 3831 hint = after[0]
3832 3832 raise error.StateError(_(b'no %s in progress') % task, hint=hint)
3833 3833
3834 3834
3835 3835 def abortgraft(ui, repo, graftstate):
3836 3836 """abort the interrupted graft and rollbacks to the state before interrupted
3837 3837 graft"""
3838 3838 if not graftstate.exists():
3839 3839 raise error.StateError(_(b"no interrupted graft to abort"))
3840 3840 statedata = readgraftstate(repo, graftstate)
3841 3841 newnodes = statedata.get(b'newnodes')
3842 3842 if newnodes is None:
3843 3843 # and old graft state which does not have all the data required to abort
3844 3844 # the graft
3845 3845 raise error.Abort(_(b"cannot abort using an old graftstate"))
3846 3846
3847 3847 # changeset from which graft operation was started
3848 3848 if len(newnodes) > 0:
3849 3849 startctx = repo[newnodes[0]].p1()
3850 3850 else:
3851 3851 startctx = repo[b'.']
3852 3852 # whether to strip or not
3853 3853 cleanup = False
3854 3854
3855 3855 if newnodes:
3856 3856 newnodes = [repo[r].rev() for r in newnodes]
3857 3857 cleanup = True
3858 3858 # checking that none of the newnodes turned public or is public
3859 3859 immutable = [c for c in newnodes if not repo[c].mutable()]
3860 3860 if immutable:
3861 3861 repo.ui.warn(
3862 3862 _(b"cannot clean up public changesets %s\n")
3863 3863 % b', '.join(bytes(repo[r]) for r in immutable),
3864 3864 hint=_(b"see 'hg help phases' for details"),
3865 3865 )
3866 3866 cleanup = False
3867 3867
3868 3868 # checking that no new nodes are created on top of grafted revs
3869 3869 desc = set(repo.changelog.descendants(newnodes))
3870 3870 if desc - set(newnodes):
3871 3871 repo.ui.warn(
3872 3872 _(
3873 3873 b"new changesets detected on destination "
3874 3874 b"branch, can't strip\n"
3875 3875 )
3876 3876 )
3877 3877 cleanup = False
3878 3878
3879 3879 if cleanup:
3880 3880 with repo.wlock(), repo.lock():
3881 3881 mergemod.clean_update(startctx)
3882 3882 # stripping the new nodes created
3883 3883 strippoints = [
3884 3884 c.node() for c in repo.set(b"roots(%ld)", newnodes)
3885 3885 ]
3886 3886 repair.strip(repo.ui, repo, strippoints, backup=False)
3887 3887
3888 3888 if not cleanup:
3889 3889 # we don't update to the startnode if we can't strip
3890 3890 startctx = repo[b'.']
3891 3891 mergemod.clean_update(startctx)
3892 3892
3893 3893 ui.status(_(b"graft aborted\n"))
3894 3894 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
3895 3895 graftstate.delete()
3896 3896 return 0
3897 3897
3898 3898
3899 3899 def readgraftstate(repo, graftstate):
3900 3900 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
3901 3901 """read the graft state file and return a dict of the data stored in it"""
3902 3902 try:
3903 3903 return graftstate.read()
3904 3904 except error.CorruptedState:
3905 3905 nodes = repo.vfs.read(b'graftstate').splitlines()
3906 3906 return {b'nodes': nodes}
3907 3907
3908 3908
3909 3909 def hgabortgraft(ui, repo):
3910 3910 """ abort logic for aborting graft using 'hg abort'"""
3911 3911 with repo.wlock():
3912 3912 graftstate = statemod.cmdstate(repo, b'graftstate')
3913 3913 return abortgraft(ui, repo, graftstate)
@@ -1,3563 +1,3563 b''
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import functools
12 12 import os
13 13 import random
14 14 import sys
15 15 import time
16 16 import weakref
17 17
18 18 from .i18n import _
19 19 from .node import (
20 20 bin,
21 21 hex,
22 22 nullid,
23 23 nullrev,
24 24 short,
25 25 )
26 26 from .pycompat import (
27 27 delattr,
28 28 getattr,
29 29 )
30 30 from . import (
31 31 bookmarks,
32 32 branchmap,
33 33 bundle2,
34 34 bundlecaches,
35 35 changegroup,
36 36 color,
37 37 commit,
38 38 context,
39 39 dirstate,
40 40 dirstateguard,
41 41 discovery,
42 42 encoding,
43 43 error,
44 44 exchange,
45 45 extensions,
46 46 filelog,
47 47 hook,
48 48 lock as lockmod,
49 49 match as matchmod,
50 50 mergestate as mergestatemod,
51 51 mergeutil,
52 52 namespaces,
53 53 narrowspec,
54 54 obsolete,
55 55 pathutil,
56 56 phases,
57 57 pushkey,
58 58 pycompat,
59 59 rcutil,
60 60 repoview,
61 61 requirements as requirementsmod,
62 62 revset,
63 63 revsetlang,
64 64 scmutil,
65 65 sparse,
66 66 store as storemod,
67 67 subrepoutil,
68 68 tags as tagsmod,
69 69 transaction,
70 70 txnutil,
71 71 util,
72 72 vfs as vfsmod,
73 73 )
74 74
75 75 from .interfaces import (
76 76 repository,
77 77 util as interfaceutil,
78 78 )
79 79
80 80 from .utils import (
81 81 hashutil,
82 82 procutil,
83 83 stringutil,
84 84 )
85 85
86 86 from .revlogutils import constants as revlogconst
87 87
88 88 release = lockmod.release
89 89 urlerr = util.urlerr
90 90 urlreq = util.urlreq
91 91
92 92 # set of (path, vfs-location) tuples. vfs-location is:
93 93 # - 'plain for vfs relative paths
94 94 # - '' for svfs relative paths
95 95 _cachedfiles = set()
96 96
97 97
98 98 class _basefilecache(scmutil.filecache):
99 99 """All filecache usage on repo are done for logic that should be unfiltered
100 100 """
101 101
102 102 def __get__(self, repo, type=None):
103 103 if repo is None:
104 104 return self
105 105 # proxy to unfiltered __dict__ since filtered repo has no entry
106 106 unfi = repo.unfiltered()
107 107 try:
108 108 return unfi.__dict__[self.sname]
109 109 except KeyError:
110 110 pass
111 111 return super(_basefilecache, self).__get__(unfi, type)
112 112
113 113 def set(self, repo, value):
114 114 return super(_basefilecache, self).set(repo.unfiltered(), value)
115 115
116 116
117 117 class repofilecache(_basefilecache):
118 118 """filecache for files in .hg but outside of .hg/store"""
119 119
120 120 def __init__(self, *paths):
121 121 super(repofilecache, self).__init__(*paths)
122 122 for path in paths:
123 123 _cachedfiles.add((path, b'plain'))
124 124
125 125 def join(self, obj, fname):
126 126 return obj.vfs.join(fname)
127 127
128 128
129 129 class storecache(_basefilecache):
130 130 """filecache for files in the store"""
131 131
132 132 def __init__(self, *paths):
133 133 super(storecache, self).__init__(*paths)
134 134 for path in paths:
135 135 _cachedfiles.add((path, b''))
136 136
137 137 def join(self, obj, fname):
138 138 return obj.sjoin(fname)
139 139
140 140
141 141 class mixedrepostorecache(_basefilecache):
142 142 """filecache for a mix files in .hg/store and outside"""
143 143
144 144 def __init__(self, *pathsandlocations):
145 145 # scmutil.filecache only uses the path for passing back into our
146 146 # join(), so we can safely pass a list of paths and locations
147 147 super(mixedrepostorecache, self).__init__(*pathsandlocations)
148 148 _cachedfiles.update(pathsandlocations)
149 149
150 150 def join(self, obj, fnameandlocation):
151 151 fname, location = fnameandlocation
152 152 if location == b'plain':
153 153 return obj.vfs.join(fname)
154 154 else:
155 155 if location != b'':
156 156 raise error.ProgrammingError(
157 157 b'unexpected location: %s' % location
158 158 )
159 159 return obj.sjoin(fname)
160 160
161 161
162 162 def isfilecached(repo, name):
163 163 """check if a repo has already cached "name" filecache-ed property
164 164
165 165 This returns (cachedobj-or-None, iscached) tuple.
166 166 """
167 167 cacheentry = repo.unfiltered()._filecache.get(name, None)
168 168 if not cacheentry:
169 169 return None, False
170 170 return cacheentry.obj, True
171 171
172 172
173 173 class unfilteredpropertycache(util.propertycache):
174 174 """propertycache that apply to unfiltered repo only"""
175 175
176 176 def __get__(self, repo, type=None):
177 177 unfi = repo.unfiltered()
178 178 if unfi is repo:
179 179 return super(unfilteredpropertycache, self).__get__(unfi)
180 180 return getattr(unfi, self.name)
181 181
182 182
183 183 class filteredpropertycache(util.propertycache):
184 184 """propertycache that must take filtering in account"""
185 185
186 186 def cachevalue(self, obj, value):
187 187 object.__setattr__(obj, self.name, value)
188 188
189 189
190 190 def hasunfilteredcache(repo, name):
191 191 """check if a repo has an unfilteredpropertycache value for <name>"""
192 192 return name in vars(repo.unfiltered())
193 193
194 194
195 195 def unfilteredmethod(orig):
196 196 """decorate method that always need to be run on unfiltered version"""
197 197
198 198 @functools.wraps(orig)
199 199 def wrapper(repo, *args, **kwargs):
200 200 return orig(repo.unfiltered(), *args, **kwargs)
201 201
202 202 return wrapper
203 203
204 204
205 205 moderncaps = {
206 206 b'lookup',
207 207 b'branchmap',
208 208 b'pushkey',
209 209 b'known',
210 210 b'getbundle',
211 211 b'unbundle',
212 212 }
213 213 legacycaps = moderncaps.union({b'changegroupsubset'})
214 214
215 215
216 216 @interfaceutil.implementer(repository.ipeercommandexecutor)
217 217 class localcommandexecutor(object):
218 218 def __init__(self, peer):
219 219 self._peer = peer
220 220 self._sent = False
221 221 self._closed = False
222 222
223 223 def __enter__(self):
224 224 return self
225 225
226 226 def __exit__(self, exctype, excvalue, exctb):
227 227 self.close()
228 228
229 229 def callcommand(self, command, args):
230 230 if self._sent:
231 231 raise error.ProgrammingError(
232 232 b'callcommand() cannot be used after sendcommands()'
233 233 )
234 234
235 235 if self._closed:
236 236 raise error.ProgrammingError(
237 237 b'callcommand() cannot be used after close()'
238 238 )
239 239
240 240 # We don't need to support anything fancy. Just call the named
241 241 # method on the peer and return a resolved future.
242 242 fn = getattr(self._peer, pycompat.sysstr(command))
243 243
244 244 f = pycompat.futures.Future()
245 245
246 246 try:
247 247 result = fn(**pycompat.strkwargs(args))
248 248 except Exception:
249 249 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
250 250 else:
251 251 f.set_result(result)
252 252
253 253 return f
254 254
255 255 def sendcommands(self):
256 256 self._sent = True
257 257
258 258 def close(self):
259 259 self._closed = True
260 260
261 261
262 262 @interfaceutil.implementer(repository.ipeercommands)
263 263 class localpeer(repository.peer):
264 264 '''peer for a local repo; reflects only the most recent API'''
265 265
266 266 def __init__(self, repo, caps=None):
267 267 super(localpeer, self).__init__()
268 268
269 269 if caps is None:
270 270 caps = moderncaps.copy()
271 271 self._repo = repo.filtered(b'served')
272 272 self.ui = repo.ui
273 273 self._caps = repo._restrictcapabilities(caps)
274 274
275 275 # Begin of _basepeer interface.
276 276
277 277 def url(self):
278 278 return self._repo.url()
279 279
280 280 def local(self):
281 281 return self._repo
282 282
283 283 def peer(self):
284 284 return self
285 285
286 286 def canpush(self):
287 287 return True
288 288
289 289 def close(self):
290 290 self._repo.close()
291 291
292 292 # End of _basepeer interface.
293 293
294 294 # Begin of _basewirecommands interface.
295 295
296 296 def branchmap(self):
297 297 return self._repo.branchmap()
298 298
299 299 def capabilities(self):
300 300 return self._caps
301 301
302 302 def clonebundles(self):
303 303 return self._repo.tryread(bundlecaches.CB_MANIFEST_FILE)
304 304
305 305 def debugwireargs(self, one, two, three=None, four=None, five=None):
306 306 """Used to test argument passing over the wire"""
307 307 return b"%s %s %s %s %s" % (
308 308 one,
309 309 two,
310 310 pycompat.bytestr(three),
311 311 pycompat.bytestr(four),
312 312 pycompat.bytestr(five),
313 313 )
314 314
315 315 def getbundle(
316 316 self, source, heads=None, common=None, bundlecaps=None, **kwargs
317 317 ):
318 318 chunks = exchange.getbundlechunks(
319 319 self._repo,
320 320 source,
321 321 heads=heads,
322 322 common=common,
323 323 bundlecaps=bundlecaps,
324 324 **kwargs
325 325 )[1]
326 326 cb = util.chunkbuffer(chunks)
327 327
328 328 if exchange.bundle2requested(bundlecaps):
329 329 # When requesting a bundle2, getbundle returns a stream to make the
330 330 # wire level function happier. We need to build a proper object
331 331 # from it in local peer.
332 332 return bundle2.getunbundler(self.ui, cb)
333 333 else:
334 334 return changegroup.getunbundler(b'01', cb, None)
335 335
336 336 def heads(self):
337 337 return self._repo.heads()
338 338
339 339 def known(self, nodes):
340 340 return self._repo.known(nodes)
341 341
342 342 def listkeys(self, namespace):
343 343 return self._repo.listkeys(namespace)
344 344
345 345 def lookup(self, key):
346 346 return self._repo.lookup(key)
347 347
348 348 def pushkey(self, namespace, key, old, new):
349 349 return self._repo.pushkey(namespace, key, old, new)
350 350
351 351 def stream_out(self):
352 352 raise error.Abort(_(b'cannot perform stream clone against local peer'))
353 353
354 354 def unbundle(self, bundle, heads, url):
355 355 """apply a bundle on a repo
356 356
357 357 This function handles the repo locking itself."""
358 358 try:
359 359 try:
360 360 bundle = exchange.readbundle(self.ui, bundle, None)
361 361 ret = exchange.unbundle(self._repo, bundle, heads, b'push', url)
362 362 if util.safehasattr(ret, b'getchunks'):
363 363 # This is a bundle20 object, turn it into an unbundler.
364 364 # This little dance should be dropped eventually when the
365 365 # API is finally improved.
366 366 stream = util.chunkbuffer(ret.getchunks())
367 367 ret = bundle2.getunbundler(self.ui, stream)
368 368 return ret
369 369 except Exception as exc:
370 370 # If the exception contains output salvaged from a bundle2
371 371 # reply, we need to make sure it is printed before continuing
372 372 # to fail. So we build a bundle2 with such output and consume
373 373 # it directly.
374 374 #
375 375 # This is not very elegant but allows a "simple" solution for
376 376 # issue4594
377 377 output = getattr(exc, '_bundle2salvagedoutput', ())
378 378 if output:
379 379 bundler = bundle2.bundle20(self._repo.ui)
380 380 for out in output:
381 381 bundler.addpart(out)
382 382 stream = util.chunkbuffer(bundler.getchunks())
383 383 b = bundle2.getunbundler(self.ui, stream)
384 384 bundle2.processbundle(self._repo, b)
385 385 raise
386 386 except error.PushRaced as exc:
387 387 raise error.ResponseError(
388 388 _(b'push failed:'), stringutil.forcebytestr(exc)
389 389 )
390 390
391 391 # End of _basewirecommands interface.
392 392
393 393 # Begin of peer interface.
394 394
395 395 def commandexecutor(self):
396 396 return localcommandexecutor(self)
397 397
398 398 # End of peer interface.
399 399
400 400
401 401 @interfaceutil.implementer(repository.ipeerlegacycommands)
402 402 class locallegacypeer(localpeer):
403 403 '''peer extension which implements legacy methods too; used for tests with
404 404 restricted capabilities'''
405 405
406 406 def __init__(self, repo):
407 407 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
408 408
409 409 # Begin of baselegacywirecommands interface.
410 410
411 411 def between(self, pairs):
412 412 return self._repo.between(pairs)
413 413
414 414 def branches(self, nodes):
415 415 return self._repo.branches(nodes)
416 416
417 417 def changegroup(self, nodes, source):
418 418 outgoing = discovery.outgoing(
419 419 self._repo, missingroots=nodes, ancestorsof=self._repo.heads()
420 420 )
421 421 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
422 422
423 423 def changegroupsubset(self, bases, heads, source):
424 424 outgoing = discovery.outgoing(
425 425 self._repo, missingroots=bases, ancestorsof=heads
426 426 )
427 427 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
428 428
429 429 # End of baselegacywirecommands interface.
430 430
431 431
432 432 # Functions receiving (ui, features) that extensions can register to impact
433 433 # the ability to load repositories with custom requirements. Only
434 434 # functions defined in loaded extensions are called.
435 435 #
436 436 # The function receives a set of requirement strings that the repository
437 437 # is capable of opening. Functions will typically add elements to the
438 438 # set to reflect that the extension knows how to handle that requirements.
439 439 featuresetupfuncs = set()
440 440
441 441
442 442 def _getsharedvfs(hgvfs, requirements):
443 443 """ returns the vfs object pointing to root of shared source
444 444 repo for a shared repository
445 445
446 446 hgvfs is vfs pointing at .hg/ of current repo (shared one)
447 447 requirements is a set of requirements of current repo (shared one)
448 448 """
449 449 # The ``shared`` or ``relshared`` requirements indicate the
450 450 # store lives in the path contained in the ``.hg/sharedpath`` file.
451 451 # This is an absolute path for ``shared`` and relative to
452 452 # ``.hg/`` for ``relshared``.
453 453 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
454 454 if requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements:
455 455 sharedpath = hgvfs.join(sharedpath)
456 456
457 457 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
458 458
459 459 if not sharedvfs.exists():
460 460 raise error.RepoError(
461 461 _(b'.hg/sharedpath points to nonexistent directory %s')
462 462 % sharedvfs.base
463 463 )
464 464 return sharedvfs
465 465
466 466
467 467 def _readrequires(vfs, allowmissing):
468 468 """ reads the require file present at root of this vfs
469 469 and return a set of requirements
470 470
471 471 If allowmissing is True, we suppress ENOENT if raised"""
472 472 # requires file contains a newline-delimited list of
473 473 # features/capabilities the opener (us) must have in order to use
474 474 # the repository. This file was introduced in Mercurial 0.9.2,
475 475 # which means very old repositories may not have one. We assume
476 476 # a missing file translates to no requirements.
477 477 try:
478 478 requirements = set(vfs.read(b'requires').splitlines())
479 479 except IOError as e:
480 480 if not (allowmissing and e.errno == errno.ENOENT):
481 481 raise
482 482 requirements = set()
483 483 return requirements
484 484
485 485
486 486 def makelocalrepository(baseui, path, intents=None):
487 487 """Create a local repository object.
488 488
489 489 Given arguments needed to construct a local repository, this function
490 490 performs various early repository loading functionality (such as
491 491 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
492 492 the repository can be opened, derives a type suitable for representing
493 493 that repository, and returns an instance of it.
494 494
495 495 The returned object conforms to the ``repository.completelocalrepository``
496 496 interface.
497 497
498 498 The repository type is derived by calling a series of factory functions
499 499 for each aspect/interface of the final repository. These are defined by
500 500 ``REPO_INTERFACES``.
501 501
502 502 Each factory function is called to produce a type implementing a specific
503 503 interface. The cumulative list of returned types will be combined into a
504 504 new type and that type will be instantiated to represent the local
505 505 repository.
506 506
507 507 The factory functions each receive various state that may be consulted
508 508 as part of deriving a type.
509 509
510 510 Extensions should wrap these factory functions to customize repository type
511 511 creation. Note that an extension's wrapped function may be called even if
512 512 that extension is not loaded for the repo being constructed. Extensions
513 513 should check if their ``__name__`` appears in the
514 514 ``extensionmodulenames`` set passed to the factory function and no-op if
515 515 not.
516 516 """
517 517 ui = baseui.copy()
518 518 # Prevent copying repo configuration.
519 519 ui.copy = baseui.copy
520 520
521 521 # Working directory VFS rooted at repository root.
522 522 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
523 523
524 524 # Main VFS for .hg/ directory.
525 525 hgpath = wdirvfs.join(b'.hg')
526 526 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
527 527 # Whether this repository is shared one or not
528 528 shared = False
529 529 # If this repository is shared, vfs pointing to shared repo
530 530 sharedvfs = None
531 531
532 532 # The .hg/ path should exist and should be a directory. All other
533 533 # cases are errors.
534 534 if not hgvfs.isdir():
535 535 try:
536 536 hgvfs.stat()
537 537 except OSError as e:
538 538 if e.errno != errno.ENOENT:
539 539 raise
540 540 except ValueError as e:
541 541 # Can be raised on Python 3.8 when path is invalid.
542 542 raise error.Abort(
543 543 _(b'invalid path %s: %s') % (path, pycompat.bytestr(e))
544 544 )
545 545
546 546 raise error.RepoError(_(b'repository %s not found') % path)
547 547
548 548 requirements = _readrequires(hgvfs, True)
549 549 shared = (
550 550 requirementsmod.SHARED_REQUIREMENT in requirements
551 551 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
552 552 )
553 553 if shared:
554 554 sharedvfs = _getsharedvfs(hgvfs, requirements)
555 555
556 556 # if .hg/requires contains the sharesafe requirement, it means
557 557 # there exists a `.hg/store/requires` too and we should read it
558 558 # NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
559 559 # is present. We never write SHARESAFE_REQUIREMENT for a repo if store
560 560 # is not present, refer checkrequirementscompat() for that
561 561 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
562 562 if shared:
563 563 # This is a shared repo
564 564 storevfs = vfsmod.vfs(sharedvfs.join(b'store'))
565 565 else:
566 566 storevfs = vfsmod.vfs(hgvfs.join(b'store'))
567 567
568 568 requirements |= _readrequires(storevfs, False)
569 569
570 570 # The .hg/hgrc file may load extensions or contain config options
571 571 # that influence repository construction. Attempt to load it and
572 572 # process any new extensions that it may have pulled in.
573 573 if loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs):
574 574 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
575 575 extensions.loadall(ui)
576 576 extensions.populateui(ui)
577 577
578 578 # Set of module names of extensions loaded for this repository.
579 579 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
580 580
581 581 supportedrequirements = gathersupportedrequirements(ui)
582 582
583 583 # We first validate the requirements are known.
584 584 ensurerequirementsrecognized(requirements, supportedrequirements)
585 585
586 586 # Then we validate that the known set is reasonable to use together.
587 587 ensurerequirementscompatible(ui, requirements)
588 588
589 589 # TODO there are unhandled edge cases related to opening repositories with
590 590 # shared storage. If storage is shared, we should also test for requirements
591 591 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
592 592 # that repo, as that repo may load extensions needed to open it. This is a
593 593 # bit complicated because we don't want the other hgrc to overwrite settings
594 594 # in this hgrc.
595 595 #
596 596 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
597 597 # file when sharing repos. But if a requirement is added after the share is
598 598 # performed, thereby introducing a new requirement for the opener, we may
599 599 # will not see that and could encounter a run-time error interacting with
600 600 # that shared store since it has an unknown-to-us requirement.
601 601
602 602 # At this point, we know we should be capable of opening the repository.
603 603 # Now get on with doing that.
604 604
605 605 features = set()
606 606
607 607 # The "store" part of the repository holds versioned data. How it is
608 608 # accessed is determined by various requirements. If `shared` or
609 609 # `relshared` requirements are present, this indicates current repository
610 610 # is a share and store exists in path mentioned in `.hg/sharedpath`
611 611 if shared:
612 612 storebasepath = sharedvfs.base
613 613 cachepath = sharedvfs.join(b'cache')
614 614 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
615 615 else:
616 616 storebasepath = hgvfs.base
617 617 cachepath = hgvfs.join(b'cache')
618 618 wcachepath = hgvfs.join(b'wcache')
619 619
620 620 # The store has changed over time and the exact layout is dictated by
621 621 # requirements. The store interface abstracts differences across all
622 622 # of them.
623 623 store = makestore(
624 624 requirements,
625 625 storebasepath,
626 626 lambda base: vfsmod.vfs(base, cacheaudited=True),
627 627 )
628 628 hgvfs.createmode = store.createmode
629 629
630 630 storevfs = store.vfs
631 631 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
632 632
633 633 # The cache vfs is used to manage cache files.
634 634 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
635 635 cachevfs.createmode = store.createmode
636 636 # The cache vfs is used to manage cache files related to the working copy
637 637 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
638 638 wcachevfs.createmode = store.createmode
639 639
640 640 # Now resolve the type for the repository object. We do this by repeatedly
641 641 # calling a factory function to produces types for specific aspects of the
642 642 # repo's operation. The aggregate returned types are used as base classes
643 643 # for a dynamically-derived type, which will represent our new repository.
644 644
645 645 bases = []
646 646 extrastate = {}
647 647
648 648 for iface, fn in REPO_INTERFACES:
649 649 # We pass all potentially useful state to give extensions tons of
650 650 # flexibility.
651 651 typ = fn()(
652 652 ui=ui,
653 653 intents=intents,
654 654 requirements=requirements,
655 655 features=features,
656 656 wdirvfs=wdirvfs,
657 657 hgvfs=hgvfs,
658 658 store=store,
659 659 storevfs=storevfs,
660 660 storeoptions=storevfs.options,
661 661 cachevfs=cachevfs,
662 662 wcachevfs=wcachevfs,
663 663 extensionmodulenames=extensionmodulenames,
664 664 extrastate=extrastate,
665 665 baseclasses=bases,
666 666 )
667 667
668 668 if not isinstance(typ, type):
669 669 raise error.ProgrammingError(
670 670 b'unable to construct type for %s' % iface
671 671 )
672 672
673 673 bases.append(typ)
674 674
675 675 # type() allows you to use characters in type names that wouldn't be
676 676 # recognized as Python symbols in source code. We abuse that to add
677 677 # rich information about our constructed repo.
678 678 name = pycompat.sysstr(
679 679 b'derivedrepo:%s<%s>' % (wdirvfs.base, b','.join(sorted(requirements)))
680 680 )
681 681
682 682 cls = type(name, tuple(bases), {})
683 683
684 684 return cls(
685 685 baseui=baseui,
686 686 ui=ui,
687 687 origroot=path,
688 688 wdirvfs=wdirvfs,
689 689 hgvfs=hgvfs,
690 690 requirements=requirements,
691 691 supportedrequirements=supportedrequirements,
692 692 sharedpath=storebasepath,
693 693 store=store,
694 694 cachevfs=cachevfs,
695 695 wcachevfs=wcachevfs,
696 696 features=features,
697 697 intents=intents,
698 698 )
699 699
700 700
701 701 def loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs=None):
702 702 """Load hgrc files/content into a ui instance.
703 703
704 704 This is called during repository opening to load any additional
705 705 config files or settings relevant to the current repository.
706 706
707 707 Returns a bool indicating whether any additional configs were loaded.
708 708
709 709 Extensions should monkeypatch this function to modify how per-repo
710 710 configs are loaded. For example, an extension may wish to pull in
711 711 configs from alternate files or sources.
712 712
713 713 sharedvfs is vfs object pointing to source repo if the current one is a
714 714 shared one
715 715 """
716 716 if not rcutil.use_repo_hgrc():
717 717 return False
718 718
719 719 ret = False
720 720 # first load config from shared source if we has to
721 721 if requirementsmod.SHARESAFE_REQUIREMENT in requirements and sharedvfs:
722 722 try:
723 723 ui.readconfig(sharedvfs.join(b'hgrc'), root=sharedvfs.base)
724 724 ret = True
725 725 except IOError:
726 726 pass
727 727
728 728 try:
729 729 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
730 730 ret = True
731 731 except IOError:
732 732 pass
733 733
734 734 try:
735 735 ui.readconfig(hgvfs.join(b'hgrc-not-shared'), root=wdirvfs.base)
736 736 ret = True
737 737 except IOError:
738 738 pass
739 739
740 740 return ret
741 741
742 742
743 743 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
744 744 """Perform additional actions after .hg/hgrc is loaded.
745 745
746 746 This function is called during repository loading immediately after
747 747 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
748 748
749 749 The function can be used to validate configs, automatically add
750 750 options (including extensions) based on requirements, etc.
751 751 """
752 752
753 753 # Map of requirements to list of extensions to load automatically when
754 754 # requirement is present.
755 755 autoextensions = {
756 756 b'git': [b'git'],
757 757 b'largefiles': [b'largefiles'],
758 758 b'lfs': [b'lfs'],
759 759 }
760 760
761 761 for requirement, names in sorted(autoextensions.items()):
762 762 if requirement not in requirements:
763 763 continue
764 764
765 765 for name in names:
766 766 if not ui.hasconfig(b'extensions', name):
767 767 ui.setconfig(b'extensions', name, b'', source=b'autoload')
768 768
769 769
770 770 def gathersupportedrequirements(ui):
771 771 """Determine the complete set of recognized requirements."""
772 772 # Start with all requirements supported by this file.
773 773 supported = set(localrepository._basesupported)
774 774
775 775 # Execute ``featuresetupfuncs`` entries if they belong to an extension
776 776 # relevant to this ui instance.
777 777 modules = {m.__name__ for n, m in extensions.extensions(ui)}
778 778
779 779 for fn in featuresetupfuncs:
780 780 if fn.__module__ in modules:
781 781 fn(ui, supported)
782 782
783 783 # Add derived requirements from registered compression engines.
784 784 for name in util.compengines:
785 785 engine = util.compengines[name]
786 786 if engine.available() and engine.revlogheader():
787 787 supported.add(b'exp-compression-%s' % name)
788 788 if engine.name() == b'zstd':
789 789 supported.add(b'revlog-compression-zstd')
790 790
791 791 return supported
792 792
793 793
794 794 def ensurerequirementsrecognized(requirements, supported):
795 795 """Validate that a set of local requirements is recognized.
796 796
797 797 Receives a set of requirements. Raises an ``error.RepoError`` if there
798 798 exists any requirement in that set that currently loaded code doesn't
799 799 recognize.
800 800
801 801 Returns a set of supported requirements.
802 802 """
803 803 missing = set()
804 804
805 805 for requirement in requirements:
806 806 if requirement in supported:
807 807 continue
808 808
809 809 if not requirement or not requirement[0:1].isalnum():
810 810 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
811 811
812 812 missing.add(requirement)
813 813
814 814 if missing:
815 815 raise error.RequirementError(
816 816 _(b'repository requires features unknown to this Mercurial: %s')
817 817 % b' '.join(sorted(missing)),
818 818 hint=_(
819 819 b'see https://mercurial-scm.org/wiki/MissingRequirement '
820 820 b'for more information'
821 821 ),
822 822 )
823 823
824 824
825 825 def ensurerequirementscompatible(ui, requirements):
826 826 """Validates that a set of recognized requirements is mutually compatible.
827 827
828 828 Some requirements may not be compatible with others or require
829 829 config options that aren't enabled. This function is called during
830 830 repository opening to ensure that the set of requirements needed
831 831 to open a repository is sane and compatible with config options.
832 832
833 833 Extensions can monkeypatch this function to perform additional
834 834 checking.
835 835
836 836 ``error.RepoError`` should be raised on failure.
837 837 """
838 838 if (
839 839 requirementsmod.SPARSE_REQUIREMENT in requirements
840 840 and not sparse.enabled
841 841 ):
842 842 raise error.RepoError(
843 843 _(
844 844 b'repository is using sparse feature but '
845 845 b'sparse is not enabled; enable the '
846 846 b'"sparse" extensions to access'
847 847 )
848 848 )
849 849
850 850
851 851 def makestore(requirements, path, vfstype):
852 852 """Construct a storage object for a repository."""
853 853 if b'store' in requirements:
854 854 if b'fncache' in requirements:
855 855 return storemod.fncachestore(
856 856 path, vfstype, b'dotencode' in requirements
857 857 )
858 858
859 859 return storemod.encodedstore(path, vfstype)
860 860
861 861 return storemod.basicstore(path, vfstype)
862 862
863 863
864 864 def resolvestorevfsoptions(ui, requirements, features):
865 865 """Resolve the options to pass to the store vfs opener.
866 866
867 867 The returned dict is used to influence behavior of the storage layer.
868 868 """
869 869 options = {}
870 870
871 871 if requirementsmod.TREEMANIFEST_REQUIREMENT in requirements:
872 872 options[b'treemanifest'] = True
873 873
874 874 # experimental config: format.manifestcachesize
875 875 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
876 876 if manifestcachesize is not None:
877 877 options[b'manifestcachesize'] = manifestcachesize
878 878
879 879 # In the absence of another requirement superseding a revlog-related
880 880 # requirement, we have to assume the repo is using revlog version 0.
881 881 # This revlog format is super old and we don't bother trying to parse
882 882 # opener options for it because those options wouldn't do anything
883 883 # meaningful on such old repos.
884 884 if (
885 885 b'revlogv1' in requirements
886 886 or requirementsmod.REVLOGV2_REQUIREMENT in requirements
887 887 ):
888 888 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
889 889 else: # explicitly mark repo as using revlogv0
890 890 options[b'revlogv0'] = True
891 891
892 892 if requirementsmod.COPIESSDC_REQUIREMENT in requirements:
893 893 options[b'copies-storage'] = b'changeset-sidedata'
894 894 else:
895 895 writecopiesto = ui.config(b'experimental', b'copies.write-to')
896 896 copiesextramode = (b'changeset-only', b'compatibility')
897 897 if writecopiesto in copiesextramode:
898 898 options[b'copies-storage'] = b'extra'
899 899
900 900 return options
901 901
902 902
903 903 def resolverevlogstorevfsoptions(ui, requirements, features):
904 904 """Resolve opener options specific to revlogs."""
905 905
906 906 options = {}
907 907 options[b'flagprocessors'] = {}
908 908
909 909 if b'revlogv1' in requirements:
910 910 options[b'revlogv1'] = True
911 911 if requirementsmod.REVLOGV2_REQUIREMENT in requirements:
912 912 options[b'revlogv2'] = True
913 913
914 914 if b'generaldelta' in requirements:
915 915 options[b'generaldelta'] = True
916 916
917 917 # experimental config: format.chunkcachesize
918 918 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
919 919 if chunkcachesize is not None:
920 920 options[b'chunkcachesize'] = chunkcachesize
921 921
922 922 deltabothparents = ui.configbool(
923 923 b'storage', b'revlog.optimize-delta-parent-choice'
924 924 )
925 925 options[b'deltabothparents'] = deltabothparents
926 926
927 927 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
928 928 lazydeltabase = False
929 929 if lazydelta:
930 930 lazydeltabase = ui.configbool(
931 931 b'storage', b'revlog.reuse-external-delta-parent'
932 932 )
933 933 if lazydeltabase is None:
934 934 lazydeltabase = not scmutil.gddeltaconfig(ui)
935 935 options[b'lazydelta'] = lazydelta
936 936 options[b'lazydeltabase'] = lazydeltabase
937 937
938 938 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
939 939 if 0 <= chainspan:
940 940 options[b'maxdeltachainspan'] = chainspan
941 941
942 942 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
943 943 if mmapindexthreshold is not None:
944 944 options[b'mmapindexthreshold'] = mmapindexthreshold
945 945
946 946 withsparseread = ui.configbool(b'experimental', b'sparse-read')
947 947 srdensitythres = float(
948 948 ui.config(b'experimental', b'sparse-read.density-threshold')
949 949 )
950 950 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
951 951 options[b'with-sparse-read'] = withsparseread
952 952 options[b'sparse-read-density-threshold'] = srdensitythres
953 953 options[b'sparse-read-min-gap-size'] = srmingapsize
954 954
955 955 sparserevlog = requirementsmod.SPARSEREVLOG_REQUIREMENT in requirements
956 956 options[b'sparse-revlog'] = sparserevlog
957 957 if sparserevlog:
958 958 options[b'generaldelta'] = True
959 959
960 960 sidedata = requirementsmod.SIDEDATA_REQUIREMENT in requirements
961 961 options[b'side-data'] = sidedata
962 962
963 963 maxchainlen = None
964 964 if sparserevlog:
965 965 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
966 966 # experimental config: format.maxchainlen
967 967 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
968 968 if maxchainlen is not None:
969 969 options[b'maxchainlen'] = maxchainlen
970 970
971 971 for r in requirements:
972 972 # we allow multiple compression engine requirement to co-exist because
973 973 # strickly speaking, revlog seems to support mixed compression style.
974 974 #
975 975 # The compression used for new entries will be "the last one"
976 976 prefix = r.startswith
977 977 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
978 978 options[b'compengine'] = r.split(b'-', 2)[2]
979 979
980 980 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
981 981 if options[b'zlib.level'] is not None:
982 982 if not (0 <= options[b'zlib.level'] <= 9):
983 983 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
984 984 raise error.Abort(msg % options[b'zlib.level'])
985 985 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
986 986 if options[b'zstd.level'] is not None:
987 987 if not (0 <= options[b'zstd.level'] <= 22):
988 988 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
989 989 raise error.Abort(msg % options[b'zstd.level'])
990 990
991 991 if requirementsmod.NARROW_REQUIREMENT in requirements:
992 992 options[b'enableellipsis'] = True
993 993
994 994 if ui.configbool(b'experimental', b'rust.index'):
995 995 options[b'rust.index'] = True
996 996 if requirementsmod.NODEMAP_REQUIREMENT in requirements:
997 997 options[b'persistent-nodemap'] = True
998 998 if ui.configbool(b'storage', b'revlog.nodemap.mmap'):
999 999 options[b'persistent-nodemap.mmap'] = True
1000 1000 epnm = ui.config(b'storage', b'revlog.nodemap.mode')
1001 1001 options[b'persistent-nodemap.mode'] = epnm
1002 1002 if ui.configbool(b'devel', b'persistent-nodemap'):
1003 1003 options[b'devel-force-nodemap'] = True
1004 1004
1005 1005 return options
1006 1006
1007 1007
1008 1008 def makemain(**kwargs):
1009 1009 """Produce a type conforming to ``ilocalrepositorymain``."""
1010 1010 return localrepository
1011 1011
1012 1012
1013 1013 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1014 1014 class revlogfilestorage(object):
1015 1015 """File storage when using revlogs."""
1016 1016
1017 1017 def file(self, path):
1018 1018 if path[0] == b'/':
1019 1019 path = path[1:]
1020 1020
1021 1021 return filelog.filelog(self.svfs, path)
1022 1022
1023 1023
1024 1024 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1025 1025 class revlognarrowfilestorage(object):
1026 1026 """File storage when using revlogs and narrow files."""
1027 1027
1028 1028 def file(self, path):
1029 1029 if path[0] == b'/':
1030 1030 path = path[1:]
1031 1031
1032 1032 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
1033 1033
1034 1034
1035 1035 def makefilestorage(requirements, features, **kwargs):
1036 1036 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1037 1037 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
1038 1038 features.add(repository.REPO_FEATURE_STREAM_CLONE)
1039 1039
1040 1040 if requirementsmod.NARROW_REQUIREMENT in requirements:
1041 1041 return revlognarrowfilestorage
1042 1042 else:
1043 1043 return revlogfilestorage
1044 1044
1045 1045
1046 1046 # List of repository interfaces and factory functions for them. Each
1047 1047 # will be called in order during ``makelocalrepository()`` to iteratively
1048 1048 # derive the final type for a local repository instance. We capture the
1049 1049 # function as a lambda so we don't hold a reference and the module-level
1050 1050 # functions can be wrapped.
1051 1051 REPO_INTERFACES = [
1052 1052 (repository.ilocalrepositorymain, lambda: makemain),
1053 1053 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
1054 1054 ]
1055 1055
1056 1056
1057 1057 @interfaceutil.implementer(repository.ilocalrepositorymain)
1058 1058 class localrepository(object):
1059 1059 """Main class for representing local repositories.
1060 1060
1061 1061 All local repositories are instances of this class.
1062 1062
1063 1063 Constructed on its own, instances of this class are not usable as
1064 1064 repository objects. To obtain a usable repository object, call
1065 1065 ``hg.repository()``, ``localrepo.instance()``, or
1066 1066 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1067 1067 ``instance()`` adds support for creating new repositories.
1068 1068 ``hg.repository()`` adds more extension integration, including calling
1069 1069 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1070 1070 used.
1071 1071 """
1072 1072
1073 1073 # obsolete experimental requirements:
1074 1074 # - manifestv2: An experimental new manifest format that allowed
1075 1075 # for stem compression of long paths. Experiment ended up not
1076 1076 # being successful (repository sizes went up due to worse delta
1077 1077 # chains), and the code was deleted in 4.6.
1078 1078 supportedformats = {
1079 1079 b'revlogv1',
1080 1080 b'generaldelta',
1081 1081 requirementsmod.TREEMANIFEST_REQUIREMENT,
1082 1082 requirementsmod.COPIESSDC_REQUIREMENT,
1083 1083 requirementsmod.REVLOGV2_REQUIREMENT,
1084 1084 requirementsmod.SIDEDATA_REQUIREMENT,
1085 1085 requirementsmod.SPARSEREVLOG_REQUIREMENT,
1086 1086 requirementsmod.NODEMAP_REQUIREMENT,
1087 1087 bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
1088 1088 requirementsmod.SHARESAFE_REQUIREMENT,
1089 1089 }
1090 1090 _basesupported = supportedformats | {
1091 1091 b'store',
1092 1092 b'fncache',
1093 1093 requirementsmod.SHARED_REQUIREMENT,
1094 1094 requirementsmod.RELATIVE_SHARED_REQUIREMENT,
1095 1095 b'dotencode',
1096 1096 requirementsmod.SPARSE_REQUIREMENT,
1097 1097 requirementsmod.INTERNAL_PHASE_REQUIREMENT,
1098 1098 }
1099 1099
1100 1100 # list of prefix for file which can be written without 'wlock'
1101 1101 # Extensions should extend this list when needed
1102 1102 _wlockfreeprefix = {
1103 1103 # We migh consider requiring 'wlock' for the next
1104 1104 # two, but pretty much all the existing code assume
1105 1105 # wlock is not needed so we keep them excluded for
1106 1106 # now.
1107 1107 b'hgrc',
1108 1108 b'requires',
1109 1109 # XXX cache is a complicatged business someone
1110 1110 # should investigate this in depth at some point
1111 1111 b'cache/',
1112 1112 # XXX shouldn't be dirstate covered by the wlock?
1113 1113 b'dirstate',
1114 1114 # XXX bisect was still a bit too messy at the time
1115 1115 # this changeset was introduced. Someone should fix
1116 1116 # the remainig bit and drop this line
1117 1117 b'bisect.state',
1118 1118 }
1119 1119
1120 1120 def __init__(
1121 1121 self,
1122 1122 baseui,
1123 1123 ui,
1124 1124 origroot,
1125 1125 wdirvfs,
1126 1126 hgvfs,
1127 1127 requirements,
1128 1128 supportedrequirements,
1129 1129 sharedpath,
1130 1130 store,
1131 1131 cachevfs,
1132 1132 wcachevfs,
1133 1133 features,
1134 1134 intents=None,
1135 1135 ):
1136 1136 """Create a new local repository instance.
1137 1137
1138 1138 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1139 1139 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1140 1140 object.
1141 1141
1142 1142 Arguments:
1143 1143
1144 1144 baseui
1145 1145 ``ui.ui`` instance that ``ui`` argument was based off of.
1146 1146
1147 1147 ui
1148 1148 ``ui.ui`` instance for use by the repository.
1149 1149
1150 1150 origroot
1151 1151 ``bytes`` path to working directory root of this repository.
1152 1152
1153 1153 wdirvfs
1154 1154 ``vfs.vfs`` rooted at the working directory.
1155 1155
1156 1156 hgvfs
1157 1157 ``vfs.vfs`` rooted at .hg/
1158 1158
1159 1159 requirements
1160 1160 ``set`` of bytestrings representing repository opening requirements.
1161 1161
1162 1162 supportedrequirements
1163 1163 ``set`` of bytestrings representing repository requirements that we
1164 1164 know how to open. May be a supetset of ``requirements``.
1165 1165
1166 1166 sharedpath
1167 1167 ``bytes`` Defining path to storage base directory. Points to a
1168 1168 ``.hg/`` directory somewhere.
1169 1169
1170 1170 store
1171 1171 ``store.basicstore`` (or derived) instance providing access to
1172 1172 versioned storage.
1173 1173
1174 1174 cachevfs
1175 1175 ``vfs.vfs`` used for cache files.
1176 1176
1177 1177 wcachevfs
1178 1178 ``vfs.vfs`` used for cache files related to the working copy.
1179 1179
1180 1180 features
1181 1181 ``set`` of bytestrings defining features/capabilities of this
1182 1182 instance.
1183 1183
1184 1184 intents
1185 1185 ``set`` of system strings indicating what this repo will be used
1186 1186 for.
1187 1187 """
1188 1188 self.baseui = baseui
1189 1189 self.ui = ui
1190 1190 self.origroot = origroot
1191 1191 # vfs rooted at working directory.
1192 1192 self.wvfs = wdirvfs
1193 1193 self.root = wdirvfs.base
1194 1194 # vfs rooted at .hg/. Used to access most non-store paths.
1195 1195 self.vfs = hgvfs
1196 1196 self.path = hgvfs.base
1197 1197 self.requirements = requirements
1198 1198 self.supported = supportedrequirements
1199 1199 self.sharedpath = sharedpath
1200 1200 self.store = store
1201 1201 self.cachevfs = cachevfs
1202 1202 self.wcachevfs = wcachevfs
1203 1203 self.features = features
1204 1204
1205 1205 self.filtername = None
1206 1206
1207 1207 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1208 1208 b'devel', b'check-locks'
1209 1209 ):
1210 1210 self.vfs.audit = self._getvfsward(self.vfs.audit)
1211 1211 # A list of callback to shape the phase if no data were found.
1212 1212 # Callback are in the form: func(repo, roots) --> processed root.
1213 1213 # This list it to be filled by extension during repo setup
1214 1214 self._phasedefaults = []
1215 1215
1216 1216 color.setup(self.ui)
1217 1217
1218 1218 self.spath = self.store.path
1219 1219 self.svfs = self.store.vfs
1220 1220 self.sjoin = self.store.join
1221 1221 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1222 1222 b'devel', b'check-locks'
1223 1223 ):
1224 1224 if util.safehasattr(self.svfs, b'vfs'): # this is filtervfs
1225 1225 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1226 1226 else: # standard vfs
1227 1227 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1228 1228
1229 1229 self._dirstatevalidatewarned = False
1230 1230
1231 1231 self._branchcaches = branchmap.BranchMapCache()
1232 1232 self._revbranchcache = None
1233 1233 self._filterpats = {}
1234 1234 self._datafilters = {}
1235 1235 self._transref = self._lockref = self._wlockref = None
1236 1236
1237 1237 # A cache for various files under .hg/ that tracks file changes,
1238 1238 # (used by the filecache decorator)
1239 1239 #
1240 1240 # Maps a property name to its util.filecacheentry
1241 1241 self._filecache = {}
1242 1242
1243 1243 # hold sets of revision to be filtered
1244 1244 # should be cleared when something might have changed the filter value:
1245 1245 # - new changesets,
1246 1246 # - phase change,
1247 1247 # - new obsolescence marker,
1248 1248 # - working directory parent change,
1249 1249 # - bookmark changes
1250 1250 self.filteredrevcache = {}
1251 1251
1252 1252 # post-dirstate-status hooks
1253 1253 self._postdsstatus = []
1254 1254
1255 1255 # generic mapping between names and nodes
1256 1256 self.names = namespaces.namespaces()
1257 1257
1258 1258 # Key to signature value.
1259 1259 self._sparsesignaturecache = {}
1260 1260 # Signature to cached matcher instance.
1261 1261 self._sparsematchercache = {}
1262 1262
1263 1263 self._extrafilterid = repoview.extrafilter(ui)
1264 1264
1265 1265 self.filecopiesmode = None
1266 1266 if requirementsmod.COPIESSDC_REQUIREMENT in self.requirements:
1267 1267 self.filecopiesmode = b'changeset-sidedata'
1268 1268
1269 1269 def _getvfsward(self, origfunc):
1270 1270 """build a ward for self.vfs"""
1271 1271 rref = weakref.ref(self)
1272 1272
1273 1273 def checkvfs(path, mode=None):
1274 1274 ret = origfunc(path, mode=mode)
1275 1275 repo = rref()
1276 1276 if (
1277 1277 repo is None
1278 1278 or not util.safehasattr(repo, b'_wlockref')
1279 1279 or not util.safehasattr(repo, b'_lockref')
1280 1280 ):
1281 1281 return
1282 1282 if mode in (None, b'r', b'rb'):
1283 1283 return
1284 1284 if path.startswith(repo.path):
1285 1285 # truncate name relative to the repository (.hg)
1286 1286 path = path[len(repo.path) + 1 :]
1287 1287 if path.startswith(b'cache/'):
1288 1288 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1289 1289 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1290 1290 # path prefixes covered by 'lock'
1291 1291 vfs_path_prefixes = (
1292 1292 b'journal.',
1293 1293 b'undo.',
1294 1294 b'strip-backup/',
1295 1295 b'cache/',
1296 1296 )
1297 1297 if any(path.startswith(prefix) for prefix in vfs_path_prefixes):
1298 1298 if repo._currentlock(repo._lockref) is None:
1299 1299 repo.ui.develwarn(
1300 1300 b'write with no lock: "%s"' % path,
1301 1301 stacklevel=3,
1302 1302 config=b'check-locks',
1303 1303 )
1304 1304 elif repo._currentlock(repo._wlockref) is None:
1305 1305 # rest of vfs files are covered by 'wlock'
1306 1306 #
1307 1307 # exclude special files
1308 1308 for prefix in self._wlockfreeprefix:
1309 1309 if path.startswith(prefix):
1310 1310 return
1311 1311 repo.ui.develwarn(
1312 1312 b'write with no wlock: "%s"' % path,
1313 1313 stacklevel=3,
1314 1314 config=b'check-locks',
1315 1315 )
1316 1316 return ret
1317 1317
1318 1318 return checkvfs
1319 1319
1320 1320 def _getsvfsward(self, origfunc):
1321 1321 """build a ward for self.svfs"""
1322 1322 rref = weakref.ref(self)
1323 1323
1324 1324 def checksvfs(path, mode=None):
1325 1325 ret = origfunc(path, mode=mode)
1326 1326 repo = rref()
1327 1327 if repo is None or not util.safehasattr(repo, b'_lockref'):
1328 1328 return
1329 1329 if mode in (None, b'r', b'rb'):
1330 1330 return
1331 1331 if path.startswith(repo.sharedpath):
1332 1332 # truncate name relative to the repository (.hg)
1333 1333 path = path[len(repo.sharedpath) + 1 :]
1334 1334 if repo._currentlock(repo._lockref) is None:
1335 1335 repo.ui.develwarn(
1336 1336 b'write with no lock: "%s"' % path, stacklevel=4
1337 1337 )
1338 1338 return ret
1339 1339
1340 1340 return checksvfs
1341 1341
1342 1342 def close(self):
1343 1343 self._writecaches()
1344 1344
1345 1345 def _writecaches(self):
1346 1346 if self._revbranchcache:
1347 1347 self._revbranchcache.write()
1348 1348
1349 1349 def _restrictcapabilities(self, caps):
1350 1350 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1351 1351 caps = set(caps)
1352 1352 capsblob = bundle2.encodecaps(
1353 1353 bundle2.getrepocaps(self, role=b'client')
1354 1354 )
1355 1355 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1356 1356 return caps
1357 1357
1358 1358 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1359 1359 # self -> auditor -> self._checknested -> self
1360 1360
1361 1361 @property
1362 1362 def auditor(self):
1363 1363 # This is only used by context.workingctx.match in order to
1364 1364 # detect files in subrepos.
1365 1365 return pathutil.pathauditor(self.root, callback=self._checknested)
1366 1366
1367 1367 @property
1368 1368 def nofsauditor(self):
1369 1369 # This is only used by context.basectx.match in order to detect
1370 1370 # files in subrepos.
1371 1371 return pathutil.pathauditor(
1372 1372 self.root, callback=self._checknested, realfs=False, cached=True
1373 1373 )
1374 1374
1375 1375 def _checknested(self, path):
1376 1376 """Determine if path is a legal nested repository."""
1377 1377 if not path.startswith(self.root):
1378 1378 return False
1379 1379 subpath = path[len(self.root) + 1 :]
1380 1380 normsubpath = util.pconvert(subpath)
1381 1381
1382 1382 # XXX: Checking against the current working copy is wrong in
1383 1383 # the sense that it can reject things like
1384 1384 #
1385 1385 # $ hg cat -r 10 sub/x.txt
1386 1386 #
1387 1387 # if sub/ is no longer a subrepository in the working copy
1388 1388 # parent revision.
1389 1389 #
1390 1390 # However, it can of course also allow things that would have
1391 1391 # been rejected before, such as the above cat command if sub/
1392 1392 # is a subrepository now, but was a normal directory before.
1393 1393 # The old path auditor would have rejected by mistake since it
1394 1394 # panics when it sees sub/.hg/.
1395 1395 #
1396 1396 # All in all, checking against the working copy seems sensible
1397 1397 # since we want to prevent access to nested repositories on
1398 1398 # the filesystem *now*.
1399 1399 ctx = self[None]
1400 1400 parts = util.splitpath(subpath)
1401 1401 while parts:
1402 1402 prefix = b'/'.join(parts)
1403 1403 if prefix in ctx.substate:
1404 1404 if prefix == normsubpath:
1405 1405 return True
1406 1406 else:
1407 1407 sub = ctx.sub(prefix)
1408 1408 return sub.checknested(subpath[len(prefix) + 1 :])
1409 1409 else:
1410 1410 parts.pop()
1411 1411 return False
1412 1412
1413 1413 def peer(self):
1414 1414 return localpeer(self) # not cached to avoid reference cycle
1415 1415
1416 1416 def unfiltered(self):
1417 1417 """Return unfiltered version of the repository
1418 1418
1419 1419 Intended to be overwritten by filtered repo."""
1420 1420 return self
1421 1421
1422 1422 def filtered(self, name, visibilityexceptions=None):
1423 1423 """Return a filtered version of a repository
1424 1424
1425 1425 The `name` parameter is the identifier of the requested view. This
1426 1426 will return a repoview object set "exactly" to the specified view.
1427 1427
1428 1428 This function does not apply recursive filtering to a repository. For
1429 1429 example calling `repo.filtered("served")` will return a repoview using
1430 1430 the "served" view, regardless of the initial view used by `repo`.
1431 1431
1432 1432 In other word, there is always only one level of `repoview` "filtering".
1433 1433 """
1434 1434 if self._extrafilterid is not None and b'%' not in name:
1435 1435 name = name + b'%' + self._extrafilterid
1436 1436
1437 1437 cls = repoview.newtype(self.unfiltered().__class__)
1438 1438 return cls(self, name, visibilityexceptions)
1439 1439
1440 1440 @mixedrepostorecache(
1441 1441 (b'bookmarks', b'plain'),
1442 1442 (b'bookmarks.current', b'plain'),
1443 1443 (b'bookmarks', b''),
1444 1444 (b'00changelog.i', b''),
1445 1445 )
1446 1446 def _bookmarks(self):
1447 1447 # Since the multiple files involved in the transaction cannot be
1448 1448 # written atomically (with current repository format), there is a race
1449 1449 # condition here.
1450 1450 #
1451 1451 # 1) changelog content A is read
1452 1452 # 2) outside transaction update changelog to content B
1453 1453 # 3) outside transaction update bookmark file referring to content B
1454 1454 # 4) bookmarks file content is read and filtered against changelog-A
1455 1455 #
1456 1456 # When this happens, bookmarks against nodes missing from A are dropped.
1457 1457 #
1458 1458 # Having this happening during read is not great, but it become worse
1459 1459 # when this happen during write because the bookmarks to the "unknown"
1460 1460 # nodes will be dropped for good. However, writes happen within locks.
1461 1461 # This locking makes it possible to have a race free consistent read.
1462 1462 # For this purpose data read from disc before locking are
1463 1463 # "invalidated" right after the locks are taken. This invalidations are
1464 1464 # "light", the `filecache` mechanism keep the data in memory and will
1465 1465 # reuse them if the underlying files did not changed. Not parsing the
1466 1466 # same data multiple times helps performances.
1467 1467 #
1468 1468 # Unfortunately in the case describe above, the files tracked by the
1469 1469 # bookmarks file cache might not have changed, but the in-memory
1470 1470 # content is still "wrong" because we used an older changelog content
1471 1471 # to process the on-disk data. So after locking, the changelog would be
1472 1472 # refreshed but `_bookmarks` would be preserved.
1473 1473 # Adding `00changelog.i` to the list of tracked file is not
1474 1474 # enough, because at the time we build the content for `_bookmarks` in
1475 1475 # (4), the changelog file has already diverged from the content used
1476 1476 # for loading `changelog` in (1)
1477 1477 #
1478 1478 # To prevent the issue, we force the changelog to be explicitly
1479 1479 # reloaded while computing `_bookmarks`. The data race can still happen
1480 1480 # without the lock (with a narrower window), but it would no longer go
1481 1481 # undetected during the lock time refresh.
1482 1482 #
1483 1483 # The new schedule is as follow
1484 1484 #
1485 1485 # 1) filecache logic detect that `_bookmarks` needs to be computed
1486 1486 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1487 1487 # 3) We force `changelog` filecache to be tested
1488 1488 # 4) cachestat for `changelog` are captured (for changelog)
1489 1489 # 5) `_bookmarks` is computed and cached
1490 1490 #
1491 1491 # The step in (3) ensure we have a changelog at least as recent as the
1492 1492 # cache stat computed in (1). As a result at locking time:
1493 1493 # * if the changelog did not changed since (1) -> we can reuse the data
1494 1494 # * otherwise -> the bookmarks get refreshed.
1495 1495 self._refreshchangelog()
1496 1496 return bookmarks.bmstore(self)
1497 1497
1498 1498 def _refreshchangelog(self):
1499 1499 """make sure the in memory changelog match the on-disk one"""
1500 1500 if 'changelog' in vars(self) and self.currenttransaction() is None:
1501 1501 del self.changelog
1502 1502
1503 1503 @property
1504 1504 def _activebookmark(self):
1505 1505 return self._bookmarks.active
1506 1506
1507 1507 # _phasesets depend on changelog. what we need is to call
1508 1508 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1509 1509 # can't be easily expressed in filecache mechanism.
1510 1510 @storecache(b'phaseroots', b'00changelog.i')
1511 1511 def _phasecache(self):
1512 1512 return phases.phasecache(self, self._phasedefaults)
1513 1513
1514 1514 @storecache(b'obsstore')
1515 1515 def obsstore(self):
1516 1516 return obsolete.makestore(self.ui, self)
1517 1517
1518 1518 @storecache(b'00changelog.i')
1519 1519 def changelog(self):
1520 1520 # load dirstate before changelog to avoid race see issue6303
1521 1521 self.dirstate.prefetch_parents()
1522 1522 return self.store.changelog(txnutil.mayhavepending(self.root))
1523 1523
1524 1524 @storecache(b'00manifest.i')
1525 1525 def manifestlog(self):
1526 1526 return self.store.manifestlog(self, self._storenarrowmatch)
1527 1527
1528 1528 @repofilecache(b'dirstate')
1529 1529 def dirstate(self):
1530 1530 return self._makedirstate()
1531 1531
1532 1532 def _makedirstate(self):
1533 1533 """Extension point for wrapping the dirstate per-repo."""
1534 1534 sparsematchfn = lambda: sparse.matcher(self)
1535 1535
1536 1536 return dirstate.dirstate(
1537 1537 self.vfs, self.ui, self.root, self._dirstatevalidate, sparsematchfn
1538 1538 )
1539 1539
1540 1540 def _dirstatevalidate(self, node):
1541 1541 try:
1542 1542 self.changelog.rev(node)
1543 1543 return node
1544 1544 except error.LookupError:
1545 1545 if not self._dirstatevalidatewarned:
1546 1546 self._dirstatevalidatewarned = True
1547 1547 self.ui.warn(
1548 1548 _(b"warning: ignoring unknown working parent %s!\n")
1549 1549 % short(node)
1550 1550 )
1551 1551 return nullid
1552 1552
1553 1553 @storecache(narrowspec.FILENAME)
1554 1554 def narrowpats(self):
1555 1555 """matcher patterns for this repository's narrowspec
1556 1556
1557 1557 A tuple of (includes, excludes).
1558 1558 """
1559 1559 return narrowspec.load(self)
1560 1560
1561 1561 @storecache(narrowspec.FILENAME)
1562 1562 def _storenarrowmatch(self):
1563 1563 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1564 1564 return matchmod.always()
1565 1565 include, exclude = self.narrowpats
1566 1566 return narrowspec.match(self.root, include=include, exclude=exclude)
1567 1567
1568 1568 @storecache(narrowspec.FILENAME)
1569 1569 def _narrowmatch(self):
1570 1570 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1571 1571 return matchmod.always()
1572 1572 narrowspec.checkworkingcopynarrowspec(self)
1573 1573 include, exclude = self.narrowpats
1574 1574 return narrowspec.match(self.root, include=include, exclude=exclude)
1575 1575
1576 1576 def narrowmatch(self, match=None, includeexact=False):
1577 1577 """matcher corresponding the the repo's narrowspec
1578 1578
1579 1579 If `match` is given, then that will be intersected with the narrow
1580 1580 matcher.
1581 1581
1582 1582 If `includeexact` is True, then any exact matches from `match` will
1583 1583 be included even if they're outside the narrowspec.
1584 1584 """
1585 1585 if match:
1586 1586 if includeexact and not self._narrowmatch.always():
1587 1587 # do not exclude explicitly-specified paths so that they can
1588 1588 # be warned later on
1589 1589 em = matchmod.exact(match.files())
1590 1590 nm = matchmod.unionmatcher([self._narrowmatch, em])
1591 1591 return matchmod.intersectmatchers(match, nm)
1592 1592 return matchmod.intersectmatchers(match, self._narrowmatch)
1593 1593 return self._narrowmatch
1594 1594
1595 1595 def setnarrowpats(self, newincludes, newexcludes):
1596 1596 narrowspec.save(self, newincludes, newexcludes)
1597 1597 self.invalidate(clearfilecache=True)
1598 1598
1599 1599 @unfilteredpropertycache
1600 1600 def _quick_access_changeid_null(self):
1601 1601 return {
1602 1602 b'null': (nullrev, nullid),
1603 1603 nullrev: (nullrev, nullid),
1604 1604 nullid: (nullrev, nullid),
1605 1605 }
1606 1606
1607 1607 @unfilteredpropertycache
1608 1608 def _quick_access_changeid_wc(self):
1609 1609 # also fast path access to the working copy parents
1610 1610 # however, only do it for filter that ensure wc is visible.
1611 1611 quick = self._quick_access_changeid_null.copy()
1612 1612 cl = self.unfiltered().changelog
1613 1613 for node in self.dirstate.parents():
1614 1614 if node == nullid:
1615 1615 continue
1616 1616 rev = cl.index.get_rev(node)
1617 1617 if rev is None:
1618 1618 # unknown working copy parent case:
1619 1619 #
1620 1620 # skip the fast path and let higher code deal with it
1621 1621 continue
1622 1622 pair = (rev, node)
1623 1623 quick[rev] = pair
1624 1624 quick[node] = pair
1625 1625 # also add the parents of the parents
1626 1626 for r in cl.parentrevs(rev):
1627 1627 if r == nullrev:
1628 1628 continue
1629 1629 n = cl.node(r)
1630 1630 pair = (r, n)
1631 1631 quick[r] = pair
1632 1632 quick[n] = pair
1633 1633 p1node = self.dirstate.p1()
1634 1634 if p1node != nullid:
1635 1635 quick[b'.'] = quick[p1node]
1636 1636 return quick
1637 1637
1638 1638 @unfilteredmethod
1639 1639 def _quick_access_changeid_invalidate(self):
1640 1640 if '_quick_access_changeid_wc' in vars(self):
1641 1641 del self.__dict__['_quick_access_changeid_wc']
1642 1642
1643 1643 @property
1644 1644 def _quick_access_changeid(self):
1645 1645 """an helper dictionnary for __getitem__ calls
1646 1646
1647 1647 This contains a list of symbol we can recognise right away without
1648 1648 further processing.
1649 1649 """
1650 1650 if self.filtername in repoview.filter_has_wc:
1651 1651 return self._quick_access_changeid_wc
1652 1652 return self._quick_access_changeid_null
1653 1653
1654 1654 def __getitem__(self, changeid):
1655 1655 # dealing with special cases
1656 1656 if changeid is None:
1657 1657 return context.workingctx(self)
1658 1658 if isinstance(changeid, context.basectx):
1659 1659 return changeid
1660 1660
1661 1661 # dealing with multiple revisions
1662 1662 if isinstance(changeid, slice):
1663 1663 # wdirrev isn't contiguous so the slice shouldn't include it
1664 1664 return [
1665 1665 self[i]
1666 1666 for i in pycompat.xrange(*changeid.indices(len(self)))
1667 1667 if i not in self.changelog.filteredrevs
1668 1668 ]
1669 1669
1670 1670 # dealing with some special values
1671 1671 quick_access = self._quick_access_changeid.get(changeid)
1672 1672 if quick_access is not None:
1673 1673 rev, node = quick_access
1674 1674 return context.changectx(self, rev, node, maybe_filtered=False)
1675 1675 if changeid == b'tip':
1676 1676 node = self.changelog.tip()
1677 1677 rev = self.changelog.rev(node)
1678 1678 return context.changectx(self, rev, node)
1679 1679
1680 1680 # dealing with arbitrary values
1681 1681 try:
1682 1682 if isinstance(changeid, int):
1683 1683 node = self.changelog.node(changeid)
1684 1684 rev = changeid
1685 1685 elif changeid == b'.':
1686 1686 # this is a hack to delay/avoid loading obsmarkers
1687 1687 # when we know that '.' won't be hidden
1688 1688 node = self.dirstate.p1()
1689 1689 rev = self.unfiltered().changelog.rev(node)
1690 1690 elif len(changeid) == 20:
1691 1691 try:
1692 1692 node = changeid
1693 1693 rev = self.changelog.rev(changeid)
1694 1694 except error.FilteredLookupError:
1695 1695 changeid = hex(changeid) # for the error message
1696 1696 raise
1697 1697 except LookupError:
1698 1698 # check if it might have come from damaged dirstate
1699 1699 #
1700 1700 # XXX we could avoid the unfiltered if we had a recognizable
1701 1701 # exception for filtered changeset access
1702 1702 if (
1703 1703 self.local()
1704 1704 and changeid in self.unfiltered().dirstate.parents()
1705 1705 ):
1706 1706 msg = _(b"working directory has unknown parent '%s'!")
1707 1707 raise error.Abort(msg % short(changeid))
1708 1708 changeid = hex(changeid) # for the error message
1709 1709 raise
1710 1710
1711 1711 elif len(changeid) == 40:
1712 1712 node = bin(changeid)
1713 1713 rev = self.changelog.rev(node)
1714 1714 else:
1715 1715 raise error.ProgrammingError(
1716 1716 b"unsupported changeid '%s' of type %s"
1717 1717 % (changeid, pycompat.bytestr(type(changeid)))
1718 1718 )
1719 1719
1720 1720 return context.changectx(self, rev, node)
1721 1721
1722 1722 except (error.FilteredIndexError, error.FilteredLookupError):
1723 1723 raise error.FilteredRepoLookupError(
1724 1724 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
1725 1725 )
1726 1726 except (IndexError, LookupError):
1727 1727 raise error.RepoLookupError(
1728 1728 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
1729 1729 )
1730 1730 except error.WdirUnsupported:
1731 1731 return context.workingctx(self)
1732 1732
1733 1733 def __contains__(self, changeid):
1734 1734 """True if the given changeid exists
1735 1735
1736 1736 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1737 1737 specified.
1738 1738 """
1739 1739 try:
1740 1740 self[changeid]
1741 1741 return True
1742 1742 except error.RepoLookupError:
1743 1743 return False
1744 1744
1745 1745 def __nonzero__(self):
1746 1746 return True
1747 1747
1748 1748 __bool__ = __nonzero__
1749 1749
1750 1750 def __len__(self):
1751 1751 # no need to pay the cost of repoview.changelog
1752 1752 unfi = self.unfiltered()
1753 1753 return len(unfi.changelog)
1754 1754
1755 1755 def __iter__(self):
1756 1756 return iter(self.changelog)
1757 1757
1758 1758 def revs(self, expr, *args):
1759 1759 '''Find revisions matching a revset.
1760 1760
1761 1761 The revset is specified as a string ``expr`` that may contain
1762 1762 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1763 1763
1764 1764 Revset aliases from the configuration are not expanded. To expand
1765 1765 user aliases, consider calling ``scmutil.revrange()`` or
1766 1766 ``repo.anyrevs([expr], user=True)``.
1767 1767
1768 1768 Returns a smartset.abstractsmartset, which is a list-like interface
1769 1769 that contains integer revisions.
1770 1770 '''
1771 1771 tree = revsetlang.spectree(expr, *args)
1772 1772 return revset.makematcher(tree)(self)
1773 1773
1774 1774 def set(self, expr, *args):
1775 1775 '''Find revisions matching a revset and emit changectx instances.
1776 1776
1777 1777 This is a convenience wrapper around ``revs()`` that iterates the
1778 1778 result and is a generator of changectx instances.
1779 1779
1780 1780 Revset aliases from the configuration are not expanded. To expand
1781 1781 user aliases, consider calling ``scmutil.revrange()``.
1782 1782 '''
1783 1783 for r in self.revs(expr, *args):
1784 1784 yield self[r]
1785 1785
1786 1786 def anyrevs(self, specs, user=False, localalias=None):
1787 1787 '''Find revisions matching one of the given revsets.
1788 1788
1789 1789 Revset aliases from the configuration are not expanded by default. To
1790 1790 expand user aliases, specify ``user=True``. To provide some local
1791 1791 definitions overriding user aliases, set ``localalias`` to
1792 1792 ``{name: definitionstring}``.
1793 1793 '''
1794 1794 if specs == [b'null']:
1795 1795 return revset.baseset([nullrev])
1796 1796 if specs == [b'.']:
1797 1797 quick_data = self._quick_access_changeid.get(b'.')
1798 1798 if quick_data is not None:
1799 1799 return revset.baseset([quick_data[0]])
1800 1800 if user:
1801 1801 m = revset.matchany(
1802 1802 self.ui,
1803 1803 specs,
1804 1804 lookup=revset.lookupfn(self),
1805 1805 localalias=localalias,
1806 1806 )
1807 1807 else:
1808 1808 m = revset.matchany(None, specs, localalias=localalias)
1809 1809 return m(self)
1810 1810
1811 1811 def url(self):
1812 1812 return b'file:' + self.root
1813 1813
1814 1814 def hook(self, name, throw=False, **args):
1815 1815 """Call a hook, passing this repo instance.
1816 1816
1817 1817 This a convenience method to aid invoking hooks. Extensions likely
1818 1818 won't call this unless they have registered a custom hook or are
1819 1819 replacing code that is expected to call a hook.
1820 1820 """
1821 1821 return hook.hook(self.ui, self, name, throw, **args)
1822 1822
1823 1823 @filteredpropertycache
1824 1824 def _tagscache(self):
1825 1825 '''Returns a tagscache object that contains various tags related
1826 1826 caches.'''
1827 1827
1828 1828 # This simplifies its cache management by having one decorated
1829 1829 # function (this one) and the rest simply fetch things from it.
1830 1830 class tagscache(object):
1831 1831 def __init__(self):
1832 1832 # These two define the set of tags for this repository. tags
1833 1833 # maps tag name to node; tagtypes maps tag name to 'global' or
1834 1834 # 'local'. (Global tags are defined by .hgtags across all
1835 1835 # heads, and local tags are defined in .hg/localtags.)
1836 1836 # They constitute the in-memory cache of tags.
1837 1837 self.tags = self.tagtypes = None
1838 1838
1839 1839 self.nodetagscache = self.tagslist = None
1840 1840
1841 1841 cache = tagscache()
1842 1842 cache.tags, cache.tagtypes = self._findtags()
1843 1843
1844 1844 return cache
1845 1845
1846 1846 def tags(self):
1847 1847 '''return a mapping of tag to node'''
1848 1848 t = {}
1849 1849 if self.changelog.filteredrevs:
1850 1850 tags, tt = self._findtags()
1851 1851 else:
1852 1852 tags = self._tagscache.tags
1853 1853 rev = self.changelog.rev
1854 1854 for k, v in pycompat.iteritems(tags):
1855 1855 try:
1856 1856 # ignore tags to unknown nodes
1857 1857 rev(v)
1858 1858 t[k] = v
1859 1859 except (error.LookupError, ValueError):
1860 1860 pass
1861 1861 return t
1862 1862
1863 1863 def _findtags(self):
1864 1864 '''Do the hard work of finding tags. Return a pair of dicts
1865 1865 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1866 1866 maps tag name to a string like \'global\' or \'local\'.
1867 1867 Subclasses or extensions are free to add their own tags, but
1868 1868 should be aware that the returned dicts will be retained for the
1869 1869 duration of the localrepo object.'''
1870 1870
1871 1871 # XXX what tagtype should subclasses/extensions use? Currently
1872 1872 # mq and bookmarks add tags, but do not set the tagtype at all.
1873 1873 # Should each extension invent its own tag type? Should there
1874 1874 # be one tagtype for all such "virtual" tags? Or is the status
1875 1875 # quo fine?
1876 1876
1877 1877 # map tag name to (node, hist)
1878 1878 alltags = tagsmod.findglobaltags(self.ui, self)
1879 1879 # map tag name to tag type
1880 1880 tagtypes = {tag: b'global' for tag in alltags}
1881 1881
1882 1882 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1883 1883
1884 1884 # Build the return dicts. Have to re-encode tag names because
1885 1885 # the tags module always uses UTF-8 (in order not to lose info
1886 1886 # writing to the cache), but the rest of Mercurial wants them in
1887 1887 # local encoding.
1888 1888 tags = {}
1889 1889 for (name, (node, hist)) in pycompat.iteritems(alltags):
1890 1890 if node != nullid:
1891 1891 tags[encoding.tolocal(name)] = node
1892 1892 tags[b'tip'] = self.changelog.tip()
1893 1893 tagtypes = {
1894 1894 encoding.tolocal(name): value
1895 1895 for (name, value) in pycompat.iteritems(tagtypes)
1896 1896 }
1897 1897 return (tags, tagtypes)
1898 1898
1899 1899 def tagtype(self, tagname):
1900 1900 '''
1901 1901 return the type of the given tag. result can be:
1902 1902
1903 1903 'local' : a local tag
1904 1904 'global' : a global tag
1905 1905 None : tag does not exist
1906 1906 '''
1907 1907
1908 1908 return self._tagscache.tagtypes.get(tagname)
1909 1909
1910 1910 def tagslist(self):
1911 1911 '''return a list of tags ordered by revision'''
1912 1912 if not self._tagscache.tagslist:
1913 1913 l = []
1914 1914 for t, n in pycompat.iteritems(self.tags()):
1915 1915 l.append((self.changelog.rev(n), t, n))
1916 1916 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1917 1917
1918 1918 return self._tagscache.tagslist
1919 1919
1920 1920 def nodetags(self, node):
1921 1921 '''return the tags associated with a node'''
1922 1922 if not self._tagscache.nodetagscache:
1923 1923 nodetagscache = {}
1924 1924 for t, n in pycompat.iteritems(self._tagscache.tags):
1925 1925 nodetagscache.setdefault(n, []).append(t)
1926 1926 for tags in pycompat.itervalues(nodetagscache):
1927 1927 tags.sort()
1928 1928 self._tagscache.nodetagscache = nodetagscache
1929 1929 return self._tagscache.nodetagscache.get(node, [])
1930 1930
1931 1931 def nodebookmarks(self, node):
1932 1932 """return the list of bookmarks pointing to the specified node"""
1933 1933 return self._bookmarks.names(node)
1934 1934
1935 1935 def branchmap(self):
1936 1936 '''returns a dictionary {branch: [branchheads]} with branchheads
1937 1937 ordered by increasing revision number'''
1938 1938 return self._branchcaches[self]
1939 1939
1940 1940 @unfilteredmethod
1941 1941 def revbranchcache(self):
1942 1942 if not self._revbranchcache:
1943 1943 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1944 1944 return self._revbranchcache
1945 1945
1946 1946 def branchtip(self, branch, ignoremissing=False):
1947 1947 '''return the tip node for a given branch
1948 1948
1949 1949 If ignoremissing is True, then this method will not raise an error.
1950 1950 This is helpful for callers that only expect None for a missing branch
1951 1951 (e.g. namespace).
1952 1952
1953 1953 '''
1954 1954 try:
1955 1955 return self.branchmap().branchtip(branch)
1956 1956 except KeyError:
1957 1957 if not ignoremissing:
1958 1958 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
1959 1959 else:
1960 1960 pass
1961 1961
1962 1962 def lookup(self, key):
1963 1963 node = scmutil.revsymbol(self, key).node()
1964 1964 if node is None:
1965 1965 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
1966 1966 return node
1967 1967
1968 1968 def lookupbranch(self, key):
1969 1969 if self.branchmap().hasbranch(key):
1970 1970 return key
1971 1971
1972 1972 return scmutil.revsymbol(self, key).branch()
1973 1973
1974 1974 def known(self, nodes):
1975 1975 cl = self.changelog
1976 1976 get_rev = cl.index.get_rev
1977 1977 filtered = cl.filteredrevs
1978 1978 result = []
1979 1979 for n in nodes:
1980 1980 r = get_rev(n)
1981 1981 resp = not (r is None or r in filtered)
1982 1982 result.append(resp)
1983 1983 return result
1984 1984
1985 1985 def local(self):
1986 1986 return self
1987 1987
1988 1988 def publishing(self):
1989 1989 # it's safe (and desirable) to trust the publish flag unconditionally
1990 1990 # so that we don't finalize changes shared between users via ssh or nfs
1991 1991 return self.ui.configbool(b'phases', b'publish', untrusted=True)
1992 1992
1993 1993 def cancopy(self):
1994 1994 # so statichttprepo's override of local() works
1995 1995 if not self.local():
1996 1996 return False
1997 1997 if not self.publishing():
1998 1998 return True
1999 1999 # if publishing we can't copy if there is filtered content
2000 2000 return not self.filtered(b'visible').changelog.filteredrevs
2001 2001
2002 2002 def shared(self):
2003 2003 '''the type of shared repository (None if not shared)'''
2004 2004 if self.sharedpath != self.path:
2005 2005 return b'store'
2006 2006 return None
2007 2007
2008 2008 def wjoin(self, f, *insidef):
2009 2009 return self.vfs.reljoin(self.root, f, *insidef)
2010 2010
2011 2011 def setparents(self, p1, p2=nullid):
2012 2012 self[None].setparents(p1, p2)
2013 2013 self._quick_access_changeid_invalidate()
2014 2014
2015 2015 def filectx(self, path, changeid=None, fileid=None, changectx=None):
2016 2016 """changeid must be a changeset revision, if specified.
2017 2017 fileid can be a file revision or node."""
2018 2018 return context.filectx(
2019 2019 self, path, changeid, fileid, changectx=changectx
2020 2020 )
2021 2021
2022 2022 def getcwd(self):
2023 2023 return self.dirstate.getcwd()
2024 2024
2025 2025 def pathto(self, f, cwd=None):
2026 2026 return self.dirstate.pathto(f, cwd)
2027 2027
2028 2028 def _loadfilter(self, filter):
2029 2029 if filter not in self._filterpats:
2030 2030 l = []
2031 2031 for pat, cmd in self.ui.configitems(filter):
2032 2032 if cmd == b'!':
2033 2033 continue
2034 2034 mf = matchmod.match(self.root, b'', [pat])
2035 2035 fn = None
2036 2036 params = cmd
2037 2037 for name, filterfn in pycompat.iteritems(self._datafilters):
2038 2038 if cmd.startswith(name):
2039 2039 fn = filterfn
2040 2040 params = cmd[len(name) :].lstrip()
2041 2041 break
2042 2042 if not fn:
2043 2043 fn = lambda s, c, **kwargs: procutil.filter(s, c)
2044 2044 fn.__name__ = 'commandfilter'
2045 2045 # Wrap old filters not supporting keyword arguments
2046 2046 if not pycompat.getargspec(fn)[2]:
2047 2047 oldfn = fn
2048 2048 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
2049 2049 fn.__name__ = 'compat-' + oldfn.__name__
2050 2050 l.append((mf, fn, params))
2051 2051 self._filterpats[filter] = l
2052 2052 return self._filterpats[filter]
2053 2053
2054 2054 def _filter(self, filterpats, filename, data):
2055 2055 for mf, fn, cmd in filterpats:
2056 2056 if mf(filename):
2057 2057 self.ui.debug(
2058 2058 b"filtering %s through %s\n"
2059 2059 % (filename, cmd or pycompat.sysbytes(fn.__name__))
2060 2060 )
2061 2061 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
2062 2062 break
2063 2063
2064 2064 return data
2065 2065
2066 2066 @unfilteredpropertycache
2067 2067 def _encodefilterpats(self):
2068 2068 return self._loadfilter(b'encode')
2069 2069
2070 2070 @unfilteredpropertycache
2071 2071 def _decodefilterpats(self):
2072 2072 return self._loadfilter(b'decode')
2073 2073
2074 2074 def adddatafilter(self, name, filter):
2075 2075 self._datafilters[name] = filter
2076 2076
2077 2077 def wread(self, filename):
2078 2078 if self.wvfs.islink(filename):
2079 2079 data = self.wvfs.readlink(filename)
2080 2080 else:
2081 2081 data = self.wvfs.read(filename)
2082 2082 return self._filter(self._encodefilterpats, filename, data)
2083 2083
2084 2084 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
2085 2085 """write ``data`` into ``filename`` in the working directory
2086 2086
2087 2087 This returns length of written (maybe decoded) data.
2088 2088 """
2089 2089 data = self._filter(self._decodefilterpats, filename, data)
2090 2090 if b'l' in flags:
2091 2091 self.wvfs.symlink(data, filename)
2092 2092 else:
2093 2093 self.wvfs.write(
2094 2094 filename, data, backgroundclose=backgroundclose, **kwargs
2095 2095 )
2096 2096 if b'x' in flags:
2097 2097 self.wvfs.setflags(filename, False, True)
2098 2098 else:
2099 2099 self.wvfs.setflags(filename, False, False)
2100 2100 return len(data)
2101 2101
2102 2102 def wwritedata(self, filename, data):
2103 2103 return self._filter(self._decodefilterpats, filename, data)
2104 2104
2105 2105 def currenttransaction(self):
2106 2106 """return the current transaction or None if non exists"""
2107 2107 if self._transref:
2108 2108 tr = self._transref()
2109 2109 else:
2110 2110 tr = None
2111 2111
2112 2112 if tr and tr.running():
2113 2113 return tr
2114 2114 return None
2115 2115
2116 2116 def transaction(self, desc, report=None):
2117 2117 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
2118 2118 b'devel', b'check-locks'
2119 2119 ):
2120 2120 if self._currentlock(self._lockref) is None:
2121 2121 raise error.ProgrammingError(b'transaction requires locking')
2122 2122 tr = self.currenttransaction()
2123 2123 if tr is not None:
2124 2124 return tr.nest(name=desc)
2125 2125
2126 2126 # abort here if the journal already exists
2127 2127 if self.svfs.exists(b"journal"):
2128 2128 raise error.RepoError(
2129 2129 _(b"abandoned transaction found"),
2130 2130 hint=_(b"run 'hg recover' to clean up transaction"),
2131 2131 )
2132 2132
2133 2133 idbase = b"%.40f#%f" % (random.random(), time.time())
2134 2134 ha = hex(hashutil.sha1(idbase).digest())
2135 2135 txnid = b'TXN:' + ha
2136 2136 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2137 2137
2138 2138 self._writejournal(desc)
2139 2139 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
2140 2140 if report:
2141 2141 rp = report
2142 2142 else:
2143 2143 rp = self.ui.warn
2144 2144 vfsmap = {b'plain': self.vfs, b'store': self.svfs} # root of .hg/
2145 2145 # we must avoid cyclic reference between repo and transaction.
2146 2146 reporef = weakref.ref(self)
2147 2147 # Code to track tag movement
2148 2148 #
2149 2149 # Since tags are all handled as file content, it is actually quite hard
2150 2150 # to track these movement from a code perspective. So we fallback to a
2151 2151 # tracking at the repository level. One could envision to track changes
2152 2152 # to the '.hgtags' file through changegroup apply but that fails to
2153 2153 # cope with case where transaction expose new heads without changegroup
2154 2154 # being involved (eg: phase movement).
2155 2155 #
2156 2156 # For now, We gate the feature behind a flag since this likely comes
2157 2157 # with performance impacts. The current code run more often than needed
2158 2158 # and do not use caches as much as it could. The current focus is on
2159 2159 # the behavior of the feature so we disable it by default. The flag
2160 2160 # will be removed when we are happy with the performance impact.
2161 2161 #
2162 2162 # Once this feature is no longer experimental move the following
2163 2163 # documentation to the appropriate help section:
2164 2164 #
2165 2165 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
2166 2166 # tags (new or changed or deleted tags). In addition the details of
2167 2167 # these changes are made available in a file at:
2168 2168 # ``REPOROOT/.hg/changes/tags.changes``.
2169 2169 # Make sure you check for HG_TAG_MOVED before reading that file as it
2170 2170 # might exist from a previous transaction even if no tag were touched
2171 2171 # in this one. Changes are recorded in a line base format::
2172 2172 #
2173 2173 # <action> <hex-node> <tag-name>\n
2174 2174 #
2175 2175 # Actions are defined as follow:
2176 2176 # "-R": tag is removed,
2177 2177 # "+A": tag is added,
2178 2178 # "-M": tag is moved (old value),
2179 2179 # "+M": tag is moved (new value),
2180 2180 tracktags = lambda x: None
2181 2181 # experimental config: experimental.hook-track-tags
2182 2182 shouldtracktags = self.ui.configbool(
2183 2183 b'experimental', b'hook-track-tags'
2184 2184 )
2185 2185 if desc != b'strip' and shouldtracktags:
2186 2186 oldheads = self.changelog.headrevs()
2187 2187
2188 2188 def tracktags(tr2):
2189 2189 repo = reporef()
2190 2190 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2191 2191 newheads = repo.changelog.headrevs()
2192 2192 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2193 2193 # notes: we compare lists here.
2194 2194 # As we do it only once buiding set would not be cheaper
2195 2195 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2196 2196 if changes:
2197 2197 tr2.hookargs[b'tag_moved'] = b'1'
2198 2198 with repo.vfs(
2199 2199 b'changes/tags.changes', b'w', atomictemp=True
2200 2200 ) as changesfile:
2201 2201 # note: we do not register the file to the transaction
2202 2202 # because we needs it to still exist on the transaction
2203 2203 # is close (for txnclose hooks)
2204 2204 tagsmod.writediff(changesfile, changes)
2205 2205
2206 2206 def validate(tr2):
2207 2207 """will run pre-closing hooks"""
2208 2208 # XXX the transaction API is a bit lacking here so we take a hacky
2209 2209 # path for now
2210 2210 #
2211 2211 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
2212 2212 # dict is copied before these run. In addition we needs the data
2213 2213 # available to in memory hooks too.
2214 2214 #
2215 2215 # Moreover, we also need to make sure this runs before txnclose
2216 2216 # hooks and there is no "pending" mechanism that would execute
2217 2217 # logic only if hooks are about to run.
2218 2218 #
2219 2219 # Fixing this limitation of the transaction is also needed to track
2220 2220 # other families of changes (bookmarks, phases, obsolescence).
2221 2221 #
2222 2222 # This will have to be fixed before we remove the experimental
2223 2223 # gating.
2224 2224 tracktags(tr2)
2225 2225 repo = reporef()
2226 2226
2227 2227 singleheadopt = (b'experimental', b'single-head-per-branch')
2228 2228 singlehead = repo.ui.configbool(*singleheadopt)
2229 2229 if singlehead:
2230 2230 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2231 2231 accountclosed = singleheadsub.get(
2232 2232 b"account-closed-heads", False
2233 2233 )
2234 2234 scmutil.enforcesinglehead(repo, tr2, desc, accountclosed)
2235 2235 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2236 2236 for name, (old, new) in sorted(
2237 2237 tr.changes[b'bookmarks'].items()
2238 2238 ):
2239 2239 args = tr.hookargs.copy()
2240 2240 args.update(bookmarks.preparehookargs(name, old, new))
2241 2241 repo.hook(
2242 2242 b'pretxnclose-bookmark',
2243 2243 throw=True,
2244 2244 **pycompat.strkwargs(args)
2245 2245 )
2246 2246 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2247 2247 cl = repo.unfiltered().changelog
2248 2248 for revs, (old, new) in tr.changes[b'phases']:
2249 2249 for rev in revs:
2250 2250 args = tr.hookargs.copy()
2251 2251 node = hex(cl.node(rev))
2252 2252 args.update(phases.preparehookargs(node, old, new))
2253 2253 repo.hook(
2254 2254 b'pretxnclose-phase',
2255 2255 throw=True,
2256 2256 **pycompat.strkwargs(args)
2257 2257 )
2258 2258
2259 2259 repo.hook(
2260 2260 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2261 2261 )
2262 2262
2263 2263 def releasefn(tr, success):
2264 2264 repo = reporef()
2265 2265 if repo is None:
2266 2266 # If the repo has been GC'd (and this release function is being
2267 2267 # called from transaction.__del__), there's not much we can do,
2268 2268 # so just leave the unfinished transaction there and let the
2269 2269 # user run `hg recover`.
2270 2270 return
2271 2271 if success:
2272 2272 # this should be explicitly invoked here, because
2273 2273 # in-memory changes aren't written out at closing
2274 2274 # transaction, if tr.addfilegenerator (via
2275 2275 # dirstate.write or so) isn't invoked while
2276 2276 # transaction running
2277 2277 repo.dirstate.write(None)
2278 2278 else:
2279 2279 # discard all changes (including ones already written
2280 2280 # out) in this transaction
2281 2281 narrowspec.restorebackup(self, b'journal.narrowspec')
2282 2282 narrowspec.restorewcbackup(self, b'journal.narrowspec.dirstate')
2283 2283 repo.dirstate.restorebackup(None, b'journal.dirstate')
2284 2284
2285 2285 repo.invalidate(clearfilecache=True)
2286 2286
2287 2287 tr = transaction.transaction(
2288 2288 rp,
2289 2289 self.svfs,
2290 2290 vfsmap,
2291 2291 b"journal",
2292 2292 b"undo",
2293 2293 aftertrans(renames),
2294 2294 self.store.createmode,
2295 2295 validator=validate,
2296 2296 releasefn=releasefn,
2297 2297 checkambigfiles=_cachedfiles,
2298 2298 name=desc,
2299 2299 )
2300 2300 tr.changes[b'origrepolen'] = len(self)
2301 2301 tr.changes[b'obsmarkers'] = set()
2302 2302 tr.changes[b'phases'] = []
2303 2303 tr.changes[b'bookmarks'] = {}
2304 2304
2305 2305 tr.hookargs[b'txnid'] = txnid
2306 2306 tr.hookargs[b'txnname'] = desc
2307 2307 tr.hookargs[b'changes'] = tr.changes
2308 2308 # note: writing the fncache only during finalize mean that the file is
2309 2309 # outdated when running hooks. As fncache is used for streaming clone,
2310 2310 # this is not expected to break anything that happen during the hooks.
2311 2311 tr.addfinalize(b'flush-fncache', self.store.write)
2312 2312
2313 2313 def txnclosehook(tr2):
2314 2314 """To be run if transaction is successful, will schedule a hook run
2315 2315 """
2316 2316 # Don't reference tr2 in hook() so we don't hold a reference.
2317 2317 # This reduces memory consumption when there are multiple
2318 2318 # transactions per lock. This can likely go away if issue5045
2319 2319 # fixes the function accumulation.
2320 2320 hookargs = tr2.hookargs
2321 2321
2322 2322 def hookfunc(unused_success):
2323 2323 repo = reporef()
2324 2324 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2325 2325 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2326 2326 for name, (old, new) in bmchanges:
2327 2327 args = tr.hookargs.copy()
2328 2328 args.update(bookmarks.preparehookargs(name, old, new))
2329 2329 repo.hook(
2330 2330 b'txnclose-bookmark',
2331 2331 throw=False,
2332 2332 **pycompat.strkwargs(args)
2333 2333 )
2334 2334
2335 2335 if hook.hashook(repo.ui, b'txnclose-phase'):
2336 2336 cl = repo.unfiltered().changelog
2337 2337 phasemv = sorted(
2338 2338 tr.changes[b'phases'], key=lambda r: r[0][0]
2339 2339 )
2340 2340 for revs, (old, new) in phasemv:
2341 2341 for rev in revs:
2342 2342 args = tr.hookargs.copy()
2343 2343 node = hex(cl.node(rev))
2344 2344 args.update(phases.preparehookargs(node, old, new))
2345 2345 repo.hook(
2346 2346 b'txnclose-phase',
2347 2347 throw=False,
2348 2348 **pycompat.strkwargs(args)
2349 2349 )
2350 2350
2351 2351 repo.hook(
2352 2352 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2353 2353 )
2354 2354
2355 2355 reporef()._afterlock(hookfunc)
2356 2356
2357 2357 tr.addfinalize(b'txnclose-hook', txnclosehook)
2358 2358 # Include a leading "-" to make it happen before the transaction summary
2359 2359 # reports registered via scmutil.registersummarycallback() whose names
2360 2360 # are 00-txnreport etc. That way, the caches will be warm when the
2361 2361 # callbacks run.
2362 2362 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2363 2363
2364 2364 def txnaborthook(tr2):
2365 2365 """To be run if transaction is aborted
2366 2366 """
2367 2367 reporef().hook(
2368 2368 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2369 2369 )
2370 2370
2371 2371 tr.addabort(b'txnabort-hook', txnaborthook)
2372 2372 # avoid eager cache invalidation. in-memory data should be identical
2373 2373 # to stored data if transaction has no error.
2374 2374 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2375 2375 self._transref = weakref.ref(tr)
2376 2376 scmutil.registersummarycallback(self, tr, desc)
2377 2377 return tr
2378 2378
2379 2379 def _journalfiles(self):
2380 2380 return (
2381 2381 (self.svfs, b'journal'),
2382 2382 (self.svfs, b'journal.narrowspec'),
2383 2383 (self.vfs, b'journal.narrowspec.dirstate'),
2384 2384 (self.vfs, b'journal.dirstate'),
2385 2385 (self.vfs, b'journal.branch'),
2386 2386 (self.vfs, b'journal.desc'),
2387 2387 (bookmarks.bookmarksvfs(self), b'journal.bookmarks'),
2388 2388 (self.svfs, b'journal.phaseroots'),
2389 2389 )
2390 2390
2391 2391 def undofiles(self):
2392 2392 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2393 2393
2394 2394 @unfilteredmethod
2395 2395 def _writejournal(self, desc):
2396 2396 self.dirstate.savebackup(None, b'journal.dirstate')
2397 2397 narrowspec.savewcbackup(self, b'journal.narrowspec.dirstate')
2398 2398 narrowspec.savebackup(self, b'journal.narrowspec')
2399 2399 self.vfs.write(
2400 2400 b"journal.branch", encoding.fromlocal(self.dirstate.branch())
2401 2401 )
2402 2402 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2403 2403 bookmarksvfs = bookmarks.bookmarksvfs(self)
2404 2404 bookmarksvfs.write(
2405 2405 b"journal.bookmarks", bookmarksvfs.tryread(b"bookmarks")
2406 2406 )
2407 2407 self.svfs.write(b"journal.phaseroots", self.svfs.tryread(b"phaseroots"))
2408 2408
2409 2409 def recover(self):
2410 2410 with self.lock():
2411 2411 if self.svfs.exists(b"journal"):
2412 2412 self.ui.status(_(b"rolling back interrupted transaction\n"))
2413 2413 vfsmap = {
2414 2414 b'': self.svfs,
2415 2415 b'plain': self.vfs,
2416 2416 }
2417 2417 transaction.rollback(
2418 2418 self.svfs,
2419 2419 vfsmap,
2420 2420 b"journal",
2421 2421 self.ui.warn,
2422 2422 checkambigfiles=_cachedfiles,
2423 2423 )
2424 2424 self.invalidate()
2425 2425 return True
2426 2426 else:
2427 2427 self.ui.warn(_(b"no interrupted transaction available\n"))
2428 2428 return False
2429 2429
2430 2430 def rollback(self, dryrun=False, force=False):
2431 2431 wlock = lock = dsguard = None
2432 2432 try:
2433 2433 wlock = self.wlock()
2434 2434 lock = self.lock()
2435 2435 if self.svfs.exists(b"undo"):
2436 2436 dsguard = dirstateguard.dirstateguard(self, b'rollback')
2437 2437
2438 2438 return self._rollback(dryrun, force, dsguard)
2439 2439 else:
2440 2440 self.ui.warn(_(b"no rollback information available\n"))
2441 2441 return 1
2442 2442 finally:
2443 2443 release(dsguard, lock, wlock)
2444 2444
2445 2445 @unfilteredmethod # Until we get smarter cache management
2446 2446 def _rollback(self, dryrun, force, dsguard):
2447 2447 ui = self.ui
2448 2448 try:
2449 2449 args = self.vfs.read(b'undo.desc').splitlines()
2450 2450 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2451 2451 if len(args) >= 3:
2452 2452 detail = args[2]
2453 2453 oldtip = oldlen - 1
2454 2454
2455 2455 if detail and ui.verbose:
2456 2456 msg = _(
2457 2457 b'repository tip rolled back to revision %d'
2458 2458 b' (undo %s: %s)\n'
2459 2459 ) % (oldtip, desc, detail)
2460 2460 else:
2461 2461 msg = _(
2462 2462 b'repository tip rolled back to revision %d (undo %s)\n'
2463 2463 ) % (oldtip, desc)
2464 2464 except IOError:
2465 2465 msg = _(b'rolling back unknown transaction\n')
2466 2466 desc = None
2467 2467
2468 2468 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2469 2469 raise error.Abort(
2470 2470 _(
2471 2471 b'rollback of last commit while not checked out '
2472 2472 b'may lose data'
2473 2473 ),
2474 2474 hint=_(b'use -f to force'),
2475 2475 )
2476 2476
2477 2477 ui.status(msg)
2478 2478 if dryrun:
2479 2479 return 0
2480 2480
2481 2481 parents = self.dirstate.parents()
2482 2482 self.destroying()
2483 2483 vfsmap = {b'plain': self.vfs, b'': self.svfs}
2484 2484 transaction.rollback(
2485 2485 self.svfs, vfsmap, b'undo', ui.warn, checkambigfiles=_cachedfiles
2486 2486 )
2487 2487 bookmarksvfs = bookmarks.bookmarksvfs(self)
2488 2488 if bookmarksvfs.exists(b'undo.bookmarks'):
2489 2489 bookmarksvfs.rename(
2490 2490 b'undo.bookmarks', b'bookmarks', checkambig=True
2491 2491 )
2492 2492 if self.svfs.exists(b'undo.phaseroots'):
2493 2493 self.svfs.rename(b'undo.phaseroots', b'phaseroots', checkambig=True)
2494 2494 self.invalidate()
2495 2495
2496 2496 has_node = self.changelog.index.has_node
2497 2497 parentgone = any(not has_node(p) for p in parents)
2498 2498 if parentgone:
2499 2499 # prevent dirstateguard from overwriting already restored one
2500 2500 dsguard.close()
2501 2501
2502 2502 narrowspec.restorebackup(self, b'undo.narrowspec')
2503 2503 narrowspec.restorewcbackup(self, b'undo.narrowspec.dirstate')
2504 2504 self.dirstate.restorebackup(None, b'undo.dirstate')
2505 2505 try:
2506 2506 branch = self.vfs.read(b'undo.branch')
2507 2507 self.dirstate.setbranch(encoding.tolocal(branch))
2508 2508 except IOError:
2509 2509 ui.warn(
2510 2510 _(
2511 2511 b'named branch could not be reset: '
2512 2512 b'current branch is still \'%s\'\n'
2513 2513 )
2514 2514 % self.dirstate.branch()
2515 2515 )
2516 2516
2517 2517 parents = tuple([p.rev() for p in self[None].parents()])
2518 2518 if len(parents) > 1:
2519 2519 ui.status(
2520 2520 _(
2521 2521 b'working directory now based on '
2522 2522 b'revisions %d and %d\n'
2523 2523 )
2524 2524 % parents
2525 2525 )
2526 2526 else:
2527 2527 ui.status(
2528 2528 _(b'working directory now based on revision %d\n') % parents
2529 2529 )
2530 2530 mergestatemod.mergestate.clean(self)
2531 2531
2532 2532 # TODO: if we know which new heads may result from this rollback, pass
2533 2533 # them to destroy(), which will prevent the branchhead cache from being
2534 2534 # invalidated.
2535 2535 self.destroyed()
2536 2536 return 0
2537 2537
2538 2538 def _buildcacheupdater(self, newtransaction):
2539 2539 """called during transaction to build the callback updating cache
2540 2540
2541 2541 Lives on the repository to help extension who might want to augment
2542 2542 this logic. For this purpose, the created transaction is passed to the
2543 2543 method.
2544 2544 """
2545 2545 # we must avoid cyclic reference between repo and transaction.
2546 2546 reporef = weakref.ref(self)
2547 2547
2548 2548 def updater(tr):
2549 2549 repo = reporef()
2550 2550 repo.updatecaches(tr)
2551 2551
2552 2552 return updater
2553 2553
2554 2554 @unfilteredmethod
2555 2555 def updatecaches(self, tr=None, full=False):
2556 2556 """warm appropriate caches
2557 2557
2558 2558 If this function is called after a transaction closed. The transaction
2559 2559 will be available in the 'tr' argument. This can be used to selectively
2560 2560 update caches relevant to the changes in that transaction.
2561 2561
2562 2562 If 'full' is set, make sure all caches the function knows about have
2563 2563 up-to-date data. Even the ones usually loaded more lazily.
2564 2564 """
2565 2565 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2566 2566 # During strip, many caches are invalid but
2567 2567 # later call to `destroyed` will refresh them.
2568 2568 return
2569 2569
2570 2570 if tr is None or tr.changes[b'origrepolen'] < len(self):
2571 2571 # accessing the 'ser ved' branchmap should refresh all the others,
2572 2572 self.ui.debug(b'updating the branch cache\n')
2573 2573 self.filtered(b'served').branchmap()
2574 2574 self.filtered(b'served.hidden').branchmap()
2575 2575
2576 2576 if full:
2577 2577 unfi = self.unfiltered()
2578 2578
2579 2579 self.changelog.update_caches(transaction=tr)
2580 2580 self.manifestlog.update_caches(transaction=tr)
2581 2581
2582 2582 rbc = unfi.revbranchcache()
2583 2583 for r in unfi.changelog:
2584 2584 rbc.branchinfo(r)
2585 2585 rbc.write()
2586 2586
2587 2587 # ensure the working copy parents are in the manifestfulltextcache
2588 2588 for ctx in self[b'.'].parents():
2589 2589 ctx.manifest() # accessing the manifest is enough
2590 2590
2591 2591 # accessing fnode cache warms the cache
2592 2592 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2593 2593 # accessing tags warm the cache
2594 2594 self.tags()
2595 2595 self.filtered(b'served').tags()
2596 2596
2597 2597 # The `full` arg is documented as updating even the lazily-loaded
2598 2598 # caches immediately, so we're forcing a write to cause these caches
2599 2599 # to be warmed up even if they haven't explicitly been requested
2600 2600 # yet (if they've never been used by hg, they won't ever have been
2601 2601 # written, even if they're a subset of another kind of cache that
2602 2602 # *has* been used).
2603 2603 for filt in repoview.filtertable.keys():
2604 2604 filtered = self.filtered(filt)
2605 2605 filtered.branchmap().write(filtered)
2606 2606
2607 2607 def invalidatecaches(self):
2608 2608
2609 2609 if '_tagscache' in vars(self):
2610 2610 # can't use delattr on proxy
2611 2611 del self.__dict__['_tagscache']
2612 2612
2613 2613 self._branchcaches.clear()
2614 2614 self.invalidatevolatilesets()
2615 2615 self._sparsesignaturecache.clear()
2616 2616
2617 2617 def invalidatevolatilesets(self):
2618 2618 self.filteredrevcache.clear()
2619 2619 obsolete.clearobscaches(self)
2620 2620 self._quick_access_changeid_invalidate()
2621 2621
2622 2622 def invalidatedirstate(self):
2623 2623 '''Invalidates the dirstate, causing the next call to dirstate
2624 2624 to check if it was modified since the last time it was read,
2625 2625 rereading it if it has.
2626 2626
2627 2627 This is different to dirstate.invalidate() that it doesn't always
2628 2628 rereads the dirstate. Use dirstate.invalidate() if you want to
2629 2629 explicitly read the dirstate again (i.e. restoring it to a previous
2630 2630 known good state).'''
2631 2631 if hasunfilteredcache(self, 'dirstate'):
2632 2632 for k in self.dirstate._filecache:
2633 2633 try:
2634 2634 delattr(self.dirstate, k)
2635 2635 except AttributeError:
2636 2636 pass
2637 2637 delattr(self.unfiltered(), 'dirstate')
2638 2638
2639 2639 def invalidate(self, clearfilecache=False):
2640 2640 '''Invalidates both store and non-store parts other than dirstate
2641 2641
2642 2642 If a transaction is running, invalidation of store is omitted,
2643 2643 because discarding in-memory changes might cause inconsistency
2644 2644 (e.g. incomplete fncache causes unintentional failure, but
2645 2645 redundant one doesn't).
2646 2646 '''
2647 2647 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2648 2648 for k in list(self._filecache.keys()):
2649 2649 # dirstate is invalidated separately in invalidatedirstate()
2650 2650 if k == b'dirstate':
2651 2651 continue
2652 2652 if (
2653 2653 k == b'changelog'
2654 2654 and self.currenttransaction()
2655 2655 and self.changelog._delayed
2656 2656 ):
2657 2657 # The changelog object may store unwritten revisions. We don't
2658 2658 # want to lose them.
2659 2659 # TODO: Solve the problem instead of working around it.
2660 2660 continue
2661 2661
2662 2662 if clearfilecache:
2663 2663 del self._filecache[k]
2664 2664 try:
2665 2665 delattr(unfiltered, k)
2666 2666 except AttributeError:
2667 2667 pass
2668 2668 self.invalidatecaches()
2669 2669 if not self.currenttransaction():
2670 2670 # TODO: Changing contents of store outside transaction
2671 2671 # causes inconsistency. We should make in-memory store
2672 2672 # changes detectable, and abort if changed.
2673 2673 self.store.invalidatecaches()
2674 2674
2675 2675 def invalidateall(self):
2676 2676 '''Fully invalidates both store and non-store parts, causing the
2677 2677 subsequent operation to reread any outside changes.'''
2678 2678 # extension should hook this to invalidate its caches
2679 2679 self.invalidate()
2680 2680 self.invalidatedirstate()
2681 2681
2682 2682 @unfilteredmethod
2683 2683 def _refreshfilecachestats(self, tr):
2684 2684 """Reload stats of cached files so that they are flagged as valid"""
2685 2685 for k, ce in self._filecache.items():
2686 2686 k = pycompat.sysstr(k)
2687 2687 if k == 'dirstate' or k not in self.__dict__:
2688 2688 continue
2689 2689 ce.refresh()
2690 2690
2691 2691 def _lock(
2692 2692 self, vfs, lockname, wait, releasefn, acquirefn, desc,
2693 2693 ):
2694 2694 timeout = 0
2695 2695 warntimeout = 0
2696 2696 if wait:
2697 2697 timeout = self.ui.configint(b"ui", b"timeout")
2698 2698 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
2699 2699 # internal config: ui.signal-safe-lock
2700 2700 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
2701 2701
2702 2702 l = lockmod.trylock(
2703 2703 self.ui,
2704 2704 vfs,
2705 2705 lockname,
2706 2706 timeout,
2707 2707 warntimeout,
2708 2708 releasefn=releasefn,
2709 2709 acquirefn=acquirefn,
2710 2710 desc=desc,
2711 2711 signalsafe=signalsafe,
2712 2712 )
2713 2713 return l
2714 2714
2715 2715 def _afterlock(self, callback):
2716 2716 """add a callback to be run when the repository is fully unlocked
2717 2717
2718 2718 The callback will be executed when the outermost lock is released
2719 2719 (with wlock being higher level than 'lock')."""
2720 2720 for ref in (self._wlockref, self._lockref):
2721 2721 l = ref and ref()
2722 2722 if l and l.held:
2723 2723 l.postrelease.append(callback)
2724 2724 break
2725 2725 else: # no lock have been found.
2726 2726 callback(True)
2727 2727
2728 2728 def lock(self, wait=True):
2729 2729 '''Lock the repository store (.hg/store) and return a weak reference
2730 2730 to the lock. Use this before modifying the store (e.g. committing or
2731 2731 stripping). If you are opening a transaction, get a lock as well.)
2732 2732
2733 2733 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2734 2734 'wlock' first to avoid a dead-lock hazard.'''
2735 2735 l = self._currentlock(self._lockref)
2736 2736 if l is not None:
2737 2737 l.lock()
2738 2738 return l
2739 2739
2740 2740 l = self._lock(
2741 2741 vfs=self.svfs,
2742 2742 lockname=b"lock",
2743 2743 wait=wait,
2744 2744 releasefn=None,
2745 2745 acquirefn=self.invalidate,
2746 2746 desc=_(b'repository %s') % self.origroot,
2747 2747 )
2748 2748 self._lockref = weakref.ref(l)
2749 2749 return l
2750 2750
2751 2751 def wlock(self, wait=True):
2752 2752 '''Lock the non-store parts of the repository (everything under
2753 2753 .hg except .hg/store) and return a weak reference to the lock.
2754 2754
2755 2755 Use this before modifying files in .hg.
2756 2756
2757 2757 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2758 2758 'wlock' first to avoid a dead-lock hazard.'''
2759 2759 l = self._wlockref and self._wlockref()
2760 2760 if l is not None and l.held:
2761 2761 l.lock()
2762 2762 return l
2763 2763
2764 2764 # We do not need to check for non-waiting lock acquisition. Such
2765 2765 # acquisition would not cause dead-lock as they would just fail.
2766 2766 if wait and (
2767 2767 self.ui.configbool(b'devel', b'all-warnings')
2768 2768 or self.ui.configbool(b'devel', b'check-locks')
2769 2769 ):
2770 2770 if self._currentlock(self._lockref) is not None:
2771 2771 self.ui.develwarn(b'"wlock" acquired after "lock"')
2772 2772
2773 2773 def unlock():
2774 2774 if self.dirstate.pendingparentchange():
2775 2775 self.dirstate.invalidate()
2776 2776 else:
2777 2777 self.dirstate.write(None)
2778 2778
2779 2779 self._filecache[b'dirstate'].refresh()
2780 2780
2781 2781 l = self._lock(
2782 2782 self.vfs,
2783 2783 b"wlock",
2784 2784 wait,
2785 2785 unlock,
2786 2786 self.invalidatedirstate,
2787 2787 _(b'working directory of %s') % self.origroot,
2788 2788 )
2789 2789 self._wlockref = weakref.ref(l)
2790 2790 return l
2791 2791
2792 2792 def _currentlock(self, lockref):
2793 2793 """Returns the lock if it's held, or None if it's not."""
2794 2794 if lockref is None:
2795 2795 return None
2796 2796 l = lockref()
2797 2797 if l is None or not l.held:
2798 2798 return None
2799 2799 return l
2800 2800
2801 2801 def currentwlock(self):
2802 2802 """Returns the wlock if it's held, or None if it's not."""
2803 2803 return self._currentlock(self._wlockref)
2804 2804
2805 2805 def checkcommitpatterns(self, wctx, match, status, fail):
2806 2806 """check for commit arguments that aren't committable"""
2807 2807 if match.isexact() or match.prefix():
2808 2808 matched = set(status.modified + status.added + status.removed)
2809 2809
2810 2810 for f in match.files():
2811 2811 f = self.dirstate.normalize(f)
2812 2812 if f == b'.' or f in matched or f in wctx.substate:
2813 2813 continue
2814 2814 if f in status.deleted:
2815 2815 fail(f, _(b'file not found!'))
2816 2816 # Is it a directory that exists or used to exist?
2817 2817 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
2818 2818 d = f + b'/'
2819 2819 for mf in matched:
2820 2820 if mf.startswith(d):
2821 2821 break
2822 2822 else:
2823 2823 fail(f, _(b"no match under directory!"))
2824 2824 elif f not in self.dirstate:
2825 2825 fail(f, _(b"file not tracked!"))
2826 2826
2827 2827 @unfilteredmethod
2828 2828 def commit(
2829 2829 self,
2830 2830 text=b"",
2831 2831 user=None,
2832 2832 date=None,
2833 2833 match=None,
2834 2834 force=False,
2835 2835 editor=None,
2836 2836 extra=None,
2837 2837 ):
2838 2838 """Add a new revision to current repository.
2839 2839
2840 2840 Revision information is gathered from the working directory,
2841 2841 match can be used to filter the committed files. If editor is
2842 2842 supplied, it is called to get a commit message.
2843 2843 """
2844 2844 if extra is None:
2845 2845 extra = {}
2846 2846
2847 2847 def fail(f, msg):
2848 raise error.Abort(b'%s: %s' % (f, msg))
2848 raise error.InputError(b'%s: %s' % (f, msg))
2849 2849
2850 2850 if not match:
2851 2851 match = matchmod.always()
2852 2852
2853 2853 if not force:
2854 2854 match.bad = fail
2855 2855
2856 2856 # lock() for recent changelog (see issue4368)
2857 2857 with self.wlock(), self.lock():
2858 2858 wctx = self[None]
2859 2859 merge = len(wctx.parents()) > 1
2860 2860
2861 2861 if not force and merge and not match.always():
2862 2862 raise error.Abort(
2863 2863 _(
2864 2864 b'cannot partially commit a merge '
2865 2865 b'(do not specify files or patterns)'
2866 2866 )
2867 2867 )
2868 2868
2869 2869 status = self.status(match=match, clean=force)
2870 2870 if force:
2871 2871 status.modified.extend(
2872 2872 status.clean
2873 2873 ) # mq may commit clean files
2874 2874
2875 2875 # check subrepos
2876 2876 subs, commitsubs, newstate = subrepoutil.precommit(
2877 2877 self.ui, wctx, status, match, force=force
2878 2878 )
2879 2879
2880 2880 # make sure all explicit patterns are matched
2881 2881 if not force:
2882 2882 self.checkcommitpatterns(wctx, match, status, fail)
2883 2883
2884 2884 cctx = context.workingcommitctx(
2885 2885 self, status, text, user, date, extra
2886 2886 )
2887 2887
2888 2888 ms = mergestatemod.mergestate.read(self)
2889 2889 mergeutil.checkunresolved(ms)
2890 2890
2891 2891 # internal config: ui.allowemptycommit
2892 2892 if cctx.isempty() and not self.ui.configbool(
2893 2893 b'ui', b'allowemptycommit'
2894 2894 ):
2895 2895 self.ui.debug(b'nothing to commit, clearing merge state\n')
2896 2896 ms.reset()
2897 2897 return None
2898 2898
2899 2899 if merge and cctx.deleted():
2900 2900 raise error.Abort(_(b"cannot commit merge with missing files"))
2901 2901
2902 2902 if editor:
2903 2903 cctx._text = editor(self, cctx, subs)
2904 2904 edited = text != cctx._text
2905 2905
2906 2906 # Save commit message in case this transaction gets rolled back
2907 2907 # (e.g. by a pretxncommit hook). Leave the content alone on
2908 2908 # the assumption that the user will use the same editor again.
2909 2909 msgfn = self.savecommitmessage(cctx._text)
2910 2910
2911 2911 # commit subs and write new state
2912 2912 if subs:
2913 2913 uipathfn = scmutil.getuipathfn(self)
2914 2914 for s in sorted(commitsubs):
2915 2915 sub = wctx.sub(s)
2916 2916 self.ui.status(
2917 2917 _(b'committing subrepository %s\n')
2918 2918 % uipathfn(subrepoutil.subrelpath(sub))
2919 2919 )
2920 2920 sr = sub.commit(cctx._text, user, date)
2921 2921 newstate[s] = (newstate[s][0], sr)
2922 2922 subrepoutil.writestate(self, newstate)
2923 2923
2924 2924 p1, p2 = self.dirstate.parents()
2925 2925 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or b'')
2926 2926 try:
2927 2927 self.hook(
2928 2928 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
2929 2929 )
2930 2930 with self.transaction(b'commit'):
2931 2931 ret = self.commitctx(cctx, True)
2932 2932 # update bookmarks, dirstate and mergestate
2933 2933 bookmarks.update(self, [p1, p2], ret)
2934 2934 cctx.markcommitted(ret)
2935 2935 ms.reset()
2936 2936 except: # re-raises
2937 2937 if edited:
2938 2938 self.ui.write(
2939 2939 _(b'note: commit message saved in %s\n') % msgfn
2940 2940 )
2941 2941 self.ui.write(
2942 2942 _(
2943 2943 b"note: use 'hg commit --logfile "
2944 2944 b".hg/last-message.txt --edit' to reuse it\n"
2945 2945 )
2946 2946 )
2947 2947 raise
2948 2948
2949 2949 def commithook(unused_success):
2950 2950 # hack for command that use a temporary commit (eg: histedit)
2951 2951 # temporary commit got stripped before hook release
2952 2952 if self.changelog.hasnode(ret):
2953 2953 self.hook(
2954 2954 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
2955 2955 )
2956 2956
2957 2957 self._afterlock(commithook)
2958 2958 return ret
2959 2959
2960 2960 @unfilteredmethod
2961 2961 def commitctx(self, ctx, error=False, origctx=None):
2962 2962 return commit.commitctx(self, ctx, error=error, origctx=origctx)
2963 2963
2964 2964 @unfilteredmethod
2965 2965 def destroying(self):
2966 2966 '''Inform the repository that nodes are about to be destroyed.
2967 2967 Intended for use by strip and rollback, so there's a common
2968 2968 place for anything that has to be done before destroying history.
2969 2969
2970 2970 This is mostly useful for saving state that is in memory and waiting
2971 2971 to be flushed when the current lock is released. Because a call to
2972 2972 destroyed is imminent, the repo will be invalidated causing those
2973 2973 changes to stay in memory (waiting for the next unlock), or vanish
2974 2974 completely.
2975 2975 '''
2976 2976 # When using the same lock to commit and strip, the phasecache is left
2977 2977 # dirty after committing. Then when we strip, the repo is invalidated,
2978 2978 # causing those changes to disappear.
2979 2979 if '_phasecache' in vars(self):
2980 2980 self._phasecache.write()
2981 2981
2982 2982 @unfilteredmethod
2983 2983 def destroyed(self):
2984 2984 '''Inform the repository that nodes have been destroyed.
2985 2985 Intended for use by strip and rollback, so there's a common
2986 2986 place for anything that has to be done after destroying history.
2987 2987 '''
2988 2988 # When one tries to:
2989 2989 # 1) destroy nodes thus calling this method (e.g. strip)
2990 2990 # 2) use phasecache somewhere (e.g. commit)
2991 2991 #
2992 2992 # then 2) will fail because the phasecache contains nodes that were
2993 2993 # removed. We can either remove phasecache from the filecache,
2994 2994 # causing it to reload next time it is accessed, or simply filter
2995 2995 # the removed nodes now and write the updated cache.
2996 2996 self._phasecache.filterunknown(self)
2997 2997 self._phasecache.write()
2998 2998
2999 2999 # refresh all repository caches
3000 3000 self.updatecaches()
3001 3001
3002 3002 # Ensure the persistent tag cache is updated. Doing it now
3003 3003 # means that the tag cache only has to worry about destroyed
3004 3004 # heads immediately after a strip/rollback. That in turn
3005 3005 # guarantees that "cachetip == currenttip" (comparing both rev
3006 3006 # and node) always means no nodes have been added or destroyed.
3007 3007
3008 3008 # XXX this is suboptimal when qrefresh'ing: we strip the current
3009 3009 # head, refresh the tag cache, then immediately add a new head.
3010 3010 # But I think doing it this way is necessary for the "instant
3011 3011 # tag cache retrieval" case to work.
3012 3012 self.invalidate()
3013 3013
3014 3014 def status(
3015 3015 self,
3016 3016 node1=b'.',
3017 3017 node2=None,
3018 3018 match=None,
3019 3019 ignored=False,
3020 3020 clean=False,
3021 3021 unknown=False,
3022 3022 listsubrepos=False,
3023 3023 ):
3024 3024 '''a convenience method that calls node1.status(node2)'''
3025 3025 return self[node1].status(
3026 3026 node2, match, ignored, clean, unknown, listsubrepos
3027 3027 )
3028 3028
3029 3029 def addpostdsstatus(self, ps):
3030 3030 """Add a callback to run within the wlock, at the point at which status
3031 3031 fixups happen.
3032 3032
3033 3033 On status completion, callback(wctx, status) will be called with the
3034 3034 wlock held, unless the dirstate has changed from underneath or the wlock
3035 3035 couldn't be grabbed.
3036 3036
3037 3037 Callbacks should not capture and use a cached copy of the dirstate --
3038 3038 it might change in the meanwhile. Instead, they should access the
3039 3039 dirstate via wctx.repo().dirstate.
3040 3040
3041 3041 This list is emptied out after each status run -- extensions should
3042 3042 make sure it adds to this list each time dirstate.status is called.
3043 3043 Extensions should also make sure they don't call this for statuses
3044 3044 that don't involve the dirstate.
3045 3045 """
3046 3046
3047 3047 # The list is located here for uniqueness reasons -- it is actually
3048 3048 # managed by the workingctx, but that isn't unique per-repo.
3049 3049 self._postdsstatus.append(ps)
3050 3050
3051 3051 def postdsstatus(self):
3052 3052 """Used by workingctx to get the list of post-dirstate-status hooks."""
3053 3053 return self._postdsstatus
3054 3054
3055 3055 def clearpostdsstatus(self):
3056 3056 """Used by workingctx to clear post-dirstate-status hooks."""
3057 3057 del self._postdsstatus[:]
3058 3058
3059 3059 def heads(self, start=None):
3060 3060 if start is None:
3061 3061 cl = self.changelog
3062 3062 headrevs = reversed(cl.headrevs())
3063 3063 return [cl.node(rev) for rev in headrevs]
3064 3064
3065 3065 heads = self.changelog.heads(start)
3066 3066 # sort the output in rev descending order
3067 3067 return sorted(heads, key=self.changelog.rev, reverse=True)
3068 3068
3069 3069 def branchheads(self, branch=None, start=None, closed=False):
3070 3070 '''return a (possibly filtered) list of heads for the given branch
3071 3071
3072 3072 Heads are returned in topological order, from newest to oldest.
3073 3073 If branch is None, use the dirstate branch.
3074 3074 If start is not None, return only heads reachable from start.
3075 3075 If closed is True, return heads that are marked as closed as well.
3076 3076 '''
3077 3077 if branch is None:
3078 3078 branch = self[None].branch()
3079 3079 branches = self.branchmap()
3080 3080 if not branches.hasbranch(branch):
3081 3081 return []
3082 3082 # the cache returns heads ordered lowest to highest
3083 3083 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3084 3084 if start is not None:
3085 3085 # filter out the heads that cannot be reached from startrev
3086 3086 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3087 3087 bheads = [h for h in bheads if h in fbheads]
3088 3088 return bheads
3089 3089
3090 3090 def branches(self, nodes):
3091 3091 if not nodes:
3092 3092 nodes = [self.changelog.tip()]
3093 3093 b = []
3094 3094 for n in nodes:
3095 3095 t = n
3096 3096 while True:
3097 3097 p = self.changelog.parents(n)
3098 3098 if p[1] != nullid or p[0] == nullid:
3099 3099 b.append((t, n, p[0], p[1]))
3100 3100 break
3101 3101 n = p[0]
3102 3102 return b
3103 3103
3104 3104 def between(self, pairs):
3105 3105 r = []
3106 3106
3107 3107 for top, bottom in pairs:
3108 3108 n, l, i = top, [], 0
3109 3109 f = 1
3110 3110
3111 3111 while n != bottom and n != nullid:
3112 3112 p = self.changelog.parents(n)[0]
3113 3113 if i == f:
3114 3114 l.append(n)
3115 3115 f = f * 2
3116 3116 n = p
3117 3117 i += 1
3118 3118
3119 3119 r.append(l)
3120 3120
3121 3121 return r
3122 3122
3123 3123 def checkpush(self, pushop):
3124 3124 """Extensions can override this function if additional checks have
3125 3125 to be performed before pushing, or call it if they override push
3126 3126 command.
3127 3127 """
3128 3128
3129 3129 @unfilteredpropertycache
3130 3130 def prepushoutgoinghooks(self):
3131 3131 """Return util.hooks consists of a pushop with repo, remote, outgoing
3132 3132 methods, which are called before pushing changesets.
3133 3133 """
3134 3134 return util.hooks()
3135 3135
3136 3136 def pushkey(self, namespace, key, old, new):
3137 3137 try:
3138 3138 tr = self.currenttransaction()
3139 3139 hookargs = {}
3140 3140 if tr is not None:
3141 3141 hookargs.update(tr.hookargs)
3142 3142 hookargs = pycompat.strkwargs(hookargs)
3143 3143 hookargs['namespace'] = namespace
3144 3144 hookargs['key'] = key
3145 3145 hookargs['old'] = old
3146 3146 hookargs['new'] = new
3147 3147 self.hook(b'prepushkey', throw=True, **hookargs)
3148 3148 except error.HookAbort as exc:
3149 3149 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3150 3150 if exc.hint:
3151 3151 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3152 3152 return False
3153 3153 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3154 3154 ret = pushkey.push(self, namespace, key, old, new)
3155 3155
3156 3156 def runhook(unused_success):
3157 3157 self.hook(
3158 3158 b'pushkey',
3159 3159 namespace=namespace,
3160 3160 key=key,
3161 3161 old=old,
3162 3162 new=new,
3163 3163 ret=ret,
3164 3164 )
3165 3165
3166 3166 self._afterlock(runhook)
3167 3167 return ret
3168 3168
3169 3169 def listkeys(self, namespace):
3170 3170 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3171 3171 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3172 3172 values = pushkey.list(self, namespace)
3173 3173 self.hook(b'listkeys', namespace=namespace, values=values)
3174 3174 return values
3175 3175
3176 3176 def debugwireargs(self, one, two, three=None, four=None, five=None):
3177 3177 '''used to test argument passing over the wire'''
3178 3178 return b"%s %s %s %s %s" % (
3179 3179 one,
3180 3180 two,
3181 3181 pycompat.bytestr(three),
3182 3182 pycompat.bytestr(four),
3183 3183 pycompat.bytestr(five),
3184 3184 )
3185 3185
3186 3186 def savecommitmessage(self, text):
3187 3187 fp = self.vfs(b'last-message.txt', b'wb')
3188 3188 try:
3189 3189 fp.write(text)
3190 3190 finally:
3191 3191 fp.close()
3192 3192 return self.pathto(fp.name[len(self.root) + 1 :])
3193 3193
3194 3194
3195 3195 # used to avoid circular references so destructors work
3196 3196 def aftertrans(files):
3197 3197 renamefiles = [tuple(t) for t in files]
3198 3198
3199 3199 def a():
3200 3200 for vfs, src, dest in renamefiles:
3201 3201 # if src and dest refer to a same file, vfs.rename is a no-op,
3202 3202 # leaving both src and dest on disk. delete dest to make sure
3203 3203 # the rename couldn't be such a no-op.
3204 3204 vfs.tryunlink(dest)
3205 3205 try:
3206 3206 vfs.rename(src, dest)
3207 3207 except OSError: # journal file does not yet exist
3208 3208 pass
3209 3209
3210 3210 return a
3211 3211
3212 3212
3213 3213 def undoname(fn):
3214 3214 base, name = os.path.split(fn)
3215 3215 assert name.startswith(b'journal')
3216 3216 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3217 3217
3218 3218
3219 3219 def instance(ui, path, create, intents=None, createopts=None):
3220 3220 localpath = util.urllocalpath(path)
3221 3221 if create:
3222 3222 createrepository(ui, localpath, createopts=createopts)
3223 3223
3224 3224 return makelocalrepository(ui, localpath, intents=intents)
3225 3225
3226 3226
3227 3227 def islocal(path):
3228 3228 return True
3229 3229
3230 3230
3231 3231 def defaultcreateopts(ui, createopts=None):
3232 3232 """Populate the default creation options for a repository.
3233 3233
3234 3234 A dictionary of explicitly requested creation options can be passed
3235 3235 in. Missing keys will be populated.
3236 3236 """
3237 3237 createopts = dict(createopts or {})
3238 3238
3239 3239 if b'backend' not in createopts:
3240 3240 # experimental config: storage.new-repo-backend
3241 3241 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3242 3242
3243 3243 return createopts
3244 3244
3245 3245
3246 3246 def newreporequirements(ui, createopts):
3247 3247 """Determine the set of requirements for a new local repository.
3248 3248
3249 3249 Extensions can wrap this function to specify custom requirements for
3250 3250 new repositories.
3251 3251 """
3252 3252 # If the repo is being created from a shared repository, we copy
3253 3253 # its requirements.
3254 3254 if b'sharedrepo' in createopts:
3255 3255 requirements = set(createopts[b'sharedrepo'].requirements)
3256 3256 if createopts.get(b'sharedrelative'):
3257 3257 requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT)
3258 3258 else:
3259 3259 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3260 3260
3261 3261 return requirements
3262 3262
3263 3263 if b'backend' not in createopts:
3264 3264 raise error.ProgrammingError(
3265 3265 b'backend key not present in createopts; '
3266 3266 b'was defaultcreateopts() called?'
3267 3267 )
3268 3268
3269 3269 if createopts[b'backend'] != b'revlogv1':
3270 3270 raise error.Abort(
3271 3271 _(
3272 3272 b'unable to determine repository requirements for '
3273 3273 b'storage backend: %s'
3274 3274 )
3275 3275 % createopts[b'backend']
3276 3276 )
3277 3277
3278 3278 requirements = {b'revlogv1'}
3279 3279 if ui.configbool(b'format', b'usestore'):
3280 3280 requirements.add(b'store')
3281 3281 if ui.configbool(b'format', b'usefncache'):
3282 3282 requirements.add(b'fncache')
3283 3283 if ui.configbool(b'format', b'dotencode'):
3284 3284 requirements.add(b'dotencode')
3285 3285
3286 3286 compengines = ui.configlist(b'format', b'revlog-compression')
3287 3287 for compengine in compengines:
3288 3288 if compengine in util.compengines:
3289 3289 break
3290 3290 else:
3291 3291 raise error.Abort(
3292 3292 _(
3293 3293 b'compression engines %s defined by '
3294 3294 b'format.revlog-compression not available'
3295 3295 )
3296 3296 % b', '.join(b'"%s"' % e for e in compengines),
3297 3297 hint=_(
3298 3298 b'run "hg debuginstall" to list available '
3299 3299 b'compression engines'
3300 3300 ),
3301 3301 )
3302 3302
3303 3303 # zlib is the historical default and doesn't need an explicit requirement.
3304 3304 if compengine == b'zstd':
3305 3305 requirements.add(b'revlog-compression-zstd')
3306 3306 elif compengine != b'zlib':
3307 3307 requirements.add(b'exp-compression-%s' % compengine)
3308 3308
3309 3309 if scmutil.gdinitconfig(ui):
3310 3310 requirements.add(b'generaldelta')
3311 3311 if ui.configbool(b'format', b'sparse-revlog'):
3312 3312 requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
3313 3313
3314 3314 # experimental config: format.exp-use-side-data
3315 3315 if ui.configbool(b'format', b'exp-use-side-data'):
3316 3316 requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
3317 3317 # experimental config: format.exp-use-copies-side-data-changeset
3318 3318 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3319 3319 requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
3320 3320 requirements.add(requirementsmod.COPIESSDC_REQUIREMENT)
3321 3321 if ui.configbool(b'experimental', b'treemanifest'):
3322 3322 requirements.add(requirementsmod.TREEMANIFEST_REQUIREMENT)
3323 3323
3324 3324 revlogv2 = ui.config(b'experimental', b'revlogv2')
3325 3325 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3326 3326 requirements.remove(b'revlogv1')
3327 3327 # generaldelta is implied by revlogv2.
3328 3328 requirements.discard(b'generaldelta')
3329 3329 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3330 3330 # experimental config: format.internal-phase
3331 3331 if ui.configbool(b'format', b'internal-phase'):
3332 3332 requirements.add(requirementsmod.INTERNAL_PHASE_REQUIREMENT)
3333 3333
3334 3334 if createopts.get(b'narrowfiles'):
3335 3335 requirements.add(requirementsmod.NARROW_REQUIREMENT)
3336 3336
3337 3337 if createopts.get(b'lfs'):
3338 3338 requirements.add(b'lfs')
3339 3339
3340 3340 if ui.configbool(b'format', b'bookmarks-in-store'):
3341 3341 requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3342 3342
3343 3343 if ui.configbool(b'format', b'use-persistent-nodemap'):
3344 3344 requirements.add(requirementsmod.NODEMAP_REQUIREMENT)
3345 3345
3346 3346 # if share-safe is enabled, let's create the new repository with the new
3347 3347 # requirement
3348 3348 if ui.configbool(b'format', b'exp-share-safe'):
3349 3349 requirements.add(requirementsmod.SHARESAFE_REQUIREMENT)
3350 3350
3351 3351 return requirements
3352 3352
3353 3353
3354 3354 def checkrequirementscompat(ui, requirements):
3355 3355 """ Checks compatibility of repository requirements enabled and disabled.
3356 3356
3357 3357 Returns a set of requirements which needs to be dropped because dependend
3358 3358 requirements are not enabled. Also warns users about it """
3359 3359
3360 3360 dropped = set()
3361 3361
3362 3362 if b'store' not in requirements:
3363 3363 if bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT in requirements:
3364 3364 ui.warn(
3365 3365 _(
3366 3366 b'ignoring enabled \'format.bookmarks-in-store\' config '
3367 3367 b'beacuse it is incompatible with disabled '
3368 3368 b'\'format.usestore\' config\n'
3369 3369 )
3370 3370 )
3371 3371 dropped.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3372 3372
3373 3373 if (
3374 3374 requirementsmod.SHARED_REQUIREMENT in requirements
3375 3375 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
3376 3376 ):
3377 3377 raise error.Abort(
3378 3378 _(
3379 3379 b"cannot create shared repository as source was created"
3380 3380 b" with 'format.usestore' config disabled"
3381 3381 )
3382 3382 )
3383 3383
3384 3384 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
3385 3385 ui.warn(
3386 3386 _(
3387 3387 b"ignoring enabled 'format.exp-share-safe' config because "
3388 3388 b"it is incompatible with disabled 'format.usestore'"
3389 3389 b" config\n"
3390 3390 )
3391 3391 )
3392 3392 dropped.add(requirementsmod.SHARESAFE_REQUIREMENT)
3393 3393
3394 3394 return dropped
3395 3395
3396 3396
3397 3397 def filterknowncreateopts(ui, createopts):
3398 3398 """Filters a dict of repo creation options against options that are known.
3399 3399
3400 3400 Receives a dict of repo creation options and returns a dict of those
3401 3401 options that we don't know how to handle.
3402 3402
3403 3403 This function is called as part of repository creation. If the
3404 3404 returned dict contains any items, repository creation will not
3405 3405 be allowed, as it means there was a request to create a repository
3406 3406 with options not recognized by loaded code.
3407 3407
3408 3408 Extensions can wrap this function to filter out creation options
3409 3409 they know how to handle.
3410 3410 """
3411 3411 known = {
3412 3412 b'backend',
3413 3413 b'lfs',
3414 3414 b'narrowfiles',
3415 3415 b'sharedrepo',
3416 3416 b'sharedrelative',
3417 3417 b'shareditems',
3418 3418 b'shallowfilestore',
3419 3419 }
3420 3420
3421 3421 return {k: v for k, v in createopts.items() if k not in known}
3422 3422
3423 3423
3424 3424 def createrepository(ui, path, createopts=None):
3425 3425 """Create a new repository in a vfs.
3426 3426
3427 3427 ``path`` path to the new repo's working directory.
3428 3428 ``createopts`` options for the new repository.
3429 3429
3430 3430 The following keys for ``createopts`` are recognized:
3431 3431
3432 3432 backend
3433 3433 The storage backend to use.
3434 3434 lfs
3435 3435 Repository will be created with ``lfs`` requirement. The lfs extension
3436 3436 will automatically be loaded when the repository is accessed.
3437 3437 narrowfiles
3438 3438 Set up repository to support narrow file storage.
3439 3439 sharedrepo
3440 3440 Repository object from which storage should be shared.
3441 3441 sharedrelative
3442 3442 Boolean indicating if the path to the shared repo should be
3443 3443 stored as relative. By default, the pointer to the "parent" repo
3444 3444 is stored as an absolute path.
3445 3445 shareditems
3446 3446 Set of items to share to the new repository (in addition to storage).
3447 3447 shallowfilestore
3448 3448 Indicates that storage for files should be shallow (not all ancestor
3449 3449 revisions are known).
3450 3450 """
3451 3451 createopts = defaultcreateopts(ui, createopts=createopts)
3452 3452
3453 3453 unknownopts = filterknowncreateopts(ui, createopts)
3454 3454
3455 3455 if not isinstance(unknownopts, dict):
3456 3456 raise error.ProgrammingError(
3457 3457 b'filterknowncreateopts() did not return a dict'
3458 3458 )
3459 3459
3460 3460 if unknownopts:
3461 3461 raise error.Abort(
3462 3462 _(
3463 3463 b'unable to create repository because of unknown '
3464 3464 b'creation option: %s'
3465 3465 )
3466 3466 % b', '.join(sorted(unknownopts)),
3467 3467 hint=_(b'is a required extension not loaded?'),
3468 3468 )
3469 3469
3470 3470 requirements = newreporequirements(ui, createopts=createopts)
3471 3471 requirements -= checkrequirementscompat(ui, requirements)
3472 3472
3473 3473 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3474 3474
3475 3475 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3476 3476 if hgvfs.exists():
3477 3477 raise error.RepoError(_(b'repository %s already exists') % path)
3478 3478
3479 3479 if b'sharedrepo' in createopts:
3480 3480 sharedpath = createopts[b'sharedrepo'].sharedpath
3481 3481
3482 3482 if createopts.get(b'sharedrelative'):
3483 3483 try:
3484 3484 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3485 3485 except (IOError, ValueError) as e:
3486 3486 # ValueError is raised on Windows if the drive letters differ
3487 3487 # on each path.
3488 3488 raise error.Abort(
3489 3489 _(b'cannot calculate relative path'),
3490 3490 hint=stringutil.forcebytestr(e),
3491 3491 )
3492 3492
3493 3493 if not wdirvfs.exists():
3494 3494 wdirvfs.makedirs()
3495 3495
3496 3496 hgvfs.makedir(notindexed=True)
3497 3497 if b'sharedrepo' not in createopts:
3498 3498 hgvfs.mkdir(b'cache')
3499 3499 hgvfs.mkdir(b'wcache')
3500 3500
3501 3501 if b'store' in requirements and b'sharedrepo' not in createopts:
3502 3502 hgvfs.mkdir(b'store')
3503 3503
3504 3504 # We create an invalid changelog outside the store so very old
3505 3505 # Mercurial versions (which didn't know about the requirements
3506 3506 # file) encounter an error on reading the changelog. This
3507 3507 # effectively locks out old clients and prevents them from
3508 3508 # mucking with a repo in an unknown format.
3509 3509 #
3510 3510 # The revlog header has version 2, which won't be recognized by
3511 3511 # such old clients.
3512 3512 hgvfs.append(
3513 3513 b'00changelog.i',
3514 3514 b'\0\0\0\2 dummy changelog to prevent using the old repo '
3515 3515 b'layout',
3516 3516 )
3517 3517
3518 3518 # Filter the requirements into working copy and store ones
3519 3519 wcreq, storereq = scmutil.filterrequirements(requirements)
3520 3520 # write working copy ones
3521 3521 scmutil.writerequires(hgvfs, wcreq)
3522 3522 # If there are store requirements and the current repository
3523 3523 # is not a shared one, write stored requirements
3524 3524 # For new shared repository, we don't need to write the store
3525 3525 # requirements as they are already present in store requires
3526 3526 if storereq and b'sharedrepo' not in createopts:
3527 3527 storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True)
3528 3528 scmutil.writerequires(storevfs, storereq)
3529 3529
3530 3530 # Write out file telling readers where to find the shared store.
3531 3531 if b'sharedrepo' in createopts:
3532 3532 hgvfs.write(b'sharedpath', sharedpath)
3533 3533
3534 3534 if createopts.get(b'shareditems'):
3535 3535 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
3536 3536 hgvfs.write(b'shared', shared)
3537 3537
3538 3538
3539 3539 def poisonrepository(repo):
3540 3540 """Poison a repository instance so it can no longer be used."""
3541 3541 # Perform any cleanup on the instance.
3542 3542 repo.close()
3543 3543
3544 3544 # Our strategy is to replace the type of the object with one that
3545 3545 # has all attribute lookups result in error.
3546 3546 #
3547 3547 # But we have to allow the close() method because some constructors
3548 3548 # of repos call close() on repo references.
3549 3549 class poisonedrepository(object):
3550 3550 def __getattribute__(self, item):
3551 3551 if item == 'close':
3552 3552 return object.__getattribute__(self, item)
3553 3553
3554 3554 raise error.ProgrammingError(
3555 3555 b'repo instances should not be used after unshare'
3556 3556 )
3557 3557
3558 3558 def close(self):
3559 3559 pass
3560 3560
3561 3561 # We may have a repoview, which intercepts __setattr__. So be sure
3562 3562 # we operate at the lowest level possible.
3563 3563 object.__setattr__(repo, '__class__', poisonedrepository)
@@ -1,1984 +1,1984 b''
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 [255]
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 [255]
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 [255]
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 [255]
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 1716 The #if execbit block above changes the hash here on some systems
1717 1717 $ hg status -A plain3
1718 1718 C plain3
1719 1719 $ hg tip
1720 1720 changeset: 32:* (glob)
1721 1721 tag: tip
1722 1722 user: test
1723 1723 date: Thu Jan 01 00:00:23 1970 +0000
1724 1724 summary: moving_files
1725 1725
1726 1726 Editing patch of newly added file
1727 1727
1728 1728 $ hg update -C .
1729 1729 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1730 1730 $ cat > editor.sh << '__EOF__'
1731 1731 > cat "$1" | sed "s/first/very/g" > tt
1732 1732 > mv tt "$1"
1733 1733 > __EOF__
1734 1734 $ cat > newfile << '__EOF__'
1735 1735 > This is the first line
1736 1736 > This is the second line
1737 1737 > This is the third line
1738 1738 > __EOF__
1739 1739 $ hg add newfile
1740 1740 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-new <<EOF
1741 1741 > y
1742 1742 > e
1743 1743 > EOF
1744 1744 diff --git a/newfile b/newfile
1745 1745 new file mode 100644
1746 1746 examine changes to 'newfile'?
1747 1747 (enter ? for help) [Ynesfdaq?] y
1748 1748
1749 1749 @@ -0,0 +1,3 @@
1750 1750 +This is the first line
1751 1751 +This is the second line
1752 1752 +This is the third line
1753 1753 record this change to 'newfile'?
1754 1754 (enter ? for help) [Ynesfdaq?] e
1755 1755
1756 1756 $ hg cat -r tip newfile
1757 1757 This is the very line
1758 1758 This is the second line
1759 1759 This is the third line
1760 1760
1761 1761 $ cat newfile
1762 1762 This is the first line
1763 1763 This is the second line
1764 1764 This is the third line
1765 1765
1766 1766 Add new file from within a subdirectory
1767 1767 $ hg update -C .
1768 1768 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1769 1769 $ mkdir folder
1770 1770 $ cd folder
1771 1771 $ echo "foo" > bar
1772 1772 $ hg add bar
1773 1773 $ hg commit -i -d '23 0' -mnewfilesubdir <<EOF
1774 1774 > y
1775 1775 > y
1776 1776 > EOF
1777 1777 diff --git a/folder/bar b/folder/bar
1778 1778 new file mode 100644
1779 1779 examine changes to 'folder/bar'?
1780 1780 (enter ? for help) [Ynesfdaq?] y
1781 1781
1782 1782 @@ -0,0 +1,1 @@
1783 1783 +foo
1784 1784 record this change to 'folder/bar'?
1785 1785 (enter ? for help) [Ynesfdaq?] y
1786 1786
1787 1787 The #if execbit block above changes the hashes here on some systems
1788 1788 $ hg tip -p
1789 1789 changeset: 34:* (glob)
1790 1790 tag: tip
1791 1791 user: test
1792 1792 date: Thu Jan 01 00:00:23 1970 +0000
1793 1793 summary: newfilesubdir
1794 1794
1795 1795 diff -r * -r * folder/bar (glob)
1796 1796 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1797 1797 +++ b/folder/bar Thu Jan 01 00:00:23 1970 +0000
1798 1798 @@ -0,0 +1,1 @@
1799 1799 +foo
1800 1800
1801 1801 $ cd ..
1802 1802
1803 1803 $ hg status -A folder/bar
1804 1804 C folder/bar
1805 1805
1806 1806 Clear win32text configuration before size/timestamp sensitive test
1807 1807
1808 1808 $ cat >> .hg/hgrc <<EOF
1809 1809 > [extensions]
1810 1810 > win32text = !
1811 1811 > [decode]
1812 1812 > ** = !
1813 1813 > [encode]
1814 1814 > ** = !
1815 1815 > [patch]
1816 1816 > eol = strict
1817 1817 > EOF
1818 1818 $ hg update -q -C null
1819 1819 $ hg update -q -C tip
1820 1820
1821 1821 Test that partially committed file is still treated as "modified",
1822 1822 even if none of mode, size and timestamp is changed on the filesystem
1823 1823 (see also issue4583).
1824 1824
1825 1825 $ cat > subdir/f1 <<EOF
1826 1826 > A
1827 1827 > a
1828 1828 > a
1829 1829 > b
1830 1830 > c
1831 1831 > d
1832 1832 > E
1833 1833 > EOF
1834 1834 $ hg diff --git subdir/f1
1835 1835 diff --git a/subdir/f1 b/subdir/f1
1836 1836 --- a/subdir/f1
1837 1837 +++ b/subdir/f1
1838 1838 @@ -1,7 +1,7 @@
1839 1839 -a
1840 1840 +A
1841 1841 a
1842 1842 a
1843 1843 b
1844 1844 c
1845 1845 d
1846 1846 -e
1847 1847 +E
1848 1848
1849 1849 $ touch -t 200001010000 subdir/f1
1850 1850
1851 1851 $ cat >> .hg/hgrc <<EOF
1852 1852 > # emulate invoking patch.internalpatch() at 2000-01-01 00:00
1853 1853 > [fakepatchtime]
1854 1854 > fakenow = 200001010000
1855 1855 >
1856 1856 > [extensions]
1857 1857 > fakepatchtime = $TESTDIR/fakepatchtime.py
1858 1858 > EOF
1859 1859 $ hg commit -i -m 'commit subdir/f1 partially' <<EOF
1860 1860 > y
1861 1861 > y
1862 1862 > n
1863 1863 > EOF
1864 1864 diff --git a/subdir/f1 b/subdir/f1
1865 1865 2 hunks, 2 lines changed
1866 1866 examine changes to 'subdir/f1'?
1867 1867 (enter ? for help) [Ynesfdaq?] y
1868 1868
1869 1869 @@ -1,6 +1,6 @@
1870 1870 -a
1871 1871 +A
1872 1872 a
1873 1873 a
1874 1874 b
1875 1875 c
1876 1876 d
1877 1877 record change 1/2 to 'subdir/f1'?
1878 1878 (enter ? for help) [Ynesfdaq?] y
1879 1879
1880 1880 @@ -2,6 +2,6 @@
1881 1881 a
1882 1882 a
1883 1883 b
1884 1884 c
1885 1885 d
1886 1886 -e
1887 1887 +E
1888 1888 record change 2/2 to 'subdir/f1'?
1889 1889 (enter ? for help) [Ynesfdaq?] n
1890 1890
1891 1891 $ cat >> .hg/hgrc <<EOF
1892 1892 > [extensions]
1893 1893 > fakepatchtime = !
1894 1894 > EOF
1895 1895
1896 1896 $ hg debugstate | grep ' subdir/f1$'
1897 1897 n 0 -1 unset subdir/f1
1898 1898 $ hg status -A subdir/f1
1899 1899 M subdir/f1
1900 1900
1901 1901 Test commands.commit.interactive.unified=0
1902 1902
1903 1903 $ hg init $TESTTMP/b
1904 1904 $ cd $TESTTMP/b
1905 1905 $ cat > foo <<EOF
1906 1906 > 1
1907 1907 > 2
1908 1908 > 3
1909 1909 > 4
1910 1910 > 5
1911 1911 > EOF
1912 1912 $ hg ci -qAm initial
1913 1913 $ cat > foo <<EOF
1914 1914 > 1
1915 1915 > change1
1916 1916 > 2
1917 1917 > 3
1918 1918 > change2
1919 1919 > 4
1920 1920 > 5
1921 1921 > EOF
1922 1922 $ printf 'y\ny\ny\n' | hg ci -im initial --config commands.commit.interactive.unified=0
1923 1923 diff --git a/foo b/foo
1924 1924 2 hunks, 2 lines changed
1925 1925 examine changes to 'foo'?
1926 1926 (enter ? for help) [Ynesfdaq?] y
1927 1927
1928 1928 @@ -1,0 +2,1 @@ 1
1929 1929 +change1
1930 1930 record change 1/2 to 'foo'?
1931 1931 (enter ? for help) [Ynesfdaq?] y
1932 1932
1933 1933 @@ -3,0 +5,1 @@ 3
1934 1934 +change2
1935 1935 record change 2/2 to 'foo'?
1936 1936 (enter ? for help) [Ynesfdaq?] y
1937 1937
1938 1938 $ cd $TESTTMP
1939 1939
1940 1940 Test diff.ignoreblanklines=1
1941 1941
1942 1942 $ hg init c
1943 1943 $ cd c
1944 1944 $ cat > foo <<EOF
1945 1945 > 1
1946 1946 > 2
1947 1947 > 3
1948 1948 > 4
1949 1949 > 5
1950 1950 > EOF
1951 1951 $ hg ci -qAm initial
1952 1952 $ cat > foo <<EOF
1953 1953 > 1
1954 1954 >
1955 1955 > 2
1956 1956 > 3
1957 1957 > change2
1958 1958 > 4
1959 1959 > 5
1960 1960 > EOF
1961 1961 $ printf 'y\ny\ny\n' | hg ci -im initial --config diff.ignoreblanklines=1
1962 1962 diff --git a/foo b/foo
1963 1963 2 hunks, 2 lines changed
1964 1964 examine changes to 'foo'?
1965 1965 (enter ? for help) [Ynesfdaq?] y
1966 1966
1967 1967 @@ -1,3 +1,4 @@
1968 1968 1
1969 1969 +
1970 1970 2
1971 1971 3
1972 1972 record change 1/2 to 'foo'?
1973 1973 (enter ? for help) [Ynesfdaq?] y
1974 1974
1975 1975 @@ -2,4 +3,5 @@
1976 1976 2
1977 1977 3
1978 1978 +change2
1979 1979 4
1980 1980 5
1981 1981 record change 2/2 to 'foo'?
1982 1982 (enter ? for help) [Ynesfdaq?] y
1983 1983
1984 1984
@@ -1,879 +1,879 b''
1 1 commit date test
2 2
3 3 $ hg init test
4 4 $ cd test
5 5 $ echo foo > foo
6 6 $ hg add foo
7 7 $ cat > $TESTTMP/checkeditform.sh <<EOF
8 8 > env | grep HGEDITFORM
9 9 > true
10 10 > EOF
11 11 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg commit -m ""
12 12 HGEDITFORM=commit.normal.normal
13 13 abort: empty commit message
14 14 [10]
15 15 $ hg commit -d '0 0' -m commit-1
16 16 $ echo foo >> foo
17 17 $ hg commit -d '1 4444444' -m commit-3
18 18 hg: parse error: impossible time zone offset: 4444444
19 19 [255]
20 20 $ hg commit -d '1 15.1' -m commit-4
21 21 hg: parse error: invalid date: '1\t15.1'
22 22 [255]
23 23 $ hg commit -d 'foo bar' -m commit-5
24 24 hg: parse error: invalid date: 'foo bar'
25 25 [255]
26 26 $ hg commit -d ' 1 4444' -m commit-6
27 27 $ hg commit -d '111111111111 0' -m commit-7
28 28 hg: parse error: date exceeds 32 bits: 111111111111
29 29 [255]
30 30 $ hg commit -d '-111111111111 0' -m commit-7
31 31 hg: parse error: date exceeds 32 bits: -111111111111
32 32 [255]
33 33 $ echo foo >> foo
34 34 $ hg commit -d '1901-12-13 20:45:52 +0000' -m commit-7-2
35 35 $ echo foo >> foo
36 36 $ hg commit -d '-2147483648 0' -m commit-7-3
37 37 $ hg log -T '{rev} {date|isodatesec}\n' -l2
38 38 3 1901-12-13 20:45:52 +0000
39 39 2 1901-12-13 20:45:52 +0000
40 40 $ hg commit -d '1901-12-13 20:45:51 +0000' -m commit-7
41 41 hg: parse error: date exceeds 32 bits: -2147483649
42 42 [255]
43 43 $ hg commit -d '-2147483649 0' -m commit-7
44 44 hg: parse error: date exceeds 32 bits: -2147483649
45 45 [255]
46 46
47 47 commit added file that has been deleted
48 48
49 49 $ echo bar > bar
50 50 $ hg add bar
51 51 $ rm bar
52 52 $ hg commit -m commit-8
53 53 nothing changed (1 missing files, see 'hg status')
54 54 [1]
55 55 $ hg commit -m commit-8-2 bar
56 56 abort: bar: file not found!
57 [255]
57 [10]
58 58
59 59 $ hg -q revert -a --no-backup
60 60
61 61 $ mkdir dir
62 62 $ echo boo > dir/file
63 63 $ hg add
64 64 adding dir/file
65 65 $ hg -v commit -m commit-9 dir
66 66 committing files:
67 67 dir/file
68 68 committing manifest
69 69 committing changelog
70 70 committed changeset 4:1957363f1ced
71 71
72 72 $ echo > dir.file
73 73 $ hg add
74 74 adding dir.file
75 75 $ hg commit -m commit-10 dir dir.file
76 76 abort: dir: no match under directory!
77 [255]
77 [10]
78 78
79 79 $ echo >> dir/file
80 80 $ mkdir bleh
81 81 $ mkdir dir2
82 82 $ cd bleh
83 83 $ hg commit -m commit-11 .
84 84 abort: bleh: no match under directory!
85 [255]
85 [10]
86 86 $ hg commit -m commit-12 ../dir ../dir2
87 87 abort: dir2: no match under directory!
88 [255]
88 [10]
89 89 $ hg -v commit -m commit-13 ../dir
90 90 committing files:
91 91 dir/file
92 92 committing manifest
93 93 committing changelog
94 94 committed changeset 5:a31d8f87544a
95 95 $ cd ..
96 96
97 97 $ hg commit -m commit-14 does-not-exist
98 98 abort: does-not-exist: * (glob)
99 [255]
99 [10]
100 100
101 101 #if symlink
102 102 $ ln -s foo baz
103 103 $ hg commit -m commit-15 baz
104 104 abort: baz: file not tracked!
105 [255]
105 [10]
106 106 $ rm baz
107 107 #endif
108 108
109 109 $ touch quux
110 110 $ hg commit -m commit-16 quux
111 111 abort: quux: file not tracked!
112 [255]
112 [10]
113 113 $ echo >> dir/file
114 114 $ hg -v commit -m commit-17 dir/file
115 115 committing files:
116 116 dir/file
117 117 committing manifest
118 118 committing changelog
119 119 committed changeset 6:32d054c9d085
120 120
121 121 An empty date was interpreted as epoch origin
122 122
123 123 $ echo foo >> foo
124 124 $ hg commit -d '' -m commit-no-date --config devel.default-date=
125 125 $ hg tip --template '{date|isodate}\n' | grep '1970'
126 126 [1]
127 127
128 128 Using the advanced --extra flag
129 129
130 130 $ echo "[extensions]" >> $HGRCPATH
131 131 $ echo "commitextras=" >> $HGRCPATH
132 132 $ hg status
133 133 ? quux
134 134 $ hg add quux
135 135 $ hg commit -m "adding internal used extras" --extra amend_source=hash
136 136 abort: key 'amend_source' is used internally, can't be set manually
137 137 [255]
138 138 $ hg commit -m "special chars in extra" --extra id@phab=214
139 139 abort: keys can only contain ascii letters, digits, '_' and '-'
140 140 [255]
141 141 $ hg commit -m "empty key" --extra =value
142 142 abort: unable to parse '=value', keys can't be empty
143 143 [255]
144 144 $ hg commit -m "adding extras" --extra sourcehash=foo --extra oldhash=bar
145 145 $ hg log -r . -T '{extras % "{extra}\n"}'
146 146 branch=default
147 147 oldhash=bar
148 148 sourcehash=foo
149 149
150 150 Failed commit with --addremove should not update dirstate
151 151
152 152 $ echo foo > newfile
153 153 $ hg status
154 154 ? newfile
155 155 $ HGEDITOR=false hg ci --addremove
156 156 adding newfile
157 157 abort: edit failed: false exited with status 1
158 158 [255]
159 159 $ hg status
160 160 ? newfile
161 161
162 162 Make sure we do not obscure unknown requires file entries (issue2649)
163 163
164 164 $ echo foo >> foo
165 165 $ echo fake >> .hg/requires
166 166 $ hg commit -m bla
167 167 abort: repository requires features unknown to this Mercurial: fake!
168 168 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
169 169 [255]
170 170
171 171 $ cd ..
172 172
173 173
174 174 partial subdir commit test
175 175
176 176 $ hg init test2
177 177 $ cd test2
178 178 $ mkdir foo
179 179 $ echo foo > foo/foo
180 180 $ mkdir bar
181 181 $ echo bar > bar/bar
182 182 $ hg add
183 183 adding bar/bar
184 184 adding foo/foo
185 185 $ HGEDITOR=cat hg ci -e -m commit-subdir-1 foo
186 186 commit-subdir-1
187 187
188 188
189 189 HG: Enter commit message. Lines beginning with 'HG:' are removed.
190 190 HG: Leave message empty to abort commit.
191 191 HG: --
192 192 HG: user: test
193 193 HG: branch 'default'
194 194 HG: added foo/foo
195 195
196 196
197 197 $ hg ci -m commit-subdir-2 bar
198 198
199 199 subdir log 1
200 200
201 201 $ hg log -v foo
202 202 changeset: 0:f97e73a25882
203 203 user: test
204 204 date: Thu Jan 01 00:00:00 1970 +0000
205 205 files: foo/foo
206 206 description:
207 207 commit-subdir-1
208 208
209 209
210 210
211 211 subdir log 2
212 212
213 213 $ hg log -v bar
214 214 changeset: 1:aa809156d50d
215 215 tag: tip
216 216 user: test
217 217 date: Thu Jan 01 00:00:00 1970 +0000
218 218 files: bar/bar
219 219 description:
220 220 commit-subdir-2
221 221
222 222
223 223
224 224 full log
225 225
226 226 $ hg log -v
227 227 changeset: 1:aa809156d50d
228 228 tag: tip
229 229 user: test
230 230 date: Thu Jan 01 00:00:00 1970 +0000
231 231 files: bar/bar
232 232 description:
233 233 commit-subdir-2
234 234
235 235
236 236 changeset: 0:f97e73a25882
237 237 user: test
238 238 date: Thu Jan 01 00:00:00 1970 +0000
239 239 files: foo/foo
240 240 description:
241 241 commit-subdir-1
242 242
243 243
244 244 $ cd ..
245 245
246 246
247 247 dot and subdir commit test
248 248
249 249 $ hg init test3
250 250 $ echo commit-foo-subdir > commit-log-test
251 251 $ cd test3
252 252 $ mkdir foo
253 253 $ echo foo content > foo/plain-file
254 254 $ hg add foo/plain-file
255 255 $ HGEDITOR=cat hg ci --edit -l ../commit-log-test foo
256 256 commit-foo-subdir
257 257
258 258
259 259 HG: Enter commit message. Lines beginning with 'HG:' are removed.
260 260 HG: Leave message empty to abort commit.
261 261 HG: --
262 262 HG: user: test
263 263 HG: branch 'default'
264 264 HG: added foo/plain-file
265 265
266 266
267 267 $ echo modified foo content > foo/plain-file
268 268 $ hg ci -m commit-foo-dot .
269 269
270 270 full log
271 271
272 272 $ hg log -v
273 273 changeset: 1:95b38e3a5b2e
274 274 tag: tip
275 275 user: test
276 276 date: Thu Jan 01 00:00:00 1970 +0000
277 277 files: foo/plain-file
278 278 description:
279 279 commit-foo-dot
280 280
281 281
282 282 changeset: 0:65d4e9386227
283 283 user: test
284 284 date: Thu Jan 01 00:00:00 1970 +0000
285 285 files: foo/plain-file
286 286 description:
287 287 commit-foo-subdir
288 288
289 289
290 290
291 291 subdir log
292 292
293 293 $ cd foo
294 294 $ hg log .
295 295 changeset: 1:95b38e3a5b2e
296 296 tag: tip
297 297 user: test
298 298 date: Thu Jan 01 00:00:00 1970 +0000
299 299 summary: commit-foo-dot
300 300
301 301 changeset: 0:65d4e9386227
302 302 user: test
303 303 date: Thu Jan 01 00:00:00 1970 +0000
304 304 summary: commit-foo-subdir
305 305
306 306 $ cd ..
307 307 $ cd ..
308 308
309 309 Issue1049: Hg permits partial commit of merge without warning
310 310
311 311 $ hg init issue1049
312 312 $ cd issue1049
313 313 $ echo a > a
314 314 $ hg ci -Ama
315 315 adding a
316 316 $ echo a >> a
317 317 $ hg ci -mb
318 318 $ hg up 0
319 319 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
320 320 $ echo b >> a
321 321 $ hg ci -mc
322 322 created new head
323 323 $ HGMERGE=true hg merge
324 324 merging a
325 325 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
326 326 (branch merge, don't forget to commit)
327 327
328 328 should fail because we are specifying a file name
329 329
330 330 $ hg ci -mmerge a
331 331 abort: cannot partially commit a merge (do not specify files or patterns)
332 332 [255]
333 333
334 334 should fail because we are specifying a pattern
335 335
336 336 $ hg ci -mmerge -I a
337 337 abort: cannot partially commit a merge (do not specify files or patterns)
338 338 [255]
339 339
340 340 should succeed
341 341
342 342 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg ci -mmerge --edit
343 343 HGEDITFORM=commit.normal.merge
344 344 $ cd ..
345 345
346 346
347 347 test commit message content
348 348
349 349 $ hg init commitmsg
350 350 $ cd commitmsg
351 351 $ echo changed > changed
352 352 $ echo removed > removed
353 353 $ hg book activebookmark
354 354 $ hg ci -qAm init
355 355
356 356 $ hg rm removed
357 357 $ echo changed >> changed
358 358 $ echo added > added
359 359 $ hg add added
360 360 $ HGEDITOR=cat hg ci -A
361 361
362 362
363 363 HG: Enter commit message. Lines beginning with 'HG:' are removed.
364 364 HG: Leave message empty to abort commit.
365 365 HG: --
366 366 HG: user: test
367 367 HG: branch 'default'
368 368 HG: bookmark 'activebookmark'
369 369 HG: added added
370 370 HG: changed changed
371 371 HG: removed removed
372 372 abort: empty commit message
373 373 [10]
374 374
375 375 test saving last-message.txt
376 376
377 377 $ hg init sub
378 378 $ echo a > sub/a
379 379 $ hg -R sub add sub/a
380 380 $ cat > sub/.hg/hgrc <<EOF
381 381 > [hooks]
382 382 > precommit.test-saving-last-message = false
383 383 > EOF
384 384
385 385 $ echo 'sub = sub' > .hgsub
386 386 $ hg add .hgsub
387 387
388 388 $ cat > $TESTTMP/editor.sh <<EOF
389 389 > echo "==== before editing:"
390 390 > cat \$1
391 391 > echo "===="
392 392 > echo "test saving last-message.txt" >> \$1
393 393 > EOF
394 394
395 395 $ rm -f .hg/last-message.txt
396 396 $ HGEDITOR="sh $TESTTMP/editor.sh" hg commit -S -q
397 397 ==== before editing:
398 398
399 399
400 400 HG: Enter commit message. Lines beginning with 'HG:' are removed.
401 401 HG: Leave message empty to abort commit.
402 402 HG: --
403 403 HG: user: test
404 404 HG: branch 'default'
405 405 HG: bookmark 'activebookmark'
406 406 HG: subrepo sub
407 407 HG: added .hgsub
408 408 HG: added added
409 409 HG: changed .hgsubstate
410 410 HG: changed changed
411 411 HG: removed removed
412 412 ====
413 413 abort: precommit.test-saving-last-message hook exited with status 1 (in subrepository "sub")
414 414 [255]
415 415 $ cat .hg/last-message.txt
416 416
417 417
418 418 test saving last-message.txt
419 419
420 420 test that '[committemplate] changeset' definition and commit log
421 421 specific template keywords work well
422 422
423 423 $ cat >> .hg/hgrc <<EOF
424 424 > [committemplate]
425 425 > changeset.commit.normal = 'HG: this is "commit.normal" template
426 426 > HG: {extramsg}
427 427 > {if(activebookmark,
428 428 > "HG: bookmark '{activebookmark}' is activated\n",
429 429 > "HG: no bookmark is activated\n")}{subrepos %
430 430 > "HG: subrepo '{subrepo}' is changed\n"}'
431 431 >
432 432 > changeset.commit = HG: this is "commit" template
433 433 > HG: {extramsg}
434 434 > {if(activebookmark,
435 435 > "HG: bookmark '{activebookmark}' is activated\n",
436 436 > "HG: no bookmark is activated\n")}{subrepos %
437 437 > "HG: subrepo '{subrepo}' is changed\n"}
438 438 >
439 439 > changeset = HG: this is customized commit template
440 440 > HG: {extramsg}
441 441 > {if(activebookmark,
442 442 > "HG: bookmark '{activebookmark}' is activated\n",
443 443 > "HG: no bookmark is activated\n")}{subrepos %
444 444 > "HG: subrepo '{subrepo}' is changed\n"}
445 445 > EOF
446 446
447 447 $ hg init sub2
448 448 $ echo a > sub2/a
449 449 $ hg -R sub2 add sub2/a
450 450 $ echo 'sub2 = sub2' >> .hgsub
451 451
452 452 $ HGEDITOR=cat hg commit -S -q
453 453 HG: this is "commit.normal" template
454 454 HG: Leave message empty to abort commit.
455 455 HG: bookmark 'activebookmark' is activated
456 456 HG: subrepo 'sub' is changed
457 457 HG: subrepo 'sub2' is changed
458 458 abort: empty commit message
459 459 [10]
460 460
461 461 $ cat >> .hg/hgrc <<EOF
462 462 > [committemplate]
463 463 > changeset.commit.normal =
464 464 > # now, "changeset.commit" should be chosen for "hg commit"
465 465 > EOF
466 466
467 467 $ hg bookmark --inactive activebookmark
468 468 $ hg forget .hgsub
469 469 $ HGEDITOR=cat hg commit -q
470 470 HG: this is "commit" template
471 471 HG: Leave message empty to abort commit.
472 472 HG: no bookmark is activated
473 473 abort: empty commit message
474 474 [10]
475 475
476 476 $ cat >> .hg/hgrc <<EOF
477 477 > [committemplate]
478 478 > changeset.commit =
479 479 > # now, "changeset" should be chosen for "hg commit"
480 480 > EOF
481 481
482 482 $ HGEDITOR=cat hg commit -q
483 483 HG: this is customized commit template
484 484 HG: Leave message empty to abort commit.
485 485 HG: no bookmark is activated
486 486 abort: empty commit message
487 487 [10]
488 488
489 489 $ cat >> .hg/hgrc <<EOF
490 490 > [committemplate]
491 491 > changeset = {desc}
492 492 > HG: mods={file_mods}
493 493 > HG: adds={file_adds}
494 494 > HG: dels={file_dels}
495 495 > HG: files={files}
496 496 > HG:
497 497 > {splitlines(diff()) % 'HG: {line}\n'
498 498 > }HG:
499 499 > HG: mods={file_mods}
500 500 > HG: adds={file_adds}
501 501 > HG: dels={file_dels}
502 502 > HG: files={files}\n
503 503 > EOF
504 504 $ hg status -amr
505 505 M changed
506 506 A added
507 507 R removed
508 508 $ HGEDITOR=cat hg commit -q -e -m "foo bar" changed
509 509 foo bar
510 510 HG: mods=changed
511 511 HG: adds=
512 512 HG: dels=
513 513 HG: files=changed
514 514 HG:
515 515 HG: diff -r d2313f97106f changed
516 516 HG: --- a/changed Thu Jan 01 00:00:00 1970 +0000
517 517 HG: +++ b/changed Thu Jan 01 00:00:00 1970 +0000
518 518 HG: @@ -1,1 +1,2 @@
519 519 HG: changed
520 520 HG: +changed
521 521 HG:
522 522 HG: mods=changed
523 523 HG: adds=
524 524 HG: dels=
525 525 HG: files=changed
526 526 $ hg status -amr
527 527 A added
528 528 R removed
529 529 $ hg parents --template "M {file_mods}\nA {file_adds}\nR {file_dels}\n"
530 530 M changed
531 531 A
532 532 R
533 533 $ hg rollback -q
534 534
535 535 $ cat >> .hg/hgrc <<EOF
536 536 > [committemplate]
537 537 > changeset = {desc}
538 538 > HG: mods={file_mods}
539 539 > HG: adds={file_adds}
540 540 > HG: dels={file_dels}
541 541 > HG: files={files}
542 542 > HG:
543 543 > {splitlines(diff("changed")) % 'HG: {line}\n'
544 544 > }HG:
545 545 > HG: mods={file_mods}
546 546 > HG: adds={file_adds}
547 547 > HG: dels={file_dels}
548 548 > HG: files={files}
549 549 > HG:
550 550 > {splitlines(diff("added")) % 'HG: {line}\n'
551 551 > }HG:
552 552 > HG: mods={file_mods}
553 553 > HG: adds={file_adds}
554 554 > HG: dels={file_dels}
555 555 > HG: files={files}
556 556 > HG:
557 557 > {splitlines(diff("removed")) % 'HG: {line}\n'
558 558 > }HG:
559 559 > HG: mods={file_mods}
560 560 > HG: adds={file_adds}
561 561 > HG: dels={file_dels}
562 562 > HG: files={files}\n
563 563 > EOF
564 564 $ HGEDITOR=cat hg commit -q -e -m "foo bar" added removed
565 565 foo bar
566 566 HG: mods=
567 567 HG: adds=added
568 568 HG: dels=removed
569 569 HG: files=added removed
570 570 HG:
571 571 HG:
572 572 HG: mods=
573 573 HG: adds=added
574 574 HG: dels=removed
575 575 HG: files=added removed
576 576 HG:
577 577 HG: diff -r d2313f97106f added
578 578 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
579 579 HG: +++ b/added Thu Jan 01 00:00:00 1970 +0000
580 580 HG: @@ -0,0 +1,1 @@
581 581 HG: +added
582 582 HG:
583 583 HG: mods=
584 584 HG: adds=added
585 585 HG: dels=removed
586 586 HG: files=added removed
587 587 HG:
588 588 HG: diff -r d2313f97106f removed
589 589 HG: --- a/removed Thu Jan 01 00:00:00 1970 +0000
590 590 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
591 591 HG: @@ -1,1 +0,0 @@
592 592 HG: -removed
593 593 HG:
594 594 HG: mods=
595 595 HG: adds=added
596 596 HG: dels=removed
597 597 HG: files=added removed
598 598 $ hg status -amr
599 599 M changed
600 600 $ hg parents --template "M {file_mods}\nA {file_adds}\nR {file_dels}\n"
601 601 M
602 602 A added
603 603 R removed
604 604 $ hg rollback -q
605 605
606 606 $ cat >> .hg/hgrc <<EOF
607 607 > # disable customizing for subsequent tests
608 608 > [committemplate]
609 609 > changeset =
610 610 > EOF
611 611
612 612 $ cd ..
613 613
614 614
615 615 commit copy
616 616
617 617 $ hg init dir2
618 618 $ cd dir2
619 619 $ echo bleh > bar
620 620 $ hg add bar
621 621 $ hg ci -m 'add bar'
622 622
623 623 $ hg cp bar foo
624 624 $ echo >> bar
625 625 $ hg ci -m 'cp bar foo; change bar'
626 626
627 627 $ hg debugrename foo
628 628 foo renamed from bar:26d3ca0dfd18e44d796b564e38dd173c9668d3a9
629 629 $ hg debugindex bar
630 630 rev linkrev nodeid p1 p2
631 631 0 0 26d3ca0dfd18 000000000000 000000000000
632 632 1 1 d267bddd54f7 26d3ca0dfd18 000000000000
633 633
634 634 Test making empty commits
635 635 $ hg commit --config ui.allowemptycommit=True -m "empty commit"
636 636 $ hg log -r . -v --stat
637 637 changeset: 2:d809f3644287
638 638 tag: tip
639 639 user: test
640 640 date: Thu Jan 01 00:00:00 1970 +0000
641 641 description:
642 642 empty commit
643 643
644 644
645 645
646 646 verify pathauditor blocks evil filepaths
647 647 $ cat > evil-commit.py <<EOF
648 648 > from __future__ import absolute_import
649 649 > from mercurial import context, hg, node, ui as uimod
650 650 > notrc = u".h\u200cg".encode('utf-8') + b'/hgrc'
651 651 > u = uimod.ui.load()
652 652 > r = hg.repository(u, b'.')
653 653 > def filectxfn(repo, memctx, path):
654 654 > return context.memfilectx(repo, memctx, path,
655 655 > b'[hooks]\nupdate = echo owned')
656 656 > c = context.memctx(r, [r.changelog.tip(), node.nullid],
657 657 > b'evil', [notrc], filectxfn, 0)
658 658 > r.commitctx(c)
659 659 > EOF
660 660 $ "$PYTHON" evil-commit.py
661 661 #if windows
662 662 $ hg co --clean tip
663 663 abort: path contains illegal component: .h\xe2\x80\x8cg\\hgrc (esc)
664 664 [255]
665 665 #else
666 666 $ hg co --clean tip
667 667 abort: path contains illegal component: .h\xe2\x80\x8cg/hgrc (esc)
668 668 [255]
669 669 #endif
670 670
671 671 $ hg rollback -f
672 672 repository tip rolled back to revision 2 (undo commit)
673 673 $ cat > evil-commit.py <<EOF
674 674 > from __future__ import absolute_import
675 675 > from mercurial import context, hg, node, ui as uimod
676 676 > notrc = b"HG~1/hgrc"
677 677 > u = uimod.ui.load()
678 678 > r = hg.repository(u, b'.')
679 679 > def filectxfn(repo, memctx, path):
680 680 > return context.memfilectx(repo, memctx, path,
681 681 > b'[hooks]\nupdate = echo owned')
682 682 > c = context.memctx(r, [r[b'tip'].node(), node.nullid],
683 683 > b'evil', [notrc], filectxfn, 0)
684 684 > r.commitctx(c)
685 685 > EOF
686 686 $ "$PYTHON" evil-commit.py
687 687 $ hg co --clean tip
688 688 abort: path contains illegal component: HG~1/hgrc
689 689 [255]
690 690
691 691 $ hg rollback -f
692 692 repository tip rolled back to revision 2 (undo commit)
693 693 $ cat > evil-commit.py <<EOF
694 694 > from __future__ import absolute_import
695 695 > from mercurial import context, hg, node, ui as uimod
696 696 > notrc = b"HG8B6C~2/hgrc"
697 697 > u = uimod.ui.load()
698 698 > r = hg.repository(u, b'.')
699 699 > def filectxfn(repo, memctx, path):
700 700 > return context.memfilectx(repo, memctx, path,
701 701 > b'[hooks]\nupdate = echo owned')
702 702 > c = context.memctx(r, [r[b'tip'].node(), node.nullid],
703 703 > b'evil', [notrc], filectxfn, 0)
704 704 > r.commitctx(c)
705 705 > EOF
706 706 $ "$PYTHON" evil-commit.py
707 707 $ hg co --clean tip
708 708 abort: path contains illegal component: HG8B6C~2/hgrc
709 709 [255]
710 710
711 711 $ cd ..
712 712
713 713 # test that an unmodified commit template message aborts
714 714
715 715 $ hg init unmodified_commit_template
716 716 $ cd unmodified_commit_template
717 717 $ echo foo > foo
718 718 $ hg add foo
719 719 $ hg commit -m "foo"
720 720 $ cat >> .hg/hgrc <<EOF
721 721 > [committemplate]
722 722 > changeset.commit = HI THIS IS NOT STRIPPED
723 723 > HG: this is customized commit template
724 724 > HG: {extramsg}
725 725 > {if(activebookmark,
726 726 > "HG: bookmark '{activebookmark}' is activated\n",
727 727 > "HG: no bookmark is activated\n")}{subrepos %
728 728 > "HG: subrepo '{subrepo}' is changed\n"}
729 729 > EOF
730 730 $ cat > $TESTTMP/notouching.sh <<EOF
731 731 > true
732 732 > EOF
733 733 $ echo foo2 > foo2
734 734 $ hg add foo2
735 735 $ HGEDITOR="sh $TESTTMP/notouching.sh" hg commit
736 736 abort: commit message unchanged
737 737 [10]
738 738
739 739 $ cd ..
740 740
741 741 test that text below the --- >8 --- special string is ignored
742 742
743 743 $ cat <<'EOF' > $TESTTMP/lowercaseline.sh
744 744 > cat $1 | sed s/LINE/line/ | tee $1.new
745 745 > mv $1.new $1
746 746 > EOF
747 747
748 748 $ hg init ignore_below_special_string
749 749 $ cd ignore_below_special_string
750 750 $ echo foo > foo
751 751 $ hg add foo
752 752 $ hg commit -m "foo"
753 753 $ cat >> .hg/hgrc <<EOF
754 754 > [committemplate]
755 755 > changeset.commit = first LINE
756 756 > HG: this is customized commit template
757 757 > HG: {extramsg}
758 758 > HG: ------------------------ >8 ------------------------
759 759 > {diff()}
760 760 > EOF
761 761 $ echo foo2 > foo2
762 762 $ hg add foo2
763 763 $ HGEDITOR="sh $TESTTMP/notouching.sh" hg ci
764 764 abort: commit message unchanged
765 765 [10]
766 766 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
767 767 first line
768 768 HG: this is customized commit template
769 769 HG: Leave message empty to abort commit.
770 770 HG: ------------------------ >8 ------------------------
771 771 diff -r e63c23eaa88a foo2
772 772 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
773 773 +++ b/foo2 Thu Jan 01 00:00:00 1970 +0000
774 774 @@ -0,0 +1,1 @@
775 775 +foo2
776 776 $ hg log -T '{desc}\n' -r .
777 777 first line
778 778
779 779 test that the special string --- >8 --- isn't used when not at the beginning of
780 780 a line
781 781
782 782 $ cat >> .hg/hgrc <<EOF
783 783 > [committemplate]
784 784 > changeset.commit = first LINE2
785 785 > another line HG: ------------------------ >8 ------------------------
786 786 > HG: this is customized commit template
787 787 > HG: {extramsg}
788 788 > HG: ------------------------ >8 ------------------------
789 789 > {diff()}
790 790 > EOF
791 791 $ echo foo >> foo
792 792 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
793 793 first line2
794 794 another line HG: ------------------------ >8 ------------------------
795 795 HG: this is customized commit template
796 796 HG: Leave message empty to abort commit.
797 797 HG: ------------------------ >8 ------------------------
798 798 diff -r 3661b22b0702 foo
799 799 --- a/foo Thu Jan 01 00:00:00 1970 +0000
800 800 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
801 801 @@ -1,1 +1,2 @@
802 802 foo
803 803 +foo
804 804 $ hg log -T '{desc}\n' -r .
805 805 first line2
806 806 another line HG: ------------------------ >8 ------------------------
807 807
808 808 also test that this special string isn't accepted when there is some extra text
809 809 at the end
810 810
811 811 $ cat >> .hg/hgrc <<EOF
812 812 > [committemplate]
813 813 > changeset.commit = first LINE3
814 814 > HG: ------------------------ >8 ------------------------foobar
815 815 > second line
816 816 > HG: this is customized commit template
817 817 > HG: {extramsg}
818 818 > HG: ------------------------ >8 ------------------------
819 819 > {diff()}
820 820 > EOF
821 821 $ echo foo >> foo
822 822 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
823 823 first line3
824 824 HG: ------------------------ >8 ------------------------foobar
825 825 second line
826 826 HG: this is customized commit template
827 827 HG: Leave message empty to abort commit.
828 828 HG: ------------------------ >8 ------------------------
829 829 diff -r ce648f5f066f foo
830 830 --- a/foo Thu Jan 01 00:00:00 1970 +0000
831 831 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
832 832 @@ -1,2 +1,3 @@
833 833 foo
834 834 foo
835 835 +foo
836 836 $ hg log -T '{desc}\n' -r .
837 837 first line3
838 838 second line
839 839
840 840 $ cd ..
841 841
842 842 testing commands.commit.post-status config option
843 843
844 844 $ hg init ci-post-st
845 845 $ cd ci-post-st
846 846 $ echo '[commands]' > .hg/hgrc
847 847 $ echo 'commit.post-status = 1' >> .hg/hgrc
848 848
849 849 $ echo 'ignored-file' > .hgignore
850 850 $ hg ci -qAm 0
851 851
852 852 $ echo 'c' > clean-file
853 853 $ echo 'a' > added-file
854 854 $ echo '?' > unknown-file
855 855 $ echo 'i' > ignored-file
856 856 $ hg add clean-file added-file
857 857 $ hg ci -m 1 clean-file
858 858 A added-file
859 859 ? unknown-file
860 860 $ hg st -mardu
861 861 A added-file
862 862 ? unknown-file
863 863
864 864 $ touch modified-file
865 865 $ hg add modified-file
866 866 $ hg ci -m 2 modified-file -q
867 867
868 868 $ echo 'm' > modified-file
869 869 $ hg ci --amend -m 'reworded' -X 're:'
870 870 saved backup bundle to $TESTTMP/ci-post-st/.hg/strip-backup/*-amend.hg (glob)
871 871 M modified-file
872 872 A added-file
873 873 ? unknown-file
874 874 $ hg st -mardu
875 875 M modified-file
876 876 A added-file
877 877 ? unknown-file
878 878
879 879 $ cd ..
@@ -1,1892 +1,1892 b''
1 1 This file used to contains all largefile tests.
2 2 Do not add any new tests in this file as it his already far too long to run.
3 3
4 4 It contains all the testing of the basic concepts of large file in a single block.
5 5
6 6 $ USERCACHE="$TESTTMP/cache"; export USERCACHE
7 7 $ mkdir "${USERCACHE}"
8 8 $ cat >> $HGRCPATH <<EOF
9 9 > [extensions]
10 10 > largefiles=
11 11 > purge=
12 12 > rebase=
13 13 > transplant=
14 14 > [phases]
15 15 > publish=False
16 16 > [largefiles]
17 17 > minsize=2
18 18 > patterns=glob:**.dat
19 19 > usercache=${USERCACHE}
20 20 > [hooks]
21 21 > precommit=sh -c "echo \\"Invoking status precommit hook\\"; hg status"
22 22 > EOF
23 23
24 24 Create the repo with a couple of revisions of both large and normal
25 25 files.
26 26 Test status and dirstate of largefiles and that summary output is correct.
27 27
28 28 $ hg init a
29 29 $ cd a
30 30 $ mkdir sub
31 31 $ echo normal1 > normal1
32 32 $ echo normal2 > sub/normal2
33 33 $ echo large1 > large1
34 34 $ echo large2 > sub/large2
35 35 $ hg add normal1 sub/normal2
36 36 $ hg add --large large1 sub/large2
37 37 $ hg commit -m "add files"
38 38 Invoking status precommit hook
39 39 A large1
40 40 A normal1
41 41 A sub/large2
42 42 A sub/normal2
43 43 $ touch large1 sub/large2
44 44 $ sleep 1
45 45 $ hg st
46 46 $ hg debugstate --no-dates
47 47 n 644 41 set .hglf/large1
48 48 n 644 41 set .hglf/sub/large2
49 49 n 644 8 set normal1
50 50 n 644 8 set sub/normal2
51 51 $ hg debugstate --large --no-dates
52 52 n 644 7 set large1
53 53 n 644 7 set sub/large2
54 54 $ echo normal11 > normal1
55 55 $ echo normal22 > sub/normal2
56 56 $ echo large11 > large1
57 57 $ echo large22 > sub/large2
58 58 $ hg commit -m "edit files"
59 59 Invoking status precommit hook
60 60 M large1
61 61 M normal1
62 62 M sub/large2
63 63 M sub/normal2
64 64 $ hg sum --large
65 65 parent: 1:ce8896473775 tip
66 66 edit files
67 67 branch: default
68 68 commit: (clean)
69 69 update: (current)
70 70 phases: 2 draft
71 71 largefiles: (no remote repo)
72 72
73 73 Commit preserved largefile contents.
74 74
75 75 $ cat normal1
76 76 normal11
77 77 $ cat large1
78 78 large11
79 79 $ cat sub/normal2
80 80 normal22
81 81 $ cat sub/large2
82 82 large22
83 83
84 84 Test status, subdir and unknown files
85 85
86 86 $ echo unknown > sub/unknown
87 87 $ hg st --all
88 88 ? sub/unknown
89 89 C large1
90 90 C normal1
91 91 C sub/large2
92 92 C sub/normal2
93 93 $ hg st --all sub
94 94 ? sub/unknown
95 95 C sub/large2
96 96 C sub/normal2
97 97 $ rm sub/unknown
98 98
99 99 Test messages and exit codes for remove warning cases
100 100
101 101 $ hg remove -A large1
102 102 not removing large1: file still exists
103 103 [1]
104 104 $ echo 'modified' > large1
105 105 $ hg remove large1
106 106 not removing large1: file is modified (use -f to force removal)
107 107 [1]
108 108 $ echo 'new' > normalnew
109 109 $ hg add normalnew
110 110 $ echo 'new' > largenew
111 111 $ hg add --large normalnew
112 112 normalnew already tracked!
113 113 $ hg remove normalnew largenew
114 114 not removing largenew: file is untracked
115 115 not removing normalnew: file has been marked for add (use 'hg forget' to undo add)
116 116 [1]
117 117 $ rm normalnew largenew
118 118 $ hg up -Cq
119 119
120 120 Remove both largefiles and normal files.
121 121
122 122 $ hg remove normal1 large1
123 123 $ hg status large1
124 124 R large1
125 125 $ hg commit -m "remove files"
126 126 Invoking status precommit hook
127 127 R large1
128 128 R normal1
129 129 $ ls -A
130 130 .hg
131 131 .hglf
132 132 sub
133 133 $ echo "testlargefile" > large1-test
134 134 $ hg add --large large1-test
135 135 $ hg st
136 136 A large1-test
137 137 $ hg rm large1-test
138 138 not removing large1-test: file has been marked for add (use forget to undo)
139 139 [1]
140 140 $ hg st
141 141 A large1-test
142 142 $ hg forget large1-test
143 143 $ hg st
144 144 ? large1-test
145 145 $ hg remove large1-test
146 146 not removing large1-test: file is untracked
147 147 [1]
148 148 $ hg forget large1-test
149 149 not removing large1-test: file is already untracked
150 150 [1]
151 151 $ rm large1-test
152 152
153 153 Copy both largefiles and normal files (testing that status output is correct).
154 154
155 155 $ hg cp sub/normal2 normal1
156 156 $ hg cp sub/large2 large1
157 157 $ hg commit -m "copy files"
158 158 Invoking status precommit hook
159 159 A large1
160 160 A normal1
161 161 $ cat normal1
162 162 normal22
163 163 $ cat large1
164 164 large22
165 165
166 166 Test moving largefiles and verify that normal files are also unaffected.
167 167
168 168 $ hg mv normal1 normal3
169 169 $ hg mv large1 large3
170 170 $ hg mv sub/normal2 sub/normal4
171 171 $ hg mv sub/large2 sub/large4
172 172 $ hg commit -m "move files"
173 173 Invoking status precommit hook
174 174 A large3
175 175 A normal3
176 176 A sub/large4
177 177 A sub/normal4
178 178 R large1
179 179 R normal1
180 180 R sub/large2
181 181 R sub/normal2
182 182 $ cat normal3
183 183 normal22
184 184 $ cat large3
185 185 large22
186 186 $ cat sub/normal4
187 187 normal22
188 188 $ cat sub/large4
189 189 large22
190 190
191 191
192 192 #if serve
193 193 Test display of largefiles in hgweb
194 194
195 195 $ hg serve -d -p $HGPORT --pid-file ../hg.pid
196 196 $ cat ../hg.pid >> $DAEMON_PIDS
197 197 $ get-with-headers.py $LOCALIP:$HGPORT 'file/tip/?style=raw'
198 198 200 Script output follows
199 199
200 200
201 201 drwxr-xr-x sub
202 202 -rw-r--r-- 41 large3
203 203 -rw-r--r-- 9 normal3
204 204
205 205
206 206 $ get-with-headers.py $LOCALIP:$HGPORT 'file/tip/sub/?style=raw'
207 207 200 Script output follows
208 208
209 209
210 210 -rw-r--r-- 41 large4
211 211 -rw-r--r-- 9 normal4
212 212
213 213
214 214 $ killdaemons.py
215 215 #endif
216 216
217 217 Test largefiles can be loaded in hgweb (wrapcommand() shouldn't fail)
218 218
219 219 $ cat <<EOF > "$TESTTMP/hgweb.cgi"
220 220 > #!$PYTHON
221 221 > from mercurial import demandimport; demandimport.enable()
222 222 > from mercurial.hgweb import hgweb
223 223 > from mercurial.hgweb import wsgicgi
224 224 > application = hgweb(b'.', b'test repo')
225 225 > wsgicgi.launch(application)
226 226 > EOF
227 227 $ . "$TESTDIR/cgienv"
228 228
229 229 $ SCRIPT_NAME='' \
230 230 > "$PYTHON" "$TESTTMP/hgweb.cgi" > /dev/null
231 231
232 232 Test archiving the various revisions. These hit corner cases known with
233 233 archiving.
234 234
235 235 $ hg archive -r 0 ../archive0
236 236 $ hg archive -r 1 ../archive1
237 237 $ hg archive -r 2 ../archive2
238 238 $ hg archive -r 3 ../archive3
239 239 $ hg archive -r 4 ../archive4
240 240 $ cd ../archive0
241 241 $ cat normal1
242 242 normal1
243 243 $ cat large1
244 244 large1
245 245 $ cat sub/normal2
246 246 normal2
247 247 $ cat sub/large2
248 248 large2
249 249 $ cd ../archive1
250 250 $ cat normal1
251 251 normal11
252 252 $ cat large1
253 253 large11
254 254 $ cat sub/normal2
255 255 normal22
256 256 $ cat sub/large2
257 257 large22
258 258 $ cd ../archive2
259 259 $ ls -A
260 260 .hg_archival.txt
261 261 sub
262 262 $ cat sub/normal2
263 263 normal22
264 264 $ cat sub/large2
265 265 large22
266 266 $ cd ../archive3
267 267 $ cat normal1
268 268 normal22
269 269 $ cat large1
270 270 large22
271 271 $ cat sub/normal2
272 272 normal22
273 273 $ cat sub/large2
274 274 large22
275 275 $ cd ../archive4
276 276 $ cat normal3
277 277 normal22
278 278 $ cat large3
279 279 large22
280 280 $ cat sub/normal4
281 281 normal22
282 282 $ cat sub/large4
283 283 large22
284 284
285 285 Commit corner case: specify files to commit.
286 286
287 287 $ cd ../a
288 288 $ echo normal3 > normal3
289 289 $ echo large3 > large3
290 290 $ echo normal4 > sub/normal4
291 291 $ echo large4 > sub/large4
292 292 $ hg commit normal3 large3 sub/normal4 sub/large4 -m "edit files again"
293 293 Invoking status precommit hook
294 294 M large3
295 295 M normal3
296 296 M sub/large4
297 297 M sub/normal4
298 298 $ cat normal3
299 299 normal3
300 300 $ cat large3
301 301 large3
302 302 $ cat sub/normal4
303 303 normal4
304 304 $ cat sub/large4
305 305 large4
306 306
307 307 One more commit corner case: commit from a subdirectory.
308 308
309 309 $ cd ../a
310 310 $ echo normal33 > normal3
311 311 $ echo large33 > large3
312 312 $ echo normal44 > sub/normal4
313 313 $ echo large44 > sub/large4
314 314 $ cd sub
315 315 $ hg commit -m "edit files yet again"
316 316 Invoking status precommit hook
317 317 M large3
318 318 M normal3
319 319 M sub/large4
320 320 M sub/normal4
321 321 $ cat ../normal3
322 322 normal33
323 323 $ cat ../large3
324 324 large33
325 325 $ cat normal4
326 326 normal44
327 327 $ cat large4
328 328 large44
329 329
330 330 Committing standins is not allowed.
331 331
332 332 $ cd ..
333 333 $ echo large3 > large3
334 334 $ hg commit .hglf/large3 -m "try to commit standin"
335 335 abort: file ".hglf/large3" is a largefile standin
336 336 (commit the largefile itself instead)
337 337 [255]
338 338
339 339 Corner cases for adding largefiles.
340 340
341 341 $ echo large5 > large5
342 342 $ hg add --large large5
343 343 $ hg add --large large5
344 344 large5 already a largefile
345 345 $ mkdir sub2
346 346 $ echo large6 > sub2/large6
347 347 $ echo large7 > sub2/large7
348 348 $ hg add --large sub2
349 349 adding sub2/large6 as a largefile
350 350 adding sub2/large7 as a largefile
351 351 $ hg st
352 352 M large3
353 353 A large5
354 354 A sub2/large6
355 355 A sub2/large7
356 356
357 357 Committing directories containing only largefiles.
358 358
359 359 $ mkdir -p z/y/x/m
360 360 $ touch z/y/x/m/large1
361 361 $ touch z/y/x/large2
362 362 $ hg add --large z/y/x/m/large1 z/y/x/large2
363 363 $ hg commit -m "Subdir with directory only containing largefiles" z
364 364 Invoking status precommit hook
365 365 M large3
366 366 A large5
367 367 A sub2/large6
368 368 A sub2/large7
369 369 A z/y/x/large2
370 370 A z/y/x/m/large1
371 371
372 372 (and a bit of log testing)
373 373
374 374 $ hg log -T '{rev}\n' z/y/x/m/large1
375 375 7
376 376 $ hg log -T '{rev}\n' z/y/x/m # with only a largefile
377 377 7
378 378
379 379 $ hg rollback --quiet
380 380 $ touch z/y/x/m/normal
381 381 $ hg add z/y/x/m/normal
382 382 $ hg commit -m "Subdir with mixed contents" z
383 383 Invoking status precommit hook
384 384 M large3
385 385 A large5
386 386 A sub2/large6
387 387 A sub2/large7
388 388 A z/y/x/large2
389 389 A z/y/x/m/large1
390 390 A z/y/x/m/normal
391 391 $ hg st
392 392 M large3
393 393 A large5
394 394 A sub2/large6
395 395 A sub2/large7
396 396 $ hg rollback --quiet
397 397 $ hg revert z/y/x/large2 z/y/x/m/large1
398 398 $ rm z/y/x/large2 z/y/x/m/large1
399 399 $ hg commit -m "Subdir with normal contents" z
400 400 Invoking status precommit hook
401 401 M large3
402 402 A large5
403 403 A sub2/large6
404 404 A sub2/large7
405 405 A z/y/x/m/normal
406 406 $ hg st
407 407 M large3
408 408 A large5
409 409 A sub2/large6
410 410 A sub2/large7
411 411 $ hg rollback --quiet
412 412 $ hg revert --quiet z
413 413 $ hg commit -m "Empty subdir" z
414 414 abort: z: no match under directory!
415 [255]
415 [10]
416 416 $ rm -rf z
417 417 $ hg ci -m "standin" .hglf
418 418 abort: file ".hglf" is a largefile standin
419 419 (commit the largefile itself instead)
420 420 [255]
421 421
422 422 Test "hg status" with combination of 'file pattern' and 'directory
423 423 pattern' for largefiles:
424 424
425 425 $ hg status sub2/large6 sub2
426 426 A sub2/large6
427 427 A sub2/large7
428 428
429 429 Config settings (pattern **.dat, minsize 2 MB) are respected.
430 430
431 431 $ echo testdata > test.dat
432 432 $ dd bs=1k count=2k if=/dev/zero of=reallylarge > /dev/null 2> /dev/null
433 433 $ hg add
434 434 adding reallylarge as a largefile
435 435 adding test.dat as a largefile
436 436
437 437 Test that minsize and --lfsize handle float values;
438 438 also tests that --lfsize overrides largefiles.minsize.
439 439 (0.250 MB = 256 kB = 262144 B)
440 440
441 441 $ dd if=/dev/zero of=ratherlarge bs=1024 count=256 > /dev/null 2> /dev/null
442 442 $ dd if=/dev/zero of=medium bs=1024 count=128 > /dev/null 2> /dev/null
443 443 $ hg --config largefiles.minsize=.25 add
444 444 adding ratherlarge as a largefile
445 445 adding medium
446 446 $ hg forget medium
447 447 $ hg --config largefiles.minsize=.25 add --lfsize=.125
448 448 adding medium as a largefile
449 449 $ dd if=/dev/zero of=notlarge bs=1024 count=127 > /dev/null 2> /dev/null
450 450 $ hg --config largefiles.minsize=.25 add --lfsize=.125
451 451 adding notlarge
452 452 $ hg forget notlarge
453 453
454 454 Test forget on largefiles.
455 455
456 456 $ hg forget large3 large5 test.dat reallylarge ratherlarge medium
457 457 $ hg commit -m "add/edit more largefiles"
458 458 Invoking status precommit hook
459 459 A sub2/large6
460 460 A sub2/large7
461 461 R large3
462 462 ? large5
463 463 ? medium
464 464 ? notlarge
465 465 ? ratherlarge
466 466 ? reallylarge
467 467 ? test.dat
468 468 $ hg st
469 469 ? large3
470 470 ? large5
471 471 ? medium
472 472 ? notlarge
473 473 ? ratherlarge
474 474 ? reallylarge
475 475 ? test.dat
476 476
477 477 Purge with largefiles: verify that largefiles are still in the working
478 478 dir after a purge.
479 479
480 480 $ hg purge --all
481 481 $ cat sub/large4
482 482 large44
483 483 $ cat sub2/large6
484 484 large6
485 485 $ cat sub2/large7
486 486 large7
487 487
488 488 Test addremove: verify that files that should be added as largefiles are added as
489 489 such and that already-existing largefiles are not added as normal files by
490 490 accident.
491 491
492 492 $ rm normal3
493 493 $ rm sub/large4
494 494 $ echo "testing addremove with patterns" > testaddremove.dat
495 495 $ echo "normaladdremove" > normaladdremove
496 496 $ hg addremove
497 497 removing sub/large4
498 498 adding testaddremove.dat as a largefile
499 499 removing normal3
500 500 adding normaladdremove
501 501
502 502 Test addremove with -R
503 503
504 504 $ hg up -C
505 505 getting changed largefiles
506 506 1 largefiles updated, 0 removed
507 507 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
508 508 $ rm normal3
509 509 $ rm sub/large4
510 510 $ echo "testing addremove with patterns" > testaddremove.dat
511 511 $ echo "normaladdremove" > normaladdremove
512 512 $ cd ..
513 513 $ hg -R a -v addremove
514 514 removing sub/large4
515 515 adding testaddremove.dat as a largefile
516 516 removing normal3
517 517 adding normaladdremove
518 518 $ cd a
519 519
520 520 Test 3364
521 521 $ hg clone . ../addrm
522 522 updating to branch default
523 523 getting changed largefiles
524 524 3 largefiles updated, 0 removed
525 525 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
526 526 $ cd ../addrm
527 527 $ cat >> .hg/hgrc <<EOF
528 528 > [hooks]
529 529 > post-commit.stat=sh -c "echo \\"Invoking status postcommit hook\\"; hg status -A"
530 530 > EOF
531 531 $ touch foo
532 532 $ hg add --large foo
533 533 $ hg ci -m "add foo"
534 534 Invoking status precommit hook
535 535 A foo
536 536 Invoking status postcommit hook
537 537 C foo
538 538 C normal3
539 539 C sub/large4
540 540 C sub/normal4
541 541 C sub2/large6
542 542 C sub2/large7
543 543 $ rm foo
544 544 $ hg st
545 545 ! foo
546 546 hmm.. no precommit invoked, but there is a postcommit??
547 547 $ hg ci -m "will not checkin"
548 548 nothing changed (1 missing files, see 'hg status')
549 549 Invoking status postcommit hook
550 550 ! foo
551 551 C normal3
552 552 C sub/large4
553 553 C sub/normal4
554 554 C sub2/large6
555 555 C sub2/large7
556 556 [1]
557 557 $ hg addremove
558 558 removing foo
559 559 $ hg st
560 560 R foo
561 561 $ hg ci -m "used to say nothing changed"
562 562 Invoking status precommit hook
563 563 R foo
564 564 Invoking status postcommit hook
565 565 C normal3
566 566 C sub/large4
567 567 C sub/normal4
568 568 C sub2/large6
569 569 C sub2/large7
570 570 $ hg st
571 571
572 572 Test 3507 (both normal files and largefiles were a problem)
573 573
574 574 $ touch normal
575 575 $ touch large
576 576 $ hg add normal
577 577 $ hg add --large large
578 578 $ hg ci -m "added"
579 579 Invoking status precommit hook
580 580 A large
581 581 A normal
582 582 Invoking status postcommit hook
583 583 C large
584 584 C normal
585 585 C normal3
586 586 C sub/large4
587 587 C sub/normal4
588 588 C sub2/large6
589 589 C sub2/large7
590 590 $ hg remove normal
591 591 $ hg addremove --traceback
592 592 $ hg ci -m "addremoved normal"
593 593 Invoking status precommit hook
594 594 R normal
595 595 Invoking status postcommit hook
596 596 C large
597 597 C normal3
598 598 C sub/large4
599 599 C sub/normal4
600 600 C sub2/large6
601 601 C sub2/large7
602 602 $ hg up -C '.^'
603 603 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
604 604 $ hg remove large
605 605 $ hg addremove --traceback
606 606 $ hg ci -m "removed large"
607 607 Invoking status precommit hook
608 608 R large
609 609 created new head
610 610 Invoking status postcommit hook
611 611 C normal
612 612 C normal3
613 613 C sub/large4
614 614 C sub/normal4
615 615 C sub2/large6
616 616 C sub2/large7
617 617
618 618 Test commit -A (issue3542)
619 619 $ echo large8 > large8
620 620 $ hg add --large large8
621 621 $ hg ci -Am 'this used to add large8 as normal and commit both'
622 622 Invoking status precommit hook
623 623 A large8
624 624 Invoking status postcommit hook
625 625 C large8
626 626 C normal
627 627 C normal3
628 628 C sub/large4
629 629 C sub/normal4
630 630 C sub2/large6
631 631 C sub2/large7
632 632 $ rm large8
633 633 $ hg ci -Am 'this used to not notice the rm'
634 634 removing large8
635 635 Invoking status precommit hook
636 636 R large8
637 637 Invoking status postcommit hook
638 638 C normal
639 639 C normal3
640 640 C sub/large4
641 641 C sub/normal4
642 642 C sub2/large6
643 643 C sub2/large7
644 644
645 645 Test that a standin can't be added as a large file
646 646
647 647 $ touch large
648 648 $ hg add --large large
649 649 $ hg ci -m "add"
650 650 Invoking status precommit hook
651 651 A large
652 652 Invoking status postcommit hook
653 653 C large
654 654 C normal
655 655 C normal3
656 656 C sub/large4
657 657 C sub/normal4
658 658 C sub2/large6
659 659 C sub2/large7
660 660 $ hg remove large
661 661 $ touch large
662 662 $ hg addremove --config largefiles.patterns=**large --traceback
663 663 adding large as a largefile
664 664
665 665 Test that outgoing --large works (with revsets too)
666 666 $ hg outgoing --rev '.^' --large
667 667 comparing with $TESTTMP/a
668 668 searching for changes
669 669 changeset: 8:c02fd3b77ec4
670 670 user: test
671 671 date: Thu Jan 01 00:00:00 1970 +0000
672 672 summary: add foo
673 673
674 674 changeset: 9:289dd08c9bbb
675 675 user: test
676 676 date: Thu Jan 01 00:00:00 1970 +0000
677 677 summary: used to say nothing changed
678 678
679 679 changeset: 10:34f23ac6ac12
680 680 user: test
681 681 date: Thu Jan 01 00:00:00 1970 +0000
682 682 summary: added
683 683
684 684 changeset: 12:710c1b2f523c
685 685 parent: 10:34f23ac6ac12
686 686 user: test
687 687 date: Thu Jan 01 00:00:00 1970 +0000
688 688 summary: removed large
689 689
690 690 changeset: 13:0a3e75774479
691 691 user: test
692 692 date: Thu Jan 01 00:00:00 1970 +0000
693 693 summary: this used to add large8 as normal and commit both
694 694
695 695 changeset: 14:84f3d378175c
696 696 user: test
697 697 date: Thu Jan 01 00:00:00 1970 +0000
698 698 summary: this used to not notice the rm
699 699
700 700 largefiles to upload (1 entities):
701 701 large8
702 702
703 703 $ cd ../a
704 704
705 705 Clone a largefiles repo.
706 706
707 707 $ hg clone . ../b
708 708 updating to branch default
709 709 getting changed largefiles
710 710 3 largefiles updated, 0 removed
711 711 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
712 712 $ cd ../b
713 713 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
714 714 7:daea875e9014 add/edit more largefiles
715 715 6:4355d653f84f edit files yet again
716 716 5:9d5af5072dbd edit files again
717 717 4:74c02385b94c move files
718 718 3:9e8fbc4bce62 copy files
719 719 2:51a0ae4d5864 remove files
720 720 1:ce8896473775 edit files
721 721 0:30d30fe6a5be add files
722 722 $ cat normal3
723 723 normal33
724 724
725 725 Test graph log
726 726
727 727 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n'
728 728 @ 7:daea875e9014 add/edit more largefiles
729 729 |
730 730 o 6:4355d653f84f edit files yet again
731 731 |
732 732 o 5:9d5af5072dbd edit files again
733 733 |
734 734 o 4:74c02385b94c move files
735 735 |
736 736 o 3:9e8fbc4bce62 copy files
737 737 |
738 738 o 2:51a0ae4d5864 remove files
739 739 |
740 740 o 1:ce8896473775 edit files
741 741 |
742 742 o 0:30d30fe6a5be add files
743 743
744 744
745 745 Test log with --patch
746 746
747 747 $ hg log --patch -r 6::7
748 748 changeset: 6:4355d653f84f
749 749 user: test
750 750 date: Thu Jan 01 00:00:00 1970 +0000
751 751 summary: edit files yet again
752 752
753 753 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/large3
754 754 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
755 755 +++ b/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
756 756 @@ -1,1 +1,1 @@
757 757 -baaf12afde9d8d67f25dab6dced0d2bf77dba47c
758 758 +7838695e10da2bb75ac1156565f40a2595fa2fa0
759 759 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
760 760 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
761 761 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
762 762 @@ -1,1 +1,1 @@
763 763 -aeb2210d19f02886dde00dac279729a48471e2f9
764 764 +971fb41e78fea4f8e0ba5244784239371cb00591
765 765 diff -r 9d5af5072dbd -r 4355d653f84f normal3
766 766 --- a/normal3 Thu Jan 01 00:00:00 1970 +0000
767 767 +++ b/normal3 Thu Jan 01 00:00:00 1970 +0000
768 768 @@ -1,1 +1,1 @@
769 769 -normal3
770 770 +normal33
771 771 diff -r 9d5af5072dbd -r 4355d653f84f sub/normal4
772 772 --- a/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
773 773 +++ b/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
774 774 @@ -1,1 +1,1 @@
775 775 -normal4
776 776 +normal44
777 777
778 778 changeset: 7:daea875e9014
779 779 tag: tip
780 780 user: test
781 781 date: Thu Jan 01 00:00:00 1970 +0000
782 782 summary: add/edit more largefiles
783 783
784 784 diff -r 4355d653f84f -r daea875e9014 .hglf/large3
785 785 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
786 786 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
787 787 @@ -1,1 +0,0 @@
788 788 -7838695e10da2bb75ac1156565f40a2595fa2fa0
789 789 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large6
790 790 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
791 791 +++ b/.hglf/sub2/large6 Thu Jan 01 00:00:00 1970 +0000
792 792 @@ -0,0 +1,1 @@
793 793 +0d6d75887db61b2c7e6c74b5dd8fc6ad50c0cc30
794 794 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large7
795 795 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
796 796 +++ b/.hglf/sub2/large7 Thu Jan 01 00:00:00 1970 +0000
797 797 @@ -0,0 +1,1 @@
798 798 +bb3151689acb10f0c3125c560d5e63df914bc1af
799 799
800 800
801 801 $ hg log --patch -r 6::7 sub/
802 802 changeset: 6:4355d653f84f
803 803 user: test
804 804 date: Thu Jan 01 00:00:00 1970 +0000
805 805 summary: edit files yet again
806 806
807 807 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
808 808 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
809 809 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
810 810 @@ -1,1 +1,1 @@
811 811 -aeb2210d19f02886dde00dac279729a48471e2f9
812 812 +971fb41e78fea4f8e0ba5244784239371cb00591
813 813 diff -r 9d5af5072dbd -r 4355d653f84f sub/normal4
814 814 --- a/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
815 815 +++ b/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
816 816 @@ -1,1 +1,1 @@
817 817 -normal4
818 818 +normal44
819 819
820 820
821 821 log with both --follow and --patch
822 822
823 823 $ hg log --follow --patch --limit 2
824 824 changeset: 7:daea875e9014
825 825 tag: tip
826 826 user: test
827 827 date: Thu Jan 01 00:00:00 1970 +0000
828 828 summary: add/edit more largefiles
829 829
830 830 diff -r 4355d653f84f -r daea875e9014 .hglf/large3
831 831 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
832 832 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
833 833 @@ -1,1 +0,0 @@
834 834 -7838695e10da2bb75ac1156565f40a2595fa2fa0
835 835 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large6
836 836 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
837 837 +++ b/.hglf/sub2/large6 Thu Jan 01 00:00:00 1970 +0000
838 838 @@ -0,0 +1,1 @@
839 839 +0d6d75887db61b2c7e6c74b5dd8fc6ad50c0cc30
840 840 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large7
841 841 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
842 842 +++ b/.hglf/sub2/large7 Thu Jan 01 00:00:00 1970 +0000
843 843 @@ -0,0 +1,1 @@
844 844 +bb3151689acb10f0c3125c560d5e63df914bc1af
845 845
846 846 changeset: 6:4355d653f84f
847 847 user: test
848 848 date: Thu Jan 01 00:00:00 1970 +0000
849 849 summary: edit files yet again
850 850
851 851 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/large3
852 852 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
853 853 +++ b/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
854 854 @@ -1,1 +1,1 @@
855 855 -baaf12afde9d8d67f25dab6dced0d2bf77dba47c
856 856 +7838695e10da2bb75ac1156565f40a2595fa2fa0
857 857 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
858 858 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
859 859 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
860 860 @@ -1,1 +1,1 @@
861 861 -aeb2210d19f02886dde00dac279729a48471e2f9
862 862 +971fb41e78fea4f8e0ba5244784239371cb00591
863 863 diff -r 9d5af5072dbd -r 4355d653f84f normal3
864 864 --- a/normal3 Thu Jan 01 00:00:00 1970 +0000
865 865 +++ b/normal3 Thu Jan 01 00:00:00 1970 +0000
866 866 @@ -1,1 +1,1 @@
867 867 -normal3
868 868 +normal33
869 869 diff -r 9d5af5072dbd -r 4355d653f84f sub/normal4
870 870 --- a/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
871 871 +++ b/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
872 872 @@ -1,1 +1,1 @@
873 873 -normal4
874 874 +normal44
875 875
876 876 $ hg log --follow --patch sub/large4
877 877 changeset: 6:4355d653f84f
878 878 user: test
879 879 date: Thu Jan 01 00:00:00 1970 +0000
880 880 summary: edit files yet again
881 881
882 882 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
883 883 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
884 884 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
885 885 @@ -1,1 +1,1 @@
886 886 -aeb2210d19f02886dde00dac279729a48471e2f9
887 887 +971fb41e78fea4f8e0ba5244784239371cb00591
888 888
889 889 changeset: 5:9d5af5072dbd
890 890 user: test
891 891 date: Thu Jan 01 00:00:00 1970 +0000
892 892 summary: edit files again
893 893
894 894 diff -r 74c02385b94c -r 9d5af5072dbd .hglf/sub/large4
895 895 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
896 896 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
897 897 @@ -1,1 +1,1 @@
898 898 -eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
899 899 +aeb2210d19f02886dde00dac279729a48471e2f9
900 900
901 901 changeset: 4:74c02385b94c
902 902 user: test
903 903 date: Thu Jan 01 00:00:00 1970 +0000
904 904 summary: move files
905 905
906 906 diff -r 9e8fbc4bce62 -r 74c02385b94c .hglf/sub/large4
907 907 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
908 908 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
909 909 @@ -0,0 +1,1 @@
910 910 +eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
911 911
912 912 changeset: 1:ce8896473775
913 913 user: test
914 914 date: Thu Jan 01 00:00:00 1970 +0000
915 915 summary: edit files
916 916
917 917 diff -r 30d30fe6a5be -r ce8896473775 .hglf/sub/large2
918 918 --- a/.hglf/sub/large2 Thu Jan 01 00:00:00 1970 +0000
919 919 +++ b/.hglf/sub/large2 Thu Jan 01 00:00:00 1970 +0000
920 920 @@ -1,1 +1,1 @@
921 921 -1deebade43c8c498a3c8daddac0244dc55d1331d
922 922 +eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
923 923
924 924 changeset: 0:30d30fe6a5be
925 925 user: test
926 926 date: Thu Jan 01 00:00:00 1970 +0000
927 927 summary: add files
928 928
929 929 diff -r 000000000000 -r 30d30fe6a5be .hglf/sub/large2
930 930 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
931 931 +++ b/.hglf/sub/large2 Thu Jan 01 00:00:00 1970 +0000
932 932 @@ -0,0 +1,1 @@
933 933 +1deebade43c8c498a3c8daddac0244dc55d1331d
934 934
935 935 $ cat sub/normal4
936 936 normal44
937 937 $ cat sub/large4
938 938 large44
939 939 $ cat sub2/large6
940 940 large6
941 941 $ cat sub2/large7
942 942 large7
943 943 $ hg log -qf sub2/large7
944 944 7:daea875e9014
945 945 $ hg log -Gqf sub2/large7
946 946 @ 7:daea875e9014
947 947 |
948 948 ~
949 949 $ cd ..
950 950
951 951 Test log from outside repo
952 952
953 953 $ hg log b/sub -T '{rev}:{node|short} {desc|firstline}\n'
954 954 6:4355d653f84f edit files yet again
955 955 5:9d5af5072dbd edit files again
956 956 4:74c02385b94c move files
957 957 1:ce8896473775 edit files
958 958 0:30d30fe6a5be add files
959 959
960 960 Test clone at revision
961 961
962 962 $ hg clone a -r 3 c
963 963 adding changesets
964 964 adding manifests
965 965 adding file changes
966 966 added 4 changesets with 10 changes to 4 files
967 967 new changesets 30d30fe6a5be:9e8fbc4bce62 (4 drafts)
968 968 updating to branch default
969 969 getting changed largefiles
970 970 2 largefiles updated, 0 removed
971 971 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
972 972 $ cd c
973 973 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
974 974 3:9e8fbc4bce62 copy files
975 975 2:51a0ae4d5864 remove files
976 976 1:ce8896473775 edit files
977 977 0:30d30fe6a5be add files
978 978 $ cat normal1
979 979 normal22
980 980 $ cat large1
981 981 large22
982 982 $ cat sub/normal2
983 983 normal22
984 984 $ cat sub/large2
985 985 large22
986 986
987 987 Old revisions of a clone have correct largefiles content (this also
988 988 tests update).
989 989
990 990 $ hg update -r 1
991 991 getting changed largefiles
992 992 1 largefiles updated, 0 removed
993 993 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
994 994 $ cat large1
995 995 large11
996 996 $ cat sub/large2
997 997 large22
998 998 $ cd ..
999 999
1000 1000 Test cloning with --all-largefiles flag
1001 1001
1002 1002 $ rm "${USERCACHE}"/*
1003 1003 $ hg clone --all-largefiles a a-backup
1004 1004 updating to branch default
1005 1005 getting changed largefiles
1006 1006 3 largefiles updated, 0 removed
1007 1007 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1008 1008 7 additional largefiles cached
1009 1009
1010 1010 $ rm "${USERCACHE}"/*
1011 1011 $ hg clone --all-largefiles -u 0 a a-clone0
1012 1012 updating to branch default
1013 1013 getting changed largefiles
1014 1014 2 largefiles updated, 0 removed
1015 1015 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1016 1016 8 additional largefiles cached
1017 1017 $ hg -R a-clone0 sum
1018 1018 parent: 0:30d30fe6a5be
1019 1019 add files
1020 1020 branch: default
1021 1021 commit: (clean)
1022 1022 update: 7 new changesets (update)
1023 1023 phases: 8 draft
1024 1024
1025 1025 $ rm "${USERCACHE}"/*
1026 1026 $ hg clone --all-largefiles -u 1 a a-clone1
1027 1027 updating to branch default
1028 1028 getting changed largefiles
1029 1029 2 largefiles updated, 0 removed
1030 1030 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1031 1031 8 additional largefiles cached
1032 1032 $ hg -R a-clone1 verify --large --lfa --lfc
1033 1033 checking changesets
1034 1034 checking manifests
1035 1035 crosschecking files in changesets and manifests
1036 1036 checking files
1037 1037 checked 8 changesets with 24 changes to 10 files
1038 1038 searching 8 changesets for largefiles
1039 1039 verified contents of 13 revisions of 6 largefiles
1040 1040 $ hg -R a-clone1 sum
1041 1041 parent: 1:ce8896473775
1042 1042 edit files
1043 1043 branch: default
1044 1044 commit: (clean)
1045 1045 update: 6 new changesets (update)
1046 1046 phases: 8 draft
1047 1047
1048 1048 $ rm "${USERCACHE}"/*
1049 1049 $ hg clone --all-largefiles -U a a-clone-u
1050 1050 10 additional largefiles cached
1051 1051 $ hg -R a-clone-u sum
1052 1052 parent: -1:000000000000 (no revision checked out)
1053 1053 branch: default
1054 1054 commit: (clean)
1055 1055 update: 8 new changesets (update)
1056 1056 phases: 8 draft
1057 1057
1058 1058 Show computed destination directory:
1059 1059
1060 1060 $ mkdir xyz
1061 1061 $ cd xyz
1062 1062 $ hg clone ../a
1063 1063 destination directory: a
1064 1064 updating to branch default
1065 1065 getting changed largefiles
1066 1066 3 largefiles updated, 0 removed
1067 1067 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1068 1068 $ cd ..
1069 1069
1070 1070 Clone URL without path:
1071 1071
1072 1072 $ hg clone file://
1073 1073 abort: repository / not found!
1074 1074 [255]
1075 1075
1076 1076 Ensure base clone command argument validation
1077 1077
1078 1078 $ hg clone -U -u 0 a a-clone-failure
1079 1079 abort: cannot specify both --noupdate and --updaterev
1080 1080 [10]
1081 1081
1082 1082 $ hg clone --all-largefiles a ssh://localhost/a
1083 1083 abort: --all-largefiles is incompatible with non-local destination ssh://localhost/a
1084 1084 [255]
1085 1085
1086 1086 Test pulling with --all-largefiles flag. Also test that the largefiles are
1087 1087 downloaded from 'default' instead of 'default-push' when no source is specified
1088 1088 (issue3584)
1089 1089
1090 1090 $ rm -Rf a-backup
1091 1091 $ hg clone -r 1 a a-backup
1092 1092 adding changesets
1093 1093 adding manifests
1094 1094 adding file changes
1095 1095 added 2 changesets with 8 changes to 4 files
1096 1096 new changesets 30d30fe6a5be:ce8896473775 (2 drafts)
1097 1097 updating to branch default
1098 1098 getting changed largefiles
1099 1099 2 largefiles updated, 0 removed
1100 1100 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1101 1101 $ rm "${USERCACHE}"/*
1102 1102 $ cd a-backup
1103 1103 $ hg pull --all-largefiles --config paths.default-push=bogus/path
1104 1104 pulling from $TESTTMP/a
1105 1105 searching for changes
1106 1106 adding changesets
1107 1107 adding manifests
1108 1108 adding file changes
1109 1109 added 6 changesets with 16 changes to 8 files
1110 1110 new changesets 51a0ae4d5864:daea875e9014 (6 drafts)
1111 1111 (run 'hg update' to get a working copy)
1112 1112 6 largefiles cached
1113 1113
1114 1114 redo pull with --lfrev and check it pulls largefiles for the right revs
1115 1115
1116 1116 $ hg rollback
1117 1117 repository tip rolled back to revision 1 (undo pull)
1118 1118 $ hg pull -v --lfrev 'heads(pulled())+min(pulled())'
1119 1119 pulling from $TESTTMP/a
1120 1120 searching for changes
1121 1121 all local changesets known remotely
1122 1122 6 changesets found
1123 1123 uncompressed size of bundle content:
1124 1124 1389 (changelog)
1125 1125 1599 (manifests)
1126 1126 254 .hglf/large1
1127 1127 564 .hglf/large3
1128 1128 572 .hglf/sub/large4
1129 1129 182 .hglf/sub2/large6
1130 1130 182 .hglf/sub2/large7
1131 1131 212 normal1
1132 1132 457 normal3
1133 1133 465 sub/normal4
1134 1134 adding changesets
1135 1135 adding manifests
1136 1136 adding file changes
1137 1137 added 6 changesets with 16 changes to 8 files
1138 1138 new changesets 51a0ae4d5864:daea875e9014 (6 drafts)
1139 1139 calling hook changegroup.lfiles: hgext.largefiles.reposetup.checkrequireslfiles
1140 1140 (run 'hg update' to get a working copy)
1141 1141 pulling largefiles for revision 7
1142 1142 found 971fb41e78fea4f8e0ba5244784239371cb00591 in store
1143 1143 found 0d6d75887db61b2c7e6c74b5dd8fc6ad50c0cc30 in store
1144 1144 found bb3151689acb10f0c3125c560d5e63df914bc1af in store
1145 1145 pulling largefiles for revision 2
1146 1146 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
1147 1147 0 largefiles cached
1148 1148
1149 1149 lfpull
1150 1150
1151 1151 $ hg lfpull -r : --config largefiles.usercache=usercache-lfpull
1152 1152 2 largefiles cached
1153 1153 $ hg lfpull -v -r 4+2 --config largefiles.usercache=usercache-lfpull
1154 1154 pulling largefiles for revision 4
1155 1155 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
1156 1156 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
1157 1157 pulling largefiles for revision 2
1158 1158 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
1159 1159 0 largefiles cached
1160 1160
1161 1161 $ ls usercache-lfpull/* | sort
1162 1162 usercache-lfpull/1deebade43c8c498a3c8daddac0244dc55d1331d
1163 1163 usercache-lfpull/4669e532d5b2c093a78eca010077e708a071bb64
1164 1164
1165 1165 $ cd ..
1166 1166
1167 1167 Rebasing between two repositories does not revert largefiles to old
1168 1168 revisions (this was a very bad bug that took a lot of work to fix).
1169 1169
1170 1170 $ hg clone a d
1171 1171 updating to branch default
1172 1172 getting changed largefiles
1173 1173 3 largefiles updated, 0 removed
1174 1174 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1175 1175 $ cd b
1176 1176 $ echo large4-modified > sub/large4
1177 1177 $ echo normal3-modified > normal3
1178 1178 $ hg commit -m "modify normal file and largefile in repo b"
1179 1179 Invoking status precommit hook
1180 1180 M normal3
1181 1181 M sub/large4
1182 1182 $ cd ../d
1183 1183 $ echo large6-modified > sub2/large6
1184 1184 $ echo normal4-modified > sub/normal4
1185 1185 $ hg commit -m "modify normal file largefile in repo d"
1186 1186 Invoking status precommit hook
1187 1187 M sub/normal4
1188 1188 M sub2/large6
1189 1189 $ cd ..
1190 1190 $ hg clone d e
1191 1191 updating to branch default
1192 1192 getting changed largefiles
1193 1193 3 largefiles updated, 0 removed
1194 1194 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1195 1195 $ cd d
1196 1196
1197 1197 More rebase testing, but also test that the largefiles are downloaded from
1198 1198 'default-push' when no source is specified (issue3584). (The largefile from the
1199 1199 pulled revision is however not downloaded but found in the local cache.)
1200 1200 Largefiles are fetched for the new pulled revision, not for existing revisions,
1201 1201 rebased or not.
1202 1202
1203 1203 $ [ ! -f .hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 ]
1204 1204 $ hg pull --rebase --all-largefiles --config paths.default-push=bogus/path --config paths.default=../b
1205 1205 pulling from $TESTTMP/b
1206 1206 searching for changes
1207 1207 adding changesets
1208 1208 adding manifests
1209 1209 adding file changes
1210 1210 added 1 changesets with 2 changes to 2 files (+1 heads)
1211 1211 new changesets a381d2c8c80e (1 drafts)
1212 1212 0 largefiles cached
1213 1213 rebasing 8:f574fb32bb45 "modify normal file largefile in repo d"
1214 1214 Invoking status precommit hook
1215 1215 M sub/normal4
1216 1216 M sub2/large6
1217 1217 saved backup bundle to $TESTTMP/d/.hg/strip-backup/f574fb32bb45-dd1d9f80-rebase.hg
1218 1218 $ [ -f .hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 ]
1219 1219 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1220 1220 9:598410d3eb9a modify normal file largefile in repo d
1221 1221 8:a381d2c8c80e modify normal file and largefile in repo b
1222 1222 7:daea875e9014 add/edit more largefiles
1223 1223 6:4355d653f84f edit files yet again
1224 1224 5:9d5af5072dbd edit files again
1225 1225 4:74c02385b94c move files
1226 1226 3:9e8fbc4bce62 copy files
1227 1227 2:51a0ae4d5864 remove files
1228 1228 1:ce8896473775 edit files
1229 1229 0:30d30fe6a5be add files
1230 1230 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n'
1231 1231 @ 9:598410d3eb9a modify normal file largefile in repo d
1232 1232 |
1233 1233 o 8:a381d2c8c80e modify normal file and largefile in repo b
1234 1234 |
1235 1235 o 7:daea875e9014 add/edit more largefiles
1236 1236 |
1237 1237 o 6:4355d653f84f edit files yet again
1238 1238 |
1239 1239 o 5:9d5af5072dbd edit files again
1240 1240 |
1241 1241 o 4:74c02385b94c move files
1242 1242 |
1243 1243 o 3:9e8fbc4bce62 copy files
1244 1244 |
1245 1245 o 2:51a0ae4d5864 remove files
1246 1246 |
1247 1247 o 1:ce8896473775 edit files
1248 1248 |
1249 1249 o 0:30d30fe6a5be add files
1250 1250
1251 1251 $ cat normal3
1252 1252 normal3-modified
1253 1253 $ cat sub/normal4
1254 1254 normal4-modified
1255 1255 $ cat sub/large4
1256 1256 large4-modified
1257 1257 $ cat sub2/large6
1258 1258 large6-modified
1259 1259 $ cat sub2/large7
1260 1260 large7
1261 1261 $ cd ../e
1262 1262 $ hg pull ../b
1263 1263 pulling from ../b
1264 1264 searching for changes
1265 1265 adding changesets
1266 1266 adding manifests
1267 1267 adding file changes
1268 1268 added 1 changesets with 2 changes to 2 files (+1 heads)
1269 1269 new changesets a381d2c8c80e (1 drafts)
1270 1270 (run 'hg heads' to see heads, 'hg merge' to merge)
1271 1271 $ hg rebase
1272 1272 rebasing 8:f574fb32bb45 "modify normal file largefile in repo d"
1273 1273 Invoking status precommit hook
1274 1274 M sub/normal4
1275 1275 M sub2/large6
1276 1276 saved backup bundle to $TESTTMP/e/.hg/strip-backup/f574fb32bb45-dd1d9f80-rebase.hg
1277 1277 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1278 1278 9:598410d3eb9a modify normal file largefile in repo d
1279 1279 8:a381d2c8c80e modify normal file and largefile in repo b
1280 1280 7:daea875e9014 add/edit more largefiles
1281 1281 6:4355d653f84f edit files yet again
1282 1282 5:9d5af5072dbd edit files again
1283 1283 4:74c02385b94c move files
1284 1284 3:9e8fbc4bce62 copy files
1285 1285 2:51a0ae4d5864 remove files
1286 1286 1:ce8896473775 edit files
1287 1287 0:30d30fe6a5be add files
1288 1288 $ cat normal3
1289 1289 normal3-modified
1290 1290 $ cat sub/normal4
1291 1291 normal4-modified
1292 1292 $ cat sub/large4
1293 1293 large4-modified
1294 1294 $ cat sub2/large6
1295 1295 large6-modified
1296 1296 $ cat sub2/large7
1297 1297 large7
1298 1298
1299 1299 Log on largefiles
1300 1300
1301 1301 - same output
1302 1302 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub/large4
1303 1303 8:a381d2c8c80e modify normal file and largefile in repo b
1304 1304 6:4355d653f84f edit files yet again
1305 1305 5:9d5af5072dbd edit files again
1306 1306 4:74c02385b94c move files
1307 1307 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub/large4
1308 1308 o 8:a381d2c8c80e modify normal file and largefile in repo b
1309 1309 :
1310 1310 o 6:4355d653f84f edit files yet again
1311 1311 |
1312 1312 o 5:9d5af5072dbd edit files again
1313 1313 |
1314 1314 o 4:74c02385b94c move files
1315 1315 |
1316 1316 ~
1317 1317 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' sub/large4
1318 1318 8:a381d2c8c80e modify normal file and largefile in repo b
1319 1319 6:4355d653f84f edit files yet again
1320 1320 5:9d5af5072dbd edit files again
1321 1321 4:74c02385b94c move files
1322 1322 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub/large4
1323 1323 o 8:a381d2c8c80e modify normal file and largefile in repo b
1324 1324 :
1325 1325 o 6:4355d653f84f edit files yet again
1326 1326 |
1327 1327 o 5:9d5af5072dbd edit files again
1328 1328 |
1329 1329 o 4:74c02385b94c move files
1330 1330 |
1331 1331 ~
1332 1332
1333 1333 - .hglf only matches largefiles, without .hglf it matches 9 bco sub/normal
1334 1334 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub
1335 1335 8:a381d2c8c80e modify normal file and largefile in repo b
1336 1336 6:4355d653f84f edit files yet again
1337 1337 5:9d5af5072dbd edit files again
1338 1338 4:74c02385b94c move files
1339 1339 1:ce8896473775 edit files
1340 1340 0:30d30fe6a5be add files
1341 1341 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub
1342 1342 o 8:a381d2c8c80e modify normal file and largefile in repo b
1343 1343 :
1344 1344 o 6:4355d653f84f edit files yet again
1345 1345 |
1346 1346 o 5:9d5af5072dbd edit files again
1347 1347 |
1348 1348 o 4:74c02385b94c move files
1349 1349 :
1350 1350 o 1:ce8896473775 edit files
1351 1351 |
1352 1352 o 0:30d30fe6a5be add files
1353 1353
1354 1354 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' sub
1355 1355 9:598410d3eb9a modify normal file largefile in repo d
1356 1356 8:a381d2c8c80e modify normal file and largefile in repo b
1357 1357 6:4355d653f84f edit files yet again
1358 1358 5:9d5af5072dbd edit files again
1359 1359 4:74c02385b94c move files
1360 1360 1:ce8896473775 edit files
1361 1361 0:30d30fe6a5be add files
1362 1362 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' sub
1363 1363 @ 9:598410d3eb9a modify normal file largefile in repo d
1364 1364 |
1365 1365 o 8:a381d2c8c80e modify normal file and largefile in repo b
1366 1366 :
1367 1367 o 6:4355d653f84f edit files yet again
1368 1368 |
1369 1369 o 5:9d5af5072dbd edit files again
1370 1370 |
1371 1371 o 4:74c02385b94c move files
1372 1372 :
1373 1373 o 1:ce8896473775 edit files
1374 1374 |
1375 1375 o 0:30d30fe6a5be add files
1376 1376
1377 1377 - globbing gives same result
1378 1378 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' 'glob:sub/*'
1379 1379 9:598410d3eb9a modify normal file largefile in repo d
1380 1380 8:a381d2c8c80e modify normal file and largefile in repo b
1381 1381 6:4355d653f84f edit files yet again
1382 1382 5:9d5af5072dbd edit files again
1383 1383 4:74c02385b94c move files
1384 1384 1:ce8896473775 edit files
1385 1385 0:30d30fe6a5be add files
1386 1386 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' 'glob:sub/*'
1387 1387 @ 9:598410d3eb9a modify normal file largefile in repo d
1388 1388 |
1389 1389 o 8:a381d2c8c80e modify normal file and largefile in repo b
1390 1390 :
1391 1391 o 6:4355d653f84f edit files yet again
1392 1392 |
1393 1393 o 5:9d5af5072dbd edit files again
1394 1394 |
1395 1395 o 4:74c02385b94c move files
1396 1396 :
1397 1397 o 1:ce8896473775 edit files
1398 1398 |
1399 1399 o 0:30d30fe6a5be add files
1400 1400
1401 1401 Rollback on largefiles.
1402 1402
1403 1403 $ echo large4-modified-again > sub/large4
1404 1404 $ hg commit -m "Modify large4 again"
1405 1405 Invoking status precommit hook
1406 1406 M sub/large4
1407 1407 $ hg rollback
1408 1408 repository tip rolled back to revision 9 (undo commit)
1409 1409 working directory now based on revision 9
1410 1410 $ hg st
1411 1411 M sub/large4
1412 1412 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1413 1413 9:598410d3eb9a modify normal file largefile in repo d
1414 1414 8:a381d2c8c80e modify normal file and largefile in repo b
1415 1415 7:daea875e9014 add/edit more largefiles
1416 1416 6:4355d653f84f edit files yet again
1417 1417 5:9d5af5072dbd edit files again
1418 1418 4:74c02385b94c move files
1419 1419 3:9e8fbc4bce62 copy files
1420 1420 2:51a0ae4d5864 remove files
1421 1421 1:ce8896473775 edit files
1422 1422 0:30d30fe6a5be add files
1423 1423 $ cat sub/large4
1424 1424 large4-modified-again
1425 1425
1426 1426 "update --check" refuses to update with uncommitted changes.
1427 1427 $ hg update --check 8
1428 1428 abort: uncommitted changes
1429 1429 [255]
1430 1430
1431 1431 "update --clean" leaves correct largefiles in working copy, even when there is
1432 1432 .orig files from revert in .hglf.
1433 1433
1434 1434 $ echo mistake > sub2/large7
1435 1435 $ hg revert sub2/large7
1436 1436 $ cat sub2/large7
1437 1437 large7
1438 1438 $ cat sub2/large7.orig
1439 1439 mistake
1440 1440 $ test ! -f .hglf/sub2/large7.orig
1441 1441
1442 1442 $ hg -q update --clean -r null
1443 1443 $ hg update --clean
1444 1444 getting changed largefiles
1445 1445 3 largefiles updated, 0 removed
1446 1446 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1447 1447 $ cat normal3
1448 1448 normal3-modified
1449 1449 $ cat sub/normal4
1450 1450 normal4-modified
1451 1451 $ cat sub/large4
1452 1452 large4-modified
1453 1453 $ cat sub2/large6
1454 1454 large6-modified
1455 1455 $ cat sub2/large7
1456 1456 large7
1457 1457 $ cat sub2/large7.orig
1458 1458 mistake
1459 1459 $ test ! -f .hglf/sub2/large7.orig
1460 1460
1461 1461 verify that largefile .orig file no longer is overwritten on every update -C:
1462 1462 $ hg update --clean
1463 1463 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1464 1464 $ cat sub2/large7.orig
1465 1465 mistake
1466 1466 $ rm sub2/large7.orig
1467 1467
1468 1468 Now "update check" is happy.
1469 1469 $ hg update --check 8
1470 1470 getting changed largefiles
1471 1471 1 largefiles updated, 0 removed
1472 1472 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1473 1473 $ hg update --check
1474 1474 getting changed largefiles
1475 1475 1 largefiles updated, 0 removed
1476 1476 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1477 1477
1478 1478 Test removing empty largefiles directories on update
1479 1479 $ test -d sub2 && echo "sub2 exists"
1480 1480 sub2 exists
1481 1481 $ hg update -q null
1482 1482 $ test -d sub2 && echo "error: sub2 should not exist anymore"
1483 1483 [1]
1484 1484 $ hg update -q
1485 1485
1486 1486 Test hg remove removes empty largefiles directories
1487 1487 $ test -d sub2 && echo "sub2 exists"
1488 1488 sub2 exists
1489 1489 $ hg remove sub2/*
1490 1490 $ test -d sub2 && echo "error: sub2 should not exist anymore"
1491 1491 [1]
1492 1492 $ hg revert sub2/large6 sub2/large7
1493 1493
1494 1494 "revert" works on largefiles (and normal files too).
1495 1495 $ echo hack3 >> normal3
1496 1496 $ echo hack4 >> sub/normal4
1497 1497 $ echo hack4 >> sub/large4
1498 1498 $ rm sub2/large6
1499 1499 $ hg revert sub2/large6
1500 1500 $ hg rm sub2/large6
1501 1501 $ echo new >> sub2/large8
1502 1502 $ hg add --large sub2/large8
1503 1503 # XXX we don't really want to report that we're reverting the standin;
1504 1504 # that's just an implementation detail. But I don't see an obvious fix. ;-(
1505 1505 $ hg revert sub
1506 1506 reverting .hglf/sub/large4
1507 1507 reverting sub/normal4
1508 1508 $ hg status
1509 1509 M normal3
1510 1510 A sub2/large8
1511 1511 R sub2/large6
1512 1512 ? sub/large4.orig
1513 1513 ? sub/normal4.orig
1514 1514 $ cat sub/normal4
1515 1515 normal4-modified
1516 1516 $ cat sub/large4
1517 1517 large4-modified
1518 1518 $ hg revert -a --no-backup
1519 1519 forgetting .hglf/sub2/large8
1520 1520 reverting normal3
1521 1521 undeleting .hglf/sub2/large6
1522 1522 $ hg status
1523 1523 ? sub/large4.orig
1524 1524 ? sub/normal4.orig
1525 1525 ? sub2/large8
1526 1526 $ cat normal3
1527 1527 normal3-modified
1528 1528 $ cat sub2/large6
1529 1529 large6-modified
1530 1530 $ rm sub/*.orig sub2/large8
1531 1531
1532 1532 revert some files to an older revision
1533 1533 $ hg revert --no-backup -r 8 sub2
1534 1534 reverting .hglf/sub2/large6
1535 1535 $ cat sub2/large6
1536 1536 large6
1537 1537 $ hg revert --no-backup -C -r '.^' sub2
1538 1538 $ hg revert --no-backup sub2
1539 1539 reverting .hglf/sub2/large6
1540 1540 $ hg status
1541 1541
1542 1542 "verify --large" actually verifies largefiles
1543 1543
1544 1544 - Where Do We Come From? What Are We? Where Are We Going?
1545 1545 $ pwd
1546 1546 $TESTTMP/e
1547 1547 $ hg paths
1548 1548 default = $TESTTMP/d
1549 1549
1550 1550 $ hg verify --large
1551 1551 checking changesets
1552 1552 checking manifests
1553 1553 crosschecking files in changesets and manifests
1554 1554 checking files
1555 1555 checked 10 changesets with 28 changes to 10 files
1556 1556 searching 1 changesets for largefiles
1557 1557 verified existence of 3 revisions of 3 largefiles
1558 1558
1559 1559 - introduce missing blob in local store repo and remote store
1560 1560 and make sure that this is caught:
1561 1561
1562 1562 $ mv $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 .
1563 1563 $ rm .hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928
1564 1564 $ hg verify --large
1565 1565 checking changesets
1566 1566 checking manifests
1567 1567 crosschecking files in changesets and manifests
1568 1568 checking files
1569 1569 checked 10 changesets with 28 changes to 10 files
1570 1570 searching 1 changesets for largefiles
1571 1571 changeset 9:598410d3eb9a: sub/large4 references missing $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928
1572 1572 verified existence of 3 revisions of 3 largefiles
1573 1573 [1]
1574 1574
1575 1575 - introduce corruption and make sure that it is caught when checking content:
1576 1576 $ echo '5 cents' > $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928
1577 1577 $ hg verify -q --large --lfc
1578 1578 changeset 9:598410d3eb9a: sub/large4 references corrupted $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928
1579 1579 [1]
1580 1580
1581 1581 - cleanup
1582 1582 $ cp e166e74c7303192238d60af5a9c4ce9bef0b7928 $TESTTMP/d/.hg/largefiles/
1583 1583 $ mv e166e74c7303192238d60af5a9c4ce9bef0b7928 .hg/largefiles/
1584 1584
1585 1585 - verifying all revisions will fail because we didn't clone all largefiles to d:
1586 1586 $ echo 'T-shirt' > $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1587 1587 $ hg verify -q --lfa --lfc
1588 1588 changeset 0:30d30fe6a5be: large1 references missing $TESTTMP/d/.hg/largefiles/4669e532d5b2c093a78eca010077e708a071bb64
1589 1589 changeset 0:30d30fe6a5be: sub/large2 references missing $TESTTMP/d/.hg/largefiles/1deebade43c8c498a3c8daddac0244dc55d1331d
1590 1590 changeset 1:ce8896473775: large1 references missing $TESTTMP/d/.hg/largefiles/5f78770c0e77ba4287ad6ef3071c9bf9c379742f
1591 1591 changeset 1:ce8896473775: sub/large2 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1592 1592 changeset 3:9e8fbc4bce62: large1 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1593 1593 changeset 4:74c02385b94c: large3 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1594 1594 changeset 4:74c02385b94c: sub/large4 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1595 1595 changeset 5:9d5af5072dbd: large3 references missing $TESTTMP/d/.hg/largefiles/baaf12afde9d8d67f25dab6dced0d2bf77dba47c
1596 1596 changeset 5:9d5af5072dbd: sub/large4 references missing $TESTTMP/d/.hg/largefiles/aeb2210d19f02886dde00dac279729a48471e2f9
1597 1597 changeset 6:4355d653f84f: large3 references missing $TESTTMP/d/.hg/largefiles/7838695e10da2bb75ac1156565f40a2595fa2fa0
1598 1598 [1]
1599 1599
1600 1600 - cleanup
1601 1601 $ rm $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1602 1602 $ rm -f .hglf/sub/*.orig
1603 1603
1604 1604 Update to revision with missing largefile - and make sure it really is missing
1605 1605
1606 1606 $ rm ${USERCACHE}/7838695e10da2bb75ac1156565f40a2595fa2fa0
1607 1607 $ hg up -r 6
1608 1608 getting changed largefiles
1609 1609 large3: largefile 7838695e10da2bb75ac1156565f40a2595fa2fa0 not available from file:/*/$TESTTMP/d (glob)
1610 1610 1 largefiles updated, 2 removed
1611 1611 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
1612 1612 $ rm normal3
1613 1613 $ echo >> sub/normal4
1614 1614 $ hg ci -m 'commit with missing files'
1615 1615 Invoking status precommit hook
1616 1616 M sub/normal4
1617 1617 ! large3
1618 1618 ! normal3
1619 1619 created new head
1620 1620 $ hg st
1621 1621 ! large3
1622 1622 ! normal3
1623 1623 $ hg up -r.
1624 1624 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1625 1625 $ hg st
1626 1626 ! large3
1627 1627 ! normal3
1628 1628 $ hg up -Cr.
1629 1629 getting changed largefiles
1630 1630 large3: largefile 7838695e10da2bb75ac1156565f40a2595fa2fa0 not available from file:/*/$TESTTMP/d (glob)
1631 1631 0 largefiles updated, 0 removed
1632 1632 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1633 1633 $ hg st
1634 1634 ! large3
1635 1635 $ hg rollback
1636 1636 repository tip rolled back to revision 9 (undo commit)
1637 1637 working directory now based on revision 6
1638 1638
1639 1639 Merge with revision with missing largefile - and make sure it tries to fetch it.
1640 1640
1641 1641 $ hg up -Cqr null
1642 1642 $ echo f > f
1643 1643 $ hg ci -Am branch
1644 1644 adding f
1645 1645 Invoking status precommit hook
1646 1646 A f
1647 1647 created new head
1648 1648 $ hg merge -r 6
1649 1649 getting changed largefiles
1650 1650 large3: largefile 7838695e10da2bb75ac1156565f40a2595fa2fa0 not available from file:/*/$TESTTMP/d (glob)
1651 1651 1 largefiles updated, 0 removed
1652 1652 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1653 1653 (branch merge, don't forget to commit)
1654 1654
1655 1655 $ hg rollback -q
1656 1656 $ hg up -Cq
1657 1657
1658 1658 Pulling 0 revisions with --all-largefiles should not fetch for all revisions
1659 1659
1660 1660 $ hg pull --all-largefiles
1661 1661 pulling from $TESTTMP/d
1662 1662 searching for changes
1663 1663 no changes found
1664 1664
1665 1665 Merging does not revert to old versions of largefiles and also check
1666 1666 that merging after having pulled from a non-default remote works
1667 1667 correctly.
1668 1668
1669 1669 $ cd ..
1670 1670 $ hg clone -r 7 e temp
1671 1671 adding changesets
1672 1672 adding manifests
1673 1673 adding file changes
1674 1674 added 8 changesets with 24 changes to 10 files
1675 1675 new changesets 30d30fe6a5be:daea875e9014 (8 drafts)
1676 1676 updating to branch default
1677 1677 getting changed largefiles
1678 1678 3 largefiles updated, 0 removed
1679 1679 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1680 1680 $ hg clone temp f
1681 1681 updating to branch default
1682 1682 getting changed largefiles
1683 1683 3 largefiles updated, 0 removed
1684 1684 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1685 1685 # Delete the largefiles in the largefiles system cache so that we have an
1686 1686 # opportunity to test that caching after a pull works.
1687 1687 $ rm "${USERCACHE}"/*
1688 1688 $ cd f
1689 1689 $ echo "large4-merge-test" > sub/large4
1690 1690 $ hg commit -m "Modify large4 to test merge"
1691 1691 Invoking status precommit hook
1692 1692 M sub/large4
1693 1693 # Test --cache-largefiles flag
1694 1694 $ hg pull --lfrev 'heads(pulled())' ../e
1695 1695 pulling from ../e
1696 1696 searching for changes
1697 1697 adding changesets
1698 1698 adding manifests
1699 1699 adding file changes
1700 1700 added 2 changesets with 4 changes to 4 files (+1 heads)
1701 1701 new changesets a381d2c8c80e:598410d3eb9a (2 drafts)
1702 1702 (run 'hg heads' to see heads, 'hg merge' to merge)
1703 1703 2 largefiles cached
1704 1704 $ hg merge
1705 1705 largefile sub/large4 has a merge conflict
1706 1706 ancestor was 971fb41e78fea4f8e0ba5244784239371cb00591
1707 1707 you can keep (l)ocal d846f26643bfa8ec210be40cc93cc6b7ff1128ea or take (o)ther e166e74c7303192238d60af5a9c4ce9bef0b7928.
1708 1708 what do you want to do? l
1709 1709 getting changed largefiles
1710 1710 1 largefiles updated, 0 removed
1711 1711 3 files updated, 1 files merged, 0 files removed, 0 files unresolved
1712 1712 (branch merge, don't forget to commit)
1713 1713 $ hg commit -m "Merge repos e and f"
1714 1714 Invoking status precommit hook
1715 1715 M normal3
1716 1716 M sub/normal4
1717 1717 M sub2/large6
1718 1718 $ cat normal3
1719 1719 normal3-modified
1720 1720 $ cat sub/normal4
1721 1721 normal4-modified
1722 1722 $ cat sub/large4
1723 1723 large4-merge-test
1724 1724 $ cat sub2/large6
1725 1725 large6-modified
1726 1726 $ cat sub2/large7
1727 1727 large7
1728 1728
1729 1729 Test status after merging with a branch that introduces a new largefile:
1730 1730
1731 1731 $ echo large > large
1732 1732 $ hg add --large large
1733 1733 $ hg commit -m 'add largefile'
1734 1734 Invoking status precommit hook
1735 1735 A large
1736 1736 $ hg update -q ".^"
1737 1737 $ echo change >> normal3
1738 1738 $ hg commit -m 'some change'
1739 1739 Invoking status precommit hook
1740 1740 M normal3
1741 1741 created new head
1742 1742 $ hg merge
1743 1743 getting changed largefiles
1744 1744 1 largefiles updated, 0 removed
1745 1745 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1746 1746 (branch merge, don't forget to commit)
1747 1747 $ hg status
1748 1748 M large
1749 1749
1750 1750 - make sure update of merge with removed largefiles fails as expected
1751 1751 $ hg rm sub2/large6
1752 1752 $ hg up -r.
1753 1753 abort: outstanding uncommitted merge
1754 1754 [255]
1755 1755
1756 1756 - revert should be able to revert files introduced in a pending merge
1757 1757 $ hg revert --all -r .
1758 1758 removing .hglf/large
1759 1759 undeleting .hglf/sub2/large6
1760 1760
1761 1761 Test that a normal file and a largefile with the same name and path cannot
1762 1762 coexist.
1763 1763
1764 1764 $ rm sub2/large7
1765 1765 $ echo "largeasnormal" > sub2/large7
1766 1766 $ hg add sub2/large7
1767 1767 sub2/large7 already a largefile
1768 1768
1769 1769 Test that transplanting a largefile change works correctly.
1770 1770
1771 1771 $ cd ..
1772 1772 $ hg clone -r 8 d g
1773 1773 adding changesets
1774 1774 adding manifests
1775 1775 adding file changes
1776 1776 added 9 changesets with 26 changes to 10 files
1777 1777 new changesets 30d30fe6a5be:a381d2c8c80e (9 drafts)
1778 1778 updating to branch default
1779 1779 getting changed largefiles
1780 1780 3 largefiles updated, 0 removed
1781 1781 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1782 1782 $ cd g
1783 1783 $ hg transplant -s ../d 598410d3eb9a
1784 1784 searching for changes
1785 1785 searching for changes
1786 1786 adding changesets
1787 1787 adding manifests
1788 1788 adding file changes
1789 1789 added 1 changesets with 2 changes to 2 files
1790 1790 new changesets 598410d3eb9a (1 drafts)
1791 1791 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1792 1792 9:598410d3eb9a modify normal file largefile in repo d
1793 1793 8:a381d2c8c80e modify normal file and largefile in repo b
1794 1794 7:daea875e9014 add/edit more largefiles
1795 1795 6:4355d653f84f edit files yet again
1796 1796 5:9d5af5072dbd edit files again
1797 1797 4:74c02385b94c move files
1798 1798 3:9e8fbc4bce62 copy files
1799 1799 2:51a0ae4d5864 remove files
1800 1800 1:ce8896473775 edit files
1801 1801 0:30d30fe6a5be add files
1802 1802 $ cat normal3
1803 1803 normal3-modified
1804 1804 $ cat sub/normal4
1805 1805 normal4-modified
1806 1806 $ cat sub/large4
1807 1807 large4-modified
1808 1808 $ cat sub2/large6
1809 1809 large6-modified
1810 1810 $ cat sub2/large7
1811 1811 large7
1812 1812
1813 1813 Cat a largefile
1814 1814 $ hg cat normal3
1815 1815 normal3-modified
1816 1816 $ hg cat sub/large4
1817 1817 large4-modified
1818 1818 $ rm "${USERCACHE}"/*
1819 1819 $ hg cat -r a381d2c8c80e -o cat.out sub/large4
1820 1820 $ cat cat.out
1821 1821 large4-modified
1822 1822 $ rm cat.out
1823 1823 $ hg cat -r a381d2c8c80e normal3
1824 1824 normal3-modified
1825 1825 $ hg cat -r '.^' normal3
1826 1826 normal3-modified
1827 1827 $ hg cat -r '.^' sub/large4 doesntexist
1828 1828 large4-modified
1829 1829 doesntexist: no such file in rev a381d2c8c80e
1830 1830 $ hg --cwd sub cat -r '.^' large4
1831 1831 large4-modified
1832 1832 $ hg --cwd sub cat -r '.^' ../normal3
1833 1833 normal3-modified
1834 1834 Cat a standin
1835 1835 $ hg cat .hglf/sub/large4
1836 1836 e166e74c7303192238d60af5a9c4ce9bef0b7928
1837 1837 $ hg cat .hglf/normal3
1838 1838 .hglf/normal3: no such file in rev 598410d3eb9a
1839 1839 [1]
1840 1840
1841 1841 Test that renaming a largefile results in correct output for status
1842 1842
1843 1843 $ hg rename sub/large4 large4-renamed
1844 1844 $ hg commit -m "test rename output"
1845 1845 Invoking status precommit hook
1846 1846 A large4-renamed
1847 1847 R sub/large4
1848 1848 $ cat large4-renamed
1849 1849 large4-modified
1850 1850 $ cd sub2
1851 1851 $ hg rename large6 large6-renamed
1852 1852 $ hg st
1853 1853 A sub2/large6-renamed
1854 1854 R sub2/large6
1855 1855 $ cd ..
1856 1856
1857 1857 Test --normal flag
1858 1858
1859 1859 $ dd if=/dev/zero bs=2k count=11k > new-largefile 2> /dev/null
1860 1860 $ hg add --normal --large new-largefile
1861 1861 abort: --normal cannot be used with --large
1862 1862 [255]
1863 1863 $ hg add --normal new-largefile
1864 1864 new-largefile: up to 69 MB of RAM may be required to manage this file
1865 1865 (use 'hg revert new-largefile' to cancel the pending addition)
1866 1866 $ hg revert new-largefile
1867 1867 $ hg --config ui.large-file-limit=22M add --normal new-largefile
1868 1868
1869 1869 Test explicit commit of switch between normal and largefile - make sure both
1870 1870 the add and the remove is committed.
1871 1871
1872 1872 $ hg up -qC
1873 1873 $ hg forget normal3 large4-renamed
1874 1874 $ hg add --large normal3
1875 1875 $ hg add large4-renamed
1876 1876 $ hg commit -m 'swap' normal3 large4-renamed
1877 1877 Invoking status precommit hook
1878 1878 A large4-renamed
1879 1879 A normal3
1880 1880 ? new-largefile
1881 1881 ? sub2/large6-renamed
1882 1882 $ hg mani
1883 1883 .hglf/normal3
1884 1884 .hglf/sub2/large6
1885 1885 .hglf/sub2/large7
1886 1886 large4-renamed
1887 1887 sub/normal4
1888 1888
1889 1889 $ cd ..
1890 1890
1891 1891
1892 1892
@@ -1,275 +1,275 b''
1 1 #require symlink
2 2
3 3 == tests added in 0.7 ==
4 4
5 5 $ hg init test-symlinks-0.7; cd test-symlinks-0.7;
6 6 $ touch foo; ln -s foo bar; ln -s nonexistent baz
7 7
8 8 import with add and addremove -- symlink walking should _not_ screwup.
9 9
10 10 $ hg add
11 11 adding bar
12 12 adding baz
13 13 adding foo
14 14 $ hg forget bar baz foo
15 15 $ hg addremove
16 16 adding bar
17 17 adding baz
18 18 adding foo
19 19
20 20 commit -- the symlink should _not_ appear added to dir state
21 21
22 22 $ hg commit -m 'initial'
23 23
24 24 $ touch bomb
25 25
26 26 again, symlink should _not_ show up on dir state
27 27
28 28 $ hg addremove
29 29 adding bomb
30 30
31 31 Assert screamed here before, should go by without consequence
32 32
33 33 $ hg commit -m 'is there a bug?'
34 34 $ cd ..
35 35
36 36
37 37 == fifo & ignore ==
38 38
39 39 $ hg init test; cd test;
40 40
41 41 $ mkdir dir
42 42 $ touch a.c dir/a.o dir/b.o
43 43
44 44 test what happens if we want to trick hg
45 45
46 46 $ hg commit -A -m 0
47 47 adding a.c
48 48 adding dir/a.o
49 49 adding dir/b.o
50 50 $ echo "relglob:*.o" > .hgignore
51 51 $ rm a.c
52 52 $ rm dir/a.o
53 53 $ rm dir/b.o
54 54 $ mkdir dir/a.o
55 55 $ ln -s nonexistent dir/b.o
56 56 $ mkfifo a.c
57 57
58 58 it should show a.c, dir/a.o and dir/b.o deleted
59 59
60 60 $ hg status
61 61 M dir/b.o
62 62 ! a.c
63 63 ! dir/a.o
64 64 ? .hgignore
65 65 $ hg status a.c
66 66 a.c: unsupported file type (type is fifo)
67 67 ! a.c
68 68 $ cd ..
69 69
70 70
71 71 == symlinks from outside the tree ==
72 72
73 73 test absolute path through symlink outside repo
74 74
75 75 $ p=`pwd`
76 76 $ hg init x
77 77 $ ln -s x y
78 78 $ cd x
79 79 $ touch f
80 80 $ hg add f
81 81 $ hg status "$p"/y/f
82 82 A f
83 83
84 84 try symlink outside repo to file inside
85 85
86 86 $ ln -s x/f ../z
87 87
88 88 this should fail
89 89
90 90 $ hg status ../z && { echo hg mistakenly exited with status 0; exit 1; } || :
91 91 abort: ../z not under root '$TESTTMP/x'
92 92 $ cd ..
93 93
94 94
95 95 == cloning symlinks ==
96 96 $ hg init clone; cd clone;
97 97
98 98 try cloning symlink in a subdir
99 99 1. commit a symlink
100 100
101 101 $ mkdir -p a/b/c
102 102 $ cd a/b/c
103 103 $ ln -s /path/to/symlink/source demo
104 104 $ cd ../../..
105 105 $ hg stat
106 106 ? a/b/c/demo
107 107 $ hg commit -A -m 'add symlink in a/b/c subdir'
108 108 adding a/b/c/demo
109 109
110 110 2. clone it
111 111
112 112 $ cd ..
113 113 $ hg clone clone clonedest
114 114 updating to branch default
115 115 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 116
117 117
118 118 == symlink and git diffs ==
119 119
120 120 git symlink diff
121 121
122 122 $ cd clonedest
123 123 $ hg diff --git -r null:tip
124 124 diff --git a/a/b/c/demo b/a/b/c/demo
125 125 new file mode 120000
126 126 --- /dev/null
127 127 +++ b/a/b/c/demo
128 128 @@ -0,0 +1,1 @@
129 129 +/path/to/symlink/source
130 130 \ No newline at end of file
131 131 $ hg export --git tip > ../sl.diff
132 132
133 133 import git symlink diff
134 134
135 135 $ hg rm a/b/c/demo
136 136 $ hg commit -m'remove link'
137 137 $ hg import ../sl.diff
138 138 applying ../sl.diff
139 139 $ hg diff --git -r 1:tip
140 140 diff --git a/a/b/c/demo b/a/b/c/demo
141 141 new file mode 120000
142 142 --- /dev/null
143 143 +++ b/a/b/c/demo
144 144 @@ -0,0 +1,1 @@
145 145 +/path/to/symlink/source
146 146 \ No newline at end of file
147 147
148 148 == symlinks and addremove ==
149 149
150 150 directory moved and symlinked
151 151
152 152 $ mkdir foo
153 153 $ touch foo/a
154 154 $ hg ci -Ama
155 155 adding foo/a
156 156 $ mv foo bar
157 157 $ ln -s bar foo
158 158 $ hg status
159 159 ! foo/a
160 160 ? bar/a
161 161 ? foo
162 162
163 163 now addremove should remove old files
164 164
165 165 $ hg addremove
166 166 adding bar/a
167 167 adding foo
168 168 removing foo/a
169 169
170 170 commit and update back
171 171
172 172 $ hg ci -mb
173 173 $ hg up '.^'
174 174 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
175 175 $ hg up tip
176 176 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
177 177
178 178 $ cd ..
179 179
180 180 == root of repository is symlinked ==
181 181
182 182 $ hg init root
183 183 $ ln -s root link
184 184 $ cd root
185 185 $ echo foo > foo
186 186 $ hg status
187 187 ? foo
188 188 $ hg status ../link
189 189 ? foo
190 190 $ hg add foo
191 191 $ hg cp foo "$TESTTMP/link/bar"
192 192 foo has not been committed yet, so no copy data will be stored for bar.
193 193 $ cd ..
194 194
195 195
196 196 $ hg init b
197 197 $ cd b
198 198 $ ln -s nothing dangling
199 199 $ hg commit -m 'commit symlink without adding' dangling
200 200 abort: dangling: file not tracked!
201 [255]
201 [10]
202 202 $ hg add dangling
203 203 $ hg commit -m 'add symlink'
204 204
205 205 $ hg tip -v
206 206 changeset: 0:cabd88b706fc
207 207 tag: tip
208 208 user: test
209 209 date: Thu Jan 01 00:00:00 1970 +0000
210 210 files: dangling
211 211 description:
212 212 add symlink
213 213
214 214
215 215 $ hg manifest --debug
216 216 2564acbe54bbbedfbf608479340b359f04597f80 644 @ dangling
217 217 $ readlink.py dangling
218 218 dangling -> nothing
219 219
220 220 $ rm dangling
221 221 $ ln -s void dangling
222 222 $ hg commit -m 'change symlink'
223 223 $ readlink.py dangling
224 224 dangling -> void
225 225
226 226
227 227 modifying link
228 228
229 229 $ rm dangling
230 230 $ ln -s empty dangling
231 231 $ readlink.py dangling
232 232 dangling -> empty
233 233
234 234
235 235 reverting to rev 0:
236 236
237 237 $ hg revert -r 0 -a
238 238 reverting dangling
239 239 $ readlink.py dangling
240 240 dangling -> nothing
241 241
242 242
243 243 backups:
244 244
245 245 $ readlink.py *.orig
246 246 dangling.orig -> empty
247 247 $ rm *.orig
248 248 $ hg up -C
249 249 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
250 250
251 251 copies
252 252
253 253 $ hg cp -v dangling dangling2
254 254 copying dangling to dangling2
255 255 $ hg st -Cmard
256 256 A dangling2
257 257 dangling
258 258 $ readlink.py dangling dangling2
259 259 dangling -> void
260 260 dangling2 -> void
261 261
262 262
263 263 Issue995: hg copy -A incorrectly handles symbolic links
264 264
265 265 $ hg up -C
266 266 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
267 267 $ mkdir dir
268 268 $ ln -s dir dirlink
269 269 $ hg ci -qAm 'add dirlink'
270 270 $ mkdir newdir
271 271 $ mv dir newdir/dir
272 272 $ mv dirlink newdir/dirlink
273 273 $ hg mv -A dirlink newdir/dirlink
274 274
275 275 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now