From: Corentin BOMPARD <corentin.bomp...@etu.univ-lyon1.fr>

Add the --set-upstream option to git pull/fetch
which lets the user set the upstream configuration
(branch.<current-branch-name>.merge and
branch.<current-branch-name>.remote) for the current branch.

A typical use-case is:

    git clone http://example.com/my-public-fork
    git remote add main http://example.com/project-main-repo
    git pull --set-upstream main master

or, instead of the last line:

    git fetch --set-upstream main master
    git merge # or git rebase

This is mostly equivalent to cloning project-main-repo (which sets
upsteam) and then "git remote add" my-public-fork, but may feel more
natural for people using a hosting system which allows forking from
the web UI.

This functionality is analog to "git push --set-upstream".

Signed-off-by: Corentin BOMPARD <corentin.bomp...@etu.univ-lyon1.fr>
Signed-off-by: Nathan BERBEZIER <nathan.berbez...@etu.univ-lyon1.fr>
Signed-off-by: Pablo CHABANNE <pablo.chaba...@etu.univ-lyon1.fr>
Signed-off-by: Matthieu Moy <g...@matthieu-moy.fr>
Patch-edited-by: Matthieu Moy <g...@matthieu-moy.fr>
---
Only style fixes and slightly improved commit message since v1.
Interdiff below:

  diff --git a/builtin/fetch.c b/builtin/fetch.c
  index 5557ae1c04..54d6b01892 100644
  --- a/builtin/fetch.c
  +++ b/builtin/fetch.c
  @@ -51,7 +51,8 @@ static int fetch_prune_tags_config = -1; /* unspecified */
   static int prune_tags = -1; /* unspecified */
   #define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */
   
  -static int all, append, dry_run, force, keep, multiple, update_head_ok, 
