Browse Source

git-branch, git-checkout: autosetup for remote branch tracking

In order to track and build on top of a branch 'topic' you track from
your upstream repository, you often would end up doing this sequence:

  git checkout -b mytopic origin/topic
  git config --add branch.mytopic.remote origin
  git config --add branch.mytopic.merge refs/heads/topic

This would first fork your own 'mytopic' branch from the 'topic'
branch you track from the 'origin' repository; then it would set up two
configuration variables so that 'git pull' without parameters does the
right thing while you are on your own 'mytopic' branch.

This commit adds a --track option to git-branch, so that "git
branch --track mytopic origin/topic" performs the latter two actions
when creating your 'mytopic' branch.

If the configuration variable branch.autosetupmerge is set to true, you
do not have to pass the --track option explicitly; further patches in
this series allow setting the variable with a "git remote add" option.
The configuration variable is off by default, and there is a --no-track
option to countermand it even if the variable is set.

Signed-off-by: Paolo Bonzini  <bonzini@gnu.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
maint
Paolo Bonzini 18 years ago committed by Junio C Hamano
parent
commit
0746d19a82
  1. 9
      Documentation/git-branch.txt
  2. 15
      Documentation/git-checkout.txt
  3. 140
      builtin-branch.c
  4. 1
      cache.h
  5. 17
      git-checkout.sh
  6. 65
      t/t3200-branch.sh
  7. 18
      trace.c

9
Documentation/git-branch.txt

@ -10,7 +10,7 @@ SYNOPSIS
[verse] [verse]
'git-branch' [--color | --no-color] [-r | -a] 'git-branch' [--color | --no-color] [-r | -a]
[-v [--abbrev=<length> | --no-abbrev]] [-v [--abbrev=<length> | --no-abbrev]]
'git-branch' [-l] [-f] <branchname> [<start-point>] 'git-branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>]
'git-branch' (-m | -M) [<oldbranch>] <newbranch> 'git-branch' (-m | -M) [<oldbranch>] <newbranch>
'git-branch' (-d | -D) [-r] <branchname>... 'git-branch' (-d | -D) [-r] <branchname>...


@ -26,6 +26,13 @@ It will start out with a head equal to the one given as <start-point>.
If no <start-point> is given, the branch will be created with a head If no <start-point> is given, the branch will be created with a head
equal to that of the currently checked out branch. equal to that of the currently checked out branch.


When a local branch is started off a remote branch, git can setup the
branch so that gitlink:git-pull[1] will appropriately merge from that
remote branch. If this behavior is desired, it is possible to make it
the default using the global `branch.autosetupmerge` configuration
flag. Otherwise, it can be chosen per-branch using the `--track`
and `--no-track` options.

With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>. With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>.
If <oldbranch> had a corresponding reflog, it is renamed to match If <oldbranch> had a corresponding reflog, it is renamed to match
<newbranch>, and a reflog entry is created to remember the branch <newbranch>, and a reflog entry is created to remember the branch

15
Documentation/git-checkout.txt

@ -8,7 +8,7 @@ git-checkout - Checkout and switch to a branch
SYNOPSIS SYNOPSIS
-------- --------
[verse] [verse]
'git-checkout' [-q] [-f] [-b <new_branch> [-l]] [-m] [<branch>] 'git-checkout' [-q] [-f] [-b [--track | --no-track] <new_branch> [-l]] [-m] [<branch>]
'git-checkout' [<tree-ish>] <paths>... 'git-checkout' [<tree-ish>] <paths>...


DESCRIPTION DESCRIPTION
@ -18,7 +18,8 @@ When <paths> are not given, this command switches branches by
updating the index and working tree to reflect the specified updating the index and working tree to reflect the specified
branch, <branch>, and updating HEAD to be <branch> or, if branch, <branch>, and updating HEAD to be <branch> or, if
specified, <new_branch>. Using -b will cause <new_branch> to specified, <new_branch>. Using -b will cause <new_branch> to
be created. be created; in this case you can use the --track or --no-track
options, which will be passed to `git branch`.


