This is a re-roll of [v2]. Thanks Junio, Stefan for the reviews last round.
Previous versions:
[v1] http://thread.gmane.org/gmane.comp.version-control.git/269258
[v2] http://thread.gmane.org/gmane.comp.version-control.git/270639
git-pull is a commonly executed command to check for new changes in the
upstream repository and, if there are, fetch and integrate them into the
current branch. Currently it is implemented by the shell script git-pull.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 series rewrites git-pull.sh into a C builtin, thus improving its
performance and portability. It is part of my GSoC project to rewrite git-pull
and git-am into builtins[1].
[1] https://gist.github.com/pyokagan/1b7b0d1f4dab6ba3cef1
Paul Tan (19):
parse-options-cb: implement parse_opt_passthru()
parse-options-cb: implement parse_opt_passthru_argv()
argv-array: implement argv_array_pushv()
pull: implement skeletal builtin pull
pull: implement fetch + merge
pull: pass verbosity, --progress flags to fetch and merge
pull: pass git-merge's options to git-merge
pull: pass git-fetch's options to git-fetch
pull: error on no merge candidates
pull: support pull.ff config
pull: check if in unresolved merge state
pull: fast-forward working tree if head is updated
pull: implement pulling into an unborn branch
pull: set reflog message
pull: teach git pull about --rebase
pull: configure --rebase via branch.<name>.rebase or pull.rebase
pull --rebase: exit early when the working directory is dirty
pull --rebase: error on no merge candidate cases
pull: remove redirection to git-pull.sh
Documentation/technical/api-argv-array.txt | 3 +
Documentation/technical/api-parse-options.txt | 13 +
Makefile | 2 +-
advice.c | 8 +
advice.h | 1 +
argv-array.c | 6 +
argv-array.h | 1 +
builtin.h | 1 +
builtin/pull.c | 881 ++++++++++++++++++++++++++
git-pull.sh => contrib/examples/git-pull.sh | 0
git.c | 1 +
parse-options-cb.c | 69 ++
parse-options.h | 6 +
13 files changed, 991 insertions(+), 1 deletion(-)
create mode 100644 builtin/pull.c
rename git-pull.sh => contrib/examples/git-pull.sh (100%)
--
2.1.4
Certain git commands, such as git-pull, are simply wrappers around other
git commands like git-fetch, git-merge and git-rebase. As such, these
wrapper commands will typically need to "pass through" command-line
options of the commands they wrap.
Implement the parse_opt_passthru() parse-options callback, which will
reconstruct the command-line option into an char* string, such that it
can be passed to another git command.
Helped-by: Johannes Schindelin [off-list ref]
Helped-by: Junio C Hamano [off-list ref]
Helped-by: Stefan Beller [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
Notes:
v3
* Reverted back to returning newly-allocated strings. Junio raised the
concern that it may not be such a good idea to burden the users of the
API to always use X.buf to access the string. Personally I believe
that memory management safety is usually more important, but I don't
have strong feelings about this.
* Extracted out the "option reconstruction" logic into a private
function so that it can be shared with parse_opt_passthru_argv() in
the next patch.
* Introduced the OPT_PASSTHRU() macro and documented it in
Documentation/technical/api-parse-options.txt. This macro relieves the
user of having to specify OPTION_CALLBACK and parse_opt_passthru() for
every option.
* The function used to be named parse_opt_pass_strbuf() to save
horizontal space, but then again parse_opt_passthru() is probably a
better name.
* Added comment to the docstring that the callback should only be used
for options where the last one wins.
Documentation/technical/api-parse-options.txt | 7 ++++
parse-options-cb.c | 49 +++++++++++++++++++++++++++
parse-options.h | 3 ++
3 files changed, 59 insertions(+)
@@ -212,6 +212,13 @@ There are some macros to easily define options: Use it to hide deprecated options that are still to be recognized and ignored silently.+`OPT_PASSTHRU(short, long, &char_var, arg_str, description, flags)`::+ Introduce an option that will be reconstructed into a char* string,+ which must be initialized to NULL. This is useful when you need to+ pass the command-line option to another command. Any previous value+ will be overwritten, so this should only be used for options where+ the last one specified on the command line wins.+ The last element of the array must be `OPT_END()`.
Certain git commands, such as git-pull, are simply wrappers around other
git commands like git-fetch, git-merge and git-rebase. As such, these
wrapper commands will typically need to "pass through" command-line
options of the commands they wrap.
Implement the parse_opt_passthru_argv() parse-options callback, which
will reconstruct all the provided command-line options into an
argv_array, such that it can be passed to another git command. This is
useful for passing command-line options that can be specified multiple
times.
Helped-by: Stefan Beller [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
Notes:
v3
* Renamed function to the more descriptive parse_opt_passthru_argv().
* Introduced and documented OPT_PASSTHRU_ARGV() macro, which saves the
user from having to specify OPTION_CALLBACK and
parse_opt_passthru_argv() for each option.
Documentation/technical/api-parse-options.txt | 6 ++++++
parse-options-cb.c | 20 ++++++++++++++++++++
parse-options.h | 3 +++
3 files changed, 29 insertions(+)
@@ -219,6 +219,12 @@ There are some macros to easily define options: will be overwritten, so this should only be used for options where the last one specified on the command line wins.+`OPT_PASSTHRU_ARGV(short, long, &argv_array_var, arg_str, description, flags)`::+ Introduce an option where all instances of it on the command-line will+ be reconstructed into an argv_array. This is useful when you need to+ pass the command-line option, which can be specified multiple times,+ to another command.+ The last element of the array must be `OPT_END()`.
When we have a null-terminated array, it would be useful to convert it
or append it to an argv_array for further manipulation.
Implement argv_array_pushv() which will push a null-terminated array of
strings on to an argv_array.
Signed-off-by: Paul Tan <redacted>
---
Documentation/technical/api-argv-array.txt | 3 +++
argv-array.c | 6 ++++++
argv-array.h | 1 +
3 files changed, 10 insertions(+)
@@ -46,6 +46,9 @@ Functions Format a string and push it onto the end of the array. This is a convenience wrapper combining `strbuf_addf` and `argv_array_push`.+`argv_array_pushv`::+ Push a null-terminated array of strings onto the end of the array.+ `argv_array_pop`:: Remove the final element from the array. If there are no elements in the array, do nothing.
Implement the fetch + merge functionality of git-pull, by first running
git-fetch with the repo and refspecs provided on the command line, then
running git-merge on FETCH_HEAD to merge the fetched refs into the
current branch.
Helped-by: Junio C Hamano [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
Notes:
v3
* Catch bug where there is are refspecs but no repo.
builtin/pull.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 61 insertions(+), 1 deletion(-)
For the purpose of rewriting git-pull.sh into a C builtin, implement a
skeletal builtin/pull.c that redirects to $GIT_EXEC_PATH/git-pull.sh if
the environment variable _GIT_USE_BUILTIN_PULL is not defined. This
allows us to fall back on the functional git-pull.sh when running the
test suite for tests that depend on a working git-pull implementation.
This redirection should be removed when all the features of git-pull.sh
have been re-implemented in builtin/pull.c.
Helped-by: Junio C Hamano [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
Notes:
v3
* style fixes
Makefile | 1 +
builtin.h | 1 +
builtin/pull.c | 33 +++++++++++++++++++++++++++++++++
git.c | 1 +
4 files changed, 36 insertions(+)
create mode 100644 builtin/pull.c
7f87aff (Teach/Fix pull/fetch -q/-v options, 2008-11-15) taught git-pull
to accept the verbosity -v and -q options and pass them to git-fetch and
git-merge.
Re-implement support for the verbosity flags by adding it to the options
list and introducing argv_push_verbosity() to push the flags into the
argv array used to execute git-fetch and git-merge.
9839018 (fetch and pull: learn --progress, 2010-02-24) and bebd2fd
(pull: propagate --progress to merge, 2011-02-20) taught git-pull to
accept the --progress option and pass it to git-fetch and git-merge.
Use OPT_PASSTHRU() implemented earlier to pass the "--[no-]progress"
command line options to git-fetch and git-merge.
Helped-by: Junio C Hamano [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
Notes:
v3
* Re-worded commit message.
builtin/pull.c | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
Commit a8c9bef (pull: improve advice for unconfigured error case,
2009-10-05) fully established the current advices given by git-pull for
the different cases where git-fetch will not have anything marked for
merge:
1. We fetched from a specific remote, and a refspec was given, but it
ended up not fetching anything. This is usually because the user
provided a wildcard refspec which had no matches on the remote end.
2. We fetched from a non-default remote, but didn't specify a branch to
merge. We can't use the configured one because it applies to the
default remote, and thus the user must specify the branches to merge.
3. We fetched from the branch's or repo's default remote, but:
a. We are not on a branch, so there will never be a configured branch
to merge with.
b. We are on a branch, but there is no configured branch to merge
with.
4. We fetched from the branch's or repo's default remote, but the
configured branch to merge didn't get fetched (either it doesn't
exist, or wasn't part of the configured fetch refspec)
Re-implement the above behavior by implementing get_merge_heads() to
parse the heads in FETCH_HEAD for merging, and implementing
die_no_merge_candidates(), which will be called when FETCH_HEAD has no
heads for merging.
Helped-by: Johannes Schindelin [off-list ref]
Helped-by: Junio C Hamano [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
Notes:
v3
* Tightening up of FETCH_HEAD parsing code and style fixes.
builtin/pull.c | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 113 insertions(+)
@@ -165,6 +167,111 @@ static void argv_push_force(struct argv_array *arr)}/**+*AppendsmergecandidatesfromFETCH_HEADthatarenotmarkednot-for-merge+*intomerge_heads.+*/+staticvoidget_merge_heads(structsha1_array*merge_heads)+{+constchar*filename=git_path("FETCH_HEAD");+FILE*fp;+structstrbufsb=STRBUF_INIT;+unsignedcharsha1[GIT_SHA1_RAWSZ];++if(!(fp=fopen(filename,"r")))+die_errno(_("could not open '%s' for reading"),filename);+while(strbuf_getline(&sb,fp,'\n')!=EOF){+if(get_sha1_hex(sb.buf,sha1))+continue;/* invalid line: does not start with SHA1 */+if(starts_with(sb.buf+GIT_SHA1_HEXSZ,"\tnot-for-merge\t"))+continue;/* ref is not-for-merge */+sha1_array_append(merge_heads,sha1);+}+fclose(fp);+strbuf_release(&sb);+}++/**+*Usedbydie_no_merge_candidates()asafor_each_remote()callbackto+*retrievethenameoftheremoteiftherepositoryonlyhasoneremote.+*/+staticintget_only_remote(structremote*remote,void*cb_data)+{+constchar**remote_name=cb_data;++if(*remote_name)+return-1;++*remote_name=remote->name;+return0;+}++/**+*Dieswiththeappropriatereasonforwhytherearenomergecandidates:+*+*1.Wefetchedfromaspecificremote,andarefspecwasgiven,butitended+*upnotfetchinganything.Thisisusuallybecausetheuserprovideda+*wildcardrefspecwhichhadnomatchesontheremoteend.+*+*2.Wefetchedfromanon-defaultremote,butdidn'tspecifyabranchto+*merge.Wecan'tusetheconfiguredonebecauseitappliestothedefault+*remote,thustheusermustspecifythebranchestomerge.+*+*3.Wefetchedfromthebranch'sorrepo'sdefaultremote,but:+*+*a.Wearenotonabranch,sotherewillneverbeaconfiguredbranchto+*mergewith.+*+*b.Weareonabranch,butthereisnoconfiguredbranchtomergewith.+*+*4.Wefetchedfromthebranch'sorrepo'sdefaultremote,buttheconfigured+*branchtomergedidn'tgetfetched.(Eitheritdoesn'texist,orwasn't+*partoftheconfiguredfetchrefspec.)+*/+staticvoidNORETURNdie_no_merge_candidates(constchar*repo,constchar**refspecs)+{+structbranch*curr_branch=branch_get("HEAD");+constchar*remote=curr_branch?curr_branch->remote_name:NULL;++if(*refspecs){+fprintf_ln(stderr,_("There are no candidates for merging among the refs that you just fetched."));+fprintf_ln(stderr,_("Generally this means that you provided a wildcard refspec which had no\n"+"matches on the remote end."));+}elseif(repo&&curr_branch&&(!remote||strcmp(repo,remote))){+fprintf_ln(stderr,_("You asked to pull from the remote '%s', but did not specify\n"+"a branch. Because this is not the default configured remote\n"+"for your current branch, you must specify a branch on the command line."),+repo);+}elseif(!curr_branch){+fprintf_ln(stderr,_("You are not currently on a branch."));+fprintf_ln(stderr,_("Please specify which branch you want to merge with."));+fprintf_ln(stderr,_("See git-pull(1) for details."));+fprintf(stderr,"\n");+fprintf_ln(stderr," git pull <remote> <branch>");+fprintf(stderr,"\n");+}elseif(!curr_branch->merge_nr){+constchar*remote_name=NULL;++if(for_each_remote(get_only_remote,&remote_name)||!remote_name)+remote_name="<remote>";++fprintf_ln(stderr,_("There is no tracking information for the current branch."));+fprintf_ln(stderr,_("Please specify which branch you want to merge with."));+fprintf_ln(stderr,_("See git-pull(1) for details."));+fprintf(stderr,"\n");+fprintf_ln(stderr," git pull <remote> <branch>");+fprintf(stderr,"\n");+fprintf_ln(stderr,_("If you wish to set tracking information for this branch you can do so with:\n"+"\n"+" git branch --set-upstream-to=%s/<branch> %s\n"),+remote_name,curr_branch->name);+}else+fprintf_ln(stderr,_("Your configuration specifies to merge with the ref '%s'\n"+"from the remote, but no such ref was fetched."),+*curr_branch->merge_name);+exit(1);+}++/***Parsesargvinto[<repo>[<refspecs>...]],returningtheirvaluesin`repo`*asastringand`refspecs`asanull-terminatedarrayofstrings.If`repo`*isnotprovidedinargv,itissettoNULL.
@@ -277,6 +384,7 @@ static int run_merge(void)intcmd_pull(intargc,constchar**argv,constchar*prefix){constchar*repo,**refspecs;+structsha1_arraymerge_heads=SHA1_ARRAY_INIT;if(!getenv("_GIT_USE_BUILTIN_PULL")){constchar*path=mkpath("%s/git-pull",git_exec_path());
Since eb2a8d9 (pull: handle git-fetch's options as well, 2015-06-02),
git-pull knows about and handles git-fetch's options, passing them to
git-fetch. Re-implement this behavior.
Since 29609e6 (pull: do nothing on --dry-run, 2010-05-25) git-pull
supported the --dry-run option, exiting after git-fetch if --dry-run is
set. Re-implement this behavior.
Signed-off-by: Paul Tan <redacted>
---
builtin/pull.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 95 insertions(+)
@@ -82,6 +97,46 @@ static struct option pull_options[] = {N_("GPG sign commit"),PARSE_OPT_OPTARG),+/* Options passed to git-fetch */+OPT_GROUP(N_("Options related to fetching")),+OPT_PASSTHRU(0,"all",&opt_all,0,+N_("fetch from all remotes"),+PARSE_OPT_NOARG),+OPT_PASSTHRU('a',"append",&opt_append,0,+N_("append to .git/FETCH_HEAD instead of overwriting"),+PARSE_OPT_NOARG),+OPT_PASSTHRU(0,"upload-pack",&opt_upload_pack,N_("path"),+N_("path to upload pack on remote end"),+0),+OPT__FORCE(&opt_force,N_("force overwrite of local branch")),+OPT_PASSTHRU('t',"tags",&opt_tags,0,+N_("fetch all tags and associated objects"),+PARSE_OPT_NOARG),+OPT_PASSTHRU('p',"prune",&opt_prune,0,+N_("prune remote-tracking branches no longer on remote"),+PARSE_OPT_NOARG),+OPT_PASSTHRU(0,"recurse-submodules",&opt_recurse_submodules,+N_("on-demand"),+N_("control recursive fetching of submodules"),+PARSE_OPT_OPTARG),+OPT_BOOL(0,"dry-run",&opt_dry_run,+N_("dry run")),+OPT_PASSTHRU('k',"keep",&opt_keep,0,+N_("keep downloaded pack"),+PARSE_OPT_NOARG),+OPT_PASSTHRU(0,"depth",&opt_depth,N_("depth"),+N_("deepen history of shallow clone"),+0),+OPT_PASSTHRU(0,"unshallow",&opt_unshallow,0,+N_("convert to a complete repository"),+PARSE_OPT_NONEG|PARSE_OPT_NOARG),+OPT_PASSTHRU(0,"update-shallow",&opt_update_shallow,0,+N_("accept refs that update .git/shallow"),+PARSE_OPT_NOARG),+OPT_PASSTHRU(0,"refmap",&opt_refmap,N_("refmap"),+N_("specify fetch refmap"),+PARSE_OPT_NONEG),+OPT_END()};
Since d38a30d (Be more user-friendly when refusing to do something
because of conflict., 2010-01-12), git-pull will error out with
user-friendly advices if the user is in the middle of a merge or has
unmerged files.
Re-implement this behavior. While the "has unmerged files" case can be
handled by die_resolve_conflict(), we introduce a new function
die_conclude_merge() for printing a different error message for when
there are no unmerged files but the merge has not been finished.
Signed-off-by: Paul Tan <redacted>
---
advice.c | 8 ++++++++
advice.h | 1 +
builtin/pull.c | 9 +++++++++
3 files changed, 18 insertions(+)
@@ -96,6 +96,14 @@ void NORETURN die_resolve_conflict(const char *me)die("Exiting because of an unresolved conflict.");}+voidNORETURNdie_conclude_merge(void)+{+error(_("You have not concluded your merge (MERGE_HEAD exists)."));+if(advice_resolve_conflict)+advise(_("Please, commit your changes before you can merge."));+die(_("Exiting because of unfinished merge."));+}+voiddetach_advice(constchar*new_name){constcharfmt[]=
f947413 (Use GIT_REFLOG_ACTION environment variable instead.,
2006-12-28) established git-pull's method for setting the reflog
message, which is to set the environment variable GIT_REFLOG_ACTION to
the evaluation of "pull${1+ $*}" if it has not already been set.
Re-implement this behavior.
Helped-by: Junio C Hamano [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
builtin/pull.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
Since b814da8 (pull: add pull.ff configuration, 2014-01-15), git-pull.sh
would lookup the configuration value of "pull.ff", and set the flag
"--ff" if its value is "true", "--no-ff" if its value is "false" and
"--ff-only" if its value is "only".
Re-implement this behavior.
Signed-off-by: Paul Tan <redacted>
---
Notes:
v3
* style fixes
builtin/pull.c | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
b4dc085 (pull: merge into unborn by fast-forwarding from empty
tree, 2013-06-20) established git-pull's current behavior of pulling
into an unborn branch by fast-forwarding the work tree from an empty
tree to the merge head, then setting HEAD to the merge head.
Re-implement this behavior by introducing pull_into_void() which will be
called instead of run_merge() if HEAD is invalid.
Helped-by: Stephen Robin [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
Notes:
v3
* style fixes
builtin/pull.c | 29 ++++++++++++++++++++++++++++-
1 file changed, 28 insertions(+), 1 deletion(-)
Since b10ac50 (Fix pulling into the same branch., 2005-08-25), git-pull,
upon detecting that git-fetch updated the current head, will
fast-forward the working tree to the updated head commit.
Re-implement this behavior.
Signed-off-by: Paul Tan <redacted>
---
builtin/pull.c | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
@@ -435,12 +436,41 @@ int cmd_pull(int argc, const char **argv, const char *prefix)if(file_exists(git_path("MERGE_HEAD")))die_conclude_merge();+if(get_sha1("HEAD",orig_head))+hashclr(orig_head);+if(run_fetch(repo,refspecs))return1;if(opt_dry_run)return0;+if(get_sha1("HEAD",curr_head))+hashclr(curr_head);++if(!is_null_sha1(orig_head)&&!is_null_sha1(curr_head)&&+hashcmp(orig_head,curr_head)){+/*+*Thefetchinvolvedupdatingthecurrentbranch.+*+*Theworkingtreeandtheindexfilearestillbasedon+*orig_headcommit,butwearemergingintocurr_head.+*Updatetheworkingtreetomatchcurr_head.+*/++warning(_("fetch updated the current branch head.\n"+"fast-forwarding your working tree from\n"+"commit %s."),sha1_to_hex(orig_head));++if(checkout_fast_forward(orig_head,curr_head,0))+die(_("Cannot fast-forward your working tree.\n"+"After making sure that you saved anything precious from\n"+"$ git diff %s\n"+"output, run\n"+"$ git reset --hard\n"+"to recover."),sha1_to_hex(orig_head));+}+get_merge_heads(&merge_heads);if(!merge_heads.nr)
@@ -27,6 +39,49 @@ static struct option pull_options[] = {N_("force progress reporting"),PARSE_OPT_NOARG),+/* Options passed to git-merge */+OPT_GROUP(N_("Options related to merging")),+OPT_PASSTHRU('n',NULL,&opt_diffstat,NULL,+N_("do not show a diffstat at the end of the merge"),+PARSE_OPT_NOARG|PARSE_OPT_NONEG),+OPT_PASSTHRU(0,"stat",&opt_diffstat,NULL,+N_("show a diffstat at the end of the merge"),+PARSE_OPT_NOARG),+OPT_PASSTHRU(0,"summary",&opt_diffstat,NULL,+N_("(synonym to --stat)"),+PARSE_OPT_NOARG|PARSE_OPT_HIDDEN),+OPT_PASSTHRU(0,"log",&opt_log,N_("n"),+N_("add (at most <n>) entries from shortlog to merge commit message"),+PARSE_OPT_OPTARG),+OPT_PASSTHRU(0,"squash",&opt_squash,NULL,+N_("create a single commit instead of doing a merge"),+PARSE_OPT_NOARG),+OPT_PASSTHRU(0,"commit",&opt_commit,NULL,+N_("perform a commit if the merge succeeds (default)"),+PARSE_OPT_NOARG),+OPT_PASSTHRU(0,"edit",&opt_edit,NULL,+N_("edit message before committing"),+PARSE_OPT_NOARG),+OPT_PASSTHRU(0,"ff",&opt_ff,NULL,+N_("allow fast-forward"),+PARSE_OPT_NOARG),+OPT_PASSTHRU(0,"ff-only",&opt_ff,NULL,+N_("abort if fast-forward is not possible"),+PARSE_OPT_NOARG|PARSE_OPT_NONEG),+OPT_PASSTHRU(0,"verify-signatures",&opt_verify_signatures,NULL,+N_("verify that the named commit has a valid GPG signature"),+PARSE_OPT_NOARG),+OPT_PASSTHRU_ARGV('s',"strategy",&opt_strategies,N_("strategy"),+N_("merge strategy to use"),+0),+OPT_PASSTHRU_ARGV('X',"strategy-option",&opt_strategy_opts,+N_("option=value"),+N_("option for selected merge strategy"),+0),+OPT_PASSTHRU('S',"gpg-sign",&opt_gpg_sign,N_("key-id"),+N_("GPG sign commit"),+PARSE_OPT_OPTARG),+OPT_END()};
@@ -101,6 +156,26 @@ static int run_merge(void)if(opt_progress)argv_array_push(&args,opt_progress);+/* Options passed to git-merge */+if(opt_diffstat)+argv_array_push(&args,opt_diffstat);+if(opt_log)+argv_array_push(&args,opt_log);+if(opt_squash)+argv_array_push(&args,opt_squash);+if(opt_commit)+argv_array_push(&args,opt_commit);+if(opt_edit)+argv_array_push(&args,opt_edit);+if(opt_ff)+argv_array_push(&args,opt_ff);+if(opt_verify_signatures)+argv_array_push(&args,opt_verify_signatures);+argv_array_pushv(&args,opt_strategies.argv);+argv_array_pushv(&args,opt_strategy_opts.argv);+if(opt_gpg_sign)+argv_array_push(&args,opt_gpg_sign);+argv_array_push(&args,"FETCH_HEAD");ret=run_command_v_opt(args.argv,RUN_GIT_CMD);argv_array_clear(&args);
Since cd67e4d (Teach 'git pull' about --rebase, 2007-11-28), if the
--rebase option is set, git-rebase is run instead of git-merge.
Re-implement this by introducing run_rebase(), which is called instead
of run_merge() if opt_rebase is a true value.
Since c85c792 (pull --rebase: be cleverer with rebased upstream
branches, 2008-01-26), git-pull handles the case where the upstream
branch was rebased since it was last fetched. The fork point (old remote
ref) of the branch from the upstream branch is calculated before fetch,
and then rebased from onto the new remote head (merge_head) after fetch.
Re-implement this by introducing get_merge_branch_2() and
get_merge_branch_1() to find the upstream branch for the
specified/current branch, and get_rebase_fork_point() which will find
the fork point between the upstream branch and current branch.
However, the above change created a problem where git-rebase cannot
detect commits that are already upstream, and thus may result in
unnecessary conflicts. cf65426 (pull --rebase: Avoid spurious conflicts
and reapplying unnecessary patches, 2010-08-12) fixes this by ignoring
the above old remote ref if it is contained within the merge base of the
merge head and the current branch.
This is re-implemented in run_rebase() where fork_point is not used if
it is the merge base returned by get_octopus_merge_base().
Helped-by: Stefan Beller [off-list ref]
Helped-by: Johannes Schindelin [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
Notes:
v3
* use branch_get_upstream()
* style fixes
* adjust to removal of branch->remote in 9e3751d (remote.c: drop
"remote" pointer from "struct branch", 2015-05-21)
* I realised that if parse_config_rebase() handled the die()-ing and
error()-ing, it would make the next patch more pleasant.
builtin/pull.c | 247 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 245 insertions(+), 2 deletions(-)
@@ -15,6 +15,53 @@#include"dir.h"#include"refs.h"+enumrebase_type{+REBASE_INVALID=-1,+REBASE_FALSE=0,+REBASE_TRUE,+REBASE_PRESERVE+};++/**+*Parsesthevalueof--rebase.Ifvalueisafalsevalue,returns+*REBASE_FALSE.Ifvalueisatruevalue,returnsREBASE_TRUE.Ifvalueis+*"preserve",returnsREBASE_PRESERVE.Ifvalueisainvalidvalue,dieswith+*afatalerroriffatalistrue,otherwisereturnsREBASE_INVALID.+*/+staticenumrebase_typeparse_config_rebase(constchar*key,constchar*value,+intfatal)+{+intv=git_config_maybe_bool("pull.rebase",value);++if(!v)+returnREBASE_FALSE;+elseif(v>0)+returnREBASE_TRUE;+elseif(!strcmp(value,"preserve"))+returnREBASE_PRESERVE;++if(fatal)+die(_("Invalid value for %s: %s"),key,value);+else+error(_("Invalid value for %s: %s"),key,value);++returnREBASE_INVALID;+}++/**+*Callbackfor--rebase,whichparsesargwithparse_config_rebase().+*/+staticintparse_opt_rebase(conststructoption*opt,constchar*arg,intunset)+{+enumrebase_type*value=opt->value;++if(arg)+*value=parse_config_rebase("--rebase",arg,0);+else+*value=unset?REBASE_FALSE:REBASE_TRUE;+return*value==REBASE_INVALID?-1:0;+}+staticconstchar*constpull_usage[]={N_("git pull [options] [<repository> [<refspec>...]]"),NULL
@@ -24,7 +71,8 @@ static const char * const pull_usage[] = {staticintopt_verbosity;staticchar*opt_progress;-/* Options passed to git-merge */+/* Options passed to git-merge or git-rebase */+staticenumrebase_typeopt_rebase;staticchar*opt_diffstat;staticchar*opt_log;staticchar*opt_squash;
@@ -58,8 +106,12 @@ static struct option pull_options[] = {N_("force progress reporting"),PARSE_OPT_NOARG),-/* Options passed to git-merge */+/* Options passed to git-merge or git-rebase */OPT_GROUP(N_("Options related to merging")),+{OPTION_CALLBACK,'r',"rebase",&opt_rebase,+N_("false|true|preserve"),+N_("incorporate changes by rebasing rather than merging"),+PARSE_OPT_OPTARG,parse_opt_rebase},OPT_PASSTHRU('n',NULL,&opt_diffstat,NULL,N_("do not show a diffstat at the end of the merge"),PARSE_OPT_NOARG|PARSE_OPT_NONEG),
@@ -449,11 +501,194 @@ static int run_merge(void)returnret;}+/**+*Returnsremote'supstreambranchforthecurrentbranch.IfremoteisNULL,+*thecurrentbranch'sconfigureddefaultremoteisused.ReturnsNULLif+*`remote`doesnotnameavalidremote,HEADdoesnotpointtoabranch,+*remoteisnotthebranch'sconfiguredremoteorthebranchdoesnothaveany+*configuredupstreambranch.+*/+staticconstchar*get_upstream_branch(constchar*remote)+{+structremote*rm;+structbranch*curr_branch;+constchar*curr_branch_remote;++rm=remote_get(remote);+if(!rm)+returnNULL;++curr_branch=branch_get("HEAD");+if(!curr_branch)+returnNULL;++curr_branch_remote=remote_for_branch(curr_branch,NULL);+assert(curr_branch_remote);++if(strcmp(curr_branch_remote,rm->name))+returnNULL;++returnbranch_get_upstream(curr_branch,NULL);+}++/**+*Derivestheremotetrackingbranchfromtheremoteandrefspec.+*+*FIXME:Thecurrentimplementationassumesthedefaultmappingof+*refs/heads/<branch_name>torefs/remotes/<remote_name>/<branch_name>.+*/+staticconstchar*get_tracking_branch(constchar*remote,constchar*refspec)+{+structrefspec*spec;+constchar*spec_src;+constchar*merge_branch;++spec=parse_fetch_refspec(1,&refspec);+spec_src=spec->src;+if(!*spec_src||!strcmp(spec_src,"HEAD"))+spec_src="HEAD";+elseif(skip_prefix(spec_src,"heads/",&spec_src))+;+elseif(skip_prefix(spec_src,"refs/heads/",&spec_src))+;+elseif(starts_with(spec_src,"refs/")||+starts_with(spec_src,"tags/")||+starts_with(spec_src,"remotes/"))+spec_src="";++if(*spec_src){+if(!strcmp(remote,"."))+merge_branch=mkpath("refs/heads/%s",spec_src);+else+merge_branch=mkpath("refs/remotes/%s/%s",remote,spec_src);+}else+merge_branch=NULL;++free_refspec(1,spec);+returnmerge_branch;+}++/**+*Giventherepoandrefspecs,setsfork_pointtothepointatwhichthe+*currentbranchforkedfromitsremotetrackingbranch.Returns0onsuccess,+*-1onfailure.+*/+staticintget_rebase_fork_point(unsignedchar*fork_point,constchar*repo,+constchar*refspec)+{+intret;+structbranch*curr_branch;+constchar*remote_branch;+structchild_processcp=CHILD_PROCESS_INIT;+structstrbufsb=STRBUF_INIT;++curr_branch=branch_get("HEAD");+if(!curr_branch)+return-1;++if(refspec)+remote_branch=get_tracking_branch(repo,refspec);+else+remote_branch=get_upstream_branch(repo);++if(!remote_branch)+return-1;++argv_array_pushl(&cp.args,"merge-base","--fork-point",+remote_branch,curr_branch->name,NULL);+cp.no_stdin=1;+cp.no_stderr=1;+cp.git_cmd=1;++ret=capture_command(&cp,&sb,GIT_SHA1_HEXSZ);+if(ret)+gotocleanup;++ret=get_sha1_hex(sb.buf,fork_point);+if(ret)+gotocleanup;++cleanup:+strbuf_release(&sb);+returnret?-1:0;+}++/**+*Setsmerge_basetotheoctopusmergebaseofcurr_head,merge_headand+*fork_point.Returns0ifamergebaseisfound,1otherwise.+*/+staticintget_octopus_merge_base(unsignedchar*merge_base,+constunsignedchar*curr_head,+constunsignedchar*merge_head,+constunsignedchar*fork_point)+{+structcommit_list*revs=NULL,*result;++commit_list_insert(lookup_commit_reference(curr_head),&revs);+commit_list_insert(lookup_commit_reference(merge_head),&revs);+if(!is_null_sha1(fork_point))+commit_list_insert(lookup_commit_reference(fork_point),&revs);++result=reduce_heads(get_octopus_merge_bases(revs));+free_commit_list(revs);+if(!result)+return1;++hashcpy(merge_base,result->item->object.sha1);+return0;+}++/**+*GiventhecurrentHEADSHA1,themergeheadreturnedfromgit-fetchandthe+*forkpointcalculatedbyget_rebase_fork_point(),runsgit-rebasewiththe+*appropriateargumentsandreturnsitsexitstatus.+*/+staticintrun_rebase(constunsignedchar*curr_head,+constunsignedchar*merge_head,+constunsignedchar*fork_point)+{+intret;+unsignedcharoct_merge_base[GIT_SHA1_RAWSZ];+structargv_arrayargs=ARGV_ARRAY_INIT;++if(!get_octopus_merge_base(oct_merge_base,curr_head,merge_head,fork_point))+if(!is_null_sha1(fork_point)&&!hashcmp(oct_merge_base,fork_point))+fork_point=NULL;++argv_array_push(&args,"rebase");++/* Shared options */+argv_push_verbosity(&args);++/* Options passed to git-rebase */+if(opt_rebase==REBASE_PRESERVE)+argv_array_push(&args,"--preserve-merges");+if(opt_diffstat)+argv_array_push(&args,opt_diffstat);+argv_array_pushv(&args,opt_strategies.argv);+argv_array_pushv(&args,opt_strategy_opts.argv);+if(opt_gpg_sign)+argv_array_push(&args,opt_gpg_sign);++argv_array_push(&args,"--onto");+argv_array_push(&args,sha1_to_hex(merge_head));++if(fork_point&&!is_null_sha1(fork_point))+argv_array_push(&args,sha1_to_hex(fork_point));+else+argv_array_push(&args,sha1_to_hex(merge_head));++ret=run_command_v_opt(args.argv,RUN_GIT_CMD);+argv_array_clear(&args);+returnret;+}+intcmd_pull(intargc,constchar**argv,constchar*prefix){constchar*repo,**refspecs;structsha1_arraymerge_heads=SHA1_ARRAY_INIT;unsignedcharorig_head[GIT_SHA1_RAWSZ],curr_head[GIT_SHA1_RAWSZ];+unsignedcharrebase_fork_point[GIT_SHA1_RAWSZ];if(!getenv("_GIT_USE_BUILTIN_PULL")){constchar*path=mkpath("%s/git-pull",git_exec_path());
Since cd67e4d (Teach 'git pull' about --rebase, 2007-11-28),
fetch+rebase could be set by default by defining the config variable
branch.<name>.rebase. This setting can be overriden on the command line
by --rebase and --no-rebase.
Since 6b37dff (pull: introduce a pull.rebase option to enable --rebase,
2011-11-06), git-pull --rebase can also be configured via the
pull.rebase configuration option.
Re-implement support for these two configuration settings by introducing
config_get_rebase() which is called before parse_options() to set the
default value of opt_rebase.
Helped-by: Stefan Beller [off-list ref]
Signed-off-by: Paul Tan <redacted>
---
Notes:
v3
* Now that parse_config_rebase() takes care of the die()-ing and
error()-ing, we only need one function again. Yay!
* The free()s is ugly though. Ideally, I would like to have a xstrfmt()
function that returns a static buffer.
* We now don't lookup the pull.rebase config if the --rebase option is
provided on the command-line.
builtin/pull.c | 34 +++++++++++++++++++++++++++++++++-
1 file changed, 33 insertions(+), 1 deletion(-)
@@ -72,7 +72,7 @@ static int opt_verbosity;staticchar*opt_progress;/* Options passed to git-merge or git-rebase */-staticenumrebase_typeopt_rebase;+staticenumrebase_typeopt_rebase=-1;staticchar*opt_diffstat;staticchar*opt_log;staticchar*opt_squash;
Re-implement the behavior introduced by f9189cf (pull --rebase: exit
early when the working directory is dirty, 2008-05-21).
Signed-off-by: Paul Tan <redacted>
---
builtin/pull.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 76 insertions(+), 1 deletion(-)
@@ -295,6 +297,73 @@ static enum rebase_type config_get_rebase(void)}/**+*Returns1ifthereareunstagedchanges,0otherwise.+*/+staticinthas_unstaged_changes(constchar*prefix)+{+structrev_inforev_info;+intresult;++init_revisions(&rev_info,prefix);+DIFF_OPT_SET(&rev_info.diffopt,IGNORE_SUBMODULES);+DIFF_OPT_SET(&rev_info.diffopt,QUICK);+diff_setup_done(&rev_info.diffopt);+result=run_diff_files(&rev_info,0);+returndiff_result_code(&rev_info.diffopt,result);+}++/**+*Returns1ifthereareuncommittedchanges,0otherwise.+*/+staticinthas_uncommitted_changes(constchar*prefix)+{+structrev_inforev_info;+intresult;++if(is_cache_unborn())+return0;++init_revisions(&rev_info,prefix);+DIFF_OPT_SET(&rev_info.diffopt,IGNORE_SUBMODULES);+DIFF_OPT_SET(&rev_info.diffopt,QUICK);+add_head_to_pending(&rev_info);+diff_setup_done(&rev_info.diffopt);+result=run_diff_index(&rev_info,1);+returndiff_result_code(&rev_info.diffopt,result);+}++/**+*Iftheworktreehasunstagedoruncommittedchanges,dieswiththe+*appropriatemessage.+*/+staticvoiddie_on_unclean_work_tree(constchar*prefix)+{+structlock_file*lock_file=xcalloc(1,sizeof(*lock_file));+intdo_die=0;++hold_locked_index(lock_file,0);+refresh_cache(REFRESH_QUIET);+update_index_if_able(&the_index,lock_file);+rollback_lock_file(lock_file);++if(has_unstaged_changes(prefix)){+error(_("Cannot pull with rebase: You have unstaged changes."));+do_die=1;+}++if(has_uncommitted_changes(prefix)){+if(do_die)+error(_("Additionally, your index contains uncommitted changes."));+else+error(_("Cannot pull with rebase: Your index contains uncommitted changes."));+do_die=1;+}++if(do_die)+exit(1);+}++/***AppendsmergecandidatesfromFETCH_HEADthatarenotmarkednot-for-merge*intomerge_heads.*/
@@ -750,9 +819,15 @@ int cmd_pull(int argc, const char **argv, const char *prefix)if(get_sha1("HEAD",orig_head))hashclr(orig_head);-if(opt_rebase)+if(opt_rebase){+if(is_null_sha1(orig_head)&&!is_cache_unborn())+die(_("Updating an unborn branch with changes added to the index."));++die_on_unclean_work_tree(prefix);+if(get_rebase_fork_point(rebase_fork_point,repo,*refspecs))hashclr(rebase_fork_point);+}if(run_fetch(repo,refspecs))return1;
Tweak the error messages printed by die_no_merge_candidates() to take
into account that we may be "rebasing against" rather than "merging
with".
Signed-off-by: Paul Tan <redacted>
---
builtin/pull.c | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
@@ -430,7 +430,10 @@ static void NORETURN die_no_merge_candidates(const char *repo, const char **refsconstchar*remote=curr_branch?curr_branch->remote_name:NULL;if(*refspecs){-fprintf_ln(stderr,_("There are no candidates for merging among the refs that you just fetched."));+if(opt_rebase)+fprintf_ln(stderr,_("There is no candidate for rebasing against among the refs that you just fetched."));+else+fprintf_ln(stderr,_("There are no candidates for merging among the refs that you just fetched."));fprintf_ln(stderr,_("Generally this means that you provided a wildcard refspec which had no\n""matches on the remote end."));}elseif(repo&&curr_branch&&(!remote||strcmp(repo,remote))){
@@ -440,7 +443,10 @@ static void NORETURN die_no_merge_candidates(const char *repo, const char **refsrepo);}elseif(!curr_branch){fprintf_ln(stderr,_("You are not currently on a branch."));-fprintf_ln(stderr,_("Please specify which branch you want to merge with."));+if(opt_rebase)+fprintf_ln(stderr,_("Please specify which branch you want to rebase against."));+else+fprintf_ln(stderr,_("Please specify which branch you want to merge with."));fprintf_ln(stderr,_("See git-pull(1) for details."));fprintf(stderr,"\n");fprintf_ln(stderr," git pull <remote> <branch>");
@@ -452,7 +458,10 @@ static void NORETURN die_no_merge_candidates(const char *repo, const char **refsremote_name="<remote>";fprintf_ln(stderr,_("There is no tracking information for the current branch."));-fprintf_ln(stderr,_("Please specify which branch you want to merge with."));+if(opt_rebase)+fprintf_ln(stderr,_("Please specify which branch you want to rebase against."));+else+fprintf_ln(stderr,_("Please specify which branch you want to merge with."));fprintf_ln(stderr,_("See git-pull(1) for details."));fprintf(stderr,"\n");fprintf_ln(stderr," git pull <remote> <branch>");
At the beginning of the rewrite of git-pull.sh to C, we introduced a
redirection to git-pull.sh if the environment variable
_GIT_USE_BUILTIN_PULL was not defined in order to not break test scripts
that relied on a functional git-pull.
Now that all of git-pull's functionality has been re-implemented in
builtin/pull.c, remove this redirection, and retire the old git-pull.sh
into contrib/examples/.
Signed-off-by: Paul Tan <redacted>
---
Makefile | 1 -
builtin/pull.c | 7 -------
git-pull.sh => contrib/examples/git-pull.sh | 0
3 files changed, 8 deletions(-)
rename git-pull.sh => contrib/examples/git-pull.sh (100%)