D7631: RFC: absorb: allowing committed changes to be absorbed into their ancestors

rdamazio (Rodrigo Damazio Bovendorp) phabricator at mercurial-scm.org
Fri Dec 13 00:56:00 EST 2019


rdamazio created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This can also be combined with --interactive to absorb just part of a
  commit into its parents.
  
  This initial implementation has the shortcoming that it does not do anything
  with the absorbed commit:
  
  - With obsmarkers, this means the user still needs to evolve/rebase manually
  - Without obsmarkers, the old commits would not be stripped at all because their child was not replaced

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7631

AFFECTED FILES
  hgext/absorb.py
  tests/test-absorb-rev.t
  tests/test-absorb.t

CHANGE DETAILS

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
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,14 @@
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > absorb=
+  > rebase=
+  > [experimental]
+  > evolution=createmarkers
   > 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 +33,13 @@
   > 5e
   > EOF
 
+Commit that, too.
+
+  $ hg commit -qm "commit to absorb"
+
 Preview absorb changes:
 
-  $ hg absorb --print-changes --dry-run
+  $ hg absorb --print-changes --dry-run -r .
   showing changes for a
           @@ -0,2 +0,2 @@
   4ec16f8 -1
@@ -66,462 +58,111 @@
   5c5f952 commit 2
   4ec16f8 commit 1
 
+Add an uncommitted working directory change:
+
+  $ echo 6 >> a
+
 Run absorb:
 
-  $ hg absorb --apply-changes
-  saved backup bundle to * (glob)
+  $ hg absorb --apply-changes -r .
+  1 new orphan changesets
   2 of 2 chunk(s) applied
-  $ hg annotate a
-  0: 1a
-  1: 2b
-  2: 3
-  3: 4d
-  4: 5e
+
+Check that the pending wdir change was left alone:
+
+  $ grep 6 a
+  6
+  $ hg update -Cq .
+
+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.
+
+  $ 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
 
-Delete a few lines and related commits will be removed if they will be empty:
+  $ hg log -G -T '{node|short} {desc} {instabilities}'
+  @  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
+
+Do it again, but this time with an unrelated commit checked out (plus working
+directory changes on top):
 
   $ cat > a <<EOF
+  > 1a
   > 2b
-  > 4d
+  > 3
+  > 4f
+  > 5g
   > EOF
-  $ echo y | hg absorb --config ui.interactive=1
+  $ hg commit -qm "commit to absorb 2"
+  $ TOABSORB=$(hg id -i)
+
+  $ hg update -q 241ace8326d0
+  $ echo committed unrelated >> a
+  $ hg commit -qm "unrelated commit"
+  $ echo pending wdir change >> a
+
+  $ hg absorb --apply-changes --print-changes -r ${TOABSORB}
   showing changes for a
-          @@ -0,1 +0,0 @@
-  f548282 -1a
-          @@ -2,1 +1,0 @@
-  ff5d556 -3
-          @@ -4,1 +2,0 @@
-  84e5416 -5e
+          @@ -3,2 +3,2 @@
+  04c8ba6 -4d
+  2f7ba78 -5e
+  04c8ba6 +4f
+  2f7ba78 +5g
   
-  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
+  2 changesets affected
+  2f7ba78 commit 5
+  04c8ba6 commit 4
+  1 new orphan changesets
+  1 of 1 chunk(s) applied
+
+  $ 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 log -G -T '{node|short} {desc} {instabilities}'
+  o  789b01face13 commit 5
   |
-  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
+  o  9c83c60f49f2 commit 4
   |
-  o  0 commit 1
+  | @  dbce69d9fe03 unrelated commit
+  | |
+  o |  484c6ac0cea3 commit 3
+  | |
+  o |  9b19176bb127 commit 2
+  |/
+  o  241ace8326d0 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:
-
-  $ 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 annotate -c a -r tip
+  241ace8326d0: 1a
+  9b19176bb127: 2b
+  484c6ac0cea3: 3
+  9c83c60f49f2: 4f
+  789b01face13: 5g
 
-  $ 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
-
-Test obsolete markers creation:
-
-  $ 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:
-
-  $ cat >> $HGRCPATH << EOF
-  > [diff]
-  > git=True
-  > EOF
-  $ cd ..
-  $ hg init repo3
-  $ cd repo3
-
-#if execbit
-  $ echo > foo.py
-  $ chmod +x foo.py
-  $ hg add foo.py
-  $ hg commit -mfoo
-#else
-  $ hg import -q --bypass - <<EOF
-  > # 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 @@
-  > +
-  > 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:
-
-  $ cd ..
-  $ hg init repo4
-  $ cd repo4
-  $ cat > a <<EOF
-  > 1
-  > 2
-  > EOF
-  $ hg commit -m a12 -A a
-  $ cat > b <<EOF
-  > 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
-  |
-  o  4 a12
-     diff --git a/a b/a
-     new file mode 100644
-     --- /dev/null
-     +++ b/a
-     @@ -0,0 +1,3 @@
-     +1
-     +2
-     +3
-  
-
-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 <<EOF
-  > 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
-  showing changes for a
-          @@ -2,2 +2,0 @@
-  f1c23dd -3
-  82dbe7f -4
-  
-  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
diff --git a/hgext/absorb.py b/hgext/absorb.py
--- a/hgext/absorb.py
+++ b/hgext/absorb.py
@@ -979,18 +979,20 @@
     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'.']
+        headctx = targetctx.p1()
         if len(headctx.parents()) > 1:
             raise error.Abort(_(b'cannot absorb into a merge'))
+        if len(targetctx.parents()) > 1:
+            raise error.Abort(_(b'cannot absorb a merge'))
+
         stack = getdraftstack(headctx, limit)
         if limit and len(stack) >= limit:
             ui.warn(
@@ -1002,8 +1004,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 +1088,13 @@
                 b'(EXPERIMENTAL)'
             ),
         ),
+        (
+            b'r',
+            b'rev',
+            b'',
+            _(b'the revision to absorb changes from, if not the working '
+              b'directory'),
+        ),
     ]
     + commands.dryrunopts
     + commands.templateopts
@@ -1099,9 +1106,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 `--rev`, 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 +1133,10 @@
         if not opts[b'dry_run']:
             cmdutil.checkunfinished(repo)
 
-        state = absorb(ui, repo, pats=pats, opts=opts)
+        rev = opts[b'rev']
+        # default to working copy
+        ctx = scmutil.revsingle(repo, rev, default=None)
+
+        state = absorb(ui, repo, ctx, pats=pats, opts=opts)
         if sum(s[0] for s in state.chunkstats.values()) == 0:
             return 1



To: rdamazio, #hg-reviewers
Cc: mercurial-devel


More information about the Mercurial-devel mailing list