All of lore.kernel.org
 help / color / mirror / Atom feed
* GSoC 2015: 2 accepted proposals
@ 2015-04-27 19:50 Matthieu Moy
  2015-04-28  8:58 ` Paul Tan
  2015-04-28 12:17 ` GSoC 2015: 2 accepted proposals karthik nayak
  0 siblings, 2 replies; 13+ messages in thread
From: Matthieu Moy @ 2015-04-27 19:50 UTC (permalink / raw)
  To: git
  Cc: karthik.188, pyokagan, Jeff King, Christian Couder,
	Johannes Schindelin, Stefan Beller

Hi,

The results just got offical: the Git organization has 2 students
accepted for the summer of code 2015.

Karthik Nayak will work on "Unifying git branch -l, git tag -l, and git
for-each-ref" mentored by Christian Couder and yours truly.

Paul Tan will work on "Make git-pull and git-am builtins", mentored by 
Johannes Schindelin and Stefan Beller.

Regards,

-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: GSoC 2015: 2 accepted proposals
  2015-04-27 19:50 GSoC 2015: 2 accepted proposals Matthieu Moy
@ 2015-04-28  8:58 ` Paul Tan
  2015-04-29 15:27   ` Johannes Schindelin
  2015-05-06  0:00   ` [PATCH 0/6] Make pull a builtin Stephen Robin
  2015-04-28 12:17 ` GSoC 2015: 2 accepted proposals karthik nayak
  1 sibling, 2 replies; 13+ messages in thread
From: Paul Tan @ 2015-04-28  8:58 UTC (permalink / raw)
  To: Johannes Schindelin, Stefan Beller
  Cc: git, karthik.188, Jeff King, Christian Couder, Matthieu Moy

Hi all,

On Tue, Apr 28, 2015 at 3:50 AM, Matthieu Moy
<Matthieu.Moy@grenoble-inp.fr> wrote:
> Hi,
>
> The results just got offical: the Git organization has 2 students
> accepted for the summer of code 2015.
>
> Karthik Nayak will work on "Unifying git branch -l, git tag -l, and git
> for-each-ref" mentored by Christian Couder and yours truly.
>
> Paul Tan will work on "Make git-pull and git-am builtins", mentored by
> Johannes Schindelin and Stefan Beller.

Thanks for accepting me, and it is my pleasure to work with everyone.
For reference, information on the proposed project can be found at
[1].

As stated in the proposal, I will be starting work on the project
immediately. The first step would be to review the test coverage of
git-pull and git-am in order to make sure that nothing breaks during
the rewriting. I am currently reviewing all related code in order to
get a better perspective on the issues involved. I am also aware of
the recent issues with git-pull[2], which I will look into.

All patches that I'm working on but are not ready to be published to
the mailing list will be cooked in their corresponding branches in my
personal git repo[3], which I will push at least once a working day.
Everyone is welcome to have a look :-).

Again, thanks all for your support and guidance, and I look forward to
working with everyone to make this project a success.

Regards,
Paul

[1] https://gist.github.com/pyokagan/1b7b0d1f4dab6ba3cef1
[2] http://thread.gmane.org/gmane.comp.version-control.git/267432
[3] https://github.com/pyokagan/git

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: GSoC 2015: 2 accepted proposals
  2015-04-27 19:50 GSoC 2015: 2 accepted proposals Matthieu Moy
  2015-04-28  8:58 ` Paul Tan
@ 2015-04-28 12:17 ` karthik nayak
  1 sibling, 0 replies; 13+ messages in thread
From: karthik nayak @ 2015-04-28 12:17 UTC (permalink / raw)
  To: Matthieu Moy, git
  Cc: pyokagan, Jeff King, Christian Couder, Johannes Schindelin,
	Stefan Beller

Hello to Everyone,

On 04/28/2015 01:20 AM, Matthieu Moy wrote:
> Hi,
>
> The results just got offical: the Git organization has 2 students
> accepted for the summer of code 2015.
>
> Karthik Nayak will work on "Unifying git branch -l, git tag -l, and git
> for-each-ref" mentored by Christian Couder and yours truly.

Thanks a lot for accepting my proposal and giving me an opportunity to 
work with Git via GSoC. Can't express how excited I am.

As Matthieu mentioned I will be working on "Unifying git branch -l, git 
tag -l, and git for-each-ref". I plan to start off by going through the 
existing implementation of each of the above commands and come up with a 
design for unification for now. Also to take notes from Jeff King's 
attempt at unification[1].

I will be pushing all my work to my personal Git repository on Github[2].
Where anyone can get involved. Will also make sure to notify the mailing 
list on a regular basis on what is going on with my project.

Looking forward to work with Git and complete this project successfully.

Regards,
Karthik

>
> Paul Tan will work on "Make git-pull and git-am builtins", mentored by
> Johannes Schindelin and Stefan Beller.

Congrats Paul, I wish you the very best.

>
> Regards,
>

