Git equivalent to Mercurial's 'hg evolve'
Asked Answered
J

1

1

Let's say I have multiple commits, like this:

---A---B---C---D
            \
             E---F---G
                  \
                   H

Now let's say I amend a change to B, creating commit B'

     B'
    /
---A---B---C---D
            \
             E---F---G
                  \
                   H

How can I rebase every descendant of B on B'?

     B'---C'---D'
    /      \
---A---B    E'---F'---G'
                   \
                    H'

In my workflow there are usually multiple branches pointing to these descendant commits and ideally I wouldn't execute a git rebase command for each branch.

In my previous company we used Mercurial, where after amending a commit you could just run 'hg evolve' and it would rebase all descendants of that commit on the new one.

Is there a way to do this in a single command in git? I've been trying to write a script to recursively cherry-pick all descendants based on the commit graph (parsed from git rev-list --children --all) but it feels like there must be an easier way.

Jadajadd answered 2/5 at 9:38 Comment(3)
I think you're looking for git rebase --update-refs.Slenderize
I don't think git provide a way to do that. rebase --update-refs looks similar but is doing it only for a branch pointing to a commit that will be rebased (so a very specific edge case). Maybe there is a tool doing that (that I don't know) but the painful side will always be to checkout each branch of the tree to do a rebase that will allow to manage conflicts. Building your own tool using new experimental git replay command if you don't need to manage conflicts could be an idea: github.blog/2024-02-23-highlights-from-git-2-44/…Isocyanide
If you write a script/program which is able to generate and run the command line git replay --onto B' B..D B..G B..H | git update-ref --stdin. It should work as long as there is no conflicts and none of the branches replayed is checked out (otherwise update-ref will fail on it)Isocyanide
I
0

Using the experimental feature git replay (available from v2.44. More information in this answer), you could create a bash script (successfully tested) called git-evolve with this content (warning I'm not a shell guy, syntax not beautiful):

#to generate and run command: git replay --onto NEW_COMMIT ^OLD_COMMIT ref1 ref2 ref3 | git update-ref --stdin

oldCommit=$1
newCommit=$2

if [ -n "$oldCommit" ] && [ -n "$newCommit" ] ; then
    echo "original commit: $oldCommit / amended new commit: $newCommit"
else
    echo "Input missing!! => original commit: $oldCommit / amended new commit: $newCommit"
    exit 128
fi

replayCommand=(replay --onto $newCommit ^$oldCommit)

branches=$(git branch --format="%(refname:short)" --contains $1)

echo "Will replay branches: $branches"

while IFS= read -r branch; do
 # echo "branch: $branch"
 # handle Detached HEAD state
 if [[ ${branch} != "(HEAD"* ]];then
    replayCommand+=($branch)
 fi

 # echo  "cmd: $replayCommand"
done <<< "$branches"

echo "cmd: git ${replayCommand[@]}"
# replay command just create commits but don't update refs. Output summary need to be passed to 'update-ref' command to make it effective. 
git "${replayCommand[@]}" | git update-ref --stdin

and call it like that:

git-evolve OLD_COMMIT_HASH NEW_AMENDED_COMMIT_HASH

and that will rebase/replay all the local branches that contains the B commit.

Remainder:

  • it's better to have replayed branches not checked out (otherwise your working directory will be in a "not synced" strange state)
  • git replay do not manage conflicts (and maybe script should be improved to manage that)
Isocyanide answered 2/5 at 17:3 Comment(1)
mywiki.wooledge.org/BashFAQ/050 – maybe you can replace $replayCommand with a function?Nonperformance

© 2022 - 2024 — McMap. All rights reserved.