When <paths> are given, this command does *not* switch When <paths> are given, this command does *not* switch
branches. It updates the named paths in the working tree from branches. It updates the named paths in the working tree from
@ -45,6 +46,16 @@ OPTIONS
by gitlink:git-check-ref-format[1]. Some of these checks by gitlink:git-check-ref-format[1]. Some of these checks
may restrict the characters allowed in a branch name. may restrict the characters allowed in a branch name.


--track::
When -b is given and a branch is created off a remote branch,
setup so that git-pull will automatically retrieve data from
the remote branch.

--no-track::
When -b is given and a branch is created off a remote branch,
force that git-pull will automatically retrieve data from
the remote branch independent of the configuration settings.

-l:: -l::
Create the new branch's ref log. This activates recording of Create the new branch's ref log. This activates recording of
all changes to made the branch ref, enabling use of date all changes to made the branch ref, enabling use of date

140
builtin-branch.c

@ -12,7 +12,7 @@
#include "builtin.h" #include "builtin.h"


static const char builtin_branch_usage[] = static const char builtin_branch_usage[] =
"git-branch [-r] (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [--color | --no-color] [-r | -a] [-v [--abbrev=<length> | --no-abbrev]]"; "git-branch [-r] (-d | -D) <branchname> | [--track | --no-track] [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [--color | --no-color] [-r | -a] [-v [--abbrev=<length> | --no-abbrev]]";


#define REF_UNKNOWN_TYPE 0x00 #define REF_UNKNOWN_TYPE 0x00
#define REF_LOCAL_BRANCH 0x01 #define REF_LOCAL_BRANCH 0x01
@ -22,6 +22,8 @@ static const char builtin_branch_usage[] =
static const char *head; static const char *head;
static unsigned char head_sha1[20]; static unsigned char head_sha1[20];


static int branch_track_remotes;

static int branch_use_color; static int branch_use_color;
static char branch_colors[][COLOR_MAXLEN] = { static char branch_colors[][COLOR_MAXLEN] = {
"\033[m", /* reset */ "\033[m", /* reset */
@ -64,6 +66,9 @@ int git_branch_config(const char *var, const char *value)
color_parse(value, var, branch_colors[slot]); color_parse(value, var, branch_colors[slot]);
return 0; return 0;
} }
if (!strcmp(var, "branch.autosetupmerge"))
branch_track_remotes = git_config_bool(var, value);

return git_default_config(var, value); return git_default_config(var, value);
} }


@ -309,14 +314,109 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev)
free_ref_list(&ref_list); free_ref_list(&ref_list);
} }


static char *config_repo;
static char *config_remote;
static const char *start_ref;
static int start_len;
static int base_len;

static int get_remote_branch_name(const char *value)
{
const char *colon;
const char *end;

if (*value == '+')
value++;

colon = strchr(value, ':');
if (!colon)
return 0;

end = value + strlen(value);

/* Try an exact match first. */
if (!strcmp(colon + 1, start_ref)) {
/* Truncate the value before the colon. */
nfasprintf(&config_repo, "%.*s", colon - value, value);
return 1;
}

/* Try with a wildcard match now. */
if (end - value > 2 && end[-2] == '/' && end[-1] == '*' &&
colon - value > 2 && colon[-2] == '/' && colon[-1] == '*' &&
(end - 2) - (colon + 1) == base_len &&
!strncmp(colon + 1, start_ref, base_len)) {
/* Replace the star with the remote branch name. */
nfasprintf(&config_repo, "%.*s%s",
(colon - 2) - value, value,
start_ref + base_len);
return 1;
}

return 0;
}

static int get_remote_config(const char *key, const char *value)
{
const char *var;
if (prefixcmp(key, "remote."))
return 0;

var = strrchr(key, '.');
if (var == key + 6)
return 0;

if (!strcmp(var, ".fetch") && get_remote_branch_name(value))
nfasprintf(&config_remote, "%.*s", var - (key + 7), key + 7);

return 0;
}

