diff --git a/hgext/absorb.py b/hgext/absorb.py --- a/hgext/absorb.py +++ b/hgext/absorb.py @@ -50,6 +50,7 @@ phases, pycompat, registrar, + rewriteutil, scmutil, util, ) @@ -979,18 +980,22 @@ return overlaycontext(memworkingcopy, ctx) -def absorb(ui, repo, stack=None, targetctx=None, pats=None, opts=None): +def absorb(ui, repo, targetctx, stack=None, pats=None, opts=None): """pick fixup chunks from targetctx, apply them to stack. - if targetctx is None, the working copy context will be used. if stack is None, the current draft stack will be used. return fixupstate. """ if stack is None: - limit = ui.configint(b'absorb', b'max-stack-size') - headctx = repo[b'.'] + targetrev = targetctx.rev() + if targetrev is not None: + rewriteutil.precheck(repo, [targetrev], action=b'absorb') + + headctx = targetctx.p1() if len(headctx.parents()) > 1: raise error.Abort(_(b'cannot absorb into a merge')) + + limit = ui.configint(b'absorb', b'max-stack-size') stack = getdraftstack(headctx, limit) if limit and len(stack) >= limit: ui.warn( @@ -1002,8 +1007,6 @@ ) if not stack: raise error.Abort(_(b'no mutable changeset to change')) - if targetctx is None: # default to working copy - targetctx = repo[None] if pats is None: pats = () if opts is None: @@ -1088,6 +1091,13 @@ b'(EXPERIMENTAL)' ), ), + ( + b'', + b'from', + b'', + _(b'the revision to absorb changes from, if not the working ' + b'directory'), + ), ] + commands.dryrunopts + commands.templateopts @@ -1099,9 +1109,9 @@ def absorbcmd(ui, repo, *pats, **opts): """incorporate corrections into the stack of draft changesets - absorb analyzes each change in your working directory and attempts to - amend the changed lines into the changesets in your stack that first - introduced those lines. + absorb analyzes each change in your working directory (or the revision given + to `--from`, if one is specified) and attempts to amend the changed lines + into the changesets in your stack that first introduced those lines. If absorb cannot find an unambiguous changeset to amend for a change, that change will be left in the working directory, untouched. They can be @@ -1126,6 +1136,11 @@ if not opts[b'dry_run']: cmdutil.checkunfinished(repo) - state = absorb(ui, repo, pats=pats, opts=opts) + source = opts[b'from'] + # default to working copy + ctx = scmutil.revsingle(repo, source, default=None, + abortifmultiple=True) + + state = absorb(ui, repo, ctx, pats=pats, opts=opts) if sum(s[0] for s in state.chunkstats.values()) == 0: return 1 diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py --- a/mercurial/scmutil.py +++ b/mercurial/scmutil.py @@ -684,13 +684,17 @@ return error.FilteredRepoLookupError(msg) -def revsingle(repo, revspec, default=b'.', localalias=None): +def revsingle(repo, revspec, default=b'.', localalias=None, + abortifmultiple=False): if not revspec and revspec != 0: return repo[default] l = revrange(repo, [revspec], localalias=localalias) - if not l: + numl = len(l) + if numl == 0: raise error.Abort(_(b'empty revision set')) + if abortifmultiple and numl > 1: + raise error.Abort(_(b'revision set matched multiple revisions')) return repo[l.last()] diff --git a/relnotes/5.3 b/relnotes/5.3 --- a/relnotes/5.3 +++ b/relnotes/5.3 @@ -1,7 +1,9 @@ == New Features == * Windows will process hgrc files in %PROGRAMDATA%\Mercurial\hgrc.d. - + * The `absorb` extension can now absorb existing changesets, in addition to + the working directory changes, which continues to be the default unless + `--source`/`-s` is specified. == New Experimental Features == diff --git a/relnotes/next b/relnotes/next --- a/relnotes/next +++ b/relnotes/next @@ -1,5 +1,8 @@ == New Features == + * The `absorb` extension can now absorb existing changesets, in addition to + the working directory changes, which continues to be the default unless + `--source`/`-s` is specified. == New Experimental Features == diff --git a/tests/test-absorb.t b/tests/test-absorb-rev.t copy from tests/test-absorb.t copy to tests/test-absorb-rev.t --- a/tests/test-absorb.t +++ b/tests/test-absorb-rev.t @@ -1,26 +1,16 @@ $ cat >> $HGRCPATH << EOF > [extensions] > absorb= + > rebase= + > [experimental] + > evolution=createmarkers + > [alias] + > glog=log -G -T '{node|short} {desc} {instabilities}' > EOF - $ sedi() { # workaround check-code - > pattern="$1" - > shift - > for i in "$@"; do - > sed "$pattern" "$i" > "$i".tmp - > mv "$i".tmp "$i" - > done - > } - $ hg init repo1 $ cd repo1 -Do not crash with empty repo: - - $ hg absorb - abort: no mutable changeset to change - [255] - Make some commits: $ for i in 1 2 3 4 5; do @@ -45,9 +35,26 @@ > 5e > EOF +Commit that, too. + + $ hg commit -qm "commit to absorb" + $ hg glog + @ 1631091f9648 commit to absorb + | + o 4f55fa657dae commit 5 + | + o ad8b8b75557f commit 4 + | + o 43f0a75bede7 commit 3 + | + o 5c5f95224a50 commit 2 + | + o 4ec16f85269a commit 1 + + Preview absorb changes: - $ hg absorb --print-changes --dry-run + $ hg absorb --print-changes --dry-run --from . showing changes for a @@ -0,2 +0,2 @@ 4ec16f8 -1 @@ -66,462 +73,185 @@ 5c5f952 commit 2 4ec16f8 commit 1 +Try to absorb multiple revisions: + + $ hg absorb --apply-changes --from '.^+.' + abort: revision set matched multiple revisions + [255] + +Add an uncommitted working directory change: + + $ echo 6 >> a + Run absorb: - $ hg absorb --apply-changes - saved backup bundle to * (glob) + $ hg absorb --apply-changes --from . + 1 new orphan changesets 2 of 2 chunk(s) applied - $ hg annotate a - 0: 1a - 1: 2b - 2: 3 - 3: 4d - 4: 5e - -Delete a few lines and related commits will be removed if they will be empty: - - $ cat > a < 2b - > 4d - > EOF - $ echo y | hg absorb --config ui.interactive=1 - showing changes for a - @@ -0,1 +0,0 @@ - f548282 -1a - @@ -2,1 +1,0 @@ - ff5d556 -3 - @@ -4,1 +2,0 @@ - 84e5416 -5e +Note: this only shows disconnected graphs because the root node was also +absorbed into. + $ hg glog + o 2f7ba78d6abc commit 5 + | + o 04c8ba6df782 commit 4 + | + o 484c6ac0cea3 commit 3 + | + o 9b19176bb127 commit 2 + | + o 241ace8326d0 commit 1 - 3 changesets affected - 84e5416 commit 5 - ff5d556 commit 3 - f548282 commit 1 - apply changes (yn)? y - saved backup bundle to * (glob) - 3 of 3 chunk(s) applied - $ hg annotate a - 1: 2b - 2: 4d - $ hg log -T '{rev} {desc}\n' -Gp - @ 2 commit 4 - | diff -r 1cae118c7ed8 -r 58a62bade1c6 a - | --- a/a Thu Jan 01 00:00:00 1970 +0000 - | +++ b/a Thu Jan 01 00:00:00 1970 +0000 - | @@ -1,1 +1,2 @@ - | 2b - | +4d + @ 1631091f9648 commit to absorb orphan + | + x 4f55fa657dae commit 5 + | + x ad8b8b75557f commit 4 | - o 1 commit 2 - | diff -r 84add69aeac0 -r 1cae118c7ed8 a - | --- a/a Thu Jan 01 00:00:00 1970 +0000 - | +++ b/a Thu Jan 01 00:00:00 1970 +0000 - | @@ -0,0 +1,1 @@ - | +2b + x 43f0a75bede7 commit 3 | - o 0 commit 1 + x 5c5f95224a50 commit 2 + | + x 4ec16f85269a commit 1 -Non 1:1 map changes will be ignored: - - $ echo 1 > a - $ hg absorb --apply-changes - nothing applied - [1] - -The prompt is not given if there are no changes to be applied, even if there -are some changes that won't be applied: - - $ hg absorb - showing changes for a - @@ -0,2 +0,1 @@ - -2b - -4d - +1 - - 0 changesets affected - nothing applied - [1] - -Insertaions: - - $ cat > a << EOF - > insert before 2b - > 2b - > 4d - > insert aftert 4d - > EOF - $ hg absorb -q --apply-changes - $ hg status - $ hg annotate a - 1: insert before 2b - 1: 2b - 2: 4d - 2: insert aftert 4d - -Bookmarks are moved: - - $ hg bookmark -r 1 b1 - $ hg bookmark -r 2 b2 - $ hg bookmark ba - $ hg bookmarks - b1 1:b35060a57a50 - b2 2:946e4bc87915 - * ba 2:946e4bc87915 - $ sedi 's/insert/INSERT/' a - $ hg absorb -q --apply-changes - $ hg status - $ hg bookmarks - b1 1:a4183e9b3d31 - b2 2:c9b20c925790 - * ba 2:c9b20c925790 - -Non-modified files are ignored: - - $ touch b - $ hg commit -A b -m b - $ touch c - $ hg add c - $ hg rm b - $ hg absorb --apply-changes - nothing applied - [1] - $ sedi 's/INSERT/Insert/' a - $ hg absorb --apply-changes - saved backup bundle to * (glob) - 2 of 2 chunk(s) applied - $ hg status - A c - R b - -Public commits will not be changed: +Check that the pending wdir change was left alone: - $ hg phase -p 1 - $ sedi 's/Insert/insert/' a - $ hg absorb -pn - showing changes for a - @@ -0,1 +0,1 @@ - -Insert before 2b - +insert before 2b - @@ -3,1 +3,1 @@ - 85b4e0e -Insert aftert 4d - 85b4e0e +insert aftert 4d - - 1 changesets affected - 85b4e0e commit 4 - $ hg absorb --apply-changes - saved backup bundle to * (glob) - 1 of 2 chunk(s) applied - $ hg diff -U 0 - diff -r 1c8eadede62a a - --- a/a Thu Jan 01 00:00:00 1970 +0000 - +++ b/a * (glob) - @@ -1,1 +1,1 @@ - -Insert before 2b - +insert before 2b - $ hg annotate a - 1: Insert before 2b - 1: 2b - 2: 4d - 2: insert aftert 4d - - $ hg co -qC 1 - $ sedi 's/Insert/insert/' a - $ hg absorb --apply-changes - abort: no mutable changeset to change - [255] - -Make working copy clean: - - $ hg co -qC ba - $ rm c - $ hg status - -Merge commit will not be changed: - - $ echo 1 > m1 - $ hg commit -A m1 -m m1 - $ hg bookmark -q -i m1 - $ hg update -q '.^' - $ echo 2 > m2 - $ hg commit -q -A m2 -m m2 - $ hg merge -q m1 - $ hg commit -m merge - $ hg bookmark -d m1 - $ hg log -G -T '{rev} {desc} {phase}\n' - @ 6 merge draft - |\ - | o 5 m2 draft - | | - o | 4 m1 draft - |/ - o 3 b draft - | - o 2 commit 4 draft - | - o 1 commit 2 public - | - o 0 commit 1 public - - $ echo 2 >> m1 - $ echo 2 >> m2 - $ hg absorb --apply-changes - abort: cannot absorb into a merge - [255] - $ hg revert -q -C m1 m2 - -Use a new repo: - - $ cd .. - $ hg init repo2 - $ cd repo2 - -Make some commits to multiple files: - - $ for f in a b; do - > for i in 1 2; do - > echo $f line $i >> $f - > hg commit -A $f -m "commit $f $i" -q - > done - > done - -Use pattern to select files to be fixed up: - - $ sedi 's/line/Line/' a b $ hg status M a - M b - $ hg absorb --apply-changes a - saved backup bundle to * (glob) - 1 of 1 chunk(s) applied - $ hg status - M b - $ hg absorb --apply-changes --exclude b - nothing applied - [1] - $ hg absorb --apply-changes b - saved backup bundle to * (glob) - 1 of 1 chunk(s) applied - $ hg status - $ cat a b - a Line 1 - a Line 2 - b Line 1 - b Line 2 - -Test config option absorb.max-stack-size: + $ hg diff + diff -r [0-9a-f]+ a (re) + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -3,3 +3,4 @@ + 3 + 4d + 5e + +6 + $ hg update -Cq . - $ sedi 's/Line/line/' a b - $ hg log -T '{rev}:{node} {desc}\n' - 3:712d16a8f445834e36145408eabc1d29df05ec09 commit b 2 - 2:74cfa6294160149d60adbf7582b99ce37a4597ec commit b 1 - 1:28f10dcf96158f84985358a2e5d5b3505ca69c22 commit a 2 - 0:f9a81da8dc53380ed91902e5b82c1b36255a4bd0 commit a 1 - $ hg --config absorb.max-stack-size=1 absorb -pn - absorb: only the recent 1 changesets will be analysed - showing changes for a - @@ -0,2 +0,2 @@ - -a Line 1 - -a Line 2 - +a line 1 - +a line 2 - showing changes for b - @@ -0,2 +0,2 @@ - -b Line 1 - 712d16a -b Line 2 - +b line 1 - 712d16a +b line 2 - - 1 changesets affected - 712d16a commit b 2 +Rebase the absorbed revision on top of the destination (as evolve would): +TODO: the evolve-type operation should happen automatically for the changeset +being absorbed, and even through that the pending wdir change should be left +alone. -Test obsolete markers creation: + $ hg rebase -d tip -r . + rebasing 5:1631091f9648 "commit to absorb" + note: not rebasing 5:1631091f9648 "commit to absorb", its destination already has all its changes - $ cat >> $HGRCPATH << EOF - > [experimental] - > evolution=createmarkers - > [absorb] - > add-noise=1 - > EOF - - $ hg --config absorb.max-stack-size=3 absorb -a - absorb: only the recent 3 changesets will be analysed - 2 of 2 chunk(s) applied - $ hg log -T '{rev}:{node|short} {desc} {get(extras, "absorb_source")}\n' - 6:3dfde4199b46 commit b 2 712d16a8f445834e36145408eabc1d29df05ec09 - 5:99cfab7da5ff commit b 1 74cfa6294160149d60adbf7582b99ce37a4597ec - 4:fec2b3bd9e08 commit a 2 28f10dcf96158f84985358a2e5d5b3505ca69c22 - 0:f9a81da8dc53 commit a 1 - $ hg absorb --apply-changes - 1 of 1 chunk(s) applied - $ hg log -T '{rev}:{node|short} {desc} {get(extras, "absorb_source")}\n' - 10:e1c8c1e030a4 commit b 2 3dfde4199b4610ea6e3c6fa9f5bdad8939d69524 - 9:816c30955758 commit b 1 99cfab7da5ffdaf3b9fc6643b14333e194d87f46 - 8:5867d584106b commit a 2 fec2b3bd9e0834b7cb6a564348a0058171aed811 - 7:8c76602baf10 commit a 1 f9a81da8dc53380ed91902e5b82c1b36255a4bd0 - -Executable files: + $ hg glog + @ 2f7ba78d6abc commit 5 + | + o 04c8ba6df782 commit 4 + | + o 484c6ac0cea3 commit 3 + | + o 9b19176bb127 commit 2 + | + o 241ace8326d0 commit 1 + + $ hg annotate -c a + 241ace8326d0: 1a + 9b19176bb127: 2b + 484c6ac0cea3: 3 + 04c8ba6df782: 4d + 2f7ba78d6abc: 5e - $ cat >> $HGRCPATH << EOF - > [diff] - > git=True - > EOF - $ cd .. - $ hg init repo3 - $ cd repo3 +Do it again, but this time with an unrelated commit checked out (plus working +directory changes on top): -#if execbit - $ echo > foo.py - $ chmod +x foo.py - $ hg add foo.py - $ hg commit -mfoo -#else - $ hg import -q --bypass - < # HG changeset patch - > foo - > - > diff --git a/foo.py b/foo.py - > new file mode 100755 - > --- /dev/null - > +++ b/foo.py - > @@ -0,0 +1,1 @@ - > + + $ cat > a < 1a + > 2b + > 3 + > 4f + > 5g > EOF - $ hg up -q -#endif - - $ echo bla > foo.py - $ hg absorb --dry-run --print-changes - showing changes for foo.py - @@ -0,1 +0,1 @@ - 99b4ae7 - - 99b4ae7 +bla - - 1 changesets affected - 99b4ae7 foo - $ hg absorb --dry-run --interactive --print-changes - diff -r 99b4ae712f84 foo.py - 1 hunks, 1 lines changed - examine changes to 'foo.py'? - (enter ? for help) [Ynesfdaq?] y - - @@ -1,1 +1,1 @@ - - - +bla - record this change to 'foo.py'? - (enter ? for help) [Ynesfdaq?] y - - showing changes for foo.py - @@ -0,1 +0,1 @@ - 99b4ae7 - - 99b4ae7 +bla - - 1 changesets affected - 99b4ae7 foo - $ hg absorb --apply-changes - 1 of 1 chunk(s) applied - $ hg diff -c . - diff --git a/foo.py b/foo.py - new file mode 100755 - --- /dev/null - +++ b/foo.py - @@ -0,0 +1,1 @@ - +bla - $ hg diff - -Remove lines may delete changesets: + $ hg commit -qm "commit to absorb 2" + $ TOABSORB=$(hg id -i) - $ cd .. - $ hg init repo4 - $ cd repo4 - $ cat > a < 1 - > 2 - > EOF - $ hg commit -m a12 -A a - $ cat > b < 1 - > 2 - > EOF - $ hg commit -m b12 -A b - $ echo 3 >> b - $ hg commit -m b3 - $ echo 4 >> b - $ hg commit -m b4 - $ echo 1 > b - $ echo 3 >> a - $ hg absorb -pn - showing changes for a - @@ -2,0 +2,1 @@ - bfafb49 +3 - showing changes for b - @@ -1,3 +1,0 @@ - 1154859 -2 - 30970db -3 - a393a58 -4 - - 4 changesets affected - a393a58 b4 - 30970db b3 - 1154859 b12 - bfafb49 a12 - $ hg absorb -av | grep became - 0:bfafb49242db: 1 file(s) changed, became 4:1a2de97fc652 - 1:115485984805: 2 file(s) changed, became 5:0c930dfab74c - 2:30970dbf7b40: became empty and was dropped - 3:a393a58b9a85: became empty and was dropped - $ hg log -T '{rev} {desc}\n' -Gp - @ 5 b12 - | diff --git a/b b/b - | new file mode 100644 - | --- /dev/null - | +++ b/b - | @@ -0,0 +1,1 @@ - | +1 + $ hg update -q 241ace8326d0 + $ echo committed unrelated >> a + $ hg commit -qm "unrelated commit" + $ echo pending wdir change >> a + $ hg glog + @ dbce69d9fe03 unrelated commit | - o 4 a12 - diff --git a/a b/a - new file mode 100644 - --- /dev/null - +++ b/a - @@ -0,0 +1,3 @@ - +1 - +2 - +3 + | o 410fcc72c5c9 commit to absorb 2 + | | + | o 2f7ba78d6abc commit 5 + | | + | o 04c8ba6df782 commit 4 + | | + | o 484c6ac0cea3 commit 3 + | | + | o 9b19176bb127 commit 2 + |/ + o 241ace8326d0 commit 1 -Use revert to make the current change and its parent disappear. -This should move us to the non-obsolete ancestor. - - $ cd .. - $ hg init repo5 - $ cd repo5 - $ cat > a < 1 - > 2 - > EOF - $ hg commit -m a12 -A a - $ hg id - bfafb49242db tip - $ echo 3 >> a - $ hg commit -m a123 a - $ echo 4 >> a - $ hg commit -m a1234 a - $ hg id - 82dbe7fd19f0 tip - $ hg revert -r 0 a - $ hg absorb -pn + $ hg absorb --apply-changes --print-changes --from ${TOABSORB} showing changes for a - @@ -2,2 +2,0 @@ - f1c23dd -3 - 82dbe7f -4 + @@ -3,2 +3,2 @@ + 04c8ba6 -4d + 2f7ba78 -5e + 04c8ba6 +4f + 2f7ba78 +5g 2 changesets affected - 82dbe7f a1234 - f1c23dd a123 - $ hg absorb --apply-changes --verbose - 1:f1c23dd5d08d: became empty and was dropped - 2:82dbe7fd19f0: became empty and was dropped - a: 1 of 1 chunk(s) applied - $ hg id - bfafb49242db tip + 2f7ba78 commit 5 + 04c8ba6 commit 4 + 1 new orphan changesets + 1 of 1 chunk(s) applied + + $ hg glog + o 789b01face13 commit 5 + | + o 9c83c60f49f2 commit 4 + | + | @ dbce69d9fe03 unrelated commit + | | + | | * 410fcc72c5c9 commit to absorb 2 orphan + | | | + | | x 2f7ba78d6abc commit 5 + | | | + +---x 04c8ba6df782 commit 4 + | | + o | 484c6ac0cea3 commit 3 + | | + o | 9b19176bb127 commit 2 + |/ + o 241ace8326d0 commit 1 + + + $ hg annotate -c a -r 'wdir()' + 241ace8326d0 : 1a + dbce69d9fe03 : committed unrelated + dbce69d9fe03+: pending wdir change + + $ hg update -Cq . + + $ hg rebase -d tip -r ${TOABSORB} + rebasing \d+:[0-9a-f]+ "commit to absorb 2" (re) + note: not rebasing \d+:[0-9a-f]+ "commit to absorb 2", its destination already has all its changes (re) + + $ hg glog + o 789b01face13 commit 5 + | + o 9c83c60f49f2 commit 4 + | + | @ dbce69d9fe03 unrelated commit + | | + o | 484c6ac0cea3 commit 3 + | | + o | 9b19176bb127 commit 2 + |/ + o 241ace8326d0 commit 1 + + + $ hg annotate -c a -r tip + 241ace8326d0: 1a + 9b19176bb127: 2b + 484c6ac0cea3: 3 + 9c83c60f49f2: 4f + 789b01face13: 5g + diff --git a/tests/test-absorb.t b/tests/test-absorb.t --- a/tests/test-absorb.t +++ b/tests/test-absorb.t @@ -143,7 +143,7 @@ nothing applied [1] -Insertaions: +Insertions: $ cat > a << EOF > insert before 2b