This patch series depends on pt/pull-builtin.
This is a re-roll of [v4]. Thanks Torsten, Stefan, Junio for the reviews last
round. Interdiff below.
Previous versions:
[WIP v1] http://thread.gmane.org/gmane.comp.version-control.git/270048
[WIP v2] http://thread.gmane.org/gmane.comp.version-control.git/271381
[WIP v3] http://thread.gmane.org/gmane.comp.version-control.git/271967
[v4] http://thread.gmane.org/gmane.comp.version-control.git/272876
git-am is a commonly used command for applying a series of patches from a
mailbox to the current branch. Currently, it is implemented by the shell script
git-am.sh. However, compared to C, shell scripts have certain deficiencies:
they need to spawn a lot of processes, introduce a lot of dependencies and
cannot take advantage of git's internal caches.
This patch series rewrites git-am.sh into optimized C builtin/am.c, and is
part of my GSoC project to rewrite git-pull and git-am into C builtins[1].
[1] https://gist.github.com/pyokagan/1b7b0d1f4dab6ba3cef1
Paul Tan (44):
wrapper: implement xopen()
wrapper: implement xfopen()
builtin-am: implement skeletal builtin am
builtin-am: implement patch queue mechanism
builtin-am: split out mbox/maildir patches with git-mailsplit
builtin-am: auto-detect mbox patches
builtin-am: extract patch and commit info with git-mailinfo
builtin-am: apply patch with git-apply
builtin-am: implement committing applied patch
builtin-am: refuse to apply patches if index is dirty
builtin-am: implement --resolved/--continue
builtin-am: implement --skip
builtin-am: implement --abort
builtin-am: reject patches when there's a session in progress
builtin-am: implement -q/--quiet
builtin-am: exit with user friendly message on failure
builtin-am: implement -s/--signoff
cache-tree: introduce write_index_as_tree()
builtin-am: implement --3way, am.threeWay
builtin-am: implement --rebasing mode
builtin-am: bypass git-mailinfo when --rebasing
builtin-am: handle stray state directory
builtin-am: implement -u/--utf8
builtin-am: implement -k/--keep, --keep-non-patch
builtin-am: implement --[no-]message-id, am.messageid
builtin-am: support --keep-cr, am.keepcr
builtin-am: implement --[no-]scissors
builtin-am: pass git-apply's options to git-apply
builtin-am: implement --ignore-date
builtin-am: implement --committer-date-is-author-date
builtin-am: implement -S/--gpg-sign, commit.gpgsign
builtin-am: invoke post-rewrite hook
builtin-am: support automatic notes copying
builtin-am: invoke applypatch-msg hook
builtin-am: invoke pre-applypatch hook
builtin-am: invoke post-applypatch hook
builtin-am: rerere support
builtin-am: support and auto-detect StGit patches
builtin-am: support and auto-detect StGit series files
builtin-am: support and auto-detect mercurial patches
builtin-am: implement -i/--interactive
builtin-am: implement legacy -b/--binary option
builtin-am: check for valid committer ident
builtin-am: remove redirection to git-am.sh
Makefile | 2 +-
builtin.h | 1 +
builtin/am.c | 2331 +++++++++++++++++++++++++++++++
cache-tree.c | 29 +-
cache-tree.h | 1 +
git-am.sh => contrib/examples/git-am.sh | 0
git-compat-util.h | 2 +
git.c | 1 +
wrapper.c | 56 +
9 files changed, 2410 insertions(+), 13 deletions(-)
create mode 100644 builtin/am.c
rename git-am.sh => contrib/examples/git-am.sh (100%)
@@ -918,7 +913,7 @@ static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr)fprintf(out,"From: %s\n",str);elseif(skip_prefix(sb.buf,"# Date ",&str)){unsignedlongtimestamp;-longtz;+longtz,tz2;char*end;errno=0;
@@ -942,10 +937,11 @@ static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr)*howevergit'stimezoneisinhours+minuteseastof*UTC.Convertit.*/-tz=tz/(60*60)*100+tz%(60*60);-tz=-tz;+tz2=labs(tz)/3600*100+labs(tz)%3600/60;+if(tz>0)+tz2=-tz2;-fprintf(out,"Date: %s\n",show_date(timestamp,tz,DATE_RFC2822));+fprintf(out,"Date: %s\n",show_date(timestamp,tz2,DATE_RFC2822));}elseif(starts_with(sb.buf,"# ")){continue;}else{
@@ -197,20 +197,30 @@ int xopen(const char *path, int oflag, ...)mode_tmode=0;va_listap;+/*+*va_arg()willhaveundefinedbehaviorifthespecifiedtypeisnot+*compatiblewiththeargumenttype.Sinceintegersarepromotedto+*ints,wefetchthenextargumentasanint,andthencastittoa+*mode_ttoavoidundefinedbehavior.+*/va_start(ap,oflag);if(oflag&O_CREAT)-mode=va_arg(ap,mode_t);+mode=va_arg(ap,int);va_end(ap);-assert(path);-for(;;){intfd=open(path,oflag,mode);if(fd>=0)returnfd;if(errno==EINTR)continue;-die_errno(_("could not open '%s'"),path);++if((oflag&O_RDWR)==O_RDWR)+die_errno(_("could not open '%s' for reading and writing"),path);+elseif((oflag&O_WRONLY)==O_WRONLY)+die_errno(_("could not open '%s' for writing"),path);+else+die_errno(_("could not open '%s' for reading"),path);}}
@@ -341,16 +351,19 @@ int xdup(int fd)*/FILE*xfopen(constchar*path,constchar*mode){-assert(path);-assert(mode);-for(;;){FILE*fp=fopen(path,mode);if(fp)returnfp;if(errno==EINTR)continue;-die_errno(_("could not open '%s'"),path);++if(*mode&&mode[1]=='+')+die_errno(_("could not open '%s' for reading and writing"),path);+elseif(*mode=='w'||*mode=='a')+die_errno(_("could not open '%s' for writing"),path);+else+die_errno(_("could not open '%s' for reading"),path);}}
A common usage pattern of open() is to check if it was successful, and
die() if it was not:
int fd = open(path, O_WRONLY | O_CREAT, 0777);
if (fd < 0)
die_errno(_("Could not open '%s' for writing."), path);
Implement a wrapper function xopen() that does the above so that we can
save a few lines of code, and make the die() messages consistent.
Helped-by: Torsten Bögershausen [off-list ref]
Helped-by: Jeff King [off-list ref]
Helped-by: Johannes Schindelin [off-list ref]
Helped-by: Junio C Hamano [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
Notes:
v5
* Remove assert()s since we do not need to over-zealously check against
insane code.
* Use va_arg(va, int) instead of va_arg(va, mode_t) to guard against
undefined behavior if mode_t is incompatible with the promoted integer
argument (int).
* The read/write error messages have returned as they can be used to
better diagnose permission errors. Hopefully I got the logic right
this time.
git-compat-util.h | 1 +
wrapper.c | 35 +++++++++++++++++++++++++++++++++++
2 files changed, 36 insertions(+)
@@ -189,6 +189,41 @@ void *xcalloc(size_t nmemb, size_t size)# endif#endif+/**+*xopen()isthesameasopen(),butitdie()siftheopen()fails.+*/+intxopen(constchar*path,intoflag,...)+{+mode_tmode=0;+va_listap;++/*+*va_arg()willhaveundefinedbehaviorifthespecifiedtypeisnot+*compatiblewiththeargumenttype.Sinceintegersarepromotedto+*ints,wefetchthenextargumentasanint,andthencastittoa+*mode_ttoavoidundefinedbehavior.+*/+va_start(ap,oflag);+if(oflag&O_CREAT)+mode=va_arg(ap,int);+va_end(ap);++for(;;){+intfd=open(path,oflag,mode);+if(fd>=0)+returnfd;+if(errno==EINTR)+continue;++if((oflag&O_RDWR)==O_RDWR)+die_errno(_("could not open '%s' for reading and writing"),path);+elseif((oflag&O_WRONLY)==O_WRONLY)+die_errno(_("could not open '%s' for writing"),path);+else+die_errno(_("could not open '%s' for reading"),path);+}+}+/**xread()isthesamearead(),butitautomaticallyrestartsread()*operationswitharecoverableerror(EAGAINandEINTR).xread()
For the purpose of rewriting git-am.sh into a C builtin, implement a
skeletal builtin/am.c that redirects to $GIT_EXEC_PATH/git-am if the
environment variable _GIT_USE_BUILTIN_AM is not defined. Since in the
Makefile git-am.sh takes precedence over builtin/am.c,
$GIT_EXEC_PATH/git-am will contain the shell script git-am.sh, and thus
this allows us to fall back on the functional git-am.sh when running the
test suite for tests that depend on a working git-am implementation.
Since git-am.sh cannot handle any environment modifications by
setup_git_directory(), "am" is declared with no setup flags in git.c. On
the other hand, to re-implement git-am.sh in builtin/am.c, we need to
run all the git dir and work tree setup logic that git.c typically does
for us. As such, we work around this temporarily by copying the logic in
git.c's run_builtin(), which is roughly:
prefix = setup_git_directory();
trace_repo_setup(prefix);
setup_work_tree();
This redirection should be removed when all the features of git-am.sh
have been re-implemented in builtin/am.c.
Helped-by: Junio C Hamano [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
Makefile | 1 +
builtin.h | 1 +
builtin/am.c | 29 +++++++++++++++++++++++++++++
git.c | 6 ++++++
4 files changed, 37 insertions(+)
create mode 100644 builtin/am.c
A common usage pattern of fopen() is to check if it succeeded, and die()
if it failed:
FILE *fp = fopen(path, "w");
if (!fp)
die_errno(_("could not open '%s' for writing"), path);
Implement a wrapper function xfopen() for the above, so that we can save
a few lines of code and make the die() messages consistent.
Helped-by: Jeff King [off-list ref]
Helped-by: Johannes Schindelin [off-list ref]
Helped-by: Junio C Hamano [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
Notes:
v5
* Removed assert()s since we do not need to over-zealously guard against
insane code.
* The read/write error messages have returned as they are useful in
diagnosing permission errors. Hopefully I got the logic right this
time.
git-compat-util.h | 1 +
wrapper.c | 21 +++++++++++++++++++++
2 files changed, 22 insertions(+)
@@ -346,6 +346,27 @@ int xdup(int fd)returnret;}+/**+*xfopen()isthesameasfopen(),butitdie()sifthefopen()fails.+*/+FILE*xfopen(constchar*path,constchar*mode)+{+for(;;){+FILE*fp=fopen(path,mode);+if(fp)+returnfp;+if(errno==EINTR)+continue;++if(*mode&&mode[1]=='+')+die_errno(_("could not open '%s' for reading and writing"),path);+elseif(*mode=='w'||*mode=='a')+die_errno(_("could not open '%s' for writing"),path);+else+die_errno(_("could not open '%s' for reading"),path);+}+}+FILE*xfdopen(intfd,constchar*mode){FILE*stream=fdopen(fd,mode);
git-am applies a series of patches. If the process terminates
abnormally, we want to be able to resume applying the series of patches.
This requires the session state to be saved in a persistent location.
Implement the mechanism of a "patch queue", represented by 2 integers --
the index of the current patch we are applying and the index of the last
patch, as well as its lifecycle through the following functions:
* am_setup(), which will set up the state directory
$GIT_DIR/rebase-apply. As such, even if the process exits abnormally,
the last-known state will still persist.
* am_load(), which is called if there is an am session in
progress, to load the last known state from the state directory so we
can resume applying patches.
* am_run(), which will do the actual patch application. After applying a
patch, it calls am_next() to increment the current patch index. The
logic for applying and committing a patch is not implemented yet.
* am_destroy(), which is finally called when we successfully applied all
the patches in the queue, to clean up by removing the state directory
and its contents.
Helped-by: Junio C Hamano [off-list ref]
Helped-by: Stefan Beller [off-list ref]
Helped-by: Johannes Schindelin [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 180 insertions(+)
@@ -6,9 +6,174 @@#include"cache.h"#include"builtin.h"#include"exec_cmd.h"+#include"parse-options.h"+#include"dir.h"++structam_state{+/* state directory path */+char*dir;++/* current and last patch numbers, 1-indexed */+intcur;+intlast;+};++/**+*Initializesam_statewiththedefaultvalues.Thestatedirectoryissetto+*dir.+*/+staticvoidam_state_init(structam_state*state,constchar*dir)+{+memset(state,0,sizeof(*state));++assert(dir);+state->dir=xstrdup(dir);+}++/**+*Releasesmemoryallocatedbyanam_state.+*/+staticvoidam_state_release(structam_state*state)+{+if(state->dir)+free(state->dir);+}++/**+*Returnspathrelativetotheam_statedirectory.+*/+staticinlineconstchar*am_path(conststructam_state*state,constchar*path)+{+assert(state->dir);+assert(path);+returnmkpath("%s/%s",state->dir,path);+}++/**+*Returns1ifthereisanamsessioninprogress,0otherwise.+*/+staticintam_in_progress(conststructam_state*state)+{+structstatst;++if(lstat(state->dir,&st)<0||!S_ISDIR(st.st_mode))+return0;+if(lstat(am_path(state,"last"),&st)||!S_ISREG(st.st_mode))+return0;+if(lstat(am_path(state,"next"),&st)||!S_ISREG(st.st_mode))+return0;+return1;+}++/**+*Readsthecontentsof`file`inthe`state`directoryinto`sb`.Returnsthe+*numberofbytesreadonsuccess,-1ifthefiledoesnotexist.If`trim`is+*set,trailingwhitespacewillberemoved.+*/+staticintread_state_file(structstrbuf*sb,conststructam_state*state,+constchar*file,inttrim)+{+strbuf_reset(sb);++if(strbuf_read_file(sb,am_path(state,file),0)>=0){+if(trim)+strbuf_trim(sb);++returnsb->len;+}++if(errno==ENOENT)+return-1;++die_errno(_("could not read '%s'"),am_path(state,file));+}++/**+*Loadsstatefromdisk.+*/+staticvoidam_load(structam_state*state)+{+structstrbufsb=STRBUF_INIT;++if(read_state_file(&sb,state,"next",1)<0)+die("BUG: state file 'next' does not exist");+state->cur=strtol(sb.buf,NULL,10);++if(read_state_file(&sb,state,"last",1)<0)+die("BUG: state file 'last' does not exist");+state->last=strtol(sb.buf,NULL,10);++strbuf_release(&sb);+}++/**+*Removestheam_statedirectory,forcefullyterminatingthecurrentam+*session.+*/+staticvoidam_destroy(conststructam_state*state)+{+structstrbufsb=STRBUF_INIT;++strbuf_addstr(&sb,state->dir);+remove_dir_recursively(&sb,0);+strbuf_release(&sb);+}++/**+*Setupanewamsessionforapplyingpatches+*/+staticvoidam_setup(structam_state*state)+{+if(mkdir(state->dir,0777)<0&&errno!=EEXIST)+die_errno(_("failed to create directory '%s'"),state->dir);++/*+*NOTE:Sincethe"next"and"last"filesdetermineifanam_state+*sessionisinprogress,theyshouldbewrittenlast.+*/++write_file(am_path(state,"next"),1,"%d",state->cur);++write_file(am_path(state,"last"),1,"%d",state->last);+}++/**+*Incrementsthepatchpointer,andcleansam_statefortheapplicationofthe+*nextpatch.+*/+staticvoidam_next(structam_state*state)+{+state->cur++;+write_file(am_path(state,"next"),1,"%d",state->cur);+}++/**+*Appliesallqueuedmail.+*/+staticvoidam_run(structam_state*state)+{+while(state->cur<=state->last){++/* NEEDSWORK: Patch application not implemented yet */++am_next(state);+}++am_destroy(state);+}intcmd_am(intargc,constchar**argv,constchar*prefix){+structam_statestate;++constchar*constusage[]={+N_("git am [options] [(<mbox>|<Maildir>)...]"),+NULL+};++structoptionoptions[]={+OPT_END()+};/**NEEDSWORK:Onceallthefeaturesofgit-am.shhavebeen
git-am.sh supports mbox, stgit and mercurial patches. Re-implement
support for splitting out mbox/maildirs using git-mailsplit, while also
implementing the framework required to support other patch formats in
the future.
Re-implement support for the --patch-format option (since a5a6755
(git-am foreign patch support: introduce patch_format, 2009-05-27)) to
allow the user to choose between the different patch formats.
Helped-by: Junio C Hamano [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 104 insertions(+), 3 deletions(-)
@@ -8,6 +8,12 @@#include"exec_cmd.h"#include"parse-options.h"#include"dir.h"+#include"run-command.h"++enumpatch_format{+PATCH_FORMAT_UNKNOWN=0,+PATCH_FORMAT_MBOX+};structam_state{/* state directory path */
@@ -16,6 +22,9 @@ struct am_state {/* current and last patch numbers, 1-indexed */intcur;intlast;++/* number of digits in patch filename */+intprec;};/**
Since 15ced75 (git-am foreign patch support: autodetect some patch
formats, 2009-05-27), git-am.sh is able to autodetect mbox, stgit and
mercurial patches through heuristics.
Re-implement support for autodetecting mbox/maildir files in
builtin/am.c.
RFC 2822 requires that lines are terminated by "\r\n". To support this,
implement strbuf_getline_crlf(), which will remove both '\n' and "\r\n"
from the end of the line.
Helped-by: Junio C Hamano [off-list ref]
Helped-by: Eric Sunshine [off-list ref]
Helped-by: Johannes Schindelin [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 109 insertions(+)
Implement do_commit(), which commits the index which contains the
results of applying the patch, along with the extracted commit message
and authorship information.
Since 29b6754 (am: remove rebase-apply directory before gc, 2010-02-22),
git gc --auto is also invoked to pack the loose objects that are created
from making the commits.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 51 insertions(+), 4 deletions(-)
@@ -696,10 +699,56 @@ static int run_apply(const struct am_state *state)}/**+*Commitsthecurrentindexwithstate->msgasthecommitmessageand+*state->author_name,state->author_emailandstate->author_dateastheauthor+*information.+*/+staticvoiddo_commit(conststructam_state*state)+{+unsignedchartree[GIT_SHA1_RAWSZ],parent[GIT_SHA1_RAWSZ],+commit[GIT_SHA1_RAWSZ];+unsignedchar*ptr;+structcommit_list*parents=NULL;+constchar*reflog_msg,*author;+structstrbufsb=STRBUF_INIT;++if(write_cache_as_tree(tree,0,NULL))+die(_("git write-tree failed to write a tree"));++if(!get_sha1_commit("HEAD",parent)){+ptr=parent;+commit_list_insert(lookup_commit(parent),&parents);+}else{+ptr=NULL;+fprintf_ln(stderr,_("applying to an empty history"));+}++author=fmt_ident(state->author_name,state->author_email,+state->author_date,IDENT_STRICT);++if(commit_tree(state->msg,state->msg_len,tree,parents,commit,+author,NULL))+die(_("failed to write commit object"));++reflog_msg=getenv("GIT_REFLOG_ACTION");+if(!reflog_msg)+reflog_msg="am";++strbuf_addf(&sb,"%s: %.*s",reflog_msg,linelen(state->msg),+state->msg);++update_ref(sb.buf,"HEAD",commit,ptr,0,UPDATE_REFS_DIE_ON_ERR);++strbuf_release(&sb);+}++/***Appliesallqueuedmail.*/staticvoidam_run(structam_state*state){+constchar*argv_gc_auto[]={"gc","--auto",NULL};+refresh_and_write_cache();while(state->cur<=state->last){
Implement applying the patch to the index using git-apply.
If a file is unchanged but stat-dirty, git-apply may erroneously fail to
apply patches, thinking that they conflict with a dirty working tree.
As such, since 2a6f08a (am: refresh the index at start and --resolved,
2011-08-15), git-am will refresh the index before applying patches.
Re-implement this behavior.
Helped-by: Junio C Hamano [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 72 insertions(+), 1 deletion(-)
@@ -561,6 +570,20 @@ static const char *msgnum(const struct am_state *state)}/**+*Refreshandwriteindex.+*/+staticvoidrefresh_and_write_cache(void)+{+staticstructlock_filelock_file;++hold_locked_index(&lock_file,1);+refresh_cache(REFRESH_QUIET);+if(write_locked_index(&the_index,&lock_file,COMMIT_LOCK))+die(_("unable to write index file"));+rollback_lock_file(&lock_file);+}++/***Parses`mail`usinggit-mailinfo,extractingitspatchandauthorshipinfo.*state->msgwillbesettothepatchmessage.state->author_name,*state->author_emailandstate->author_datewillbesettothepatchauthor's
@@ -650,10 +673,35 @@ finish:}/**+*Appliescurrentpatchwithgit-apply.Returns0onsuccess,-1otherwise.+*/+staticintrun_apply(conststructam_state*state)+{+structchild_processcp=CHILD_PROCESS_INIT;++cp.git_cmd=1;++argv_array_push(&cp.args,"apply");+argv_array_push(&cp.args,"--index");+argv_array_push(&cp.args,am_path(state,"patch"));++if(run_command(&cp))+return-1;++/* Reload index as git-apply will have modified it. */+discard_cache();+read_cache();++return0;+}++/***Appliesallqueuedmail.*/staticvoidam_run(structam_state*state){+refresh_and_write_cache();+while(state->cur<=state->last){constchar*mail=am_path(state,msgnum(state));
@@ -666,7 +714,27 @@ static void am_run(struct am_state *state)write_author_script(state);write_commit_msg(state);-/* NEEDSWORK: Patch application not implemented yet */+printf_ln(_("Applying: %.*s"),linelen(state->msg),state->msg);++if(run_apply(state)<0){+intadvice_amworkdir=1;++printf_ln(_("Patch failed at %s %.*s"),msgnum(state),+linelen(state->msg),state->msg);++git_config_get_bool("advice.amworkdir",&advice_amworkdir);++if(advice_amworkdir)+printf_ln(_("The copy of the patch that failed is found in: %s"),+am_path(state,"patch"));++exit(128);+}++/*+*NEEDSWORK:Afterthepatchhasbeenappliedtotheindex+*withgit-apply,weneedtomakecommitaswell.+*/next:am_next(state);
@@ -728,6 +796,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)argc=parse_options(argc,argv,prefix,options,usage,0);+if(read_index_preload(&the_index,NULL)<0)+die(_("failed to read the index"));+if(am_in_progress(&state))am_load(&state);else{
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am
will refuse to apply patches if the index is dirty. Re-implement this
behavior in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 45 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
For the purpose of applying the patch and committing the results,
implement extracting the patch data, commit message and authorship from
an e-mail message using git-mailinfo.
git-mailinfo is run as a separate process, but ideally in the future,
we should be be able to access its functionality directly without
spawning a new process.
Helped-by: Junio C Hamano [off-list ref]
Helped-by: Jeff King [off-list ref]
Helped-by: Johannes Schindelin [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 335 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 335 insertions(+)
@@ -9,6 +9,23 @@#include"parse-options.h"#include"dir.h"#include"run-command.h"+#include"quote.h"++/**+*Returns1ifthefileisemptyordoesnotexist,0otherwise.+*/+staticintis_empty_file(constchar*filename)+{+structstatst;++if(stat(filename,&st)<0){+if(errno==ENOENT)+return1;+die_errno(_("could not stat %s"),filename);+}++return!st.st_size;+}/***Likestrbuf_getline(),buttreatsboth'\n'and"\r\n"aslineterminators.
@@ -38,6 +55,13 @@ struct am_state {intcur;intlast;+/* commit metadata and message */+char*author_name;+char*author_email;+char*author_date;+char*msg;+size_tmsg_len;+/* number of digits in patch filename */intprec;};
@@ -115,6 +151,167 @@ static int read_state_file(struct strbuf *sb, const struct am_state *state,}/**+*ReadsaKEY=VALUEshellvariableassignmentfrom`fp`,returningtheVALUE+*asanewly-allocatedstring.VALUEmustbeaquotedstring,andtheKEYmust+*match`key`.ReturnsNULLonfailure.+*+*Thisisusedbyread_author_script()toreadtheGIT_AUTHOR_*variablesfrom+*theauthor-script.+*/+staticchar*read_shell_var(FILE*fp,constchar*key)+{+structstrbufsb=STRBUF_INIT;+constchar*str;++if(strbuf_getline(&sb,fp,'\n'))+gotofail;++if(!skip_prefix(sb.buf,key,&str))+gotofail;++if(!skip_prefix(str,"=",&str))+gotofail;++strbuf_remove(&sb,0,str-sb.buf);++str=sq_dequote(sb.buf);+if(!str)+gotofail;++returnstrbuf_detach(&sb,NULL);++fail:+strbuf_release(&sb);+returnNULL;+}++/**+*Readsandparsesthestatedirectory's"author-script"file,andsets+*state->author_name,state->author_emailandstate->author_dateaccordingly.+*Returns0onsuccess,-1ifthefilecouldnotbeparsed.+*+*Theauthorscriptisoftheformat:+*+*GIT_AUTHOR_NAME='$author_name'+*GIT_AUTHOR_EMAIL='$author_email'+*GIT_AUTHOR_DATE='$author_date'+*+*where$author_name,$author_emailand$author_datearequoted.Wearestrict+*withourparsing,asthefilewasmeanttobeeval'dintheoldgit-am.sh+*script,andthusifthefilediffersfromwhatthisfunctionexpects,itis+*bettertobailoutthantodosomethingthattheuserdoesnotexpect.+*/+staticintread_author_script(structam_state*state)+{+constchar*filename=am_path(state,"author-script");+FILE*fp;++assert(!state->author_name);+assert(!state->author_email);+assert(!state->author_date);++fp=fopen(filename,"r");+if(!fp){+if(errno==ENOENT)+return0;+die_errno(_("could not open '%s' for reading"),filename);+}++state->author_name=read_shell_var(fp,"GIT_AUTHOR_NAME");+if(!state->author_name){+fclose(fp);+return-1;+}++state->author_email=read_shell_var(fp,"GIT_AUTHOR_EMAIL");+if(!state->author_email){+fclose(fp);+return-1;+}++state->author_date=read_shell_var(fp,"GIT_AUTHOR_DATE");+if(!state->author_date){+fclose(fp);+return-1;+}++if(fgetc(fp)!=EOF){+fclose(fp);+return-1;+}++fclose(fp);+return0;+}++/**+*Savesstate->author_name,state->author_emailandstate->author_dateinthe+*statedirectory's"author-script"file.+*/+staticvoidwrite_author_script(conststructam_state*state)+{+structstrbufsb=STRBUF_INIT;++assert(state->author_name);+assert(state->author_email);+assert(state->author_date);++strbuf_addstr(&sb,"GIT_AUTHOR_NAME=");+sq_quote_buf(&sb,state->author_name);+strbuf_addch(&sb,'\n');++strbuf_addstr(&sb,"GIT_AUTHOR_EMAIL=");+sq_quote_buf(&sb,state->author_email);+strbuf_addch(&sb,'\n');++strbuf_addstr(&sb,"GIT_AUTHOR_DATE=");+sq_quote_buf(&sb,state->author_date);+strbuf_addch(&sb,'\n');++write_file(am_path(state,"author-script"),1,"%s",sb.buf);++strbuf_release(&sb);+}++/**+*Readsthecommitmessagefromthestatedirectory's"final-commit"file,+*settingstate->msgtoitscontentsandstate->msg_lentothelengthofits+*contentsinbytes.+*+*Returns0onsuccess,-1ifthefiledoesnotexist.+*/+staticintread_commit_msg(structam_state*state)+{+structstrbufsb=STRBUF_INIT;++assert(!state->msg);++if(read_state_file(&sb,state,"final-commit",0)<0){+strbuf_release(&sb);+return-1;+}++state->msg=strbuf_detach(&sb,&state->msg_len);+return0;+}++/**+*Savesstate->msginthestatedirectory's"final-commit"file.+*/+staticvoidwrite_commit_msg(conststructam_state*state)+{+intfd;+constchar*filename=am_path(state,"final-commit");++assert(state->msg);++fd=xopen(filename,O_WRONLY|O_CREAT,0666);+if(write_in_full(fd,state->msg,state->msg_len)<0)+die_errno(_("could not write to %s"),filename);+close(fd);+}++/***Loadsstatefromdisk.*/staticvoidam_load(structam_state*state)
@@ -129,6 +326,11 @@ static void am_load(struct am_state *state)die("BUG: state file 'last' does not exist");state->last=strtol(sb.buf,NULL,10);+if(read_author_script(state)<0)+die(_("could not parse author script"));++read_commit_msg(state);+strbuf_release(&sb);}
@@ -321,19 +523,152 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,*/staticvoidam_next(structam_state*state){+if(state->author_name)+free(state->author_name);+state->author_name=NULL;++if(state->author_email)+free(state->author_email);+state->author_email=NULL;++if(state->author_date)+free(state->author_date);+state->author_date=NULL;++if(state->msg)+free(state->msg);+state->msg=NULL;+state->msg_len=0;++unlink(am_path(state,"author-script"));+unlink(am_path(state,"final-commit"));+state->cur++;write_file(am_path(state,"next"),1,"%d",state->cur);}/**+*Returnsthefilenameofthecurrentpatchemail.+*/+staticconstchar*msgnum(conststructam_state*state)+{+staticstructstrbufsb=STRBUF_INIT;++strbuf_reset(&sb);+strbuf_addf(&sb,"%0*d",state->prec,state->cur);++returnsb.buf;+}++/**+*Parses`mail`usinggit-mailinfo,extractingitspatchandauthorshipinfo.+*state->msgwillbesettothepatchmessage.state->author_name,+*state->author_emailandstate->author_datewillbesettothepatchauthor's+*name,emailanddaterespectively.Thepatchbodywillbewrittentothe+*statedirectory's"patch"file.+*+*Returns1ifthepatchshouldbeskipped,0otherwise.+*/+staticintparse_mail(structam_state*state,constchar*mail)+{+FILE*fp;+structchild_processcp=CHILD_PROCESS_INIT;+structstrbufsb=STRBUF_INIT;+structstrbufmsg=STRBUF_INIT;+structstrbufauthor_name=STRBUF_INIT;+structstrbufauthor_date=STRBUF_INIT;+structstrbufauthor_email=STRBUF_INIT;+intret=0;++cp.git_cmd=1;+cp.in=xopen(mail,O_RDONLY,0);+cp.out=xopen(am_path(state,"info"),O_WRONLY|O_CREAT,0777);++argv_array_push(&cp.args,"mailinfo");+argv_array_push(&cp.args,am_path(state,"msg"));+argv_array_push(&cp.args,am_path(state,"patch"));++if(run_command(&cp)<0)+die("could not parse patch");++close(cp.in);+close(cp.out);++/* Extract message and author information */+fp=xfopen(am_path(state,"info"),"r");+while(!strbuf_getline(&sb,fp,'\n')){+constchar*x;++if(skip_prefix(sb.buf,"Subject: ",&x)){+if(msg.len)+strbuf_addch(&msg,'\n');+strbuf_addstr(&msg,x);+}elseif(skip_prefix(sb.buf,"Author: ",&x))+strbuf_addstr(&author_name,x);+elseif(skip_prefix(sb.buf,"Email: ",&x))+strbuf_addstr(&author_email,x);+elseif(skip_prefix(sb.buf,"Date: ",&x))+strbuf_addstr(&author_date,x);+}+fclose(fp);++/* Skip pine's internal folder data */+if(!strcmp(author_name.buf,"Mail System Internal Data")){+ret=1;+gotofinish;+}++if(is_empty_file(am_path(state,"patch"))){+printf_ln(_("Patch is empty. Was it split wrong?"));+exit(128);+}++strbuf_addstr(&msg,"\n\n");+if(strbuf_read_file(&msg,am_path(state,"msg"),0)<0)+die_errno(_("could not read '%s'"),am_path(state,"msg"));+stripspace(&msg,0);++assert(!state->author_name);+state->author_name=strbuf_detach(&author_name,NULL);++assert(!state->author_email);+state->author_email=strbuf_detach(&author_email,NULL);++assert(!state->author_date);+state->author_date=strbuf_detach(&author_date,NULL);++assert(!state->msg);+state->msg=strbuf_detach(&msg,&state->msg_len);++finish:+strbuf_release(&msg);+strbuf_release(&author_date);+strbuf_release(&author_email);+strbuf_release(&author_name);+strbuf_release(&sb);+returnret;+}++/***Appliesallqueuedmail.*/staticvoidam_run(structam_state*state){while(state->cur<=state->last){+constchar*mail=am_path(state,msgnum(state));++if(!file_exists(mail))+gotonext;++if(parse_mail(state,mail))+gotonext;/* mail should be skipped */++write_author_script(state);+write_commit_msg(state);/* NEEDSWORK: Patch application not implemented yet */+next:am_next(state);}
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am
supported resuming from a failed patch application by skipping the
current patch. Re-implement this feature by introducing am_skip().
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 119 insertions(+), 2 deletions(-)
@@ -872,6 +874,114 @@ static void am_resolve(struct am_state *state)}/**+*Performsacheckoutfast-forwardfrom`head`to`remote`.If`reset`is+*true,anyunmergedentrieswillbediscarded.Returns0onsuccess,-1on+*failure.+*/+staticintfast_forward_to(structtree*head,structtree*remote,intreset)+{+structlock_file*lock_file=xcalloc(1,sizeof(structlock_file));+structunpack_trees_optionsopts;+structtree_desct[2];++if(parse_tree(head)||parse_tree(remote))+return-1;++hold_locked_index(lock_file,1);++refresh_cache(REFRESH_QUIET);++memset(&opts,0,sizeof(opts));+opts.head_idx=1;+opts.src_index=&the_index;+opts.dst_index=&the_index;+opts.update=1;+opts.merge=1;+opts.reset=reset;+opts.fn=twoway_merge;+init_tree_desc(&t[0],head->buffer,head->size);+init_tree_desc(&t[1],remote->buffer,remote->size);++if(unpack_trees(2,t,&opts)){+rollback_lock_file(lock_file);+return-1;+}++if(write_locked_index(&the_index,lock_file,COMMIT_LOCK))+die(_("unable to write new index file"));++return0;+}++/**+*Cleantheindexwithouttouchingentriesthatarenotmodifiedbetween+*`head`and`remote`.+*/+staticintclean_index(constunsignedchar*head,constunsignedchar*remote)+{+structlock_file*lock_file=xcalloc(1,sizeof(structlock_file));+structtree*head_tree,*remote_tree,*index_tree;+unsignedcharindex[GIT_SHA1_RAWSZ];+structpathspecpathspec;++head_tree=parse_tree_indirect(head);+if(!head_tree)+returnerror(_("Could not parse object '%s'."),sha1_to_hex(head));++remote_tree=parse_tree_indirect(remote);+if(!remote_tree)+returnerror(_("Could not parse object '%s'."),sha1_to_hex(remote));++read_cache_unmerged();++if(fast_forward_to(head_tree,head_tree,1))+return-1;++if(write_cache_as_tree(index,0,NULL))+return-1;++index_tree=parse_tree_indirect(index);+if(!index_tree)+returnerror(_("Could not parse object '%s'."),sha1_to_hex(index));++if(fast_forward_to(index_tree,remote_tree,0))+return-1;++memset(&pathspec,0,sizeof(pathspec));++hold_locked_index(lock_file,1);++if(read_tree(remote_tree,0,&pathspec)){+rollback_lock_file(lock_file);+return-1;+}++if(write_locked_index(&the_index,lock_file,COMMIT_LOCK))+die(_("unable to write new index file"));++remove_branch_state();++return0;+}++/**+*Resumethecurrentamsessionbyskippingthecurrentpatch.+*/+staticvoidam_skip(structam_state*state)+{+unsignedcharhead[GIT_SHA1_RAWSZ];++if(get_sha1("HEAD",head))+hashcpy(head,EMPTY_TREE_SHA1_BIN);++if(clean_index(head,head))+die(_("failed to clean index"));++am_next(state);+am_run(state);+}++/***parse_options()callbackthatvalidatesandsetsopt->valuetothe*PATCH_FORMAT_*enumvaluecorrespondingto`arg`.*/
@@ -899,7 +1010,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)constchar*constusage[]={N_("git am [options] [(<mbox>|<Maildir>)...]"),-N_("git am [options] --continue"),+N_("git am [options] (--continue | --skip)"),NULL};
@@ -913,6 +1024,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)OPT_CMDMODE('r',"resolved",&resume,N_("synonyms for --continue"),RESUME_RESOLVED),+OPT_CMDMODE(0,"skip",&resume,+N_("skip the current patch"),+RESUME_SKIP),OPT_END()};
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am
would error out if the user gave it mbox(s) on the command-line, but
there was a session in progress.
Since c95b138 (Fix git-am safety checks, 2006-09-15), git-am would
detect if the user attempted to feed it a mbox via stdin, by checking if
stdin is not a tty and there is no resume command given.
Re-implement the above two safety checks.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
@@ -1147,9 +1147,24 @@ int cmd_am(int argc, const char **argv, const char *prefix)if(read_index_preload(&the_index,NULL)<0)die(_("failed to read the index"));-if(am_in_progress(&state))+if(am_in_progress(&state)){+/*+*Catchusererrortofeeduspatcheswhenthereisasession+*inprogress:+*+*1.mboxpath(s)areprovidedonthecommand-line.+*2.stdinisnotatty:theuseristryingtofeedusapatch+*fromstandardinput.Thisissomewhatunreliable--stdin+*couldbe/dev/nullforexampleandthecallerdidnot+*intendtofeedusapatchbutwantedtocontinue+*unattended.+*/+if(argc||(resume==RESUME_FALSE&&!isatty(0)))+die(_("previous rebase directory %s still exists but mbox given."),+state.dir);+am_load(&state);-else{+}else{structargv_arraypaths=ARGV_ARRAY_INIT;inti;
Since 0e987a1 (am, rebase: teach quiet option, 2009-06-16), git-am
supported the --quiet option, and when told to be quiet, would only
speak on failure. Re-implement this by introducing the say() function,
which works like fprintf_ln(), but would only write to the stream when
state->quiet is false.
Signed-off-by: Paul Tan <redacted>
---
Notes:
v5
* Removed GIT_QUIET environment variable, as it turns out
git-sh-setup.sh will reset it, which means the user's environment
cannot affect it.
builtin/am.c | 30 +++++++++++++++++++++++++++---
1 file changed, 27 insertions(+), 3 deletions(-)
@@ -778,7 +801,7 @@ static void do_commit(const struct am_state *state)commit_list_insert(lookup_commit(parent),&parents);}else{ptr=NULL;-fprintf_ln(stderr,_("applying to an empty history"));+say(state,stderr,_("applying to an empty history"));}author=fmt_ident(state->author_name,state->author_email,
@@ -873,7 +896,7 @@ static void am_resolve(struct am_state *state)die(_("cannot resume: %s does not exist."),am_path(state,"author-script"));-printf_ln(_("Applying: %.*s"),linelen(state->msg),state->msg);+say(state,stdout,_("Applying: %.*s"),linelen(state->msg),state->msg);if(!index_has_changes(NULL)){printf_ln(_("No changes - did you forget to use 'git add'?\n"
@@ -1105,6 +1128,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)};structoptionoptions[]={+OPT__QUIET(&state.quiet,N_("be quiet")),OPT_CALLBACK(0,"patch-format",&patch_format,N_("format"),N_("format the patch(es) are in"),parse_opt_patchformat),
Since 3e5057a (git am --abort, 2008-07-16), git-am supported the --abort
option that will rewind HEAD back to the original commit. Re-implement
this through am_abort().
Since 7b3b7e3 (am --abort: keep unrelated commits since the last failure
and warn, 2010-12-21), to prevent commits made since the last failure
from being lost, git-am will not rewind HEAD back to the original
commit if HEAD moved since the last failure. Re-implement this through
safe_to_abort().
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 99 insertions(+), 3 deletions(-)
@@ -982,6 +1003,74 @@ static void am_skip(struct am_state *state)}/**+*ReturnstrueifitissafetoresetHEADtotheORIG_HEAD,falseotherwise.+*+*ItisnotsafetoresetHEADwhen:+*1.git-ampreviouslyfailedbecausetheindexwasdirty.+*2.HEADhasmovedsincegit-ampreviouslyfailed.+*/+staticintsafe_to_abort(conststructam_state*state)+{+structstrbufsb=STRBUF_INIT;+unsignedcharabort_safety[GIT_SHA1_RAWSZ],head[GIT_SHA1_RAWSZ];++if(file_exists(am_path(state,"dirtyindex")))+return0;++if(read_state_file(&sb,state,"abort-safety",1)>0){+if(get_sha1_hex(sb.buf,abort_safety))+die(_("could not parse %s"),am_path(state,"abort_safety"));+}else+hashclr(abort_safety);++if(get_sha1("HEAD",head))+hashclr(head);++if(!hashcmp(head,abort_safety))+return1;++error(_("You seem to have moved HEAD since the last 'am' failure.\n"+"Not rewinding to ORIG_HEAD"));++return0;+}++/**+*Abortsthecurrentamsessionifitissafetodoso.+*/+staticvoidam_abort(structam_state*state)+{+unsignedcharcurr_head[GIT_SHA1_RAWSZ],orig_head[GIT_SHA1_RAWSZ];+inthas_curr_head,has_orig_head;+constchar*curr_branch;++if(!safe_to_abort(state)){+am_destroy(state);+return;+}++curr_branch=resolve_refdup("HEAD",0,curr_head,NULL);+has_curr_head=!is_null_sha1(curr_head);+if(!has_curr_head)+hashcpy(curr_head,EMPTY_TREE_SHA1_BIN);++has_orig_head=!get_sha1("ORIG_HEAD",orig_head);+if(!has_orig_head)+hashcpy(orig_head,EMPTY_TREE_SHA1_BIN);++clean_index(curr_head,orig_head);++if(has_orig_head)+update_ref("am --abort","HEAD",orig_head,+has_curr_head?curr_head:NULL,0,+UPDATE_REFS_DIE_ON_ERR);+elseif(curr_branch)+delete_ref(curr_branch,NULL,REF_NODEREF);++am_destroy(state);+}++/***parse_options()callbackthatvalidatesandsetsopt->valuetothe*PATCH_FORMAT_*enumvaluecorrespondingto`arg`.*/
@@ -1010,7 +1100,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)constchar*constusage[]={N_("git am [options] [(<mbox>|<Maildir>)...]"),-N_("git am [options] (--continue | --skip)"),+N_("git am [options] (--continue | --skip | --abort)"),NULL};
@@ -1027,6 +1117,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)OPT_CMDMODE(0,"skip",&resume,N_("skip the current patch"),RESUME_SKIP),+OPT_CMDMODE(0,"abort",&resume,+N_("restore the original branch and abort the patching operation."),+RESUME_ABORT),OPT_END()};
Since ced9456 (Give the user a hint for how to continue in the case that
git-am fails because it requires user intervention, 2006-05-02), git-am
prints additional information on how the user can re-invoke git-am to
resume patch application after resolving the failure. Re-implement this
through the die_user_resolve() function.
Since cc12005 (Make git rebase interactive help match documentation.,
2006-05-13), git-am supports the --resolvemsg option which is used by
git-rebase to override the message printed out when git-am fails.
Re-implement this option.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 32 ++++++++++++++++++++++++++++----
1 file changed, 28 insertions(+), 4 deletions(-)
@@ -668,6 +671,25 @@ static int index_has_changes(struct strbuf *sb)}/**+*Dieswithauser-friendlymessageonhowtoproceedafterresolvingthe+*problem.Thismessagecanbeoverriddenwithstate->resolvemsg.+*/+staticvoidNORETURNdie_user_resolve(conststructam_state*state)+{+if(state->resolvemsg){+printf_ln("%s",state->resolvemsg);+}else{+constchar*cmdline="git am";++printf_ln(_("When you have resolved this problem, run \"%s --continue\"."),cmdline);+printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."),cmdline);+printf_ln(_("To restore the original branch and stop patching, run \"%s --abort\"."),cmdline);+}++exit(128);+}++/***Parses`mail`usinggit-mailinfo,extractingitspatchandauthorshipinfo.*state->msgwillbesettothepatchmessage.state->author_name,*state->author_emailandstate->author_datewillbesettothepatchauthor's
@@ -727,7 +749,7 @@ static int parse_mail(struct am_state *state, const char *mail)if(is_empty_file(am_path(state,"patch"))){printf_ln(_("Patch is empty. Was it split wrong?"));-exit(128);+die_user_resolve(state);}strbuf_addstr(&msg,"\n\n");
@@ -868,7 +890,7 @@ static void am_run(struct am_state *state)printf_ln(_("The copy of the patch that failed is found in: %s"),am_path(state,"patch"));-exit(128);+die_user_resolve(state);}do_commit(state);
@@ -902,13 +924,13 @@ static void am_resolve(struct am_state *state)printf_ln(_("No changes - did you forget to use 'git add'?\n""If there is nothing left to stage, chances are that something else\n""already introduced the same changes; you might want to skip this patch."));-exit(128);+die_user_resolve(state);}if(unmerged_cache()){printf_ln(_("You still have unmerged paths in your index.\n""Did you forget to use 'git add'?"));-exit(128);+die_user_resolve(state);}do_commit(state);
@@ -1132,6 +1154,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)OPT_CALLBACK(0,"patch-format",&patch_format,N_("format"),N_("format the patch(es) are in"),parse_opt_patchformat),+OPT_STRING(0,"resolvemsg",&state.resolvemsg,NULL,+N_("override error message when patch failure occurs")),OPT_CMDMODE(0,"continue",&resume,N_("continue applying patches after resolving a conflict"),RESUME_RESOLVED),
Should git-am terminate unexpectedly between the point where the state
directory is created, but the "next" and "last" files are not written
yet, a stray state directory will be left behind.
As such, since b141f3c (am: handle stray $dotest directory, 2013-06-15),
git-am.sh explicitly recognizes such a stray directory, and allows the
user to remove it with am --abort.
Re-implement this feature in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
@@ -1531,6 +1531,23 @@ int cmd_am(int argc, const char **argv, const char *prefix)structargv_arraypaths=ARGV_ARRAY_INIT;inti;+/*+*Handlestraystatedirectoryintheindependent-runcase.In+*the--rebasingcase,itisuptothecallertotakecareof+*straydirectories.+*/+if(file_exists(state.dir)&&!state.rebasing){+if(resume==RESUME_ABORT){+am_destroy(&state);+am_state_release(&state);+return0;+}++die(_("Stray %s directory found.\n"+"Use \"git am --abort\" to remove it."),+state.dir);+}+if(resume)die(_("Resolve operation not in progress, we are not resuming."));
Since 017678b (am/mailinfo: Disable scissors processing by default,
2009-08-26), git-am supported the --[no-]scissors option, passing it to
git-mailinfo.
Re-implement support for this option in builtin/am.c.
Since the default setting of --scissors in git-mailinfo can be
configured with mailinfo.scissors (and perhaps through other settings in
the future), to be safe we make an explicit distinction between
SCISSORS_UNSET, SCISSORS_TRUE and SCISSORS_FALSE.
Signed-off-by: Paul Tan <redacted>
---
Notes:
v5
* Previously, SCISSORS_FALSE was 1 while SCISSORS_TRUE was 0. This
should be the other way around.
builtin/am.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
@@ -74,6 +74,12 @@ enum keep_type {KEEP_NON_PATCH/* pass -b flag to git-mailinfo */};+enumscissors_type{+SCISSORS_UNSET=-1,+SCISSORS_FALSE=0,/* pass --no-scissors to git-mailinfo */+SCISSORS_TRUE/* pass --scissors to git-mailinfo */+};+structam_state{/* state directory path */char*dir;
@@ -106,6 +112,9 @@ struct am_state {/* pass -m flag to git-mailinfo */intmessage_id;+/* one of the enum scissors_type values */+intscissors;+/* override error message when patch failure occurs */constchar*resolvemsg;
@@ -642,6 +661,22 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,write_file(am_path(state,"messageid"),1,state->message_id?"t":"f");+switch(state->scissors){+caseSCISSORS_UNSET:+str="";+break;+caseSCISSORS_FALSE:+str="f";+break;+caseSCISSORS_TRUE:+str="t";+break;+default:+die("BUG: invalid value for state->scissors");+}++write_file(am_path(state,"scissors"),1,"%s",str);+if(state->rebasing)write_file(am_path(state,"rebasing"),1,"%s","");else
@@ -831,6 +866,19 @@ static int parse_mail(struct am_state *state, const char *mail)if(state->message_id)argv_array_push(&cp.args,"-m");+switch(state->scissors){+caseSCISSORS_UNSET:+break;+caseSCISSORS_FALSE:+argv_array_push(&cp.args,"--no-scissors");+break;+caseSCISSORS_TRUE:+argv_array_push(&cp.args,"--scissors");+break;+default:+die("BUG: invalid value for state->scissors");+}+argv_array_push(&cp.args,am_path(state,"msg"));argv_array_push(&cp.args,am_path(state,"patch"));
@@ -1560,6 +1608,8 @@ int cmd_am(int argc, const char **argv, const char *prefix){OPTION_SET_INT,0,"no-keep-cr",&keep_cr,NULL,N_("do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"),PARSE_OPT_NOARG|PARSE_OPT_NONEG,NULL,0},+OPT_BOOL('c',"scissors",&state.scissors,+N_("strip everything before a scissors line")),OPT_CALLBACK(0,"patch-format",&patch_format,N_("format"),N_("format the patch(es) are in"),parse_opt_patchformat),
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sh supported the -u,--utf8 option. If set, the -u option will be
passed to git-mailinfo to re-code the commit log message and authorship
in the charset specified by i18n.commitencoding. If unset, the -n option
will be passed to git-mailinfo, which disables the re-encoding.
Since d84029b (--utf8 is now default for 'git-am', 2007-01-08), --utf8
is specified by default in git-am.sh.
Re-implement the above in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
@@ -1464,6 +1474,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)OPT__QUIET(&state.quiet,N_("be quiet")),OPT_BOOL('s',"signoff",&state.append_signoff,N_("add a Signed-off-by line to the commit message")),+OPT_BOOL('u',"utf8",&state.utf8,+N_("recode into utf8 (default)")),OPT_CALLBACK(0,"patch-format",&patch_format,N_("format"),N_("format the patch(es) are in"),parse_opt_patchformat),
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sh supported the -k/--keep option to pass the -k option to
git-mailsplit.
Since f7e5ea1 (am: learn passing -b to mailinfo, 2012-01-16), git-am.sh
supported the --keep-non-patch option to pass the -b option to
git-mailsplit.
Re-implement these two options in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 52 insertions(+)
@@ -68,6 +68,12 @@ enum patch_format {PATCH_FORMAT_MBOX};+enumkeep_type{+KEEP_FALSE=0,+KEEP_TRUE,/* pass -k flag to git-mailinfo */+KEEP_NON_PATCH/* pass -b flag to git-mailinfo */+};+structam_state{/* state directory path */char*dir;
@@ -94,6 +100,9 @@ struct am_state {intutf8;+/* one of the enum keep_type values */+intkeep;+/* override error message when patch failure occurs */constchar*resolvemsg;
@@ -588,6 +606,22 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,write_file(am_path(state,"utf8"),1,state->utf8?"t":"f");+switch(state->keep){+caseKEEP_FALSE:+str="f";+break;+caseKEEP_TRUE:+str="t";+break;+caseKEEP_NON_PATCH:+str="b";+break;+default:+die("BUG: invalid value for state->keep");+}++write_file(am_path(state,"keep"),1,"%s",str);+if(state->rebasing)write_file(am_path(state,"rebasing"),1,"%s","");else
@@ -760,6 +794,20 @@ static int parse_mail(struct am_state *state, const char *mail)argv_array_push(&cp.args,"mailinfo");argv_array_push(&cp.args,state->utf8?"-u":"-n");++switch(state->keep){+caseKEEP_FALSE:+break;+caseKEEP_TRUE:+argv_array_push(&cp.args,"-k");+break;+caseKEEP_NON_PATCH:+argv_array_push(&cp.args,"-b");+break;+default:+die("BUG: invalid value for state->keep");+}+argv_array_push(&cp.args,am_path(state,"msg"));argv_array_push(&cp.args,am_path(state,"patch"));
@@ -1476,6 +1524,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)N_("add a Signed-off-by line to the commit message")),OPT_BOOL('u',"utf8",&state.utf8,N_("recode into utf8 (default)")),+OPT_SET_INT('k',"keep",&state.keep,+N_("pass -k flag to git-mailinfo"),KEEP_TRUE),+OPT_SET_INT(0,"keep-non-patch",&state.keep,+N_("pass -b flag to git-mailinfo"),KEEP_NON_PATCH),OPT_CALLBACK(0,"patch-format",&patch_format,N_("format"),N_("format the patch(es) are in"),parse_opt_patchformat),
Since 5e835ca (rebase: do not munge commit log message, 2008-04-16),
git am --rebasing no longer gets the commit log message from the patch,
but reads it directly from the commit identified by the "From " header
line.
Since 43c2325 (am: use get_author_ident_from_commit instead of mailinfo
when rebasing, 2010-06-16), git am --rebasing also gets the author name,
email and date directly from the commit.
Since 0fbb95d (am: don't call mailinfo if $rebasing, 2012-06-26), git am
--rebasing does not use git-mailinfo to get the patch body, but rather
generates it directly from the commit itself.
The above 3 commits introduced a separate parse_mail() code path in
git-am.sh's --rebasing mode that bypasses git-mailinfo. Re-implement
this code path in builtin/am.c as parse_mail_rebase().
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 132 insertions(+), 2 deletions(-)
@@ -816,6 +818,129 @@ finish:}/**+*Setscommit_idtothecommithashwherethemailwasgeneratedfrom.+*Returns0onsuccess,-1onfailure.+*/+staticintget_mail_commit_sha1(unsignedchar*commit_id,constchar*mail)+{+structstrbufsb=STRBUF_INIT;+FILE*fp=xfopen(mail,"r");+constchar*x;++if(strbuf_getline(&sb,fp,'\n'))+return-1;++if(!skip_prefix(sb.buf,"From ",&x))+return-1;++if(get_sha1_hex(x,commit_id)<0)+return-1;++strbuf_release(&sb);+fclose(fp);+return0;+}++/**+*Setsstate->msg,state->author_name,state->author_email,state->author_date+*tothecommit'srespectiveinfo.+*/+staticvoidget_commit_info(structam_state*state,structcommit*commit)+{+constchar*buffer,*ident_line,*author_date,*msg;+size_tident_len;+structident_splitident_split;+structstrbufsb=STRBUF_INIT;++buffer=logmsg_reencode(commit,NULL,get_commit_output_encoding());++ident_line=find_commit_header(buffer,"author",&ident_len);++if(split_ident_line(&ident_split,ident_line,ident_len)<0){+strbuf_add(&sb,ident_line,ident_len);+die(_("invalid ident line: %s"),sb.buf);+}++assert(!state->author_name);+if(ident_split.name_begin){+strbuf_add(&sb,ident_split.name_begin,+ident_split.name_end-ident_split.name_begin);+state->author_name=strbuf_detach(&sb,NULL);+}else+state->author_name=xstrdup("");++assert(!state->author_email);+if(ident_split.mail_begin){+strbuf_add(&sb,ident_split.mail_begin,+ident_split.mail_end-ident_split.mail_begin);+state->author_email=strbuf_detach(&sb,NULL);+}else+state->author_email=xstrdup("");++author_date=show_ident_date(&ident_split,DATE_NORMAL);+strbuf_addstr(&sb,author_date);+assert(!state->author_date);+state->author_date=strbuf_detach(&sb,NULL);++assert(!state->msg);+msg=strstr(buffer,"\n\n");+if(!msg)+die(_("unable to parse commit %s"),sha1_to_hex(commit->object.sha1));+state->msg=xstrdup(msg+2);+state->msg_len=strlen(state->msg);+}++/**+*Writes`commit`asapatchtothestatedirectory's"patch"file.+*/+staticvoidwrite_commit_patch(conststructam_state*state,structcommit*commit)+{+structrev_inforev_info;+FILE*fp;++fp=xfopen(am_path(state,"patch"),"w");+init_revisions(&rev_info,NULL);+rev_info.diff=1;+rev_info.abbrev=0;+rev_info.disable_stdin=1;+rev_info.show_root_diff=1;+rev_info.diffopt.output_format=DIFF_FORMAT_PATCH;+rev_info.no_commit_id=1;+DIFF_OPT_SET(&rev_info.diffopt,BINARY);+DIFF_OPT_SET(&rev_info.diffopt,FULL_INDEX);+rev_info.diffopt.use_color=0;+rev_info.diffopt.file=fp;+rev_info.diffopt.close_file=1;+add_pending_object(&rev_info,&commit->object,"");+diff_setup_done(&rev_info.diffopt);+log_tree_commit(&rev_info,commit);+}++/**+*Likeparse_mail(),butparsesthemailbylookingupitscommitID+*directly.Thisisusedin--rebasingmodetobypassgit-mailinfo'smunging+*ofpatches.+*+*Willalwaysreturn0asthepatchshouldneverbeskipped.+*/+staticintparse_mail_rebase(structam_state*state,constchar*mail)+{+structcommit*commit;+unsignedcharcommit_sha1[GIT_SHA1_RAWSZ];++if(get_mail_commit_sha1(commit_sha1,mail)<0)+die(_("could not parse %s"),mail);++commit=lookup_commit_or_die(commit_sha1,mail);++get_commit_info(state,commit);++write_commit_patch(state,commit);++return0;+}++/***Appliescurrentpatchwithgit-apply.Returns0onsuccess,-1otherwise.If*`index_file`isnotNULL,thepatchwillbeappliedtothatindex.*/
@@ -1019,12 +1144,17 @@ static void am_run(struct am_state *state)while(state->cur<=state->last){constchar*mail=am_path(state,msgnum(state));-intapply_status;+intapply_status,skip;if(!file_exists(mail))gotonext;-if(parse_mail(state,mail))+if(state->rebasing)+skip=parse_mail_rebase(state,mail);+else+skip=parse_mail(state,mail);++if(skip)gotonext;/* mail should be skipped */write_author_script(state);
Since a078f73 (git-am: add --message-id/--no-message-id, 2014-11-25),
git-am.sh supported the --[no-]message-id options, and the
"am.messageid" setting which specifies the default option.
--[no-]message-id tells git-am whether or not the -m option should be
passed to git-mailinfo.
Re-implement this option in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
@@ -103,6 +103,9 @@ struct am_state {/* one of the enum keep_type values */intkeep;+/* pass -m flag to git-mailinfo */+intmessage_id;+/* override error message when patch failure occurs */constchar*resolvemsg;
@@ -808,6 +818,9 @@ static int parse_mail(struct am_state *state, const char *mail)die("BUG: invalid value for state->keep");}+if(state->message_id)+argv_array_push(&cp.args,"-m");+argv_array_push(&cp.args,am_path(state,"msg"));argv_array_push(&cp.args,am_path(state,"patch"));
@@ -1528,6 +1541,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)N_("pass -k flag to git-mailinfo"),KEEP_TRUE),OPT_SET_INT(0,"keep-non-patch",&state.keep,N_("pass -b flag to git-mailinfo"),KEEP_NON_PATCH),+OPT_BOOL('m',"message-id",&state.message_id,+N_("pass -m flag to git-mailinfo")),OPT_CALLBACK(0,"patch-format",&patch_format,N_("format"),N_("format the patch(es) are in"),parse_opt_patchformat),
Since 0c15cc9 (git-am: --resolved., 2005-11-16), git-am supported
resuming from a failed patch application. The user will manually apply
the patch, and the run git am --resolved which will then commit the
resulting index. Re-implement this feature by introducing am_resolve().
Since it makes no sense for the user to run am --resolved when there is
no session in progress, we error out in this case.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 62 insertions(+), 1 deletion(-)
@@ -836,6 +836,42 @@ next:}/**+*Resumethecurrentamsessionafterpatchapplicationfailure.Theuserdid+*allthehardwork,andwedonothavetodoanypatchapplication.Just+*trustandcommitwhattheuserhasintheindexandworkingtree.+*/+staticvoidam_resolve(structam_state*state)+{+if(!state->msg)+die(_("cannot resume: %s does not exist."),+am_path(state,"final-commit"));++if(!state->author_name||!state->author_email||!state->author_date)+die(_("cannot resume: %s does not exist."),+am_path(state,"author-script"));++printf_ln(_("Applying: %.*s"),linelen(state->msg),state->msg);++if(!index_has_changes(NULL)){+printf_ln(_("No changes - did you forget to use 'git add'?\n"+"If there is nothing left to stage, chances are that something else\n"+"already introduced the same changes; you might want to skip this patch."));+exit(128);+}++if(unmerged_cache()){+printf_ln(_("You still have unmerged paths in your index.\n"+"Did you forget to use 'git add'?"));+exit(128);+}++do_commit(state);++am_next(state);+am_run(state);+}++/***parse_options()callbackthatvalidatesandsetsopt->valuetothe*PATCH_FORMAT_*enumvaluecorrespondingto`arg`.*/
@@ -850,13 +886,20 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, intreturn0;}+enumresume_mode{+RESUME_FALSE=0,+RESUME_RESOLVED+};+intcmd_am(intargc,constchar**argv,constchar*prefix){structam_statestate;intpatch_format=PATCH_FORMAT_UNKNOWN;+enumresume_moderesume=RESUME_FALSE;constchar*constusage[]={N_("git am [options] [(<mbox>|<Maildir>)...]"),+N_("git am [options] --continue"),NULL};
@@ -864,6 +907,12 @@ int cmd_am(int argc, const char **argv, const char *prefix)OPT_CALLBACK(0,"patch-format",&patch_format,N_("format"),N_("format the patch(es) are in"),parse_opt_patchformat),+OPT_CMDMODE(0,"continue",&resume,+N_("continue applying patches after resolving a conflict"),+RESUME_RESOLVED),+OPT_CMDMODE('r',"resolved",&resume,+N_("synonyms for --continue"),+RESUME_RESOLVED),OPT_END()};
@@ -897,6 +946,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)structargv_arraypaths=ARGV_ARRAY_INIT;inti;+if(resume)+die(_("Resolve operation not in progress, we are not resuming."));+for(i=0;i<argc;i++){if(is_absolute_path(argv[i])||!prefix)argv_array_push(&paths,argv[i]);
Since a79ec62 (git-am: Add --ignore-date option, 2009-01-24), git-am.sh
supported the --ignore-date option, and would use the current timestamp
instead of the one provided in the patch if the option was set.
Re-implement this option in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
@@ -1674,6 +1677,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)OPT_CMDMODE(0,"abort",&resume,N_("restore the original branch and abort the patching operation."),RESUME_ABORT),+OPT_BOOL(0,"ignore-date",&state.ignore_date,+N_("use current timestamp for author date")),OPT_HIDDEN_BOOL(0,"rebasing",&state.rebasing,N_("(internal use for git-rebase)")),OPT_END()
Since ad2c928 (git-am: Add command line parameter `--keep-cr` passing it
to git-mailsplit, 2010-02-27), git-am.sh supported the --keep-cr option
and would pass it to git-mailsplit.
Since e80d4cb (git-am: Add am.keepcr and --no-keep-cr to override it,
2010-02-27), git-am.sh supported the am.keepcr config setting, which
controls whether --keep-cr is on by default.
Re-implement the above in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 29 +++++++++++++++++++++++------
1 file changed, 23 insertions(+), 6 deletions(-)
@@ -1543,6 +1554,12 @@ int cmd_am(int argc, const char **argv, const char *prefix)N_("pass -b flag to git-mailinfo"),KEEP_NON_PATCH),OPT_BOOL('m',"message-id",&state.message_id,N_("pass -m flag to git-mailinfo")),+{OPTION_SET_INT,0,"keep-cr",&keep_cr,NULL,+N_("pass --keep-cr flag to git-mailsplit for mbox format"),+PARSE_OPT_NOARG|PARSE_OPT_NONEG,NULL,1},+{OPTION_SET_INT,0,"no-keep-cr",&keep_cr,NULL,+N_("do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"),+PARSE_OPT_NOARG|PARSE_OPT_NONEG,NULL,0},OPT_CALLBACK(0,"patch-format",&patch_format,N_("format"),N_("format the patch(es) are in"),parse_opt_patchformat),
git-am.sh recognizes some of git-apply's options, and would pass them to
git-apply:
* --whitespace, since 8c31cb8 (git-am: --whitespace=x option.,
2006-02-28)
* -C, since 67dad68 (add -C[NUM] to git-am, 2007-02-08)
* -p, since 2092a1f (Teach git-am to pass -p option down to git-apply,
2007-02-11)
* --directory, since b47dfe9 (git-am: add --directory=<dir> option,
2009-01-11)
* --reject, since b80da42 (git-am: implement --reject option passed to
git-apply, 2009-01-23)
* --ignore-space-change, --ignore-whitespace, since 86c91f9 (git apply:
option to ignore whitespace differences, 2009-08-04)
* --exclude, since 77e9e49 (am: pass exclude down to apply, 2011-08-03)
* --include, since 58725ef (am: support --include option, 2012-03-28)
* --reject, since b80da42 (git-am: implement --reject option passed to
git-apply, 2009-01-23)
Re-implement support for these options in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 47 insertions(+)
@@ -1610,9 +1630,36 @@ int cmd_am(int argc, const char **argv, const char *prefix)PARSE_OPT_NOARG|PARSE_OPT_NONEG,NULL,0},OPT_BOOL('c',"scissors",&state.scissors,N_("strip everything before a scissors line")),+OPT_PASSTHRU_ARGV(0,"whitespace",&state.git_apply_opts,N_("action"),+N_("pass it through git-apply"),+0),+OPT_PASSTHRU_ARGV(0,"ignore-space-change",&state.git_apply_opts,NULL,+N_("pass it through git-apply"),+PARSE_OPT_NOARG),+OPT_PASSTHRU_ARGV(0,"ignore-whitespace",&state.git_apply_opts,NULL,+N_("pass it through git-apply"),+PARSE_OPT_NOARG),+OPT_PASSTHRU_ARGV(0,"directory",&state.git_apply_opts,N_("root"),+N_("pass it through git-apply"),+0),+OPT_PASSTHRU_ARGV(0,"exclude",&state.git_apply_opts,N_("path"),+N_("pass it through git-apply"),+0),+OPT_PASSTHRU_ARGV(0,"include",&state.git_apply_opts,N_("path"),+N_("pass it through git-apply"),+0),+OPT_PASSTHRU_ARGV('C',NULL,&state.git_apply_opts,N_("n"),+N_("pass it through git-apply"),+0),+OPT_PASSTHRU_ARGV('p',NULL,&state.git_apply_opts,N_("num"),+N_("pass it through git-apply"),+0),OPT_CALLBACK(0,"patch-format",&patch_format,N_("format"),N_("format the patch(es) are in"),parse_opt_patchformat),+OPT_PASSTHRU_ARGV(0,"reject",&state.git_apply_opts,NULL,+N_("pass it through git-apply"),+PARSE_OPT_NOARG),OPT_STRING(0,"resolvemsg",&state.resolvemsg,NULL,N_("override error message when patch failure occurs")),OPT_CMDMODE(0,"continue",&resume,
Since 3b4e395 (am: add the --gpg-sign option, 2014-02-01), git-am.sh
supported the --gpg-sign option, and would pass it to git-commit-tree,
thus GPG-signing the commit object.
Re-implement this option in builtin/am.c.
git-commit-tree would also sign the commit by default if the
commit.gpgsign setting is true. Since we do not run commit-tree, we
re-implement this behavior by handling the commit.gpgsign setting
ourselves.
Helped-by: Stefan Beller [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
Notes:
v5
* Renamed local "sign_commit" variable in am_state_init() to "gpgsign"
to aid code review.
builtin/am.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
@@ -1688,6 +1695,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)N_("lie about committer date")),OPT_BOOL(0,"ignore-date",&state.ignore_date,N_("use current timestamp for author date")),+{OPTION_STRING,'S',"gpg-sign",&state.sign_commit,N_("key-id"),+N_("GPG-sign commits"),+PARSE_OPT_OPTARG,NULL,(intptr_t)""},OPT_HIDDEN_BOOL(0,"rebasing",&state.rebasing,N_("(internal use for git-rebase)")),OPT_END()
Since 96e1948 (rebase: invoke post-rewrite hook, 2010-03-12), git-am.sh
will invoke the post-rewrite hook after it successfully finishes
applying all the queued patches.
To do this, when parsing a mail to extract its patch and metadata, in
--rebasing mode git-am.sh will also store the original commit ID in the
$state_dir/original-commit file. Once it applies and commits the patch,
the original commit ID, and the new commit ID, will be appended to the
$state_dir/rewritten file.
Once all of the queued mail have been processed, git-am.sh will then
invoke the post-rewrite hook with the contents of the
$state_dir/rewritten file.
Re-implement this in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 55 insertions(+)
@@ -95,6 +95,9 @@ struct am_state {char*msg;size_tmsg_len;+/* when --rebasing, records the original commit the patch came from */+unsignedcharorig_commit[GIT_SHA1_RAWSZ];+/* number of digits in patch filename */intprec;
Since eb2151b (rebase: support automatic notes copying, 2010-03-12),
git-am.sh supported automatic notes copying in --rebasing mode by
invoking "git notes copy" once it has finished applying all the patches.
Re-implement this feature in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
@@ -514,6 +515,64 @@ static int run_post_rewrite_hook(const struct am_state *state)}/**+*Readsthestatedirectory's"rewritten"file,andcopiesnotesfromtheold+*commitslistedinthefiletotheirrewrittencommits.+*+*Returns0onsuccess,-1onfailure.+*/+staticintcopy_notes_for_rebase(conststructam_state*state)+{+structnotes_rewrite_cfg*c;+structstrbufsb=STRBUF_INIT;+constchar*invalid_line=_("Malformed input line: '%s'.");+constchar*msg="Notes added by 'git rebase'";+FILE*fp;+intret=0;++assert(state->rebasing);++c=init_copy_notes_for_rewrite("rebase");+if(!c)+return0;++fp=xfopen(am_path(state,"rewritten"),"r");++while(!strbuf_getline(&sb,fp,'\n')){+unsignedcharfrom_obj[GIT_SHA1_RAWSZ],to_obj[GIT_SHA1_RAWSZ];++if(sb.len!=GIT_SHA1_HEXSZ*2+1){+ret=error(invalid_line,sb.buf);+gotofinish;+}++if(get_sha1_hex(sb.buf,from_obj)){+ret=error(invalid_line,sb.buf);+gotofinish;+}++if(sb.buf[GIT_SHA1_HEXSZ]!=' '){+ret=error(invalid_line,sb.buf);+gotofinish;+}++if(get_sha1_hex(sb.buf+GIT_SHA1_HEXSZ+1,to_obj)){+ret=error(invalid_line,sb.buf);+gotofinish;+}++if(copy_note_for_rewrite(c,from_obj,to_obj))+ret=error(_("Failed to copy notes from '%s' to '%s'"),+sha1_to_hex(from_obj),sha1_to_hex(to_obj));+}++finish:+finish_copy_notes_for_rewrite(c,msg);+fclose(fp);+strbuf_release(&sb);+returnret;+}++/***DeterminesifthefilelookslikeapieceofRFC2822mailbygrabbingall*non-indentedlinesandcheckingiftheylookliketheybeginwithvalid*headerfieldnames.
Since 3f01ad6 (am: Add --committer-date-is-author-date option,
2009-01-22), git-am.sh implemented the --committer-date-is-author-date
option, which tells git-am to use the timestamp recorded in the email
message as both author and committer date.
Re-implement this option in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 9 +++++++++
1 file changed, 9 insertions(+)
@@ -1677,6 +1683,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)OPT_CMDMODE(0,"abort",&resume,N_("restore the original branch and abort the patching operation."),RESUME_ABORT),+OPT_BOOL(0,"committer-date-is-author-date",+&state.committer_date_is_author_date,+N_("lie about committer date")),OPT_BOOL(0,"ignore-date",&state.ignore_date,N_("use current timestamp for author date")),OPT_HIDDEN_BOOL(0,"rebasing",&state.rebasing,
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sg will invoke the pre-applypatch hook after applying the patch
to the index, but before a commit is made. Should the hook exit with a
non-zero status, git am will exit.
Re-implement this in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 3 +++
1 file changed, 3 insertions(+)
Since c574e68 (git-am foreign patch support: StGIT support, 2009-05-27),
git-am.sh supported converting StGit patches into RFC2822 mail patches
that can be parsed with git-mailinfo.
Implement this by introducing two functions in builtin/am.c:
stgit_patch_to_mail() and split_mail_conv().
stgit_patch_to_mail() is a callback function for split_mail_conv(), and
contains the logic for converting an StGit patch into an RFC2822 mail
patch.
split_mail_conv() implements the logic to go through each file in the
`paths` list, reading from stdin where specified, and calls the callback
function to write the converted patch to the corresponding output file
in the state directory. This interface should be generic enough to
support other foreign patch formats in the future.
Since 15ced75 (git-am foreign patch support: autodetect some patch
formats, 2009-05-27), git-am.sh is able to auto-detect StGit patches.
Re-implement this in builtin/am.c.
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
Notes:
v5
* Rewrite of the loop in str_isspace() to be clearer.
builtin/am.c | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 131 insertions(+), 1 deletion(-)
@@ -65,9 +65,22 @@ static int linelen(const char *msg)returnstrchrnul(msg,'\n')-msg;}+/**+*Returnstrueif`str`consistsofonlywhitespace,falseotherwise.+*/+staticintstr_isspace(constchar*str)+{+for(;*str;str++)+if(!isspace(*str))+return0;++return1;+}+enumpatch_format{PATCH_FORMAT_UNKNOWN=0,-PATCH_FORMAT_MBOX+PATCH_FORMAT_MBOX,+PATCH_FORMAT_STGIT};enumkeep_type{
@@ -646,6 +659,8 @@ static int detect_patch_format(const char **paths){enumpatch_formatret=PATCH_FORMAT_UNKNOWN;structstrbufl1=STRBUF_INIT;+structstrbufl2=STRBUF_INIT;+structstrbufl3=STRBUF_INIT;FILE*fp;/*
@@ -671,6 +686,23 @@ static int detect_patch_format(const char **paths)gotodone;}+strbuf_reset(&l2);+strbuf_getline_crlf(&l2,fp);+strbuf_reset(&l3);+strbuf_getline_crlf(&l3,fp);++/*+*IfthesecondlineisemptyandthethirdisaFrom,AuthororDate+*entry,thisislikelyanStGitpatch.+*/+if(l1.len&&!l2.len&&+(starts_with(l3.buf,"From:")||+starts_with(l3.buf,"Author:")||+starts_with(l3.buf,"Date:"))){+ret=PATCH_FORMAT_STGIT;+gotodone;+}+if(l1.len&&is_mail(fp)){ret=PATCH_FORMAT_MBOX;gotodone;
@@ -711,6 +743,100 @@ static int split_mail_mbox(struct am_state *state, const char **paths, int keep_}/**+*Callbacksignatureforsplit_mail_conv().Theforeignpatchshouldbe+*readfrom`in`,andtheconvertedpatch(inRFC2822mailformat)shouldbe+*writtento`out`.Return0onsuccess,or-1onfailure.+*/+typedefint(*mail_conv_fn)(FILE*out,FILE*in,intkeep_cr);++/**+*Calls`fn`foreachfilein`paths`toconverttheforeignpatchtothe+*RFC2822mailformatsuitableforparsingwithgit-mailinfo.+*+*Returns0onsuccess,-1onfailure.+*/+staticintsplit_mail_conv(mail_conv_fnfn,structam_state*state,+constchar**paths,intkeep_cr)+{+staticconstchar*stdin_only[]={"-",NULL};+inti;++if(!*paths)+paths=stdin_only;++for(i=0;*paths;paths++,i++){+FILE*in,*out;+constchar*mail;+intret;++if(!strcmp(*paths,"-"))+in=stdin;+else+in=fopen(*paths,"r");++if(!in)+returnerror(_("could not open '%s' for reading: %s"),+*paths,strerror(errno));++mail=mkpath("%s/%0*d",state->dir,state->prec,i+1);++out=fopen(mail,"w");+if(!out)+returnerror(_("could not open '%s' for writing: %s"),+mail,strerror(errno));++ret=fn(out,in,keep_cr);++fclose(out);+fclose(in);++if(ret)+returnerror(_("could not parse patch '%s'"),*paths);+}++state->cur=1;+state->last=i;+return0;+}++/**+*Asplit_mail_conv()callbackthatconvertsanStGitpatchtoanRFC2822+*messagesuitableforparsingwithgit-mailinfo.+*/+staticintstgit_patch_to_mail(FILE*out,FILE*in,intkeep_cr)+{+structstrbufsb=STRBUF_INIT;+intsubject_printed=0;++while(!strbuf_getline(&sb,in,'\n')){+constchar*str;++if(str_isspace(sb.buf))+continue;+elseif(skip_prefix(sb.buf,"Author:",&str))+fprintf(out,"From:%s\n",str);+elseif(starts_with(sb.buf,"From")||starts_with(sb.buf,"Date"))+fprintf(out,"%s\n",sb.buf);+elseif(!subject_printed){+fprintf(out,"Subject: %s\n",sb.buf);+subject_printed=1;+}else{+fprintf(out,"\n%s\n",sb.buf);+break;+}+}++strbuf_reset(&sb);+while(strbuf_fread(&sb,8192,in)>0){+fwrite(sb.buf,1,sb.len,out);+strbuf_reset(&sb);+}++strbuf_release(&sb);+return0;+}++/***Splitsalistoffiles/directoriesintoindividualemailpatches.Eachpath*in`paths`mustbeafile/directorythatisformattedaccordingto*`patch_format`.
Since 3041c32 (am: --rebasing, 2008-03-04), git-am.sh supported the
--rebasing option, which is used internally by git-rebase to tell git-am
that it is being used for its purpose. It would create the empty file
$state_dir/rebasing to help "completion" scripts tell if the ongoing
operation is am or rebase.
As of 0fbb95d (am: don't call mailinfo if $rebasing, 2012-06-26),
--rebasing also implies --3way as well.
Since a1549e1 (am: return control to caller, for housekeeping,
2013-05-12), git-am.sh would only clean up the state directory when it
is not --rebasing, instead deferring cleanup to git-rebase.sh.
Re-implement the above in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 31 +++++++++++++++++++++++++++----
1 file changed, 27 insertions(+), 4 deletions(-)
@@ -1330,6 +1351,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)OPT_CMDMODE(0,"abort",&resume,N_("restore the original branch and abort the patching operation."),RESUME_ABORT),+OPT_HIDDEN_BOOL(0,"rebasing",&state.rebasing,+N_("(internal use for git-rebase)")),OPT_END()};
Since c574e68 (git-am foreign patch support: StGIT support, 2009-05-27),
git-am.sh is able to read a single StGit series file and, for each StGit
patch listed in the file, convert the StGit patch into a RFC2822 mail
patch suitable for parsing with git-mailinfo, and queue them in the
state directory for applying.
Since 15ced75 (git-am foreign patch support: autodetect some patch
formats, 2009-05-27), git-am.sh is able to auto-detect StGit series
files by checking to see if the file starts with the string:
# This series applies on GIT commit
Re-implement the above in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 58 insertions(+), 1 deletion(-)
@@ -80,7 +80,8 @@ static int str_isspace(const char *str)enumpatch_format{PATCH_FORMAT_UNKNOWN=0,PATCH_FORMAT_MBOX,-PATCH_FORMAT_STGIT+PATCH_FORMAT_STGIT,+PATCH_FORMAT_STGIT_SERIES};enumkeep_type{
@@ -686,6 +687,11 @@ static int detect_patch_format(const char **paths)gotodone;}+if(starts_with(l1.buf,"# This series applies on GIT commit")){+ret=PATCH_FORMAT_STGIT_SERIES;+gotodone;+}+strbuf_reset(&l2);strbuf_getline_crlf(&l2,fp);strbuf_reset(&l3);
@@ -837,6 +843,53 @@ static int stgit_patch_to_mail(FILE *out, FILE *in, int keep_cr)}/**+*ThisfunctiononlysupportsasingleStGitseriesfilein`paths`.+*+*GivenanStGitseriesfile,convertstheStGitpatchesintheseriesinto+*RFC2822messagessuitableforparsingwithgit-mailinfo,andqueuesthemin+*thestatedirectory.+*+*Returns0onsuccess,-1onfailure.+*/+staticintsplit_mail_stgit_series(structam_state*state,constchar**paths,+intkeep_cr)+{+constchar*series_dir;+char*series_dir_buf;+FILE*fp;+structargv_arraypatches=ARGV_ARRAY_INIT;+structstrbufsb=STRBUF_INIT;+intret;++if(!paths[0]||paths[1])+returnerror(_("Only one StGIT patch series can be applied at once"));++series_dir_buf=xstrdup(*paths);+series_dir=dirname(series_dir_buf);++fp=fopen(*paths,"r");+if(!fp)+returnerror(_("could not open '%s' for reading: %s"),*paths,+strerror(errno));++while(!strbuf_getline(&sb,fp,'\n')){+if(*sb.buf=='#')+continue;/* skip comment lines */++argv_array_push(&patches,mkpath("%s/%s",series_dir,sb.buf));+}++fclose(fp);+strbuf_release(&sb);+free(series_dir_buf);++ret=split_mail_conv(stgit_patch_to_mail,state,patches.argv,keep_cr);++argv_array_clear(&patches);+returnret;+}++/***Splitsalistoffiles/directoriesintoindividualemailpatches.Eachpath*in`paths`mustbeafile/directorythatisformattedaccordingto*`patch_format`.
git-am.sh will call git-rerere at the following events:
* "git rerere" when a three-way merge fails to record the conflicted
automerge results. Since 8389b52 (git-rerere: reuse recorded resolve.,
2006-01-28)
* Since cb6020b (Teach --[no-]rerere-autoupdate option to merge,
revert and friends, 2009-12-04), git-am.sh supports the
--[no-]rerere-autoupdate option as well, and would pass it to
git-rerere.
* "git rerere" when --resolved, to record the hand resolution. Since
f131dd4 (rerere: record (or avoid misrecording) resolved, skipped or
aborted rebase/am, 2006-12-08)
* "git rerere clear" when --skip-ing. Since f131dd4 (rerere: record (or
avoid misrecording) resolved, skipped or aborted rebase/am,
2006-12-08)
* "git rerere clear" when --abort-ing. Since 3e5057a (git am --abort,
2008-07-16)
Re-implement the above in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
@@ -1352,6 +1355,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pao.verbosity=0;if(merge_recursive_generic(&o,our_tree,his_tree,1,bases,&result)){+rerere(state->allow_rerere_autoupdate);free(his_tree_name);returnerror(_("Failed to merge in the changes."));}
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sh will invoke the post-applypatch hook after the patch is
applied and a commit is made. The exit code of the hook is ignored.
Re-implement this in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 2 ++
1 file changed, 2 insertions(+)
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am
supported the --signoff option which will append a signoff at the end of
the commit messsage. Re-implement this feature in parse_mail() by
calling append_signoff() if the option is set.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
@@ -757,6 +765,9 @@ static int parse_mail(struct am_state *state, const char *mail)die_errno(_("could not read '%s'"),am_path(state,"msg"));stripspace(&msg,0);+if(state->append_signoff)+append_signoff(&msg,0,0);+assert(!state->author_name);state->author_name=strbuf_detach(&author_name,NULL);
@@ -1151,6 +1162,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)structoptionoptions[]={OPT__QUIET(&state.quiet,N_("be quiet")),+OPT_BOOL('s',"signoff",&state.append_signoff,+N_("add a Signed-off-by line to the commit message")),OPT_CALLBACK(0,"patch-format",&patch_format,N_("format"),N_("format the patch(es) are in"),parse_opt_patchformat),
Since 0cfd112 (am: preliminary support for hg patches, 2011-08-29),
git-am.sh could convert mercurial patches to an RFC2822 mail patch
suitable for parsing with git-mailinfo, and queue them in the state
directory for application.
Since 15ced75 (git-am foreign patch support: autodetect some patch
formats, 2009-05-27), git-am.sh was able to auto-detect mercurial
patches by checking if the file begins with the line:
# HG changeset patch
Re-implement the above in builtin/am.c.
Helped-by: Stefan Beller [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
Notes:
v5
* v4 had a math fail in the timestamp conversion. Fixed the math.
* In C89, it is implementation defined whether integer division rounds
towards 0 or towards negative infinity. To be safe, we do the
timestamp conversion with positive integers only, and then negate the
result appropriately.
builtin/am.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 73 insertions(+), 1 deletion(-)
A caller may wish to write a temporary index as a tree. However,
write_cache_as_tree() assumes that the index was read from, and will
write to, the default index file path. Introduce write_index_as_tree()
which removes this limitation by allowing the caller to specify its own
index_state and index file path.
Signed-off-by: Paul Tan <redacted>
---
cache-tree.c | 29 +++++++++++++++++------------
cache-tree.h | 1 +
2 files changed, 18 insertions(+), 12 deletions(-)
@@ -603,23 +603,23 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)*/lock_file=xcalloc(1,sizeof(structlock_file));-newfd=hold_locked_index(lock_file,1);+newfd=hold_lock_file_for_update(lock_file,index_path,LOCK_DIE_ON_ERROR);-entries=read_cache();+entries=read_index_from(index_state,index_path);if(entries<0)returnWRITE_TREE_UNREADABLE_INDEX;if(flags&WRITE_TREE_IGNORE_CACHE_TREE)-cache_tree_free(&(active_cache_tree));+cache_tree_free(&index_state->cache_tree);-if(!active_cache_tree)-active_cache_tree=cache_tree();+if(!index_state->cache_tree)+index_state->cache_tree=cache_tree();-was_valid=cache_tree_fully_valid(active_cache_tree);+was_valid=cache_tree_fully_valid(index_state->cache_tree);if(!was_valid){-if(cache_tree_update(&the_index,flags)<0)+if(cache_tree_update(index_state,flags)<0)returnWRITE_TREE_UNMERGED_INDEX;if(0<=newfd){-if(!write_locked_index(&the_index,lock_file,COMMIT_LOCK))+if(!write_locked_index(index_state,lock_file,COMMIT_LOCK))newfd=-1;}/* Not being able to write is fine -- we are only interested
@@ -631,14 +631,14 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)}if(prefix){-structcache_tree*subtree=-cache_tree_find(active_cache_tree,prefix);+structcache_tree*subtree;+subtree=cache_tree_find(index_state->cache_tree,prefix);if(!subtree)returnWRITE_TREE_PREFIX_ERROR;hashcpy(sha1,subtree->sha1);}else-hashcpy(sha1,active_cache_tree->sha1);+hashcpy(sha1,index_state->cache_tree->sha1);if(0<=newfd)rollback_lock_file(lock_file);
@@ -646,6 +646,11 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)return0;}+intwrite_cache_as_tree(unsignedchar*sha1,intflags,constchar*prefix)+{+returnwrite_index_as_tree(sha1,&the_index,get_index_file(),flags,prefix);+}+staticvoidprime_cache_tree_rec(structcache_tree*it,structtree*tree){structtree_descdesc;
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sh supported the --interactive mode. After parsing the patch mail
and extracting the patch, commit message and authorship info, an
interactive session will begin that allows the user to choose between:
* applying the patch
* applying the patch and all subsequent patches (by disabling
interactive mode in subsequent patches)
* skipping the patch
* editing the commit message
Since f89ad67 (Add [v]iew patch in git-am interactive., 2005-10-25),
git-am.sh --interactive also supported viewing the patch to be applied.
When --resolved-ing in --interactive mode, we need to take care to
update the patch with the contents of the index, such that the correct
patch will be displayed when the patch is viewed in interactive mode.
Re-implement the above in builtin/am.c
Signed-off-by: Paul Tan <redacted>
---
Notes:
Can't be tested because even with test_terminal isatty(0) still returns
false.
builtin/am.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 105 insertions(+), 1 deletion(-)
@@ -118,6 +119,8 @@ struct am_state {/* number of digits in patch filename */intprec;+intinteractive;+intthreeway;intquiet;
@@ -1212,7 +1215,7 @@ static void NORETURN die_user_resolve(const struct am_state *state)if(state->resolvemsg){printf_ln("%s",state->resolvemsg);}else{-constchar*cmdline="git am";+constchar*cmdline=state->interactive?"git am -i":"git am";printf_ln(_("When you have resolved this problem, run \"%s --continue\"."),cmdline);printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."),cmdline);
@@ -1681,6 +1714,65 @@ static void do_commit(const struct am_state *state)}/**+*Interactivelyprompttheuseronwhetherthecurrentpatchshouldbe+*applied.+*+*Returns0iftheuserchoosestoapplythepatch,1iftheuserchoosesto+*skipit.+*/+staticintdo_interactive(structam_state*state)+{+assert(state->msg);++if(!isatty(0))+die(_("cannot be interactive without stdin connected to a terminal."));++for(;;){+constchar*reply;++puts(_("Commit Body is:"));+puts("--------------------------");+printf("%s",state->msg);+puts("--------------------------");++/*+*TRANSLATORS:Makesuretoinclude[y],[n],[e],[v]and[a]+*inyourtranslation.TheprogramwillonlyacceptEnglish+*inputatthispoint.+*/+reply=git_prompt(_("Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all: "),PROMPT_ECHO);++if(!reply){+continue;+}elseif(*reply=='y'||*reply=='Y'){+return0;+}elseif(*reply=='a'||*reply=='A'){+state->interactive=0;+return0;+}elseif(*reply=='n'||*reply=='N'){+return1;+}elseif(*reply=='e'||*reply=='E'){+structstrbufmsg=STRBUF_INIT;++if(!launch_editor(am_path(state,"final-commit"),&msg,NULL)){+free(state->msg);+state->msg=strbuf_detach(&msg,&state->msg_len);+}+strbuf_release(&msg);+}elseif(*reply=='v'||*reply=='V'){+constchar*pager=git_pager(1);+structchild_processcp=CHILD_PROCESS_INIT;++if(!pager)+pager="cat";+argv_array_push(&cp.args,pager);+argv_array_push(&cp.args,am_path(state,"patch"));+run_command(&cp);+}+}+}++/***Appliesallqueuedmail.*/staticvoidam_run(structam_state*state)
@@ -2053,6 +2155,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)};structoptionoptions[]={+OPT_BOOL('i',"interactive",&state.interactive,+N_("run interactively")),OPT_BOOL('3',"3way",&state.threeway,N_("allow fall back on 3way merging if needed")),OPT__QUIET(&state.quiet,N_("be quiet")),
The -b/--binary option was initially implemented in 087b674 (git-am:
--binary; document --resume and --binary., 2005-11-16). The option will
pass the --binary flag to git-apply to allow it to apply binary patches.
However, in 2b6eef9 (Make apply --binary a no-op., 2006-09-06), --binary
was been made a no-op in git-apply. Following that, since cb3a160
(git-am: ignore --binary option, 2008-08-09), the --binary option in
git-am is ignored as well.
In 6c15a1c (am: officially deprecate -b/--binary option, 2012-03-13),
the --binary option was tweaked to its present behavior: when set, the
message:
The -b/--binary option has been a no-op for long time, and it
will be removed. Please do not use it anymore.
will be printed.
Re-implement this in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 7 +++++++
1 file changed, 7 insertions(+)
@@ -2157,6 +2158,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)structoptionoptions[]={OPT_BOOL('i',"interactive",&state.interactive,N_("run interactively")),+OPT_HIDDEN_BOOL('b',"binary",&binary,+N_("(historical option -- no-op")),OPT_BOOL('3',"3way",&state.threeway,N_("allow fall back on 3way merging if needed")),OPT__QUIET(&state.quiet,N_("be quiet")),
@@ -2257,6 +2260,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)argc=parse_options(argc,argv,prefix,options,usage,0);+if(binary>=0)+fprintf_ln(stderr,_("The -b/--binary option has been a no-op for long time, and\n"+"it will be removed. Please do not use it anymore."));+if(read_index_preload(&the_index,NULL)<0)die(_("failed to read the index"));
When commit_tree() is called, if the user does not have an explicit
committer ident configured, it will attempt to construct a default
committer ident based on the user's and system's info (e.g. gecos field,
hostname etc.) However, if a default committer ident is unable to be
constructed, commit_tree() will die(), but at this point of git-am's
execution, there will already be changes made to the index and work
tree.
This can be confusing to new users, and as such since d64e6b0 (Keep
Porcelainish from failing by broken ident after making changes.,
2006-02-18) git-am.sh will check to see if the committer ident has been
configured, or a default one can be constructed, before even starting to
apply patches.
Re-implement this in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
Notes:
v5
* modified commit message
builtin/am.c | 3 +++
1 file changed, 3 insertions(+)
@@ -2264,6 +2264,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)fprintf_ln(stderr,_("The -b/--binary option has been a no-op for long time, and\n""it will be removed. Please do not use it anymore."));+/* Ensure a valid committer ident can be constructed */+git_committer_info(IDENT_STRICT);+if(read_index_preload(&the_index,NULL)<0)die(_("failed to read the index"));
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sh supported the --3way option, and if set, would attempt to do a
3-way merge if the initial patch application fails.
Since d96a275 (git-am: add am.threeWay config variable, 2015-06-04), the
setting am.threeWay configures if the --3way option is set by default.
Since 5d86861 (am -3: list the paths that needed 3-way fallback,
2012-03-28), in a 3-way merge git-am.sh would list the paths that needed
3-way fallback, so that the user can review them more carefully to spot
mismerges.
Re-implement the above in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
Notes:
v5
* s/a index/an index/
builtin/am.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 153 insertions(+), 4 deletions(-)
@@ -807,8 +836,106 @@ static int run_apply(const struct am_state *state)/* Reload index as git-apply will have modified it. */discard_cache();+read_cache_from(index_file?index_file:get_index_file());++return0;+}++/**+*Buildsanindexthatcontainsjusttheblobsneededfora3waymerge.+*/+staticintbuild_fake_ancestor(conststructam_state*state,constchar*index_file)+{+structchild_processcp=CHILD_PROCESS_INIT;++cp.git_cmd=1;+argv_array_push(&cp.args,"apply");+argv_array_pushf(&cp.args,"--build-fake-ancestor=%s",index_file);+argv_array_push(&cp.args,am_path(state,"patch"));++if(run_command(&cp))+return-1;++return0;+}++/**+*Attemptathreewaymerge,usingindex_pathasthetemporaryindex.+*/+staticintfall_back_threeway(conststructam_state*state,constchar*index_path)+{+unsignedcharorig_tree[GIT_SHA1_RAWSZ],his_tree[GIT_SHA1_RAWSZ],+our_tree[GIT_SHA1_RAWSZ];+constunsignedchar*bases[1]={orig_tree};+structmerge_optionso;+structcommit*result;+char*his_tree_name;++if(get_sha1("HEAD",our_tree)<0)+hashcpy(our_tree,EMPTY_TREE_SHA1_BIN);++if(build_fake_ancestor(state,index_path))+returnerror("could not build fake ancestor");++discard_cache();+read_cache_from(index_path);++if(write_index_as_tree(orig_tree,&the_index,index_path,0,NULL))+returnerror(_("Repository lacks necessary blobs to fall back on 3-way merge."));++say(state,stdout,_("Using index info to reconstruct a base tree..."));++if(!state->quiet){+/*+*Listpathsthatneeded3-wayfallback,sothattheusercan+*reviewthemwithextracaretospotmismerges.+*/+structrev_inforev_info;+constchar*diff_filter_str="--diff-filter=AM";++init_revisions(&rev_info,NULL);+rev_info.diffopt.output_format=DIFF_FORMAT_NAME_STATUS;+diff_opt_parse(&rev_info.diffopt,&diff_filter_str,1);+add_pending_sha1(&rev_info,"HEAD",our_tree,0);+diff_setup_done(&rev_info.diffopt);+run_diff_index(&rev_info,1);+}++if(run_apply(state,index_path))+returnerror(_("Did you hand edit your patch?\n"+"It does not apply to blobs recorded in its index."));++if(write_index_as_tree(his_tree,&the_index,index_path,0,NULL))+returnerror("could not write tree");++say(state,stdout,_("Falling back to patching base and 3-way merge..."));++discard_cache();read_cache();+/*+*Thisisnotsowrong.Dependingonwhichbasewepicked,orig_tree+*maybewildlydifferentfromours,buthis_treehasthesamesetof+*wildlydifferentchangesinpartsthepatchdidnottouch,so+*recursiveendsupcancelingthem,sayingthatwerevertedallthose+*changes.+*/++init_merge_options(&o);++o.branch1="HEAD";+his_tree_name=xstrfmt("%.*s",linelen(state->msg),state->msg);+o.branch2=his_tree_name;++if(state->quiet)+o.verbosity=0;++if(merge_recursive_generic(&o,our_tree,his_tree,1,bases,&result)){+free(his_tree_name);+returnerror(_("Failed to merge in the changes."));+}++free(his_tree_name);return0;}
@@ -1161,6 +1308,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)};structoptionoptions[]={+OPT_BOOL('3',"3way",&state.threeway,+N_("allow fall back on 3way merging if needed")),OPT__QUIET(&state.quiet,N_("be quiet")),OPT_BOOL('s',"signoff",&state.append_signoff,N_("add a Signed-off-by line to the commit message")),
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sh will invoke the applypatch-msg hooks just after extracting the
patch message. If the applypatch-msg hook exits with a non-zero status,
git-am.sh abort before even applying the patch to the index.
Re-implement this in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
At the beginning of the rewrite of git-am.sh to C, in order to not break
existing test scripts that depended on a functional git-am, a
redirection to git-am.sh was introduced that would activate if the
environment variable _GIT_USE_BUILTIN_AM was not defined.
Now that all of git-am.sh's functionality has been re-implemented in
builtin/am.c, remove this redirection, and retire git-am.sh into
contrib/examples/.
Signed-off-by: Paul Tan <redacted>
---
Makefile | 1 -
builtin/am.c | 15 ---------------
git-am.sh => contrib/examples/git-am.sh | 0
git.c | 7 +------
4 files changed, 1 insertion(+), 22 deletions(-)
rename git-am.sh => contrib/examples/git-am.sh (100%)
From: Stefan Beller <hidden> Date: 2016-06-15 23:05:45
On Tue, Jul 7, 2015 at 7:20 AM, Paul Tan [off-list ref] wrote:
quoted hunk
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am
supported resuming from a failed patch application by skipping the
current patch. Re-implement this feature by introducing am_skip().
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 119 insertions(+), 2 deletions(-)
@@ -872,6 +874,114 @@ static void am_resolve(struct am_state *state)}/**+*Performsacheckoutfast-forwardfrom`head`to`remote`.If`reset`is+*true,anyunmergedentrieswillbediscarded.Returns0onsuccess,-1on+*failure.+*/+staticintfast_forward_to(structtree*head,structtree*remote,intreset)+{+structlock_file*lock_file=xcalloc(1,sizeof(structlock_file));+structunpack_trees_optionsopts;+structtree_desct[2];++if(parse_tree(head)||parse_tree(remote))+return-1;++hold_locked_index(lock_file,1);++refresh_cache(REFRESH_QUIET);++memset(&opts,0,sizeof(opts));+opts.head_idx=1;+opts.src_index=&the_index;+opts.dst_index=&the_index;+opts.update=1;+opts.merge=1;+opts.reset=reset;+opts.fn=twoway_merge;+init_tree_desc(&t[0],head->buffer,head->size);+init_tree_desc(&t[1],remote->buffer,remote->size);++if(unpack_trees(2,t,&opts)){+rollback_lock_file(lock_file);+return-1;+}++if(write_locked_index(&the_index,lock_file,COMMIT_LOCK))+die(_("unable to write new index file"));++return0;+}++/**+*Cleantheindexwithouttouchingentriesthatarenotmodifiedbetween+*`head`and`remote`.+*/+staticintclean_index(constunsignedchar*head,constunsignedchar*remote)+{+structlock_file*lock_file=xcalloc(1,sizeof(structlock_file));+structtree*head_tree,*remote_tree,*index_tree;+unsignedcharindex[GIT_SHA1_RAWSZ];+structpathspecpathspec;++head_tree=parse_tree_indirect(head);+if(!head_tree)+returnerror(_("Could not parse object '%s'."),sha1_to_hex(head));++remote_tree=parse_tree_indirect(remote);+if(!remote_tree)+returnerror(_("Could not parse object '%s'."),sha1_to_hex(remote));++read_cache_unmerged();++if(fast_forward_to(head_tree,head_tree,1))+return-1;++if(write_cache_as_tree(index,0,NULL))+return-1;++index_tree=parse_tree_indirect(index);+if(!index_tree)+returnerror(_("Could not parse object '%s'."),sha1_to_hex(index));++if(fast_forward_to(index_tree,remote_tree,0))+return-1;++memset(&pathspec,0,sizeof(pathspec));+
All returns before this point leak the memory of `lock_file`.
quoted hunk
+ hold_locked_index(lock_file, 1);
+
+ if (read_tree(remote_tree, 0, &pathspec)) {
+ rollback_lock_file(lock_file);
+ return -1;
+ }
+
+ if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+ die(_("unable to write new index file"));
+
+ remove_branch_state();
+
+ return 0;
+}
+
+/**
+ * Resume the current am session by skipping the current patch.
+ */
+static void am_skip(struct am_state *state)
+{
+ unsigned char head[GIT_SHA1_RAWSZ];
+
+ if (get_sha1("HEAD", head))
+ hashcpy(head, EMPTY_TREE_SHA1_BIN);
+
+ if (clean_index(head, head))
+ die(_("failed to clean index"));
+
+ am_next(state);
+ am_run(state);
+}
+
+/**
* parse_options() callback that validates and sets opt->value to the
* PATCH_FORMAT_* enum value corresponding to `arg`.
*/
On Tue, Jul 14, 2015 at 3:05 AM, Stefan Beller [off-list ref] wrote:
All returns before this point leak the memory of `lock_file`.
Yeah, it's intentional. From Documentation/technical/api-lockfile.txt:
* Allocates a `struct lock_file` either as a static variable or on the
heap, initialized to zeros. Once you use the structure to call the
`hold_lock_file_*` family of functions, it belongs to the lockfile
subsystem and its storage must remain valid throughout the life of
the program (i.e. you cannot use an on-stack variable to hold this
structure).
Thanks,
Paul
From: Stefan Beller <hidden> Date: 2016-06-15 23:05:45
On Tue, Jul 14, 2015 at 2:34 AM, Paul Tan [off-list ref] wrote:
On Tue, Jul 14, 2015 at 3:05 AM, Stefan Beller [off-list ref] wrote:
quoted
All returns before this point leak the memory of `lock_file`.
Yeah, it's intentional. From Documentation/technical/api-lockfile.txt:
* Allocates a `struct lock_file` either as a static variable or on the
heap, initialized to zeros. Once you use the structure to call the
`hold_lock_file_*` family of functions, it belongs to the lockfile
subsystem and its storage must remain valid throughout the life of
the program (i.e. you cannot use an on-stack variable to hold this
structure).
Thanks,
Paul
So what I meant to suggest, was to only allocate the memory if we really need it
by moving the allocation further down.
static int clean_index(const unsigned char *head, const unsigned char *remote)
{
struct lock_file *lock_file;
...
... // includes return -1, which would not leak the memory already allocated
...
lock_file = xalloc (...);
hold_locked_index(lock_file, 1);
On Tue, Jul 14, 2015 at 09:54:10AM -0700, Stefan Beller wrote:
So what I meant to suggest, was to only allocate the memory if we really need it
by moving the allocation further down.
static int clean_index(const unsigned char *head, const unsigned char *remote)
{
struct lock_file *lock_file;
...
... // includes return -1, which would not leak the memory already allocated
...
lock_file = xalloc (...);
hold_locked_index(lock_file, 1);
Ah, sorry, I misinterpreted your message ><. Thanks for catching this.
I've moved all the lock file memory allocation to just before the
hold_locked_index() calls on my end.
@@ -1962,7 +1962,7 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset)*/staticintclean_index(constunsignedchar*head,constunsignedchar*remote){-structlock_file*lock_file=xcalloc(1,sizeof(structlock_file));+structlock_file*lock_file;structtree*head_tree,*remote_tree,*index_tree;unsignedcharindex[GIT_SHA1_RAWSZ];structpathspecpathspec;