static void set_branch_defaults(const char *name, const char *real_ref)
{
char key[1024];
const char *slash = strrchr(real_ref, '/');

if (!slash)
return;

start_ref = real_ref;
start_len = strlen(real_ref);
base_len = slash - real_ref;
git_config(get_remote_config);

if (config_repo && config_remote) {
if (sizeof(key) <=
snprintf(key, sizeof(key), "branch.%s.remote", name))
die("what a long branch name you have!");
git_config_set(key, config_remote);

/*
* We do not have to check if we have enough space for
* the 'merge' key, since it's shorter than the
* previous 'remote' key, which we already checked.
*/
snprintf(key, sizeof(key), "branch.%s.merge", name);
git_config_set(key, config_repo);

printf("Branch %s set up to track remote branch %s.\n",
name, real_ref);
}

if (config_repo)
free(config_repo);
if (config_remote)
free(config_remote);
}

static void create_branch(const char *name, const char *start_name, static void create_branch(const char *name, const char *start_name,
unsigned char *start_sha1, unsigned char *start_sha1,
int force, int reflog) int force, int reflog, int track)
{ {
struct ref_lock *lock; struct ref_lock *lock;
struct commit *commit; struct commit *commit;
unsigned char sha1[20]; unsigned char sha1[20];
char ref[PATH_MAX], msg[PATH_MAX + 20]; char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20];
int forcing = 0; int forcing = 0;


snprintf(ref, sizeof ref, "refs/heads/%s", name); snprintf(ref, sizeof ref, "refs/heads/%s", name);
@ -331,10 +431,14 @@ static void create_branch(const char *name, const char *start_name,
forcing = 1; forcing = 1;
} }


if (start_sha1) if (start_sha1) {
/* detached HEAD */ /* detached HEAD */
hashcpy(sha1, start_sha1); hashcpy(sha1, start_sha1);
else if (get_sha1(start_name, sha1)) real_ref = NULL;
}
else if (dwim_ref(start_name, strlen(start_name), sha1, &real_ref) > 1)
die("Ambiguous object name: '%s'.", start_name);
else if (real_ref == NULL)
die("Not a valid object name: '%s'.", start_name); die("Not a valid object name: '%s'.", start_name);


if ((commit = lookup_commit_reference(sha1)) == NULL) if ((commit = lookup_commit_reference(sha1)) == NULL)
@ -355,8 +459,17 @@ static void create_branch(const char *name, const char *start_name,
snprintf(msg, sizeof msg, "branch: Created from %s", snprintf(msg, sizeof msg, "branch: Created from %s",
start_name); start_name);


/* When branching off a remote branch, set up so that git-pull
automatically merges from there. So far, this is only done for
remotes registered via .git/config. */
if (real_ref && track)
set_branch_defaults(name, real_ref);

if (write_ref_sha1(lock, sha1, msg) < 0) if (write_ref_sha1(lock, sha1, msg) < 0)
die("Failed to write ref: %s.", strerror(errno)); die("Failed to write ref: %s.", strerror(errno));

if (real_ref)
free(real_ref);
} }


static void rename_branch(const char *oldname, const char *newname, int force) static void rename_branch(const char *oldname, const char *newname, int force)
@ -398,11 +511,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
int delete = 0, force_delete = 0, force_create = 0; int delete = 0, force_delete = 0, force_create = 0;
int rename = 0, force_rename = 0; int rename = 0, force_rename = 0;
int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0; int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
int reflog = 0; int reflog = 0, track;
int kinds = REF_LOCAL_BRANCH; int kinds = REF_LOCAL_BRANCH;
int i; int i;


git_config(git_branch_config); git_config(git_branch_config);
track = branch_track_remotes;


