1161 lines
34 KiB
C
1161 lines
34 KiB
C
/*
|
|
* Builtin "git pull"
|
|
*
|
|
* Based on git-pull.sh by Junio C Hamano
|
|
*
|
|
* Fetch one or more remote refs and merge it/them into the current HEAD.
|
|
*/
|
|
|
|
#include "builtin.h"
|
|
#include "advice.h"
|
|
#include "config.h"
|
|
#include "gettext.h"
|
|
#include "hex.h"
|
|
#include "merge.h"
|
|
#include "object-name.h"
|
|
#include "parse-options.h"
|
|
#include "run-command.h"
|
|
#include "oid-array.h"
|
|
#include "remote.h"
|
|
#include "dir.h"
|
|
#include "path.h"
|
|
#include "read-cache-ll.h"
|
|
#include "rebase.h"
|
|
#include "refs.h"
|
|
#include "refspec.h"
|
|
#include "submodule.h"
|
|
#include "submodule-config.h"
|
|
#include "wt-status.h"
|
|
#include "commit-reach.h"
|
|
#include "sequencer.h"
|
|
|
|
/**
|
|
* Parses the value of --rebase. If value is a false value, returns
|
|
* REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is
|
|
* "merges", returns REBASE_MERGES. If value is a invalid value, dies with
|
|
* a fatal error if fatal is true, otherwise returns REBASE_INVALID.
|
|
*/
|
|
static enum rebase_type parse_config_rebase(const char *key, const char *value,
|
|
int fatal)
|
|
{
|
|
enum rebase_type v = rebase_parse_value(value);
|
|
if (v != REBASE_INVALID)
|
|
return v;
|
|
|
|
if (fatal)
|
|
die(_("invalid value for '%s': '%s'"), key, value);
|
|
else
|
|
error(_("invalid value for '%s': '%s'"), key, value);
|
|
|
|
return REBASE_INVALID;
|
|
}
|
|
|
|
/**
|
|
* Callback for --rebase, which parses arg with parse_config_rebase().
|
|
*/
|
|
static int parse_opt_rebase(const struct option *opt, const char *arg, int unset)
|
|
{
|
|
enum rebase_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;
|
|
}
|
|
|
|
static const char * const pull_usage[] = {
|
|
N_("git pull [<options>] [<repository> [<refspec>...]]"),
|
|
NULL
|
|
};
|
|
|
|
/* Shared options */
|
|
static int opt_verbosity;
|
|
static const char *opt_progress;
|
|
static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
|
|
static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;
|
|
|
|
/* Options passed to git-merge or git-rebase */
|
|
static enum rebase_type opt_rebase = -1;
|
|
static const char *opt_diffstat;
|
|
static const char *opt_log;
|
|
static const char *opt_signoff;
|
|
static const char *opt_squash;
|
|
static const char *opt_commit;
|
|
static const char *opt_edit;
|
|
static const char *cleanup_arg;
|
|
static const char *opt_ff;
|
|
static const char *opt_verify_signatures;
|
|
static const char *opt_verify;
|
|
static int opt_autostash = -1;
|
|
static int config_autostash;
|
|
static int check_trust_level = 1;
|
|
static struct strvec opt_strategies = STRVEC_INIT;
|
|
static struct strvec opt_strategy_opts = STRVEC_INIT;
|
|
static const char *opt_gpg_sign;
|
|
static int opt_allow_unrelated_histories;
|
|
|
|
/* Options passed to git-fetch */
|
|
static const char *opt_all;
|
|
static const char *opt_append;
|
|
static const char *opt_upload_pack;
|
|
static int opt_force;
|
|
static const char *opt_tags;
|
|
static const char *opt_prune;
|
|
static const char *max_children;
|
|
static int opt_dry_run;
|
|
static const char *opt_keep;
|
|
static const char *opt_depth;
|
|
static const char *opt_unshallow;
|
|
static const char *opt_update_shallow;
|
|
static const char *opt_refmap;
|
|
static const char *opt_ipv4;
|
|
static const char *opt_ipv6;
|
|
static int opt_show_forced_updates = -1;
|
|
static const char *set_upstream;
|
|
static struct strvec opt_fetch = STRVEC_INIT;
|
|
|
|
static struct option pull_options[] = {
|
|
/* Shared options */
|
|
OPT__VERBOSITY(&opt_verbosity),
|
|
OPT_PASSTHRU(0, "progress", &opt_progress, NULL,
|
|
N_("force progress reporting"),
|
|
PARSE_OPT_NOARG),
|
|
OPT_CALLBACK_F(0, "recurse-submodules",
|
|
&recurse_submodules_cli, N_("on-demand"),
|
|
N_("control for recursive fetching of submodules"),
|
|
PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),
|
|
|
|
/* Options passed to git-merge or git-rebase */
|
|
OPT_GROUP(N_("Options related to merging")),
|
|
OPT_CALLBACK_F('r', "rebase", &opt_rebase,
|
|
"(false|true|merges|interactive)",
|
|
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),
|
|
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, "signoff", &opt_signoff, NULL,
|
|
N_("add a Signed-off-by trailer"),
|
|
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_CLEANUP(&cleanup_arg),
|
|
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", &opt_verify, NULL,
|
|
N_("control use of pre-merge-commit and commit-msg hooks"),
|
|
PARSE_OPT_NOARG),
|
|
OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL,
|
|
N_("verify that the named commit has a valid GPG signature"),
|
|
PARSE_OPT_NOARG),
|
|
OPT_BOOL(0, "autostash", &opt_autostash,
|
|
N_("automatically stash/stash pop before and after")),
|
|
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_SET_INT(0, "allow-unrelated-histories",
|
|
&opt_allow_unrelated_histories,
|
|
N_("allow merging unrelated histories"), 1),
|
|
|
|
/* Options passed to git-fetch */
|
|
OPT_GROUP(N_("Options related to fetching")),
|
|
OPT_PASSTHRU(0, "all", &opt_all, NULL,
|
|
N_("fetch from all remotes"),
|
|
PARSE_OPT_NOARG),
|
|
OPT_PASSTHRU('a', "append", &opt_append, NULL,
|
|
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"), 0),
|
|
OPT_PASSTHRU('t', "tags", &opt_tags, NULL,
|
|
N_("fetch all tags and associated objects"),
|
|
PARSE_OPT_NOARG),
|
|
OPT_PASSTHRU('p', "prune", &opt_prune, NULL,
|
|
N_("prune remote-tracking branches no longer on remote"),
|
|
PARSE_OPT_NOARG),
|
|
OPT_PASSTHRU('j', "jobs", &max_children, N_("n"),
|
|
N_("number of submodules pulled in parallel"),
|
|
PARSE_OPT_OPTARG),
|
|
OPT_BOOL(0, "dry-run", &opt_dry_run,
|
|
N_("dry run")),
|
|
OPT_PASSTHRU('k', "keep", &opt_keep, NULL,
|
|
N_("keep downloaded pack"),
|
|
PARSE_OPT_NOARG),
|
|
OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"),
|
|
N_("deepen history of shallow clone"),
|
|
0),
|
|
OPT_PASSTHRU_ARGV(0, "shallow-since", &opt_fetch, N_("time"),
|
|
N_("deepen history of shallow repository based on time"),
|
|
0),
|
|
OPT_PASSTHRU_ARGV(0, "shallow-exclude", &opt_fetch, N_("revision"),
|
|
N_("deepen history of shallow clone, excluding rev"),
|
|
0),
|
|
OPT_PASSTHRU_ARGV(0, "deepen", &opt_fetch, N_("n"),
|
|
N_("deepen history of shallow clone"),
|
|
0),
|
|
OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL,
|
|
N_("convert to a complete repository"),
|
|
PARSE_OPT_NONEG | PARSE_OPT_NOARG),
|
|
OPT_PASSTHRU(0, "update-shallow", &opt_update_shallow, NULL,
|
|
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_PASSTHRU_ARGV('o', "server-option", &opt_fetch,
|
|
N_("server-specific"),
|
|
N_("option to transmit"),
|
|
0),
|
|
OPT_PASSTHRU('4', "ipv4", &opt_ipv4, NULL,
|
|
N_("use IPv4 addresses only"),
|
|
PARSE_OPT_NOARG),
|
|
OPT_PASSTHRU('6', "ipv6", &opt_ipv6, NULL,
|
|
N_("use IPv6 addresses only"),
|
|
PARSE_OPT_NOARG),
|
|
OPT_PASSTHRU_ARGV(0, "negotiation-tip", &opt_fetch, N_("revision"),
|
|
N_("report that we have only objects reachable from this object"),
|
|
0),
|
|
OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates,
|
|
N_("check for forced-updates on all updated branches")),
|
|
OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL,
|
|
N_("set upstream for git pull/fetch"),
|
|
PARSE_OPT_NOARG),
|
|
|
|
OPT_END()
|
|
};
|
|
|
|
/**
|
|
* Pushes "-q" or "-v" switches into arr to match the opt_verbosity level.
|
|
*/
|
|
static void argv_push_verbosity(struct strvec *arr)
|
|
{
|
|
int verbosity;
|
|
|
|
for (verbosity = opt_verbosity; verbosity > 0; verbosity--)
|
|
strvec_push(arr, "-v");
|
|
|
|
for (verbosity = opt_verbosity; verbosity < 0; verbosity++)
|
|
strvec_push(arr, "-q");
|
|
}
|
|
|
|
/**
|
|
* Pushes "-f" switches into arr to match the opt_force level.
|
|
*/
|
|
static void argv_push_force(struct strvec *arr)
|
|
{
|
|
int force = opt_force;
|
|
while (force-- > 0)
|
|
strvec_push(arr, "-f");
|
|
}
|
|
|
|
/**
|
|
* Sets the GIT_REFLOG_ACTION environment variable to the concatenation of argv
|
|
*/
|
|
static void set_reflog_message(int argc, const char **argv)
|
|
{
|
|
int i;
|
|
struct strbuf msg = STRBUF_INIT;
|
|
|
|
for (i = 0; i < argc; i++) {
|
|
if (i)
|
|
strbuf_addch(&msg, ' ');
|
|
strbuf_addstr(&msg, argv[i]);
|
|
}
|
|
|
|
setenv("GIT_REFLOG_ACTION", msg.buf, 0);
|
|
|
|
strbuf_release(&msg);
|
|
}
|
|
|
|
/**
|
|
* If pull.ff is unset, returns NULL. If pull.ff is "true", returns "--ff". If
|
|
* pull.ff is "false", returns "--no-ff". If pull.ff is "only", returns
|
|
* "--ff-only". Otherwise, if pull.ff is set to an invalid value, die with an
|
|
* error.
|
|
*/
|
|
static const char *config_get_ff(void)
|
|
{
|
|
const char *value;
|
|
|
|
if (git_config_get_value("pull.ff", &value))
|
|
return NULL;
|
|
|
|
switch (git_parse_maybe_bool(value)) {
|
|
case 0:
|
|
return "--no-ff";
|
|
case 1:
|
|
return "--ff";
|
|
}
|
|
|
|
if (!strcmp(value, "only"))
|
|
return "--ff-only";
|
|
|
|
die(_("invalid value for '%s': '%s'"), "pull.ff", value);
|
|
}
|
|
|
|
/**
|
|
* Returns the default configured value for --rebase. It first looks for the
|
|
* value of "branch.$curr_branch.rebase", where $curr_branch is the current
|
|
* branch, and if HEAD is detached or the configuration key does not exist,
|
|
* looks for the value of "pull.rebase". If both configuration keys do not
|
|
* exist, returns REBASE_FALSE.
|
|
*/
|
|
static enum rebase_type config_get_rebase(int *rebase_unspecified)
|
|
{
|
|
struct branch *curr_branch = branch_get("HEAD");
|
|
const char *value;
|
|
|
|
if (curr_branch) {
|
|
char *key = xstrfmt("branch.%s.rebase", curr_branch->name);
|
|
|
|
if (!git_config_get_value(key, &value)) {
|
|
enum rebase_type ret = parse_config_rebase(key, value, 1);
|
|
free(key);
|
|
return ret;
|
|
}
|
|
|
|
free(key);
|
|
}
|
|
|
|
if (!git_config_get_value("pull.rebase", &value))
|
|
return parse_config_rebase("pull.rebase", value, 1);
|
|
|
|
*rebase_unspecified = 1;
|
|
|
|
return REBASE_FALSE;
|
|
}
|
|
|
|
/**
|
|
* Read config variables.
|
|
*/
|
|
static int git_pull_config(const char *var, const char *value,
|
|
const struct config_context *ctx, void *cb)
|
|
{
|
|
if (!strcmp(var, "rebase.autostash")) {
|
|
config_autostash = git_config_bool(var, value);
|
|
return 0;
|
|
} else if (!strcmp(var, "submodule.recurse")) {
|
|
recurse_submodules = git_config_bool(var, value) ?
|
|
RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
|
|
return 0;
|
|
} else if (!strcmp(var, "gpg.mintrustlevel")) {
|
|
check_trust_level = 0;
|
|
}
|
|
|
|
return git_default_config(var, value, ctx, cb);
|
|
}
|
|
|
|
/**
|
|
* Appends merge candidates from FETCH_HEAD that are not marked not-for-merge
|
|
* into merge_heads.
|
|
*/
|
|
static void get_merge_heads(struct oid_array *merge_heads)
|
|
{
|
|
const char *filename = git_path_fetch_head(the_repository);
|
|
FILE *fp;
|
|
struct strbuf sb = STRBUF_INIT;
|
|
struct object_id oid;
|
|
|
|
fp = xfopen(filename, "r");
|
|
while (strbuf_getline_lf(&sb, fp) != EOF) {
|
|