Merge branch 'js/rebase'
* js/rebase: Teach rebase -i about --preserve-merges rebase -i: provide reasonable reflog for the rebased branch rebase -i: several cleanups ignore git-rebase--interactive Teach rebase an interactive mode Move the pick_author code to git-sh-setup
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -96,6 +96,7 @@ git-push
|
|||||||
git-quiltimport
|
git-quiltimport
|
||||||
git-read-tree
|
git-read-tree
|
||||||
git-rebase
|
git-rebase
|
||||||
|
git-rebase--interactive
|
||||||
git-receive-pack
|
git-receive-pack
|
||||||
git-reflog
|
git-reflog
|
||||||
git-relink
|
git-relink
|
||||||
|
@ -8,7 +8,8 @@ git-rebase - Forward-port local commits to the updated upstream head
|
|||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
[verse]
|
[verse]
|
||||||
'git-rebase' [-v] [--merge] [-C<n>] [--onto <newbase>] <upstream> [<branch>]
|
'git-rebase' [-i | --interactive] [-v | --verbose] [--merge] [-C<n>]
|
||||||
|
[-p | --preserve-merges] [--onto <newbase>] <upstream> [<branch>]
|
||||||
'git-rebase' --continue | --skip | --abort
|
'git-rebase' --continue | --skip | --abort
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
@ -208,6 +209,14 @@ OPTIONS
|
|||||||
context exist they all must match. By default no context is
|
context exist they all must match. By default no context is
|
||||||
ever ignored.
|
ever ignored.
|
||||||
|
|
||||||
|
-i, \--interactive::
|
||||||
|
Make a list of the commits which are about to be rebased. Let the
|
||||||
|
user edit that list before rebasing.
|
||||||
|
|
||||||
|
-p, \--preserve-merges::
|
||||||
|
Instead of ignoring merges, try to recreate them. This option
|
||||||
|
only works in interactive mode.
|
||||||
|
|
||||||
include::merge-strategies.txt[]
|
include::merge-strategies.txt[]
|
||||||
|
|
||||||
NOTES
|
NOTES
|
||||||
@ -226,9 +235,100 @@ pre-rebase hook script for an example.
|
|||||||
You must be in the top directory of your project to start (or continue)
|
You must be in the top directory of your project to start (or continue)
|
||||||
a rebase. Upon completion, <branch> will be the current branch.
|
a rebase. Upon completion, <branch> will be the current branch.
|
||||||
|
|
||||||
Author
|
INTERACTIVE MODE
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Rebasing interactively means that you have a chance to edit the commits
|
||||||
|
which are rebased. You can reorder the commits, and you can
|
||||||
|
remove them (weeding out bad or otherwise unwanted patches).
|
||||||
|
|
||||||
|
The interactive mode is meant for this type of workflow:
|
||||||
|
|
||||||
|
1. have a wonderful idea
|
||||||
|
2. hack on the code
|
||||||
|
3. prepare a series for submission
|
||||||
|
4. submit
|
||||||
|
|
||||||
|
where point 2. consists of several instances of
|
||||||
|
|
||||||
|
a. regular use
|
||||||
|
1. finish something worthy of a commit
|
||||||
|
2. commit
|
||||||
|
b. independent fixup
|
||||||
|
1. realize that something does not work
|
||||||
|
2. fix that
|
||||||
|
3. commit it
|
||||||
|
|
||||||
|
Sometimes the thing fixed in b.2. cannot be amended to the not-quite
|
||||||
|
perfect commit it fixes, because that commit is buried deeply in a
|
||||||
|
patch series. That is exactly what interactive rebase is for: use it
|
||||||
|
after plenty of "a"s and "b"s, by rearranging and editing
|
||||||
|
commits, and squashing multiple commits into one.
|
||||||
|
|
||||||
|
Start it with the last commit you want to retain as-is:
|
||||||
|
|
||||||
|
git rebase -i <after-this-commit>
|
||||||
|
|
||||||
|
An editor will be fired up with all the commits in your current branch
|
||||||
|
(ignoring merge commits), which come after the given commit. You can
|
||||||
|
reorder the commits in this list to your heart's content, and you can
|
||||||
|
remove them. The list looks more or less like this:
|
||||||
|
|
||||||
|
-------------------------------------------
|
||||||
|
pick deadbee The oneline of this commit
|
||||||
|
pick fa1afe1 The oneline of the next commit
|
||||||
|
...
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
The oneline descriptions are purely for your pleasure; `git-rebase` will
|
||||||
|
not look at them but at the commit names ("deadbee" and "fa1afe1" in this
|
||||||
|
example), so do not delete or edit the names.
|
||||||
|
|
||||||
|
By replacing the command "pick" with the command "edit", you can tell
|
||||||
|
`git-rebase` to stop after applying that commit, so that you can edit
|
||||||
|
the files and/or the commit message, amend the commit, and continue
|
||||||
|
rebasing.
|
||||||
|
|
||||||
|
If you want to fold two or more commits into one, replace the command
|
||||||
|
"pick" with "squash" for the second and subsequent commit. If the
|
||||||
|
commits had different authors, it will attribute the squashed commit to
|
||||||
|
the author of the last commit.
|
||||||
|
|
||||||
|
In both cases, or when a "pick" does not succeed (because of merge
|
||||||
|
errors), the loop will stop to let you fix things, and you can continue
|
||||||
|
the loop with `git rebase --continue`.
|
||||||
|
|
||||||
|
For example, if you want to reorder the last 5 commits, such that what
|
||||||
|
was HEAD~4 becomes the new HEAD. To achieve that, you would call
|
||||||
|
`git-rebase` like this:
|
||||||
|
|
||||||
|
----------------------
|
||||||
|
$ git rebase -i HEAD~5
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
And move the first patch to the end of the list.
|
||||||
|
|
||||||
|
You might want to preserve merges, if you have a history like this:
|
||||||
|
|
||||||
|
------------------
|
||||||
|
X
|
||||||
|
\
|
||||||
|
A---M---B
|
||||||
|
/
|
||||||
|
---o---O---P---Q
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Suppose you want to rebase the side branch starting at "A" to "Q". Make
|
||||||
|
sure that the current HEAD is "B", and call
|
||||||
|
|
||||||
|
-----------------------------
|
||||||
|
$ git rebase -i -p --onto Q O
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
Authors
|
||||||
------
|
------
|
||||||
Written by Junio C Hamano <junkio@cox.net>
|
Written by Junio C Hamano <junkio@cox.net> and
|
||||||
|
Johannes E. Schindelin <johannes.schindelin@gmx.de>
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
--------------
|
--------------
|
||||||
|
2
Makefile
2
Makefile
@ -204,7 +204,7 @@ SCRIPT_SH = \
|
|||||||
git-fetch.sh \
|
git-fetch.sh \
|
||||||
git-ls-remote.sh \
|
git-ls-remote.sh \
|
||||||
git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
|
git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
|
||||||
git-pull.sh git-rebase.sh \
|
git-pull.sh git-rebase.sh git-rebase--interactive.sh \
|
||||||
git-repack.sh git-request-pull.sh git-reset.sh \
|
git-repack.sh git-request-pull.sh git-reset.sh \
|
||||||
git-sh-setup.sh \
|
git-sh-setup.sh \
|
||||||
git-tag.sh git-verify-tag.sh \
|
git-tag.sh git-verify-tag.sh \
|
||||||
|
@ -483,34 +483,8 @@ fi >>"$GIT_DIR"/COMMIT_EDITMSG
|
|||||||
# Author
|
# Author
|
||||||
if test '' != "$use_commit"
|
if test '' != "$use_commit"
|
||||||
then
|
then
|
||||||
pick_author_script='
|
eval "$(get_author_ident_from_commit "$use_commit")"
|
||||||
/^author /{
|
export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
|
||||||
s/'\''/'\''\\'\'\''/g
|
|
||||||
h
|
|
||||||
s/^author \([^<]*\) <[^>]*> .*$/\1/
|
|
||||||
s/'\''/'\''\'\'\''/g
|
|
||||||
s/.*/GIT_AUTHOR_NAME='\''&'\''/p
|
|
||||||
|
|
||||||
g
|
|
||||||
s/^author [^<]* <\([^>]*\)> .*$/\1/
|
|
||||||
s/'\''/'\''\'\'\''/g
|
|
||||||
s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
|
|
||||||
|
|
||||||
g
|
|
||||||
s/^author [^<]* <[^>]*> \(.*\)$/\1/
|
|
||||||
s/'\''/'\''\'\'\''/g
|
|
||||||
s/.*/GIT_AUTHOR_DATE='\''&'\''/p
|
|
||||||
|
|
||||||
q
|
|
||||||
}
|
|
||||||
'
|
|
||||||
encoding=$(git config i18n.commitencoding || echo UTF-8)
|
|
||||||
set_author_env=`git show -s --pretty=raw --encoding="$encoding" "$use_commit" |
|
|
||||||
LANG=C LC_ALL=C sed -ne "$pick_author_script"`
|
|
||||||
eval "$set_author_env"
|
|
||||||
export GIT_AUTHOR_NAME
|
|
||||||
export GIT_AUTHOR_EMAIL
|
|
||||||
export GIT_AUTHOR_DATE
|
|
||||||
fi
|
fi
|
||||||
if test '' != "$force_author"
|
if test '' != "$force_author"
|
||||||
then
|
then
|
||||||
|
410
git-rebase--interactive.sh
Executable file
410
git-rebase--interactive.sh
Executable file
@ -0,0 +1,410 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright (c) 2006 Johannes E. Schindelin
|
||||||
|
|
||||||
|
# SHORT DESCRIPTION
|
||||||
|
#
|
||||||
|
# This script makes it easy to fix up commits in the middle of a series,
|
||||||
|
# and rearrange commits.
|
||||||
|
#
|
||||||
|
# The original idea comes from Eric W. Biederman, in
|
||||||
|
# http://article.gmane.org/gmane.comp.version-control.git/22407
|
||||||
|
|
||||||
|
USAGE='(--continue | --abort | --skip | [--preserve-merges] [--verbose]
|
||||||
|
[--onto <branch>] <upstream> [<branch>])'
|
||||||
|
|
||||||
|
. git-sh-setup
|
||||||
|
require_work_tree
|
||||||
|
|
||||||
|
DOTEST="$GIT_DIR/.dotest-merge"
|
||||||
|
TODO="$DOTEST"/todo
|
||||||
|
DONE="$DOTEST"/done
|
||||||
|
REWRITTEN="$DOTEST"/rewritten
|
||||||
|
PRESERVE_MERGES=
|
||||||
|
STRATEGY=
|
||||||
|
VERBOSE=
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
require_clean_work_tree () {
|
||||||
|
# test if working tree is dirty
|
||||||
|
git rev-parse --verify HEAD > /dev/null &&
|
||||||
|
git update-index --refresh &&
|
||||||
|
git diff-files --quiet &&
|
||||||
|
git diff-index --cached --quiet HEAD ||
|
||||||
|
die "Working tree is dirty"
|
||||||
|
}
|
||||||
|
|
||||||
|
ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
|
||||||
|
|
||||||
|
comment_for_reflog () {
|
||||||
|
case "$ORIG_REFLOG_ACTION" in
|
||||||
|
''|rebase*)
|
||||||
|
GIT_REFLOG_ACTION="rebase -i ($1)"
|
||||||
|
export GIT_REFLOG_ACTION
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
mark_action_done () {
|
||||||
|
sed -e 1q < "$TODO" >> "$DONE"
|
||||||
|
sed -e 1d < "$TODO" >> "$TODO".new
|
||||||
|
mv -f "$TODO".new "$TODO"
|
||||||
|
}
|
||||||
|
|
||||||
|
make_patch () {
|
||||||
|
parent_sha1=$(git rev-parse --verify "$1"^ 2> /dev/null)
|
||||||
|
git diff "$parent_sha1".."$1" > "$DOTEST"/patch
|
||||||
|
}
|
||||||
|
|
||||||
|
die_with_patch () {
|
||||||
|
make_patch "$1"
|
||||||
|
die "$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
die_abort () {
|
||||||
|
rm -rf "$DOTEST"
|
||||||
|
die "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
pick_one () {
|
||||||
|
case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
|
||||||
|
git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
|
||||||
|
test -d "$REWRITTEN" &&
|
||||||
|
pick_one_preserving_merges "$@" && return
|
||||||
|
parent_sha1=$(git rev-parse --verify $sha1^ 2>/dev/null)
|
||||||
|
current_sha1=$(git rev-parse --verify HEAD)
|
||||||
|
if [ $current_sha1 = $parent_sha1 ]; then
|
||||||
|
git reset --hard $sha1
|
||||||
|
sha1=$(git rev-parse --short $sha1)
|
||||||
|
warn Fast forward to $sha1
|
||||||
|
else
|
||||||
|
git cherry-pick $STRATEGY "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
pick_one_preserving_merges () {
|
||||||
|
case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
|
||||||
|
sha1=$(git rev-parse $sha1)
|
||||||
|
|
||||||
|
if [ -f "$DOTEST"/current-commit ]
|
||||||
|
then
|
||||||
|
current_commit=$(cat "$DOTEST"/current-commit) &&
|
||||||
|
git rev-parse HEAD > "$REWRITTEN"/$current_commit &&
|
||||||
|
rm "$DOTEST"/current-commit ||
|
||||||
|
die "Cannot write current commit's replacement sha1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# rewrite parents; if none were rewritten, we can fast-forward.
|
||||||
|
fast_forward=t
|
||||||
|
preserve=t
|
||||||
|
new_parents=
|
||||||
|
for p in $(git rev-list --parents -1 $sha1 | cut -d\ -f2-)
|
||||||
|
do
|
||||||
|
if [ -f "$REWRITTEN"/$p ]
|
||||||
|
then
|
||||||
|
preserve=f
|
||||||
|
new_p=$(cat "$REWRITTEN"/$p)
|
||||||
|
test $p != $new_p && fast_forward=f
|
||||||
|
case "$new_parents" in
|
||||||
|
*$new_p*)
|
||||||
|
;; # do nothing; that parent is already there
|
||||||
|
*)
|
||||||
|
new_parents="$new_parents $new_p"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
case $fast_forward in
|
||||||
|
t)
|
||||||
|
echo "Fast forward to $sha1"
|
||||||
|
test $preserve=f && echo $sha1 > "$REWRITTEN"/$sha1
|
||||||
|
;;
|
||||||
|
f)
|
||||||
|
test "a$1" = a-n && die "Refusing to squash a merge: $sha1"
|
||||||
|
|
||||||
|
first_parent=$(expr "$new_parents" : " \([^ ]*\)")
|
||||||
|
# detach HEAD to current parent
|
||||||
|
git checkout $first_parent 2> /dev/null ||
|
||||||
|
die "Cannot move HEAD to $first_parent"
|
||||||
|
|
||||||
|
echo $sha1 > "$DOTEST"/current-commit
|
||||||
|
case "$new_parents" in
|
||||||
|
\ *\ *)
|
||||||
|
# redo merge
|
||||||
|
author_script=$(get_author_ident_from_commit $sha1)
|
||||||
|
eval "$author_script"
|
||||||
|
msg="$(git cat-file commit $sha1 | \
|
||||||
|
sed -e '1,/^$/d' -e "s/[\"\\]/\\\\&/g")"
|
||||||
|
# NEEDSWORK: give rerere a chance
|
||||||
|
if ! git merge $STRATEGY -m "$msg" $new_parents
|
||||||
|
then
|
||||||
|
echo "$msg" > "$GIT_DIR"/MERGE_MSG
|
||||||
|
warn Error redoing merge $sha1
|
||||||
|
warn
|
||||||
|
warn After fixup, please use
|
||||||
|
die "$author_script git commit"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
git cherry-pick $STRATEGY "$@" ||
|
||||||
|
die_with_patch $sha1 "Could not pick $sha1"
|
||||||
|
esac
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
do_next () {
|
||||||
|
read command sha1 rest < "$TODO"
|
||||||
|
case "$command" in
|
||||||
|
\#|'')
|
||||||
|
mark_action_done
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
pick)
|
||||||
|
comment_for_reflog pick
|
||||||
|
|
||||||
|
mark_action_done
|
||||||
|
pick_one $sha1 ||
|
||||||
|
die_with_patch $sha1 "Could not apply $sha1... $rest"
|
||||||
|
;;
|
||||||
|
edit)
|
||||||
|
comment_for_reflog edit
|
||||||
|
|
||||||
|
mark_action_done
|
||||||
|
pick_one $sha1 ||
|
||||||
|
die_with_patch $sha1 "Could not apply $sha1... $rest"
|
||||||
|
make_patch $sha1
|
||||||
|
warn
|
||||||
|
warn "You can amend the commit now, with"
|
||||||
|
warn
|
||||||
|
warn " git commit --amend"
|
||||||
|
warn
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
squash)
|
||||||
|
comment_for_reflog squash
|
||||||
|
|
||||||
|
test -z "$(grep -ve '^$' -e '^#' < $DONE)" &&
|
||||||
|
die "Cannot 'squash' without a previous commit"
|
||||||
|
|
||||||
|
mark_action_done
|
||||||
|
failed=f
|
||||||
|
pick_one -n $sha1 || failed=t
|
||||||
|
MSG="$DOTEST"/message
|
||||||
|
echo "# This is a combination of two commits." > "$MSG"
|
||||||
|
echo "# The first commit's message is:" >> "$MSG"
|
||||||
|
echo >> "$MSG"
|
||||||
|
git cat-file commit HEAD | sed -e '1,/^$/d' >> "$MSG"
|
||||||
|
echo >> "$MSG"
|
||||||
|
echo "# And this is the 2nd commit message:" >> "$MSG"
|
||||||
|
echo >> "$MSG"
|
||||||
|
git cat-file commit $sha1 | sed -e '1,/^$/d' >> "$MSG"
|
||||||
|
git reset --soft HEAD^
|
||||||
|
author_script=$(get_author_ident_from_commit $sha1)
|
||||||
|
case $failed in
|
||||||
|
f)
|
||||||
|
# This is like --amend, but with a different message
|
||||||
|
eval "$author_script"
|
||||||
|
export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
|
||||||
|
git commit -F "$MSG" -e
|
||||||
|
;;
|
||||||
|
t)
|
||||||
|
cp "$MSG" "$GIT_DIR"/MERGE_MSG
|
||||||
|
warn
|
||||||
|
warn "Could not apply $sha1... $rest"
|
||||||
|
warn "After you fixed that, commit the result with"
|
||||||
|
warn
|
||||||
|
warn " $(echo $author_script | tr '\012' ' ') \\"
|
||||||
|
warn " git commit -F \"$GIT_DIR\"/MERGE_MSG -e"
|
||||||
|
die_with_patch $sha1 ""
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
warn "Unknown command: $command $sha1 $rest"
|
||||||
|
die_with_patch $sha1 "Please fix this in the file $TODO."
|
||||||
|
esac
|
||||||
|
test -s "$TODO" && return
|
||||||
|
|
||||||
|
comment_for_reflog finish &&
|
||||||
|
HEADNAME=$(cat "$DOTEST"/head-name) &&
|
||||||
|
OLDHEAD=$(cat "$DOTEST"/head) &&
|
||||||
|
SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) &&
|
||||||
|
if [ -d "$REWRITTEN" ]
|
||||||
|
then
|
||||||
|
test -f "$DOTEST"/current-commit &&
|
||||||
|
current_commit=$(cat "$DOTEST"/current-commit) &&
|
||||||
|
git rev-parse HEAD > "$REWRITTEN"/$current_commit
|
||||||
|
NEWHEAD=$(cat "$REWRITTEN"/$OLDHEAD)
|
||||||
|
else
|
||||||
|
NEWHEAD=$(git rev-parse HEAD)
|
||||||
|
fi &&
|
||||||
|
message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" &&
|
||||||
|
git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD &&
|
||||||
|
git symbolic-ref HEAD $HEADNAME &&
|
||||||
|
rm -rf "$DOTEST" &&
|
||||||
|
warn "Successfully rebased and updated $HEADNAME."
|
||||||
|
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
do_rest () {
|
||||||
|
while :
|
||||||
|
do
|
||||||
|
do_next
|
||||||
|
done
|
||||||
|
test -f "$DOTEST"/verbose &&
|
||||||
|
git diff --stat $(cat "$DOTEST"/head)..HEAD
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
while case $# in 0) break ;; esac
|
||||||
|
do
|
||||||
|
case "$1" in
|
||||||
|
--continue)
|
||||||
|
comment_for_reflog continue
|
||||||
|
|
||||||
|
test -d "$DOTEST" || die "No interactive rebase running"
|
||||||
|
|
||||||
|
require_clean_work_tree
|
||||||
|
do_rest
|
||||||
|
;;
|
||||||
|
--abort)
|
||||||
|
comment_for_reflog abort
|
||||||
|
|
||||||
|
test -d "$DOTEST" || die "No interactive rebase running"
|
||||||
|
|
||||||
|
HEADNAME=$(cat "$DOTEST"/head-name)
|
||||||
|
HEAD=$(cat "$DOTEST"/head)
|
||||||
|
git symbolic-ref HEAD $HEADNAME &&
|
||||||
|
git reset --hard $HEAD &&
|
||||||
|
rm -rf "$DOTEST"
|
||||||
|
exit
|
||||||
|
;;
|
||||||
|
--skip)
|
||||||
|
comment_for_reflog skip
|
||||||
|
|
||||||
|
test -d "$DOTEST" || die "No interactive rebase running"
|
||||||
|
|
||||||
|
git reset --hard && do_rest
|
||||||
|
;;
|
||||||
|
-s|--strategy)
|
||||||
|
shift
|
||||||
|
case "$#,$1" in
|
||||||
|
*,*=*)
|
||||||
|
STRATEGY="-s `expr "z$1" : 'z-[^=]*=\(.*\)'`" ;;
|
||||||
|
1,*)
|
||||||
|
usage ;;
|
||||||
|
*)
|
||||||
|
STRATEGY="-s $2"
|
||||||
|
shift ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
--merge)
|
||||||
|
# we use merge anyway
|
||||||
|
;;
|
||||||
|
-C*)
|
||||||
|
die "Interactive rebase uses merge, so $1 does not make sense"
|
||||||
|
;;
|
||||||
|
-v|--verbose)
|
||||||
|
VERBOSE=t
|
||||||
|
;;
|
||||||
|
-p|--preserve-merges)
|
||||||
|
PRESERVE_MERGES=t
|
||||||
|
;;
|
||||||
|
-i|--interactive)
|
||||||
|
# yeah, we know
|
||||||
|
;;
|
||||||
|
''|-h)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
test -d "$DOTEST" &&
|
||||||
|
die "Interactive rebase already started"
|
||||||
|
|
||||||
|
git var GIT_COMMITTER_IDENT >/dev/null ||
|
||||||
|
die "You need to set your committer info first"
|
||||||
|
|
||||||
|
comment_for_reflog start
|
||||||
|
|
||||||
|
ONTO=
|
||||||
|
case "$1" in
|
||||||
|
--onto)
|
||||||
|
ONTO=$(git rev-parse --verify "$2") ||
|
||||||
|
die "Does not point to a valid commit: $2"
|
||||||
|
shift; shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
require_clean_work_tree
|
||||||
|
|
||||||
|
if [ ! -z "$2"]
|
||||||
|
then
|
||||||
|
git show-ref --verify --quiet "refs/heads/$2" ||
|
||||||
|
die "Invalid branchname: $2"
|
||||||
|
git checkout "$2" ||
|
||||||
|
die "Could not checkout $2"
|
||||||
|
fi
|
||||||
|
|
||||||
|
HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
|
||||||
|
UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
|
||||||
|
|
||||||
|
test -z "$ONTO" && ONTO=$UPSTREAM
|
||||||
|
|
||||||
|
mkdir "$DOTEST" || die "Could not create temporary $DOTEST"
|
||||||
|
: > "$DOTEST"/interactive || die "Could not mark as interactive"
|
||||||
|
git symbolic-ref HEAD > "$DOTEST"/head-name ||
|
||||||
|
die "Could not get HEAD"
|
||||||
|
|
||||||
|
echo $HEAD > "$DOTEST"/head
|
||||||
|
echo $UPSTREAM > "$DOTEST"/upstream
|
||||||
|
echo $ONTO > "$DOTEST"/onto
|
||||||
|
test t = "$VERBOSE" && : > "$DOTEST"/verbose
|
||||||
|
if [ t = "$PRESERVE_MERGES" ]
|
||||||
|
then
|
||||||
|
# $REWRITTEN contains files for each commit that is
|
||||||
|
# reachable by at least one merge base of $HEAD and
|
||||||
|
# $UPSTREAM. They are not necessarily rewritten, but
|
||||||
|
# their children might be.
|
||||||
|
# This ensures that commits on merged, but otherwise
|
||||||
|
# unrelated side branches are left alone. (Think "X"
|
||||||
|
# in the man page's example.)
|
||||||
|
mkdir "$REWRITTEN" &&
|
||||||
|
for c in $(git merge-base --all $HEAD $UPSTREAM)
|
||||||
|
do
|
||||||
|
echo $ONTO > "$REWRITTEN"/$c ||
|
||||||
|
die "Could not init rewritten commits"
|
||||||
|
done
|
||||||
|
MERGES_OPTION=
|
||||||
|
else
|
||||||
|
MERGES_OPTION=--no-merges
|
||||||
|
fi
|
||||||
|
|
||||||
|
SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
|
||||||
|
SHORTHEAD=$(git rev-parse --short $HEAD)
|
||||||
|
SHORTONTO=$(git rev-parse --short $ONTO)
|
||||||
|
cat > "$TODO" << EOF
|
||||||
|
# Rebasing $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO
|
||||||
|
#
|
||||||
|
# Commands:
|
||||||
|
# pick = use commit
|
||||||
|
# edit = use commit, but stop for amending
|
||||||
|
# squash = use commit, but meld into previous commit
|
||||||
|
EOF
|
||||||
|
git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
|
||||||
|
--abbrev=7 --reverse $UPSTREAM..$HEAD | \
|
||||||
|
sed "s/^/pick /" >> "$TODO"
|
||||||
|
|
||||||
|
test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
|
||||||
|
die_abort "Nothing to do"
|
||||||
|
|
||||||
|
cp "$TODO" "$TODO".backup
|
||||||
|
${VISUAL:-${EDITOR:-vi}} "$TODO" ||
|
||||||
|
die "Could not execute editor"
|
||||||
|
|
||||||
|
test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
|
||||||
|
die_abort "Nothing to do"
|
||||||
|
|
||||||
|
git checkout $ONTO && do_rest
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
@ -3,7 +3,7 @@
|
|||||||
# Copyright (c) 2005 Junio C Hamano.
|
# Copyright (c) 2005 Junio C Hamano.
|
||||||
#
|
#
|
||||||
|
|
||||||
USAGE='[-v] [--onto <newbase>] <upstream> [<branch>]'
|
USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<branch>]'
|
||||||
LONG_USAGE='git-rebase replaces <branch> with a new branch of the
|
LONG_USAGE='git-rebase replaces <branch> with a new branch of the
|
||||||
same name. When the --onto option is provided the new branch starts
|
same name. When the --onto option is provided the new branch starts
|
||||||
out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
|
out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
|
||||||
@ -120,6 +120,16 @@ finish_rb_merge () {
|
|||||||
echo "All done."
|
echo "All done."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_interactive () {
|
||||||
|
test -f "$dotest"/interactive ||
|
||||||
|
while case $#,"$1" in 0,|*,-i|*,--interactive) break ;; esac
|
||||||
|
do
|
||||||
|
shift
|
||||||
|
done && test -n "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_interactive "$@" && exec git-rebase--interactive "$@"
|
||||||
|
|
||||||
while case "$#" in 0) break ;; esac
|
while case "$#" in 0) break ;; esac
|
||||||
do
|
do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
@ -49,6 +49,33 @@ require_work_tree () {
|
|||||||
die "fatal: $0 cannot be used without a working tree."
|
die "fatal: $0 cannot be used without a working tree."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_author_ident_from_commit () {
|
||||||
|
pick_author_script='
|
||||||
|
/^author /{
|
||||||
|
s/'\''/'\''\\'\'\''/g
|
||||||
|
h
|
||||||
|
s/^author \([^<]*\) <[^>]*> .*$/\1/
|
||||||
|
s/'\''/'\''\'\'\''/g
|
||||||
|
s/.*/GIT_AUTHOR_NAME='\''&'\''/p
|
||||||
|
|
||||||
|
g
|
||||||
|
s/^author [^<]* <\([^>]*\)> .*$/\1/
|
||||||
|
s/'\''/'\''\'\'\''/g
|
||||||
|
s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
|
||||||
|
|
||||||
|
g
|
||||||
|
s/^author [^<]* <[^>]*> \(.*\)$/\1/
|
||||||
|
s/'\''/'\''\'\'\''/g
|
||||||
|
s/.*/GIT_AUTHOR_DATE='\''&'\''/p
|
||||||
|
|
||||||
|
q
|
||||||
|
}
|
||||||
|
'
|
||||||
|
encoding=$(git config i18n.commitencoding || echo UTF-8)
|
||||||
|
git show -s --pretty=raw --encoding="$encoding" "$1" |
|
||||||
|
LANG=C LC_ALL=C sed -ne "$pick_author_script"
|
||||||
|
}
|
||||||
|
|
||||||
if [ -z "$LONG_USAGE" ]
|
if [ -z "$LONG_USAGE" ]
|
||||||
then
|
then
|
||||||
LONG_USAGE="Usage: $0 $USAGE"
|
LONG_USAGE="Usage: $0 $USAGE"
|
||||||
|
191
t/t3404-rebase-interactive.sh
Executable file
191
t/t3404-rebase-interactive.sh
Executable file
@ -0,0 +1,191 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright (c) 2007 Johannes E. Schindelin
|
||||||
|
#
|
||||||
|
|
||||||
|
test_description='git rebase interactive
|
||||||
|
|
||||||
|
This test runs git rebase "interactively", by faking an edit, and verifies
|
||||||
|
that the result still makes sense.
|
||||||
|
'
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
# set up two branches like this:
|
||||||
|
#
|
||||||
|
# A - B - C - D - E
|
||||||
|
# \
|
||||||
|
# F - G - H
|
||||||
|
# \
|
||||||
|
# I
|
||||||
|
#
|
||||||
|
# where B, D and G touch the same file.
|
||||||
|
|
||||||
|
test_expect_success 'setup' '
|
||||||
|
: > file1 &&
|
||||||
|
git add file1 &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m A &&
|
||||||
|
git tag A &&
|
||||||
|
echo 1 > file1 &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m B file1 &&
|
||||||
|
: > file2 &&
|
||||||
|
git add file2 &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m C &&
|
||||||
|
echo 2 > file1 &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m D file1 &&
|
||||||
|
: > file3 &&
|
||||||
|
git add file3 &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m E &&
|
||||||
|
git checkout -b branch1 A &&
|
||||||
|
: > file4 &&
|
||||||
|
git add file4 &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m F &&
|
||||||
|
git tag F &&
|
||||||
|
echo 3 > file1 &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m G file1 &&
|
||||||
|
: > file5 &&
|
||||||
|
git add file5 &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m H &&
|
||||||
|
git checkout -b branch2 F &&
|
||||||
|
: > file6 &&
|
||||||
|
git add file6 &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m I &&
|
||||||
|
git tag I
|
||||||
|
'
|
||||||
|
|
||||||
|
cat > fake-editor.sh << EOF
|
||||||
|
#!/bin/sh
|
||||||
|
test "\$1" = .git/COMMIT_EDITMSG && exit
|
||||||
|
test -z "\$FAKE_LINES" && exit
|
||||||
|
grep -v "^#" < "\$1" > "\$1".tmp
|
||||||
|
rm "\$1"
|
||||||
|
cat "\$1".tmp
|
||||||
|
action=pick
|
||||||
|
for line in \$FAKE_LINES; do
|
||||||
|
case \$line in
|
||||||
|
squash)
|
||||||
|
action="\$line";;
|
||||||
|
*)
|
||||||
|
echo sed -n "\${line}s/^pick/\$action/p"
|
||||||
|
sed -n "\${line}p" < "\$1".tmp
|
||||||
|
sed -n "\${line}s/^pick/\$action/p" < "\$1".tmp >> "\$1"
|
||||||
|
action=pick;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod a+x fake-editor.sh
|
||||||
|
VISUAL="$(pwd)/fake-editor.sh"
|
||||||
|
export VISUAL
|
||||||
|
|
||||||
|
test_expect_success 'no changes are a nop' '
|
||||||
|
git rebase -i F &&
|
||||||
|
test $(git rev-parse I) = $(git rev-parse HEAD)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'rebase on top of a non-conflicting commit' '
|
||||||
|
git checkout branch1 &&
|
||||||
|
git tag original-branch1 &&
|
||||||
|
git rebase -i branch2 &&
|
||||||
|
test file6 = $(git diff --name-only original-branch1) &&
|
||||||
|
test $(git rev-parse I) = $(git rev-parse HEAD~2)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'reflog for the branch shows state before rebase' '
|
||||||
|
test $(git rev-parse branch1@{1}) = $(git rev-parse original-branch1)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'exchange two commits' '
|
||||||
|
FAKE_LINES="2 1" git rebase -i HEAD~2 &&
|
||||||
|
test H = $(git cat-file commit HEAD^ | tail -n 1) &&
|
||||||
|
test G = $(git cat-file commit HEAD | tail -n 1)
|
||||||
|
'
|
||||||
|
|
||||||
|
cat > expect << EOF
|
||||||
|
diff --git a/file1 b/file1
|
||||||
|
index e69de29..00750ed 100644
|
||||||
|
--- a/file1
|
||||||
|
+++ b/file1
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+3
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > expect2 << EOF
|
||||||
|
<<<<<<< HEAD:file1
|
||||||
|
2
|
||||||
|
=======
|
||||||
|
3
|
||||||
|
>>>>>>> b7ca976... G:file1
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'stop on conflicting pick' '
|
||||||
|
git tag new-branch1 &&
|
||||||
|
! git rebase -i master &&
|
||||||
|
diff -u expect .git/.dotest-merge/patch &&
|
||||||
|
diff -u expect2 file1 &&
|
||||||
|
test 4 = $(grep -v "^#" < .git/.dotest-merge/done | wc -l) &&
|
||||||
|
test 0 = $(grep -v "^#" < .git/.dotest-merge/todo | wc -l)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'abort' '
|
||||||
|
git rebase --abort &&
|
||||||
|
test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
|
||||||
|
! test -d .git/.dotest-merge
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'retain authorship' '
|
||||||
|
echo A > file7 &&
|
||||||
|
git add file7 &&
|
||||||
|
test_tick &&
|
||||||
|
GIT_AUTHOR_NAME="Twerp Snog" git commit -m "different author" &&
|
||||||
|
git tag twerp &&
|
||||||
|
git rebase -i --onto master HEAD^ &&
|
||||||
|
git show HEAD | grep "^Author: Twerp Snog"
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'squash' '
|
||||||
|
git reset --hard twerp &&
|
||||||
|
echo B > file7 &&
|
||||||
|
test_tick &&
|
||||||
|
GIT_AUTHOR_NAME="Nitfol" git commit -m "nitfol" file7 &&
|
||||||
|
echo "******************************" &&
|
||||||
|
FAKE_LINES="1 squash 2" git rebase -i --onto master HEAD~2 &&
|
||||||
|
test B = $(cat file7) &&
|
||||||
|
test $(git rev-parse HEAD^) = $(git rev-parse master)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'retain authorship when squashing' '
|
||||||
|
git show HEAD | grep "^Author: Nitfol"
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'preserve merges with -p' '
|
||||||
|
git checkout -b to-be-preserved master^ &&
|
||||||
|
: > unrelated-file &&
|
||||||
|
git add unrelated-file &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m "unrelated" &&
|
||||||
|
git checkout -b to-be-rebased master &&
|
||||||
|
echo B > file1 &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m J file1 &&
|
||||||
|
test_tick &&
|
||||||
|
git merge to-be-preserved &&
|
||||||
|
echo C > file1 &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m K file1 &&
|
||||||
|
git rebase -i -p --onto branch1 master &&
|
||||||
|
test $(git rev-parse HEAD^^2) = $(git rev-parse to-be-preserved) &&
|
||||||
|
test $(git rev-parse HEAD~3) = $(git rev-parse branch1) &&
|
||||||
|
test $(git show HEAD:file1) = C &&
|
||||||
|
test $(git show HEAD~2:file1) = B
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
Reference in New Issue
Block a user