verbosity, deepen_relative, set_upstream;
  +static int all, append, dry_run, force, keep, multiple, update_head_ok;
  +static int verbosity, deepen_relative, set_upstream;
   static int progress = -1;
   static int enable_auto_gc = 1;
   static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
  @@ -1377,12 +1378,14 @@ static int do_fetch(struct transport *transport,
                struct ref *source_ref = NULL;
   
                /*
  -              * We're setting the upstream configuration for the current 
branch. The
  -              * relevent upstream is the fetched branch that is meant to be 
merged with
  -              * the current one, i.e. the one fetched to FETCH_HEAD.
  +              * We're setting the upstream configuration for the
  +              * current branch. The relevent upstream is the
  +              * fetched branch that is meant to be merged with the
  +              * current one, i.e. the one fetched to FETCH_HEAD.
                 *
  -              * When there are several such branches, consider the request 
ambiguous and
  -              * err on the safe side by doing nothing and just emit a 
warning.
  +              * When there are several such branches, consider the
  +              * request ambiguous and err on the safe side by doing
  +              * nothing and just emit a warning.
                 */
                for (rm = ref_map; rm; rm = rm->next) {
                        if (!rm->peer_ref) {
  @@ -1396,17 +1399,17 @@ static int do_fetch(struct transport *transport,
                }
                if (source_ref) {
                        if (!strcmp(source_ref->name, "HEAD") ||
  -                             starts_with(source_ref->name, "refs/heads/")) {
  -                             install_branch_config(0, branch->name,
  +                         starts_with(source_ref->name, "refs/heads/"))
  +                             install_branch_config(0,
  +                                                   branch->name,
                                                      transport->remote->name,
                                                      source_ref->name);
  -                     } else if (starts_with(source_ref->name, 
"refs/remotes/")) {
  +                     else if (starts_with(source_ref->name, "refs/remotes/"))
                                warning(_("not setting upstream for a remote 
remote-tracking branch"));
  -                     } else if (starts_with(source_ref->name, "refs/tags/")) 
{
  +                     else if (starts_with(source_ref->name, "refs/tags/"))
                                warning(_("not setting upstream for a remote 
tag"));
  -                     } else {
  +                     else
                                warning(_("unknown branch type"));
  -                     }
                } else {
                        warning(_("no source branch found.\n"
                                "you need to specify exactly one branch with 
the --set-upstream option."));
  

 Documentation/fetch-options.txt |   7 ++
 builtin/fetch.c                 |  51 ++++++++-
 builtin/pull.c                  |   6 ++
 t/t5553-set-upstream.sh         | 178 ++++++++++++++++++++++++++++++++
 4 files changed, 241 insertions(+), 1 deletion(-)
 create mode 100755 t/t5553-set-upstream.sh

diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt
index 3c9b4f9e09..99df1f3d4e 100644
--- a/Documentation/fetch-options.txt
+++ b/Documentation/fetch-options.txt
@@ -169,6 +169,13 @@ ifndef::git-pull[]
        Disable recursive fetching of submodules (this has the same effect as
        using the `--recurse-submodules=no` option).
 
+--set-upstream::
+       If the remote is fetched successfully, pull and add upstream
+       (tracking) reference, used by argument-less
+       linkgit:git-pull[1] and other commands. For more information,
+       see `branch.<name>.merge` and `branch.<name>.remote` in
+       linkgit:git-config[1].
+
 --submodule-prefix=<path>::
        Prepend <path> to paths printed in informative messages
        such as "Fetching submodule foo".  This option is used
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 717dd14e89..54d6b01892 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -23,6 +23,7 @@
 #include "packfile.h"
 #include "list-objects-filter-options.h"
 #include "commit-reach.h"
+#include "branch.h"
 
 #define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000)
 
@@ -50,7 +51,8 @@ static int fetch_prune_tags_config = -1; /* unspecified */
 static int prune_tags = -1; /* unspecified */
 #define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */
 
-static int all, append, dry_run, force, keep, multiple, update_head_ok, 
verbosity, deepen_relative;
+static int all, append, dry_run, force, keep, multiple, update_head_ok;
+static int verbosity, deepen_relative, set_upstream;
 static int progress = -1;
 static int enable_auto_gc = 1;
 static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
@@ -123,6 +125,8 @@ static struct option builtin_fetch_options[] = {
        OPT__VERBOSITY(&verbosity),
        OPT_BOOL(0, "all", &all,
                 N_("fetch from all remotes")),
+       OPT_BOOL(0, "set-upstream", &set_upstream,
+                N_("set upstream for git pull/fetch")),
        OPT_BOOL('a', "append", &append,
                 N_("append to .git/FETCH_HEAD instead of overwriting")),
        OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
@@ -1367,6 +1371,51 @@ static int do_fetch(struct transport *transport,
                retcode = 1;
                goto cleanup;
        }
+
+       if (set_upstream) {
+               struct branch *branch = branch_get("HEAD");
+               struct ref *rm;
+               struct ref *source_ref = NULL;
+
+               /*
+                * We're setting the upstream configuration for the
+                * current branch. The relevent upstream is the
+                * fetched branch that is meant to be merged with the
+                * current one, i.e. the one fetched to FETCH_HEAD.
+                *
+                * When there are several such branches, consider the
+                * request ambiguous and err on the safe side by doing
+                * nothing and just emit a warning.
+                */
+               for (rm = ref_map; rm; rm = rm->next) {
+                       if (!rm->peer_ref) {
+                               if (source_ref) {
+                                       warning(_("multiple branch detected, 
incompatible with --set-upstream"));
+                                       goto skip;
+                               } else {
+                                       source_ref = rm;
+                               }
+                       }
+               }
+               if (source_ref) {
+                       if (!strcmp(source_ref->name, "HEAD") ||
+                           starts_with(source_ref->name, "refs/heads/"))
+                               install_branch_config(0,
+                                                     branch->name,
+                                                     transport->remote->name,
+                                                     source_ref->name);
+                       else if (starts_with(source_ref->name, "refs/remotes/"))
+                               warning(_("not setting upstream for a remote 
remote-tracking branch"));
+                       else if (starts_with(source_ref->name, "refs/tags/"))
+                               warning(_("not setting upstream for a remote 
tag"));
+                       else
+                               warning(_("unknown branch type"));
+               } else {
+                       warning(_("no source branch found.\n"
+                               "you need to specify exactly one branch with 
the --set-upstream option."));
+               }
+       }
+ skip:
        free_refs(ref_map);
 
        /* if neither --no-tags nor --tags was specified, do automated tag
diff --git a/builtin/pull.c b/builtin/pull.c
index f1eaf6e6ed..d25ff13a60 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -129,6 +129,7 @@ static char *opt_refmap;
 static char *opt_ipv4;
 static char *opt_ipv6;
 static int opt_show_forced_updates = -1;
+static char *set_upstream;
 
 static struct option pull_options[] = {
        /* Shared options */
@@ -243,6 +244,9 @@ static struct option pull_options[] = {
                PARSE_OPT_NOARG),
        OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates,
                 N_("check for forced-updates on all updated branches")),
+       OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL,
+               N_("set upstream for git pull/fetch"),
+               PARSE_OPT_NOARG),
 
        OPT_END()
 };
@@ -556,6 +560,8 @@ static int run_fetch(const char *repo, const char 
**refspecs)
                argv_array_push(&args, "--show-forced-updates");
        else if (opt_show_forced_updates == 0)
                argv_array_push(&args, "--no-show-forced-updates");
+       if (set_upstream)
+               argv_array_push(&args, set_upstream);
 
        if (repo) {
                argv_array_push(&args, repo);
diff --git a/t/t5553-set-upstream.sh b/t/t5553-set-upstream.sh
new file mode 100755
index 0000000000..81975ad8f9
--- /dev/null
+++ b/t/t5553-set-upstream.sh
@@ -0,0 +1,178 @@
+#!/bin/sh
+
+test_description='"git fetch/pull --set-upstream" basic tests.'
+. ./test-lib.sh
+
+check_config () {
+       printf "%s\n" "$2" "$3" >"expect.$1" &&
+       {
+               git config "branch.$1.remote" && git config "branch.$1.merge"
+       } >"actual.$1" &&
+       test_cmp "expect.$1" "actual.$1"
+}
+
+check_config_missing () {
+       test_expect_code 1 git config "branch.$1.remote" &&
+       test_expect_code 1 git config "branch.$1.merge"
+}
+
+clear_config () {
+       for branch in "$@"; do
+               test_might_fail git config --unset-all "branch.$branch.remote"
+               test_might_fail git config --unset-all "branch.$branch.merge"
+       done
+}
+
+ensure_fresh_upstream () {
+       rm -rf parent && git init --bare parent
+}
+
+test_expect_success 'setup bare parent fetch' '
+       ensure_fresh_upstream &&
+       git remote add upstream parent
+'
+
+test_expect_success 'setup commit on master and other fetch' '
+       test_commit one &&
+       git push upstream master &&
+       git checkout -b other &&
+       test_commit two &&
+       git push upstream other
+'
+
+# tests for fetch --set-upstream
+
+test_expect_success 'fetch --set-upstream does not set upstream w/o branch' '
+       clear_config master other &&
+       git checkout master &&
+       git fetch --set-upstream upstream &&
+       check_config_missing master &&
+       check_config_missing other
+'
+
+test_expect_success 'fetch --set-upstream upstream master sets branch master 
but not other' '
+       clear_config master other &&
+       git fetch --set-upstream upstream master &&
+       check_config master upstream refs/heads/master &&
+       check_config_missing other
+'
+
+test_expect_success 'fetch --set-upstream upstream other sets branch other' '
+       clear_config master other &&
+       git fetch --set-upstream upstream other &&
+       check_config master upstream refs/heads/other &&
+       check_config_missing other
+'
+
+test_expect_success 'fetch --set-upstream master:other does not set the branch 
other2' '
+       clear_config other2 &&
+       git fetch --set-upstream upstream master:other2 &&
+       check_config_missing other2
+'
+
+test_expect_success 'fetch --set-upstream http://nosuchdomain.example.com 
fails with invalid url' '
+       # master explicitly not cleared, we check that it is not touched from 
previous value
+       clear_config other other2 &&
+       test_must_fail git fetch --set-upstream http://nosuchdomain.example.com 
&&
+       check_config master upstream refs/heads/other &&
+       check_config_missing other &&
+       check_config_missing other2
+'
+
+test_expect_success 'fetch --set-upstream with valid URL sets upstream to URL' 
'
+       clear_config other other2 &&
+       url="file://'"$PWD"'" &&
+       git fetch --set-upstream "$url" &&
+       check_config master "$url" HEAD &&
+       check_config_missing other &&
+       check_config_missing other2
+'
+
+# tests for pull --set-upstream
+
+test_expect_success 'setup bare parent pull' '
+       git remote rm upstream &&
+       ensure_fresh_upstream &&
+       git remote add upstream parent
+'
+
+test_expect_success 'setup commit on master and other pull' '
+       test_commit three &&
+       git push --tags upstream master &&
+       test_commit four &&
+       git push upstream other
+'
+
+test_expect_success 'pull --set-upstream upstream master sets branch master 
but not other' '
+       clear_config master other &&
+       git pull --set-upstream upstream master &&
+       check_config master upstream refs/heads/master &&
+       check_config_missing other
+'
+
+test_expect_success 'pull --set-upstream master:other2 does not set the branch 
other2' '
+       clear_config other2 &&
+       git pull --set-upstream upstream master:other2 &&
+       check_config_missing other2
+'
+
+test_expect_success 'pull --set-upstream upstream other sets branch master' '
+       clear_config master other &&
+       git pull --set-upstream upstream other &&
+       check_config master upstream refs/heads/other &&
+       check_config_missing other
+'
+
+test_expect_success 'pull --set-upstream upstream tag does not set the tag' '
+       clear_config three &&
+       git pull --tags --set-upstream upstream three &&
+       check_config_missing three
+'
+
+test_expect_success 'pull --set-upstream http://nosuchdomain.example.com fails 
with invalid url' '
+       # master explicitly not cleared, we check that it is not touched from 
previous value
+       clear_config other other2 three &&
+       test_must_fail git pull --set-upstream http://nosuchdomain.example.com 
&&
+       check_config master upstream refs/heads/other &&
+       check_config_missing other &&
+       check_config_missing other2 &&
+       check_config_missing three
+'
+
+test_expect_success 'pull --set-upstream upstream HEAD sets branch HEAD' '
+       clear_config master other &&
+       git pull --set-upstream upstream HEAD &&
+       check_config master upstream HEAD &&
+       git checkout other &&
+       git pull --set-upstream upstream HEAD &&
+       check_config other upstream HEAD
+'
+
+test_expect_success 'pull --set-upstream upstream with more than one branch 
does nothing' '
+       clear_config master three &&
+       git pull --set-upstream upstream master three &&
+       check_config_missing master &&
+       check_config_missing three
+'
+
+test_expect_success 'pull --set-upstream with valid URL sets upstream to URL' '
+       clear_config master other other2 &&
+       git checkout master &&
+       url="file://'"$PWD"'" &&
+       git pull --set-upstream "$url" &&
+       check_config master "$url" HEAD &&
+       check_config_missing other &&
+       check_config_missing other2
+'
+
+test_expect_success 'pull --set-upstream with valid URL and branch sets 
branch' '
+       clear_config master other other2 &&
+       git checkout master &&
+       url="file://'"$PWD"'" &&
+       git pull --set-upstream "$url" master &&
+       check_config master "$url" refs/heads/master &&
+       check_config_missing other &&
+       check_config_missing other2
+'
+
+test_done
-- 
2.20.1.98.gecbdaf0

Reply via email to