From: Clemens Buchacher <hidden> Date: 2016-06-15 22:52:44
Interactive rebase is frequently used not to rebase history, but to
manipulate recent commits. This is typically done using the following
command:
git rebase -i HEAD~N
Where N has to be large enough such that the the range HEAD~N..HEAD
contains the desired commits. At the same time, it should be small
enough such that the range HEAD~N..HEAD does not include published
commits or a merge commit. Otherwise, the user may accidentally change
published history. Rebasing a merge commit can also have the generally
undesirable effect of linearizing the merge history.
In order to determine a suitable range automatically, it is a reasonable
heuristic to rebase onto the most recent merge commit. It does not
guarantee that published commits are not included -- indeed there is no
way to do that. But, the range is usually large enough to contain the
desired commits. Also, this mechanism works regardless of whether or not
branch tracking has been configured.
So instead of the above command, one can instead use the following:
git rebase --fix
By default, the range is limited to a maximum of 20 commits. This can be
changed by passing a different number to --fix, e.g.:
git rebase --fix=50
Signed-off-by: Clemens Buchacher <redacted>
---
Also on branch cb/rebase-fix at https://github.com/drizzd/git .
Documentation/git-rebase.txt | 9 +++++++++
git-rebase.sh | 37 ++++++++++++++++++++++++++++++++++++-
2 files changed, 45 insertions(+), 1 deletions(-)
@@ -332,6 +332,15 @@ link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for details). user edit that list before rebasing. This mode can also be used to split commits (see SPLITTING COMMITS below).+--fix=<n>::+ Searches commit history backwards from the current commit until the+ most recent merge commit, or until a maximum of <n> preceding commits+ (default: 20), and runs rebase -i <commit>^. The resulting range is+ typically large enough to contain recent commits which the user might+ want to edit, while avoiding the usually undesirable effects of+ rebasing a merge commit, which obviates the need to find a suitable+ base commit manually.+ -p:: --preserve-merges:: Instead of ignoring merges, try to recreate them.
@@ -95,6 +96,7 @@ type=state_dir=# One of {'', continue, skip, abort}, as parsed from command lineaction=+rebase_fix=preserve_merges=autosquash=test"$(gitconfig--boolrebase.autosquash)"="true"&&autosquash=t
@@ -178,6 +180,22 @@ run_pre_rebase_hook () {fi}+latest_merge_commit()+{+max_nr_commits=$1++latest_merge=$(gitrev-list-1--mergesHEAD)+iftest-z"$latest_merge"+then+range=HEAD+else+range=$latest_merge..HEAD+fi++range_start=$(gitrev-list-"$max_nr_commits""$range"|tail-1)+echo$(gitrev-parse$range_start^)+}+test-f"$apply_dir"/applying&&die'It looks like git-am is in progress. Cannot rebase.'
@@ -220,6 +238,20 @@ do-i)interactive_rebase=explicit;;+--fix)+interactive_rebase=explicit+rebase_fix=20+# Parse optional argument.+iftest"${2#-}"="$2"+then+if!expr"$2":"^[0-9]\+$">/dev/null+then+die"Invalid argument to rebase --fix: $2"+fi+rebase_fix=$2+shift+fi+;;-p)preserve_merges=ttest-z"$interactive_rebase"&&interactive_rebase=implied
@@ -375,7 +407,10 @@ if test -z "$rebase_root"thencase"$#"in0)-if!upstream_name=$(gitrev-parse--symbolic-full-name\+iftest-n"$rebase_fix"+then+upstream_name=$(latest_merge_commit$rebase_fix)+elif!upstream_name=$(gitrev-parse--symbolic-full-name\--verify-q@{upstream}2>/dev/null)then.git-parse-remote
@@ -332,6 +332,15 @@ link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for details). user edit that list before rebasing. This mode can also be used to split commits (see SPLITTING COMMITS below).+--fix=<n>::+ Searches commit history backwards from the current commit until the+ most recent merge commit, or until a maximum of <n> preceding commits+ (default: 20), and runs rebase -i <commit>^. The resulting range is+ typically large enough to contain recent commits which the user might+ want to edit, while avoiding the usually undesirable effects of+ rebasing a merge commit, which obviates the need to find a suitable+ base commit manually.
Funny. :) I wonder if this is possible to generalize, to something like
git rebase -i foo^{last-merge}
or even something like
git rebase -i foo^{first:--merges}
(where "<commit>^{first:<rev-list args>}" would mean something like
"the first commit listed by "git rev-list <rev-list args> <commit>").
What do you think?
From: Jakub Narebski <hidden> Date: 2016-06-15 22:52:44
Clemens Buchacher [off-list ref] writes:
[...]
In order to determine a suitable range automatically, it is a reasonable
heuristic to rebase onto the most recent merge commit.
Why not additionally / instead take into account remote-tracking
branches for "push" remotes?
It does not
guarantee that published commits are not included -- indeed there is no
way to do that. But, the range is usually large enough to contain the
desired commits. Also, this mechanism works regardless of whether or not
branch tracking has been configured.
So instead of the above command, one can instead use the following:
git rebase --fix
By default, the range is limited to a maximum of 20 commits. This can be
changed by passing a different number to --fix, e.g.:
git rebase --fix=50
Signed-off-by: Clemens Buchacher <redacted>
From: Clemens Buchacher <hidden> Date: 2016-06-15 22:52:44
On Sun, Jan 08, 2012 at 01:57:03PM -0800, Jakub Narebski wrote:
quoted
In order to determine a suitable range automatically, it is a reasonable
heuristic to rebase onto the most recent merge commit.
Why not additionally / instead take into account remote-tracking
branches for "push" remotes?
For me personally, remote-tracking does not work. I frequently branch
locally, and even if I do branch from a remote branch, it's often not
from a public branch, but rather my own private branch that I
synchronize between repos and machines. So my remote-tracking
configuration is usually an awful mess, and it does not feel like fixing
it up manually would be worth the trouble.
As a result, I don't trust remote-tracking and I do not use any of the
features associated with it. For my uses of rebase --fix it would
therefore be counter-productive to consider remote-tracking information.
What I did consider was adding a comment to the list of "pick <commit>"
that interactive rebase offers saying:
# older commits are already contained in the current upstream branch
Also, I often rewrite commits that are also contained in other branches.
That typically happens when I am reworking a topic that has already been
tested extensively. In that case I like to keep the original branch
around for reference, even if I end up not using it eventually.
From: Clemens Buchacher <hidden> Date: 2016-06-15 22:52:45
On Sun, Jan 08, 2012 at 04:01:27PM -0600, Jonathan Nieder wrote:
Funny. :) I wonder if this is possible to generalize, to something like
git rebase -i foo^{last-merge}
What do you think?
I suppose if the history has no merges, I would return the root commit?
Uh, and now I realize I have a bug. In a repo with only linear history:
$ git rebase --fix
fatal: ambiguous argument '4ccf67b9fa2ae247e55b86648d650cb368f286c2^': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions
fatal: Needed a single revision
invalid upstream 4ccf67b9fa2ae247e55b86648d650cb368f286c2^
Funny. :) I wonder if this is possible to generalize, to something like
git rebase -i foo^{last-merge}
or even something like
git rebase -i foo^{first:--merges}
(where "<commit>^{first:<rev-list args>}" would mean something like
"the first commit listed by "git rev-list <rev-list args> <commit>").
What do you think?
Is something like this over-generalized?
http://kerneltrap.org/mailarchive/git/2010/12/24/47502
A good thing I see from having a specific option for "-i HEAD~n" is
that it's potentially shorter to type. For someone who does rebase a
lot and has CapsLock turned to Ctrl, it helps. Maybe "rebase -I" ==
"rebase -i HEAD^{last-merge}" (or "rebase -i
<the-revision-used-last-time>") and "rebase -I <n>" == "rebase -i
HEAD~<n>"?
--
Duy
Yes, I suspect that at the moment (i.e., in the absence of a large
collection of examples to show their utility), both your ^{~custom}
and my ^{first:rev-list args} are overengineered, and that they do
something that is more clearly expressed using the shell's command
substitution feature:
git rebase -i $(git rev-list --merges HEAD | head -1)
So why did I suggest it?
I guess I was reacting to the implementation of the
rebase-recent-commits command. I understand that it was a sketch, but
it felt a little ad hoc. If it could be expressed as a clean
two-liner, I would be more comfortable since the burden of maintaining
it would be less.
Thanks for clarifying.
Sincerely,
Jonathan
From: Michael Haggerty <hidden> Date: 2016-06-15 22:52:45
On 01/08/2012 10:31 PM, Clemens Buchacher wrote:
Interactive rebase is frequently used not to rebase history, but to
manipulate recent commits. This is typically done using the following
command:
git rebase -i HEAD~N
Where N has to be large enough such that the the range HEAD~N..HEAD
contains the desired commits. At the same time, it should be small
enough such that the range HEAD~N..HEAD does not include published
commits or a merge commit. Otherwise, the user may accidentally change
published history. Rebasing a merge commit can also have the generally
undesirable effect of linearizing the merge history.
In order to determine a suitable range automatically, it is a reasonable
heuristic to rebase onto the most recent merge commit. It does not
guarantee that published commits are not included -- indeed there is no
way to do that. But, the range is usually large enough to contain the
desired commits. Also, this mechanism works regardless of whether or not
branch tracking has been configured.
So instead of the above command, one can instead use the following:
git rebase --fix
Two comments:
* The name "--fix" might be confusing because of its similarity to the
"fixup" command that can be specified in the interactive instructions file.
* I agree with you that "interactive rebase is frequently used not to
rebase history, but to manipulate recent commits". In fact, I use
interactive rebase *only* for manipulating recent commits and
non-interactive rebase *only* for changing commits' ancestry. I think
it is a good idea to make these two uses more distinct. For example, it
makes me nervous that I might mis-type the <upstream> parameter when I
am trying to touch up commits and end up inadvertently rebasing the
commits onto a new parent.
Michael
--
Michael Haggerty
mhagger@alum.mit.edu
http://softwareswirl.blogspot.com/
Two comments:
* The name "--fix" might be confusing because of its similarity to the
"fixup" command that can be specified in the interactive instructions file.
* I agree with you that "interactive rebase is frequently used not to
rebase history, but to manipulate recent commits". In fact, I use
interactive rebase *only* for manipulating recent commits and
non-interactive rebase *only* for changing commits' ancestry. I think
it is a good idea to make these two uses more distinct. For example, it
makes me nervous that I might mis-type the<upstream> parameter when I
am trying to touch up commits and end up inadvertently rebasing the
commits onto a new parent.
He could all it --touchup like you did above.
v/r,
neal