Skip to content

Commit

Permalink
Merge branch 'ab/checkout-default-remote'
Browse files Browse the repository at this point in the history
"git checkout" and "git worktree add" learned to honor
checkout.defaultRemote when auto-vivifying a local branch out of a
remote tracking branch in a repository with multiple remotes that
have tracking branches that share the same names.

* ab/checkout-default-remote:
  checkout & worktree: introduce checkout.defaultRemote
  checkout: add advice for ambiguous "checkout <branch>"
  builtin/checkout.c: use "ret" variable for return
  checkout: pass the "num_matches" up to callers
  checkout.c: change "unique" member to "num_matches"
  checkout.c: introduce an *_INIT macro
  checkout.h: wrap the arguments to unique_tracking_name()
  checkout tests: index should be clean after dwim checkout
  • Loading branch information
gitster committed Aug 2, 2018
2 parents a81575a + 8d7b558 commit 50858ed
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 16 deletions.
26 changes: 26 additions & 0 deletions Documentation/config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,16 @@ advice.*::
Advice shown when you used linkgit:git-checkout[1] to
move to the detach HEAD state, to instruct how to create
a local branch after the fact.
checkoutAmbiguousRemoteBranchName::
Advice shown when the argument to
linkgit:git-checkout[1] ambiguously resolves to a
remote tracking branch on more than one remote in
situations where an unambiguous argument would have
otherwise caused a remote-tracking branch to be
checked out. See the `checkout.defaultRemote`
configuration variable for how to set a given remote
to used by default in some situations where this
advice would be printed.
amWorkDir::
Advice that shows the location of the patch file when
linkgit:git-am[1] fails to apply it.
Expand Down Expand Up @@ -1104,6 +1114,22 @@ browser.<tool>.path::
browse HTML help (see `-w` option in linkgit:git-help[1]) or a
working repository in gitweb (see linkgit:git-instaweb[1]).

checkout.defaultRemote::
When you run 'git checkout <something>' and only have one
remote, it may implicitly fall back on checking out and
tracking e.g. 'origin/<something>'. This stops working as soon
as you have more than one remote with a '<something>'
reference. This setting allows for setting the name of a
preferred remote that should always win when it comes to
disambiguation. The typical use-case is to set this to
`origin`.
+
Currently this is used by linkgit:git-checkout[1] when 'git checkout
<something>' will checkout the '<something>' branch on another remote,
and by linkgit:git-worktree[1] when 'git worktree add' refers to a
remote branch. This setting might be used for other checkout-like
commands or functionality in the future.

clean.requireForce::
A boolean to make git-clean do nothing unless given -f,
-i or -n. Defaults to true.
Expand Down
9 changes: 9 additions & 0 deletions Documentation/git-checkout.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ equivalent to
$ git checkout -b <branch> --track <remote>/<branch>
------------
+
If the branch exists in multiple remotes and one of them is named by
the `checkout.defaultRemote` configuration variable, we'll use that
one for the purposes of disambiguation, even if the `<branch>` isn't
unique across all remotes. Set it to
e.g. `checkout.defaultRemote=origin` to always checkout remote
branches from there if `<branch>` is ambiguous but exists on the
'origin' remote. See also `checkout.defaultRemote` in
linkgit:git-config[1].
+
You could omit <branch>, in which case the command degenerates to
"check out the current branch", which is a glorified no-op with
rather expensive side-effects to show only the tracking information,
Expand Down
9 changes: 9 additions & 0 deletions Documentation/git-worktree.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ with a matching name, treat as equivalent to:
$ git worktree add --track -b <branch> <path> <remote>/<branch>
------------
+
If the branch exists in multiple remotes and one of them is named by
the `checkout.defaultRemote` configuration variable, we'll use that
one for the purposes of disambiguation, even if the `<branch>` isn't
unique across all remotes. Set it to
e.g. `checkout.defaultRemote=origin` to always checkout remote
branches from there if `<branch>` is ambiguous but exists on the
'origin' remote. See also `checkout.defaultRemote` in
linkgit:git-config[1].
+
If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
then, as a convenience, the new worktree is associated with a branch
(call it `<branch>`) named after `$(basename <path>)`. If `<branch>`
Expand Down
2 changes: 2 additions & 0 deletions advice.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ int advice_add_embedded_repo = 1;
int advice_ignored_hook = 1;
int advice_waiting_for_editor = 1;
int advice_graft_file_deprecated = 1;
int advice_checkout_ambiguous_remote_branch_name = 1;

