A rather standard (in 1.5) procedure for branching off a remote archive is: git checkout -b branchname remote/upstreambranch git config --add branch.branchname.remote remote git config --add branch.branchname.merge refs/heads/upstreambranch In this case, we can save the user some effort if "git branch" (and "git checkout -b") automatically do the two "git-config --add"s when the source branch is remote. There is a good chance that some user wants to merge something different, but in that case they have to specify what to merge _anyway_. The behavior is controlled by core.trackremotebranches (off by default; subject to review later), and can be fine-grained to a specific invocation of "git branch" using the new --track and --no-track options. Signed-off-by: Paolo Bonzini <bonzini@xxxxxxx> --- Documentation/git-branch.txt | 9 ++++++ builtin-branch.c | 56 ++++++++++++++++++++++++++++++++++++------- cache.h | 1 config.c | 5 +++ environment.c | 1 5 files changed, 63 insertions(+), 9 deletions(-) Includes comments by Johannes Schindelin on not using xmalloc for buffers, and better variable names. Default is "false" in this version, unlike previous versions. diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index aa1fdd4..4ccbb3c 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git-branch' [--color | --no-color] [-r | -a] [-v [--abbrev=<length>]] -'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' (-d | -D) [-r] <branchname>... @@ -25,6 +25,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 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 `core.trackremotebranches` option. +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>. If <oldbranch> had a corresponding reflog, it is renamed to match <newbranch>, and a reflog entry is created to remember the branch diff --git a/builtin-branch.c b/builtin-branch.c index d0179b0..96658ff 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -12,7 +12,7 @@ #include "builtin.h" 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>]]"; + "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>]]"; #define REF_UNKNOWN_TYPE 0x00 #define REF_LOCAL_BRANCH 0x01 @@ -308,15 +308,34 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev) free_ref_list(&ref_list); } +static void register_pull (const char *name, const char *remote_branch_name) +{ + char *slash = strchr(remote_branch_name, '/'); + char key[1024], value[1024]; + + if (strlen(remote_branch_name) >= 1024 - 11 + || strlen(name) >= 1024 - 15) + die ("what a long branch name you have!"); + + snprintf(key, sizeof(key), "branch.%s.remote", name); + snprintf(value, sizeof(value), "%.*s", slash - remote_branch_name, + remote_branch_name); + git_config_set(key, value); + + snprintf(key, sizeof(key), "branch.%s.merge", name); + snprintf(value, sizeof(value), "refs/heads/%s", slash + 1); + git_config_set(key, value); +} + static void create_branch(const char *name, const char *start_name, unsigned char *start_sha1, - int force, int reflog) + int force, int reflog, int track) { struct ref_lock *lock; struct commit *commit; unsigned char sha1[20]; - char ref[PATH_MAX], msg[PATH_MAX + 20]; - int forcing = 0; + char *real_ref = NULL, ref[PATH_MAX], msg[PATH_MAX + 20]; + int forcing = 0, remote = 0; snprintf(ref, sizeof ref, "refs/heads/%s", name); if (check_ref_format(ref)) @@ -333,7 +354,9 @@ static void create_branch(const char *name, const char *start_name, if (start_sha1) /* detached HEAD */ hashcpy(sha1, start_sha1); - else if (get_sha1(start_name, sha1)) + else if (dwim_ref(start_name, strlen (start_name), sha1, &real_ref)) + remote = !prefixcmp(real_ref, "refs/remotes/"); + else die("Not a valid object name: '%s'.", start_name); if ((commit = lookup_commit_reference(sha1)) == NULL) @@ -354,8 +377,16 @@ static void create_branch(const char *name, const char *start_name, snprintf(msg, sizeof msg, "branch: Created from %s", start_name); + /* When branching off a remote branch, set up so that git-pull + automatically merges from there. */ + if (remote && track) + register_pull (name, real_ref + 13); + if (write_ref_sha1(lock, sha1, msg) < 0) 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) @@ -397,11 +428,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix) int delete = 0, force_delete = 0, force_create = 0; int rename = 0, force_rename = 0; int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0; - int reflog = 0; + int reflog = 0, track; int kinds = REF_LOCAL_BRANCH; int i; git_config(git_branch_config); + track = track_remote_branches; for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -412,6 +444,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix) i++; break; } + if (!strcmp(arg, "--track")) { + track = 1; + continue; + } + if (!strcmp(arg, "--no-track")) { + track = 0; + continue; + } if (!strcmp(arg, "-d")) { delete = 1; continue; @@ -490,9 +530,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix) else if (rename && (i == argc - 2)) rename_branch(argv[i], argv[i + 1], force_rename); 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) - create_branch(argv[i], argv[i+1], NULL, force_create, reflog); + create_branch(argv[i], argv[i+1], NULL, force_create, reflog, + track); else usage(builtin_branch_usage); diff --git a/cache.h b/cache.h index 8bbc142..585a9b4 100644 --- a/cache.h +++ b/cache.h @@ -205,6 +205,7 @@ extern int trust_executable_bit; extern int assume_unchanged; extern int prefer_symlink_refs; extern int log_all_ref_updates; +extern int track_remote_branches; extern int warn_ambiguous_refs; extern int shared_repository; extern const char *apply_default_whitespace; diff --git a/config.c b/config.c index 0ff413b..49df7bd 100644 --- a/config.c +++ b/config.c @@ -294,6 +294,11 @@ int git_default_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.trackremotebranches")) { + track_remote_branches = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "core.legacyheaders")) { use_legacy_headers = git_config_bool(var, value); return 0; diff --git a/environment.c b/environment.c index 570e32a..e440d05 100644 --- a/environment.c +++ b/environment.c @@ -17,6 +17,7 @@ int assume_unchanged; int prefer_symlink_refs; int is_bare_repository_cfg = -1; /* unspecified */ int log_all_ref_updates = -1; /* unspecified */ +int track_remote_branches = 0; int warn_ambiguous_refs = 1; int repository_format_version; char *git_commit_encoding; - To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html