Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion Documentation/git-checkout.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,26 @@ of it").
resets _<branch>_ to the start point instead of failing.

`-t`::
`--track[=(direct|inherit)]`::
`--track[=(direct|inherit|fetch)[,...]]`::
When creating a new branch, set up "upstream" configuration. See
`--track` in linkgit:git-branch[1] for details. As a convenience,
--track without -b implies branch creation.
+
The argument is a comma-separated list. `direct` (the default) and
`inherit` select the tracking mode and are mutually exclusive. Adding
`fetch` requests that the remote be fetched before _<start-point>_ is
resolved, so the new branch starts from a fresh tip: when
_<start-point>_ is in _<remote>/<branch>_ form, only that branch is
updated; when _<start-point>_ is a bare _<remote>_ (e.g. `origin`), the
branch named by _<remote>/HEAD_ is updated, and the checkout fails
with a hint to configure that symref if it is not set. The checkout
also fails if no configured remote's fetch refspec maps to
_<start-point>_, or if more than one does (in which case the `fetch`
cannot be unambiguously routed). If the fetch itself fails and the
corresponding remote-tracking ref already exists, a warning is printed
and the checkout proceeds from the existing tip; otherwise the checkout
is aborted.
+
If no `-b` option is given, the name of the new branch will be
derived from the remote-tracking branch, by looking at the local part of
the refspec configured for the corresponding remote, and then stripping
Expand Down
5 changes: 3 additions & 2 deletions Documentation/git-switch.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,11 @@ variable.
attached to a terminal, regardless of `--quiet`.

`-t`::
`--track[ (direct|inherit)]`::
`--track[=(direct|inherit|fetch)[,...]]`::
When creating a new branch, set up "upstream" configuration.
`-c` is implied. See `--track` in linkgit:git-branch[1] for
details.
details, and `--track` in linkgit:git-checkout[1] for the
`fetch` mode.
+
If no `-c` option is given, the name of the new branch will be derived
from the remote-tracking branch, by looking at the local part of the
Expand Down
96 changes: 52 additions & 44 deletions branch.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,9 @@
#include "run-command.h"
#include "strmap.h"

struct tracking {
struct refspec_item spec;
struct string_list *srcs;
const char *remote;
int matches;
};

struct find_tracked_branch_cb {
struct tracking *tracking;
struct string_list ambiguous_remotes;
struct string_list *ambiguous_remotes;
};

static int find_tracked_branch(struct remote *remote, void *priv)
Expand All @@ -45,10 +38,10 @@ static int find_tracked_branch(struct remote *remote, void *priv)
break;
case 2:
/* there are at least two remotes; backfill the first one */
string_list_append(&ftb->ambiguous_remotes, tracking->remote);
string_list_append(ftb->ambiguous_remotes, tracking->remote);
/* fall through */
default:
string_list_append(&ftb->ambiguous_remotes, remote->name);
string_list_append(ftb->ambiguous_remotes, remote->name);
free(tracking->spec.src);
string_list_clear(tracking->srcs, 0);
break;
Expand All @@ -59,6 +52,51 @@ static int find_tracked_branch(struct remote *remote, void *priv)
return 0;
}

void find_tracking_remote_for_ref(struct tracking *tracking,
struct string_list *ambiguous_remotes)
{
struct find_tracked_branch_cb ftb_cb = {
.tracking = tracking,
.ambiguous_remotes = ambiguous_remotes,
};

for_each_remote(find_tracked_branch, &ftb_cb);
}

void advise_ambiguous_fetch_refspec(const char *dst,
const struct string_list *ambiguous_remotes)
{
struct strbuf remotes_advice = STRBUF_INIT;
struct string_list_item *item;

if (!advice_enabled(ADVICE_AMBIGUOUS_FETCH_REFSPEC))
return;

for_each_string_list_item(item, ambiguous_remotes)
/*
* TRANSLATORS: This is a line listing a remote with duplicate
* refspecs in the advice message below. For RTL languages you'll
* probably want to swap the "%s" and leading " " space around.
*/
strbuf_addf(&remotes_advice, _(" %s\n"), item->string);

/*
* TRANSLATORS: The second argument is a \n-delimited list of
* duplicate refspecs, composed above.
*/
advise(_("There are multiple remotes whose fetch refspecs map to the remote\n"
"tracking ref '%s':\n"
"%s"
"\n"
"This is typically a configuration error.\n"
"\n"
"To support setting up tracking branches, ensure that\n"
"different remotes' fetch refspecs map into different\n"
"tracking namespaces."), dst,
remotes_advice.buf);
strbuf_release(&remotes_advice);
}