[1] https://github.com/peff/git/tree/jk/for-each-ref-contains-wip
[2] https://github.com/KarthikNayak/git

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: GSoC 2015: 2 accepted proposals
  2015-04-28  8:58 ` Paul Tan
@ 2015-04-29 15:27   ` Johannes Schindelin
  2015-05-06  0:00   ` [PATCH 0/6] Make pull a builtin Stephen Robin
  1 sibling, 0 replies; 13+ messages in thread
From: Johannes Schindelin @ 2015-04-29 15:27 UTC (permalink / raw)
  To: Paul Tan
  Cc: Stefan Beller, git, karthik.188, Jeff King, Christian Couder,
	Matthieu Moy

Hi Paul,

On 2015-04-28 10:58, Paul Tan wrote:

> As stated in the proposal, I will be starting work on the project
> immediately. The first step would be to review the test coverage of
> git-pull and git-am in order to make sure that nothing breaks during
> the rewriting. I am currently reviewing all related code in order to
> get a better perspective on the issues involved. I am also aware of
> the recent issues with git-pull[2], which I will look into.
> 
> All patches that I'm working on but are not ready to be published to
> the mailing list will be cooked in their corresponding branches in my
> personal git repo[3], which I will push at least once a working day.
> Everyone is welcome to have a look :-).
> 
> Again, thanks all for your support and guidance, and I look forward to
> working with everyone to make this project a success.

Sounds good! Feel free to ping me on https://gitter.im/git-for-windows/git for a more interactive chat. Email is fine with me, too, though.

Ciao,
Johannes

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 0/6] Make pull a builtin
  2015-04-28  8:58 ` Paul Tan
  2015-04-29 15:27   ` Johannes Schindelin
@ 2015-05-06  0:00   ` Stephen Robin
  2015-05-06  0:00     ` [PATCH 1/6] merge: tidy up options Stephen Robin
                       ` (6 more replies)
  1 sibling, 7 replies; 13+ messages in thread
From: Stephen Robin @ 2015-05-06  0:00 UTC (permalink / raw)
  To: pyokagan; +Cc: git

Hi Paul,

Congratulations on getting your project accepted for GSOC. Here's my
attempt at implementing pull as a builtin, maybe it will be of some use
as you look to progress your version.

It's fairly complete in the sense that all the features of git-pull.sh
should be implemented, the test suite passes, and I've been using it
myself without issue. At the same time it's some way from finished as
I've never had time to test it fully and there are parts of the code I'm
not happy with.

Apologies for not sharing this with you earlier.  I have been too busy
with paid work to look at any open source projects for some months.

Good luck with your project!

Regards
Stephen


Stephen Robin (6):
  merge: tidy up options
  merge: move error message given when a merge needs committing to
    advice.c
  merge-base: split handle_fork_point to make reuse easier
  pull: reimplement as a builtin in C
  pull: allow interactive rebase
  parse-remote: dismantle git-parse-remote.sh

 .gitignore                           |    2 -
 Documentation/config.txt             |    1 +
 Documentation/git-parse-remote.txt   |   23 -
 Documentation/git-pull.txt           |    4 +-
 Makefile                             |    3 +-
 advice.c                             |    9 +
 advice.h                             |    1 +
 builtin.h                            |    3 +
 builtin/merge-base.c                 |   64 ++-
 builtin/merge.c                      |   16 +-
 builtin/pull.c                       | 1006 ++++++++++++++++++++++++++++++++++
 builtin/remote.c                     |    8 +-
 command-list.txt                     |    1 -
 contrib/examples/git-parse-remote.sh |   89 +++
 contrib/examples/git-pull.sh         |  340 ++++++++++++
 git-parse-remote.sh                  |   89 ---
 git-pull.sh                          |  340 ------------
 git-rebase.sh                        |   36 +-
 git-submodule.sh                     |    8 +-
 git.c                                |    1 +
 20 files changed, 1547 insertions(+), 497 deletions(-)
 delete mode 100644 Documentation/git-parse-remote.txt
 create mode 100644 builtin/pull.c
 create mode 100644 contrib/examples/git-parse-remote.sh
 create mode 100755 contrib/examples/git-pull.sh
 delete mode 100644 git-parse-remote.sh
 delete mode 100755 git-pull.sh

-- 
2.4.0.7.gf20f26f

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 1/6] merge: tidy up options
  2015-05-06  0:00   ` [PATCH 0/6] Make pull a builtin Stephen Robin
@ 2015-05-06  0:00     ` Stephen Robin
  2015-05-06  0:00     ` [PATCH 2/6] merge: move error message given when a merge needs committing to advice.c Stephen Robin
                       ` (5 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.

Hide --summary (it is a deprecated synonym for --stat).
Correct capitalization of the description of --verify-signatures.
Correct indentation of code.

Signed-off-by: Stephen Robin <stephen.robin@gmail.com>
---
 builtin/merge.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/builtin/merge.c b/builtin/merge.c
index 3b0f8f9..d6b0579 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -194,10 +194,10 @@ static struct option builtin_merge_options[] = {
 		PARSE_OPT_NOARG, option_parse_n },
 	OPT_BOOL(0, "stat", &show_diffstat,
 		N_("show a diffstat at the end of the merge")),
-	OPT_BOOL(0, "summary", &show_diffstat, N_("(synonym to --stat)")),
+	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 },
+		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,
@@ -210,7 +210,7 @@ static struct option builtin_merge_options[] = {
 		PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, FF_ONLY },
 	OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
 	OPT_BOOL(0, "verify-signatures", &verify_signatures,
-		N_("Verify that the named commit has a valid GPG signature")),
+		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"),
@@ -223,7 +223,7 @@ static struct option builtin_merge_options[] = {
 		N_("abort the current in-progress merge")),
 	OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1),
 	{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"),
-	  N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+		N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
 	OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")),
 	OPT_END()
 };
