This is a re-roll of [v1]. Thanks Junio, Torsten, Jeff, Eric for the reviews
last round.
Previous versions:
[v1] http://thread.gmane.org/gmane.comp.version-control.git/270048
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 WIP 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 (19):
wrapper: implement xopen()
wrapper: implement xfopen()
am: implement skeletal builtin am
am: implement patch queue mechanism
am: split out mbox/maildir patches with git-mailsplit
am: detect mbox patches
am: extract patch, message and authorship with git-mailinfo
am: apply patch with git-apply
am: commit applied patch
am: refresh the index at start
am: refuse to apply patches if index is dirty
am: implement --resolved/--continue
am: implement --skip
am: implement --abort
am: implement quiet option
am: exit with user friendly message on patch failure
am: implement am --signoff
cache-tree: introduce write_index_as_tree()
am: implement 3-way merge
Makefile | 1 +
builtin.h | 1 +
builtin/am.c | 1214 +++++++++++++++++++++++++++++++++++++++++++++++++++++
cache-tree.c | 29 +-
cache-tree.h | 1 +
git-compat-util.h | 2 +
git.c | 1 +
wrapper.c | 43 ++
8 files changed, 1280 insertions(+), 12 deletions(-)
create mode 100644 builtin/am.c
--
2.1.4
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]
Signed-off-by: Paul Tan <redacted>
---
Notes:
v2
* retry on EINTR
* mode is now an optional argument in xopen(). We use the mode argument
only if O_CREAT is specified in oflag.
git-compat-util.h | 1 +
wrapper.c | 25 +++++++++++++++++++++++++
2 files changed, 26 insertions(+)
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.
Signed-off-by: Paul Tan <redacted>
---
Notes:
v2
* Removed the error message distinction between reading and writing.
* Handle EINTR.
git-compat-util.h | 1 +
wrapper.c | 18 ++++++++++++++++++
2 files changed, 19 insertions(+)
@@ -336,6 +336,24 @@ int xdup(int fd)returnret;}+/**+*xfopen()isthesameasfopen(),butitdie()sifthefopen()fails.+*/+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);+}+}+FILE*xfdopen(intfd,constchar*mode){FILE*stream=fdopen(fd,mode);
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.
This redirection will be removed when all the features of git-am.sh have
been re-implemented in builtin/am.c.
Signed-off-by: Paul Tan <redacted>
---
Notes:
v2
* Declare struct am_state state as static.
Makefile | 1 +
builtin.h | 1 +
builtin/am.c | 20 ++++++++++++++++++++
git.c | 1 +
4 files changed, 23 insertions(+)
create mode 100644 builtin/am.c
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.
Signed-off-by: Paul Tan <redacted>
---
Notes:
v2
* Declare struct am_state as static
builtin/am.c | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 164 insertions(+)
@@ -6,6 +6,154 @@#include"cache.h"#include"builtin.h"#include"exec_cmd.h"+#include"parse-options.h"+#include"dir.h"++structam_state{+/* state directory path */+structstrbufdir;++/* current and last patch numbers, 1-indexed */+intcur;+intlast;+};++/**+*Initializesam_statewiththedefaultvalues.+*/+staticvoidam_state_init(structam_state*state)+{+memset(state,0,sizeof(*state));++strbuf_init(&state->dir,0);+}++/**+*Releasememoryallocatedbyanam_state.+*/+staticvoidam_state_release(structam_state*state)+{+strbuf_release(&state->dir);+}++/**+*Returnspathrelativetotheam_statedirectory.+*/+staticinlineconstchar*am_path(conststructam_state*state,constchar*path)+{+returnmkpath("%s/%s",state->dir.buf,path);+}++/**+*Returns1ifthereisanamsessioninprogress,0otherwise.+*/+staticintam_in_progress(conststructam_state*state)+{+structstatst;++if(lstat(state->dir.buf,&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`.Thethirdargumentcanbeusedtogiveahint+*aboutthefilesize,toavoidreallocs.Returnsnumberofbytesreadon+*success,-1ifthefiledoesnotexist.Iftrimisset,trailingwhitespace+*willberemovedfromthefilecontents.+*/+staticintread_state_file(structstrbuf*sb,constchar*file,size_thint,inttrim)+{+strbuf_reset(sb);+if(strbuf_read_file(sb,file,hint)>=0){+if(trim)+strbuf_rtrim(sb);++returnsb->len;+}++if(errno==ENOENT)+return-1;++die_errno(_("could not read '%s'"),file);+}++/**+*Loadsstatefromdisk.+*/+staticvoidam_load(structam_state*state)+{+structstrbufsb=STRBUF_INIT;++read_state_file(&sb,am_path(state,"next"),8,1);+state->cur=strtol(sb.buf,NULL,10);++read_state_file(&sb,am_path(state,"last"),8,1);+state->last=strtol(sb.buf,NULL,10);++strbuf_release(&sb);+}++/**+*Removetheam_statedirectory.+*/+staticvoidam_destroy(conststructam_state*state)+{+structstrbufsb=STRBUF_INIT;++strbuf_addstr(&sb,state->dir.buf);+remove_dir_recursively(&sb,0);+strbuf_release(&sb);+}++/**+*Setupanewamsessionforapplyingpatches+*/+staticvoidam_setup(structam_state*state)+{+if(mkdir(state->dir.buf,0777)<0&&errno!=EEXIST)+die_errno(_("failed to create directory '%s'"),state->dir.buf);++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);+}++/**+*Appliesallqueuedpatches.+*/+staticvoidam_run(structam_state*state)+{+while(state->cur<=state->last)+am_next(state);++am_destroy(state);+}++staticstructam_statestate;++staticconstchar*constam_usage[]={+N_("git am [options] [(<mbox>|<Maildir>)...]"),+NULL+};++staticstructoptionam_options[]={+OPT_END()+};intcmd_am(intargc,constchar**argv,constchar*prefix){
@@ -16,5 +164,21 @@ int cmd_am(int argc, const char **argv, const char *prefix)die_errno("could not exec %s",path);}+git_config(git_default_config,NULL);++am_state_init(&state);+strbuf_addstr(&state.dir,git_path("rebase-apply"));++argc=parse_options(argc,argv,prefix,am_options,am_usage,0);++if(am_in_progress(&state))+am_load(&state);+else+am_setup(&state);++am_run(&state);++am_state_release(&state);+return0;}
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.
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
Notes:
v2
* Various small code tweaks suggested by Eric.
builtin/am.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 98 insertions(+)
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.
Signed-off-by: Paul Tan <redacted>
---
Notes:
v2
* Declare int opt_patchformat as static.
* Fix up indentation style for the switch()
builtin/am.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 103 insertions(+), 4 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;};/**
Implement applying the patch to the index using git-apply.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 54 insertions(+), 1 deletion(-)
@@ -27,6 +27,18 @@ static int is_empty_file(const char *filename)return!st.st_size;}+/**+*Returnsthefirstlineofmsg+*/+staticconstchar*firstline(constchar*msg)+{+staticstructstrbufsb=STRBUF_INIT;++strbuf_reset(&sb);+strbuf_add(&sb,msg,strchrnul(msg,'\n')-msg);+returnsb.buf;+}+enumpatch_format{PATCH_FORMAT_UNKNOWN=0,PATCH_FORMAT_MBOX
@@ -512,6 +524,29 @@ static int parse_patch(struct am_state *state, const char *patch)return0;}+/*+*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;+}+/***Appliesallqueuedpatches.*/
@@ -529,7 +564,25 @@ static void am_run(struct am_state *state)write_author_script(state);write_file(am_path(state,"final-commit"),1,"%s",state->msg.buf);-/* TODO: Patch application not implemented yet */+printf_ln(_("Applying: %s"),firstline(state->msg.buf));++if(run_apply(state)<0){+intvalue;++printf_ln(_("Patch failed at %s %s"),msgnum(state),+firstline(state->msg.buf));++if(!git_config_get_bool("advice.amworkdir",&value)&&!value)+printf_ln(_("The copy of the patch that failed is found in: %s"),+am_path(state,"patch"));++exit(128);+}++/*+*TODO:Afterthepatchhasbeenappliedtotheindexwith+*git-apply,weneedtomakecommitaswell.+*/next:am_next(state);
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.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 20 ++++++++++++++++++++
1 file changed, 20 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]
Signed-off-by: Paul Tan <redacted>
---
Notes:
v2
* use die_errno()
* use '%*d' as the format specifier for msgnum()
builtin/am.c | 228 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 228 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;+}enumpatch_format{PATCH_FORMAT_UNKNOWN=0,
@@ -23,6 +40,12 @@ struct am_state {intcur;intlast;+/* commit message and metadata */+structstrbufauthor_name;+structstrbufauthor_email;+structstrbufauthor_date;+structstrbufmsg;+/* number of digits in patch filename */intprec;};
@@ -293,6 +418,98 @@ static void am_next(struct am_state *state){state->cur++;write_file(am_path(state,"next"),1,"%d",state->cur);++strbuf_reset(&state->author_name);+strbuf_reset(&state->author_email);+strbuf_reset(&state->author_date);+unlink(am_path(state,"author-script"));++strbuf_reset(&state->msg);+unlink(am_path(state,"final-commit"));+}++/**+*Returnsthefilenameofthecurrentpatch.+*/+staticconstchar*msgnum(conststructam_state*state)+{+staticstructstrbufsb=STRBUF_INIT;++strbuf_reset(&sb);+strbuf_addf(&sb,"%0*d",state->prec,state->cur);++returnsb.buf;+}++/**+*Parses`patch`usinggit-mailinfo.state->msgwillbesettothepatch+*message.state->author_name,state->author_email,state->author_datewillbe+*settothepatchauthor'sname,emailanddaterespectively.Thepatch's+*bodywillbewrittento"$state_dir/patch",where$state_diristhestate+*directory.+*+*Returns1ifthepatchshouldbeskipped,0otherwise.+*/+staticintparse_patch(structam_state*state,constchar*patch)+{+FILE*fp;+structchild_processcp=CHILD_PROCESS_INIT;+structstrbufsb=STRBUF_INIT;++cp.git_cmd=1;+cp.in=xopen(patch,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(state->msg.len)+strbuf_addch(&state->msg,'\n');+strbuf_addstr(&state->msg,x);+}elseif(skip_prefix(sb.buf,"Author: ",&x)){+if(state->author_name.len)+strbuf_addch(&state->author_name,'\n');+strbuf_addstr(&state->author_name,x);+}elseif(skip_prefix(sb.buf,"Email: ",&x)){+if(state->author_email.len)+strbuf_addch(&state->author_email,'\n');+strbuf_addstr(&state->author_email,x);+}elseif(skip_prefix(sb.buf,"Date: ",&x)){+if(state->author_date.len)+strbuf_addch(&state->author_date,'\n');+strbuf_addstr(&state->author_date,x);+}+}+fclose(fp);++/* Skip pine's internal folder data */+if(!strcmp(state->author_name.buf,"Mail System Internal Data"))+return1;++if(is_empty_file(am_path(state,"patch")))+die(_("Patch is empty. Was it split wrong?\n"+"If you would prefer to skip this patch, instead run \"git am --skip\".\n"+"To restore the original branch and stop patching run \"git am --abort\"."));++strbuf_addstr(&state->msg,"\n\n");+if(strbuf_read_file(&state->msg,am_path(state,"msg"),0)<0)+die_errno(_("could not read '%s'"),am_path(state,"msg"));+stripspace(&state->msg,0);++return0;}/**
@@ -301,9 +518,20 @@ static void am_next(struct am_state *state)staticvoidam_run(structam_state*state){while(state->cur<=state->last){+constchar*patch=am_path(state,msgnum(state));++if(!file_exists(patch))+gotonext;++if(parse_patch(state,patch))+gotonext;/* patch should be skipped */++write_author_script(state);+write_file(am_path(state,"final-commit"),1,"%s",state->msg.buf);/* TODO: Patch application not implemented yet */+next:am_next(state);}
Implement do_commit(), which commits the index which contains the
results of applying the patch, along with the extracted commit message
and authorship information.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 46 insertions(+), 4 deletions(-)
@@ -548,6 +551,48 @@ 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[20],parent[20],commit[20];+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.buf,state->author_email.buf,+state->author_date.buf,IDENT_STRICT);++if(commit_tree(state->msg.buf,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,firstline(state->msg.buf));++update_ref(sb.buf,"HEAD",commit,ptr,0,UPDATE_REFS_DIE_ON_ERR);++strbuf_release(&sb);+}++/***Appliesallqueuedpatches.*/staticvoidam_run(structam_state*state)
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.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
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().
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 51 insertions(+), 1 deletion(-)
@@ -697,6 +697,34 @@ next:}/**+*Resumethecurrentamsessionafterpatchapplicationfailure.Theuserdid+*allthehardwork,andwedonothavetodoanypatchapplication.Just+*trustandcommitwhattheuserhasintheindexandworkingtree.+*/+staticvoidam_resolve(structam_state*state)+{+printf_ln(_("Applying: %s"),firstline(state->msg.buf));++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`.*/
@@ -711,17 +739,30 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, intreturn0;}+enumresume_mode{+RESUME_FALSE=0,+RESUME_RESOLVED+};+staticstructam_statestate;staticintopt_patch_format;+staticenumresume_modeopt_resume;staticconstchar*constam_usage[]={N_("git am [options] [(<mbox>|<Maildir>)...]"),+N_("git am [options] --continue"),NULL};staticstructoptionam_options[]={OPT_CALLBACK(0,"patch-format",&opt_patch_format,N_("format"),N_("format the patch(es) are in"),parse_opt_patchformat),+OPT_CMDMODE(0,"continue",&opt_resume,+N_("continue applying patches after resolving a conflict"),+RESUME_RESOLVED),+OPT_CMDMODE('r',"resolved",&opt_resume,+N_("synonyms for --continue"),+RESUME_RESOLVED),OPT_END()};
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(-)
@@ -725,6 +727,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`.*/
@@ -750,7 +861,7 @@ static enum resume_mode opt_resume;staticconstchar*constam_usage[]={N_("git am [options] [(<mbox>|<Maildir>)...]"),-N_("git am [options] --continue"),+N_("git am [options] (--continue | --skip)"),NULL};
@@ -763,6 +874,9 @@ static struct option am_options[] = {OPT_CMDMODE('r',"resolved",&opt_resume,N_("synonyms for --continue"),RESUME_RESOLVED),+OPT_CMDMODE(0,"skip",&opt_resume,+N_("skip the current patch"),+RESUME_SKIP),OPT_END()};
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 feature 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 | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 92 insertions(+), 3 deletions(-)
@@ -834,6 +855,67 @@ static void am_skip(struct am_state *state)am_run(state);}+staticintsafe_to_abort(conststructam_state*state)+{+structstrbufsb=STRBUF_INIT;+unsignedcharabort_safety[20],head[20];++if(file_exists(am_path(state,"dirtyindex")))+return0;++if(read_state_file(&sb,am_path(state,"abort-safety"),40,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[20],orig_head[20];+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`.
@@ -861,7 +944,7 @@ static enum resume_mode opt_resume;staticconstchar*constam_usage[]={N_("git am [options] [(<mbox>|<Maildir>)...]"),-N_("git am [options] (--continue | --skip)"),+N_("git am [options] (--continue | --skip | --abort)"),NULL};
@@ -877,6 +960,9 @@ static struct option am_options[] = {OPT_CMDMODE(0,"skip",&opt_resume,N_("skip the current patch"),RESUME_SKIP),+OPT_CMDMODE(0,"abort",&opt_resume,+N_("restore the original branch and abort the patching operation."),+RESUME_ABORT),OPT_END()};
Since 0e987a1 (am, rebase: teach quiet option, 2009-06-16), git-am
supported the --quiet option and GIT_QUIET environment variable, 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>
---
builtin/am.c | 36 +++++++++++++++++++++++++++++++++---
1 file changed, 33 insertions(+), 3 deletions(-)
@@ -644,7 +673,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.buf,state->author_email.buf,
@@ -726,7 +755,7 @@ next:*/staticvoidam_resolve(structam_state*state){-printf_ln(_("Applying: %s"),firstline(state->msg.buf));+say(state,stdout,_("Applying: %s"),firstline(state->msg.buf));if(!index_has_changes(NULL)){printf_ln(_("No changes - did you forget to use 'git add'?\n"
@@ -949,6 +978,7 @@ static const char * const am_usage[] = {};staticstructoptionam_options[]={+OPT__QUIET(&state.quiet,N_("be quiet")),OPT_CALLBACK(0,"patch-format",&opt_patch_format,N_("format"),N_("format the patch(es) are in"),parse_opt_patchformat),OPT_CMDMODE(0,"continue",&opt_resume,
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 | 26 +++++++++++++++++++++++---
1 file changed, 23 insertions(+), 3 deletions(-)
@@ -629,6 +632,21 @@ static int parse_patch(struct am_state *state, const char *patch)return0;}+/**+*Dieswithauser-friendlymessageonhowtoproceedafterresolvingthe+*problem.Thismessagecanbeoverriddenwithstate->resolvemsg.+*/+staticvoidNORETURNdie_user_resolve(conststructam_state*state)+{+if(state->resolvemsg)+printf_ln("%s",state->resolvemsg);+else+printf_ln(_("When you have resolved this problem, run \"git am --continue\".\n"+"If you prefer to skip this patch, run \"git am --skip\" instead.\n"+"To restore the original branch and stop patching, run \"git am --abort\"."));+exit(128);+}+/**Appliescurrentpatchwithgit-apply.Returns0onsuccess,-1otherwise.*/
@@ -736,7 +754,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);
@@ -761,13 +779,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);
@@ -981,6 +999,8 @@ static struct option am_options[] = {OPT__QUIET(&state.quiet,N_("be quiet")),OPT_CALLBACK(0,"patch-format",&opt_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",&opt_resume,N_("continue applying patches after resolving a conflict"),RESUME_RESOLVED),
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 by calling
append_signoff() if the option is set.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
@@ -629,6 +637,9 @@ static int parse_patch(struct am_state *state, const char *patch)die_errno(_("could not read '%s'"),am_path(state,"msg"));stripspace(&state->msg,0);+if(state->sign)+append_signoff(&state->msg,0,0);+return0;}
@@ -997,6 +1008,8 @@ static const char * const am_usage[] = {staticstructoptionam_options[]={OPT__QUIET(&state.quiet,N_("be quiet")),+OPT_BOOL('s',"signoff",&state.sign,+N_("add a Signed-off-by line to the commit message")),OPT_CALLBACK(0,"patch-format",&opt_patch_format,N_("format"),N_("format the patch(es) are in"),parse_opt_patchformat),OPT_STRING(0,"resolvemsg",&state.resolvemsg,NULL,
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
supported the --3way option, and if set, would attempt to do a 3-way
merge if the initial patch application fails. Re-implement this feature
through the fall_back_threeway() function.
Signed-off-by: Paul Tan <redacted>
---
builtin/am.c | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 129 insertions(+), 4 deletions(-)
@@ -676,8 +695,92 @@ 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;+}++/*+*Buildsaindexthatcontainsjusttheblobsneededfora3waymerge.+*/+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[20],his_tree[20],our_tree[20];+constunsignedchar*bases[1]={orig_tree};+structmerge_optionso;+structcommit*result;++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){+structchild_processcp=CHILD_PROCESS_INIT;+cp.git_cmd=1;+argv_array_pushf(&cp.env_array,"GIT_INDEX_FILE=%s",index_path);+argv_array_pushl(&cp.args,"diff-index","--cached",+"--diff-filter=AM","--name-status","HEAD",NULL);+run_command(&cp);+}++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";+o.branch2=firstline(state->msg.buf);+if(state->quiet)+o.verbosity=0;++if(merge_recursive_generic(&o,our_tree,his_tree,1,bases,&result))+returnerror(_("Failed to merge in the changes."));+return0;}
@@ -1007,6 +1130,8 @@ static const char * const am_usage[] = {};staticstructoptionam_options[]={+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.sign,N_("add a Signed-off-by line to the commit message")),
From: Stefan Beller <hidden> Date: 2016-06-15 23:05:16
On Thu, Jun 11, 2015 at 3:21 AM, Paul Tan [off-list ref] wrote:
quoted hunk
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.
Signed-off-by: Paul Tan <redacted>
---
Notes:
v2
* Declare struct am_state as static
builtin/am.c | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 164 insertions(+)
Did you mean to declare all the functions below to be static or the
struct as well?
Reading further, you declared it static below. I thought maybe it'd be
useful to have definition
and declaration up here, but having all declarations further below may
be even better.
quoted hunk
+ /* state directory path */
+ struct strbuf dir;
+
+ /* current and last patch numbers, 1-indexed */
+ int cur;
+ int last;
+};
+
+/**
+ * Initializes am_state with the default values.
+ */
+static void am_state_init(struct am_state *state)
+{
+ memset(state, 0, sizeof(*state));
+
+ strbuf_init(&state->dir, 0);
+}
+
+/**
+ * Release memory allocated by an am_state.
+ */
+static void am_state_release(struct am_state *state)
+{
+ strbuf_release(&state->dir);
+}
+
+/**
+ * Returns path relative to the am_state directory.
+ */
+static inline const char *am_path(const struct am_state *state, const char *path)
+{
+ return mkpath("%s/%s", state->dir.buf, path);
+}
+
+/**
+ * Returns 1 if there is an am session in progress, 0 otherwise.
+ */
+static int am_in_progress(const struct am_state *state)
+{
+ struct stat st;
+
+ if (lstat(state->dir.buf, &st) < 0 || !S_ISDIR(st.st_mode))
+ return 0;
+ if (lstat(am_path(state, "last"), &st) || !S_ISREG(st.st_mode))
+ return 0;
+ if (lstat(am_path(state, "next"), &st) || !S_ISREG(st.st_mode))
+ return 0;
+ return 1;
+}
+
+/**
+ * Reads the contents of `file`. The third argument can be used to give a hint
+ * about the file size, to avoid reallocs. Returns number of bytes read on
+ * success, -1 if the file does not exist. If trim is set, trailing whitespace
+ * will be removed from the file contents.
+ */
+static int read_state_file(struct strbuf *sb, const char *file, size_t hint, int trim)
+{
+ strbuf_reset(sb);
+ if (strbuf_read_file(sb, file, hint) >= 0) {
+ if (trim)
+ strbuf_rtrim(sb);
+
+ return sb->len;
+ }
+
+ if (errno == ENOENT)
+ return -1;
+
+ die_errno(_("could not read '%s'"), file);
+}
+
+/**
+ * Loads state from disk.
+ */
+static void am_load(struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ read_state_file(&sb, am_path(state, "next"), 8, 1);
+ state->cur = strtol(sb.buf, NULL, 10);
+
+ read_state_file(&sb, am_path(state, "last"), 8, 1);
+ state->last = strtol(sb.buf, NULL, 10);
+
+ strbuf_release(&sb);
+}
+
+/**
+ * Remove the am_state directory.
+ */
+static void am_destroy(const struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ strbuf_addstr(&sb, state->dir.buf);
+ remove_dir_recursively(&sb, 0);
+ strbuf_release(&sb);
+}
+
+/**
+ * Setup a new am session for applying patches
+ */
+static void am_setup(struct am_state *state)
+{
+ if (mkdir(state->dir.buf, 0777) < 0 && errno != EEXIST)
+ die_errno(_("failed to create directory '%s'"), state->dir.buf);
+
+ write_file(am_path(state, "next"), 1, "%d", state->cur);
+
+ write_file(am_path(state, "last"), 1, "%d", state->last);
+}
+
+/**
+ * Increments the patch pointer, and cleans am_state for the application of the
+ * next patch.
+ */
+static void am_next(struct am_state *state)
+{
+ state->cur++;
+ write_file(am_path(state, "next"), 1, "%d", state->cur);
+}
+
+/**
+ * Applies all queued patches.
+ */
+static void am_run(struct am_state *state)
+{
+ while (state->cur <= state->last)
+ am_next(state);
+
+ am_destroy(state);
+}
+
+static struct am_state state;
+
+static const char * const am_usage[] = {
+ N_("git am [options] [(<mbox>|<Maildir>)...]"),
+ NULL
+};
+
+static struct option am_options[] = {
+ OPT_END()
+};
int cmd_am(int argc, const char **argv, const char *prefix)
{
From: Stefan Beller <hidden> Date: 2016-06-15 23:05:16
On Thu, Jun 11, 2015 at 3:21 AM, Paul Tan [off-list ref] wrote:
quoted hunk
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.
Signed-off-by: Paul Tan <redacted>
---
Notes:
v2
* Declare int opt_patchformat as static.
* Fix up indentation style for the switch()
builtin/am.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 103 insertions(+), 4 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;};/**
When reviewing the previous patch I did look at this loop for awhile confused,
if you want to apply patches in am_next(state) and thought there might be
a better approach.
Maybe you want to move this chunk with the TODO into the previous patch,
so it's clear after reading the documentation of am_run, that the actual am is
missing there.
quoted hunk
am_destroy(state);
}
+/**
+ * parse_options() callback that validates and sets opt->value to the
+ * PATCH_FORMAT_* enum value corresponding to `arg`.
+ */
+static int parse_opt_patchformat(const struct option *opt, const char *arg, int unset)
+{
+ int *opt_value = opt->value;
+
+ if (!strcmp(arg, "mbox"))
+ *opt_value = PATCH_FORMAT_MBOX;
+ else
+ return -1;
+ return 0;
+}
+
static struct am_state state;
+static int opt_patch_format;
static const char * const am_usage[] = {
N_("git am [options] [(<mbox>|<Maildir>)...]"),
When reviewing the previous patch I did look at this loop for awhile confused,
if you want to apply patches in am_next(state) and thought there might be
a better approach.
Maybe you want to move this chunk with the TODO into the previous patch,
so it's clear after reading the documentation of am_run, that the actual am is
missing there.
Ah right, this is a mistake. This comment should be in the previous patch.
Thanks,
Paul
Reading further, you declared it static below. I thought maybe it'd be
useful to have definition
and declaration up here, but having all declarations further below may
be even better.
Right, I aimed to have a strict separation between "git-am: the
functionality" and "git-am: the command-line interface", where the
latter depends on the former, and not the other way round (or have
circular dependencies). The former perhaps could even be moved into
libgit.a in the future.
Thanks,
Paul