for (i = 1; i < argc; i++) { for (i = 1; i < argc; i++) {
const char *arg = argv[i]; const char *arg = argv[i];
@ -413,6 +527,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
i++; i++;
break; break;
} }
if (!strcmp(arg, "--track")) {
track = 1;
continue;
}
if (!strcmp(arg, "--no-track")) {
track = 0;
continue;
}
if (!strcmp(arg, "-d")) { if (!strcmp(arg, "-d")) {
delete = 1; delete = 1;
continue; continue;
@ -499,9 +621,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
else if (rename && (i == argc - 2)) else if (rename && (i == argc - 2))
rename_branch(argv[i], argv[i + 1], force_rename); rename_branch(argv[i], argv[i + 1], force_rename);
else if (i == argc - 1) else if (i == argc - 1)
create_branch(argv[i], head, head_sha1, force_create, reflog); create_branch(argv[i], head, head_sha1, force_create, reflog,
track);
else if (i == argc - 2) else if (i == argc - 2)
create_branch(argv[i], argv[i+1], NULL, force_create, reflog); create_branch(argv[i], argv[i+1], NULL, force_create, reflog,
track);
else else
usage(builtin_branch_usage); usage(builtin_branch_usage);



1
cache.h

@ -480,6 +480,7 @@ extern struct tag *alloc_tag_node(void);
extern void alloc_report(void); extern void alloc_report(void);


/* trace.c */ /* trace.c */
extern int nfasprintf(char **str, const char *fmt, ...);
extern int nfvasprintf(char **str, const char *fmt, va_list va); extern int nfvasprintf(char **str, const char *fmt, va_list va);
extern void trace_printf(const char *format, ...); extern void trace_printf(const char *format, ...);
extern void trace_argv_printf(const char **argv, int count, const char *format, ...); extern void trace_argv_printf(const char **argv, int count, const char *format, ...);

17
git-checkout.sh

@ -12,6 +12,7 @@ new=
new_name= new_name=
force= force=
branch= branch=
track=
newbranch= newbranch=
newbranch_log= newbranch_log=
merge= merge=
@ -33,7 +34,10 @@ while [ "$#" != "0" ]; do
die "git checkout: we do not like '$newbranch' as a branch name." die "git checkout: we do not like '$newbranch' as a branch name."
;; ;;
"-l") "-l")
newbranch_log=1 newbranch_log=-l
;;
"--track"|"--no-track")
track="$arg"
;; ;;
"-f") "-f")
force=1 force=1
@ -85,6 +89,11 @@ while [ "$#" != "0" ]; do
esac esac
done done


case "$new_branch,$track" in
,--*)
die "git checkout: --track and --no-track require -b"
esac

case "$force$merge" in case "$force$merge" in
11) 11)
die "git checkout: -f and -m are incompatible" die "git checkout: -f and -m are incompatible"
@ -235,11 +244,7 @@ fi
# #
if [ "$?" -eq 0 ]; then if [ "$?" -eq 0 ]; then
if [ "$newbranch" ]; then if [ "$newbranch" ]; then
if [ "$newbranch_log" ]; then git-branch $track $newbranch_log "$newbranch" "$new_name" || exit
mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$newbranch")
touch "$GIT_DIR/logs/refs/heads/$newbranch"
fi
git-update-ref -m "checkout: Created from $new_name" "refs/heads/$newbranch" $new || exit
branch="$newbranch" branch="$newbranch"
fi fi
if test -n "$branch" if test -n "$branch"

65
t/t3200-branch.sh

@ -47,17 +47,6 @@ test_expect_success \
test ! -f .git/refs/heads/d/e/f && test ! -f .git/refs/heads/d/e/f &&
test ! -f .git/logs/refs/heads/d/e/f' test ! -f .git/logs/refs/heads/d/e/f'


cat >expect <<EOF
0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 checkout: Created from master
EOF
test_expect_success \
'git checkout -b g/h/i -l should create a branch and a log' \
'GIT_COMMITTER_DATE="2005-05-26 23:30" \
git-checkout -b g/h/i -l master &&
test -f .git/refs/heads/g/h/i &&
test -f .git/logs/refs/heads/g/h/i &&
diff expect .git/logs/refs/heads/g/h/i'

test_expect_success \ test_expect_success \
'git branch j/k should work after branch j has been deleted' \ 'git branch j/k should work after branch j has been deleted' \
'git-branch j && 'git-branch j &&
@ -117,4 +106,58 @@ test_expect_failure \
ln -s real-u .git/logs/refs/heads/u && ln -s real-u .git/logs/refs/heads/u &&
git-branch -m u v' git-branch -m u v'