-- 
2.4.0.7.gf20f26f

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 2/6] merge: move error message given when a merge needs committing to advice.c
  2015-05-06  0:00   ` [PATCH 0/6] Make pull a builtin Stephen Robin
  2015-05-06  0:00     ` [PATCH 1/6] merge: tidy up options Stephen Robin
@ 2015-05-06  0:00     ` Stephen Robin
  2015-05-06  0:00     ` [PATCH 3/6] merge-base: split handle_fork_point to make reuse easier Stephen Robin
                       ` (4 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.

This will allow the message to be re-used in other places.

Signed-off-by: Stephen Robin <stephen.robin@gmail.com>
---
 advice.c        | 9 +++++++++
 advice.h        | 1 +
 builtin/merge.c | 6 +-----
 3 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/advice.c b/advice.c
index 575bec2..747792e 100644
--- a/advice.c
+++ b/advice.c
@@ -96,6 +96,15 @@ void NORETURN die_resolve_conflict(const char *me)
 	die("Exiting because of an unresolved conflict.");
 }
 
+void NORETURN die_merge_in_progress()
+{
+	if (advice_resolve_conflict)
+		die(_("You have not concluded your merge (MERGE_HEAD exists).\n"
+		"Please, commit your changes before you merge."));
+	else
+		die(_("You have not concluded your merge (MERGE_HEAD exists)."));
+}
+
 void detach_advice(const char *new_name)
 {
 	const char fmt[] =
diff --git a/advice.h b/advice.h
index 5ecc6c1..75c261a 100644
--- a/advice.h
+++ b/advice.h
@@ -24,6 +24,7 @@ __attribute__((format (printf, 1, 2)))
 void advise(const char *advice, ...);
 int error_resolve_conflict(const char *me);
 extern void NORETURN die_resolve_conflict(const char *me);
+extern void NORETURN die_merge_in_progress();
 void detach_advice(const char *new_name);
 
 #endif /* ADVICE_H */
diff --git a/builtin/merge.c b/builtin/merge.c
index d6b0579..45d93bb 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -1134,11 +1134,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		 * There is no unmerged entry, don't advise 'git
 		 * add/rm <file>', just 'git commit'.
 		 */
-		if (advice_resolve_conflict)
-			die(_("You have not concluded your merge (MERGE_HEAD exists).\n"
-				  "Please, commit your changes before you merge."));
-		else
-			die(_("You have not concluded your merge (MERGE_HEAD exists)."));
+		die_merge_in_progress();
 	}
 	if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
 		if (advice_resolve_conflict)
-- 
2.4.0.7.gf20f26f

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 3/6] merge-base: split handle_fork_point to make reuse easier
  2015-05-06  0:00   ` [PATCH 0/6] Make pull a builtin Stephen Robin
  2015-05-06  0:00     ` [PATCH 1/6] merge: tidy up options Stephen Robin
  2015-05-06  0:00     ` [PATCH 2/6] merge: move error message given when a merge needs committing to advice.c Stephen Robin
