{"thread":{"id":"7148","subject":"[PATCH, final version] git-branch, git-checkout: autosetup for remote branch tracking","startedAt":"2007-03-08T06:49:41Z","lastAt":"2007-03-10T00:59:25Z","messageCount":7,"participants":["Paolo Bonzini","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"36594","messageId":"45EFB205.4000604@lu.unisi.ch","threadId":"7148","inReplyTo":null,"subject":"[PATCH, final version] git-branch, git-checkout: autosetup for remote branch tracking","fromName":"Paolo Bonzini","fromEmail":"paolo.bonzini@lu.unisi.ch","sentAt":"2007-03-08T06:49:41Z","receivedAt":"2007-03-08T06:49:41Z","isPatch":true,"sender":{"key":"bonzini@gnu.org","avatar":"https://avatars.githubusercontent.com/u/42082?v=4"},"body":"In order to track and build on top of a branch 'topic' you track from\nyour upstream repository, you often would end up doing this sequence:\n\n  git checkout -b mytopic origin/topic\n  git config --add branch.mytopic.remote origin\n  git config --add branch.mytopic.merge refs/heads/topic\n\nThis would first fork your own 'mytopic' branch from the 'topic'\nbranch you track from the 'origin' repository; then it would set up two\nconfiguration variables so that 'git pull' without parameters does the\nright thing while you are on your own 'mytopic' branch.\n\nThis commit adds a --track option to git-branch, so that \"git\nbranch --track mytopic origin/topic\" performs the latter two actions\nwhen creating your 'mytopic' branch.\n\nIf the configuration variable branch.autosetupmerge is set to true, you\ndo not have to pass the --track option explicitly; further patches in\nthis series allow setting the variable with a \"git remote add\" option.\nThe configuration variable is off by default, and there is a --no-track\noption to countermand it even if the variable is set.\n\nSigned-off-by: Paolo Bonzini  <bonzini@gnu.org>\n---\n Documentation/git-branch.txt   |    9 ++\n Documentation/git-checkout.txt |   15 ++++\n builtin-branch.c               |  128 ++++++++++++++++++++++++++++++++++++++---\n cache.h                        |    1 \n git-checkout.sh                |   17 +++--\n t/t3200-branch.sh              |   65 +++++++++++++++++---\n trace.c                        |   14 +---\n 7 files changed, 214 insertions(+), 35 deletions(-)\n\n\tThis is the hopefully final version.  I am now looking at all\n\tremote configs, not restricting the search to those in refs/remotes/\n\t(and this made the parsing simpler, so no sscanf anymore).\n\tI also gave up the per-remote config and I am using instead a\n\tglobal variable.\n\n\tThis version also includes tests and changes to git-checkout.\n\tgit-remote is no longer affected by the change.\n\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 3ea3b80..603f87f 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -10,7 +10,7 @@ SYNOPSIS\n [verse]\n 'git-branch' [--color | --no-color] [-r | -a]\n \t   [-v [--abbrev=<length> | --no-abbrev]]\n-'git-branch' [-l] [-f] <branchname> [<start-point>]\n+'git-branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>]\n 'git-branch' (-m | -M) [<oldbranch>] <newbranch>\n 'git-branch' (-d | -D) [-r] <branchname>...\n \n@@ -26,6 +26,13 @@ It will start out with a head equal to the one given as <start-point>.\n If no <start-point> is given, the branch will be created with a head\n equal to that of the currently checked out branch.\n \n+When a local branch is started off a remote branch, git can setup the\n+branch so that gitlink:git-pull[1] will appropriately merge from that\n+remote branch.  If this behavior is desired, it is possible to make it\n+the default using the global `branch.autosetupmerge` configuration\n+flag.  Otherwise, it can be chosen per-branch using the `--track`\n+and `--no-track` options.\n+\n With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>.\n If <oldbranch> had a corresponding reflog, it is renamed to match\n <newbranch>, and a reflog entry is created to remember the branch\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 1ae77be..f5b2d50 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -8,7 +8,7 @@ git-checkout - Checkout and switch to a branch\n SYNOPSIS\n --------\n [verse]\n-'git-checkout' [-q] [-f] [-b <new_branch> [-l]] [-m] [<branch>]\n+'git-checkout' [-q] [-f] [-b [--track | --no-track] <new_branch> [-l]] [-m] [<branch>]\n 'git-checkout' [<tree-ish>] <paths>...\n \n DESCRIPTION\n@@ -18,7 +18,8 @@ When <paths> are not given, this command switches branches by\n updating the index and working tree to reflect the specified\n branch, <branch>, and updating HEAD to be <branch> or, if\n specified, <new_branch>.  Using -b will cause <new_branch> to\n-be created.\n+be created; in this case you can use the --track or --no-track\n+options, which will be passed to `git branch`.\n \n When <paths> are given, this command does *not* switch\n branches.  It updates the named paths in the working tree from\n@@ -45,6 +46,16 @@ OPTIONS\n \tby gitlink:git-check-ref-format[1].  Some of these checks\n \tmay restrict the characters allowed in a branch name.\n \n+--track::\n+\tWhen -b is given and a branch is created off a remote branch,\n+\tsetup so that git-pull will automatically retrieve data from\n+\tthe remote branch.\n+\n+--no-track::\n+\tWhen -b is given and a branch is created off a remote branch,\n+\tforce that git-pull will automatically retrieve data from\n+\tthe remote branch independent of the configuration settings.\n+\n -l::\n \tCreate the new branch's ref log.  This activates recording of\n \tall changes to made the branch ref, enabling use of date\ndiff --git a/builtin-branch.c b/builtin-branch.c\nindex d371849..34b1bbf 100644\n--- a/builtin-branch.c\n+++ b/builtin-branch.c\n@@ -12,7 +12,7 @@\n #include \"builtin.h\"\n \n static const char builtin_branch_usage[] =\n-  \"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]]\";\n+  \"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]]\";\n \n #define REF_UNKNOWN_TYPE    0x00\n #define REF_LOCAL_BRANCH    0x01\n@@ -22,6 +22,8 @@ static const char builtin_branch_usage[] =\n static const char *head;\n static unsigned char head_sha1[20];\n \n+static int branch_track_remotes;\n+\n static int branch_use_color;\n static char branch_colors[][COLOR_MAXLEN] = {\n \t\"\\033[m\",\t/* reset */\n@@ -64,6 +66,9 @@ int git_branch_config(const char *var, const char *value)\n \t\tcolor_parse(value, var, branch_colors[slot]);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"branch.autosetupmerge\"))\n+\t\tbranch_track_remotes = git_config_bool(var, value);\n+\n \treturn git_default_config(var, value);\n }\n \n@@ -308,14 +313,102 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev)\n \tfree_ref_list(&ref_list);\n }\n \n+static char *config_repo;\n+static char *config_remote;\n+static const char *start_ref;\n+static int start_len;\n+static int base_len;\n+\n+static int get_remote_branch_name(const char* value)\n+{\n+\tconst char *colon;\n+\tconst char *end;\n+\n+\tif (*value == '+')\n+\t\tvalue++;\n+\n+\tcolon = strchr(value, ':');\n+\tend = value + strlen(value);\n+\n+\t/* Try an exact match first.  */\n+\tif (!strcmp(colon + 1, start_ref)) {\n+\t\t/* Truncate the value before the colon.  */\n+\t\tnfasprintf(&config_repo, \"%.*s\", colon - value, value);\n+\t\treturn 1;\n+\t}\n+\n+\t/* Try with a wildcard match now.  */\n+\tif (end - value > 2 && end[-2] == '/' && end[-1] == '*' &&\n+\t    colon - value > 2 && colon[-2] == '/' && colon[-1] == '*' &&\n+\t    (end - 2) - (colon + 1) == base_len &&\n+\t    !strncmp(colon + 1, start_ref, base_len)) {\n+\t\t/* Replace the star with the remote branch name.  */\n+\t\tnfasprintf(&config_repo, \"%.*s%s\",\n+\t\t\t   (colon - 2) - value, value,\n+\t\t\t   start_ref + base_len);\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int get_remote_config(const char* key, const char* value)\n+{\n+\tconst char *var;\n+        if (prefixcmp(key, \"remote.\"))\n+\t\treturn 0;\n+\n+\tvar = strrchr(key, '.');\n+\tif (var == key + 6)\n+\t\treturn 0;\n+\n+\tif (!strcmp(var, \".fetch\") && get_remote_branch_name(value))\n+\t\tnfasprintf(&config_remote, \"%.*s\", var - (key + 7), key + 7);\n+\n+        return 0;\n+}\n+\n+static void set_branch_defaults(const char *name, const char *real_ref)\n+{\n+\tchar key[1024];\n+\tconst char *slash = strrchr(real_ref, '/');\n+\n+\tif (!slash)\n+\t\treturn;\n+\n+\tstart_ref = real_ref;\n+\tstart_len = strlen(real_ref);\n+\tbase_len = slash - real_ref;\n+\tgit_config(get_remote_config);\n+\n+\t/* Change to != 0 to enable this feature by default.  */\n+\tif (config_repo && config_remote) {\n+\t\tif (snprintf(key, sizeof(key), \"branch.%s.remote\", name)\n+\t\t    > sizeof(key))\n+\t\t\tdie(\"what a long branch name you have!\");\n+\t\tgit_config_set(key, config_remote);\n+\n+\t\tsnprintf(key, sizeof(key), \"branch.%s.merge\", name);\n+\t\tgit_config_set(key, config_repo);\n+\n+\t\tprintf(\"Branch %s set up to track remote branch %s.\\n\",\n+\t\t       name, real_ref);\n+\t}\n+\n+\tif (config_repo)\n+\t\tfree(config_repo);\n+\tif (config_remote)\n+\t\tfree(config_remote);\n+}\n+\n static void create_branch(const char *name, const char *start_name,\n \t\t\t  unsigned char *start_sha1,\n-\t\t\t  int force, int reflog)\n+\t\t\t  int force, int reflog, int track)\n {\n \tstruct ref_lock *lock;\n \tstruct commit *commit;\n \tunsigned char sha1[20];\n-\tchar ref[PATH_MAX], msg[PATH_MAX + 20];\n+\tchar *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20];\n \tint forcing = 0;\n \n \tsnprintf(ref, sizeof ref, \"refs/heads/%s\", name);\n@@ -333,7 +426,9 @@ static void create_branch(const char *name, const char *start_name,\n \tif (start_sha1)\n \t\t/* detached HEAD */\n \t\thashcpy(sha1, start_sha1);\n-\telse if (get_sha1(start_name, sha1))\n+\telse if (dwim_ref(start_name, strlen(start_name), sha1, &real_ref) > 1)\n+\t\tdie(\"Ambiguous object name: '%s'.\", start_name);\n+\telse if (real_ref == NULL)\n \t\tdie(\"Not a valid object name: '%s'.\", start_name);\n \n \tif ((commit = lookup_commit_reference(sha1)) == NULL)\n@@ -354,8 +449,17 @@ static void create_branch(const char *name, const char *start_name,\n \t\tsnprintf(msg, sizeof msg, \"branch: Created from %s\",\n \t\t\t start_name);\n \n+\t/* When branching off a remote branch, set up so that git-pull\n+\t   automatically merges from there.  So far, this is only done for\n+\t   remotes registered via .git/config.  */\n+\tif (real_ref && track)\n+\t\tset_branch_defaults(name, real_ref);\n+\n \tif (write_ref_sha1(lock, sha1, msg) < 0)\n \t\tdie(\"Failed to write ref: %s.\", strerror(errno));\n+\n+\tif (real_ref)\n+\t\tfree(real_ref);\n }\n \n static void rename_branch(const char *oldname, const char *newname, int force)\n@@ -397,11 +501,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tint delete = 0, force_delete = 0, force_create = 0;\n \tint rename = 0, force_rename = 0;\n \tint verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;\n-\tint reflog = 0;\n+\tint reflog = 0, track;\n \tint kinds = REF_LOCAL_BRANCH;\n \tint i;\n \n \tgit_config(git_branch_config);\n+\ttrack = branch_track_remotes;\n \n \tfor (i = 1; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n@@ -412,6 +517,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\t\ti++;\n \t\t\tbreak;\n \t\t}\n+\t\tif (!strcmp(arg, \"--track\")) {\n+\t\t\ttrack = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"--no-track\")) {\n+\t\t\ttrack = 0;\n+\t\t\tcontinue;\n+\t\t}\n \t\tif (!strcmp(arg, \"-d\")) {\n \t\t\tdelete = 1;\n \t\t\tcontinue;\n@@ -498,9 +611,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \telse if (rename && (i == argc - 2))\n \t\trename_branch(argv[i], argv[i + 1], force_rename);\n \telse if (i == argc - 1)\n-\t\tcreate_branch(argv[i], head, head_sha1, force_create, reflog);\n+\t\tcreate_branch(argv[i], head, head_sha1, force_create, reflog,\n+\t\t\t      track);\n \telse if (i == argc - 2)\n-\t\tcreate_branch(argv[i], argv[i+1], NULL, force_create, reflog);\n+\t\tcreate_branch(argv[i], argv[i+1], NULL, force_create, reflog,\n+\t\t\t      track);\n \telse\n \t\tusage(builtin_branch_usage);\n \ndiff --git a/cache.h b/cache.h\nindex 4b5a754..cb4fbfd 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -480,6 +480,7 @@ extern struct tag *alloc_tag_node(void);\n extern void alloc_report(void);\n \n /* trace.c */\n+extern int nfasprintf(char **str, const char *fmt, ...);\n extern int nfvasprintf(char **str, const char *fmt, va_list va);\n extern void trace_printf(const char *format, ...);\n extern void trace_argv_printf(const char **argv, int count, const char *format, ...);\ndiff --git a/git-checkout.sh b/git-checkout.sh\nindex 14835a4..6caa9fd 100755\n--- a/git-checkout.sh\n+++ b/git-checkout.sh\n@@ -12,6 +12,7 @@ new=\n new_name=\n force=\n branch=\n+track=\n newbranch=\n newbranch_log=\n merge=\n@@ -33,7 +34,10 @@ while [ \"$#\" != \"0\" ]; do\n \t\t\tdie \"git checkout: we do not like '$newbranch' as a branch name.\"\n \t\t;;\n \t\"-l\")\n-\t\tnewbranch_log=1\n+\t\tnewbranch_log=-l\n+\t\t;;\n+\t\"--track\"|\"--no-track\")\n+\t\ttrack=\"$arg\"\n \t\t;;\n \t\"-f\")\n \t\tforce=1\n@@ -85,6 +89,11 @@ while [ \"$#\" != \"0\" ]; do\n     esac\n done\n \n+case \"$new_branch,$track\" in\n+,--*)\n+\tdie \"git checkout: --track and --no-track require -b\"\n+esac\n+\n case \"$force$merge\" in\n 11)\n \tdie \"git checkout: -f and -m are incompatible\"\n@@ -235,11 +244,7 @@ fi\n #\n if [ \"$?\" -eq 0 ]; then\n \tif [ \"$newbranch\" ]; then\n-\t\tif [ \"$newbranch_log\" ]; then\n-\t\t\tmkdir -p $(dirname \"$GIT_DIR/logs/refs/heads/$newbranch\")\n-\t\t\ttouch \"$GIT_DIR/logs/refs/heads/$newbranch\"\n-\t\tfi\n-\t\tgit-update-ref -m \"checkout: Created from $new_name\" \"refs/heads/$newbranch\" $new || exit\n+\t\tgit-branch $track $newbranch_log \"$newbranch\" \"$new_name\" || exit\n \t\tbranch=\"$newbranch\"\n \tfi\n \tif test -n \"$branch\"\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex 5565c27..75c000a 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -47,17 +47,6 @@ test_expect_success \\\n \t test ! -f .git/refs/heads/d/e/f &&\n \t test ! -f .git/logs/refs/heads/d/e/f'\n \n-cat >expect <<EOF\n-0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000\tcheckout: Created from master\n-EOF\n-test_expect_success \\\n-    'git checkout -b g/h/i -l should create a branch and a log' \\\n-\t'GIT_COMMITTER_DATE=\"2005-05-26 23:30\" \\\n-     git-checkout -b g/h/i -l master &&\n-\t test -f .git/refs/heads/g/h/i &&\n-\t test -f .git/logs/refs/heads/g/h/i &&\n-\t diff expect .git/logs/refs/heads/g/h/i'\n-\n test_expect_success \\\n     'git branch j/k should work after branch j has been deleted' \\\n        'git-branch j &&\n@@ -117,4 +106,58 @@ test_expect_failure \\\n      ln -s real-u .git/logs/refs/heads/u &&\n      git-branch -m u v'\n \n+test_expect_success 'test tracking setup via --track' \\\n+    'git-config remote.local.url . &&\n+     git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&\n+     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&\n+     git-branch --track my1 local/master &&\n+     test $(git-config branch.my1.remote) = local &&\n+     test $(git-config branch.my1.merge) = refs/heads/master'\n+\n+test_expect_success 'test tracking setup (non-wildcard, matching)' \\\n+    'git-config remote.local.url . &&\n+     git-config remote.local.fetch refs/heads/master:refs/remotes/local/master &&\n+     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&\n+     git-branch --track my4 local/master &&\n+     test $(git-config branch.my4.remote) = local &&\n+     test $(git-config branch.my4.merge) = refs/heads/master'\n+\n+test_expect_success 'test tracking setup (non-wildcard, not matching)' \\\n+    'git-config remote.local.url . &&\n+     git-config remote.local.fetch refs/heads/s:refs/remotes/local/s &&\n+     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&\n+     git-branch --track my5 local/master &&\n+     ! test $(git-config branch.my5.remote) = local &&\n+     ! test $(git-config branch.my5.merge) = refs/heads/master'\n+\n+test_expect_success 'test tracking setup via config' \\\n+    'git-config branch.autosetupmerge true &&\n+     git-config remote.local.url . &&\n+     git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&\n+     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&\n+     git-branch my3 local/master &&\n+     test $(git-config branch.my3.remote) = local &&\n+     test $(git-config branch.my3.merge) = refs/heads/master'\n+\n+test_expect_success 'test overriding tracking setup via --no-track' \\\n+    'git-config branch.autosetupmerge true &&\n+     git-config remote.local.url . &&\n+     git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&\n+     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&\n+     git-branch --no-track my2 local/master &&\n+     ! test $(git-config branch.my2.remote) = local &&\n+     ! test $(git-config branch.my2.merge) = refs/heads/master'\n+\n+# Keep this test last, as it changes the current branch\n+cat >expect <<EOF\n+0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000\tbranch: Created from master\n+EOF\n+test_expect_success \\\n+    'git checkout -b g/h/i -l should create a branch and a log' \\\n+\t'GIT_COMMITTER_DATE=\"2005-05-26 23:30\" \\\n+     git-checkout -b g/h/i -l master &&\n+\t test -f .git/refs/heads/g/h/i &&\n+\t test -f .git/logs/refs/heads/g/h/i &&\n+\t diff expect .git/logs/refs/heads/g/h/i'\n+\n test_done\ndiff --git a/trace.c b/trace.c\nindex 27fef86..1d4179d 100644\n--- a/trace.c\n+++ b/trace.c\n@@ -26,14 +26,14 @@\n #include \"quote.h\"\n \n /* Stolen from \"imap-send.c\". */\n-static int git_vasprintf(char **strp, const char *fmt, va_list ap)\n+int nfvasprintf(char **strp, const char *fmt, va_list ap)\n {\n \tint len;\n \tchar tmp[1024];\n \n \tif ((len = vsnprintf(tmp, sizeof(tmp), fmt, ap)) < 0 ||\n \t    !(*strp = xmalloc(len + 1)))\n-\t\treturn -1;\n+\t\tdie(\"Fatal: Out of memory\\n\");\n \tif (len >= (int)sizeof(tmp))\n \t\tvsprintf(*strp, fmt, ap);\n \telse\n@@ -41,13 +41,11 @@ static int git_vasprintf(char **strp, const char *fmt, va_list ap)\n \treturn len;\n }\n \n-/* Stolen from \"imap-send.c\". */\n-int nfvasprintf(char **str, const char *fmt, va_list va)\n+int nfasprintf(char **str, const char *fmt, ...)\n {\n-\tint ret = git_vasprintf(str, fmt, va);\n-\tif (ret < 0)\n-\t\tdie(\"Fatal: Out of memory\\n\");\n-\treturn ret;\n+\tva_list args;\n+\tva_start(args, fmt);\n+\treturn nfvasprintf(str, fmt, args);\n }\n \n /* Get a trace file descriptor from GIT_TRACE env variable. */\n"},{"id":"36605","messageId":"7vzm6o9ie1.fsf@assigned-by-dhcp.cox.net","threadId":"7148","inReplyTo":"45EFB205.4000604@lu.unisi.ch","subject":"Re: [PATCH, final version] git-branch, git-checkout: autosetup for remote branch tracking","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-03-08T09:47:18Z","receivedAt":"2007-03-08T09:47:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Paolo Bonzini <paolo.bonzini@lu.unisi.ch> writes:\n\n> ...\n> Signed-off-by: Paolo Bonzini  <bonzini@gnu.org>\n\nBeautifully done, with even a sign-off ;-).\n\n> @@ -45,6 +46,16 @@ OPTIONS\n>  \tby gitlink:git-check-ref-format[1].  Some of these checks\n>  \tmay restrict the characters allowed in a branch name.\n>  \n> +--track::\n> +\tWhen -b is given and a branch is created off a remote branch,\n> +\tsetup so that git-pull will automatically retrieve data from\n> +\tthe remote branch.\n> +\n> +--no-track::\n> +\tWhen -b is given and a branch is created off a remote branch,\n> +\tforce that git-pull will automatically retrieve data from\n> +\tthe remote branch independent of the configuration settings.\n> +\n>  -l::\n>  \tCreate the new branch's ref log.  This activates recording of\n>  \tall changes to made the branch ref, enabling use of date\n\nEarlier I said \"checkout -b\" vs \"checkout -B\", but deciding the\ntracking default with congiguration mechanism and overriding\nwith an additional --track/--no-track as you did here is way\nsuperiour.  Very nice.\n\n> diff --git a/builtin-branch.c b/builtin-branch.c\n> index d371849..34b1bbf 100644\n> --- a/builtin-branch.c\n> +++ b/builtin-branch.c\n> @@ -308,14 +313,102 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev)\n> +static int get_remote_branch_name(const char* value)\n> +{\n> +\tconst char *colon;\n> +\tconst char *end;\n> +\n> +\tif (*value == '+')\n> +\t\tvalue++;\n> +\n> +\tcolon = strchr(value, ':');\n> +\tend = value + strlen(value);\n> +\n> +\t/* Try an exact match first.  */\n> +\tif (!strcmp(colon + 1, start_ref)) {\n\nCareful.  colon may be NULL here, in which case you would want\nto return with 0 from this function before this strcmp.\n\nYou seem to declare variables with \"const char *colon\" but\nfunction args with \"const char* value\".  Are they deliberately\ndone differently?  The existing code seems to favor asterisk\nnext to the variable name, not type name.\n\nOther than that, I find this function and get_remote_config()\nvery nicely done.\n\n> +static void set_branch_defaults(const char *name, const char *real_ref)\n> +{\n> +\tchar key[1024];\n> +\tconst char *slash = strrchr(real_ref, '/');\n> +\n> +\tif (!slash)\n> +\t\treturn;\n> +\n> +\tstart_ref = real_ref;\n> +\tstart_len = strlen(real_ref);\n> +\tbase_len = slash - real_ref;\n> +\tgit_config(get_remote_config);\n> +\n> +\t/* Change to != 0 to enable this feature by default.  */\n> +\tif (config_repo && config_remote) {\n> +\t\tif (snprintf(key, sizeof(key), \"branch.%s.remote\", name)\n> +\t\t    > sizeof(key))\n> +\t\t\tdie(\"what a long branch name you have!\");\n> +\t\tgit_config_set(key, config_remote);\n> +\n> +\t\tsnprintf(key, sizeof(key), \"branch.%s.merge\", name);\n> +\t\tgit_config_set(key, config_repo);\n\nThis is nice, but you might want to have a comment that says why\nyou do not have to check the return value from the second\nsnprintf().  Otherwise, somebody less careful may update this\npart of the code and swap the order of config_remote and\nconfig_repo are set up without thinking in the future.\n\n> diff --git a/trace.c b/trace.c\n> index 27fef86..1d4179d 100644\n> --- a/trace.c\n> +++ b/trace.c\n> @@ -41,13 +41,11 @@ static int git_vasprintf(char **strp, const char *fmt, va_list ap)\n>  \treturn len;\n>  }\n>  \n> +int nfasprintf(char **str, const char *fmt, ...)\n>  {\n> +\tva_list args;\n> +\tva_start(args, fmt);\n> +\treturn nfvasprintf(str, fmt, args);\n>  }\n>  \n>  /* Get a trace file descriptor from GIT_TRACE env variable. */\n\nIt might not matter on common architectures in practice, but\nva_start() must be matched by a corresponding va_end() in the\nsame function.\n"},{"id":"36608","messageId":"45EFDE4B.3010408@lu.unisi.ch","threadId":"7148","inReplyTo":"7vzm6o9ie1.fsf@assigned-by-dhcp.cox.net","subject":"[PATCH, nitpickingly final version] git-branch, git-checkout: autosetup for remote branch tracking","fromName":"Paolo Bonzini","fromEmail":"paolo.bonzini@lu.unisi.ch","sentAt":"2007-03-08T09:58:35Z","receivedAt":"2007-03-08T09:58:35Z","isPatch":true,"sender":{"key":"bonzini@gnu.org","avatar":"https://avatars.githubusercontent.com/u/42082?v=4"},"body":"In order to track and build on top of a branch 'topic' you track from\nyour upstream repository, you often would end up doing this sequence:\n\n  git checkout -b mytopic origin/topic\n  git config --add branch.mytopic.remote origin\n  git config --add branch.mytopic.merge refs/heads/topic\n\nThis would first fork your own 'mytopic' branch from the 'topic'\nbranch you track from the 'origin' repository; then it would set up two\nconfiguration variables so that 'git pull' without parameters does the\nright thing while you are on your own 'mytopic' branch.\n\nThis commit adds a --track option to git-branch, so that \"git\nbranch --track mytopic origin/topic\" performs the latter two actions\nwhen creating your 'mytopic' branch.\n\nIf the configuration variable branch.autosetupmerge is set to true, you\ndo not have to pass the --track option explicitly; further patches in\nthis series allow setting the variable with a \"git remote add\" option.\nThe configuration variable is off by default, and there is a --no-track\noption to countermand it even if the variable is set.\n\nSigned-off-by: Paolo Bonzini  <bonzini@gnu.org>\n---\n Documentation/git-branch.txt   |    9 ++\n Documentation/git-checkout.txt |   15 +++-\n builtin-branch.c               |  137 ++++++++++++++++++++++++++++++++++++++---\n cache.h                        |    1\n git-checkout.sh                |   17 +++--\n t/t3200-branch.sh              |   65 ++++++++++++++++---\n trace.c                        |   18 ++---\n 7 files changed, 226 insertions(+), 36 deletions(-)\n\n\tThis fixes all the nits you pointed out. :-D\n\n\tNow, this was an experience to make...\n\n\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 3ea3b80..603f87f 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -10,7 +10,7 @@ SYNOPSIS\n [verse]\n 'git-branch' [--color | --no-color] [-r | -a]\n \t   [-v [--abbrev=<length> | --no-abbrev]]\n-'git-branch' [-l] [-f] <branchname> [<start-point>]\n+'git-branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>]\n 'git-branch' (-m | -M) [<oldbranch>] <newbranch>\n 'git-branch' (-d | -D) [-r] <branchname>...\n \n@@ -26,6 +26,13 @@ It will start out with a head equal to the one given as <start-point>.\n If no <start-point> is given, the branch will be created with a head\n equal to that of the currently checked out branch.\n \n+When a local branch is started off a remote branch, git can setup the\n+branch so that gitlink:git-pull[1] will appropriately merge from that\n+remote branch.  If this behavior is desired, it is possible to make it\n+the default using the global `branch.autosetupmerge` configuration\n+flag.  Otherwise, it can be chosen per-branch using the `--track`\n+and `--no-track` options.\n+\n With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>.\n If <oldbranch> had a corresponding reflog, it is renamed to match\n <newbranch>, and a reflog entry is created to remember the branch\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 1ae77be..f5b2d50 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -8,7 +8,7 @@ git-checkout - Checkout and switch to a branch\n SYNOPSIS\n --------\n [verse]\n-'git-checkout' [-q] [-f] [-b <new_branch> [-l]] [-m] [<branch>]\n+'git-checkout' [-q] [-f] [-b [--track | --no-track] <new_branch> [-l]] [-m] [<branch>]\n 'git-checkout' [<tree-ish>] <paths>...\n \n DESCRIPTION\n@@ -18,7 +18,8 @@ When <paths> are not given, this command switches branches by\n updating the index and working tree to reflect the specified\n branch, <branch>, and updating HEAD to be <branch> or, if\n specified, <new_branch>.  Using -b will cause <new_branch> to\n-be created.\n+be created; in this case you can use the --track or --no-track\n+options, which will be passed to `git branch`.\n \n When <paths> are given, this command does *not* switch\n branches.  It updates the named paths in the working tree from\n@@ -45,6 +46,16 @@ OPTIONS\n \tby gitlink:git-check-ref-format[1].  Some of these checks\n \tmay restrict the characters allowed in a branch name.\n \n+--track::\n+\tWhen -b is given and a branch is created off a remote branch,\n+\tsetup so that git-pull will automatically retrieve data from\n+\tthe remote branch.\n+\n+--no-track::\n+\tWhen -b is given and a branch is created off a remote branch,\n+\tforce that git-pull will automatically retrieve data from\n+\tthe remote branch independent of the configuration settings.\n+\n -l::\n \tCreate the new branch's ref log.  This activates recording of\n \tall changes to made the branch ref, enabling use of date\ndiff --git a/builtin-branch.c b/builtin-branch.c\nindex d371849..43140c4 100644\n--- a/builtin-branch.c\n+++ b/builtin-branch.c\n@@ -12,7 +12,7 @@\n #include \"builtin.h\"\n \n static const char builtin_branch_usage[] =\n-  \"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]]\";\n+  \"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]]\";\n \n #define REF_UNKNOWN_TYPE    0x00\n #define REF_LOCAL_BRANCH    0x01\n@@ -22,6 +22,8 @@ static const char builtin_branch_usage[] =\n static const char *head;\n static unsigned char head_sha1[20];\n \n+static int branch_track_remotes;\n+\n static int branch_use_color;\n static char branch_colors[][COLOR_MAXLEN] = {\n \t\"\\033[m\",\t/* reset */\n@@ -64,6 +66,9 @@ int git_branch_config(const char *var, const char *value)\n \t\tcolor_parse(value, var, branch_colors[slot]);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"branch.autosetupmerge\"))\n+\t\tbranch_track_remotes = git_config_bool(var, value);\n+\n \treturn git_default_config(var, value);\n }\n \n@@ -308,14 +313,107 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev)\n \tfree_ref_list(&ref_list);\n }\n \n+static char *config_repo;\n+static char *config_remote;\n+static const char *start_ref;\n+static int start_len;\n+static int base_len;\n+\n+static int get_remote_branch_name(const char *value)\n+{\n+\tconst char *colon;\n+\tconst char *end;\n+\n+\tif (*value == '+')\n+\t\tvalue++;\n+\n+\tcolon = strchr(value, ':');\n+        if (!colon)\n+\t\treturn 0;\n+\n+\tend = value + strlen(value);\n+\n+\t/* Try an exact match first.  */\n+\tif (!strcmp(colon + 1, start_ref)) {\n+\t\t/* Truncate the value before the colon.  */\n+\t\tnfasprintf(&config_repo, \"%.*s\", colon - value, value);\n+\t\treturn 1;\n+\t}\n+\n+\t/* Try with a wildcard match now.  */\n+\tif (end - value > 2 && end[-2] == '/' && end[-1] == '*' &&\n+\t    colon - value > 2 && colon[-2] == '/' && colon[-1] == '*' &&\n+\t    (end - 2) - (colon + 1) == base_len &&\n+\t    !strncmp(colon + 1, start_ref, base_len)) {\n+\t\t/* Replace the star with the remote branch name.  */\n+\t\tnfasprintf(&config_repo, \"%.*s%s\",\n+\t\t\t   (colon - 2) - value, value,\n+\t\t\t   start_ref + base_len);\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int get_remote_config(const char *key, const char *value)\n+{\n+\tconst char *var;\n+        if (prefixcmp(key, \"remote.\"))\n+\t\treturn 0;\n+\n+\tvar = strrchr(key, '.');\n+\tif (var == key + 6)\n+\t\treturn 0;\n+\n+\tif (!strcmp(var, \".fetch\") && get_remote_branch_name(value))\n+\t\tnfasprintf(&config_remote, \"%.*s\", var - (key + 7), key + 7);\n+\n+        return 0;\n+}\n+\n+static void set_branch_defaults(const char *name, const char *real_ref)\n+{\n+\tchar key[1024];\n+\tconst char *slash = strrchr(real_ref, '/');\n+\n+\tif (!slash)\n+\t\treturn;\n+\n+\tstart_ref = real_ref;\n+\tstart_len = strlen(real_ref);\n+\tbase_len = slash - real_ref;\n+\tgit_config(get_remote_config);\n+\n+\t/* Change to != 0 to enable this feature by default.  */\n+\tif (config_repo && config_remote) {\n+\t\tif (snprintf(key, sizeof(key), \"branch.%s.remote\", name)\n+\t\t    > sizeof(key))\n+\t\t\tdie(\"what a long branch name you have!\");\n+\t\tgit_config_set(key, config_remote);\n+\n+\t\t/* We must have space for the key, since it's shorter than\n+\t\t   the previous one.  */\n+\t\tsnprintf(key, sizeof(key), \"branch.%s.merge\", name);\n+\t\tgit_config_set(key, config_repo);\n+\n+\t\tprintf(\"Branch %s set up to track remote branch %s.\\n\",\n+\t\t       name, real_ref);\n+\t}\n+\n+\tif (config_repo)\n+\t\tfree(config_repo);\n+\tif (config_remote)\n+\t\tfree(config_remote);\n+}\n+\n static void create_branch(const char *name, const char *start_name,\n \t\t\t  unsigned char *start_sha1,\n-\t\t\t  int force, int reflog)\n+\t\t\t  int force, int reflog, int track)\n {\n \tstruct ref_lock *lock;\n \tstruct commit *commit;\n \tunsigned char sha1[20];\n-\tchar ref[PATH_MAX], msg[PATH_MAX + 20];\n+\tchar *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20];\n \tint forcing = 0;\n \n \tsnprintf(ref, sizeof ref, \"refs/heads/%s\", name);\n@@ -330,10 +428,13 @@ static void create_branch(const char *name, const char *start_name,\n \t\tforcing = 1;\n \t}\n \n-\tif (start_sha1)\n+\tif (start_sha1) {\n \t\t/* detached HEAD */\n \t\thashcpy(sha1, start_sha1);\n-\telse if (get_sha1(start_name, sha1))\n+\t\treal_ref = NULL;\n+\t} else if (dwim_ref(start_name, strlen(start_name), sha1, &real_ref) > 1)\n+\t\tdie(\"Ambiguous object name: '%s'.\", start_name);\n+\telse if (real_ref == NULL)\n \t\tdie(\"Not a valid object name: '%s'.\", start_name);\n \n \tif ((commit = lookup_commit_reference(sha1)) == NULL)\n@@ -354,8 +455,17 @@ static void create_branch(const char *name, const char *start_name,\n \t\tsnprintf(msg, sizeof msg, \"branch: Created from %s\",\n \t\t\t start_name);\n \n+\t/* When branching off a remote branch, set up so that git-pull\n+\t   automatically merges from there.  So far, this is only done for\n+\t   remotes registered via .git/config.  */\n+\tif (real_ref && track)\n+\t\tset_branch_defaults(name, real_ref);\n+\n \tif (write_ref_sha1(lock, sha1, msg) < 0)\n \t\tdie(\"Failed to write ref: %s.\", strerror(errno));\n+\n+\tif (real_ref)\n+\t\tfree(real_ref);\n }\n \n static void rename_branch(const char *oldname, const char *newname, int force)\n@@ -397,11 +507,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tint delete = 0, force_delete = 0, force_create = 0;\n \tint rename = 0, force_rename = 0;\n \tint verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;\n-\tint reflog = 0;\n+\tint reflog = 0, track;\n \tint kinds = REF_LOCAL_BRANCH;\n \tint i;\n \n \tgit_config(git_branch_config);\n+\ttrack = branch_track_remotes;\n \n \tfor (i = 1; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n@@ -412,6 +523,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\t\ti++;\n \t\t\tbreak;\n \t\t}\n+\t\tif (!strcmp(arg, \"--track\")) {\n+\t\t\ttrack = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"--no-track\")) {\n+\t\t\ttrack = 0;\n+\t\t\tcontinue;\n+\t\t}\n \t\tif (!strcmp(arg, \"-d\")) {\n \t\t\tdelete = 1;\n \t\t\tcontinue;\n@@ -498,9 +617,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \telse if (rename && (i == argc - 2))\n \t\trename_branch(argv[i], argv[i + 1], force_rename);\n \telse if (i == argc - 1)\n-\t\tcreate_branch(argv[i], head, head_sha1, force_create, reflog);\n+\t\tcreate_branch(argv[i], head, head_sha1, force_create, reflog,\n+\t\t\t      track);\n \telse if (i == argc - 2)\n-\t\tcreate_branch(argv[i], argv[i+1], NULL, force_create, reflog);\n+\t\tcreate_branch(argv[i], argv[i+1], NULL, force_create, reflog,\n+\t\t\t      track);\n \telse\n \t\tusage(builtin_branch_usage);\n \ndiff --git a/cache.h b/cache.h\nindex 4b5a754..cb4fbfd 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -480,6 +480,7 @@ extern struct tag *alloc_tag_node(void);\n extern void alloc_report(void);\n \n /* trace.c */\n+extern int nfasprintf(char **str, const char *fmt, ...);\n extern int nfvasprintf(char **str, const char *fmt, va_list va);\n extern void trace_printf(const char *format, ...);\n extern void trace_argv_printf(const char **argv, int count, const char *format, ...);\ndiff --git a/git-checkout.sh b/git-checkout.sh\nindex 14835a4..6caa9fd 100755\n--- a/git-checkout.sh\n+++ b/git-checkout.sh\n@@ -12,6 +12,7 @@ new=\n new_name=\n force=\n branch=\n+track=\n newbranch=\n newbranch_log=\n merge=\n@@ -33,7 +34,10 @@ while [ \"$#\" != \"0\" ]; do\n \t\t\tdie \"git checkout: we do not like '$newbranch' as a branch name.\"\n \t\t;;\n \t\"-l\")\n-\t\tnewbranch_log=1\n+\t\tnewbranch_log=-l\n+\t\t;;\n+\t\"--track\"|\"--no-track\")\n+\t\ttrack=\"$arg\"\n \t\t;;\n \t\"-f\")\n \t\tforce=1\n@@ -85,6 +89,11 @@ while [ \"$#\" != \"0\" ]; do\n     esac\n done\n \n+case \"$new_branch,$track\" in\n+,--*)\n+\tdie \"git checkout: --track and --no-track require -b\"\n+esac\n+\n case \"$force$merge\" in\n 11)\n \tdie \"git checkout: -f and -m are incompatible\"\n@@ -235,11 +244,7 @@ fi\n #\n if [ \"$?\" -eq 0 ]; then\n \tif [ \"$newbranch\" ]; then\n-\t\tif [ \"$newbranch_log\" ]; then\n-\t\t\tmkdir -p $(dirname \"$GIT_DIR/logs/refs/heads/$newbranch\")\n-\t\t\ttouch \"$GIT_DIR/logs/refs/heads/$newbranch\"\n-\t\tfi\n-\t\tgit-update-ref -m \"checkout: Created from $new_name\" \"refs/heads/$newbranch\" $new || exit\n+\t\tgit-branch $track $newbranch_log \"$newbranch\" \"$new_name\" || exit\n \t\tbranch=\"$newbranch\"\n \tfi\n \tif test -n \"$branch\"\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex 5565c27..75c000a 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -47,17 +47,6 @@ test_expect_success \\\n \t test ! -f .git/refs/heads/d/e/f &&\n \t test ! -f .git/logs/refs/heads/d/e/f'\n \n-cat >expect <<EOF\n-0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000\tcheckout: Created from master\n-EOF\n-test_expect_success \\\n-    'git checkout -b g/h/i -l should create a branch and a log' \\\n-\t'GIT_COMMITTER_DATE=\"2005-05-26 23:30\" \\\n-     git-checkout -b g/h/i -l master &&\n-\t test -f .git/refs/heads/g/h/i &&\n-\t test -f .git/logs/refs/heads/g/h/i &&\n-\t diff expect .git/logs/refs/heads/g/h/i'\n-\n test_expect_success \\\n     'git branch j/k should work after branch j has been deleted' \\\n        'git-branch j &&\n@@ -117,4 +106,58 @@ test_expect_failure \\\n      ln -s real-u .git/logs/refs/heads/u &&\n      git-branch -m u v'\n \n+test_expect_success 'test tracking setup via --track' \\\n+    'git-config remote.local.url . &&\n+     git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&\n+     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&\n+     git-branch --track my1 local/master &&\n+     test $(git-config branch.my1.remote) = local &&\n+     test $(git-config branch.my1.merge) = refs/heads/master'\n+\n+test_expect_success 'test tracking setup (non-wildcard, matching)' \\\n+    'git-config remote.local.url . &&\n+     git-config remote.local.fetch refs/heads/master:refs/remotes/local/master &&\n+     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&\n+     git-branch --track my4 local/master &&\n+     test $(git-config branch.my4.remote) = local &&\n+     test $(git-config branch.my4.merge) = refs/heads/master'\n+\n+test_expect_success 'test tracking setup (non-wildcard, not matching)' \\\n+    'git-config remote.local.url . &&\n+     git-config remote.local.fetch refs/heads/s:refs/remotes/local/s &&\n+     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&\n+     git-branch --track my5 local/master &&\n+     ! test $(git-config branch.my5.remote) = local &&\n+     ! test $(git-config branch.my5.merge) = refs/heads/master'\n+\n+test_expect_success 'test tracking setup via config' \\\n+    'git-config branch.autosetupmerge true &&\n+     git-config remote.local.url . &&\n+     git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&\n+     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&\n+     git-branch my3 local/master &&\n+     test $(git-config branch.my3.remote) = local &&\n+     test $(git-config branch.my3.merge) = refs/heads/master'\n+\n+test_expect_success 'test overriding tracking setup via --no-track' \\\n+    'git-config branch.autosetupmerge true &&\n+     git-config remote.local.url . &&\n+     git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&\n+     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&\n+     git-branch --no-track my2 local/master &&\n+     ! test $(git-config branch.my2.remote) = local &&\n+     ! test $(git-config branch.my2.merge) = refs/heads/master'\n+\n+# Keep this test last, as it changes the current branch\n+cat >expect <<EOF\n+0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000\tbranch: Created from master\n+EOF\n+test_expect_success \\\n+    'git checkout -b g/h/i -l should create a branch and a log' \\\n+\t'GIT_COMMITTER_DATE=\"2005-05-26 23:30\" \\\n+     git-checkout -b g/h/i -l master &&\n+\t test -f .git/refs/heads/g/h/i &&\n+\t test -f .git/logs/refs/heads/g/h/i &&\n+\t diff expect .git/logs/refs/heads/g/h/i'\n+\n test_done\ndiff --git a/trace.c b/trace.c\nindex 27fef86..27b2e85 100644\n--- a/trace.c\n+++ b/trace.c\n@@ -26,14 +26,14 @@\n #include \"quote.h\"\n \n /* Stolen from \"imap-send.c\". */\n-static int git_vasprintf(char **strp, const char *fmt, va_list ap)\n+int nfvasprintf(char **strp, const char *fmt, va_list ap)\n {\n \tint len;\n \tchar tmp[1024];\n \n \tif ((len = vsnprintf(tmp, sizeof(tmp), fmt, ap)) < 0 ||\n \t    !(*strp = xmalloc(len + 1)))\n-\t\treturn -1;\n+\t\tdie(\"Fatal: Out of memory\\n\");\n \tif (len >= (int)sizeof(tmp))\n \t\tvsprintf(*strp, fmt, ap);\n \telse\n@@ -41,13 +41,15 @@ static int git_vasprintf(char **strp, const char *fmt, va_list ap)\n \treturn len;\n }\n \n-/* Stolen from \"imap-send.c\". */\n-int nfvasprintf(char **str, const char *fmt, va_list va)\n+int nfasprintf(char **str, const char *fmt, ...)\n {\n-\tint ret = git_vasprintf(str, fmt, va);\n-\tif (ret < 0)\n-\t\tdie(\"Fatal: Out of memory\\n\");\n-\treturn ret;\n+\tint rc;\n+\tva_list args;\n+\n+\tva_start(args, fmt);\n+\trc = nfvasprintf(str, fmt, args);\n+\tva_end(args);\n+\treturn args;\n }\n \n /* Get a trace file descriptor from GIT_TRACE env variable. */\n"},{"id":"36614","messageId":"7vabyo9gr6.fsf@assigned-by-dhcp.cox.net","threadId":"7148","inReplyTo":"45EFDE4B.3010408@lu.unisi.ch","subject":"Re: [PATCH, nitpickingly final version] git-branch, git-checkout: autosetup for remote branch tracking","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-03-08T10:22:37Z","receivedAt":"2007-03-08T10:22:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Paolo Bonzini <paolo.bonzini@lu.unisi.ch> writes:\n\n> \tThis fixes all the nits you pointed out. :-D\n> \tNow, this was an experience to make...\n\nThanks.\n\nWill apply after reviewing once more tomorrow, with fix-ups\nlocally if needed.  No need to resend.\n\n> +\n> +\tcolon = strchr(value, ':');\n> +        if (!colon)\n\ns/^\\040{8}/\\011/;\n\n> diff --git a/trace.c b/trace.c\n> index 27fef86..27b2e85 100644\n> --- a/trace.c\n> +++ b/trace.c\n> @@ -41,13 +41,15 @@ static in...\n> +int nfasprintf(char **str, const char *fmt, ...)\n>  {\n> +\tint rc;\n> +\tva_list args;\n> +\n> +\tva_start(args, fmt);\n> +\trc = nfvasprintf(str, fmt, args);\n> +\tva_end(args);\n> +\treturn args;\n>  }\n\ns/return args/return rc/;\n"},{"id":"36652","messageId":"7vr6rz8khv.fsf@assigned-by-dhcp.cox.net","threadId":"7148","inReplyTo":"7vabyo9gr6.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH, nitpickingly final version] git-branch, git-checkout: autosetup for remote branch tracking","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-03-08T21:59:24Z","receivedAt":"2007-03-08T21:59:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <junkio@cox.net> writes:\n\n> Paolo Bonzini <paolo.bonzini@lu.unisi.ch> writes:\n>\n>> \tThis fixes all the nits you pointed out. :-D\n>> \tNow, this was an experience to make...\n>\n> Thanks.\n>\n> Will apply after reviewing once more tomorrow, with fix-ups\n> locally if needed.  No need to resend.\n\nGaah.\n\nThe new create_branch() is totally borked, and I did not notice\nit while exchanging review e-mails.\n\nThe problem is that we have:\n\n\tif (real_ref == NULL)\n        \tdie();\n\nwhich is completely bogus.  We do \"checkout -b temp v2.6.20~15\"\nall the time, and in that case dwim_ref(v2.6.20~15) rightfully\nwould say \"nope, that's not a branch tip\".  We have no right to\ndie there.\n\nI think your new tests should have caught this kind of breakage,\nbut now I notice that there is no such test that makes sure that\nno auto set-up is made when the original branch is not a remote\ntracking branch (your new tests are only interested in testing\nto see if the new feature works, and it does not test that there\nis no regression to old usage).  The breakage was caught by an\nunrelated test, t3900.\n\nThe appended is on top of your \"final\".  This is what I am\nconsidering to apply currently.\n\n\nGrumble....\n\n---\n\ndiff --git a/builtin-branch.c b/builtin-branch.c\nindex 011a2fc..1d6f0cb 100644\n--- a/builtin-branch.c\n+++ b/builtin-branch.c\n@@ -409,7 +409,6 @@ static void set_branch_defaults(const char *name, const char *real_ref)\n }\n \n static void create_branch(const char *name, const char *start_name,\n-\t\t\t  unsigned char *start_sha1,\n \t\t\t  int force, int reflog, int track)\n {\n \tstruct ref_lock *lock;\n@@ -430,15 +429,22 @@ static void create_branch(const char *name, const char *start_name,\n \t\tforcing = 1;\n \t}\n \n-\tif (start_sha1) {\n-\t\t/* detached HEAD */\n-\t\thashcpy(sha1, start_sha1);\n+\treal_ref = NULL;\n+\tif (get_sha1(start_name, sha1))\n+\t\tdie(\"Not a valid object name: '%s'.\", start_name);\n+\n+\tswitch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) {\n+\tcase 0:\n+\t\t/* Not branching from any existing branch */\n \t\treal_ref = NULL;\n-\t}\n-\telse if (dwim_ref(start_name, strlen(start_name), sha1, &real_ref) > 1)\n+\t\tbreak;\n+\tcase 1:\n+\t\t/* Unique completion -- good */\n+\t\tbreak;\n+\tdefault:\n \t\tdie(\"Ambiguous object name: '%s'.\", start_name);\n-\telse if (real_ref == NULL)\n-\t\tdie(\"Not a valid object name: '%s'.\", start_name);\n+\t\tbreak;\n+\t}\n \n \tif ((commit = lookup_commit_reference(sha1)) == NULL)\n \t\tdie(\"Not a valid branch point: '%s'.\", start_name);\n@@ -619,12 +625,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\trename_branch(head, argv[i], force_rename);\n \telse if (rename && (i == argc - 2))\n \t\trename_branch(argv[i], argv[i + 1], force_rename);\n-\telse if (i == argc - 1)\n-\t\tcreate_branch(argv[i], head, head_sha1, force_create, reflog,\n-\t\t\t      track);\n-\telse if (i == argc - 2)\n-\t\tcreate_branch(argv[i], argv[i+1], NULL, force_create, reflog,\n-\t\t\t      track);\n+\telse if (i == argc - 1 || i == argc - 2)\n+\t\tcreate_branch(argv[i], (i == argc - 2) ? argv[i+1] : head,\n+\t\t\t      force_create, reflog, track);\n \telse\n \t\tusage(builtin_branch_usage);\n \n"},{"id":"36654","messageId":"7virdb8jys.fsf_-_@assigned-by-dhcp.cox.net","threadId":"7148","inReplyTo":"7vr6rz8khv.fsf@assigned-by-dhcp.cox.net","subject":"[PATCH] git-branch: rename nor delete should not work when HEAD is detached.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-03-08T22:10:51Z","receivedAt":"2007-03-08T22:10:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"While reviewing the latest round of 'branch --track' changes, I\nnoticed that we do not do anything special when renaming or\ndeleting a detached HEAD.  We do not have anything to rename nor\ndelete in this case, so instead of attempting to rename/delete\nrefs/heads/HEAD, we should error out.\n\n---\n * CC'ed are not guilty parties, but people who might know the\n   code better than others.\n\ndiff --git a/builtin-branch.c b/builtin-branch.c\nindex 06d8a8c..28d4b71 100644\n--- a/builtin-branch.c\n+++ b/builtin-branch.c\n@@ -483,6 +483,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tdie(\"Failed to resolve HEAD as a valid ref.\");\n \tif (!strcmp(head, \"HEAD\")) {\n \t\tdetached = 1;\n+\t\tif (rename || delete)\n+\t\t\tdie(\"Cannot rename nor delete a detached HEAD\");\n \t}\n \telse {\n \t\tif (prefixcmp(head, \"refs/heads/\"))\n"},{"id":"36708","messageId":"7v4pot3ocy.fsf@assigned-by-dhcp.cox.net","threadId":"7148","inReplyTo":"7vr6rz8khv.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH, nitpickingly final version] git-branch, git-checkout: autosetup for remote branch tracking","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-03-10T00:59:25Z","receivedAt":"2007-03-10T00:59:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <junkio@cox.net> writes:\n\n> Gaah.\n>\n> The new create_branch() is totally borked, and I did not notice\n> it while exchanging review e-mails.\n> ...\n> The appended is on top of your \"final\".  This is what I am\n> considering to apply currently.\n>\n> Grumble....\n\nI _like_ this feature.  I even think it _might_ make sense to\nturn it on by default --- I would definitely argue for it if\nthis were before 1.5.0 which we have enough time to give advance\nwarning to users that the defaults and UIs would change, and the\nonly reason I am saying \"it _might_\" is because it _is_ a change\nand it is post 1.5.0.\n\nBut because I had to say Gaah, I am currently in \"discouraged\"\nstate.  Extra eyeballing from the list, tests and Acks are\ngreatly appreciated on this series from Paolo.\n"}]}