* [PATCH 4/6] pull: reimplement as a builtin in C
2015-05-06 0:00 ` [PATCH 0/6] Make pull a builtin Stephen Robin
` (2 preceding siblings ...)
2015-05-06 0:00 ` [PATCH 3/6] merge-base: split handle_fork_point to make reuse easier Stephen Robin
@ 2015-05-06 0:00 ` Stephen Robin
2015-05-06 0:00 ` [PATCH 5/6] pull: allow interactive rebase Stephen Robin
` (2 subsequent siblings)
6 siblings, 0 replies; 13+ messages in thread
From: Stephen Robin @ 2015-05-06 0:00 UTC (permalink / raw)
To: pyokagan; +Cc: git
THIS PATCH SERIES IS NOT CODE-COMPLETE OR FULLY TESTED.
See code comments beginning TODO for work remaining.
Known differences between builtin/pull.c and git-pull.sh:
- The output of git pull -h and git pull --help-all has changed. It is
now more comprehensive and consistent with other builtins.
- The error message given when dying due to unmerged changes in the
working tree has changed. In git-pull.sh it would list all unmerged
files. In builtin/pull.c it is consistent with git merge and will not
list file status.
- --s and --st can no longer be used as a shorthand for --strategy.
They would be ambiguous with --stat.
Signed-off-by: Stephen Robin <stephen.robin@gmail.com>
---
.gitignore | 1 -
Makefile | 2 +-
builtin.h | 1 +
builtin/pull.c | 993 +++++++++++++++++++++++++++++++++++++++++++
contrib/examples/git-pull.sh | 340 +++++++++++++++
git-pull.sh | 340 ---------------
git.c | 1 +
7 files changed, 1336 insertions(+), 342 deletions(-)
create mode 100644 builtin/pull.c
create mode 100755 contrib/examples/git-pull.sh
delete mode 100755 git-pull.sh
diff --git a/.gitignore b/.gitignore
index a052419..6287647 100644
--- a/.gitignore
+++ b/.gitignore
@@ -108,7 +108,6 @@
/git-patch-id
/git-prune
/git-prune-packed
-/git-pull
/git-push
/git-quiltimport
/git-read-tree
diff --git a/Makefile b/Makefile
index 5f3987f..8d8fb3a 100644
--- a/Makefile
+++ b/Makefile
@@ -472,7 +472,6 @@ SCRIPT_SH += git-merge-octopus.sh
SCRIPT_SH += git-merge-one-file.sh
SCRIPT_SH += git-merge-resolve.sh
SCRIPT_SH += git-mergetool.sh
-SCRIPT_SH += git-pull.sh
SCRIPT_SH += git-quiltimport.sh
SCRIPT_SH += git-rebase.sh
SCRIPT_SH += git-remote-testgit.sh
@@ -874,6 +873,7 @@ BUILTIN_OBJS += builtin/pack-refs.o
BUILTIN_OBJS += builtin/patch-id.o
BUILTIN_OBJS += builtin/prune-packed.o
BUILTIN_OBJS += builtin/prune.o
+BUILTIN_OBJS += builtin/pull.o
BUILTIN_OBJS += builtin/push.o
BUILTIN_OBJS += builtin/read-tree.o
BUILTIN_OBJS += builtin/receive-pack.o
diff --git a/builtin.h b/builtin.h
index d8e0e5a..b2d7daa 100644
--- a/builtin.h
+++ b/builtin.h
@@ -100,6 +100,7 @@ extern int cmd_pack_redundant(int argc, const char **argv, const char *prefix);
extern int cmd_patch_id(int argc, const char **argv, const char *prefix);
extern int cmd_prune(int argc, const char **argv, const char *prefix);
extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
+extern int cmd_pull(int argc, const char **argv, const char *prefix);
extern int cmd_push(int argc, const char **argv, const char *prefix);
extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);
diff --git a/builtin/pull.c b/builtin/pull.c
new file mode 100644
index 0000000..f420b4a
--- /dev/null
+++ b/builtin/pull.c
@@ -0,0 +1,993 @@
+/*
+ * Copyright (c) 2015 Stephen Robin <stephen.robin@gmail.com>
+ *
+ * Based on git-pull.sh by Junio C Hamano
+ */
+
+#include "builtin.h"
+#include "parse-options.h"
+#include "submodule.h"
+#include "dir.h"
+#include "run-command.h"
+#include "argv-array.h"
+#include "fmt-merge-msg.h"
+#include "refs.h"
+#include "revision.h"
+#include "remote.h"
+
+enum ff_type {
+ FF_NOT_SET = -1,
+ FF_NO,
+ FF_ALLOW,
+ FF_ONLY
+};
+
+enum pull_mode {
+ PULL_NOT_SET = -1,
+ PULL_MERGE,
+ PULL_REBASE,
+ PULL_PRESERVE_MERGES_REBASE
+};
+
+static const char * const builtin_pull_usage[] = {
+ N_("git pull [<options>] [<fetch-options>] [<repository> [<refspec>...]]"),
+ NULL
+};
+
+static enum pull_mode pull_mode_from_args = PULL_NOT_SET;
+static enum pull_mode pull_mode_from_branch_config = PULL_NOT_SET;
+static enum pull_mode pull_mode_from_config = PULL_NOT_SET;
+
+static enum ff_type ff_type_from_args = FF_NOT_SET;
+static enum ff_type ff_type_from_config = FF_NOT_SET;
+
+/*
+ * These are passed to the underlying fetch / merge / rebase commands but not
+ * used to make decisions within pull itself.
+ * 1 command line includes --option
+ * 0 command line includes --no-option
+ * -1 neither is on command line
+ */
+static int progress = -1;
+static int show_diffstat = -1;
+static int shortlog_len = -1;
+static int squash = -1;
+static int option_commit = -1;
+static int option_edit = -1;
+static int verify_signatures = -1;
+
+static int dryrun;
+static int verbosity;
+static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
+
+static const char **use_strategies;
+static size_t use_strategies_nr, use_strategies_alloc;
+
+static const char **xopts;
+static size_t xopts_nr, xopts_alloc;
+
+static const char *option_gpg_sign;
+
+static const char *curr_branch, *curr_branch_short;
+
+static int parse_pull_mode(const char *name, const char* arg,
+ enum pull_mode *option_rebase)
+{
+ int arg_as_bool = git_config_maybe_bool(name, arg);
+
+ if (arg_as_bool == 0) {
+ *option_rebase = PULL_MERGE;
+ return 0;
+ }
+
+ if (arg_as_bool == 1) {
+ *option_rebase = PULL_REBASE;
+ return 0;
+ }
+
+ if (!strcmp(arg, "preserve")) {
+ *option_rebase = PULL_PRESERVE_MERGES_REBASE;
+ return 0;
+ }
+
+ error(_("Invalid value for %s, should be 'true', 'false' or 'preserve'."), name);
+ return -1;
+}
+
+static int parse_ff_type(const char *name, const char* arg,
+ enum ff_type *fast_forward)
+{
+ int arg_as_bool = git_config_maybe_bool(name, arg);
+
+ if (arg_as_bool == 0) {
+ *fast_forward = FF_NO;
+ return 0;
+ }
+
+ if (arg_as_bool == 1) {
+ *fast_forward = FF_ALLOW;
+ return 0;
+ }
+
+ if (!strcmp(arg, "only")) {
+ *fast_forward = FF_ONLY;
+ return 0;
+ }
+
+ error(_("Invalid value for %s, should be 'true', 'false', or 'only'."), name);
+ return -1;
+}
+
+static int git_pull_config(const char *key, const char *value, void *cb)
+{
+ if (curr_branch_short &&
+ starts_with(key, "branch.") &&
+ starts_with(key + 7, curr_branch_short) &&
+ !strcmp(key + 7 + strlen(curr_branch_short), ".rebase"))
+ return parse_pull_mode(key, value, &pull_mode_from_branch_config);
+
+ if (!strcmp(key, "pull.rebase"))
+ return parse_pull_mode(key, value, &pull_mode_from_config);
+
+ if (!strcmp(key, "pull.ff"))
+ return parse_ff_type(key, value, &ff_type_from_config);
+
+ return fmt_merge_msg_config(key, value, cb);
+}
+
+static int option_parse_n(const struct option *opt,
+ const char *arg, int unset)
+{
+ show_diffstat = unset;
+ return 0;
+}
+
+static int option_parse_recurse_submodules(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset)
+ recurse_submodules = RECURSE_SUBMODULES_OFF;
+ else if (arg)
+ recurse_submodules = parse_fetch_recurse_submodules_arg(opt->long_name, arg);
+ else
+ recurse_submodules = RECURSE_SUBMODULES_ON;
+
+ return 0;
+}
+
+static int option_parse_rebase(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset) {
+ pull_mode_from_args = PULL_MERGE;
+ return 0;
+ }
+
+ if (!arg) {
+ pull_mode_from_args = PULL_REBASE;
+ return 0;
+ }
+
+ if (!parse_pull_mode(opt->long_name, arg, &pull_mode_from_args))
+ return 0;
+
+ return -1;
+}
+
+static int option_parse_strategy(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset)
+ return 0;
+
+ ALLOC_GROW(use_strategies, use_strategies_nr + 1, use_strategies_alloc);
+ use_strategies[use_strategies_nr++] = xstrdup(arg);
+ return 0;
+}
+
+static int option_parse_x(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset)
+ return 0;
+
+ ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
+ xopts[xopts_nr++] = xstrdup(arg);
+ return 0;
+}
+
+static struct option builtin_pull_options[] = {
+ { OPTION_CALLBACK, 0, "rebase", NULL, N_("true|false|preserve"),
+ N_("incorporate changes by rebasing rather than merging"),
+ PARSE_OPT_OPTARG, option_parse_rebase },
+ OPT_BOOL(0, "progress", &progress,
+ N_("force progress reporting")),
+ OPT__VERBOSITY(&verbosity),
+ { OPTION_CALLBACK, 0, "recurse-submodules", NULL, N_("on-demand"),
+ N_("control recursive fetching of submodules"),
+ PARSE_OPT_OPTARG, option_parse_recurse_submodules },
+ /* We can't use OPT__DRY_RUN as it uses 'n' as the short form, which we use as the short form of stat. */
+ OPT_BOOL(0, "dry-run", &dryrun, N_("dry run")),
+ { OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+ N_("do not show a diffstat at the end of the merge"),
+ PARSE_OPT_NOARG, option_parse_n },
+ OPT_BOOL(0, "stat", &show_diffstat,
+ N_("show a diffstat at the end of the merge")),
+ OPT_HIDDEN_BOOL(0, "summary", &show_diffstat, N_("(synonym to --stat)")),
+ { OPTION_INTEGER, 0, "log", &shortlog_len, N_("n"),
+ N_("add (at most <n>) entries from shortlog to merge commit message"),
+ PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN },
+ OPT_BOOL(0, "squash", &squash,
+ N_("create a single commit instead of doing a merge")),
+ OPT_BOOL(0, "commit", &option_commit,
+ N_("perform a commit if the merge succeeds (default)")),
+ OPT_BOOL('e', "edit", &option_edit,
+ N_("edit message before committing")),
+ OPT_SET_INT(0, "ff", &ff_type_from_args,
+ N_("allow fast-forward (default)"), FF_ALLOW),
+ { OPTION_SET_INT, 0, "ff-only", &ff_type_from_args, NULL,
+ N_("abort if fast-forward is not possible"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, FF_ONLY },
+ OPT_BOOL(0, "verify-signatures", &verify_signatures,
+ N_("verify that the named commit has a valid GPG signature")),
+ OPT_CALLBACK('s', "strategy", &use_strategies, N_("strategy"),
+ N_("merge strategy to use"), option_parse_strategy),
+ OPT_CALLBACK('X', "strategy-option", &xopts, N_("option=value"),
+ N_("option for selected merge strategy"), option_parse_x),
+ { OPTION_STRING, 'S', "gpg-sign", &option_gpg_sign, N_("key-id"),
+ N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+ OPT_END()
+};
+
+static enum pull_mode get_pull_mode()
+{
+ /* use --[no-]rebase[=preserve] from the command line, if specified. */
+ if (pull_mode_from_args != PULL_NOT_SET)
+ return pull_mode_from_args;
+
+ /* otherwise use branch.<name>.rebase from config, if set. */
+ if (pull_mode_from_branch_config != PULL_NOT_SET)
+ return pull_mode_from_branch_config;
+
+ /* otherwise use pull.rebase from config, if set. */
+ if (pull_mode_from_config != PULL_NOT_SET)
+ return pull_mode_from_config;
+
+ /* use merge by default. */
+ return PULL_MERGE;
+}
+
+static enum ff_type get_ff_type()
+{
+ if (ff_type_from_args != FF_NOT_SET)
+ return ff_type_from_args;
+
+ if (ff_type_from_config != FF_NOT_SET)
+ return ff_type_from_config;
+
+ return FF_ALLOW;
+}
+
+static char *get_merge_msg()
+{
+ const char *inpath = NULL;
+ FILE *in;
+ struct strbuf input = STRBUF_INIT, output = STRBUF_INIT;
+ struct fmt_merge_msg_opts opts;
+
+ inpath = git_path("FETCH_HEAD");
+ in = fopen(inpath, "r");
+ if (!in)
+ die_errno("cannot open '%s'", inpath);
+
+ if (strbuf_read(&input, fileno(in), 0) < 0)
+ die_errno("could not read '%s'", inpath);
+
+ fclose(in);
+
+ memset(&opts, 0, sizeof(opts));
+ opts.add_title = 1;
+ opts.credit_people = 1;
+ opts.shortlog_len = shortlog_len;
+
+ fmt_merge_msg(&input, &output, &opts);
+
+ strbuf_release(&input);
+
+ return strbuf_detach(&output, NULL);
+}
+
+static const struct string_list get_merge_head()
+{
+ /*
+ * Read FETCH_HEAD line by line
+ * ... exclude lines containing \tnot-for-merge\t
+ * ... exclude everything after the first tab in remaining lines
+ * ... result is a list of sha1s to be merged.
+ */
+
+ const char *filename;
+ FILE *fp;
+ struct strbuf line = STRBUF_INIT;
+ struct string_list merge_head = STRING_LIST_INIT_DUP;
+ char *ptr;
+
+ filename = git_path("FETCH_HEAD");
+ fp = fopen(filename, "r");
+ if (!fp)
+ die_errno(_("could not open '%s' for reading"), filename);
+
+ while (strbuf_getline(&line, fp, '\n') != EOF) {
+
+ ptr = strstr(line.buf, "\tnot-for-merge\t");
+ if (!ptr) {
+ ptr = strchr(line.buf, '\t');
+ if (ptr) {
+ strbuf_setlen(&line, ptr - line.buf);
+ string_list_append(&merge_head, line.buf);
+ }
+ }
+
+ strbuf_reset(&line);
+ }
+
+ fclose(fp);
+ strbuf_release(&line);
+
+ return merge_head;
+}
+
+static int run_fetch(const int additional_argc, const char **additional_argv)
+{
+ int v, q, idx, result;
+ struct argv_array argv = ARGV_ARRAY_INIT;
+
+ argv_array_push(&argv, "fetch");
+
+ for (v = verbosity; v > 0; v--)
+ argv_array_push(&argv, "-v");
+
+ for (q = verbosity; q < 0; q++)
+ argv_array_push(&argv, "-q");
+
+ if (progress == 1)
+ argv_array_push(&argv, "--progress");
+ else if (progress == 0)
+ argv_array_push(&argv, "--no-progress");
+
+ if (dryrun)
+ argv_array_push(&argv, "--dry-run");
+
+ if (recurse_submodules == RECURSE_SUBMODULES_ON)
+ argv_array_push(&argv, "--recurse-submodules");
+ else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)
+ argv_array_push(&argv, "--recurse-submodules=on-demand");
+ else if (recurse_submodules == RECURSE_SUBMODULES_OFF)
+ argv_array_push(&argv, "--no-recurse-submodules");
+
+ argv_array_push(&argv, "--update-head-ok");
+
+ for (idx = 0; idx < additional_argc; idx++)
+ argv_array_push(&argv, additional_argv[idx]);
+
+ result = run_command_v_opt(argv.argv, RUN_GIT_CMD);
+
+ argv_array_clear(&argv);
+
+ return result;
+}
+
+static int run_merge(const struct string_list merge_head)
+{
+ int v, q, idx, result;
+ struct argv_array argv = ARGV_ARRAY_INIT;
+ const char *merge_msg;
+ enum ff_type fast_forward_type;
+
+ merge_msg = get_merge_msg();
+ fast_forward_type = get_ff_type();
+
+
+ argv_array_push(&argv, "merge");
+
+ if (show_diffstat == 1)
+ argv_array_push(&argv, "--stat");
+ else if (show_diffstat == 0)
+ argv_array_push(&argv, "--no-stat");
+
+ if (option_commit == 1)
+ argv_array_push(&argv, "--commit");
+ else if (option_commit == 0)
+ argv_array_push(&argv, "--no-commit");
+
+ if (verify_signatures == 1)
+ argv_array_push(&argv, "--verify-signatures");
+ else if (verify_signatures == 0)
+ argv_array_push(&argv, "--no-verify-signatures");
+
+ if (option_edit == 1)
+ argv_array_push(&argv, "--edit");
+ else if (option_edit == 0)
+ argv_array_push(&argv, "--no-edit");
+
+ if (squash == 1)
+ argv_array_push(&argv, "--squash");
+ else if (squash == 0)
+ argv_array_push(&argv, "--no-squash");
+
+ if (fast_forward_type == FF_ALLOW)
+ argv_array_push(&argv, "--ff");
+ else if (fast_forward_type == FF_NO)
+ argv_array_push(&argv, "--no-ff");
+ else if (fast_forward_type == FF_ONLY)
+ argv_array_push(&argv, "--ff-only");
+
+ if (shortlog_len >= 0)
+ argv_array_pushf(&argv, "--log=%d", shortlog_len);
+
+ for (idx = 0; idx < use_strategies_nr; idx++)
+ argv_array_pushf(&argv, "--strategy=%s", use_strategies[idx]);
+
+ for (idx = 0; idx < xopts_nr; idx++)
+ argv_array_pushf(&argv, "-X%s", xopts[idx]);
+
+ for (v = verbosity; v > 0; v--)
+ argv_array_push(&argv, "-v");
+
+ for (q = verbosity; q < 0; q++)
+ argv_array_push(&argv, "-q");
+
+ if (progress == 1)
+ argv_array_push(&argv, "--progress");
+ else if (progress == 0)
+ argv_array_push(&argv, "--no-progress");
+
+ if (option_gpg_sign)
+ argv_array_pushf(&argv, "-S%s", option_gpg_sign);
+
+ argv_array_pushf(&argv, "\"%s\"", merge_msg);
+
+ argv_array_push(&argv, "HEAD");
+
+ for (idx = 0; idx < merge_head.nr; idx++)
+ argv_array_push(&argv, merge_head.items[idx].string);
+
+ result = run_command_v_opt(argv.argv, RUN_GIT_CMD);
+
+ argv_array_clear(&argv);
+
+ return result;
+}
+
+static const char *run_show_branch(const char *merge_head,
+ const char *fork_point_for_rebase)
+{
+ /*
+ * TODO Should be able to do the same thing without needing to fork another
+ * git instance. It's just a simple search of the graph after all.
+ */
+
+ int len;
+ struct child_process cp;
+ struct strbuf buffer = STRBUF_INIT;
+ struct argv_array argv = ARGV_ARRAY_INIT;
+
+ argv_array_push(&argv, "show-branch");
+ argv_array_push(&argv, "--merge-base");
+
+ argv_array_push(&argv, curr_branch);
+ argv_array_push(&argv, merge_head);
+ argv_array_push(&argv, fork_point_for_rebase);
+
+ memset(&cp, 0, sizeof(cp));
+ cp.argv = argv.argv;
+ cp.out = -1;
+ cp.git_cmd = 1;
+
+ if (start_command(&cp))
+ die(_("could not run git show-branch."));
+ len = strbuf_read(&buffer, cp.out, 1024);
+ close(cp.out);
+
+ if (finish_command(&cp) || len < 0)
+ die(_("show-branch failed"));
+ else if (!len)
+ return NULL;
+
+ return strbuf_detach(&buffer, NULL);
+}
+
+static int run_rebase(const struct string_list merge_head, const char *fork_point,
+ const enum pull_mode pull_mode)
+{
+ int v, q, idx, result;
+ struct argv_array argv = ARGV_ARRAY_INIT;
+
+ if (merge_head.nr > 1)
+ die(_("Cannot rebase onto multiple branches"));
+
+ if (fork_point) {
+ const char *show_branch_result = run_show_branch(merge_head.items[0].string, fork_point);
+ if (!strcmp(fork_point, show_branch_result))
+ fork_point = NULL;
+ }
+
+ argv_array_push(&argv, "rebase");
+
+ if (show_diffstat == 1)
+ argv_array_push(&argv, "--stat");
+ else if (show_diffstat == 0)
+ argv_array_push(&argv, "--no-stat");
+
+ for (idx = 0; idx < use_strategies_nr; idx++)
+ argv_array_pushf(&argv, "--strategy=%s", use_strategies[idx]);
+
+ for (idx = 0; idx < xopts_nr; idx++)
+ argv_array_pushf(&argv, "-X%s", xopts[idx]);
+
+ if (pull_mode == PULL_PRESERVE_MERGES_REBASE)
+ argv_array_push(&argv, "--preserve-merges");
+
+ for (v = verbosity; v > 0; v--)
+ argv_array_push(&argv, "-v");
+
+ for (q = verbosity; q < 0; q++)
+ argv_array_push(&argv, "-q");
+
+ if (option_gpg_sign)
+ argv_array_pushf(&argv, "-S%s", option_gpg_sign);
+
+ argv_array_push(&argv, "--onto");
+ argv_array_push(&argv, merge_head.items[0].string);
+
+ if (fork_point)
+ argv_array_push(&argv, fork_point);
+ else
+ argv_array_push(&argv, merge_head.items[0].string);
+
+ result = run_command_v_opt(argv.argv, RUN_GIT_CMD);
+
+ argv_array_clear(&argv);
+
+ return result;
+}
+
+static int get_remote_name(struct remote *remote, void *priv)
+{
+ struct string_list *list = priv;
+ string_list_append(list, remote->name);
+ return 0;
+}
+
+static void error_on_no_merge_candidates(enum pull_mode mode,
+ int argc, const char **argv)
+{
+ const char *op_type, *op_prep;
+ int idx;
+ struct branch *branch;
+
+ /*
+ * TODO Existing bug in git-pull.sh, add another patch to the series to fix:
+ * This function fails to take into account any arguments to be passed to
+ * git fetch other than the remote and the refs.
+ *
+ * Set up a pair of test repos as follows:
+ * mkdir repo
+ * cd repo
+ * git init
+ * echo test > test
+ * git add test
+ * git commit -m "a commit"
+ * cd ..
+ * git clone repo cloned
+ * cd cloned
+ * git remote add nondefaultremote "../repo"
+ *
+ * Now compare the output of:
+ * git pull nondefaultremote
+ * git pull -p nondefaultremote
+ *
+ * The messages should be identical but aren't, the second is incorrect.
+ */
+
+ for (idx = 0; idx < argc; idx++) {
+ if (!strcmp("-t", argv[idx]) || starts_with(argv[idx], "--t"))
+ die(_("It doesn't make sense to pull all tags; you probably meant:\n"
+ "git fetch --tags"));
+ }
+
+ branch = branch_get(curr_branch_short);
+
+ if (mode == PULL_MERGE) {
+ op_type = "merge";
+ /*
+ * TRANSLATORS: This is the preposition associated with the merge
+ * operation. In English it is used as "specify the branch you want to
+ * merge _with_"
+ */
+ op_prep = _("with");
+ } else {
+ op_type = "rebase";
+ /*
+ * TRANSLATORS: This is the preposition associated with the rebase
+ * operation. In English it is used as "specify the branch you want to
+ * rebase _against_"
+ */
+ op_prep = _("against");
+ }
+
+ if (argc > 1) {
+ if (mode == PULL_MERGE)
+ die(_("There are no candidates for merging\n"
+ "among the refs that you just fetched.\n"
+ "Generally this means that you provided a wildcard refspec which had no\n"
+ "matches on the remote end."));
+ else
+ die(_("There is no candidate for rebasing against\n"
+ "among the refs that you just fetched.\n"
+ "Generally this means that you provided a wildcard refspec which had no\n"
+ "matches on the remote end.\n"));
+ } else if (argc > 0 && branch && branch->remote_name &&
+ strcmp(argv[0], branch->remote_name)) {
+ die(_("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."), argv[0]);
+ } else if (!curr_branch_short) {
+ /*
+ * TRANSLATORS: first parameter is the operation (merge or rebase),
+ * second is the preposition (with or against in English).
+ */
+ die(_("You are not currently on a branch. Please specify which\n"
+ "branch you want to %s %s. See git help pull for details.\n\n"
+ " git pull <remote> <branch>"), op_type, op_prep);
+ } else if (!branch || !branch->merge || !branch->merge[0] ||
+ !branch->merge[0]->dst || !branch->remote_name) {
+ /* If there's only one remote, use that in the suggestion */
+ struct string_list list = STRING_LIST_INIT_NODUP;
+ char *remote_name;
+
+ for_each_remote(get_remote_name, &list);
+
+ if (list.nr == 1)
+ remote_name = list.items[0].string;
+ else
+ remote_name = "<remote>";
+ /*
+ * TRANSLATORS: first parameter is the operation (merge or rebase),
+ * second is the preposition (with or against in English).
+ */
+ die(_("There is no tracking information for the current branch.\n"
+ "Please specify which branch you want to %s %s.\n"
+ "See git help pull for details\n\n"
+ " git pull <remote> <branch>\n\n"
+ "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"),
+ op_type, op_prep, remote_name, curr_branch_short);
+ } else {
+ const char *upstream_short = strncmp(branch->merge[0]->dst, "refs/heads/", 11)
+ ? branch->merge[0]->dst : branch->merge[0]->dst + 11;
+ /*
+ * TRANSLATORS: first parameter is the operation (merge or rebase),
+ * second is the preposition (with or against in English).
+ */
+ die(_("Your configuration specifies to %s %s the ref '%s'\n"
+ "from the remote, but no such ref was fetched."),
+ op_type, op_prep, upstream_short);
+ }
+}
+
+static const char *get_remote_merge_branch(int argc, const char **argv)
+{
+ /*
+ * TODO Existing bug in git-pull.sh, add another patch to the series to fix:
+ * This function fails to take into account any arguments to be passed to git
+ * fetch other than the remote and the refs.
+ *
+ * See also error_on_no_merge_candidates, it has the same problem.
+ */
+
+ /*
+ * TODO Existing bug in git-pull.sh, add another patch to the series to fix:
+ * This function doesn't always take into account mapping of remote to local
+ * branch names.
+ */
+
+ if (argc <= 1) {
+ struct remote *my_remote;
+ struct branch *branch;
+
+ my_remote = (argc == 1) ? remote_get(argv[0]) : remote_get(NULL);
+ if (!my_remote || !my_remote->name)
+ return NULL;
+
+ branch = branch_get(curr_branch_short);
+ if (!branch || !branch->merge || !branch->merge[0] ||
+ !branch->merge[0]->dst || !branch->remote_name)
+ return NULL;
+
+ if (strcmp(branch->remote_name, my_remote->name))
+ return NULL;
+
+ return branch->merge[0]->dst;
+
+ } else {
+ /*
+ * TODO Code here is ugly but should do the same thing as
+ * git-parse-remote.sh. I haven't fully tested it as I want to rewrite
+ * the whole function in a subsequent patch anyway.
+ */
+
+ static const char **refs_to_parse = NULL;
+ int refs_to_parse_nr = argc - 1;
+ int i;
+ struct refspec *parsed_refspec;
+ char *remote_ref;
+
+ refs_to_parse = xcalloc(argc, sizeof(const char *));
+ for (i = 0; i < refs_to_parse_nr; i++)
+ refs_to_parse[i] = argv[i + 1];
+ refs_to_parse[i] = NULL;
+
+ parsed_refspec = parse_fetch_refspec(refs_to_parse_nr, refs_to_parse);
+
+ remote_ref = parsed_refspec->src;
+
+ if (!remote_ref)
+ remote_ref = "HEAD";
+ else if (starts_with(remote_ref, "heads/"))
+ remote_ref += strlen("heads/");
+ else if (starts_with(remote_ref, "refs/heads/"))
+ remote_ref += strlen("refs/heads/");
+ else if (starts_with(remote_ref, "refs/"))
+ remote_ref = NULL;
+ else if (starts_with(remote_ref, "tags/"))
+ remote_ref = NULL;
+ else if (starts_with(remote_ref, "remotes/"))
+ remote_ref = NULL;
+
+ if (!remote_ref)
+ return NULL;
+
+ if (!strcmp(argv[0], ".")) {
+ char *full_ref = xmalloc(strlen(remote_ref) + 12);
+ strcpy(full_ref, "refs/heads/");
+ strcat(full_ref, remote_ref);
+ return full_ref;
+ } else {
+ char *full_ref = xmalloc(strlen(remote_ref) +strlen(argv[0]) + 15);
+ strcpy(full_ref, "refs/remotes/");
+ strcat(full_ref, argv[0]);
+ strcat(full_ref, "/");
+ strcat(full_ref, remote_ref);
+ return full_ref;
+ }
+
+ }
+}
+
+static char *find_fork_point_for_rebase(int argc, const char** argv, unsigned char sha1_orig_head[20])
+{
+ const char *remote_ref;
+ const unsigned char *fork_point_sha1;
+
+ remote_ref = get_remote_merge_branch(argc, argv);
+ if (!remote_ref)
+ return NULL;
+
+ fork_point_sha1 = get_fork_point(remote_ref, sha1_orig_head);
+ if (is_null_sha1(fork_point_sha1))
+ return NULL;
+
+ return sha1_to_hex(fork_point_sha1);
+}
+
+static int fast_forward_unborn_branch(const struct string_list merge_head)
+{
+ /*
+ * Pulling into an unborn branch.
+ * We claim the index is based on an empty tree and try to fast-forward
+ * to merge-head. This ensures we will not lose index / worktree
+ * changes that the user already made on the unborn branch.
+ */
+ unsigned char sha1[20];
+
+ if (merge_head.nr > 1)
+ die(_("Cannot merge multiple branches into empty head"));
+
+ if (get_sha1_hex(merge_head.items[0].string, sha1))
+ die(_("Unable to find '%s'. FETCH_HEAD may be corrupt"), merge_head.items[0].string);
+
+ if (checkout_fast_forward(EMPTY_TREE_SHA1_BIN, sha1, 0))
+ return 1;
+
+ if (update_ref("initial pull", "HEAD", sha1, NULL, 0, UPDATE_REFS_DIE_ON_ERR))
+ return 1;
+
+ return 0;
+}
+
+static int check_for_unstaged_changes()
+{
+ struct rev_info rev;
+ int result;
+
+ /* Check for changes in the working tree */
+ init_revisions(&rev, NULL);
+ setup_revisions(0, NULL, &rev, NULL);
+ DIFF_OPT_SET(&rev.diffopt, IGNORE_SUBMODULES);
+ DIFF_OPT_SET(&rev.diffopt, QUICK);
+ DIFF_OPT_SET(&rev.diffopt, HAS_CHANGES);
+ DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
+ result = run_diff_files(&rev, 0);
+
+ return diff_result_code(&rev.diffopt, result);
+}
+
+static int check_for_uncommitted_changes()
+{
+ struct rev_info rev;
+ struct setup_revision_opt opt;
+ int result;
+
+ /* Check for changes in the index */
+ init_revisions(&rev, NULL);
+ memset(&opt, 0, sizeof(opt));
+ opt.def = "HEAD";
+ setup_revisions(0, NULL, &rev, &opt);
+ DIFF_OPT_SET(&rev.diffopt, IGNORE_SUBMODULES);
+ DIFF_OPT_SET(&rev.diffopt, QUICK);
+ DIFF_OPT_SET(&rev.diffopt, HAS_CHANGES);
+ DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
+ result = run_diff_index(&rev, 1);
+
+ return diff_result_code(&rev.diffopt, result);
+}
+
+static void check_state_before_starting(const enum pull_mode mode, const int unborn)
+{
+ /*
+ * TODO This function is as close to git-pull.sh as possible. We should be
+ * able to tidy it up and improve it now it's in c (e.g. fail early if
+ * cherry-pick in progress), perhaps using wt_status stuff.
+ */
+
+ if (read_cache_unmerged())
+ die_resolve_conflict("pull");
+
+ if (file_exists(git_path("MERGE_HEAD")))
+ die_merge_in_progress();
+
+ if (mode != PULL_MERGE) {
+
+ /*
+ * TODO Existing bug in git-pull.sh, add another patch to the series to fix:
+ * We'll die unnecessarily on the next line if files are added to the
+ * index then removed again, leaving the index empty, e.g.
+ *
+ * mkdir temp
+ * cd temp
+ * git init
+ * echo test > test
+ * git add test
+ * git reset test
+ * rm test
+ * git pull --rebase ../another-repo
+ * => "updating an unborn branch with changes added to the index"
+ */
+ if (unborn && file_exists(get_index_file()))
+ die(_("Updating an unborn branch with changes added to the index"));
+
+ if (!unborn) {
+ int unstaged_changes = 0;
+ int uncommited_changes = 0;
+
+ if (read_cache_preload(NULL) < 0)
+ die(_("Corrupt index file"));
+
+ refresh_cache(REFRESH_QUIET | REFRESH_IGNORE_SUBMODULES);
+
+ unstaged_changes = check_for_unstaged_changes();
+ uncommited_changes = check_for_uncommitted_changes();
+
+ if (unstaged_changes && uncommited_changes)
+ die(_("Cannot pull with rebase: You have unstaged changes and your index contains uncommitted changes./n"
+ "Please commit or stash them."));
+
+ if (unstaged_changes)
+ die(_("Cannot pull with rebase: You have unstaged changes./n"
+ "Please commit or stash them."));
+
+ if (uncommited_changes)
+ die(_("Cannot pull with rebase: Your index contains uncommitted changes./n"
+ "Please commit or stash them."));
+ }
+ }
+}
+
+static void set_reflog_message(int argc, const char **argv)
+{
+ int idx;
+ struct strbuf reflog_message = STRBUF_INIT;
+
+ for (idx = 0; idx < argc; idx++) {
+ strbuf_addstr(&reflog_message, argv[idx]);
+ strbuf_addch(&reflog_message, ' ');
+ }
+
+ strbuf_trim(&reflog_message);
+
+ setenv("GIT_REFLOG_ACTION", reflog_message.buf, 0);
+
+ strbuf_release(&reflog_message);
+}
+
+int cmd_pull(int argc, const char **argv, const char *prefix)
+{
+ unsigned char sha1_orig_head[20], sha1_curr_head[20];
+ enum pull_mode mode = PULL_NOT_SET;
+ char *fork_point_for_rebase = NULL;
+
+ set_reflog_message(argc, argv);
+
+ curr_branch = resolve_refdup("HEAD", 0, sha1_orig_head, NULL);
+ if (curr_branch && starts_with(curr_branch, "refs/heads/"))
+ curr_branch_short = curr_branch + 11;
+
+ git_config(git_pull_config, NULL);
+
+ argc = parse_options(argc, argv, prefix, builtin_pull_options,
+ builtin_pull_usage, PARSE_OPT_KEEP_UNKNOWN);
+
+ mode = get_pull_mode();
+
+ if (shortlog_len < 0)
+ shortlog_len = (merge_log_config > 0) ? merge_log_config : 0;
+
+ check_state_before_starting(mode, is_null_sha1(sha1_orig_head));
+
+ if (mode != PULL_MERGE && !is_null_sha1(sha1_orig_head))
+ fork_point_for_rebase = find_fork_point_for_rebase(argc, argv, sha1_orig_head);
+
+ if (run_fetch(argc, argv))
+ return 1;
+
+ if (dryrun)
+ return 0;
+
+ get_sha1("HEAD", sha1_curr_head);
+
+ if (!is_null_sha1(sha1_orig_head) &&
+ hashcmp(sha1_orig_head, sha1_curr_head)) {
+ /*
+ * The fetch involved updating the current branch.
+ * The working tree and the index file are still based on the
+ * orig_head commit, but we are merging into curr_head.
+ * First update the working tree to match curr_head.
+ */
+
+ /* TRANSLATORS: %s is a SHA1 identifying a commit. */
+ warning(_("fetch updated the current branch head.\n"
+ "fast-forwarding your working tree from\n"
+ "commit %s."), sha1_to_hex(sha1_orig_head));
+
+ if (checkout_fast_forward(sha1_orig_head, sha1_curr_head, 0))
+ die(_("Cannot fast-forward your working tree.\n"
+ "After making sure that you saved anything precious from\n\n"
+ " git diff %s\n\n"
+ "output, run\n\n"
+ " git reset --hard\n\n"
+ "to recover."), sha1_to_hex(sha1_orig_head));
+ }
+
+ const struct string_list merge_head = get_merge_head();
+ if (merge_head.nr == 0)
+ error_on_no_merge_candidates(mode, argc, argv);
+
+ if (is_null_sha1(sha1_orig_head))
+ return fast_forward_unborn_branch(merge_head);
+
+ if (mode == PULL_MERGE)
+ return run_merge(merge_head);
+
+ return run_rebase(merge_head, fork_point_for_rebase, mode);
+}
diff --git a/contrib/examples/git-pull.sh b/contrib/examples/git-pull.sh
new file mode 100755
index 0000000..4d4fc77
--- /dev/null
+++ b/contrib/examples/git-pull.sh
@@ -0,0 +1,340 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+# Fetch one or more remote refs and merge it/them into the current HEAD.
+
+USAGE='[-n | --no-stat] [--[no-]commit] [--[no-]squash] [--[no-]ff|--ff-only] [--[no-]rebase|--rebase=preserve] [-s strategy]... [<fetch-options>] <repo> <head>...'
+LONG_USAGE='Fetch one or more remote refs and integrate it/them with the current HEAD.'
+SUBDIRECTORY_OK=Yes
+OPTIONS_SPEC=
+. git-sh-setup
+. git-sh-i18n
+set_reflog_action "pull${1+ $*}"
+require_work_tree_exists
+cd_to_toplevel
+
+
+die_conflict () {
+ git diff-index --cached --name-status -r --ignore-submodules HEAD --
+ if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
+ die "$(gettext "Pull is not possible because you have unmerged files.
+Please, fix them up in the work tree, and then use 'git add/rm <file>'
+as appropriate to mark resolution and make a commit.")"
+ else
+ die "$(gettext "Pull is not possible because you have unmerged files.")"
+ fi
+}
+
+die_merge () {
+ if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
+ die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).
+Please, commit your changes before you can merge.")"
+ else
+ die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).")"
+ fi
+}
+
+test -z "$(git ls-files -u)" || die_conflict
+test -f "$GIT_DIR/MERGE_HEAD" && die_merge
+
+bool_or_string_config () {
+ git config --bool "$1" 2>/dev/null || git config "$1"
+}
+
+strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
+log_arg= verbosity= progress= recurse_submodules= verify_signatures=
+merge_args= edit= rebase_args=
+curr_branch=$(git symbolic-ref -q HEAD)
+curr_branch_short="${curr_branch#refs/heads/}"
+rebase=$(bool_or_string_config branch.$curr_branch_short.rebase)
+if test -z "$rebase"
+then
+ rebase=$(bool_or_string_config pull.rebase)
+fi
+
+# Setup default fast-forward options via `pull.ff`
+pull_ff=$(git config pull.ff)
+case "$pull_ff" in
+false)
+ no_ff=--no-ff
+ ;;
+only)
+ ff_only=--ff-only
+ ;;
+esac
+
+
+dry_run=
+while :
+do
+ case "$1" in
+ -q|--quiet)
+ verbosity="$verbosity -q" ;;
+ -v|--verbose)
+ verbosity="$verbosity -v" ;;
+ --progress)
+ progress=--progress ;;
+ --no-progress)
+ progress=--no-progress ;;
+ -n|--no-stat|--no-summary)
+ diffstat=--no-stat ;;
+ --stat|--summary)
+ diffstat=--stat ;;
+ --log|--no-log)
+ log_arg=$1 ;;
+ --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
+ no_commit=--no-commit ;;
+ --c|--co|--com|--comm|--commi|--commit)
+ no_commit=--commit ;;
+ -e|--edit)
+ edit=--edit ;;
+ --no-edit)
+ edit=--no-edit ;;
+ --sq|--squ|--squa|--squas|--squash)
+ squash=--squash ;;
+ --no-sq|--no-squ|--no-squa|--no-squas|--no-squash)
+ squash=--no-squash ;;
+ --ff)
+ no_ff=--ff ;;
+ --no-ff)
+ no_ff=--no-ff ;;
+ --ff-only)
+ ff_only=--ff-only ;;
+ -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
+ --strateg=*|--strategy=*|\
+ -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
+ case "$#,$1" in
+ *,*=*)
+ strategy=$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
+ 1,*)
+ usage ;;
+ *)
+ strategy="$2"
+ shift ;;
+ esac
+ strategy_args="${strategy_args}-s $strategy "
+ ;;
+ -X*)
+ case "$#,$1" in
+ 1,-X)
+ usage ;;
+ *,-X)
+ xx="-X $(git rev-parse --sq-quote "$2")"
+ shift ;;
+ *,*)
+ xx=$(git rev-parse --sq-quote "$1") ;;
+ esac
+ merge_args="$merge_args$xx "
+ ;;
+ -r=*|--r=*|--re=*|--reb=*|--reba=*|--rebas=*|--rebase=*)
+ rebase="${1#*=}"
+ ;;
+ -r|--r|--re|--reb|--reba|--rebas|--rebase)
+ rebase=true
+ ;;
+ --no-r|--no-re|--no-reb|--no-reba|--no-rebas|--no-rebase)
+ rebase=false
+ ;;
+ --recurse-submodules)
+ recurse_submodules=--recurse-submodules
+ ;;
+ --recurse-submodules=*)
+ recurse_submodules="$1"
+ ;;
+ --no-recurse-submodules)
+ recurse_submodules=--no-recurse-submodules
+ ;;
+ --verify-signatures)
+ verify_signatures=--verify-signatures
+ ;;
+ --no-verify-signatures)
+ verify_signatures=--no-verify-signatures
+ ;;
+ --gpg-sign|-S)
+ gpg_sign_args=-S
+ ;;
+ --gpg-sign=*)
+ gpg_sign_args=$(git rev-parse --sq-quote "-S${1#--gpg-sign=}")
+ ;;
+ -S*)
+ gpg_sign_args=$(git rev-parse --sq-quote "$1")
+ ;;
+ --d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run)
+ dry_run=--dry-run
+ ;;
+ -h|--help-all)
+ usage
+ ;;
+ *)
+ # Pass thru anything that may be meant for fetch.
+ break
+ ;;
+ esac
+ shift
+done
+
+case "$rebase" in
+preserve)
+ rebase=true
+ rebase_args=--preserve-merges
+ ;;
+true|false|'')
+ ;;
+*)
+ echo "Invalid value for --rebase, should be true, false, or preserve"
+ usage
+ exit 1
+ ;;
+esac
+
+error_on_no_merge_candidates () {
+ exec >&2
+ for opt
+ do
+ case "$opt" in
+ -t|--t|--ta|--tag|--tags)
+ echo "It doesn't make sense to pull all tags; you probably meant:"
+ echo " git fetch --tags"
+ exit 1
+ esac
+ done
+
+ if test true = "$rebase"
+ then
+ op_type=rebase
+ op_prep=against
+ else
+ op_type=merge
+ op_prep=with
+ fi
+
+ upstream=$(git config "branch.$curr_branch_short.merge")
+ remote=$(git config "branch.$curr_branch_short.remote")
+
+ if [ $# -gt 1 ]; then
+ if [ "$rebase" = true ]; then
+ printf "There is no candidate for rebasing against "
+ else
+ printf "There are no candidates for merging "
+ fi
+ echo "among the refs that you just fetched."
+ echo "Generally this means that you provided a wildcard refspec which had no"
+ echo "matches on the remote end."
+ elif [ $# -gt 0 ] && [ "$1" != "$remote" ]; then
+ echo "You asked to pull from the remote '$1', but did not specify"
+ echo "a branch. Because this is not the default configured remote"
+ echo "for your current branch, you must specify a branch on the command line."
+ elif [ -z "$curr_branch" -o -z "$upstream" ]; then
+ . git-parse-remote
+ error_on_missing_default_upstream "pull" $op_type $op_prep \
+ "git pull <remote> <branch>"
+ else
+ echo "Your configuration specifies to $op_type $op_prep the ref '${upstream#refs/heads/}'"
+ echo "from the remote, but no such ref was fetched."
+ fi
+ exit 1
+}
+
+test true = "$rebase" && {
+ if ! git rev-parse -q --verify HEAD >/dev/null
+ then
+ # On an unborn branch
+ if test -f "$GIT_DIR/index"
+ then
+ die "$(gettext "updating an unborn branch with changes added to the index")"
+ fi
+ else
+ require_clean_work_tree "pull with rebase" "Please commit or stash them."
+ fi
+ oldremoteref= &&
+ test -n "$curr_branch" &&
+ . git-parse-remote &&
+ remoteref="$(get_remote_merge_branch "$@" 2>/dev/null)" &&
+ oldremoteref=$(git merge-base --fork-point "$remoteref" $curr_branch 2>/dev/null)
+}
+orig_head=$(git rev-parse -q --verify HEAD)
+git fetch $verbosity $progress $dry_run $recurse_submodules --update-head-ok "$@" || exit 1
+test -z "$dry_run" || exit 0
+
+curr_head=$(git rev-parse -q --verify HEAD)
+if test -n "$orig_head" && test "$curr_head" != "$orig_head"
+then
+ # The fetch involved updating the current branch.
+
+ # The working tree and the index file is still based on the
+ # $orig_head commit, but we are merging into $curr_head.
+ # First update the working tree to match $curr_head.
+
+ eval_gettextln "Warning: fetch updated the current branch head.
+Warning: fast-forwarding your working tree from
+Warning: commit \$orig_head." >&2
+ git update-index -q --refresh
+ git read-tree -u -m "$orig_head" "$curr_head" ||
+ die "$(eval_gettext "Cannot fast-forward your working tree.
+After making sure that you saved anything precious from
+$ git diff \$orig_head
+output, run
+$ git reset --hard
+to recover.")"
+
+fi
+
+merge_head=$(sed -e '/ not-for-merge /d' \
+ -e 's/ .*//' "$GIT_DIR"/FETCH_HEAD | \
+ tr '\012' ' ')
+
+case "$merge_head" in
+'')
+ error_on_no_merge_candidates "$@"
+ ;;
+?*' '?*)
+ if test -z "$orig_head"
+ then
+ die "$(gettext "Cannot merge multiple branches into empty head")"
+ fi
+ if test true = "$rebase"
+ then
+ die "$(gettext "Cannot rebase onto multiple branches")"
+ fi
+ ;;
+esac
+
+# Pulling into unborn branch: a shorthand for branching off
+# FETCH_HEAD, for lazy typers.
+if test -z "$orig_head"
+then
+ # Two-way merge: we claim the index is based on an empty tree,
+ # and try to fast-forward to HEAD. This ensures we will not
+ # lose index/worktree changes that the user already made on
+ # the unborn branch.
+ empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+ git read-tree -m -u $empty_tree $merge_head &&
+ git update-ref -m "initial pull" HEAD $merge_head "$curr_head"
+ exit
+fi
+
+if test true = "$rebase"
+then
+ o=$(git show-branch --merge-base $curr_branch $merge_head $oldremoteref)
+ if test "$oldremoteref" = "$o"
+ then
+ unset oldremoteref
+ fi
+fi
+
+merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
+case "$rebase" in
+true)
+ eval="git-rebase $diffstat $strategy_args $merge_args $rebase_args $verbosity"
+ eval="$eval $gpg_sign_args"
+ eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
+ ;;
+*)
+ eval="git-merge $diffstat $no_commit $verify_signatures $edit $squash $no_ff $ff_only"
+ eval="$eval $log_arg $strategy_args $merge_args $verbosity $progress"
+ eval="$eval $gpg_sign_args"
+ eval="$eval \"\$merge_name\" HEAD $merge_head"
+ ;;
+esac
+eval "exec $eval"
diff --git a/git-pull.sh b/git-pull.sh
deleted file mode 100755
index 4d4fc77..0000000
--- a/git-pull.sh
+++ /dev/null
@@ -1,340 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Junio C Hamano
-#
-# Fetch one or more remote refs and merge it/them into the current HEAD.
-
-USAGE='[-n | --no-stat] [--[no-]commit] [--[no-]squash] [--[no-]ff|--ff-only] [--[no-]rebase|--rebase=preserve] [-s strategy]... [<fetch-options>] <repo> <head>...'
-LONG_USAGE='Fetch one or more remote refs and integrate it/them with the current HEAD.'
-SUBDIRECTORY_OK=Yes
-OPTIONS_SPEC=
-. git-sh-setup
-. git-sh-i18n
-set_reflog_action "pull${1+ $*}"
-require_work_tree_exists
-cd_to_toplevel
-
-
-die_conflict () {
- git diff-index --cached --name-status -r --ignore-submodules HEAD --
- if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
- die "$(gettext "Pull is not possible because you have unmerged files.
-Please, fix them up in the work tree, and then use 'git add/rm <file>'
-as appropriate to mark resolution and make a commit.")"
- else
- die "$(gettext "Pull is not possible because you have unmerged files.")"
- fi
-}
-
-die_merge () {
- if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
- die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).
-Please, commit your changes before you can merge.")"
- else
- die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).")"
- fi
-}
-
-test -z "$(git ls-files -u)" || die_conflict
-test -f "$GIT_DIR/MERGE_HEAD" && die_merge
-
-bool_or_string_config () {
- git config --bool "$1" 2>/dev/null || git config "$1"
-}
-
-strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
-log_arg= verbosity= progress= recurse_submodules= verify_signatures=
-merge_args= edit= rebase_args=
-curr_branch=$(git symbolic-ref -q HEAD)
-curr_branch_short="${curr_branch#refs/heads/}"
-rebase=$(bool_or_string_config branch.$curr_branch_short.rebase)
-if test -z "$rebase"
-then
- rebase=$(bool_or_string_config pull.rebase)
-fi
-
-# Setup default fast-forward options via `pull.ff`
-pull_ff=$(git config pull.ff)
-case "$pull_ff" in
-false)
- no_ff=--no-ff
- ;;
-only)
- ff_only=--ff-only
- ;;
-esac
-
-
-dry_run=
-while :
-do
- case "$1" in
- -q|--quiet)
- verbosity="$verbosity -q" ;;
- -v|--verbose)
- verbosity="$verbosity -v" ;;
- --progress)
- progress=--progress ;;
- --no-progress)
- progress=--no-progress ;;
- -n|--no-stat|--no-summary)
- diffstat=--no-stat ;;
- --stat|--summary)
- diffstat=--stat ;;
- --log|--no-log)
- log_arg=$1 ;;
- --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
- no_commit=--no-commit ;;
- --c|--co|--com|--comm|--commi|--commit)
- no_commit=--commit ;;
- -e|--edit)
- edit=--edit ;;
- --no-edit)
- edit=--no-edit ;;
- --sq|--squ|--squa|--squas|--squash)
- squash=--squash ;;
- --no-sq|--no-squ|--no-squa|--no-squas|--no-squash)
- squash=--no-squash ;;
- --ff)
- no_ff=--ff ;;
- --no-ff)
- no_ff=--no-ff ;;
- --ff-only)
- ff_only=--ff-only ;;
- -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
- --strateg=*|--strategy=*|\
- -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
- case "$#,$1" in
- *,*=*)
- strategy=$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
- 1,*)
- usage ;;
- *)
- strategy="$2"
- shift ;;
- esac
- strategy_args="${strategy_args}-s $strategy "
- ;;
- -X*)
- case "$#,$1" in
- 1,-X)
- usage ;;
- *,-X)
- xx="-X $(git rev-parse --sq-quote "$2")"
- shift ;;
- *,*)
- xx=$(git rev-parse --sq-quote "$1") ;;
- esac
- merge_args="$merge_args$xx "
- ;;
- -r=*|--r=*|--re=*|--reb=*|--reba=*|--rebas=*|--rebase=*)
- rebase="${1#*=}"
- ;;
- -r|--r|--re|--reb|--reba|--rebas|--rebase)
- rebase=true
- ;;
- --no-r|--no-re|--no-reb|--no-reba|--no-rebas|--no-rebase)
- rebase=false
- ;;
- --recurse-submodules)
- recurse_submodules=--recurse-submodules
- ;;
- --recurse-submodules=*)
- recurse_submodules="$1"
- ;;
- --no-recurse-submodules)
- recurse_submodules=--no-recurse-submodules
- ;;
- --verify-signatures)
- verify_signatures=--verify-signatures
- ;;
- --no-verify-signatures)
- verify_signatures=--no-verify-signatures
- ;;
- --gpg-sign|-S)
- gpg_sign_args=-S
- ;;
- --gpg-sign=*)
- gpg_sign_args=$(git rev-parse --sq-quote "-S${1#--gpg-sign=}")
- ;;
- -S*)
- gpg_sign_args=$(git rev-parse --sq-quote "$1")
- ;;
- --d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run)
- dry_run=--dry-run
- ;;
- -h|--help-all)
- usage
- ;;
- *)
- # Pass thru anything that may be meant for fetch.
- break
- ;;
- esac
- shift
-done
-
-case "$rebase" in
-preserve)
- rebase=true
- rebase_args=--preserve-merges
- ;;
-true|false|'')
- ;;
-*)
- echo "Invalid value for --rebase, should be true, false, or preserve"
- usage
- exit 1
- ;;
-esac
-
-error_on_no_merge_candidates () {
- exec >&2
- for opt
- do
- case "$opt" in
- -t|--t|--ta|--tag|--tags)
- echo "It doesn't make sense to pull all tags; you probably meant:"
- echo " git fetch --tags"
- exit 1
- esac
- done
-
- if test true = "$rebase"
- then
- op_type=rebase
- op_prep=against
- else
- op_type=merge
- op_prep=with
- fi
-
- upstream=$(git config "branch.$curr_branch_short.merge")
- remote=$(git config "branch.$curr_branch_short.remote")
-
- if [ $# -gt 1 ]; then
- if [ "$rebase" = true ]; then
- printf "There is no candidate for rebasing against "
- else
- printf "There are no candidates for merging "
- fi
- echo "among the refs that you just fetched."
- echo "Generally this means that you provided a wildcard refspec which had no"
- echo "matches on the remote end."
- elif [ $# -gt 0 ] && [ "$1" != "$remote" ]; then
- echo "You asked to pull from the remote '$1', but did not specify"
- echo "a branch. Because this is not the default configured remote"
- echo "for your current branch, you must specify a branch on the command line."
- elif [ -z "$curr_branch" -o -z "$upstream" ]; then
- . git-parse-remote
- error_on_missing_default_upstream "pull" $op_type $op_prep \
- "git pull <remote> <branch>"
- else
- echo "Your configuration specifies to $op_type $op_prep the ref '${upstream#refs/heads/}'"
- echo "from the remote, but no such ref was fetched."
- fi
- exit 1
-}
-
-test true = "$rebase" && {
- if ! git rev-parse -q --verify HEAD >/dev/null
- then
- # On an unborn branch
- if test -f "$GIT_DIR/index"
- then
- die "$(gettext "updating an unborn branch with changes added to the index")"
- fi
- else
- require_clean_work_tree "pull with rebase" "Please commit or stash them."
- fi
- oldremoteref= &&
- test -n "$curr_branch" &&
- . git-parse-remote &&
- remoteref="$(get_remote_merge_branch "$@" 2>/dev/null)" &&
- oldremoteref=$(git merge-base --fork-point "$remoteref" $curr_branch 2>/dev/null)
-}
-orig_head=$(git rev-parse -q --verify HEAD)
-git fetch $verbosity $progress $dry_run $recurse_submodules --update-head-ok "$@" || exit 1
-test -z "$dry_run" || exit 0
-
-curr_head=$(git rev-parse -q --verify HEAD)
-if test -n "$orig_head" && test "$curr_head" != "$orig_head"
-then
- # The fetch involved updating the current branch.
-
- # The working tree and the index file is still based on the
- # $orig_head commit, but we are merging into $curr_head.
- # First update the working tree to match $curr_head.
-
- eval_gettextln "Warning: fetch updated the current branch head.
-Warning: fast-forwarding your working tree from
-Warning: commit \$orig_head." >&2
- git update-index -q --refresh
- git read-tree -u -m "$orig_head" "$curr_head" ||
- die "$(eval_gettext "Cannot fast-forward your working tree.
-After making sure that you saved anything precious from
-$ git diff \$orig_head
-output, run
-$ git reset --hard
-to recover.")"
-
-fi
-
-merge_head=$(sed -e '/ not-for-merge /d' \
- -e 's/ .*//' "$GIT_DIR"/FETCH_HEAD | \
- tr '\012' ' ')
-
-case "$merge_head" in
-'')
- error_on_no_merge_candidates "$@"
- ;;
-?*' '?*)
- if test -z "$orig_head"
- then
- die "$(gettext "Cannot merge multiple branches into empty head")"
- fi
- if test true = "$rebase"
- then
- die "$(gettext "Cannot rebase onto multiple branches")"
- fi
- ;;
-esac
-
-# Pulling into unborn branch: a shorthand for branching off
-# FETCH_HEAD, for lazy typers.
-if test -z "$orig_head"
-then
- # Two-way merge: we claim the index is based on an empty tree,
- # and try to fast-forward to HEAD. This ensures we will not
- # lose index/worktree changes that the user already made on
- # the unborn branch.
- empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
- git read-tree -m -u $empty_tree $merge_head &&
- git update-ref -m "initial pull" HEAD $merge_head "$curr_head"
- exit
-fi
-
-if test true = "$rebase"
-then
- o=$(git show-branch --merge-base $curr_branch $merge_head $oldremoteref)
- if test "$oldremoteref" = "$o"
- then
- unset oldremoteref
- fi
-fi
-
-merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
-case "$rebase" in
-true)
- eval="git-rebase $diffstat $strategy_args $merge_args $rebase_args $verbosity"
- eval="$eval $gpg_sign_args"
- eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
- ;;
-*)
- eval="git-merge $diffstat $no_commit $verify_signatures $edit $squash $no_ff $ff_only"
- eval="$eval $log_arg $strategy_args $merge_args $verbosity $progress"
- eval="$eval $gpg_sign_args"
- eval="$eval \"\$merge_name\" HEAD $merge_head"
- ;;
-esac
-eval "exec $eval"
diff --git a/git.c b/git.c
index 42a4ee5..cd824b6 100644
--- a/git.c
+++ b/git.c
@@ -445,6 +445,7 @@ static struct cmd_struct commands[] = {
{ "pickaxe", cmd_blame, RUN_SETUP },
{ "prune", cmd_prune, RUN_SETUP },
{ "prune-packed", cmd_prune_packed, RUN_SETUP },
+ { "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE },
{ "push", cmd_push, RUN_SETUP },
{ "read-tree", cmd_read_tree, RUN_SETUP },
{ "receive-pack", cmd_receive_pack },
--
2.4.0.7.gf20f26f
^ permalink raw reply related [flat|nested] 13+ messages in thread