@ 2015-05-06  0:00     ` Stephen Robin
  2015-05-06  0:00     ` [PATCH 4/6] pull: reimplement as a builtin in C Stephen Robin
                       ` (3 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.

Separate function handle_fork_point into one part that deals with command
line arguments, printing error messages and exiting the process, and a
second part that contains only the algorithm.  The later part can be
re-used elsewhere.

Signed-off-by: Stephen Robin <stephen.robin@gmail.com>
---
 builtin.h            |  2 ++
 builtin/merge-base.c | 64 ++++++++++++++++++++++++++++++++--------------------
 2 files changed, 41 insertions(+), 25 deletions(-)

diff --git a/builtin.h b/builtin.h
index b87df70..d8e0e5a 100644
--- a/builtin.h
+++ b/builtin.h
@@ -27,6 +27,8 @@ extern int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
 
 extern int textconv_object(const char *path, unsigned mode, const unsigned char *sha1, int sha1_valid, char **buf, unsigned long *buf_size);
 
+extern const unsigned char *get_fork_point(const char *refname, const unsigned char *derived_sha1);
+
 extern int is_builtin(const char *s);
 
 extern int cmd_add(int argc, const char **argv, const char *prefix);
diff --git a/builtin/merge-base.c b/builtin/merge-base.c
index 08a8217..7a22cf1 100644
--- a/builtin/merge-base.c
+++ b/builtin/merge-base.c
@@ -145,30 +145,19 @@ static int collect_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
 	return 0;
 }
 
-static int handle_fork_point(int argc, const char **argv)
+const unsigned char *get_fork_point(const char *refname, const unsigned char *derived_sha1)
 {
-	unsigned char sha1[20];
-	char *refname;
-	const char *commitname;
+	/*
+	 * TODO I dislike exporting this function via builtin.h. I would prefer to
+	 * move it to libgit if possible.
+	 */
+	unsigned const char *fork_point_sha1 = null_sha1;
 	struct rev_collect revs;
 	struct commit *derived;
 	struct commit_list *bases;
-	int i, ret = 0;
-
-	switch (dwim_ref(argv[0], strlen(argv[0]), sha1, &refname)) {
-	case 0:
-		die("No such ref: '%s'", argv[0]);
-	case 1:
-		break; /* good */
-	default:
-		die("Ambiguous refname: '%s'", argv[0]);
-	}
-
-	commitname = (argc == 2) ? argv[1] : "HEAD";
-	if (get_sha1(commitname, sha1))
-		die("Not a valid object name: '%s'", commitname);
+	int i;
 
-	derived = lookup_commit_reference(sha1);
+	derived = lookup_commit_reference(derived_sha1);
 	memset(&revs, 0, sizeof(revs));
 	revs.initial = 1;
 	for_each_reflog_ent(refname, collect_one_reflog_ent, &revs);
@@ -182,25 +171,50 @@ static int handle_fork_point(int argc, const char **argv)
 	 * There should be one and only one merge base, when we found
 	 * a common ancestor among reflog entries.
 	 */
-	if (!bases || bases->next) {
-		ret = 1;
+	if (!bases || bases->next)
 		goto cleanup_return;
-	}
 
 	/* And the found one must be one of the reflog entries */
 	for (i = 0; i < revs.nr; i++)
 		if (&bases->item->object == &revs.commit[i]->object)
 			break; /* found */
 	if (revs.nr <= i) {
-		ret = 1; /* not found */
+		/* not found */
 		goto cleanup_return;
 	}
 
-	printf("%s\n", sha1_to_hex(bases->item->object.sha1));
+	fork_point_sha1 = bases->item->object.sha1;
 
 cleanup_return:
 	free_commit_list(bases);
-	return ret;
+	return fork_point_sha1;
+}
+
+static int handle_fork_point(int argc, const char **argv)
+{
+	unsigned char sha1[20];
+	char *refname;
+	const char *commitname;
+	const unsigned char *fork_point_sha1;
+
+	switch (dwim_ref(argv[0], strlen(argv[0]), sha1, &refname)) {
+	case 0:
+		die("No such ref: '%s'", argv[0]);
+	case 1:
+		break; /* good */
+	default:
+		die("Ambiguous refname: '%s'", argv[0]);
+	}
+
+	commitname = (argc == 2) ? argv[1] : "HEAD";
+	if (get_sha1(commitname, sha1))
+		die("Not a valid object name: '%s'", commitname);
+
+	fork_point_sha1 = get_fork_point(refname, sha1);
+	if (!is_null_sha1(fork_point_sha1))
+		printf("%s\n", sha1_to_hex(fork_point_sha1));
+
+	return is_null_sha1(fork_point_sha1);
 }
 
 int cmd_merge_base(int argc, const char **argv, const char *prefix)
-- 
2.4.0.7.gf20f26f

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [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

* [PATCH 5/6] pull: allow interactive rebase
  2015-05-06  0:00   ` [PATCH 0/6] Make pull a builtin Stephen Robin
                       ` (3 preceding siblings ...)
  2015-05-06  0:00     ` [PATCH 4/6] pull: reimplement as a builtin in C Stephen Robin
@ 2015-05-06  0:00     ` Stephen Robin
  2015-05-06  5:43       ` Johannes Schindelin
  2015-05-06  0:00     ` [PATCH 6/6] parse-remote: dismantle git-parse-remote.sh Stephen Robin
  2015-05-06  4:27     ` [PATCH 0/6] Make pull a builtin Paul Tan
  6 siblings, 1 reply; 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.

Teach 'git pull' the option --rebase=interactive
Teach 'git remote' that the value for config variable branch.<name>.rebase
can be 'interactive'

Based-on-patch-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Stephen Robin <stephen.robin@gmail.com>
---

Notes:
    This feature is already present in msysgit.  This patch can be used
    either to bring the feature into standard git, or to resolve the
    conflict that will occur in the next rebase of msysgit.

 Documentation/config.txt   |  1 +
 Documentation/git-pull.txt |  4 +++-
 builtin/pull.c             | 19 ++++++++++++++++---
 builtin/remote.c           |  8 ++++++--
 4 files changed, 26 insertions(+), 6 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 2e5ceaf..91314ef 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -849,6 +849,7 @@ branch.<name>.rebase::
 	instead of merging the default branch from the default remote when
 	"git pull" is run. See "pull.rebase" for doing this in a non
 	branch-specific manner.
+	When the value is `interactive`, the rebase is run in interactive mode.
 +
 	When preserve, also pass `--preserve-merges` along to 'git rebase'
 	so that locally committed merge commits will not be flattened
diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt
index 712ab4b..8014908 100644
--- a/Documentation/git-pull.txt
+++ b/Documentation/git-pull.txt
@@ -104,7 +104,7 @@ Options related to merging
 include::merge-options.txt[]
 
 -r::
---rebase[=false|true|preserve]::
+--rebase[=false|true|preserve|interactive]::
 	When true, rebase the current branch on top of the upstream
 	branch after fetching. If there is a remote-tracking branch
 	corresponding to the upstream branch and the upstream branch
@@ -116,6 +116,8 @@ to `git rebase` so that locally created merge commits will not be flattened.
 +
 When false, merge the current branch into the upstream branch.
 +
+When `interactive`, enable the interactive mode of rebase.
++
 See `pull.rebase`, `branch.<name>.rebase` and `branch.autoSetupRebase` in
 linkgit:git-config[1] if you want to make `git pull` always use
 `--rebase` instead of merging.
diff --git a/builtin/pull.c b/builtin/pull.c
index f420b4a..76c2f72 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -26,7 +26,8 @@ enum pull_mode {
 	PULL_NOT_SET = -1,
 	PULL_MERGE,
 	PULL_REBASE,
-	PULL_PRESERVE_MERGES_REBASE
+	PULL_PRESERVE_MERGES_REBASE,
+	PULL_INTERACTIVE_REBASE
 };
 
 static const char * const builtin_pull_usage[] = {
@@ -85,12 +86,22 @@ static int parse_pull_mode(const char *name, const char* arg,
 		return 0;
 	}
 
+	if (!strcmp(arg, "interactive")) {
+		*option_rebase = PULL_INTERACTIVE_REBASE;
+		return 0;
+	}
+
+	if (!strcmp(arg, "i")) {
+		*option_rebase = PULL_INTERACTIVE_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);
+	error(_("Invalid value for %s, should be 'true', 'false', 'interactive' or 'preserve'."), name);
 	return -1;
 }
 
