Say I have this:
I1 -- I2 -- I3
\ \
F1 -- F2 -- ? -- F3 -- F4 -- F5
I created a new feature branch, F, I made commit F1 and F2, and then after that I wanted to incorporate changes from the integration branch to prevent conflicts.
So I do:
git fetch origin I
git checkout F
git rebase origin/I
So what it should look like now is something like this:
I1 -- I2 -- I3
\
F1 -- F2 -- F3 -- F4 -- F5
my question is - now I want to squash commits F1-F5, into one commit. How can I safely squash the commits?
I can do git reset --soft x
, but what is x
? How can I find which commit to go back to? I don't want to lose any of the history from the integration branch.
In English, I suppose it would be the oldest commit on my branch that isn't in the integration branch, but also where that commit is younger than any commits from the integration branch. (?) The latter scenario might occur if I do a git merge
instead of git rebase
(by accident).
Try git fetch && GIT_SEQUENCE_EDITOR="sed -i '' 's/^pick/squash/'" git rebase -i @{u}
(you can squeeze this into an alias, shell or Git-to-shell).
Consider the following: When you run git rebase
, you tell git rebase
where to put the copies. That is, you run:
git checkout $branch
git rebase $onto
where $branch
is the name of your branch, and $onto
is the thing that specifies how to do the rebase. Since the copies go after the commit specified by $onto
, $onto
is automatically the correct point for your git reset --soft
.
If you have git pull --rebase
run git rebase
for you, you give up direct control of $onto
here. The git pull
command first runs git fetch $remote
for some $remote
, then uses the fetched information that git fetch
records (in $GIT_DIR/FETCH_HEAD
) as the $onto
parameter.
There is a bit of extra complication here due to the fork-point code, but in modern Git, you have the advantage that git fetch
will update $upstream
where $upstream
is the upstream of the current branch, after which git rebase
will use $upstream
and do all the fork-point stuff automatically, so that $upstream
is still correct. In this case, using:
. git-sh-setup
branch=$(git symbolic-ref -q --short HEAD) || die 'not currently on a branch'
remote=$(git config --get branch.$branch.remote) || die 'current branch has no upstream'
git fetch $remote && git rebase || die 'fetch or rebase failed'
git reset --soft $upstream && git commit
as your script (named, e.g., git-rebase-and-squash
in your $PATH
and run as git rebase-and-squash
) would mostly do the trick.
Rather than using reset --soft
and git commit
directly, though, you might want to make the last line read:
GIT_SEQUENCE_EDITOR="sed -i '' 's/^pick/squash/'" git rebase -i $upstream
which would convert all the pick
s to squash
es for you, and in this case you would not even need the earlier git rebase
, so that the whole script collapses down to git fetch
followed by git rebase -i @{upstream}
. (Whether you still want to automate the sequence editing is up to you.)
If you don't mind slightly misleading error messages, you don't need the checking either. Running git fetch
will fetch from origin
by default, and @upstream
will fail noisily if there is no current branch or if the current branch has no upstream, so this can be reduced to a two-command-long script or alias:
git fetch && GIT_SEQUENCE_EDITOR="sed -i '' 's/^pick/squash/'" git rebase -i @{u}