static int advice_use_color = -1;
static char advice_colors[][COLOR_MAXLEN] = {
Expand Down Expand Up @@ -75,6 +76,7 @@ static struct {
{ "ignoredHook", &advice_ignored_hook },
{ "waitingForEditor", &advice_waiting_for_editor },
{ "graftFileDeprecated", &advice_graft_file_deprecated },
{ "checkoutAmbiguousRemoteBranchName", &advice_checkout_ambiguous_remote_branch_name },

/* make this an alias for backward compatibility */
{ "pushNonFastForward", &advice_push_update_rejected }
Expand Down
1 change: 1 addition & 0 deletions advice.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ extern int advice_add_embedded_repo;
extern int advice_ignored_hook;
extern int advice_waiting_for_editor;
extern int advice_graft_file_deprecated;
extern int advice_checkout_ambiguous_remote_branch_name;

int git_default_advice_config(const char *var, const char *value);
__attribute__((format (printf, 1, 2)))
Expand Down
41 changes: 33 additions & 8 deletions builtin/checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "resolve-undo.h"
#include "submodule-config.h"
#include "submodule.h"
#include "advice.h"

static const char * const checkout_usage[] = {
N_("git checkout [<options>] <branch>"),
Expand Down Expand Up @@ -879,7 +880,8 @@ static int parse_branchname_arg(int argc, const char **argv,
int dwim_new_local_branch_ok,
struct branch_info *new_branch_info,
struct checkout_opts *opts,
struct object_id *rev)
struct object_id *rev,
int *dwim_remotes_matched)
{
struct tree **source_tree = &opts->source_tree;
const char **new_branch = &opts->new_branch;
Expand Down Expand Up @@ -911,8 +913,10 @@ static int parse_branchname_arg(int argc, const char **argv,
* (b) If <something> is _not_ a commit, either "--" is present
* or <something> is not a path, no -t or -b was given, and
* and there is a tracking branch whose name is <something>
* in one and only one remote, then this is a short-hand to
* fork local <something> from that remote-tracking branch.
* in one and only one remote (or if the branch exists on the
* remote named in checkout.defaultRemote), then this is a
* short-hand to fork local <something> from that
* remote-tracking branch.
*
* (c) Otherwise, if "--" is present, treat it like case (1).
*
Expand Down Expand Up @@ -973,7 +977,8 @@ static int parse_branchname_arg(int argc, const char **argv,
recover_with_dwim = 0;

if (recover_with_dwim) {
const char *remote = unique_tracking_name(arg, rev);
const char *remote = unique_tracking_name(arg, rev,
dwim_remotes_matched);
if (remote) {
*new_branch = arg;
arg = remote;
Expand Down Expand Up @@ -1110,6 +1115,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
struct branch_info new_branch_info;
char *conflict_style = NULL;
int dwim_new_local_branch = 1;
int dwim_remotes_matched = 0;
struct option options[] = {
OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
Expand Down Expand Up @@ -1222,7 +1228,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
opts.track == BRANCH_TRACK_UNSPECIFIED &&
!opts.new_branch;
int n = parse_branchname_arg(argc, argv, dwim_ok,
&new_branch_info, &opts, &rev);
&new_branch_info, &opts, &rev,
&dwim_remotes_matched);
argv += n;
argc -= n;
}
Expand Down Expand Up @@ -1264,8 +1271,26 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
}

UNLEAK(opts);
if (opts.patch_mode || opts.pathspec.nr)
return checkout_paths(&opts, new_branch_info.name);
else
if (opts.patch_mode || opts.pathspec.nr) {
int ret = checkout_paths(&opts, new_branch_info.name);
if (ret && dwim_remotes_matched > 1 &&
advice_checkout_ambiguous_remote_branch_name)
advise(_("'%s' matched more than one remote tracking branch.\n"
"We found %d remotes with a reference that matched. So we fell back\n"
"on trying to resolve the argument as a path, but failed there too!\n"
"\n"
"If you meant to check out a remote tracking branch on, e.g. 'origin',\n"
"you can do so by fully qualifying the name with the --track option:\n"
"\n"
" git checkout --track origin/<name>\n"
"\n"
"If you'd like to always have checkouts of an ambiguous <name> prefer\n"
"one remote, e.g. the 'origin' remote, consider setting\n"
"checkout.defaultRemote=origin in your config."),
argv[0],
dwim_remotes_matched);
return ret;
} else {
return checkout_branch(&opts, &new_branch_info);
}
}
4 changes: 2 additions & 2 deletions builtin/worktree.c
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ static const char *dwim_branch(const char *path, const char **new_branch)
if (guess_remote) {
struct object_id oid;
const char *remote =
unique_tracking_name(*new_branch, &oid);
unique_tracking_name(*new_branch, &oid, NULL);
return remote;
}
return NULL;
Expand Down Expand Up @@ -484,7 +484,7 @@ static int add(int ac, const char **av, const char *prefix)

commit = lookup_commit_reference_by_name(branch);
if (!commit) {
remote = unique_tracking_name(branch, &oid);
remote = unique_tracking_name(branch, &oid, NULL);
if (remote) {
new_branch = branch;
branch = remote;
Expand Down
37 changes: 32 additions & 5 deletions checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@
#include "remote.h"
#include "refspec.h"
#include "checkout.h"
#include "config.h"

struct tracking_name_data {
/* const */ char *src_ref;
char *dst_ref;
struct object_id *dst_oid;
int unique;
int num_matches;
const char *default_remote;
char *default_dst_ref;
struct object_id *default_dst_oid;
};

#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0, NULL, NULL, NULL }

static int check_tracking_name(struct remote *remote, void *cb_data)
{
struct tracking_name_data *cb = cb_data;
Expand All @@ -21,24 +27,45 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
free(query.dst);
return 0;
}
cb->num_matches++;
if (cb->default_remote && !strcmp(remote->name, cb->default_remote)) {
struct object_id *dst = xmalloc(sizeof(*cb->default_dst_oid));
cb->default_dst_ref = xstrdup(query.dst);
oidcpy(dst, cb->dst_oid);
cb->default_dst_oid = dst;
}
if (cb->dst_ref) {
free(query.dst);
cb->unique = 0;
return 0;
}
cb->dst_ref = query.dst;
return 0;
}

const char *unique_tracking_name(const char *name, struct object_id *oid)
const char *unique_tracking_name(const char *name, struct object_id *oid,
int *dwim_remotes_matched)
{
struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 };
struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
const char *default_remote = NULL;
if (!git_config_get_string_const("checkout.defaultremote", &default_remote))
cb_data.default_remote = default_remote;
cb_data.src_ref = xstrfmt("refs/heads/%s", name);
cb_data.dst_oid = oid;
for_each_remote(check_tracking_name, &cb_data);
if (dwim_remotes_matched)
*dwim_remotes_matched = cb_data.num_matches;
free(cb_data.src_ref);
if (cb_data.unique)
free((char *)default_remote);
if (cb_data.num_matches == 1) {
free(cb_data.default_dst_ref);
free(cb_data.default_dst_oid);
return cb_data.dst_ref;
}
free(cb_data.dst_ref);
if (cb_data.default_dst_ref) {
oidcpy(oid, cb_data.default_dst_oid);
free(cb_data.default_dst_oid);
return cb_data.default_dst_ref;
}
return NULL;
}
4 changes: 3 additions & 1 deletion checkout.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
* tracking branch. Return the name of the remote if such a branch
* exists, NULL otherwise.
*/
extern const char *unique_tracking_name(const char *name, struct object_id *oid);
extern const char *unique_tracking_name(const char *name,
struct object_id *oid,
int *dwim_remotes_matched);

#endif /* CHECKOUT_H */
Loading

0 comments on commit 50858ed

Please sign in to comment.