Skip to content

Commit 1678b81

Browse files
pyokagangitster
authored andcommitted
pull: teach git pull about --rebase
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 <[email protected]> Helped-by: Johannes Schindelin <[email protected]> Signed-off-by: Paul Tan <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 41fca09 commit 1678b81

File tree

1 file changed

+245
-2
lines changed

1 file changed

+245
-2
lines changed

builtin/pull.c

Lines changed: 245 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,53 @@
1515
#include "dir.h"
1616
#include "refs.h"
1717

18+
enum rebase_type {
19+
REBASE_INVALID = -1,
20+
REBASE_FALSE = 0,
21+
REBASE_TRUE,
22+
REBASE_PRESERVE
23+
};
24+
25+
/**
26+
* Parses the value of --rebase. If value is a false value, returns
27+
* REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is
28+
* "preserve", returns REBASE_PRESERVE. If value is a invalid value, dies with
29+
* a fatal error if fatal is true, otherwise returns REBASE_INVALID.
30+
*/
31+
static enum rebase_type parse_config_rebase(const char *key, const char *value,
32+
int fatal)
33+
{
34+
int v = git_config_maybe_bool("pull.rebase", value);
35+
36+
if (!v)
37+
return REBASE_FALSE;
38+
else if (v > 0)
39+
return REBASE_TRUE;
40+
else if (!strcmp(value, "preserve"))
41+
return REBASE_PRESERVE;
42+
43+
if (fatal)
44+
die(_("Invalid value for %s: %s"), key, value);
45+
else
46+
error(_("Invalid value for %s: %s"), key, value);
47+
48+
return REBASE_INVALID;
49+
}
50+
51+
/**
52+
* Callback for --rebase, which parses arg with parse_config_rebase().
53+
*/
54+
static int parse_opt_rebase(const struct option *opt, const char *arg, int unset)
55+
{
56+
enum rebase_type *value = opt->value;
57+
58+
if (arg)
59+
*value = parse_config_rebase("--rebase", arg, 0);
60+
else
61+
*value = unset ? REBASE_FALSE : REBASE_TRUE;
62+
return *value == REBASE_INVALID ? -1 : 0;
63+
}
64+
1865
static const char * const pull_usage[] = {
1966
N_("git pull [options] [<repository> [<refspec>...]]"),
2067
NULL
@@ -24,7 +71,8 @@ static const char * const pull_usage[] = {
2471
static int opt_verbosity;
2572
static char *opt_progress;
2673

27-
/* Options passed to git-merge */
74+
/* Options passed to git-merge or git-rebase */
75+
static enum rebase_type opt_rebase;
2876
static char *opt_diffstat;
2977
static char *opt_log;
3078
static char *opt_squash;
@@ -58,8 +106,12 @@ static struct option pull_options[] = {
58106
N_("force progress reporting"),
59107
PARSE_OPT_NOARG),
60108

61-
/* Options passed to git-merge */
109+
/* Options passed to git-merge or git-rebase */
62110
OPT_GROUP(N_("Options related to merging")),
111+
{ OPTION_CALLBACK, 'r', "rebase", &opt_rebase,
112+
N_("false|true|preserve"),
113+
N_("incorporate changes by rebasing rather than merging"),
114+
PARSE_OPT_OPTARG, parse_opt_rebase },
63115
OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,
64116
N_("do not show a diffstat at the end of the merge"),
65117
PARSE_OPT_NOARG | PARSE_OPT_NONEG),
@@ -449,11 +501,194 @@ static int run_merge(void)
449501
return ret;
450502
}
451503

