From: Johannes Schindelin <hidden> Date: 2016-08-31 08:55:18
This marks the count down to '3': two more patch series after this
(really tiny ones) and we have a faster rebase -i.
The idea of this patch series is to teach the sequencer to understand
all of the commands in `git-rebase-todo` scripts, to execute them and to
behave pretty much very the same as `git rebase -i --continue` when
called with the newly-introduced REPLAY_INTERACTIVE_REBASE setting.
Most of these patches should be pretty much straight-forward. When not,
I tried to make a point of describing enough background in the commit
message. Please feel free to point out where my explanations fall short.
Note that even after this patch series is applied, rebase -i is still
unaffected. It will require the next patch series which introduces the
rebase--helper that essentially implements `git rebase -i --continue` by
calling the sequencer with the appropriate options.
The final patch series will move a couple of pre- and post-processing
steps into the rebase--helper/sequencer (such as expanding/shrinking the
SHA-1s, reordering the fixup!/squash! lines, etc). This might sound like
a mere add-on, but it is essential for the speed improvements: those
stupid little processing steps really dominated the execution time in my
tests.
Apart from mostly cosmetic patches (and the occasional odd bug that I
fixed promptly), I used these patches since mid May to perform all of my
interactive rebases. In mid June, I had the idea to teach rebase -i to
run *both* scripted rebase and rebase--helper and to cross-validate the
results. This slowed down all my interactive rebases since, but helped
me catch three rather obscure bugs (e.g. that git commit --fixup unfolds
long onelines and rebase -i still finds the correct original commit).
This is all only to say that I am rather confident that the current code
does the job.
As stated earlier, my plan is to integrate all of these patch series
into Git for Windows v2.10.0. And then be mostly offline during the
second half of September ;-) There are multiple reasons why I want to
pull through with this: I have been working on these patches since mid
February and it is high time that they benefit users other than me.
Also: I got a bit tired of postponing the integration for a couple of
Git for Windows versions already. Originally, I had planned it for
v2.9.0, but then I wanted to give the patches some review, regretting
that idea when it took one of the 14 patch series over a month to
stabilize.
This means that I will not perform any major refactoring at this stage,
but postpone any such work, if necessary at all.
Just to reiterate why I do all this: it speeds up the interactive rebase
substantially. Even with a not yet fully builtin rebase -i, but just the
part after the user edited the `git-rebase-todo` script.
The performance test I introduced to demonstrate this (p3404) shows a
speed-up of +380% here (i.e. roughly 5x), from ~8.8 seconds to ~1.8
seconds. This is on Windows, where the performance impact of avoiding
shell scripting is most noticable.
On MacOSX and on Linux, the speed-up is less pronounced, but still
noticable, at least if you trust Travis CI, which I abused to perform
that test for me. Check for yourself (searching for "3404.2") here:
https://travis-ci.org/git/git/builds/156295227. According to those logs,
p3404 is speeded up from ~0.45 seconds to ~0.12 seconds on Linux (read:
about 3.5x) and from ~1.7 seconds to ~0.5 seconds on MacOSX (read:
almost 4x).
Johannes Schindelin (34):
sequencer: support a new action: 'interactive rebase'
sequencer (rebase -i): implement the 'noop' command
sequencer (rebase -i): implement the 'edit' command
sequencer (rebase -i): implement the 'exec' command
sequencer (rebase -i): learn about the 'verbose' mode
sequencer (rebase -i): write the 'done' file
sequencer (rebase -i): add support for the 'fixup' and 'squash'
commands
sequencer (rebase -i): implement the short commands
sequencer (rebase -i): write an author-script file
sequencer (rebase -i): allow continuing with staged changes
sequencer (rebase -i): remove CHERRY_PICK_HEAD when no longer needed
sequencer (rebase -i): skip some revert/cherry-pick specific code path
sequencer (rebase -i): the todo can be empty when continuing
sequencer (rebase -i): update refs after a successful rebase
sequencer (rebase -i): leave a patch upon error
sequencer (rebase -i): implement the 'reword' command
sequencer (rebase -i): allow fast-forwarding for edit/reword
sequencer (rebase -i): refactor setting the reflog message
sequencer (rebase -i): set the reflog message consistently
sequencer (rebase -i): copy commit notes at end
sequencer (rebase -i): record interrupted commits in rewritten, too
sequencer (rebase -i): run the post-rewrite hook, if needed
sequencer (rebase -i): respect the rebase.autostash setting
sequencer (rebase -i): respect strategy/strategy_opts settings
sequencer (rebase -i): allow rescheduling commands
sequencer (rebase -i): implement the 'drop' command
sequencer (rebase -i): differentiate between comments and 'noop'
run_command_opt(): optionally hide stderr when the command succeeds
sequencer (rebase -i): show only failed `git commit`'s output
sequencer (rebase -i): show only failed cherry-picks' output
sequencer (rebase -i): suggest --edit-todo upon unknown command
sequencer (rebase -i): show the progress
sequencer (rebase -i): write the progress into files
sequencer (rebase -i): write out the final message
run-command.c | 23 ++
run-command.h | 1 +
sequencer.c | 990 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---
sequencer.h | 4 +-
4 files changed, 971 insertions(+), 47 deletions(-)
Based-On: prepare-sequencer-and-wt_status at https://github.com/dscho/git
Fetch-Base-Via: git fetch https://github.com/dscho/git prepare-sequencer-and-wt_status
Published-As: https://github.com/dscho/git/releases/tag/sequencer-i-v1
Fetch-It-Via: git fetch https://github.com/dscho/git sequencer-i-v1
--
2.10.0.rc2.102.g5c102ec
base-commit: 45bfeb0c4044dfcf090ad97e839d250d62308fac
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:54:55
This is a huge patch, and at the same time a huge step forward to
execute the performance-critical parts of the interactive rebase in a
builtin command.
Since 'fixup' and 'squash' are not only similar, but also need to know
about each other (we want to reduce a series of fixups/squashes into a
single, final commit message edit, from the user's point of view), we
really have to implement them both at the same time.
Most of the actual work is done by the existing code path that already
handles the "pick" and the "edit" commands; We added support for other
features (e.g. to amend the commit message) in the patches leading up to
this one, yet there are still quite a few bits in this patch that simply
would not make sense as individual patches (such as: determining whether
there was anything to "fix up" in the "todo" script, etc).
In theory, it would be possible to reuse the fast-forward code path also
for the fixup and the squash code paths, but in practice this would make
the code less readable. The end result cannot be fast-forwarded anyway,
therefore let's just extend the cherry-picking code path for now.
Since the sequencer parses the entire `git-rebase-todo` script in one go,
fixup or squash commands without a preceding pick can be reported early
(in git-rebase--interactive, we could only report such errors just before
executing the fixup/squash).
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 242 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 231 insertions(+), 11 deletions(-)
@@ -689,16 +722,129 @@ static const char *command_to_string(const enum todo_command command)die("Unknown command: %d",command);}+staticintis_fixup(enumtodo_commandcommand)+{+returncommand==TODO_FIXUP||command==TODO_SQUASH;+}++staticconstchar*nth_for_number(intn)+{+intn1=n%10,n10=n%100;++if(n1==1&&n10!=11)+return"st";+if(n1==2&&n10!=12)+return"nd";+if(n1==3&&n10!=13)+return"rd";+return"th";+}++staticintupdate_squash_messages(enumtodo_commandcommand,+structcommit*commit,structreplay_opts*opts)+{+structstrbufbuf=STRBUF_INIT;+intcount;+constchar*message,*body;++if(file_exists(rebase_path_squash_msg())){+char*p,*p2;++if(strbuf_read_file(&buf,rebase_path_squash_msg(),2048)<=0)+returnerror("Could not read %s",+rebase_path_squash_msg());++if(buf.buf[0]=='\n'||!skip_prefix(buf.buf+1,+" This is a combination of ",+(constchar**)&p))+returnerror("Unexpected 1st line of squash message:\n"+"\n\t%.*s",+(int)(strchrnul(buf.buf,'\n')-buf.buf),+buf.buf);+count=strtol(p,&p2,10);++if(count<1||*p2!=' ')+returnerror("Invalid 1st line of squash message:\n"+"\n\t%.*s",+(int)(strchrnul(buf.buf,'\n')-buf.buf),+buf.buf);++sprintf((char*)p,"%d",++count);+if(!*p2)+*p2=' ';+else{+*(++p2)='c';+strbuf_insert(&buf,p2-buf.buf," ",1);+}+}+else{+unsignedcharhead[20];+structcommit*head_commit;+constchar*head_message,*body;++if(get_sha1("HEAD",head))+returnerror("Need a HEAD to fixup");+if(!(head_commit=lookup_commit_reference(head)))+returnerror("Could not read HEAD");+if(!(head_message=get_commit_buffer(head_commit,NULL)))+returnerror("Could not read HEAD's commit message");++body=strstr(head_message,"\n\n");+if(!body)+body="";+else+body=skip_blank_lines(body+2);+if(write_file_gently(rebase_path_fixup_msg(),body,0))+returnerror("Cannot write %s",+rebase_path_fixup_msg());++count=2;+strbuf_addf(&buf,"%c This is a combination of 2 commits.\n"+"%c The first commit's message is:\n\n%s",+comment_line_char,comment_line_char,body);++unuse_commit_buffer(head_commit,head_message);+}++if(!(message=get_commit_buffer(commit,NULL)))+returnerror("Could not read commit message of %s",+oid_to_hex(&commit->object.oid));+body=strstr(message,"\n\n");+if(!body)+body="";+else+body=skip_blank_lines(body+2);++if(command==TODO_SQUASH){+unlink(rebase_path_fixup_msg());+strbuf_addf(&buf,"\n%c This is the %d%s commit message:\n\n%s",+comment_line_char,+count,nth_for_number(count),body);+}+elseif(command==TODO_FIXUP){+strbuf_addf(&buf,+"\n%c The %d%s commit message will be skipped:\n\n",+comment_line_char,count,nth_for_number(count));+strbuf_add_commented_lines(&buf,body,strlen(body));+}+else+returnerror("Unknown command: %d",command);+unuse_commit_buffer(commit,message);++returnwrite_message(&buf,rebase_path_squash_msg());+}staticintdo_pick_commit(enumtodo_commandcommand,structcommit*commit,-structreplay_opts*opts)+structreplay_opts*opts,intfinal_fixup){+intedit=opts->edit,cleanup_commit_message=0;+constchar*msg_file=edit?NULL:git_path_merge_msg();unsignedcharhead[20];structcommit*base,*next,*parent;constchar*base_label,*next_label;structcommit_messagemsg={NULL,NULL,NULL,NULL};structstrbufmsgbuf=STRBUF_INIT;-intres=0,unborn=0,allow;+intres=0,unborn=0,amend=0,allow;if(opts->no_commit){/*
@@ -1011,8 +1183,15 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list)if(parse_insn_line(item,p,eol)){res|=error("Invalid line %d: %.*s",i,(int)(eol-p),p);-item->command=-1;+item->command=TODO_NOOP;}+if(fixup_okay)+;/* do nothing */+elseif(is_fixup(item->command))+returnerror("Cannot '%s' without a previous commit",+command_to_string(item->command));+elseif(item->command!=TODO_NOOP)+fixup_okay=1;p=*eol?eol+1:eol;}if(!todo_list->nr)
@@ -1406,6 +1585,20 @@ static int error_with_patch(struct commit *commit,returnexit_code;}+staticinterror_failed_squash(structcommit*commit,+structreplay_opts*opts,intsubject_len,constchar*subject)+{+if(rename(rebase_path_squash_msg(),rebase_path_message()))+returnerror("Could not rename %s to %s",+rebase_path_squash_msg(),rebase_path_message());+unlink(rebase_path_fixup_msg());+unlink(git_path("MERGE_MSG"));+if(copy_file(git_path("MERGE_MSG"),rebase_path_message(),0666))+returnerror("Could not copy %s to %s",rebase_path_message(),+git_path("MERGE_MSG"));+returnerror_with_patch(commit,subject,subject_len,opts,1,0);+}+staticintdo_exec(constchar*command_line){constchar*child_argv[]={NULL,NULL};
@@ -1447,6 +1640,21 @@ static int do_exec(const char *command_line)returnstatus;}+staticintis_final_fixup(structtodo_list*todo_list)+{+inti=todo_list->current;++if(!is_fixup(todo_list->items[i].command))+return0;++while(++i<todo_list->nr)+if(is_fixup(todo_list->items[i].command))+return0;+elseif(todo_list->items[i].command<TODO_NOOP)+break;+return1;+}+staticintpick_commits(structtodo_list*todo_list,structreplay_opts*opts){intres=0;
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:55:01
When calling `git rebase -i -v`, the user wants to see some statistics
after the commits were rebased. Let's show some.
The strbuf we use to perform that task will be used for other things
in subsequent commits, hence it is declared and initialized in a wider
scope than strictly needed here.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 22 ++++++++++++++++++++++
sequencer.h | 1 +
2 files changed, 23 insertions(+)
@@ -63,6 +63,8 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha")*command-line(andareonlyconsumed,notmodified,bythesequencer).*/staticGIT_PATH_FUNC(rebase_path_gpg_sign_opt,"rebase-merge/gpg_sign_opt")+staticGIT_PATH_FUNC(rebase_path_orig_head,"rebase-merge/orig-head")+staticGIT_PATH_FUNC(rebase_path_verbose,"rebase-merge/verbose")/* We will introduce the 'interactive rebase' mode later */staticinlineintis_rebase_i(conststructreplay_opts*opts)
@@ -1096,6 +1098,9 @@ static int read_populate_opts(struct replay_opts *opts)}}+if(file_exists(rebase_path_verbose()))+opts->verbose=1;+return0;}
@@ -1468,9 +1473,26 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)}if(is_rebase_i(opts)){+structstrbufbuf=STRBUF_INIT;+/* Stopped in the middle, as planned? */if(todo_list->current<todo_list->nr)return0;++if(opts->verbose){+constchar*argv[]={+"diff-tree","--stat",NULL,NULL+};++if(!read_oneliner(&buf,rebase_path_orig_head(),0))+returnerror("Could not read %s",+rebase_path_orig_head());+strbuf_addstr(&buf,"..HEAD");+argv[2]=buf.buf;+run_command_v_opt(argv,RUN_GIT_CMD);+strbuf_reset(&buf);+}+strbuf_release(&buf);}/*
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:55:04
The 'exec' command is a little special among rebase -i's commands, as it
does *not* have a SHA-1 as first parameter. Instead, everything after the
`exec` command is treated as command-line to execute.
Let's reuse the arg/arg_len fields of the todo_item structure (which hold
the oneline for pick/edit commands) to point to the command-line.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 58 insertions(+)
@@ -1372,6 +1381,47 @@ static int error_with_patch(struct commit *commit,returnexit_code;}+staticintdo_exec(constchar*command_line)+{+constchar*child_argv[]={NULL,NULL};+intdirty,status;++fprintf(stderr,"Executing: %s\n",command_line);+child_argv[0]=command_line;+status=run_command_v_opt(child_argv,RUN_USING_SHELL);++/* force re-reading of the cache */+if(discard_cache()<0||read_cache()<0)+returnerror(_("Could not read index"));++dirty=require_clean_work_tree("rebase",NULL,1,1);++if(status){+warning("Execution failed: %s\n%s"+"You can fix the problem, and then run\n"+"\n"+" git rebase --continue\n"+"\n",+command_line,+dirty?"and made changes to the index and/or the "+"working tree\n":"");+if(status==127)+/* command not found */+status=1;+}+elseif(dirty){+warning("Execution succeeded: %s\nbut "+"left changes to the index and/or the working tree\n"+"Commit or stash your changes, and then run\n"+"\n"+" git rebase --continue\n"+"\n",command_line);+status=1;+}++returnstatus;+}+staticintpick_commits(structtodo_list*todo_list,structreplay_opts*opts){intres=0;
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:55:05
This patch is a straight-forward reimplementation of the `edit`
operation of the interactive rebase command.
Well, not *quite* straight-forward: when stopping, the `edit`
command wants to write the `patch` file (which is not only the
patch, but includes the commit message and author information). To
that end, this patch requires the earlier work that taught the
log-tree machinery to respect the `file` setting of
rev_info->diffopt to write to a file stream different than stdout.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 112 insertions(+), 2 deletions(-)
@@ -43,6 +44,20 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")*/staticGIT_PATH_FUNC(rebase_path_author_script,"rebase-merge/author-script")/*+*Whenan"edit"rebasecommandisbeingprocessed,theSHA1ofthe+*committobeeditedisrecordedinthisfile.When"git rebase+*--continue" is executed, if there are any staged changes then they+*willbeamendedtotheHEADcommit,butonlyprovidedtheHEAD+*commitisstillthecommittobeedited.Whenanyotherrebase+*commandisprocessed,thisfileisdeleted.+*/+staticGIT_PATH_FUNC(rebase_path_amend,"rebase-merge/amend")+/*+*Whenwestopatagivenpatchviathe"edit"command,thisfilecontains+*thelongcommitnameofthecorrespondingpatch.+*/+staticGIT_PATH_FUNC(rebase_path_stopped_sha,"rebase-merge/stopped-sha")+/**Thefollowingfilesarewrittenbygit-rebasejustafterparsingthe*command-line(andareonlyconsumed,notmodified,bythesequencer).*/
@@ -1279,9 +1296,85 @@ static int save_opts(struct replay_opts *opts)returnres;}+staticintmake_patch(structcommit*commit,structreplay_opts*opts)+{+structstrbufbuf=STRBUF_INIT;+structrev_infolog_tree_opt;+constchar*commit_buffer=get_commit_buffer(commit,NULL),*subject;+intres=0;++if(write_file_gently(rebase_path_stopped_sha(),+short_commit_name(commit),1)<0)+return-1;++strbuf_addf(&buf,"%s/patch",get_dir(opts));+memset(&log_tree_opt,0,sizeof(log_tree_opt));+init_revisions(&log_tree_opt,NULL);+log_tree_opt.abbrev=0;+log_tree_opt.diff=1;+log_tree_opt.diffopt.output_format=DIFF_FORMAT_PATCH;+log_tree_opt.disable_stdin=1;+log_tree_opt.no_commit_id=1;+log_tree_opt.diffopt.file=fopen(buf.buf,"w");+log_tree_opt.diffopt.use_color=GIT_COLOR_NEVER;+if(!log_tree_opt.diffopt.file)+res|=error_errno("could not open '%s'",buf.buf);+else{+res|=log_tree_commit(&log_tree_opt,commit);+fclose(log_tree_opt.diffopt.file);+}+strbuf_reset(&buf);++strbuf_addf(&buf,"%s/message",get_dir(opts));+if(!file_exists(buf.buf)){+find_commit_subject(commit_buffer,&subject);+res|=write_file_gently(buf.buf,subject,1);+unuse_commit_buffer(commit,commit_buffer);+}+strbuf_release(&buf);++returnres;+}++staticintintend_to_amend(void)+{+unsignedcharhead[20];++if(get_sha1("HEAD",head))+returnerror("Cannot read HEAD");++returnwrite_file_gently(rebase_path_amend(),sha1_to_hex(head),1);+}++staticinterror_with_patch(structcommit*commit,+constchar*subject,intsubject_len,+structreplay_opts*opts,intexit_code,intto_amend)+{+if(make_patch(commit,opts))+return-1;++if(to_amend){+if(intend_to_amend())+return-1;++fprintf(stderr,"You can amend the commit now, with\n"+"\n"+" git commit --amend %s\n"+"\n"+"Once you are satisfied with your changes, run\n"+"\n"+" git rebase --continue\n",gpg_sign_opt_quoted(opts));+}+elseif(exit_code)+fprintf(stderr,"Could not apply %s... %.*s\n",+short_commit_name(commit),subject_len,subject);++returnexit_code;+}+staticintpick_commits(structtodo_list*todo_list,structreplay_opts*opts){-intres;+intres=0;setenv(GIT_REFLOG_ACTION,action_name(opts),0);if(opts->allow_ff)
@@ -1294,9 +1387,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)structtodo_item*item=todo_list->items+todo_list->current;if(save_todo(todo_list,opts))return-1;-if(item->command<=TODO_REVERT)+if(item->command<=TODO_EDIT){res=do_pick_commit(item->command,item->commit,opts);+if(item->command==TODO_EDIT){+structcommit*commit=item->commit;+if(!res)+warning("Stopped at %s... %.*s",+short_commit_name(commit),+item->arg_len,item->arg);+returnerror_with_patch(commit,+item->arg,item->arg_len,opts,res,+!res);+}+}elseif(item->command!=TODO_NOOP)returnerror("Unknown command %d",item->command);
@@ -1305,6 +1409,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)returnres;}+if(is_rebase_i(opts)){+/* Stopped in the middle, as planned? */+if(todo_list->current<todo_list->nr)+return0;+}+/**Sequenceofpicksfinishedsuccessfully;cleanupby*removingthe.git/sequencerdirectory
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:55:09
The 'noop' command is probably the most boring of all rebase -i commands
to support in the sequencer.
Which makes it an excellent candidate for this first stab to add support
for rebase -i's commands to the sequencer.
For the moment, let's also treat empty lines and commented-out lines as
'noop'; We will refine that handling later in this patch series.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 28 +++++++++++++++++++++++++---
1 file changed, 25 insertions(+), 3 deletions(-)
@@ -924,6 +934,13 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)if(i>=ARRAY_SIZE(todo_command_strings))return-1;+if(item->command==TODO_NOOP){+item->commit=NULL;+item->arg=bol;+item->arg_len=eol-bol;+return0;+}+/* Eat up extra spaces/ tabs before object name */padding=strspn(bol," \t");if(!padding)
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:55:15
This patch introduces a new action for the sequencer. It really does not
do a whole lot of its own right now, but lays the ground work for
patches to come. The intention, of course, is to finally make the
sequencer the work horse of the interactive rebase (the original idea
behind the "sequencer" concept).
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 32 +++++++++++++++++++++++++++++---
sequencer.h | 3 ++-
2 files changed, 31 insertions(+), 4 deletions(-)
@@ -28,6 +28,14 @@ static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")staticGIT_PATH_FUNC(git_path_opts_file,"sequencer/opts")staticGIT_PATH_FUNC(git_path_head_file,"sequencer/head")+staticGIT_PATH_FUNC(rebase_path,"rebase-merge")+/*+*Thefilecontainingrebasecommands,comments,andemptylines.+*Thisfileiscreatedby"git rebase -i"theneditedbytheuser.As+*thelinesareprocessed,theyareremovedfromthefrontofthis+*fileandwrittentothetailof'done'.+*/+staticGIT_PATH_FUNC(rebase_path_todo,"rebase-merge/git-rebase-todo")/**AscripttosettheGIT_AUTHOR_NAME,GIT_AUTHOR_EMAIL,and*GIT_AUTHOR_DATEthatwillbeusedforthecommitthatiscurrently
@@ -43,16 +51,20 @@ static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt")/* We will introduce the 'interactive rebase' mode later */staticinlineintis_rebase_i(conststructreplay_opts*opts){-return0;+returnopts->action==REPLAY_INTERACTIVE_REBASE;}staticconstchar*get_dir(conststructreplay_opts*opts){+if(is_rebase_i(opts))+returnrebase_path();returngit_path_seq_dir();}staticconstchar*get_todo_path(conststructreplay_opts*opts){+if(is_rebase_i(opts))+returnrebase_path_todo();returngit_path_todo_file();}
@@ -407,7 +427,10 @@ static int do_recursive_merge(struct commit *base, struct commit *next,if(active_cache_changed&&write_locked_index(&the_index,&index_lock,COMMIT_LOCK))-/* TRANSLATORS: %s will be "revert" or "cherry-pick" */+/*+*TRANSLATORS:%swillbe"revert","cherry-pick"or+*"rebase -i".+*/returnerror(_("%s: Unable to write new index file"),action_name(opts));rollback_lock_file(&index_lock);
@@ -1186,6 +1209,9 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)constchar*todo_path=get_todo_path(opts);intnext=todo_list->current,offset,fd;+if(is_rebase_i(opts))+next++;+fd=hold_lock_file_for_update(&todo_lock,todo_path,0);if(fd<0)returnerror_errno(_("Could not lock '%s'"),
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:55:20
For users' convenience, most rebase commands can be abbreviated, e.g.
'p' instead of 'pick' and 'x' instead of 'exec'. Let's teach the
sequencer to handle those abbreviated commands just fine.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 34 +++++++++++++++++++++-------------
1 file changed, 21 insertions(+), 13 deletions(-)
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:55:21
When the interactive rebase aborts, it writes out an author-script file
to record the author information for the current commit. As we are about
to teach the sequencer how to perform the actions behind an interactive
rebase, it needs to write those author-script files, too.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 48 insertions(+), 1 deletion(-)
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:55:22
When a cherry-pick continues without a "todo script", the intention is
simply to pick a single commit.
However, when an interactive rebase is continued without a "todo
script", it means that the last command has been completed and that we
now need to clean up.
This commit guards the revert/cherry-pick specific steps so that they
are not executed in rebase -i mode.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 23 +++++++++++++----------
1 file changed, 13 insertions(+), 10 deletions(-)
@@ -1859,22 +1859,25 @@ int sequencer_continue(struct replay_opts *opts)if(commit_staged_changes(opts))return-1;}-if(!file_exists(get_todo_path(opts)))+elseif(!file_exists(get_todo_path(opts)))returncontinue_single_pick();if(read_populate_opts(opts)||read_populate_todo(&todo_list,opts))return-1;-/* Verify that the conflict has been resolved */-if(file_exists(git_path_cherry_pick_head())||-file_exists(git_path_revert_head())){-intret=continue_single_pick();-if(ret)-returnret;+if(!is_rebase_i(opts)){+/* Verify that the conflict has been resolved */+if(file_exists(git_path_cherry_pick_head())||+file_exists(git_path_revert_head())){+intret=continue_single_pick();+if(ret)+returnret;+}+if(index_differs_from("HEAD",0))+returnerror_dirty_index(opts);+todo_list.current++;}-if(index_differs_from("HEAD",0))-returnerror_dirty_index(opts);-todo_list.current++;+res=pick_commits(&todo_list,opts);todo_list_release(&todo_list);returnres;
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:55:25
The scripted version of the interactive rebase already does that.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
@@ -1813,8 +1813,13 @@ static int commit_staged_changes(struct replay_opts *opts)if(has_unstaged_changes(1))returnerror(_("Cannot rebase: You have unstaged changes."));-if(!has_uncommitted_changes(0))+if(!has_uncommitted_changes(0)){+constchar*cherry_pick_head=git_path("CHERRY_PICK_HEAD");++if(file_exists(cherry_pick_head)&&unlink(cherry_pick_head))+returnerror("Could not remove CHERRY_PICK_HEAD");return0;+}if(file_exists(rebase_path_amend())){structstrbufrev=STRBUF_INIT;
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:55:26
When the last command of an interactive rebase fails, the user needs to
resolve the problem and then continue the interactive rebase. Naturally,
the todo script is empty by then. So let's not complain about that!
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:55:29
When an interactive rebase is interrupted, the user may stage changes
before continuing, and we need to commit those changes in that case.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 39 +++++++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
@@ -1807,6 +1807,41 @@ static int continue_single_pick(void)returnrun_command_v_opt(argv,RUN_GIT_CMD);}+staticintcommit_staged_changes(structreplay_opts*opts)+{+intamend=0;++if(has_unstaged_changes(1))+returnerror(_("Cannot rebase: You have unstaged changes."));+if(!has_uncommitted_changes(0))+return0;++if(file_exists(rebase_path_amend())){+structstrbufrev=STRBUF_INIT;+unsignedcharhead[20],to_amend[20];++if(get_sha1("HEAD",head))+returnerror("Cannot amend non-existing commit");+if(!read_oneliner(&rev,rebase_path_amend(),0))+returnerror("Invalid file: %s",rebase_path_amend());+if(get_sha1_hex(rev.buf,to_amend))+returnerror("Invalid contents: %s",+rebase_path_amend());+if(hashcmp(head,to_amend))+returnerror("\nYou have uncommitted changes in your "+"working tree. Please, commit them\nfirst and "+"then run 'git rebase --continue' again.");++strbuf_release(&rev);+amend=1;+}++if(sequencer_commit(rebase_path_message(),opts,1,1,amend,0))+returnerror("Could not commit staged changes.");+unlink(rebase_path_amend());+return0;+}+intsequencer_continue(structreplay_opts*opts){structtodo_listtodo_list=TODO_LIST_INIT;
@@ -1815,6 +1850,10 @@ int sequencer_continue(struct replay_opts *opts)if(read_and_refresh_cache(opts))return-1;+if(is_rebase_i(opts)){+if(commit_staged_changes(opts))+return-1;+}if(!file_exists(get_todo_path(opts)))returncontinue_single_pick();if(read_populate_opts(opts)||
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:56:01
This is now trivial, as all the building blocks are in place: all we need
to do is to flip the "edit" switch when committing.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:56:04
We already used the same reflog message as the scripted version of rebase
-i when finishing. With this commit, we do that also for all the commands
before that.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 4 ++++
1 file changed, 4 insertions(+)
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:56:06
This makes the code DRYer, with the obvious benefit that we can enhance
the code further in a single place.
We can also reuse the functionality elsewhere by calling this new
function.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 33 ++++++++++++++++++++++++++-------
1 file changed, 26 insertions(+), 7 deletions(-)
@@ -1803,19 +1824,17 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)if(!read_oneliner(&buf,rebase_path_orig_head(),0)||get_sha1_hex(buf.buf,orig))returnerror("Could not read orig-head");-strbuf_addf(&buf,"rebase -i (finish): %s onto ",-head_ref.buf);if(!read_oneliner(&buf,rebase_path_onto(),0))returnerror("Could not read 'onto'");-if(update_ref(buf.buf,head_ref.buf,head,orig,+msg=reflog_message(opts,"finish","%s onto %s",+head_ref.buf,buf.buf);+if(update_ref(msg,head_ref.buf,head,orig,REF_NODEREF,UPDATE_REFS_MSG_ON_ERR))returnerror("Could not update %s",head_ref.buf);-strbuf_reset(&buf);-strbuf_addf(&buf,-"rebase -i (finish): returning to %s",+msg=reflog_message(opts,"finish","returning to %s",head_ref.buf);-if(create_symref("HEAD",head_ref.buf,buf.buf))+if(create_symref("HEAD",head_ref.buf,msg))returnerror("Could not update HEAD to %s",head_ref.buf);strbuf_reset(&buf);
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:56:07
When continuing after a `pick` command failed, we want that commit
to show up in the rewritten-list (and its notes to be rewritten), too.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 11 +++++++++++
1 file changed, 11 insertions(+)
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:56:08
When rebasing commits that have commit notes attached, the interactive
rebase rewrites those notes faithfully at the end. The sequencer must
do this, too, if it wishes to do interactive rebase's job.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
@@ -1813,6 +1873,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)if(is_rebase_i(opts)){structstrbufhead_ref=STRBUF_INIT,buf=STRBUF_INIT;+structstatst;/* Stopped in the middle, as planned? */if(todo_list->current<todo_list->nr)
@@ -1857,6 +1918,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)run_command_v_opt(argv,RUN_GIT_CMD);strbuf_reset(&buf);}+flush_rewritten_pending();+if(!stat(rebase_path_rewritten_list(),&st)&&+st.st_size>0){+structchild_processchild=CHILD_PROCESS_INIT;++child.in=open(rebase_path_rewritten_list(),O_RDONLY);+child.git_cmd=1;+argv_array_push(&child.args,"notes");+argv_array_push(&child.args,"copy");+argv_array_push(&child.args,"--for-rewrite=rebase");+/* we don't care if this copying failed */+run_command(&child);+}+strbuf_release(&buf);strbuf_release(&head_ref);}
@@ -1930,6 +1932,17 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)argv_array_push(&child.args,"--for-rewrite=rebase");/* we don't care if this copying failed */run_command(&child);++if(post_rewrite_hook){+structchild_processhook=CHILD_PROCESS_INIT;++hook.in=open(rebase_path_rewritten_list(),+O_RDONLY);+argv_array_push(&hook.args,post_rewrite_hook);+argv_array_push(&hook.args,"rebase");+/* we don't care if this hook failed */+run_command(&hook);+}}strbuf_release(&buf);
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:56:23
The parsing part of a 'drop' command is almost identical to parsing a
'pick', while the operation is the same as that of a 'noop'.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:56:24
Git's `rebase` command inspects the `rebase.autostash` config setting
to determine whether it should stash any uncommitted changes before
rebasing and re-apply them afterwards.
As we introduce more bits and pieces to let the sequencer act as
interactive rebase's backend, here is the part that adds support for
the autostash feature.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 43 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
@@ -111,6 +111,7 @@ static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head")staticGIT_PATH_FUNC(rebase_path_verbose,"rebase-merge/verbose")staticGIT_PATH_FUNC(rebase_path_head_name,"rebase-merge/head-name")staticGIT_PATH_FUNC(rebase_path_onto,"rebase-merge/onto")+staticGIT_PATH_FUNC(rebase_path_autostash,"rebase-merge/autostash")/* We will introduce the 'interactive rebase' mode later */staticinlineintis_rebase_i(conststructreplay_opts*opts)
@@ -1783,6 +1784,47 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)return-1;}+staticintapply_autostash(structreplay_opts*opts)+{+structstrbufstash_sha1=STRBUF_INIT;+structchild_processchild=CHILD_PROCESS_INIT;+intret=0;++if(!read_oneliner(&stash_sha1,rebase_path_autostash(),1)){+strbuf_release(&stash_sha1);+return0;+}+strbuf_trim(&stash_sha1);++child.git_cmd=1;+argv_array_push(&child.args,"stash");+argv_array_push(&child.args,"apply");+argv_array_push(&child.args,stash_sha1.buf);+if(!run_command(&child))+printf(_("Applied autostash."));+else{+structchild_processstore=CHILD_PROCESS_INIT;++store.git_cmd=1;+argv_array_push(&store.args,"stash");+argv_array_push(&store.args,"store");+argv_array_push(&store.args,"-m");+argv_array_push(&store.args,"autostash");+argv_array_push(&store.args,"-q");+argv_array_push(&store.args,stash_sha1.buf);+if(run_command(&store))+ret=error(_("Cannot store %s"),stash_sha1.buf);+else+printf(_("Applying autostash resulted in conflicts.\n"+"Your changes are safe in the stash.\n"+"You can run \"git stash pop\" or"+" \"git stash drop\" at any time.\n"));+}++strbuf_release(&stash_sha1);+returnret;+}+staticconstchar*reflog_message(structreplay_opts*opts,constchar*sub_action,constchar*fmt,...){
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:56:26
In the upcoming patch, we will support rebase -i's progress
reporting. The progress skips comments but counts 'noop's.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:56:27
The sequencer already has an idea about using different merge
strategies. We just piggy-back on top of that, using rebase -i's
own settings, when running the sequencer in interactive rebase mode.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
@@ -112,6 +112,8 @@ static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose")staticGIT_PATH_FUNC(rebase_path_head_name,"rebase-merge/head-name")staticGIT_PATH_FUNC(rebase_path_onto,"rebase-merge/onto")staticGIT_PATH_FUNC(rebase_path_autostash,"rebase-merge/autostash")+staticGIT_PATH_FUNC(rebase_path_strategy,"rebase-merge/strategy")+staticGIT_PATH_FUNC(rebase_path_strategy_opts,"rebase-merge/strategy_opts")/* We will introduce the 'interactive rebase' mode later */staticinlineintis_rebase_i(conststructreplay_opts*opts)
@@ -1404,6 +1406,26 @@ static int read_populate_opts(struct replay_opts *opts)if(file_exists(rebase_path_verbose()))opts->verbose=1;+if(read_oneliner(&buf,rebase_path_strategy(),0)){+opts->strategy=+sequencer_entrust(opts,+strbuf_detach(&buf,NULL));+if(read_oneliner(&buf,+rebase_path_strategy_opts(),0)){+inti;+opts->xopts_nr=split_cmdline(buf.buf,+&opts->xopts);+for(i=0;i<opts->xopts_nr;i++)+skip_prefix(opts->xopts[i],"--",+&opts->xopts[i]);+if(opts->xopts_nr)+sequencer_entrust(opts,+strbuf_detach(&buf,NULL));+else+strbuf_release(&buf);+}+}+return0;}
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:56:30
The interactive rebase has the very special magic that a cherry-pick
that exits with a status different from 0 and 1 signifies a failure to
even record that a cherry-pick was started.
This can happen e.g. when a fast-forward fails because it would
overwrite untracked files.
In that case, we must reschedule the command that we thought we already
had at least started successfully.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 6 ++++++
1 file changed, 6 insertions(+)
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:56:33
The sequencer already knew how to fast-forward instead of
cherry-picking, if possible.
We want to continue to do this, of course, but in case of the 'reword'
command, we will need to call `git commit` after fast-forwarding.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 25 +++++++++++++++++--------
1 file changed, 17 insertions(+), 8 deletions(-)
@@ -942,11 +942,23 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,elseparent=commit->parents->item;+if(get_message(commit,&msg)!=0)+returnerror(_("Cannot get commit message for %s"),+oid_to_hex(&commit->object.oid));+if(opts->allow_ff&&!is_fixup(command)&&((parent&&!hashcmp(parent->object.oid.hash,head))||-(!parent&&unborn)))-returnfast_forward_to(commit->object.oid.hash,head,unborn,opts);-+(!parent&&unborn))){+if(is_rebase_i(opts))+write_author_script(msg.message);+res|=fast_forward_to(commit->object.oid.hash,head,unborn,+opts);+if(res||command!=TODO_REWORD)+gotoleave;+edit=amend=1;+msg_file=NULL;+gotofast_forward_edit;+}if(parent&&parse_commit(parent)<0)/* TRANSLATORS: The first %s will be "revert" or"cherry-pick",thesecond%saSHA1*/
@@ -954,10 +966,6 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,command_to_string(command),oid_to_hex(&parent->object.oid));-if(get_message(commit,&msg)!=0)-returnerror(_("Cannot get commit message for %s"),-oid_to_hex(&commit->object.oid));-/**"commit"isanexistingcommit.Wewouldwanttoapply*thedifferenceitintroducessinceitsfirstparent"prev"
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:56:50
This will be needed to hide the output of `git commit` when the
sequencer handles an interactive rebase's script.
Signed-off-by: Johannes Schindelin <redacted>
---
run-command.c | 23 +++++++++++++++++++++++
run-command.h | 1 +
2 files changed, 24 insertions(+)
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:56:53
This is the behavior of the shell script version of the interactive
rebase, by using the `output` function defined in `git-rebase.sh`.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:56:54
This is the same behavior as known from `git rebase -i`.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
@@ -1346,8 +1346,12 @@ static int read_populate_todo(struct todo_list *todo_list,close(fd);res=parse_insn_buffer(todo_list->buf.buf,todo_list);-if(res)+if(res){+if(is_rebase_i(opts))+returnerror("Please fix this using "+"'git rebase --edit-todo'.");returnerror(_("Unusable instruction sheet: %s"),todo_file);+}if(!todo_list->nr&&(!is_rebase_i(opts)||!file_exists(rebase_path_done())))returnerror(_("No commits parsed."));
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:57:03
The interactive rebase keeps the user informed about its progress.
If the sequencer wants to do the grunt work of the interactive
rebase, it also needs to show that progress.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:57:06
For the benefit of e.g. the shell prompt, the interactive rebase not
only displays the progress for the user to see, but also writes it into
the msgnum/end files in the state directory.
Teach the sequencer this new trick.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 30 +++++++++++++++++++++++++++---
1 file changed, 27 insertions(+), 3 deletions(-)
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:57:07
This is the behavior of the shell script version of the interactive
rebase, by using the `output` function defined in `git-rebase.sh`.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 4 ++++
1 file changed, 4 insertions(+)
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:57:08
The shell script version of the interactive rebase has a very specific
final message. Teach the sequencer to print the same.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 3 +++
1 file changed, 3 insertions(+)
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:57:14
Just like the interactive rebase, we want to leave a 'patch' file for
further inspection by the user (even if we never tried to actually apply
that patch, since we're cherry-picking instead).
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 3 +++
1 file changed, 3 insertions(+)
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:57:53
In the interactive rebase, commands that were successfully processed are
not simply discarded, but appended to the 'done' file instead. This is
used e.g. to display the current state to the user in the output of
`git status` or the progress.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
@@ -1272,6 +1278,20 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)todo_path,strerror(errno));if(commit_lock_file(&todo_lock)<0)returnerror(_("Error wrapping up %s."),todo_path);++if(is_rebase_i(opts)){+constchar*done_path=rebase_path_done();+intfd=open(done_path,O_CREAT|O_WRONLY|O_APPEND,0666);+intprev_offset=!next?0:+todo_list->items[next-1].offset_in_buf;++if(offset>prev_offset&&write_in_full(fd,+todo_list->buf.buf+prev_offset,+offset-prev_offset)<0)+returnerror(_("Could not write to %s (%s)"),+done_path,strerror(errno));+close(fd);+}return0;}
From: Johannes Schindelin <hidden> Date: 2016-08-31 08:57:59
An interactive rebase operates on a detached HEAD (to keep the reflog
of the original branch relatively clean), and updates the branch only
at the end.
Now that the sequencer learns to perform interactive rebases, it also
needs to learn the trick to update the branch before removing the
directory containing the state of the interactive rebase.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 32 +++++++++++++++++++++++++++++++-
1 file changed, 31 insertions(+), 1 deletion(-)
@@ -100,6 +100,8 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha")staticGIT_PATH_FUNC(rebase_path_gpg_sign_opt,"rebase-merge/gpg_sign_opt")staticGIT_PATH_FUNC(rebase_path_orig_head,"rebase-merge/orig-head")staticGIT_PATH_FUNC(rebase_path_verbose,"rebase-merge/verbose")+staticGIT_PATH_FUNC(rebase_path_head_name,"rebase-merge/head-name")+staticGIT_PATH_FUNC(rebase_path_onto,"rebase-merge/onto")/* We will introduce the 'interactive rebase' mode later */staticinlineintis_rebase_i(conststructreplay_opts*opts)
@@ -1769,12 +1771,39 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)}if(is_rebase_i(opts)){-structstrbufbuf=STRBUF_INIT;+structstrbufhead_ref=STRBUF_INIT,buf=STRBUF_INIT;/* Stopped in the middle, as planned? */if(todo_list->current<todo_list->nr)return0;+if(read_oneliner(&head_ref,rebase_path_head_name(),0)&&+starts_with(head_ref.buf,"refs/")){+unsignedcharhead[20],orig[20];++if(get_sha1("HEAD",head))+returnerror("Cannot read HEAD");+if(!read_oneliner(&buf,rebase_path_orig_head(),0)||+get_sha1_hex(buf.buf,orig))+returnerror("Could not read orig-head");+strbuf_addf(&buf,"rebase -i (finish): %s onto ",+head_ref.buf);+if(!read_oneliner(&buf,rebase_path_onto(),0))+returnerror("Could not read 'onto'");+if(update_ref(buf.buf,head_ref.buf,head,orig,+REF_NODEREF,UPDATE_REFS_MSG_ON_ERR))+returnerror("Could not update %s",+head_ref.buf);+strbuf_reset(&buf);+strbuf_addf(&buf,+"rebase -i (finish): returning to %s",+head_ref.buf);+if(create_symref("HEAD",head_ref.buf,buf.buf))+returnerror("Could not update HEAD to %s",+head_ref.buf);+strbuf_reset(&buf);+}+if(opts->verbose){constchar*argv[]={"diff-tree","--stat",NULL,NULL
From: Dennis Kaarsemaker <hidden> Date: 2016-08-31 18:39:39
On wo, 2016-08-31 at 10:54 +0200, Johannes Schindelin wrote:
In the interactive rebase, commands that were successfully processed are
not simply discarded, but appended to the 'done' file instead. This is
used e.g. to display the current state to the user in the output of
`git status` or the progress.
Wouldn't it make more sense to have this patch before the ones that
implement the actual rebase commands?
Hmm, and after reading more of this series, I think the same applies to
some other patches too, e.g. 08/34 and 14/34, so I'm probably missing
something. So before I make a fool of myself and suggest that the
implementation of the actual commands should come at the end, maybe you
could tell me what I'm missing :)
D.
From: Dennis Kaarsemaker <hidden> Date: 2016-08-31 18:39:47
On wo, 2016-08-31 at 10:55 +0200, Johannes Schindelin wrote:
Just like the interactive rebase, we want to leave a 'patch' file for
further inspection by the user (even if we never tried to actually apply
that patch, since we're cherry-picking instead).
Signed-off-by: Johannes Schindelin <redacted>
This commit message confuses me. Did you mean s/Just like the/When
doing an/?
D.
It sounds wrong to have a function named is_fixup return true when the
command isn't a fixup but a squash. Maybe name it
changes_previous_commit or something?
+static const char *nth_for_number(int n)
+{
+ int n1 = n % 10, n10 = n % 100;
+
+ if (n1 == 1 && n10 != 11)
+ return "st";
+ if (n1 == 2 && n10 != 12)
+ return "nd";
+ if (n1 == 3 && n10 != 13)
+ return "rd";
+ return "th";
+}
8---
+ if (command == TODO_SQUASH) {
+ unlink(rebase_path_fixup_msg());
+ strbuf_addf(&buf, "\n%c This is the %d%s commit message:\n\n%s",
+ comment_line_char,
+ count, nth_for_number(count), body);
+ }
+ else if (command == TODO_FIXUP) {
+ strbuf_addf(&buf,
+ "\n%c The %d%s commit message will be skipped:\n\n",
+ comment_line_char, count, nth_for_number(count));
+ strbuf_add_commented_lines(&buf, body, strlen(body));
+ }
This way of handling numbers is not translatable, and I really think we
should mark these strings for translation, like they are in the .sh
version.
D.
From: Dennis Kaarsemaker <hidden> Date: 2016-09-01 08:33:53
On wo, 2016-08-31 at 10:56 +0200, Johannes Schindelin wrote:
quoted hunk
The interactive rebase keeps the user informed about its progress.
If the sequencer wants to do the grunt work of the interactive
rebase, it also needs to show that progress.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
if (save_todo(todo_list, opts))
return -1;
if (is_rebase_i(opts)) {
+ if (item->command != TODO_COMMENT)
+ fprintf(stderr, "Rebasing (%d/%d)%s",
+ ++(todo_list->done_nr),
+ todo_list->total_nr,
+ opts->verbose ? "\n" : "\r");
unlink(rebase_path_message());
unlink(rebase_path_author_script());
unlink(rebase_path_stopped_sha());
(picking a random commit that shows this 'symptom')
You're sprinking a lot of is_rebase_i's around sequencer.c to make sure
there are no changes in behaviour. I wonder if the right balance has
been struck between 'no changes in behaviour' and 'common behaviour'.
For instance, in this case, maybe it would be a better idea for non-
rebase uses of the sequencer to also show progress.
D.
(picking a random commit that touches this enum)
In a few places you now make comparisons like "< TODO_NOOP", so I think
it would be good to have a comment near the definition of this enum
that says that ordering matters and why, so people don't attempt to add
a new TODO_FOOBAR at the end.
D.
From: Johannes Schindelin <hidden> Date: 2016-09-01 08:53:06
Hi Dennis,
On Wed, 31 Aug 2016, Dennis Kaarsemaker wrote:
On wo, 2016-08-31 at 10:54 +0200, Johannes Schindelin wrote:
quoted
In the interactive rebase, commands that were successfully processed are
not simply discarded, but appended to the 'done' file instead. This is
used e.g. to display the current state to the user in the output of
`git status` or the progress.
Wouldn't it make more sense to have this patch before the ones that
implement the actual rebase commands?
I waffled about the order so many times that I don't know anymore. The
thing is, while the sequencer is taught incrementally to understand all of
the rebase -i functionality, rebase -i itself is not touched, on purpose.
In the case of the "done" file, my thoughts were: the commands do not need
this file *at all*. In fact, if we did not write the "done" file at all,
the only two types of test failures in the test suite would be 1) git
status' output and 2) the prompt testing for the progress.
So you see, functionally, the "done" file is only relevant to the progress
part of the patch series.
As such, I'd rather keep this patch in the current place, just before
introducing the progress.
Hmm, and after reading more of this series, I think the same applies to
some other patches too, e.g. 08/34 and 14/34, so I'm probably missing
something. So before I make a fool of myself and suggest that the
implementation of the actual commands should come at the end, maybe you
could tell me what I'm missing :)
No, no, don't hesitate to suggest reorderings. I am really thankful for
the discussion we are having, so that the outcome is better than what I
have right now. If the outcome would be the very same patches, but with
more confidence, it would still be better than what I have right now ;-)
Ciao,
Dscho
From: Johannes Schindelin <hidden> Date: 2016-09-01 13:04:01
Hi Dennis,
On Wed, 31 Aug 2016, Dennis Kaarsemaker wrote:
On wo, 2016-08-31 at 10:55 +0200, Johannes Schindelin wrote:
quoted
Just like the interactive rebase, we want to leave a 'patch' file for
further inspection by the user (even if we never tried to actually apply
that patch, since we're cherry-picking instead).
Signed-off-by: Johannes Schindelin <redacted>
This commit message confuses me. Did you mean s/Just like the/When
doing an/?
What I meant is this: when calling `git rebase -i` right now, i.e. before
any of my rebase--helper work, a failing rebase will leave a `patch` file
in the `.git/rebase-merge/` directory. Since the sequencer is in the
process of learning how to do an interactive rebase, it needs to learn the
same trick.
But I guess that your suggested edit makes things much clearer.
Thanks,
Dscho
From: Johannes Schindelin <hidden> Date: 2016-09-01 14:58:00
Hi Dennis,
On Thu, 1 Sep 2016, Dennis Kaarsemaker wrote:
On wo, 2016-08-31 at 10:55 +0200, Johannes Schindelin wrote:
quoted
+ if (!stat(rebase_path_rewritten_list(), &st) &&
+ st.st_size > 0) {
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ child.in = open(rebase_path_rewritten_list(), O_RDONLY);
+ child.git_cmd = 1;
+ argv_array_push(&child.args, "notes");
+ argv_array_push(&child.args, "copy");
+ argv_array_push(&child.args, "--for-rewrite=rebase");
+ /* we don't care if this copying failed */
+ run_command(&child);
+ }
I know this is a strict port of git-rebase--interactive.sh, but
shouldn't we at least warn the user that the copy failed?
At this point, I want to have as faithful a conversion as possible (except
that I do not care for the speed of git-rebase--interactive.sh, of
course).
Besides, I am fairly certain that a failure will result in an error
message. We just do not act on the exit value.
Ciao,
Dscho
It sounds wrong to have a function named is_fixup return true when the
command isn't a fixup but a squash. Maybe name it
changes_previous_commit or something?
I can see how that may sound confusing, unless you understand that a
squash is a fixup that lets the user edit the commit message, too. So
essentially squash = fixup + edit, if you will.
Maybe the name is more appropriate in that light?
quoted
+static const char *nth_for_number(int n)
+{
+ int n1 = n % 10, n10 = n % 100;
+
+ if (n1 == 1 && n10 != 11)
+ return "st";
+ if (n1 == 2 && n10 != 12)
+ return "nd";
+ if (n1 == 3 && n10 != 13)
+ return "rd";
+ return "th";
+}
quoted
8---
quoted
+ if (command == TODO_SQUASH) {
+ unlink(rebase_path_fixup_msg());
+ strbuf_addf(&buf, "\n%c This is the %d%s commit message:\n\n%s",
+ comment_line_char,
+ count, nth_for_number(count), body);
+ }
+ else if (command == TODO_FIXUP) {
+ strbuf_addf(&buf,
+ "\n%c The %d%s commit message will be skipped:\n\n",
+ comment_line_char, count, nth_for_number(count));
+ strbuf_add_commented_lines(&buf, body, strlen(body));
+ }
This way of handling numbers is not translatable, and I really think we
should mark these strings for translation, like they are in the .sh
version.
Ah, this is the risk of working on something as big as rebase--helper.
Back when I started with it, the relevant code in git-rebase--interactive
read like this:
nth_string () {
case "$1" in
*1[0-9]|*[04-9]) echo "$1"th;;
*1) echo "$1"st;;
*2) echo "$1"nd;;
*3) echo "$1"rd;;
esac
}
I merely did a faithful translation of that...
Now, I see that git-rebase--interactive was switched to use eval_gettext,
which in turn is handled in git-sh-i18n whose code is quite convoluted. In
the absence of gettext, it uses git-sh-i18n--envsubst, which has no C API
whatsoever.
And I see that the beautiful ordinal computation was given up in favor of
a lousy "#1", "#2", "#3", etc (it used to be "1st", "2nd", "3rd" etc).
In any case, translation is not my main concern until v2.10.0, so I'll
take care of this after that release.
Ciao,
Dscho
From: Johannes Schindelin <hidden> Date: 2016-09-01 15:19:47
Hi Dennis,
On Thu, 1 Sep 2016, Dennis Kaarsemaker wrote:
On wo, 2016-08-31 at 10:56 +0200, Johannes Schindelin wrote:
quoted
The interactive rebase keeps the user informed about its progress.
If the sequencer wants to do the grunt work of the interactive
rebase, it also needs to show that progress.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
if (save_todo(todo_list, opts))
return -1;
if (is_rebase_i(opts)) {
+ if (item->command != TODO_COMMENT)
+ fprintf(stderr, "Rebasing (%d/%d)%s",
+ ++(todo_list->done_nr),
+ todo_list->total_nr,
+ opts->verbose ? "\n" : "\r");
unlink(rebase_path_message());
unlink(rebase_path_author_script());
unlink(rebase_path_stopped_sha());
(picking a random commit that shows this 'symptom')
You're sprinking a lot of is_rebase_i's around sequencer.c to make sure
there are no changes in behaviour. I wonder if the right balance has
been struck between 'no changes in behaviour' and 'common behaviour'.
For instance, in this case, maybe it would be a better idea for non-
rebase uses of the sequencer to also show progress.
Actually, adding progress would make for a fine add-on patch series. I
still would like to have as faithful a conversion as possible, and
everything else can come after that.
This strategy has a couple of advantages:
- we can concentrate on correctness for now,
- I get something to show for my time with Git for Windows v2.10.0, and
- the add-on patches do not have to be done by *me* ;-)
Ciao,
Dscho
(picking a random commit that touches this enum)
In a few places you now make comparisons like "< TODO_NOOP", so I think
it would be good to have a comment near the definition of this enum
that says that ordering matters and why, so people don't attempt to add
a new TODO_FOOBAR at the end.
True.
It does not seem that we have a precedent for that. The closest is what I
had in an early iteration of the fsck message IDs, and subsequently things
were refactored so that it is not the order, but a flag, that determines
what the command does.
Not sure how to do this elegantly. Maybe like this?
enum todo_command {
TODO_PICK_COMMANDS = 0,
TODO_PICK = TODO_PICK_COMMANDS,
TODO_SQUASH,
TODO_NON_PICK_COMMANDS,
TODO_EXEC = TODO_NON_PICK_COMMANDS,
TODO_NOOP_COMMANDS,
TODO_NOOP = TODO_NOOP_COMMANDS,
TODO_DROP
TODO_DROP,
TODO_LAST_COMMAND,
TODO_COMMENT = TODO_LAST_COMMAND
};
But that is so god-awful to read.
Still unsure,
Dscho
(picking a random commit that touches this enum)
In a few places you now make comparisons like "< TODO_NOOP", so I
think
it would be good to have a comment near the definition of this enum
that says that ordering matters and why, so people don't attempt to
add
a new TODO_FOOBAR at the end.
True.
It does not seem that we have a precedent for that. The closest is
what I
had in an early iteration of the fsck message IDs, and subsequently
things
were refactored so that it is not the order, but a flag, that
determines
what the command does.
Not sure how to do this elegantly. Maybe like this?
enum todo_command {
TODO_PICK_COMMANDS = 0,
TODO_PICK = TODO_PICK_COMMANDS,
TODO_SQUASH,
TODO_NON_PICK_COMMANDS,
TODO_EXEC = TODO_NON_PICK_COMMANDS,
TODO_NOOP_COMMANDS,
TODO_NOOP = TODO_NOOP_COMMANDS,
TODO_DROP
TODO_DROP,
TODO_LAST_COMMAND,
TODO_COMMENT = TODO_LAST_COMMAND
};
But that is so god-awful to read.
Agreed, that sure is awful.
How about something like
/*
* Note that ordering matters in this enum. Not only must it match the
* mapping below, it is also divided into several sections that matter.
* When adding new commands, make sure you add it in the right section.
*/
enum todo_command {
/* All commands that handle commits */
TODO_PICK,
...
/* All commands that do something else than pick */
TODO_EXEC,
...
/* All commands that do nothing but are counted for reporting progress */
TODO_NOOP,
...
/* Comments, which are not counted
TODO_COMMENT
}
It sounds wrong to have a function named is_fixup return true when
the
command isn't a fixup but a squash. Maybe name it
changes_previous_commit or something?
I can see how that may sound confusing, unless you understand that a
squash is a fixup that lets the user edit the commit message, too. So
essentially squash = fixup + edit, if you will.
Maybe the name is more appropriate in that light?
Kinda makes sense. It's not how I use fixup/squash as a user of rebase
-i though. But we can't go there, that's bikeshed country :)
quoted
quoted
+static const char *nth_for_number(int n)
+{
+ int n1 = n % 10, n10 = n % 100;
+
+ if (n1 == 1 && n10 != 11)
+ return "st";
+ if (n1 == 2 && n10 != 12)
+ return "nd";
+ if (n1 == 3 && n10 != 13)
+ return "rd";
+ return "th";
+}
8---
+ if (command == TODO_SQUASH) {
+ unlink(rebase_path_fixup_msg());
+ strbuf_addf(&buf, "\n%c This is the %d%s commit
message:\n\n%s",
+ comment_line_char,
+ count, nth_for_number(count), body);
+ }
+ else if (command == TODO_FIXUP) {
+ strbuf_addf(&buf,
+ "\n%c The %d%s commit message will be
skipped:\n\n",
+ comment_line_char, count,
nth_for_number(count));
+ strbuf_add_commented_lines(&buf, body,
strlen(body));
+ }
This way of handling numbers is not translatable, and I really
think we
should mark these strings for translation, like they are in the .sh
version.
Ah, this is the risk of working on something as big as rebase
--helper.
Back when I started with it, the relevant code in git-rebase
--interactive
read like this:
nth_string () {
case "$1" in
*1[0-9]|*[04-9]) echo "$1"th;;
*1) echo "$1"st;;
*2) echo "$1"nd;;
*3) echo "$1"rd;;
esac
}
I merely did a faithful translation of that...
Now, I see that git-rebase--interactive was switched to use
eval_gettext,
which in turn is handled in git-sh-i18n whose code is quite
convoluted. In
the absence of gettext, it uses git-sh-i18n--envsubst, which has no C
API
whatsoever.
And I see that the beautiful ordinal computation was given up in
favor of
a lousy "#1", "#2", "#3", etc (it used to be "1st", "2nd", "3rd"
etc).
In any case, translation is not my main concern until v2.10.0, so
I'll
take care of this after that release.
Hmm, not sure if I agree with that. I'd see it as a regression to lose
the i18n there.
D.
From: Johannes Schindelin <hidden> Date: 2016-09-02 07:14:06
Hi Dennis,
On Thu, 1 Sep 2016, Dennis Kaarsemaker wrote:
On do, 2016-09-01 at 17:17 +0200, Johannes Schindelin wrote:
quoted
And I see that the beautiful ordinal computation was given up in favor
of a lousy "#1", "#2", "#3", etc (it used to be "1st", "2nd", "3rd"
etc).
In any case, translation is not my main concern until v2.10.0, so I'll
take care of this after that release.
Hmm, not sure if I agree with that. I'd see it as a regression to lose
the i18n there.
As Git for Windows does not ship with translations (for multiple reasons),
it would not be a regression.
Having said that, I see that having a different text than the current
rebase -i can be seen as a regression, so I changed that.
Thanks for the review!
Dscho
From: Johannes Schindelin <hidden> Date: 2016-09-02 07:32:20
Hi Dennis,
On Thu, 1 Sep 2016, Dennis Kaarsemaker wrote:
/*
* Note that ordering matters in this enum. Not only must it match the
* mapping below, it is also divided into several sections that matter.
* When adding new commands, make sure you add it in the right section.
*/
enum todo_command {
/* All commands that handle commits */
TODO_PICK,
...
/* All commands that do something else than pick */
TODO_EXEC,
...
/* All commands that do nothing but are counted for reporting progress */
TODO_NOOP,
...
/* Comments, which are not counted
TODO_COMMENT
}
From: Dennis Kaarsemaker <hidden> Date: 2016-09-02 12:07:08
On vr, 2016-09-02 at 09:13 +0200, Johannes Schindelin wrote:
As Git for Windows does not ship with translations (for multiple
reasons), it would not be a regression.
I'm confused, how does "git for windows does not ship with
translations" translate to "this is not a regression"? Is this patch
series only meant to be for git for windows and not go into git.git
itself?
D.
From: Johannes Schindelin <hidden> Date: 2016-09-02 14:22:41
Hi Dennis,
On Fri, 2 Sep 2016, Dennis Kaarsemaker wrote:
On vr, 2016-09-02 at 09:13 +0200, Johannes Schindelin wrote:
quoted
As Git for Windows does not ship with translations (for multiple
reasons), it would not be a regression.
I'm confused, how does "git for windows does not ship with
translations" translate to "this is not a regression"? Is this patch
series only meant to be for git for windows and not go into git.git
itself?
Oh, I thought I had clarified my plan... The timeline is:
- I submit the remaining rebase--helper patch series for review (last week
and this one),
- I publish a preview of Git for Windows v2.10.0 that already uses these
patches (done: https://github.com/git-for-windows/git/releases/tag/v2.9.3.windows.3)
- once upstream Git v2.10.0 is released (possibly today, after my work
hours), I perform a final "Git garden shears" run (read: rebase Git for
Windows' patches, retaining the branch structure) on top of v2.10.0 and
release Git for Windows v2.10.0, tagged as v2.10.0.windows.1 in
https://github.com/git-for-windows/git (due to time zone differences
relative to Junio, the most likely time for this release would be
some time around noon tomorrow, given that the release engineering takes
roughly 2-4 hours, running unsupervised for the most part).
- as far as Git for Windows is concerned, l10n is not really an issue yet:
the installer is released without any localizations.
- After releasing Git for Windows v2.10.0, I will pay a lot of attention
to feedback. Not only to hear a lot of praise, but also to catch any
possible regressions. Not that I expect anything dramatic to happen
because I really tested this as thoroughly as I can: not a single one of
my interactive rebases since mid May has been performed without
involving the rebase--helper. In the three cases where I *did* find a
regression, I solved it immediately, of course.
- After releasing Git for Windows v2.10.0, I will have a nice beer. Or
three.
- Then I will leisurely try to address the l10n issues.
- Then, I will send out the current iterations of the patch series that
are in flight.
- I have the entire week to address concerns with Git for Windows as well
as with the patch series (where the former takes precedence, of course).
- The second half of September, I will relax from this marathon that
started in early February. Meaning: I will be mostly offline.
I hope this clarifies why I am not so concerned about some issues such as
translation, or commit messages, or grammar, and more so about others,
such as incorrect code.
Ciao,
Dscho
From: Dennis Kaarsemaker <hidden> Date: 2016-09-02 17:04:43
On vr, 2016-09-02 at 16:22 +0200, Johannes Schindelin wrote:
I hope this clarifies why I am not so concerned about some issues
such as translation, or commit messages, or grammar, and more so
about others, such as incorrect code.
From: Kevin Daudt <hidden> Date: 2016-09-02 21:13:38
On Wed, Aug 31, 2016 at 10:54:02AM +0200, Johannes Schindelin wrote:
quoted hunk
@@ -43,16 +51,20 @@ static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") /* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) {- return 0;+ return opts->action == REPLAY_INTERACTIVE_REBASE; } static const char *get_dir(const struct replay_opts *opts) {+ if (is_rebase_i(opts))+ return rebase_path(); return git_path_seq_dir(); } static const char *get_todo_path(const struct replay_opts *opts) {+ if (is_rebase_i(opts))+ return rebase_path_todo(); return git_path_todo_file(); }
This patch fails to apply for me because function is_rebase_i has never
been introduced before (no record of it anywhere). Currently, only
IS_REBASE_I macro is present.
From: Johannes Schindelin <hidden> Date: 2016-09-03 06:57:59
Hi Kevin,
On Fri, 2 Sep 2016, Kevin Daudt wrote:
On Wed, Aug 31, 2016 at 10:54:02AM +0200, Johannes Schindelin wrote:
quoted
@@ -43,16 +51,20 @@ static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") /* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) {- return 0;+ return opts->action == REPLAY_INTERACTIVE_REBASE; } static const char *get_dir(const struct replay_opts *opts) {+ if (is_rebase_i(opts))+ return rebase_path(); return git_path_seq_dir(); } static const char *get_todo_path(const struct replay_opts *opts) {+ if (is_rebase_i(opts))+ return rebase_path_todo(); return git_path_todo_file(); }
This patch fails to apply for me because function is_rebase_i has never
been introduced before (no record of it anywhere). Currently, only
IS_REBASE_I macro is present.
I did not send out a new iteration of the prepare-sequencer patch series
(mostly because I wanted reviewers to look at the later patch series,
rather than re-review a 2nd iteration of something they already saw).
But I did address the concerns mentioned in the review already, of course,
because part of the reason to show those patch series was to get valuable
feedback before including the work in Git for Windows v2.10.0.
You can find the current iteration of the prepare-sequencer here:
https://github.com/dscho/git/compare/libify-sequencer...prepare-sequencer
Please note that I will most likely try to address some l10n concerns
before sending out the next iteration of the patch series.
The patch introducing is_rebase_i() (and no longer IS_REBASE_I()) is:
https://github.com/dscho/git/commit/76d272020bb72618957308f06083c807efe59aca
If you want to have the latest iteration of the entire patch thicket, just
`git fetch https://github.com/dscho/git interactive-rebase`. I update that
more frequently than I send out updates via mail.
Ciao,
Johannes
From: Johannes Schindelin <hidden> Date: 2016-12-13 15:31:57
The 'exec' command is a little special among rebase -i's commands, as it
does *not* have a SHA-1 as first parameter. Instead, everything after the
`exec` command is treated as command-line to execute.
Let's reuse the arg/arg_len fields of the todo_item structure (which hold
the oneline for pick/edit commands) to point to the command-line.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 59 insertions(+)
@@ -664,6 +665,8 @@ enum todo_command {TODO_PICK=0,TODO_REVERT,TODO_EDIT,+/* commands that do something else than handling a single commit */+TODO_EXEC,/* commands that do nothing but are counted for reporting progress */TODO_NOOP};
@@ -1396,6 +1406,47 @@ static int error_with_patch(struct commit *commit,returnexit_code;}+staticintdo_exec(constchar*command_line)+{+constchar*child_argv[]={NULL,NULL};+intdirty,status;++fprintf(stderr,"Executing: %s\n",command_line);+child_argv[0]=command_line;+status=run_command_v_opt(child_argv,RUN_USING_SHELL);++/* force re-reading of the cache */+if(discard_cache()<0||read_cache()<0)+returnerror(_("could not read index"));++dirty=require_clean_work_tree("rebase",NULL,1,1);++if(status){+warning(_("execution failed: %s\n%s"+"You can fix the problem, and then run\n"+"\n"+" git rebase --continue\n"+"\n"),+command_line,+dirty?N_("and made changes to the index and/or the "+"working tree\n"):"");+if(status==127)+/* command not found */+status=1;+}+elseif(dirty){+warning(_("execution succeeded: %s\nbut "+"left changes to the index and/or the working tree\n"+"Commit or stash your changes, and then run\n"+"\n"+" git rebase --continue\n"+"\n"),command_line);+status=1;+}++returnstatus;+}+staticintpick_commits(structtodo_list*todo_list,structreplay_opts*opts){intres=0;
From: Johannes Schindelin <hidden> Date: 2016-12-13 15:32:00
This patch is a straight-forward reimplementation of the `edit`
operation of the interactive rebase command.
Well, not *quite* straight-forward: when stopping, the `edit`
command wants to write the `patch` file (which is not only the
patch, but includes the commit message and author information). To
that end, this patch requires the earlier work that taught the
log-tree machinery to respect the `file` setting of
rev_info->diffopt to write to a file stream different than stdout.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 114 insertions(+), 2 deletions(-)
@@ -43,6 +44,20 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")*/staticGIT_PATH_FUNC(rebase_path_author_script,"rebase-merge/author-script")/*+*Whenan"edit"rebasecommandisbeingprocessed,theSHA1ofthe+*committobeeditedisrecordedinthisfile.When"git rebase+*--continue" is executed, if there are any staged changes then they+*willbeamendedtotheHEADcommit,butonlyprovidedtheHEAD+*commitisstillthecommittobeedited.Whenanyotherrebase+*commandisprocessed,thisfileisdeleted.+*/+staticGIT_PATH_FUNC(rebase_path_amend,"rebase-merge/amend")+/*+*Whenwestopatagivenpatchviathe"edit"command,thisfilecontains+*thelongcommitnameofthecorrespondingpatch.+*/+staticGIT_PATH_FUNC(rebase_path_stopped_sha,"rebase-merge/stopped-sha")+/**Thefollowingfilesarewrittenbygit-rebasejustafterparsingthe*command-line(andareonlyconsumed,notmodified,bythesequencer).*/
@@ -648,6 +663,7 @@ enum todo_command {/* commands that handle commits */TODO_PICK=0,TODO_REVERT,+TODO_EDIT,/* commands that do nothing but are counted for reporting progress */TODO_NOOP};
@@ -1301,9 +1318,87 @@ static int save_opts(struct replay_opts *opts)returnres;}+staticintmake_patch(structcommit*commit,structreplay_opts*opts)+{+structstrbufbuf=STRBUF_INIT;+structrev_infolog_tree_opt;+constchar*commit_buffer=get_commit_buffer(commit,NULL),*subject,*p;+intres=0;++p=short_commit_name(commit);+if(write_message(p,strlen(p),rebase_path_stopped_sha(),1)<0)+return-1;++strbuf_addf(&buf,"%s/patch",get_dir(opts));+memset(&log_tree_opt,0,sizeof(log_tree_opt));+init_revisions(&log_tree_opt,NULL);+log_tree_opt.abbrev=0;+log_tree_opt.diff=1;+log_tree_opt.diffopt.output_format=DIFF_FORMAT_PATCH;+log_tree_opt.disable_stdin=1;+log_tree_opt.no_commit_id=1;+log_tree_opt.diffopt.file=fopen(buf.buf,"w");+log_tree_opt.diffopt.use_color=GIT_COLOR_NEVER;+if(!log_tree_opt.diffopt.file)+res|=error_errno(_("could not open '%s'"),buf.buf);+else{+res|=log_tree_commit(&log_tree_opt,commit);+fclose(log_tree_opt.diffopt.file);+}+strbuf_reset(&buf);++strbuf_addf(&buf,"%s/message",get_dir(opts));+if(!file_exists(buf.buf)){+find_commit_subject(commit_buffer,&subject);+res|=write_message(subject,strlen(subject),buf.buf,1);+unuse_commit_buffer(commit,commit_buffer);+}+strbuf_release(&buf);++returnres;+}++staticintintend_to_amend(void)+{+unsignedcharhead[20];+char*p;++if(get_sha1("HEAD",head))+returnerror(_("cannot read HEAD"));++p=sha1_to_hex(head);+returnwrite_message(p,strlen(p),rebase_path_amend(),1);+}++staticinterror_with_patch(structcommit*commit,+constchar*subject,intsubject_len,+structreplay_opts*opts,intexit_code,intto_amend)+{+if(make_patch(commit,opts))+return-1;++if(to_amend){+if(intend_to_amend())+return-1;++fprintf(stderr,"You can amend the commit now, with\n"+"\n"+" git commit --amend %s\n"+"\n"+"Once you are satisfied with your changes, run\n"+"\n"+" git rebase --continue\n",gpg_sign_opt_quoted(opts));+}+elseif(exit_code)+fprintf(stderr,"Could not apply %s... %.*s\n",+short_commit_name(commit),subject_len,subject);++returnexit_code;+}+staticintpick_commits(structtodo_list*todo_list,structreplay_opts*opts){-intres;+intres=0;setenv(GIT_REFLOG_ACTION,action_name(opts),0);if(opts->allow_ff)
@@ -1316,9 +1411,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)structtodo_item*item=todo_list->items+todo_list->current;if(save_todo(todo_list,opts))return-1;-if(item->command<=TODO_REVERT)+if(item->command<=TODO_EDIT){res=do_pick_commit(item->command,item->commit,opts);+if(item->command==TODO_EDIT){+structcommit*commit=item->commit;+if(!res)+warning(_("stopped at %s... %.*s"),+short_commit_name(commit),+item->arg_len,item->arg);+returnerror_with_patch(commit,+item->arg,item->arg_len,opts,res,+!res);+}+}elseif(item->command!=TODO_NOOP)returnerror(_("unknown command %d"),item->command);
@@ -1327,6 +1433,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)returnres;}+if(is_rebase_i(opts)){+/* Stopped in the middle, as planned? */+if(todo_list->current<todo_list->nr)+return0;+}+/**Sequenceofpicksfinishedsuccessfully;cleanupby*removingthe.git/sequencerdirectory
From: Johannes Schindelin <hidden> Date: 2016-12-13 15:32:03
When a cherry-pick continues without a "todo script", the intention is
simply to pick a single commit.
However, when an interactive rebase is continued without a "todo
script", it means that the last command has been completed and that we
now need to clean up.
This commit guards the revert/cherry-pick specific steps so that they
are not executed in rebase -i mode.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 25 ++++++++++++++-----------
1 file changed, 14 insertions(+), 11 deletions(-)
@@ -1882,25 +1882,28 @@ int sequencer_continue(struct replay_opts *opts)if(commit_staged_changes(opts))return-1;}-if(!file_exists(get_todo_path(opts)))+elseif(!file_exists(get_todo_path(opts)))returncontinue_single_pick();if(read_populate_opts(opts))return-1;if((res=read_populate_todo(&todo_list,opts)))gotorelease_todo_list;-/* Verify that the conflict has been resolved */-if(file_exists(git_path_cherry_pick_head())||-file_exists(git_path_revert_head())){-res=continue_single_pick();-if(res)+if(!is_rebase_i(opts)){+/* Verify that the conflict has been resolved */+if(file_exists(git_path_cherry_pick_head())||+file_exists(git_path_revert_head())){+res=continue_single_pick();+if(res)+gotorelease_todo_list;+}+if(index_differs_from("HEAD",0,0)){+res=error_dirty_index(opts);gotorelease_todo_list;+}+todo_list.current++;}-if(index_differs_from("HEAD",0,0)){-res=error_dirty_index(opts);-gotorelease_todo_list;-}-todo_list.current++;+res=pick_commits(&todo_list,opts);release_todo_list:todo_list_release(&todo_list);
From: Johannes Schindelin <hidden> Date: 2016-12-13 15:32:23
An interactive rebase operates on a detached HEAD (to keep the reflog
of the original branch relatively clean), and updates the branch only
at the end.
Now that the sequencer learns to perform interactive rebases, it also
needs to learn the trick to update the branch before removing the
directory containing the state of the interactive rebase.
We introduce a new head_ref variable in a wider scope than necessary at
the moment, to allow for a later patch that prints out "Successfully
rebased and updated <ref>".
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 32 +++++++++++++++++++++++++++++++-
1 file changed, 31 insertions(+), 1 deletion(-)
@@ -1793,12 +1795,39 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)}if(is_rebase_i(opts)){-structstrbufbuf=STRBUF_INIT;+structstrbufhead_ref=STRBUF_INIT,buf=STRBUF_INIT;/* Stopped in the middle, as planned? */if(todo_list->current<todo_list->nr)return0;+if(read_oneliner(&head_ref,rebase_path_head_name(),0)&&+starts_with(head_ref.buf,"refs/")){+unsignedcharhead[20],orig[20];++if(get_sha1("HEAD",head))+returnerror(_("cannot read HEAD"));+if(!read_oneliner(&buf,rebase_path_orig_head(),0)||+get_sha1_hex(buf.buf,orig))+returnerror(_("could not read orig-head"));+strbuf_addf(&buf,"rebase -i (finish): %s onto ",+head_ref.buf);+if(!read_oneliner(&buf,rebase_path_onto(),0))+returnerror(_("could not read 'onto'"));+if(update_ref(buf.buf,head_ref.buf,head,orig,+REF_NODEREF,UPDATE_REFS_MSG_ON_ERR))+returnerror(_("could not update %s"),+head_ref.buf);+strbuf_reset(&buf);+strbuf_addf(&buf,+"rebase -i (finish): returning to %s",+head_ref.buf);+if(create_symref("HEAD",head_ref.buf,buf.buf))+returnerror(_("could not update HEAD to %s"),+head_ref.buf);+strbuf_reset(&buf);+}+if(opts->verbose){constchar*argv[]={"diff-tree","--stat",NULL,NULL
From: Johannes Schindelin <hidden> Date: 2016-12-13 15:32:27
In the upcoming patch, we will support rebase -i's progress
reporting. The progress skips comments but counts 'noop's.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
@@ -770,7 +770,9 @@ enum todo_command {TODO_EXEC,/* commands that do nothing but are counted for reporting progress */TODO_NOOP,-TODO_DROP+TODO_DROP,+/* comments (not counted for reporting progress) */+TODO_COMMENT};staticstruct{
From: Johannes Schindelin <hidden> Date: 2016-12-13 15:32:28
In the interactive rebase, commands that were successfully processed are
not simply discarded, but appended to the 'done' file instead. This is
used e.g. to display the current state to the user in the output of
`git status` or the progress.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
@@ -1295,6 +1301,23 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)returnerror_errno(_("could not write to '%s'"),todo_path);if(commit_lock_file(&todo_lock)<0)returnerror(_("failed to finalize '%s'."),todo_path);++if(is_rebase_i(opts)){+constchar*done_path=rebase_path_done();+intfd=open(done_path,O_CREAT|O_WRONLY|O_APPEND,0666);+intprev_offset=!next?0:+todo_list->items[next-1].offset_in_buf;++if(fd>=0&&offset>prev_offset&&+write_in_full(fd,todo_list->buf.buf+prev_offset,+offset-prev_offset)<0){+close(fd);+returnerror_errno(_("could not write to '%s'"),+done_path);+}+if(fd>=0)+close(fd);+}return0;}
From: Johannes Schindelin <hidden> Date: 2016-12-13 15:32:29
When calling `git rebase -i -v`, the user wants to see some statistics
after the commits were rebased. Let's show some.
The strbuf we use to perform that task will be used for other things
in subsequent commits, hence it is declared and initialized in a wider
scope than strictly needed here.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 22 ++++++++++++++++++++++
sequencer.h | 1 +
2 files changed, 23 insertions(+)
@@ -1121,6 +1123,9 @@ static int read_populate_opts(struct replay_opts *opts)}strbuf_release(&buf);+if(file_exists(rebase_path_verbose()))+opts->verbose=1;+return0;}
@@ -1493,9 +1498,26 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)}if(is_rebase_i(opts)){+structstrbufbuf=STRBUF_INIT;+/* Stopped in the middle, as planned? */if(todo_list->current<todo_list->nr)return0;++if(opts->verbose){+constchar*argv[]={+"diff-tree","--stat",NULL,NULL+};++if(!read_oneliner(&buf,rebase_path_orig_head(),0))+returnerror(_("could not read '%s'"),+rebase_path_orig_head());+strbuf_addstr(&buf,"..HEAD");+argv[2]=buf.buf;+run_command_v_opt(argv,RUN_GIT_CMD);+strbuf_reset(&buf);+}+strbuf_release(&buf);}/*
From: Johannes Schindelin <hidden> Date: 2016-12-13 15:32:31
This is a huge patch, and at the same time a huge step forward to
execute the performance-critical parts of the interactive rebase in a
builtin command.
Since 'fixup' and 'squash' are not only similar, but also need to know
about each other (we want to reduce a series of fixups/squashes into a
single, final commit message edit, from the user's point of view), we
really have to implement them both at the same time.
Most of the actual work is done by the existing code path that already
handles the "pick" and the "edit" commands; We added support for other
features (e.g. to amend the commit message) in the patches leading up to
this one, yet there are still quite a few bits in this patch that simply
would not make sense as individual patches (such as: determining whether
there was anything to "fix up" in the "todo" script, etc).
In theory, it would be possible to reuse the fast-forward code path also
for the fixup and the squash code paths, but in practice this would make
the code less readable. The end result cannot be fast-forwarded anyway,
therefore let's just extend the cherry-picking code path for now.
Since the sequencer parses the entire `git-rebase-todo` script in one go,
fixup or squash commands without a preceding pick can be reported early
(in git-rebase--interactive, we could only report such errors just before
executing the fixup/squash).
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 232 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 222 insertions(+), 10 deletions(-)
@@ -673,6 +702,8 @@ enum todo_command {TODO_PICK=0,TODO_REVERT,TODO_EDIT,+TODO_FIXUP,+TODO_SQUASH,/* commands that do something else than handling a single commit */TODO_EXEC,/* commands that do nothing but are counted for reporting progress */
@@ -694,16 +727,119 @@ static const char *command_to_string(const enum todo_command command)die("Unknown command: %d",command);}+staticintis_fixup(enumtodo_commandcommand)+{+returncommand==TODO_FIXUP||command==TODO_SQUASH;+}++staticintupdate_squash_messages(enumtodo_commandcommand,+structcommit*commit,structreplay_opts*opts)+{+structstrbufbuf=STRBUF_INIT;+intcount,res;+constchar*message,*body;++if(file_exists(rebase_path_squash_msg())){+char*p,*p2;++if(strbuf_read_file(&buf,rebase_path_squash_msg(),2048)<=0)+returnerror(_("could not read '%s'"),+rebase_path_squash_msg());++if(buf.buf[0]!=comment_line_char||+!skip_prefix(buf.buf+1," This is a combination of ",+(constchar**)&p))+returnerror(_("unexpected 1st line of squash message:"+"\n\n\t%.*s"),+(int)(strchrnul(buf.buf,'\n')-buf.buf),+buf.buf);+count=strtol(p,&p2,10);++if(count<1||*p2!=' ')+returnerror(_("invalid 1st line of squash message:\n"+"\n\t%.*s"),+(int)(strchrnul(buf.buf,'\n')-buf.buf),+buf.buf);++sprintf((char*)p,"%d",++count);+if(!*p2)+*p2=' ';+else{+*(++p2)='c';+strbuf_insert(&buf,p2-buf.buf," ",1);+}+}+else{+unsignedcharhead[20];+structcommit*head_commit;+constchar*head_message,*body;++if(get_sha1("HEAD",head))+returnerror(_("need a HEAD to fixup"));+if(!(head_commit=lookup_commit_reference(head)))+returnerror(_("could not read HEAD"));+if(!(head_message=get_commit_buffer(head_commit,NULL)))+returnerror(_("could not read HEAD's commit message"));++body=strstr(head_message,"\n\n");+if(!body)+body="";+else+body=skip_blank_lines(body+2);+if(write_message(body,strlen(body),+rebase_path_fixup_msg(),0))+returnerror(_("cannot write '%s'"),+rebase_path_fixup_msg());++count=2;+strbuf_addf(&buf,_("%c This is a combination of 2 commits.\n"+"%c The first commit's message is:\n\n%s"),+comment_line_char,comment_line_char,body);++unuse_commit_buffer(head_commit,head_message);+}++if(!(message=get_commit_buffer(commit,NULL)))+returnerror(_("could not read commit message of %s"),+oid_to_hex(&commit->object.oid));+body=strstr(message,"\n\n");+if(!body)+body="";+else+body=skip_blank_lines(body+2);++if(command==TODO_SQUASH){+unlink(rebase_path_fixup_msg());+strbuf_addf(&buf,_("\n%c This is the commit message #%d:\n"+"\n%s"),+comment_line_char,count,body);+}+elseif(command==TODO_FIXUP){+strbuf_addf(&buf,_("\n%c The commit message #%d "+"will be skipped:\n\n"),+comment_line_char,count);+strbuf_add_commented_lines(&buf,body,strlen(body));+}+else+returnerror(_("unknown command: %d"),command);+unuse_commit_buffer(commit,message);++res=write_message(buf.buf,buf.len,rebase_path_squash_msg(),0);+strbuf_release(&buf);+returnres;+}staticintdo_pick_commit(enumtodo_commandcommand,structcommit*commit,-structreplay_opts*opts)+structreplay_opts*opts,intfinal_fixup){+intedit=opts->edit,cleanup_commit_message=0;+constchar*msg_file=edit?NULL:git_path_merge_msg();unsignedcharhead[20];structcommit*base,*next,*parent;constchar*base_label,*next_label;structcommit_messagemsg={NULL,NULL,NULL,NULL};structstrbufmsgbuf=STRBUF_INIT;-intres,unborn=0,allow;+intres,unborn=0,amend=0,allow;if(opts->no_commit){/*
@@ -1024,8 +1187,16 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list)if(parse_insn_line(item,p,eol)){res=error(_("invalid line %d: %.*s"),i,(int)(eol-p),p);-item->command=-1;+item->command=TODO_NOOP;}++if(fixup_okay)+;/* do nothing */+elseif(is_fixup(item->command))+returnerror(_("cannot '%s' without a previous commit"),+command_to_string(item->command));+elseif(item->command!=TODO_NOOP)+fixup_okay=1;}if(!todo_list->nr)returnerror(_("no commits parsed."));
@@ -1434,6 +1605,20 @@ static int error_with_patch(struct commit *commit,returnexit_code;}+staticinterror_failed_squash(structcommit*commit,+structreplay_opts*opts,intsubject_len,constchar*subject)+{+if(rename(rebase_path_squash_msg(),rebase_path_message()))+returnerror(_("could not rename '%s' to '%s'"),+rebase_path_squash_msg(),rebase_path_message());+unlink(rebase_path_fixup_msg());+unlink(git_path("MERGE_MSG"));+if(copy_file(git_path("MERGE_MSG"),rebase_path_message(),0666))+returnerror(_("could not copy '%s' to '%s'"),+rebase_path_message(),git_path("MERGE_MSG"));+returnerror_with_patch(commit,subject,subject_len,opts,1,0);+}+staticintdo_exec(constchar*command_line){constchar*child_argv[]={NULL,NULL};
@@ -1475,6 +1660,21 @@ static int do_exec(const char *command_line)returnstatus;}+staticintis_final_fixup(structtodo_list*todo_list)+{+inti=todo_list->current;++if(!is_fixup(todo_list->items[i].command))+return0;++while(++i<todo_list->nr)+if(is_fixup(todo_list->items[i].command))+return0;+elseif(todo_list->items[i].command<TODO_NOOP)+break;+return1;+}+staticintpick_commits(structtodo_list*todo_list,structreplay_opts*opts){intres=0;
From: Johannes Schindelin <hidden> Date: 2016-12-13 15:32:43
When the last command of an interactive rebase fails, the user needs to
resolve the problem and then continue the interactive rebase. Naturally,
the todo script is empty by then. So let's not complain about that!
To that end, let's move that test out of the function that parses the
todo script, and into the more high-level function read_populate_todo().
This is also necessary by now because the lower-level parse_insn_buffer()
has no idea whether we are performing an interactive rebase or not.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
From: Johannes Schindelin <hidden> Date: 2016-12-13 15:32:46
This will be needed to hide the output of `git commit` when the
sequencer handles an interactive rebase's script.
Signed-off-by: Johannes Schindelin <redacted>
---
run-command.c | 23 +++++++++++++++++++++++
run-command.h | 1 +
2 files changed, 24 insertions(+)
From: Johannes Schindelin <hidden> Date: 2016-12-13 15:32:48
The parsing part of a 'drop' command is almost identical to parsing a
'pick', while the operation is the same as that of a 'noop'.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
@@ -769,7 +769,8 @@ enum todo_command {/* commands that do something else than handling a single commit */TODO_EXEC,/* commands that do nothing but are counted for reporting progress */-TODO_NOOP+TODO_NOOP,+TODO_DROP};staticstruct{
From: Johannes Schindelin <hidden> Date: 2016-12-13 15:32:49
When doing an interactive rebase, we want to leave a 'patch' file for
further inspection by the user (even if we never tried to actually apply
that patch, since we're cherry-picking instead).
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 3 +++
1 file changed, 3 insertions(+)
From: Johannes Schindelin <hidden> Date: 2016-12-13 15:32:50
The interactive rebase has the very special magic that a cherry-pick
that exits with a status different from 0 and 1 signifies a failure to
even record that a cherry-pick was started.
This can happen e.g. when a fast-forward fails because it would
overwrite untracked files.
In that case, we must reschedule the command that we thought we already
had at least started successfully.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 6 ++++++
1 file changed, 6 insertions(+)
From: Johannes Schindelin <hidden> Date: 2016-12-13 15:32:58
The sequencer already has an idea about using different merge
strategies. We just piggy-back on top of that, using rebase -i's
own settings, when running the sequencer in interactive rebase mode.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 26 +++++++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
From: Johannes Schindelin <hidden> Date: 2016-12-13 15:33:01
When rebasing commits that have commit notes attached, the interactive
rebase rewrites those notes faithfully at the end. The sequencer must
do this, too, if it wishes to do interactive rebase's job.
Signed-off-by: Johannes Schindelin <redacted>
---
sequencer.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 76 insertions(+)
@@ -1837,6 +1898,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)if(is_rebase_i(opts)){structstrbufhead_ref=STRBUF_INIT,buf=STRBUF_INIT;+structstatst;/* Stopped in the middle, as planned? */if(todo_list->current<todo_list->nr)
@@ -1881,6 +1943,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)run_command_v_opt(argv,RUN_GIT_CMD);strbuf_reset(&buf);}+flush_rewritten_pending();+if(!stat(rebase_path_rewritten_list(),&st)&&+st.st_size>0){+structchild_processchild=CHILD_PROCESS_INIT;++child.in=open(rebase_path_rewritten_list(),O_RDONLY);+child.git_cmd=1;+argv_array_push(&child.args,"notes");+argv_array_push(&child.args,"copy");+argv_array_push(&child.args,"--for-rewrite=rebase");+/* we don't care if this copying failed */+run_command(&child);+}+strbuf_release(&buf);strbuf_release(&head_ref);}