static int should_setup_rebase(const char *origin)
{
switch (autorebase) {
Expand Down Expand Up @@ -254,11 +292,8 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
{
struct tracking tracking;
struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
struct string_list ambiguous_remotes = STRING_LIST_INIT_DUP;
int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
struct find_tracked_branch_cb ftb_cb = {
.tracking = &tracking,
.ambiguous_remotes = STRING_LIST_INIT_DUP,
};

if (!track)
BUG("asked to set up tracking, but tracking is disallowed");
Expand All @@ -267,7 +302,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
tracking.spec.dst = (char *)orig_ref;
tracking.srcs = &tracking_srcs;
if (track != BRANCH_TRACK_INHERIT)
for_each_remote(find_tracked_branch, &ftb_cb);
find_tracking_remote_for_ref(&tracking, &ambiguous_remotes);
else if (inherit_tracking(&tracking, orig_ref))
goto cleanup;

Expand All @@ -293,34 +328,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
if (tracking.matches > 1) {
int status = die_message(_("not tracking: ambiguous information for ref '%s'"),
orig_ref);
if (advice_enabled(ADVICE_AMBIGUOUS_FETCH_REFSPEC)) {
struct strbuf remotes_advice = STRBUF_INIT;
struct string_list_item *item;

for_each_string_list_item(item, &ftb_cb.ambiguous_remotes)
/*
* TRANSLATORS: This is a line listing a remote with duplicate
* refspecs in the advice message below. For RTL languages you'll
* probably want to swap the "%s" and leading " " space around.
*/
strbuf_addf(&remotes_advice, _(" %s\n"), item->string);

/*
* TRANSLATORS: The second argument is a \n-delimited list of
* duplicate refspecs, composed above.
*/
advise(_("There are multiple remotes whose fetch refspecs map to the remote\n"
"tracking ref '%s':\n"
"%s"
"\n"
"This is typically a configuration error.\n"
"\n"
"To support setting up tracking branches, ensure that\n"
"different remotes' fetch refspecs map into different\n"
"tracking namespaces."), orig_ref,
remotes_advice.buf);
strbuf_release(&remotes_advice);
}
advise_ambiguous_fetch_refspec(orig_ref, &ambiguous_remotes);
exit(status);
}

Expand All @@ -347,7 +355,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,

cleanup:
string_list_clear(&tracking_srcs, 0);
string_list_clear(&ftb_cb.ambiguous_remotes, 0);
string_list_clear(&ambiguous_remotes, 0);
}

int read_branch_desc(struct strbuf *buf, const char *branch_name)
Expand Down
16 changes: 16 additions & 0 deletions branch.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
#ifndef BRANCH_H
#define BRANCH_H

#include "refspec.h"
#include "string-list.h"

struct repository;
struct strbuf;

struct tracking {
struct refspec_item spec;
struct string_list *srcs;
const char *remote;
int matches;
};

void find_tracking_remote_for_ref(struct tracking *tracking,
struct string_list *ambiguous_remotes);

void advise_ambiguous_fetch_refspec(const char *dst,
const struct string_list *ambiguous_remotes);

enum branch_track {
BRANCH_TRACK_UNSPECIFIED = -1,
BRANCH_TRACK_NEVER = 0,
Expand Down
139 changes: 135 additions & 4 deletions builtin/checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
#include "preload-index.h"
#include "read-cache.h"
#include "refs.h"
#include "refspec.h"
#include "remote.h"
#include "repo-settings.h"
#include "resolve-undo.h"
#include "revision.h"
#include "run-command.h"
#include "sequencer.h"
#include "setup.h"
#include "strvec.h"
Expand Down Expand Up @@ -62,6 +64,7 @@ struct checkout_opts {
int count_checkout_paths;
int overlay_mode;
int dwim_new_local_branch;
int fetch;
int discard_changes;
int accept_ref;
int accept_pathspec;
Expand Down Expand Up @@ -115,6 +118,129 @@ struct branch_info {
char *checkout;
};

static void fetch_remote_for_start_point(const char *arg, int quiet)
{
struct strbuf dst = STRBUF_INIT;
struct tracking tracking;
struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
struct string_list ambiguous_remotes = STRING_LIST_INIT_DUP;
struct child_process cmd = CHILD_PROCESS_INIT;
struct object_id oid;
struct remote *named_remote;
int bare_ns;

strbuf_addf(&dst, "refs/remotes/%s", arg);
if (check_refname_format(dst.buf, 0))
die(_("cannot fetch start-point '%s': not a valid "
"remote-tracking name"), arg);

named_remote = remote_get(arg);
bare_ns = !strchr(arg, '/') ||
(named_remote && remote_is_configured(named_remote, 1));
if (bare_ns) {
char *head_path = xstrfmt("refs/remotes/%s/HEAD", arg);
const char *head_target =
refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
head_path,
RESOLVE_REF_READING |
RESOLVE_REF_NO_RECURSE,
&oid, NULL);
if (head_target &&
starts_with(head_target, dst.buf) &&
head_target[dst.len] == '/' &&
!check_refname_format(head_target, 0)) {
strbuf_reset(&dst);
strbuf_addstr(&dst, head_target);
bare_ns = 0;
}
free(head_path);
}

memset(&tracking, 0, sizeof(tracking));
tracking.spec.dst = dst.buf;
tracking.srcs = &tracking_srcs;
find_tracking_remote_for_ref(&tracking, &ambiguous_remotes);

if (tracking.matches > 1) {
int status = die_message(_("cannot fetch start-point '%s': "
"fetch refspecs of multiple remotes "
"map to '%s'"), arg, dst.buf);
advise_ambiguous_fetch_refspec(dst.buf, &ambiguous_remotes);
exit(status);
}

if (!tracking.matches) {
if (bare_ns && named_remote &&
remote_is_configured(named_remote, 1))
die(_("cannot fetch start-point '%s': "
"'refs/remotes/%s/HEAD' is not set; run "
"'git remote set-head %s --auto' to set it"),
arg, arg, arg);
die(_("cannot fetch start-point '%s': no configured remote's "
"fetch refspec matches it"), arg);
}

strvec_push(&cmd.args, "fetch");
if (quiet)
strvec_push(&cmd.args, "--quiet");
strvec_pushl(&cmd.args, tracking.remote,
tracking_srcs.items[0].string, NULL);
cmd.git_cmd = 1;
if (run_command(&cmd)) {
if (!refs_read_ref(get_main_ref_store(the_repository),
dst.buf, &oid))
warning(_("failed to fetch start-point '%s'; "
"using existing '%s'"), arg, dst.buf);
else
die(_("failed to fetch start-point '%s'"), arg);
}

string_list_clear(&tracking_srcs, 0);
string_list_clear(&ambiguous_remotes, 0);
strbuf_release(&dst);
}

static int parse_opt_checkout_track(const struct option *opt,
const char *arg, int unset)
{
struct checkout_opts *opts = opt->value;
struct string_list tokens = STRING_LIST_INIT_DUP;
struct string_list_item *item;
int saw_direct = 0;
int ret = 0;

opts->fetch = 0;
if (unset) {
opts->track = BRANCH_TRACK_NEVER;
return 0;
}
opts->track = BRANCH_TRACK_EXPLICIT;
if (!arg)
return 0;

string_list_split(&tokens, arg, ",", -1);
for_each_string_list_item(item, &tokens) {
if (!strcmp(item->string, "fetch"))
opts->fetch = 1;
else if (!strcmp(item->string, "direct"))
saw_direct = 1;
else if (!strcmp(item->string, "inherit"))
opts->track = BRANCH_TRACK_INHERIT;
else {
ret = error(_("option `%s' expects \"%s\", \"%s\", "
"or \"%s\""),
"--track", "direct", "inherit", "fetch");
goto out;
}
}
if (saw_direct && opts->track == BRANCH_TRACK_INHERIT)
ret = error(_("option `%s' cannot combine \"%s\" and \"%s\""),
"--track", "direct", "inherit");
out:
string_list_clear(&tokens, 0);
return ret;
}

static void branch_info_release(struct branch_info *info)
{
free(info->name);
Expand Down Expand Up @@ -1733,10 +1859,10 @@ static struct option *add_common_switch_branch_options(
{
struct option options[] = {
OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
OPT_CALLBACK_F('t', "track", &opts->track, "(direct|inherit)",
OPT_CALLBACK_F('t', "track", opts, "(direct|inherit|fetch)[,...]",
N_("set branch tracking configuration"),
PARSE_OPT_OPTARG,
parse_opt_tracking_mode),
parse_opt_checkout_track),
OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
PARSE_OPT_NOCOMPLETE),
OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unborn branch")),
Expand Down Expand Up @@ -1941,8 +2067,13 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
opts->dwim_new_local_branch &&
opts->track == BRANCH_TRACK_UNSPECIFIED &&
!opts->new_branch;
int n = parse_branchname_arg(argc, argv, dwim_ok, which_command,
&new_branch_info, opts, &rev);
int n;

if (opts->fetch)
fetch_remote_for_start_point(argv[0], opts->quiet);

n = parse_branchname_arg(argc, argv, dwim_ok, which_command,
&new_branch_info, opts, &rev);
argv += n;
argc -= n;
} else if (!opts->accept_ref && opts->from_treeish) {
Expand Down
Loading
Loading