504+
/**
505+
* Returns remote's upstream branch for the current branch. If remote is NULL,
506+
* the current branch's configured default remote is used. Returns NULL if
507+
* `remote` does not name a valid remote, HEAD does not point to a branch,
508+
* remote is not the branch's configured remote or the branch does not have any
509+
* configured upstream branch.
510+
*/
511+
static const char *get_upstream_branch(const char *remote)
512+
{
513+
struct remote *rm;
514+
struct branch *curr_branch;
515+
const char *curr_branch_remote;
516+
517+
rm = remote_get(remote);
518+
if (!rm)
519+
return NULL;
520+
521+
curr_branch = branch_get("HEAD");
522+
if (!curr_branch)
523+
return NULL;
524+
525+
curr_branch_remote = remote_for_branch(curr_branch, NULL);
526+
assert(curr_branch_remote);
527+
528+
if (strcmp(curr_branch_remote, rm->name))
529+
return NULL;
530+
531+
return branch_get_upstream(curr_branch, NULL);
532+
}
533+
534+
/**
535+
* Derives the remote tracking branch from the remote and refspec.
536+
*
537+
* FIXME: The current implementation assumes the default mapping of
538+
* refs/heads/<branch_name> to refs/remotes/<remote_name>/<branch_name>.
539+
*/
540+
static const char *get_tracking_branch(const char *remote, const char *refspec)
541+
{
542+
struct refspec *spec;
543+
const char *spec_src;
544+
const char *merge_branch;
545+
546+
spec = parse_fetch_refspec(1, &refspec);
547+
spec_src = spec->src;
548+
if (!*spec_src || !strcmp(spec_src, "HEAD"))
549+
spec_src = "HEAD";
550+
else if (skip_prefix(spec_src, "heads/", &spec_src))
551+
;
552+
else if (skip_prefix(spec_src, "refs/heads/", &spec_src))
553+
;
554+
else if (starts_with(spec_src, "refs/") ||
555+
starts_with(spec_src, "tags/") ||
556+
starts_with(spec_src, "remotes/"))
557+
spec_src = "";
558+
559+
if (*spec_src) {
560+
if (!strcmp(remote, "."))
561+
merge_branch = mkpath("refs/heads/%s", spec_src);
562+
else
563+
merge_branch = mkpath("refs/remotes/%s/%s", remote, spec_src);
564+
} else
565+
merge_branch = NULL;
566+
567+
free_refspec(1, spec);
568+
return merge_branch;
569+
}
570+
571+
/**
572+
* Given the repo and refspecs, sets fork_point to the point at which the
573+
* current branch forked from its remote tracking branch. Returns 0 on success,
574+
* -1 on failure.
575+
*/
576+
static int get_rebase_fork_point(unsigned char *fork_point, const char *repo,
577+
const char *refspec)
578+
{
579+
int ret;
580+
struct branch *curr_branch;
581+
const char *remote_branch;
582+
struct child_process cp = CHILD_PROCESS_INIT;
583+
struct strbuf sb = STRBUF_INIT;
584+
585+
curr_branch = branch_get("HEAD");
586+
if (!curr_branch)
587+
return -1;
588+
589+
if (refspec)
590+
remote_branch = get_tracking_branch(repo, refspec);
591+
else
592+
remote_branch = get_upstream_branch(repo);
593+
594+
if (!remote_branch)
595+
return -1;
596+
597+
argv_array_pushl(&cp.args, "merge-base", "--fork-point",
598+
remote_branch, curr_branch->name, NULL);
599+
cp.no_stdin = 1;
600+
cp.no_stderr = 1;
601+
cp.git_cmd = 1;
602+
603+
ret = capture_command(&cp, &sb, GIT_SHA1_HEXSZ);
604+
if (ret)
605+
goto cleanup;
606+
607+
ret = get_sha1_hex(sb.buf, fork_point);
608+
if (ret)
609+
goto cleanup;
610+
611+
cleanup:
612+
strbuf_release(&sb);
613+
return ret ? -1 : 0;
614+
}
615+
616+
/**
617+
* Sets merge_base to the octopus merge base of curr_head, merge_head and
618+
* fork_point. Returns 0 if a merge base is found, 1 otherwise.
619+
*/
620+
static int get_octopus_merge_base(unsigned char *merge_base,
621+
const unsigned char *curr_head,
622+
const unsigned char *merge_head,
623+
const unsigned char *fork_point)
624+
{
625+
struct commit_list *revs = NULL, *result;
626+
627+
commit_list_insert(lookup_commit_reference(curr_head), &revs);
628+
commit_list_insert(lookup_commit_reference(merge_head), &revs);
629+
if (!is_null_sha1(fork_point))
630+
commit_list_insert(lookup_commit_reference(fork_point), &revs);
631+
632+
result = reduce_heads(get_octopus_merge_bases(revs));
633+
free_commit_list(revs);
634+
if (!result)
635+
return 1;
636+
637+
hashcpy(merge_base, result->item->object.sha1);
638+
return 0;
639+
}
640+
641+
/**
642+
* Given the current HEAD SHA1, the merge head returned from git-fetch and the
643+
* fork point calculated by get_rebase_fork_point(), runs git-rebase with the
644+
* appropriate arguments and returns its exit status.
645+
*/
646+
static int run_rebase(const unsigned char *curr_head,
647+
const unsigned char *merge_head,
648+
const unsigned char *fork_point)
649+
{
650+
int ret;
651+
unsigned char oct_merge_base[GIT_SHA1_RAWSZ];
652+
struct argv_array args = ARGV_ARRAY_INIT;
653+
654+
if (!get_octopus_merge_base(oct_merge_base, curr_head, merge_head, fork_point))
655+
if (!is_null_sha1(fork_point) && !hashcmp(oct_merge_base, fork_point))
656+
fork_point = NULL;
657+
658+
argv_array_push(&args, "rebase");
659+
660+
/* Shared options */
661+
argv_push_verbosity(&args);
662+
663+
/* Options passed to git-rebase */
664+
if (opt_rebase == REBASE_PRESERVE)
665+
argv_array_push(&args, "--preserve-merges");
666+
if (opt_diffstat)
667+
argv_array_push(&args, opt_diffstat);
668+
argv_array_pushv(&args, opt_strategies.argv);
669+
argv_array_pushv(&args, opt_strategy_opts.argv);
670+
if (opt_gpg_sign)
671+
argv_array_push(&args, opt_gpg_sign);
672+
673+
argv_array_push(&args, "--onto");
674+
argv_array_push(&args, sha1_to_hex(merge_head));
675+
676+
if (fork_point && !is_null_sha1(fork_point))
677+
argv_array_push(&args, sha1_to_hex(fork_point));
678+
else
679+
argv_array_push(&args, sha1_to_hex(merge_head));
680+
681+
ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
682+
argv_array_clear(&args);
683+
return ret;
684+
}
685+
452686
int cmd_pull(int argc, const char **argv, const char *prefix)
453687
{
454688
const char *repo, **refspecs;
455689
struct sha1_array merge_heads = SHA1_ARRAY_INIT;
456690
unsigned char orig_head[GIT_SHA1_RAWSZ], curr_head[GIT_SHA1_RAWSZ];
691+
unsigned char rebase_fork_point[GIT_SHA1_RAWSZ];
457692

458693
if (!getenv("_GIT_USE_BUILTIN_PULL")) {
459694
const char *path = mkpath("%s/git-pull", git_exec_path());
@@ -483,6 +718,10 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
483718
if (get_sha1("HEAD", orig_head))
484719
hashclr(orig_head);
485720

721+
if (opt_rebase)
722+
if (get_rebase_fork_point(rebase_fork_point, repo, *refspecs))
723+
hashclr(rebase_fork_point);
724+
486725
if (run_fetch(repo, refspecs))
487726
return 1;
488727

@@ -524,6 +763,10 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
524763
if (merge_heads.nr > 1)
525764
die(_("Cannot merge multiple branches into empty head."));
526765
return pull_into_void(*merge_heads.sha1, curr_head);
766+
} else if (opt_rebase) {
767+
if (merge_heads.nr > 1)
768+
die(_("Cannot rebase onto multiple branches."));
769+
return run_rebase(curr_head, *merge_heads.sha1, rebase_fork_point);
527770
} else
528771
return run_merge();
529772
}

0 commit comments

Comments
 (0)