test_expect_success 'test tracking setup via --track' \
'git-config remote.local.url . &&
git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
(git-show-ref -q refs/remotes/local/master || git-fetch local) &&
git-branch --track my1 local/master &&
test $(git-config branch.my1.remote) = local &&
test $(git-config branch.my1.merge) = refs/heads/master'

test_expect_success 'test tracking setup (non-wildcard, matching)' \
'git-config remote.local.url . &&
git-config remote.local.fetch refs/heads/master:refs/remotes/local/master &&
(git-show-ref -q refs/remotes/local/master || git-fetch local) &&
git-branch --track my4 local/master &&
test $(git-config branch.my4.remote) = local &&
test $(git-config branch.my4.merge) = refs/heads/master'

test_expect_success 'test tracking setup (non-wildcard, not matching)' \
'git-config remote.local.url . &&
git-config remote.local.fetch refs/heads/s:refs/remotes/local/s &&
(git-show-ref -q refs/remotes/local/master || git-fetch local) &&
git-branch --track my5 local/master &&
! test $(git-config branch.my5.remote) = local &&
! test $(git-config branch.my5.merge) = refs/heads/master'

test_expect_success 'test tracking setup via config' \
'git-config branch.autosetupmerge true &&
git-config remote.local.url . &&
git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
(git-show-ref -q refs/remotes/local/master || git-fetch local) &&
git-branch my3 local/master &&
test $(git-config branch.my3.remote) = local &&
test $(git-config branch.my3.merge) = refs/heads/master'

test_expect_success 'test overriding tracking setup via --no-track' \
'git-config branch.autosetupmerge true &&
git-config remote.local.url . &&
git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
(git-show-ref -q refs/remotes/local/master || git-fetch local) &&
git-branch --no-track my2 local/master &&
! test $(git-config branch.my2.remote) = local &&
! test $(git-config branch.my2.merge) = refs/heads/master'

# Keep this test last, as it changes the current branch
cat >expect <<EOF
0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master
EOF
test_expect_success \
'git checkout -b g/h/i -l should create a branch and a log' \
'GIT_COMMITTER_DATE="2005-05-26 23:30" \
git-checkout -b g/h/i -l master &&
test -f .git/refs/heads/g/h/i &&
test -f .git/logs/refs/heads/g/h/i &&
diff expect .git/logs/refs/heads/g/h/i'

test_done test_done

18
trace.c

@ -26,14 +26,14 @@
#include "quote.h" #include "quote.h"


/* Stolen from "imap-send.c". */ /* Stolen from "imap-send.c". */
static int git_vasprintf(char **strp, const char *fmt, va_list ap) int nfvasprintf(char **strp, const char *fmt, va_list ap)
{ {
int len; int len;
char tmp[1024]; char tmp[1024];


if ((len = vsnprintf(tmp, sizeof(tmp), fmt, ap)) < 0 || if ((len = vsnprintf(tmp, sizeof(tmp), fmt, ap)) < 0 ||
!(*strp = xmalloc(len + 1))) !(*strp = xmalloc(len + 1)))
return -1; die("Fatal: Out of memory\n");
if (len >= (int)sizeof(tmp)) if (len >= (int)sizeof(tmp))
vsprintf(*strp, fmt, ap); vsprintf(*strp, fmt, ap);
else else
@ -41,13 +41,15 @@ static int git_vasprintf(char **strp, const char *fmt, va_list ap)
return len; return len;
} }


/* Stolen from "imap-send.c". */ int nfasprintf(char **str, const char *fmt, ...)
int nfvasprintf(char **str, const char *fmt, va_list va)
{ {
int ret = git_vasprintf(str, fmt, va); int rc;
if (ret < 0) va_list args;
die("Fatal: Out of memory\n");
return ret; va_start(args, fmt);
rc = nfvasprintf(str, fmt, args);
va_end(args);
return rc;
} }


/* Get a trace file descriptor from GIT_TRACE env variable. */ /* Get a trace file descriptor from GIT_TRACE env variable. */

Loading…
Cancel
Save