From: Erwan Mathoniere <erwan.mathoniere@grenoble-inp.org>
To: git@vger.kernel.org
Cc: jordan.de-gea@grenoble-inp.org, samuel.groot@grenoble-inp.org,
erwan.mathoniere@grenoble-inp.org, tom.russello@grenoble-inp.org,
gitster@pobox.com, Matthieu Moy <matthieu.moy@grenoble-inp.fr>
Subject: [RFC/PATCH v2] pull: add --set-upstream
Date: Mon, 6 Jun 2016 11:34:37 +0200 [thread overview]
Message-ID: <20160606093437.1992-1-erwan.mathoniere@grenoble-inp.org> (raw)
In-Reply-To: <20160525152528.22202-1-erwan.mathoniere@grenoble-inp.org>
Implement `git pull [--set-upstream | -u] <remote> <refspecs>` that set
tracking to the remote branch the user just pulled from.
After successfully pulling from `<remote>`, for each `<refspec>`
described in format `<remote_branch>:<local_branch>`, set
`branch.<local_branch>.remote` to `<remote>` and
`branch.<local_branch>.merge` to `refs/heads/<remote_branch>`. If
`<refspec>` lacks `<local_branch>` in the previous format or directly
refers to a branch, use the current branch as `<local_branch>` in the
above configuration setting.
`git push` has already its `--set-upstream`, it makes sense to have its
symmetrical for `git pull`.
For a beginner, when trying to use argumentless `git pull` without
tracking information set, advising to use
`git branch --set-upstream-to` to set upstream can be quite confusing.
Using this `git pull --set-upstream` is easier and more natural.
Signed-off-by: Erwan Mathoniere <erwan.mathoniere@grenoble-inp.org>
Signed-off-by: Jordan De Gea <jordan.de-gea@grenoble-inp.org>
Signed-off-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
---
Changes from v1:
- Code reshaped to :
* warn + no-op when pulling from or to something that isn't a branch
or a configured remote
* set upstream only after successfully merging/rebasing
- More relevant documentation
- Tests reshaped to be more independent from each others
- More tests (tags, detached heads, non-configured remote...)
For now, the documentation is quite hard to understand, but I didn't
figure how to explain without using too technical words. Should it stay
as it is or should I write something similar the above commit message?
Allowing to set non-configured repository as upstream isn't easy to
handle since the type of refspec must be checked and this is done by
verifying the existence of the remote-tracking branch at
`refs/remotes/<remote>/<branch>`.
Documentation/git-pull.txt | 18 +++++
builtin/pull.c | 106 ++++++++++++++++++++++++++++-
t/t5544-pull-upstream.sh | 164 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 285 insertions(+), 3 deletions(-)
create mode 100755 t/t5544-pull-upstream.sh
diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt
index d033b25..6ae5e58 100644
--- a/Documentation/git-pull.txt
+++ b/Documentation/git-pull.txt
@@ -93,6 +93,24 @@ OPTIONS
has to be called afterwards to bring the work tree up to date with the
merge result.
+-u::
+--set-upstream::
+ After successfully pulling from explicitly given <repository> and
+ <refspecs>, set the configuration of the local branches pulled on, so
+ that each one tracks the remote branch pulled from. If a configuration
+ already exists, it is overwriten. For example, with `git pull -u origin
+ branch` the current branch will track `branch` from `origin`.
++
+If two or more branches are pulled on the same local branch, only the last one
+in arguments will be tracked.
++
+The given <repository> must be a configured remote. Can only set tracking to
+remote branches (e.g. can't set upstream to remote HEAD).
++
+Works symmetrically as `--set-upstream` for linkgit:git-push[1]. Allow using
+argumentless linkgit:git-pull[1] and other commands. For more information, see
+`branch.<name>.merge` in linkgit:git-config[1].
+
Options related to merging
~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/builtin/pull.c b/builtin/pull.c
index 1d7333c..d9823d5 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -17,6 +17,7 @@
#include "revision.h"
#include "tempfile.h"
#include "lockfile.h"
+#include "branch.h"
enum rebase_type {
REBASE_INVALID = -1,
@@ -109,6 +110,9 @@ static char *opt_unshallow;
static char *opt_update_shallow;
static char *opt_refmap;
+/* Options about upstream */
+static int opt_set_upstream;
+
static struct option pull_options[] = {
/* Shared options */
OPT__VERBOSITY(&opt_verbosity),
@@ -116,6 +120,9 @@ static struct option pull_options[] = {
N_("force progress reporting"),
PARSE_OPT_NOARG),
+ /* Options about upstream */
+ OPT_BOOL('u', "set-upstream", &opt_set_upstream, N_("set upstream for git pull/status")),
+
/* Options passed to git-merge or git-rebase */
OPT_GROUP(N_("Options related to merging")),
{ OPTION_CALLBACK, 'r', "rebase", &opt_rebase,
@@ -497,6 +504,10 @@ static void NORETURN die_no_merge_candidates(const char *repo, const char **refs
fprintf(stderr, "\n");
fprintf_ln(stderr, _("If you wish to set tracking information for this branch you can do so with:"));
fprintf(stderr, "\n");
+ fprintf_ln(stderr, " git pull -u %s %s", _("<remote>"), _("<branch>"));
+ fprintf(stderr, "\n");
+ fprintf_ln(stderr, _("or"));
+ fprintf(stderr, "\n");
fprintf_ln(stderr, " git branch --set-upstream-to=%s/%s %s\n",
remote_name, _("<branch>"), curr_branch->name);
} else
@@ -829,8 +840,92 @@ static int run_rebase(const unsigned char *curr_head,
return ret;
}
+static void set_pull_upstream(const char *repo, const char **refspecs_name)
+{
+ unsigned char sha1[GIT_SHA1_RAWSZ];
+ struct refspec *refspec;
+ struct branch *branch;
+ struct remote *remote;
+ struct strbuf buf;
+ struct refspec tracking_refspec;
+ int nr_refspec, i, flags;
+
+ if (!repo) {
+ warning(_("a remote must be specified to set the upstream"));
+ return;
+ }
+
+ remote = remote_get(repo);
+ if (!remote) {
+ warning(_("cannot set upstream: "
+ "'%s' is not a configured remote"), repo);
+ }
+
+ for (nr_refspec = 0; refspecs_name[nr_refspec] != NULL; nr_refspec++)
+ ; /* just counting */
+
+ if (nr_refspec == 0) {
+ warning(_("a remote branch must be specified to set the upstream"));
+ return;
+ }
+
+ strbuf_init(&buf, 0);
+ refspec = parse_fetch_refspec(nr_refspec, refspecs_name);
+
+ for (i = 0; i < nr_refspec; i++) {
+ if (refspec[i].pattern) {
+ warning(_("upstream cannot be set for patterns"));
+ continue;
+ }
+
+ branch = branch_get(refspec[i].dst);
+ if (!branch || !ref_exists(branch->refname)) {
+ if (!refspec[i].dst || !*refspec[i].dst)
+ warning(_("could not set upstream of HEAD when "
+ "it does not point to any branch."));
+ else
+ warning(_("cannot set upstream: "
+ "'%s' is not a branch"), refspec[i].dst);
+
+ continue;
+ }
+
+ if (!refspec[i].src || !*refspec[i].src) {
+ warning(_("remote branch must be specified "
+ "to set upstream"));
+ continue;
+ }
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "refs/heads/%s", refspec[i].src);
+ memset(&tracking_refspec, 0, sizeof(struct refspec));
+ tracking_refspec.src = buf.buf;
+
+ if (remote_find_tracking(remote, &tracking_refspec)) {
+ warning(_("cannot set upstream: "
+ "no such remote branch '%s'"), refspec[i].src);
+ continue;
+ }
+
+ if (!resolve_ref_unsafe(tracking_refspec.dst, RESOLVE_REF_READING,
+ sha1, &flags)
+ || (flags & REF_ISSYMREF)) {
+ warning(_("cannot set upstream: "
+ "no such remote branch '%s'"), refspec[i].src);
+ continue;
+ }
+
+ install_branch_config((opt_verbosity >= 0 ? BRANCH_CONFIG_VERBOSE : 0),
+ branch->name, repo, buf.buf);
+ }
+
+ free_refspec(nr_refspec, refspec);
+ strbuf_release(&buf);
+}
+
int cmd_pull(int argc, const char **argv, const char *prefix)
{
+ int ret;
const char *repo, **refspecs;
struct sha1_array merge_heads = SHA1_ARRAY_INIT;
unsigned char orig_head[GIT_SHA1_RAWSZ], curr_head[GIT_SHA1_RAWSZ];
@@ -918,11 +1013,16 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
if (is_null_sha1(orig_head)) {
if (merge_heads.nr > 1)
die(_("Cannot merge multiple branches into empty head."));
- return pull_into_void(*merge_heads.sha1, curr_head);
+ ret = pull_into_void(*merge_heads.sha1, curr_head);
} else if (opt_rebase) {
if (merge_heads.nr > 1)
die(_("Cannot rebase onto multiple branches."));
- return run_rebase(curr_head, *merge_heads.sha1, rebase_fork_point);
+ ret = run_rebase(curr_head, *merge_heads.sha1, rebase_fork_point);
} else
- return run_merge();
+ ret = run_merge();
+
+ if (opt_set_upstream && ret < 128)
+ set_pull_upstream(repo, refspecs);
+
+ return ret;
}
diff --git a/t/t5544-pull-upstream.sh b/t/t5544-pull-upstream.sh
new file mode 100755
index 0000000..59f009d
--- /dev/null
+++ b/t/t5544-pull-upstream.sh
@@ -0,0 +1,164 @@
+#!/bin/sh
+
+test_description='pull with --set-upstream'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
+
+test_config_unchanged () {
+ git config --list --local >original
+ "$@"
+ git config --list --local >modified
+ test_cmp original modified
+}
+
+check_config () {
+ (echo "$2"; echo "$3") >expect
+ (git config branch.$1.remote
+ git config branch.$1.merge) >actual
+ test_cmp expect actual
+}
+
+test_expect_success 'setup repos' '
+ git init parent &&
+ (
+ cd parent &&
+ echo content >file &&
+ git add file &&
+ git commit -am one &&
+ git tag initial_tag &&
+ git checkout -b master2 &&
+ echo content_modified >file &&
+ git commit -am "file modification"
+ git checkout -b other master &&
+ echo content >file2 &&
+ git add file2 &&
+ git commit -am two &&
+ git checkout -b other2
+ ) &&
+ git init step_parent &&
+ (
+ cd step_parent &&
+ echo step_content >step_file &&
+ git add step_file &&
+ git commit -m step_one
+ ) &&
+ git remote add upstream parent &&
+ git remote add step_upstream step_parent &&
+ git pull upstream master &&
+ git branch other
+'
+
+test_expect_success 'pull -u master' '
+ git pull -u upstream master &&
+ check_config master upstream refs/heads/master
+'
+
+test_expect_success 'pull -u takes the last branch as upstream' '
+ test_might_fail git config --unset branch.master.merge &&
+ test_might_fail git config --unset branch.master.remote &&
+ git pull -u upstream master master2 &&
+ check_config master upstream refs/heads/master2
+'
+
+test_expect_success 'pull -u master:other' '
+ git pull -u upstream master:other &&
+ check_config other upstream refs/heads/master
+'
+
+
+test_expect_success 'pull -u tracking non-local branch' '
+ git checkout -b master2_local &&
+ git pull -u upstream master2 &&
+ check_config master2_local upstream refs/heads/master2
+'
+
+
+test_expect_success 'pull -u --dry-run other:other' '
+ git config branch.other.merge refs/heads/master &&
+ git config branch.other.remote upstream &&
+ git pull -u --dry-run upstream other:other &&
+ check_config other upstream refs/heads/master
+'
+
+test_expect_success 'pull -u master2:master2 master:other' '
+ git branch master2 &&
+ git pull -u upstream master2:master2 master:other &&
+ check_config master2 upstream refs/heads/master2 &&
+ check_config other upstream refs/heads/master
+'
+
+test_expect_success 'pull -u HEAD does not track origin/HEAD nor remote HEAD on origin' '
+ git checkout -b other_head master &&
+ git fetch upstream other &&
+ git remote set-head upstream other &&
+ test_config_unchanged git pull -u upstream HEAD
+'
+
+test_expect_success 'pull -u sets upstream when merge conflicts occur' '
+ git checkout -b master_edited master &&
+ echo conflict >file2 &&
+ git add file2 &&
+ git commit -am conflict &&
+ test_must_fail git pull -u upstream other &&
+ git rm file2 &&
+ git commit &&
+ check_config master_edited upstream refs/heads/other
+'
+
+test_expect_success 'pull -u should not work when merging unrelated histories' '
+ git checkout master &&
+ test_config_unchanged test_must_fail git pull -u step_parent master
+'
+
+test_expect_success 'pull -u sets upstream after rebasing' '
+ git checkout -b other_rebased other &&
+ git config branch.other_rebased.merge master &&
+ ls .git/refs/remotes/upstream &&
+ git pull -r -u upstream master2 &&
+ git branch --set-upstream-to=upstream/master2 &&
+ ls .git/refs/remotes/upstream &&
+ check_config other_rebased upstream refs/heads/master2
+'
+
+test_expect_success 'pull -u refs/heads/*:refs/remotes/origin/* should not work' '
+ git checkout master &&
+ test_config_unchanged git pull -u upstream "refs/heads/*:refs/remotes/upstream/*"
+'
+
+test_expect_success 'pull -u master:refs/remotes/origin/master should not work' '
+ test_config_unchanged git pull -u upstream master:refs/remotes/upstream/master
+'
+
+test_expect_success 'pull -u with a tag should not work' '
+ git checkout master &&
+ test_config_unchanged git pull -u upstream initial_tag
+'
+
+test_expect_success 'pull -u on detached head should not work' '
+ git checkout HEAD^0 &&
+ test_config_unchanged git pull -u upstream master2 &&
+ git checkout -
+'
+
+test_expect_success 'pull -u with an unconfigured remote should not work' '
+ git checkout master &&
+ git clone parent unconfigured_parent &&
+ test_config_unchanged git pull -u "$(pwd)/unconfigured_parent" other2
+'
+
+test_expect_success 'pull -u with a modified remote.fetch' '
+ git remote add origin_modified parent &&
+ git push upstream master:custom_branch &&
+ git config remote.origin_modified.fetch "+refs/heads/*:refs/remotes/custom/*" &&
+ git checkout -b lonely_branch master &&
+ git pull -u origin_modified custom_branch &&
+ check_config lonely_branch origin_modified refs/heads/custom_branch
+'
+
+test_expect_success TTY 'quiet pull' '
+ git checkout master &&
+ test_terminal git pull -u --quiet upstream master 2>&1 | tee output &&
+ test_cmp /dev/null output
+'
+
+test_done
--
2.8.2.662.g22d3535.dirty
next prev parent reply other threads:[~2016-06-06 9:36 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-05-25 15:25 [RFC/PATCH] pull: set-upstream implementation Erwan Mathoniere
2016-05-25 18:09 ` Junio C Hamano
2016-05-29 20:00 ` Erwan Mathoniere
2016-06-06 9:34 ` Erwan Mathoniere [this message]
2016-06-06 15:54 ` [RFC/PATCH v2] pull: add --set-upstream Matthieu Moy
2016-06-06 19:06 ` Junio C Hamano
2016-06-07 7:06 ` Matthieu Moy
2016-06-07 12:54 ` Erwan Mathoniere
2016-06-07 13:15 ` Erwan Mathoniere
2016-06-07 12:43 ` Erwan Mathoniere
2016-06-06 16:29 ` Philip Oakley
2016-06-07 13:42 ` Erwan Mathoniere
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: http://vger.kernel.org/majordomo-info.html
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20160606093437.1992-1-erwan.mathoniere@grenoble-inp.org \
--to=erwan.mathoniere@grenoble-inp.org \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=jordan.de-gea@grenoble-inp.org \
--cc=matthieu.moy@grenoble-inp.fr \
--cc=samuel.groot@grenoble-inp.org \
--cc=tom.russello@grenoble-inp.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://80x24.org/mirrors/git.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).