[PATCH 4/6] pull: reimplement as a builtin in C

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxxxxx>
---
 .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@xxxxxxxxx>
+ *
+ * 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

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]