@@ -197,7 +208,7 @@ static int option_parse_x(const struct option *opt,
 }
 
 static struct option builtin_pull_options[] = {
-	{ OPTION_CALLBACK, 0, "rebase", NULL, N_("true|false|preserve"),
+	{ OPTION_CALLBACK, 0, "rebase", NULL, N_("true|false|interactive|preserve"),
 		N_("incorporate changes by rebasing rather than merging"),
 		PARSE_OPT_OPTARG, option_parse_rebase },
 	OPT_BOOL(0, "progress", &progress,
@@ -527,6 +538,8 @@ static int run_rebase(const struct string_list merge_head, const char *fork_poin
 
 	if (pull_mode == PULL_PRESERVE_MERGES_REBASE)
 		argv_array_push(&argv, "--preserve-merges");
+	else if (pull_mode == PULL_INTERACTIVE_REBASE)
+		argv_array_push(&argv, "-i");
 
 	for (v = verbosity; v > 0; v--)
 		argv_array_push(&argv, "-v");
diff --git a/builtin/remote.c b/builtin/remote.c
index 5d3ab90..af6b21d 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -245,7 +245,7 @@ static int add(int argc, const char **argv)
 struct branch_info {
 	char *remote_name;
 	struct string_list merge;
-	int rebase;
+	enum { NO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE } rebase;
 };
 
 static struct string_list branch_list;
@@ -306,6 +306,8 @@ static int config_read_branches(const char *key, const char *value, void *cb)
 				info->rebase = v;
 			else if (!strcmp(value, "preserve"))
 				info->rebase = 1;
+			else if (!strcmp(value, "interactive"))
+				info->rebase = INTERACTIVE_REBASE;
 		}
 	}
 	return 0;
@@ -1000,7 +1002,9 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)
 
 	printf("    %-*s ", show_info->width, item->string);
 	if (branch_info->rebase) {
-		printf_ln(_("rebases onto remote %s"), merge->items[0].string);
+		printf_ln(_(branch_info->rebase == INTERACTIVE_REBASE ?
+			"rebases interactively onto remote %s" :
+			"rebases onto remote %s"), merge->items[0].string);
 		return 0;
 	} else if (show_info->any_rebase) {
 		printf_ln(_(" merges with remote %s"), merge->items[0].string);
-- 
2.4.0.7.gf20f26f

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 6/6] parse-remote: dismantle git-parse-remote.sh
  2015-05-06  0:00   ` [PATCH 0/6] Make pull a builtin Stephen Robin
                       ` (4 preceding siblings ...)
  2015-05-06  0:00     ` [PATCH 5/6] pull: allow interactive rebase Stephen Robin
@ 2015-05-06  0:00     ` Stephen Robin
  2015-05-06  4:27     ` [PATCH 0/6] Make pull a builtin Paul Tan
  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.

Following the conversion of git-pull.sh to a builtin, git-parse-remote.sh
is only used from two places:
function error_on_missing_default_upstream is used within git-rebase.sh
only.
function get_default_remote is used within git-submodule.sh only.

Move these two functions into the scripts within which they're used, and
delete git-parse-remote.sh itself.

Signed-off-by: Stephen Robin <stephen.robin@gmail.com>
---
 .gitignore                           |  1 -
 Documentation/git-parse-remote.txt   | 23 ----------
 Makefile                             |  1 -
 command-list.txt                     |  1 -
 contrib/examples/git-parse-remote.sh | 89 ++++++++++++++++++++++++++++++++++++
 git-parse-remote.sh                  | 89 ------------------------------------
 git-rebase.sh                        | 36 ++++++++++++++-
 git-submodule.sh                     |  8 +++-
 8 files changed, 131 insertions(+), 117 deletions(-)
 delete mode 100644 Documentation/git-parse-remote.txt
 create mode 100644 contrib/examples/git-parse-remote.sh
 delete mode 100644 git-parse-remote.sh

diff --git a/.gitignore b/.gitignore
index 6287647..dd25b33 100644
--- a/.gitignore
+++ b/.gitignore
@@ -104,7 +104,6 @@
 /git-pack-redundant
 /git-pack-objects
 /git-pack-refs
-/git-parse-remote
 /git-patch-id
 /git-prune
 /git-prune-packed
diff --git a/Documentation/git-parse-remote.txt b/Documentation/git-parse-remote.txt
deleted file mode 100644
index a45ea1e..0000000
--- a/Documentation/git-parse-remote.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-git-parse-remote(1)
-===================
-
-NAME
-----
-git-parse-remote - Routines to help parsing remote repository access parameters
-
-
-SYNOPSIS
---------
-[verse]
-'. "$(git --exec-path)/git-parse-remote"'
-
-DESCRIPTION
------------
-This script is included in various scripts to supply
-routines to parse files under $GIT_DIR/remotes/ and
-$GIT_DIR/branches/ and configuration variables that are related
-to fetching, pulling and pushing.
-
-GIT
----
-Part of the linkgit:git[1] suite
diff --git a/Makefile b/Makefile
index 8d8fb3a..d41224c 100644
--- a/Makefile
+++ b/Makefile
@@ -481,7 +481,6 @@ SCRIPT_SH += git-submodule.sh
 SCRIPT_SH += git-web--browse.sh
 
 SCRIPT_LIB += git-mergetool--lib
-SCRIPT_LIB += git-parse-remote
 SCRIPT_LIB += git-rebase--am
 SCRIPT_LIB += git-rebase--interactive
 SCRIPT_LIB += git-rebase--merge
diff --git a/command-list.txt b/command-list.txt
index f1eae08..273f69e 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -86,7 +86,6 @@ git-p4                                  foreignscminterface
 git-pack-objects                        plumbingmanipulators
 git-pack-redundant                      plumbinginterrogators
 git-pack-refs                           ancillarymanipulators
-git-parse-remote                        synchelpers
 git-patch-id                            purehelpers
 git-prune                               ancillarymanipulators
 git-prune-packed                        plumbingmanipulators
diff --git a/contrib/examples/git-parse-remote.sh b/contrib/examples/git-parse-remote.sh
new file mode 100644
index 0000000..55fe8d5
--- /dev/null
+++ b/contrib/examples/git-parse-remote.sh
@@ -0,0 +1,89 @@
+# This is a shell library to calculate the remote repository and
+# upstream branch that should be pulled by "git pull" from the current
+# branch.
+
+# git-ls-remote could be called from outside a git managed repository;
+# this would fail in that case and would issue an error message.
+GIT_DIR=$(git rev-parse -q --git-dir) || :;
+
+get_default_remote () {
+	curr_branch=$(git symbolic-ref -q HEAD)
+	curr_branch="${curr_branch#refs/heads/}"
+	origin=$(git config --get "branch.$curr_branch.remote")
+	echo ${origin:-origin}
+}
+
+get_remote_merge_branch () {
+	case "$#" in
+	0|1)
+	    origin="$1"
+	    default=$(get_default_remote)
+	    test -z "$origin" && origin=$default
+	    curr_branch=$(git symbolic-ref -q HEAD) &&
+	    [ "$origin" = "$default" ] &&
+	    echo $(git for-each-ref --format='%(upstream)' $curr_branch)
+	    ;;
+	*)
+	    repo=$1
+	    shift
+	    ref=$1
+	    # FIXME: It should return the tracking branch
+	    #        Currently only works with the default mapping
+	    case "$ref" in
+	    +*)
+		ref=$(expr "z$ref" : 'z+\(.*\)')
+		;;
+	    esac
+	    expr "z$ref" : 'z.*:' >/dev/null || ref="${ref}:"
+	    remote=$(expr "z$ref" : 'z\([^:]*\):')
+	    case "$remote" in
+	    '' | HEAD ) remote=HEAD ;;
+	    heads/*) remote=${remote#heads/} ;;
+	    refs/heads/*) remote=${remote#refs/heads/} ;;
+	    refs/* | tags/* | remotes/* ) remote=
+	    esac
+	    [ -n "$remote" ] && case "$repo" in
+		.)
+		    echo "refs/heads/$remote"
+		    ;;
+		*)
+		    echo "refs/remotes/$repo/$remote"
+		    ;;
+	    esac
+	esac
+}
+
+error_on_missing_default_upstream () {
+	cmd="$1"
+	op_type="$2"
+	op_prep="$3"
+	example="$4"
+	branch_name=$(git symbolic-ref -q HEAD)
+	# If there's only one remote, use that in the suggestion
+	remote="<remote>"
+	if test $(git remote | wc -l) = 1
+	then
+		remote=$(git remote)
+	fi
+
+	if test -z "$branch_name"
+	then
+		echo "You are not currently on a branch. Please specify which
+branch you want to $op_type $op_prep. See git-${cmd}(1) for details.
+
+    $example
+"
+	else
+		echo "There is no tracking information for the current branch.
+Please specify which branch you want to $op_type $op_prep.
+See git-${cmd}(1) for details
+
+    $example
+
+If you wish to set tracking information for this branch you can do so with:
+
+    git branch --set-upstream-to=$remote/<branch> ${branch_name#refs/heads/}
+"
+	fi
+	exit 1
+}
diff --git a/git-parse-remote.sh b/git-parse-remote.sh
deleted file mode 100644
index 55fe8d5..0000000
--- a/git-parse-remote.sh
+++ /dev/null
@@ -1,89 +0,0 @@
-# This is a shell library to calculate the remote repository and
-# upstream branch that should be pulled by "git pull" from the current
-# branch.
-
-# git-ls-remote could be called from outside a git managed repository;
-# this would fail in that case and would issue an error message.
-GIT_DIR=$(git rev-parse -q --git-dir) || :;
-
-get_default_remote () {
-	curr_branch=$(git symbolic-ref -q HEAD)
-	curr_branch="${curr_branch#refs/heads/}"
-	origin=$(git config --get "branch.$curr_branch.remote")
-	echo ${origin:-origin}
-}
-
-get_remote_merge_branch () {
-	case "$#" in
-	0|1)
-	    origin="$1"
-	    default=$(get_default_remote)
-	    test -z "$origin" && origin=$default
-	    curr_branch=$(git symbolic-ref -q HEAD) &&
-	    [ "$origin" = "$default" ] &&
-	    echo $(git for-each-ref --format='%(upstream)' $curr_branch)
-	    ;;
-	*)
-	    repo=$1
-	    shift
-	    ref=$1
-	    # FIXME: It should return the tracking branch
-	    #        Currently only works with the default mapping
-	    case "$ref" in
-	    +*)
-		ref=$(expr "z$ref" : 'z+\(.*\)')
-		;;
-	    esac
-	    expr "z$ref" : 'z.*:' >/dev/null || ref="${ref}:"
-	    remote=$(expr "z$ref" : 'z\([^:]*\):')
-	    case "$remote" in
-	    '' | HEAD ) remote=HEAD ;;
-	    heads/*) remote=${remote#heads/} ;;
-	    refs/heads/*) remote=${remote#refs/heads/} ;;
-	    refs/* | tags/* | remotes/* ) remote=
-	    esac
-	    [ -n "$remote" ] && case "$repo" in
-		.)
-		    echo "refs/heads/$remote"
-		    ;;
-		*)
-		    echo "refs/remotes/$repo/$remote"
-		    ;;
-	    esac
-	esac
-}
-
-error_on_missing_default_upstream () {
-	cmd="$1"
-	op_type="$2"
-	op_prep="$3"
-	example="$4"
-	branch_name=$(git symbolic-ref -q HEAD)
-	# If there's only one remote, use that in the suggestion
-	remote="<remote>"
-	if test $(git remote | wc -l) = 1
-	then
-		remote=$(git remote)
-	fi
-
-	if test -z "$branch_name"
-	then
-		echo "You are not currently on a branch. Please specify which
-branch you want to $op_type $op_prep. See git-${cmd}(1) for details.
-
-    $example
-"
-	else
-		echo "There is no tracking information for the current branch.
-Please specify which branch you want to $op_type $op_prep.
-See git-${cmd}(1) for details
-
-    $example
-
-If you wish to set tracking information for this branch you can do so with:
-
-    git branch --set-upstream-to=$remote/<branch> ${branch_name#refs/heads/}
-"
-	fi
-	exit 1
-}
diff --git a/git-rebase.sh b/git-rebase.sh
index 55da9db..7b157ec 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -209,6 +209,41 @@ run_pre_rebase_hook () {
 	fi
 }
 
+error_on_missing_default_upstream () {
+	cmd="$1"
+	op_type="$2"
+	op_prep="$3"
+	example="$4"
+	branch_name=$(git symbolic-ref -q HEAD)
+	# If there's only one remote, use that in the suggestion
+	remote="<remote>"
+	if test $(git remote | wc -l) = 1
+	then
+		remote=$(git remote)
+	fi
+
+	if test -z "$branch_name"
+	then
+		echo "You are not currently on a branch. Please specify which
+branch you want to $op_type $op_prep. See git-${cmd}(1) for details.
+
+    $example
+"
+	else
+		echo "There is no tracking information for the current branch.
+Please specify which branch you want to $op_type $op_prep.
+See git-${cmd}(1) for details
+
+    $example
+
+If you wish to set tracking information for this branch you can do so with:
+
+    git branch --set-upstream-to=$remote/<branch> ${branch_name#refs/heads/}
+"
+	fi
+	exit 1
+}
+
 test -f "$apply_dir"/applying &&
 	die "$(gettext "It looks like git-am is in progress. Cannot rebase.")"
 
@@ -446,7 +481,6 @@ then
 		if ! upstream_name=$(git rev-parse --symbolic-full-name \
 			--verify -q @{upstream} 2>/dev/null)
 		then
-			. git-parse-remote
 			error_on_missing_default_upstream "rebase" "rebase" \
 				"against" "git rebase <branch>"
 		fi
diff --git a/git-submodule.sh b/git-submodule.sh
index 36797c3..08c31eb 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -17,7 +17,6 @@ OPTIONS_SPEC=
 SUBDIRECTORY_OK=Yes
 . git-sh-setup
 . git-sh-i18n
-. git-parse-remote
 require_work_tree
 wt_prefix=$(git rev-parse --show-prefix)
 cd_to_toplevel
@@ -37,6 +36,13 @@ prefix=
 custom_name=
 depth=
 
+get_default_remote () {
+	curr_branch=$(git symbolic-ref -q HEAD)
+	curr_branch="${curr_branch#refs/heads/}"
+	origin=$(git config --get "branch.$curr_branch.remote")
+	echo ${origin:-origin}
+}
+
 # The function takes at most 2 arguments. The first argument is the
 # URL that navigates to the submodule origin repo. When relative, this URL
 # is relative to the superproject origin URL repo. The second up_path
-- 
2.4.0.7.gf20f26f

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH 0/6] Make pull a builtin
  2015-05-06  0:00   ` [PATCH 0/6] Make pull a builtin Stephen Robin
                       ` (5 preceding siblings ...)
  2015-05-06  0:00     ` [PATCH 6/6] parse-remote: dismantle git-parse-remote.sh Stephen Robin
@ 2015-05-06  4:27     ` Paul Tan
  6 siblings, 0 replies; 13+ messages in thread
From: Paul Tan @ 2015-05-06  4:27 UTC (permalink / raw)
  To: Stephen Robin; +Cc: Git List

Hi Stephen,

On Wed, May 6, 2015 at 8:00 AM, Stephen Robin <stephen.robin@gmail.com> wrote:
> Hi Paul,
>
> Congratulations on getting your project accepted for GSOC. Here's my
> attempt at implementing pull as a builtin, maybe it will be of some use
> as you look to progress your version.
>
> It's fairly complete in the sense that all the features of git-pull.sh
> should be implemented, the test suite passes, and I've been using it
> myself without issue. At the same time it's some way from finished as
> I've never had time to test it fully and there are parts of the code I'm
> not happy with.
>
> Apologies for not sharing this with you earlier.  I have been too busy
> with paid work to look at any open source projects for some months.
>
> Good luck with your project!
>
> Regards
> Stephen

Thank you so much for sharing your work. I'll be referencing this.

Regards,
Paul

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 5/6] pull: allow interactive rebase
  2015-05-06  0:00     ` [PATCH 5/6] pull: allow interactive rebase Stephen Robin
@ 2015-05-06  5:43       ` Johannes Schindelin
  0 siblings, 0 replies; 13+ messages in thread
From: Johannes Schindelin @ 2015-05-06  5:43 UTC (permalink / raw)
  To: Stephen Robin; +Cc: pyokagan, git

Hi Stephen,

On 2015-05-06 02:00, Stephen Robin wrote:

> Notes:
>     This feature is already present in msysgit.

Please note that it is "Git for Windows". The term "msysgit" just refers to the development environment required to compile Git for Windows 1.x and build its installer.

And yeah, I should contribute my changes already. Having said that, the --rebase=interactive support should not be a part of the initial conversion to a builtin because git-pull.sh as of git.git does not support it.

BTW Thanks for contributing the patch series, even if it is incomplete as you noted. I am sure that Paul (and for that matter, Stefan and I) will find it useful to see how you implemented the builtin `git pull`.

Ciao,
Johannes

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2015-05-06  5:43 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-04-27 19:50 GSoC 2015: 2 accepted proposals Matthieu Moy
2015-04-28  8:58 ` Paul Tan
2015-04-29 15:27   ` Johannes Schindelin
2015-05-06  0:00   ` [PATCH 0/6] Make pull a builtin Stephen Robin
2015-05-06  0:00     ` [PATCH 1/6] merge: tidy up options Stephen Robin
2015-05-06  0:00     ` [PATCH 2/6] merge: move error message given when a merge needs committing to advice.c Stephen Robin
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     ` [PATCH 4/6] pull: reimplement as a builtin in C Stephen Robin
2015-05-06  0:00     ` [PATCH 5/6] pull: allow interactive rebase Stephen Robin
2015-05-06  5:43       ` Johannes Schindelin
2015-05-06  0:00     ` [PATCH 6/6] parse-remote: dismantle git-parse-remote.sh Stephen Robin
2015-05-06  4:27     ` [PATCH 0/6] Make pull a builtin Paul Tan
2015-04-28 12:17 ` GSoC 2015: 2 accepted proposals karthik nayak

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.