rebase -i: fix ORIG_HEAD handling
cover rebase -i: fix ORIG_HEAD handling
The buffer containing the oid for ORIG_HEAD is overwritten before ORIG_HEAD is created. This series fixes that bug and then converts the code to use struct object_id rather than passing around strings.
Thanks to Caspar for reporting the bug and providing a reproducible example
Phillip Wood (4):
rebase -i: stop overwriting ORIG_HEAD buffer
rebase -i: use struct object_id rather than looking up commit rebase -i: use struct object_id when writing state rebase -i: simplify get_revision_ranges()
builtin/rebase.c | 20 ++++++++++---------- sequencer.c | 15 ++++++--------- sequencer.h | 7 ++++--- t/t3404-rebase-interactive.sh | 11 +++++++++++ 4 files changed, 31 insertions(+), 22 deletions(-)
base-commit: 2e673356aefa8ed19be3c878f966ad6189ecb510
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-773%2Fphillipwood%2Fwip%2Frebase-fix-orig_head-handling-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-773/phillipwood/wip/rebase-fix-orig_head-handling-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/773
Signature · 1 line
gitgitgadget
PATCH 1/4 rebase -i: stop overwriting ORIG_HEAD buffer
From: Phillip Wood <[email protected]>
After rebasing ORIG_HEAD is supposed to point to the old HEAD of the rebased branch. Unfortunately the buffer storing the oid was overwritten with a new oid before ORIG_HEAD was created. The buffer is also used when writing .git/rebase-merge/orig-head which is used by rebase --abort to restore the previous head. Luckily that file is written before the buffer is overwritten. As we want the full oid find_unique_abbrev() is replaced with oid_to_hex_r() rather than find_unique_abbrev_r().
I think that all of the users of head_hash should actually be using opts->orig_head instead as passing a string rather than a struct object_id around is a hang over from the scripted implementation. This patch just fixes the immediate bug and adds a regression test based on Caspar's reproduction example. The users will be converted to use struct object_id and head_hash removed in the next few commits.
--- builtin/rebase.c | 10 +++++----- t/t3404-rebase-interactive.sh | 11 +++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-)
builtin/rebase.c +5 -5
@@ -270,15 +270,15 @@ static int edit_todo_file(unsigned flags) }static int get_revision_ranges (struct commit *upstream,struct commit *onto, -struct object_id *orig_head,const char **head_hash, +struct object_id *orig_head,char *head_hash,char **revisions,char **shortrevisions) {struct commit *base_rev = upstream ? upstream : onto;const char *shorthead; - *head_hash =find_unique_abbrev (orig_head,GIT_MAX_HEXSZ ); +oid_to_hex_r (head_hash, orig_head); *revisions =xstrfmt ("%s...%s" ,oid_to_hex (&base_rev->object.oid), - *head_hash); + head_hash); shorthead =find_unique_abbrev (orig_head,DEFAULT_ABBREV );
@@ -327,7 +327,7 @@ static void split_exec_commands(const char *cmd, struct string_list *commands)static int do_interactive_rebase (struct rebase_options *opts,unsigned flags) {int ret; -const char *head_hash =NULL ; +char head_hash[GIT_MAX_HEXSZ ];char *revisions =NULL , *shortrevisions =NULL ;struct strvec make_script_args =STRVEC_INIT ;struct todo_list todo_list =TODO_LIST_INIT ;
@@ -335,7 +335,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)struct string_list commands =STRING_LIST_INIT_DUP ;if (get_revision_ranges (opts->upstream, opts->onto, &opts->orig_head, - &head_hash, &revisions, &shortrevisions)) + head_hash, &revisions, &shortrevisions))return -1 ; if (init_basic_state(&replay,
t/t3404-rebase-interactive.sh +11 -0
@@ -1797,6 +1797,17 @@ test_expect_success 'todo has correct onto hash' ' test_i18ngrep "^# Rebase ..* onto $onto" actual ' +test_expect_success 'ORIG_HEAD is updated correctly' ' + test_when_finished "git checkout master && git branch -D test-orig-head" && + git checkout -b test-orig-head A && + git commit --allow-empty -m A1 && + git commit --allow-empty -m A2 && + git commit --allow-empty -m A3 && + git commit --allow-empty -m A4 && + git rebase master && + test_cmp_rev ORIG_HEAD test-orig-head@{1} +' + # This must be the last test in this file test_expect_success '$EDITOR and friends are unchanged' ' test_editor_unchanged
Signature · 1 line
gitgitgadget
Re: [PATCH 1/4] rebase -i: stop overwriting ORIG_HEAD buffer
Quoted 1 line · Phillip Wood via GitGitGadget
From: Phillip Wood <[email protected]>
My initial impression after seeing the recent report about ORIG_HEAD was "hmph, these days, rebasing is done on detached HEAD and the final step updates the target branch only once, so @{1} is much easier to use---perhaps it is time to deprecate use of ORIG_HEAD?". After all, ORIG_HEAD was invented way before we had reflog, and given that one of the goal of reflog was to give more general recovery mechanism than going back one-step like ORIG_HEAD allowed us to, and "rebase" were taught to work on detached HEAD to make @{1} more useful, it would not be too bad to eventually retire ORIG_HEAD in a distant future, I thought.
But it is a good initiative anyway to make ORIG_HEAD again work as documented. Thanks for working on it.
Quoted 1 line
After rebasing
ORIG_HEADis supposed to point to the old HEAD of the
A comma after "rebasing".
Quoted 7 lines
rebased branch. Unfortunately the buffer storing the oid was overwritten with a new oid before
ORIG_HEADwas created. The buffer is also used when writing .git/rebase-merge/orig-head which is used byrebase --abortto restore the previous head. Luckily that file is written before the buffer is overwritten. As we want the full oidfind_unique_abbrev()is replaced withoid_to_hex_r()rather thanfind_unique_abbrev_r().
The above is hard to read and understand. It is unclear where the observation of the current behaviour (which is often the explanation of the cause of the bug) ends and the description of new behaviour begins.
... old HEAD of the rebased branch. The code used find_unique_abbrev() to obtain the object name of the old HEAD and wrote to both .git/rebase-merge/orig-head (used by rebase --abort to go back to the previous state) and to ORIG_HEAD. The buffer find_unique_abbrev() gives back is volatile, unfortunately, and was overwritten after the former file is written but before ORIG_FILE is written, leaving an incorrect object name in it.
Up to that point is the observation of the current code, which explains where the bug comes from. Please have a paragraph break after that, before explaining the solution, e.g.
Avoid relying on the volatile buffer of find_unique_abbrev(), and instead supply our own buffer to keep the object name. Because we want to use the full object name, use oid_to_hex_r() instead of find_unique_abbrev_r() to do so.
Quoted 6 lines
I think that all of the users of
head_hashshould actually be usingopts->orig_headinstead as passing a string rather than a structobject_idaround is a hang over from the scripted implementation. This patch just fixes the immediate bug and adds a regression test based on Caspar's reproduction example. The users will be converted to use structobject_idandhead_hashremoved in the next few commits.
Makes sense.
Quoted 4 lines
Reported-by Caspar Duregger <[email protected]>Signed-off-by Phillip Wood <[email protected]>---
If you cite "Caspar's repro example" like that in the log message, it is incomplete to leave out a URL to the mail archive.
Quoted 67 lines
builtin/rebase.c | 10 +++++----- t/t3404-rebase-interactive.sh | 11 +++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-)builtin/rebase.c +5
-5@@ -270,15 +270,15 @@ static int edit_todo_file(unsigned flags) }static int get_revision_ranges (struct commit *upstream,struct commit *onto, -struct object_id *orig_head,const char **head_hash, +struct object_id *orig_head,char *head_hash,char **revisions,char **shortrevisions) {struct commit *base_rev = upstream ? upstream : onto;const char *shorthead; - *head_hash =find_unique_abbrev (orig_head,GIT_MAX_HEXSZ ); +oid_to_hex_r (head_hash, orig_head); *revisions =xstrfmt ("%s...%s" ,oid_to_hex (&base_rev->object.oid), - *head_hash); + head_hash); shorthead =find_unique_abbrev (orig_head,DEFAULT_ABBREV );@@ -327,7 +327,7 @@ static void split_exec_commands(const char *cmd, struct string_list *commands)static int do_interactive_rebase (struct rebase_options *opts,unsigned flags) {int ret; -const char *head_hash =NULL ; +char head_hash[GIT_MAX_HEXSZ ];char *revisions =NULL , *shortrevisions =NULL ;struct strvec make_script_args =STRVEC_INIT ;struct todo_list todo_list =TODO_LIST_INIT ;@@ -335,7 +335,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)struct string_list commands =STRING_LIST_INIT_DUP ;if (get_revision_ranges (opts->upstream, opts->onto, &opts->orig_head, - &head_hash, &revisions, &shortrevisions)) + head_hash, &revisions, &shortrevisions))return -1 ; if (init_basic_state(&replay,t/t3404-rebase-interactive.sh +11
-0@@ -1797,6 +1797,17 @@ test_expect_success 'todo has correct onto hash' ' test_i18ngrep "^# Rebase ..* onto $onto" actual ' +test_expect_success 'ORIG_HEAD is updated correctly' ' + test_when_finished "git checkout master && git branch -D test-orig-head" && + git checkout -b test-orig-head A && + git commit --allow-empty -m A1 && + git commit --allow-empty -m A2 && + git commit --allow-empty -m A3 && + git commit --allow-empty -m A4 && + git rebase master && + test_cmp_rev ORIG_HEAD test-orig-head@{1} +' + # This must be the last test in this file test_expect_success '$EDITOR and friends are unchanged' ' test_editor_unchanged
Re: [PATCH 1/4] rebase -i: stop overwriting ORIG_HEAD buffer
Quoted 4 lines · Junio C Hamano
Quoted 1 line · Phillip Wood via GitGitGadget
From: Phillip Wood <[email protected]>
Quoted 10 lines
My initial impression after seeing the recent report about
ORIG_HEADwas "hmph, these days, rebasing is done on detached HEAD and the final step updates the target branch only once, so @{1} is much easier to use---perhaps it is time to deprecate use ofORIG_HEAD?". After all,ORIG_HEADwas invented way before we had reflog, and given that one of the goal of reflog was to give more general recovery mechanism than going back one-step likeORIG_HEADallowed us to, and "rebase" were taught to work on detached HEAD to make @{1} more useful, it would not be too bad to eventually retireORIG_HEADin a distant future, I thought.
Thanks for filling in the history. As reset sets ORIG_HEAD as well as rebase it can be confusing so retiring it in the future maybe a good idea.
Quoted 6 lines
But it is a good initiative anyway to make
ORIG_HEADagain work as documented. Thanks for working on it.Quoted 1 line
After rebasing
ORIG_HEADis supposed to point to the old HEAD of theA comma after "rebasing".
Sure
Quoted 22 lines
Quoted 7 lines
rebased branch. Unfortunately the buffer storing the oid was overwritten with a new oid before
ORIG_HEADwas created. The buffer is also used when writing .git/rebase-merge/orig-head which is used byrebase --abortto restore the previous head. Luckily that file is written before the buffer is overwritten. As we want the full oidfind_unique_abbrev()is replaced withoid_to_hex_r()rather thanfind_unique_abbrev_r().The above is hard to read and understand. It is unclear where the observation of the current behaviour (which is often the explanation of the cause of the bug) ends and the description of new behaviour begins.
... old HEAD of the rebased branch. The code used
find_unique_abbrev()to obtain the object name of the old HEAD and wrote to both .git/rebase-merge/orig-head (used byrebase --abortto go back to the previous state) and toORIG_HEAD. The bufferfind_unique_abbrev()gives back is volatile, unfortunately, and was overwritten after the former file is written but beforeORIG_FILEis written, leaving an incorrect object name in it.
Quoted 8 lines
Up to that point is the observation of the current code, which explains where the bug comes from. Please have a paragraph break after that, before explaining the solution, e.g.
Avoid relying on the volatile buffer of
find_unique_abbrev(), and instead supply our own buffer to keep the object name. Because we want to use the full object name, useoid_to_hex_r()instead offind_unique_abbrev_r()to do so.
That's much clearer thanks. I wonder if swapping to oid_to_hex_r() rather than find_unique_abbev_r() is complicating this commit unnecessarily as the code that is changed here is deleted in patch 4, maybe we should do the switch there.
Quoted 10 lines
Quoted 6 lines
I think that all of the users of
head_hashshould actually be usingopts->orig_headinstead as passing a string rather than a structobject_idaround is a hang over from the scripted implementation. This patch just fixes the immediate bug and adds a regression test based on Caspar's reproduction example. The users will be converted to use structobject_idandhead_hashremoved in the next few commits.Makes sense.
Quoted 1 line
Quoted 6 lines
Quoted 3 lines
Reported-by Caspar Duregger <[email protected]>Signed-off-by Phillip Wood <[email protected]>---
If you cite "Caspar's repro example" like that in the log message, it is incomplete to leave out a URL to the mail archive.
Good point I'll add a url
Thanks for your comments
Phillip
Quoted 67 lines
Quoted 67 lines
builtin/rebase.c | 10 +++++----- t/t3404-rebase-interactive.sh | 11 +++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-)builtin/rebase.c +5
-5@@ -270,15 +270,15 @@ static int edit_todo_file(unsigned flags) }static int get_revision_ranges (struct commit *upstream,struct commit *onto, -struct object_id *orig_head,const char **head_hash, +struct object_id *orig_head,char *head_hash,char **revisions,char **shortrevisions) {struct commit *base_rev = upstream ? upstream : onto;const char *shorthead; - *head_hash =find_unique_abbrev (orig_head,GIT_MAX_HEXSZ ); +oid_to_hex_r (head_hash, orig_head); *revisions =xstrfmt ("%s...%s" ,oid_to_hex (&base_rev->object.oid), - *head_hash); + head_hash); shorthead =find_unique_abbrev (orig_head,DEFAULT_ABBREV );@@ -327,7 +327,7 @@ static void split_exec_commands(const char *cmd, struct string_list *commands)static int do_interactive_rebase (struct rebase_options *opts,unsigned flags) {int ret; -const char *head_hash =NULL ; +char head_hash[GIT_MAX_HEXSZ ];char *revisions =NULL , *shortrevisions =NULL ;struct strvec make_script_args =STRVEC_INIT ;struct todo_list todo_list =TODO_LIST_INIT ;@@ -335,7 +335,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)struct string_list commands =STRING_LIST_INIT_DUP ;if (get_revision_ranges (opts->upstream, opts->onto, &opts->orig_head, - &head_hash, &revisions, &shortrevisions)) + head_hash, &revisions, &shortrevisions))return -1 ; if (init_basic_state(&replay,t/t3404-rebase-interactive.sh +11
-0@@ -1797,6 +1797,17 @@ test_expect_success 'todo has correct onto hash' ' test_i18ngrep "^# Rebase ..* onto $onto" actual ' +test_expect_success 'ORIG_HEAD is updated correctly' ' + test_when_finished "git checkout master && git branch -D test-orig-head" && + git checkout -b test-orig-head A && + git commit --allow-empty -m A1 && + git commit --allow-empty -m A2 && + git commit --allow-empty -m A3 && + git commit --allow-empty -m A4 && + git rebase master && + test_cmp_rev ORIG_HEAD test-orig-head@{1} +' + # This must be the last test in this file test_expect_success '$EDITOR and friends are unchanged' ' test_editor_unchanged
Re: [PATCH 1/4] rebase -i: stop overwriting ORIG_HEAD buffer
In reply to Junio C HamanoAm Di., 27. Okt. 2020 um 22:10 Uhr schrieb Junio C Hamano <[email protected]>:
Quoted 11 lines
My initial impression after seeing the recent report about
ORIG_HEADwas "hmph, these days, rebasing is done on detached HEAD and the final step updates the target branch only once, so @{1} is much easier to use---perhaps it is time to deprecate use ofORIG_HEAD?". After all,ORIG_HEADwas invented way before we had reflog, and given that one of the goal of reflog was to give more general recovery mechanism than going back one-step likeORIG_HEADallowed us to, and "rebase" were taught to work on detached HEAD to make @{1} more useful, it would not be too bad to eventually retireORIG_HEADin a distant future, I thought.
Phillip pointed out that ORIG_HEAD is actually not save *if* there is a reset or rebase --skip during the rebase. Otherwise, by design, ORIG_HEAD would be easier to use, as in the form <branch_name>@{<n>} two things have to be decided and can go wrong.
Wouldn't it be better for the mid-term run to set ORIG_HEAD at the end of the rebase operation, basically during cleanup, and make the commitment that it always points to the right thing for undoing the recent rebase or to compare it (git diff ORIG_HEAD) to see possible unwanted merge resolutions.
I say mid-term because a git rebase --undo with additional safety checks and a --autostash for example is IMO the better long-run UX, in a very distant future.
Quoted 4 lines
But it is a good initiative anyway to make
ORIG_HEADagain work as documented. Thanks for working on it.
Regards
Caspar Duregger
Re: [PATCH 1/4] rebase -i: stop overwriting ORIG_HEAD buffer
Quoted 4 lines · herr.kaste
Phillip pointed out that
ORIG_HEADis actually not save *if* there is aresetorrebase --skipduring the rebase. Otherwise, by design,ORIG_HEADwould be easier to use, as in the form<branch_name>@{<n>}two things have to be decided and can go wrong.
What "two"? You should be able to just say @{1} regardless---that was the whole point of performing all the intermediate steps while on the detached HEAD so that you can rely on <n> being 1, and @{<num or time>} is a short-hand of <branch>@{<num or time>} for the current branch, and not a short-hand for HEAD@{...}, to help such a use case.
Or am I missing something?
Re: [PATCH 1/4] rebase -i: stop overwriting ORIG_HEAD buffer
Am Di., 3. Nov. 2020 um 01:21 Uhr schrieb Junio C Hamano <[email protected]>:
Quoted 8 lines
Quoted 4 lines · herr.kaste
Phillip pointed out that
ORIG_HEADis actually not save *if* there is aresetorrebase --skipduring the rebase. Otherwise, by design,ORIG_HEADwould be easier to use, as in the form<branch_name>@{<n>}two things have to be decided and can go wrong.
Quoted 8 lines
What "two"? You should be able to just say @{1} regardless---that was the whole point of performing all the intermediate steps while on the detached HEAD so that you can rely on <n> being 1, and @{<num or time>} is a short-hand of <branch>@{<num or time>} for the current branch, and not a short-hand for HEAD@{...}, to help such a use case.
Or am I missing something?
Well, "@{1}" basically means: from the stream of things that happened take the first. It is very natural to refer to the most recent thing differently. In practice, until now, I used the {...} form only to refer to older things. To put it differently, using {...} I'm researching history.
From the docs:
ORIG_HEAD is created by commands that move your HEAD in a drastic way, to record the position of the HEAD before their operation, so that you can easily change the tip of the branch back to the state before you ran them.
That's just humane. You do something, and then you revert. I don't need a concept of a written history here, just of recency.
PATCH 2/4 rebase -i: use struct object_id rather than looking up commit
In reply to Phillip Wood via GitGitGadgetFrom: Phillip Wood <[email protected]>
We already have a struct object_id containing the oid that we want to set ORIG_HEAD to so use that rather than converting it to a string and then calling get_oid() on that string.
--- builtin/rebase.c | 5 +++-- sequencer.c | 10 +++------- sequencer.h | 5 +++-- 3 files changed, 9 insertions(+), 11 deletions(-)
builtin/rebase.c +3 -2
@@ -370,8 +370,9 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)split_exec_commands (opts->cmd, &commands); ret =complete_action (the_repository, &replay, flags, - shortrevisions, opts->onto_name, opts->onto, head_hash, - &commands, opts->autosquash, &todo_list); + shortrevisions, opts->onto_name, opts->onto, + &opts->orig_head, &commands, opts->autosquash, + &todo_list); }string_list_clear (&commands,0 );
sequencer.c +3 -7
@@ -3965,21 +3965,17 @@ static int run_git_checkout(struct repository *r, struct replay_opts *opts,static int checkout_onto (struct repository *r,struct replay_opts *opts,const char *onto_name,const struct object_id *onto, -const char *orig_head) +const struct object_id *orig_head) { -struct object_id oid;const char *action =reflog_message (opts,"start" ,"checkout %s" , onto_name); -if (get_oid (orig_head, &oid)) -return error (_ ("%s: not a valid OID" ), orig_head); -if (run_git_checkout (r, opts,oid_to_hex (onto), action)) {apply_autostash (rebase_path_autostash ());sequencer_remove_state (opts);return error (_ ("could not detach HEAD" )); } -return update_ref (NULL ,"ORIG_HEAD" , &oid,NULL ,0 ,UPDATE_REFS_MSG_ON_ERR ); +return update_ref (NULL ,"ORIG_HEAD" , orig_head,NULL ,0 ,UPDATE_REFS_MSG_ON_ERR ); }static int stopped_at_head (struct repository *r)
@@ -5314,7 +5310,7 @@ static int skip_unnecessary_picks(struct repository *r,int complete_action (struct repository *r,struct replay_opts *opts,unsigned flags,const char *shortrevisions,const char *onto_name, -struct commit *onto,const char *orig_head, +struct commit *onto,const struct object_id *orig_head,struct string_list *commands,unsigned autosquash,struct todo_list *todo_list) {
sequencer.h +3 -2
@@ -163,8 +163,9 @@ void todo_list_add_exec_commands(struct todo_list *todo_list, struct string_list *commands);int complete_action (struct repository *r,struct replay_opts *opts,unsigned flags,const char *shortrevisions,const char *onto_name, -struct commit *onto,const char *orig_head,struct string_list *commands, -unsigned autosquash,struct todo_list *todo_list); +struct commit *onto,const struct object_id *orig_head, +struct string_list *commands,unsigned autosquash, +struct todo_list *todo_list);int todo_list_rearrange_squash (struct todo_list *todo_list);/*
Signature · 1 line
gitgitgadget
PATCH 3/4 rebase -i: use struct object_id when writing state
In reply to Phillip Wood via GitGitGadgetFrom: Phillip Wood <[email protected]>
Rather than passing a string around pass the struct object_id that the string was created from call oid_hex() when we write the file.
--- builtin/rebase.c | 3 ++- sequencer.c | 5 +++-- sequencer.h | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-)
builtin/rebase.c +2 -1
@@ -296,7 +296,8 @@ static int get_revision_ranges(struct commit *upstream, struct commit *onto, }static int init_basic_state (struct replay_opts *opts,const char *head_name, -struct commit *onto,const char *orig_head) +struct commit *onto, +const struct object_id *orig_head) {FILE *interactive;
sequencer.c +3 -2
@@ -2692,7 +2692,7 @@ static void write_strategy_opts(struct replay_opts *opts) }int write_basic_state (struct replay_opts *opts,const char *head_name, -struct commit *onto,const char *orig_head) +struct commit *onto,const struct object_id *orig_head) {if (head_name)write_file (rebase_path_head_name (),"%s\n" , head_name);
@@ -2700,7 +2700,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,write_file (rebase_path_onto (),"%s\n" ,oid_to_hex (&onto->object.oid));if (orig_head) -write_file (rebase_path_orig_head (),"%s\n" , orig_head); +write_file (rebase_path_orig_head (),"%s\n" , +oid_to_hex (orig_head));if (opts->quiet)write_file (rebase_path_quiet (),"%s" ,"" );
sequencer.h +1 -1
@@ -227,7 +227,7 @@ int read_author_script(const char *path, char **name, char **email, char **date, int allow_missing);void parse_strategy_opts (struct replay_opts *opts,char *raw_opts);int write_basic_state (struct replay_opts *opts,const char *head_name, -struct commit *onto,const char *orig_head); +struct commit *onto,const struct object_id *orig_head);void sequencer_post_commit_cleanup (struct repository *r,int verbose);int sequencer_get_last_command (struct repository * r,enum replay_action *action);
Signature · 1 line
gitgitgadget
PATCH 4/4 rebase -i: simplify get_revision_ranges()
In reply to Phillip Wood via GitGitGadgetFrom: Phillip Wood <[email protected]>
Now that all the external users of head_hash have been converted to use a opts->orig_head instead we can stop returning head_hash from get_revision_ranges().
--- builtin/rebase.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-)
builtin/rebase.c +5 -7
@@ -270,15 +270,14 @@ static int edit_todo_file(unsigned flags) }static int get_revision_ranges (struct commit *upstream,struct commit *onto, -struct object_id *orig_head,char *head_hash, -char **revisions,char **shortrevisions) +struct object_id *orig_head,char **revisions, +char **shortrevisions) {struct commit *base_rev = upstream ? upstream : onto;const char *shorthead; -oid_to_hex_r (head_hash, orig_head); *revisions =xstrfmt ("%s...%s" ,oid_to_hex (&base_rev->object.oid), - head_hash); +oid_to_hex (orig_head)); shorthead =find_unique_abbrev (orig_head,DEFAULT_ABBREV );
@@ -328,7 +327,6 @@ static void split_exec_commands(const char *cmd, struct string_list *commands)static int do_interactive_rebase (struct rebase_options *opts,unsigned flags) {int ret; -char head_hash[GIT_MAX_HEXSZ ];char *revisions =NULL , *shortrevisions =NULL ;struct strvec make_script_args =STRVEC_INIT ;struct todo_list todo_list =TODO_LIST_INIT ;
@@ -336,12 +334,12 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)struct string_list commands =STRING_LIST_INIT_DUP ;if (get_revision_ranges (opts->upstream, opts->onto, &opts->orig_head, - head_hash, &revisions, &shortrevisions)) + &revisions, &shortrevisions))return -1 ;if (init_basic_state (&replay, opts->head_name ? opts->head_name :"detached HEAD" , - opts->onto, head_hash)) { + opts->onto, &opts->orig_head)) {free (revisions);free (shortrevisions);
Signature · 1 line
gitgitgadget
v2 cover rebase -i: fix ORIG_HEAD handling
In reply to Phillip Wood via GitGitGadgetThe buffer containing the oid for ORIG_HEAD is overwritten before ORIG_HEAD is created. This series fixes that bug and then converts the code to use struct object_id rather than passing around strings.
Thanks to Caspar for reporting the bug and providing a reproducible example
Changes since v1:
* Updated the commit message to patch 1 as suggested by Junio
* Moved the conversion from find_unique_abbrev() to oid_to_hex() from patch 1 to patch 4
* Fixed a compilation error in patch 3 (the required change was in patch 4 by a mistake)
Cc: Johannes Schindelin [email protected] [[email protected]], Caspar Duregger [email protected] [[email protected]]
Phillip Wood (4):
rebase -i: stop overwriting ORIG_HEAD buffer
rebase -i: use struct object_id rather than looking up commit rebase -i: use struct object_id when writing state rebase -i: simplify get_revision_ranges()
builtin/rebase.c | 20 ++++++++++---------- sequencer.c | 15 ++++++--------- sequencer.h | 7 ++++--- t/t3404-rebase-interactive.sh | 11 +++++++++++ 4 files changed, 31 insertions(+), 22 deletions(-)
base-commit: 2e673356aefa8ed19be3c878f966ad6189ecb510
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-773%2Fphillipwood%2Fwip%2Frebase-fix-orig_head-handling-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-773/phillipwood/wip/rebase-fix-orig_head-handling-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/773
Range-diff vs v1:
1: 24f2c4a623 ! 1: da05958c58 rebase -i: stop overwriting ORIG_HEAD buffer
@@ Metadata
## Commit message ##
rebase -i: stop overwriting ORIG_HEAD buffer
- After rebasing ORIG_HEAD is supposed to point to the old HEAD of the
- rebased branch. Unfortunately the buffer storing the oid was
- overwritten with a new oid before ORIG_HEAD was created. The buffer is
- also used when writing .git/rebase-merge/orig-head which is used by
- rebase --abort to restore the previous head. Luckily that file is
- written before the buffer is overwritten. As we want the full oid
- find_unique_abbrev() is replaced with oid_to_hex_r() rather than
- find_unique_abbrev_r().
+ After rebasing, ORIG_HEAD is supposed to point to the old HEAD of the
+ rebased branch. The code used find_unique_abbrev() to obtain the
+ object name of the old HEAD and wrote to both
+ .git/rebase-merge/orig-head (used by rebase --abort to go back to
+ the previous state) and to ORIG_HEAD. The buffer find_unique_abbrev()
+ gives back is volatile, unfortunately, and was overwritten after the
+ former file is written but before ORIG_FILE is written, leaving an
+ incorrect object name in it.
+
+ Avoid relying on the volatile buffer of find_unique_abbrev(), and
+ instead supply our own buffer to keep the object name.
I think that all of the users of head_hash should actually be using opts->orig_head instead as passing a string rather than a struct object_id around is a hang over from the scripted implementation. This patch just fixes the immediate bug and adds a regression test based on
- Caspar's reproduction example. The users will be converted to use
+ Caspar's reproduction example[1]. The users will be converted to use
struct object_id and head_hash removed in the next few commits.
+ [1] https://lore.kernel.org/git/CAFzd1+7PDg2PZgKw7U0kdepdYuoML9wSN4kofmB_-8NHrbbrHg@mail.gmail.com
+
@@ builtin/rebase.c: static int edit_todo_file(unsigned flags)
const char *shorthead;
- *head_hash = find_unique_abbrev(orig_head, GIT_MAX_HEXSZ);
-+ oid_to_hex_r(head_hash, orig_head);
++ find_unique_abbrev_r(head_hash, orig_head, GIT_MAX_HEXSZ);
*revisions = xstrfmt("%s...%s", oid_to_hex(&base_rev->object.oid),
- *head_hash);
+ head_hash);
2: 589aed08f1 = 2: da499f1e4c rebase -i: use struct object_id rather than looking up commit 3: 11494a7b79 ! 3: 076d212915 rebase -i: use struct object_id when writing state
@@ builtin/rebase.c: static int get_revision_ranges(struct commit *upstream, struct
{
FILE *interactive;
+@@ builtin/rebase.c: static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) +
+ if (init_basic_state(&replay,
+ opts->head_name ? opts->head_name : "detached HEAD",
+- opts->onto, head_hash)) {
++ opts->onto, &opts->orig_head)) {
+ free(revisions);
+ free(shortrevisions);
+
## sequencer.c ##
@@ sequencer.c: static void write_strategy_opts(struct replay_opts *opts)
4: ed78f8628a ! 4: faae3ccff5 rebase -i: simplify get_revision_ranges()
@@ Commit message
use a opts->orig_head instead we can stop returning head_hash from
get_revision_ranges().
+ Because we want to pass the full object names back to the caller in
+ `revisions` the find_unique_abbrev_r() call that was used to initialize
+ `head_hash` is replaced with oid_to_hex().
+
Signed-off-by: Phillip Wood <[email protected]>
## builtin/rebase.c ##
@@ builtin/rebase.c: static int edit_todo_file(unsigned flags)
struct commit *base_rev = upstream ? upstream : onto;
const char *shorthead;
-- oid_to_hex_r(head_hash, orig_head);
+- find_unique_abbrev_r(head_hash, orig_head, GIT_MAX_HEXSZ);
*revisions = xstrfmt("%s...%s", oid_to_hex(&base_rev->object.oid),
- head_hash);
+ oid_to_hex(orig_head));
@@ builtin/rebase.c: static int do_interactive_rebase(struct rebase_options *opts,
return -1;
if (init_basic_state(&replay,
- opts->head_name ? opts->head_name : "detached HEAD",
-- opts->onto, head_hash)) {
-+ opts->onto, &opts->orig_head)) {
- free(revisions);
- free(shortrevisions);
- Signature · 1 line
gitgitgadget
PATCH v2 1/4 rebase -i: stop overwriting ORIG_HEAD buffer
From: Phillip Wood <[email protected]>
After rebasing, ORIG_HEAD is supposed to point to the old HEAD of the rebased branch. The code used find_unique_abbrev() to obtain the object name of the old HEAD and wrote to both .git/rebase-merge/orig-head (used by rebase --abort to go back to the previous state) and to ORIG_HEAD. The buffer find_unique_abbrev() gives back is volatile, unfortunately, and was overwritten after the former file is written but before ORIG_FILE is written, leaving an incorrect object name in it.
Avoid relying on the volatile buffer of find_unique_abbrev(), and instead supply our own buffer to keep the object name.
I think that all of the users of head_hash should actually be using opts->orig_head instead as passing a string rather than a struct object_id around is a hang over from the scripted implementation. This patch just fixes the immediate bug and adds a regression test based on Caspar's reproduction example[1]. The users will be converted to use struct object_id and head_hash removed in the next few commits.
[1] https://lore.kernel.org/git/CAFzd1+7PDg2PZgKw7U0kdepdYuoML9wSN4kofmB_-8NHrbbrHg@mail.gmail.com
--- builtin/rebase.c | 10 +++++----- t/t3404-rebase-interactive.sh | 11 +++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-)
builtin/rebase.c +5 -5
@@ -270,15 +270,15 @@ static int edit_todo_file(unsigned flags) }static int get_revision_ranges (struct commit *upstream,struct commit *onto, -struct object_id *orig_head,const char **head_hash, +struct object_id *orig_head,char *head_hash,char **revisions,char **shortrevisions) {struct commit *base_rev = upstream ? upstream : onto;const char *shorthead; - *head_hash =find_unique_abbrev (orig_head,GIT_MAX_HEXSZ ); +find_unique_abbrev_r (head_hash, orig_head,GIT_MAX_HEXSZ ); *revisions =xstrfmt ("%s...%s" ,oid_to_hex (&base_rev->object.oid), - *head_hash); + head_hash); shorthead =find_unique_abbrev (orig_head,DEFAULT_ABBREV );
@@ -327,7 +327,7 @@ static void split_exec_commands(const char *cmd, struct string_list *commands)static int do_interactive_rebase (struct rebase_options *opts,unsigned flags) {int ret; -const char *head_hash =NULL ; +char head_hash[GIT_MAX_HEXSZ ];char *revisions =NULL , *shortrevisions =NULL ;struct strvec make_script_args =STRVEC_INIT ;struct todo_list todo_list =TODO_LIST_INIT ;
@@ -335,7 +335,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)struct string_list commands =STRING_LIST_INIT_DUP ;if (get_revision_ranges (opts->upstream, opts->onto, &opts->orig_head, - &head_hash, &revisions, &shortrevisions)) + head_hash, &revisions, &shortrevisions))return -1 ; if (init_basic_state(&replay,
t/t3404-rebase-interactive.sh +11 -0
@@ -1797,6 +1797,17 @@ test_expect_success 'todo has correct onto hash' ' test_i18ngrep "^# Rebase ..* onto $onto" actual ' +test_expect_success 'ORIG_HEAD is updated correctly' ' + test_when_finished "git checkout master && git branch -D test-orig-head" && + git checkout -b test-orig-head A && + git commit --allow-empty -m A1 && + git commit --allow-empty -m A2 && + git commit --allow-empty -m A3 && + git commit --allow-empty -m A4 && + git rebase master && + test_cmp_rev ORIG_HEAD test-orig-head@{1} +' + # This must be the last test in this file test_expect_success '$EDITOR and friends are unchanged' ' test_editor_unchanged
Signature · 1 line
gitgitgadget
PATCH v2 2/4 rebase -i: use struct object_id rather than looking up commit
In reply to Phillip Wood via GitGitGadgetFrom: Phillip Wood <[email protected]>
We already have a struct object_id containing the oid that we want to set ORIG_HEAD to so use that rather than converting it to a string and then calling get_oid() on that string.
--- builtin/rebase.c | 5 +++-- sequencer.c | 10 +++------- sequencer.h | 5 +++-- 3 files changed, 9 insertions(+), 11 deletions(-)
builtin/rebase.c +3 -2
@@ -370,8 +370,9 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)split_exec_commands (opts->cmd, &commands); ret =complete_action (the_repository, &replay, flags, - shortrevisions, opts->onto_name, opts->onto, head_hash, - &commands, opts->autosquash, &todo_list); + shortrevisions, opts->onto_name, opts->onto, + &opts->orig_head, &commands, opts->autosquash, + &todo_list); }string_list_clear (&commands,0 );
sequencer.c +3 -7
@@ -3965,21 +3965,17 @@ static int run_git_checkout(struct repository *r, struct replay_opts *opts,static int checkout_onto (struct repository *r,struct replay_opts *opts,const char *onto_name,const struct object_id *onto, -const char *orig_head) +const struct object_id *orig_head) { -struct object_id oid;const char *action =reflog_message (opts,"start" ,"checkout %s" , onto_name); -if (get_oid (orig_head, &oid)) -return error (_ ("%s: not a valid OID" ), orig_head); -if (run_git_checkout (r, opts,oid_to_hex (onto), action)) {apply_autostash (rebase_path_autostash ());sequencer_remove_state (opts);return error (_ ("could not detach HEAD" )); } -return update_ref (NULL ,"ORIG_HEAD" , &oid,NULL ,0 ,UPDATE_REFS_MSG_ON_ERR ); +return update_ref (NULL ,"ORIG_HEAD" , orig_head,NULL ,0 ,UPDATE_REFS_MSG_ON_ERR ); }static int stopped_at_head (struct repository *r)
@@ -5314,7 +5310,7 @@ static int skip_unnecessary_picks(struct repository *r,int complete_action (struct repository *r,struct replay_opts *opts,unsigned flags,const char *shortrevisions,const char *onto_name, -struct commit *onto,const char *orig_head, +struct commit *onto,const struct object_id *orig_head,struct string_list *commands,unsigned autosquash,struct todo_list *todo_list) {
sequencer.h +3 -2
@@ -163,8 +163,9 @@ void todo_list_add_exec_commands(struct todo_list *todo_list, struct string_list *commands);int complete_action (struct repository *r,struct replay_opts *opts,unsigned flags,const char *shortrevisions,const char *onto_name, -struct commit *onto,const char *orig_head,struct string_list *commands, -unsigned autosquash,struct todo_list *todo_list); +struct commit *onto,const struct object_id *orig_head, +struct string_list *commands,unsigned autosquash, +struct todo_list *todo_list);int todo_list_rearrange_squash (struct todo_list *todo_list);/*
Signature · 1 line
gitgitgadget
PATCH v2 3/4 rebase -i: use struct object_id when writing state
In reply to Phillip Wood via GitGitGadgetFrom: Phillip Wood <[email protected]>
Rather than passing a string around pass the struct object_id that the string was created from call oid_hex() when we write the file.
--- builtin/rebase.c | 5 +++-- sequencer.c | 5 +++-- sequencer.h | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-)
builtin/rebase.c +3 -2
@@ -296,7 +296,8 @@ static int get_revision_ranges(struct commit *upstream, struct commit *onto, }static int init_basic_state (struct replay_opts *opts,const char *head_name, -struct commit *onto,const char *orig_head) +struct commit *onto, +const struct object_id *orig_head) {FILE *interactive;
@@ -340,7 +341,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)if (init_basic_state (&replay, opts->head_name ? opts->head_name :"detached HEAD" , - opts->onto, head_hash)) { + opts->onto, &opts->orig_head)) {free (revisions);free (shortrevisions);
sequencer.c +3 -2
@@ -2692,7 +2692,7 @@ static void write_strategy_opts(struct replay_opts *opts) }int write_basic_state (struct replay_opts *opts,const char *head_name, -struct commit *onto,const char *orig_head) +struct commit *onto,const struct object_id *orig_head) {if (head_name)write_file (rebase_path_head_name (),"%s\n" , head_name);
@@ -2700,7 +2700,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,write_file (rebase_path_onto (),"%s\n" ,oid_to_hex (&onto->object.oid));if (orig_head) -write_file (rebase_path_orig_head (),"%s\n" , orig_head); +write_file (rebase_path_orig_head (),"%s\n" , +oid_to_hex (orig_head));if (opts->quiet)write_file (rebase_path_quiet (),"%s" ,"" );
sequencer.h +1 -1
@@ -227,7 +227,7 @@ int read_author_script(const char *path, char **name, char **email, char **date, int allow_missing);void parse_strategy_opts (struct replay_opts *opts,char *raw_opts);int write_basic_state (struct replay_opts *opts,const char *head_name, -struct commit *onto,const char *orig_head); +struct commit *onto,const struct object_id *orig_head);void sequencer_post_commit_cleanup (struct repository *r,int verbose);int sequencer_get_last_command (struct repository * r,enum replay_action *action);
Signature · 1 line
gitgitgadget
PATCH v2 4/4 rebase -i: simplify get_revision_ranges()
In reply to Phillip Wood via GitGitGadgetFrom: Phillip Wood <[email protected]>
Now that all the external users of head_hash have been converted to use a opts->orig_head instead we can stop returning head_hash from get_revision_ranges().
Because we want to pass the full object names back to the caller in revisions the find_unique_abbrev_r() call that was used to initialize head_hash is replaced with oid_to_hex().
--- builtin/rebase.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-)
builtin/rebase.c +4 -6
@@ -270,15 +270,14 @@ static int edit_todo_file(unsigned flags) }static int get_revision_ranges (struct commit *upstream,struct commit *onto, -struct object_id *orig_head,char *head_hash, -char **revisions,char **shortrevisions) +struct object_id *orig_head,char **revisions, +char **shortrevisions) {struct commit *base_rev = upstream ? upstream : onto;const char *shorthead; -find_unique_abbrev_r (head_hash, orig_head,GIT_MAX_HEXSZ ); *revisions =xstrfmt ("%s...%s" ,oid_to_hex (&base_rev->object.oid), - head_hash); +oid_to_hex (orig_head)); shorthead =find_unique_abbrev (orig_head,DEFAULT_ABBREV );
@@ -328,7 +327,6 @@ static void split_exec_commands(const char *cmd, struct string_list *commands)static int do_interactive_rebase (struct rebase_options *opts,unsigned flags) {int ret; -char head_hash[GIT_MAX_HEXSZ ];char *revisions =NULL , *shortrevisions =NULL ;struct strvec make_script_args =STRVEC_INIT ;struct todo_list todo_list =TODO_LIST_INIT ;
@@ -336,7 +334,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)struct string_list commands =STRING_LIST_INIT_DUP ;if (get_revision_ranges (opts->upstream, opts->onto, &opts->orig_head, - head_hash, &revisions, &shortrevisions)) + &revisions, &shortrevisions))return -1 ; if (init_basic_state(&replay,
Signature · 1 line
gitgitgadget