{"thread":{"id":"7108","subject":"[PATCH 1/3, portability fixes] git-branch: add --track and --no-track options","startedAt":"2007-03-06T08:43:44Z","lastAt":"2007-03-06T08:43:44Z","messageCount":1,"participants":["Paolo Bonzini"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"36440","messageId":"45ED29C0.50406@lu.unisi.ch","threadId":"7108","inReplyTo":null,"subject":"[PATCH 1/3, portability fixes] git-branch: add --track and --no-track options","fromName":"Paolo Bonzini","fromEmail":"paolo.bonzini@lu.unisi.ch","sentAt":"2007-03-06T08:43:44Z","receivedAt":"2007-03-06T08:43:44Z","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 remote.NAME.trackintolocalbranches is\nset to true, you do not have to pass the --track option explicitly;\nfurther patches in this series allow setting the variable with a \"git\nremote add\" option.  The configuration variable is off by default, and\nthere is a --no-track option to countermand it even if the variable is\nset.\n\nSigned-off-by: Paolo Bonzini  <bonzini@gnu.org>\n---\n Documentation/git-branch.txt |    9 ++-\n builtin-branch.c             |  126 ++++++++++++++++++++++++++++++++++++++++---\n cache.h                      |    1 \n trace.c                      |   14 ++--\n 4 files changed, 134 insertions(+), 16 deletions(-)\n\n\tThis version provides a variable-argument wrapper nfasprintf around\n\tthe git function nfvasprintf, and uses it instead of the GNU extension\n\tasprintf.\n\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 3ea3b80..bd65b98 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 `trackintolocalbranches` option of the\n+corresponding remote.  Otherwise, it can be chosen per-branch using\n+the `--track` 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/builtin-branch.c b/builtin-branch.c\nindex d371849..f39759b 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@@ -308,14 +308,99 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev)\n \tfree_ref_list(&ref_list);\n }\n \n+static int config_track;\n+static char *config_repo;\n+static const char *start_ref;\n+static int start_len;\n+static int remote_len;\n+\n+static void get_remote_branch_name(const char* value)\n+{\n+\tint len_first = -1, len_second = -1;\n+\tif (*value == '+')\n+\t\tvalue++;\n+\n+\t/* Try an exact match first.  */\n+\tsscanf(value, \"refs/%*[^:]:%n\", &len_first);\n+\tif (len_first != -1\n+\t    && !strcmp(value + len_first, start_ref)) {\n+\t\t/* Truncate the value before the colon.  */\n+\t\tnfasprintf(&config_repo, \"%.*s\", len_first - 1, value);\n+\t\treturn;\n+\t}\n+\n+\t/* Try with a wildcard match now.  */\n+\tsscanf(value, \"refs/%*[^/]/*:%nrefs/remotes/%*[^/]/*%n\",\n+\t       &len_first, &len_second);\n+\tif (len_first != -1 && len_second != -1\n+\t    && (len_second - 2) - len_first == remote_len + 13\n+\t    && !strncmp(value + len_first, start_ref, remote_len + 13)) {\n+\t\t/* Replace the star with the remote branch name.  */\n+\t\tnfasprintf(&config_repo, \"%.*s%s\",\n+\t\t\t   len_first - 3, value,\n+\t\t\t   start_ref + remote_len + 13);\n+\t}\n+}\n+\n+static int get_remote_config(const char* key, const char* value)\n+{\n+        if (!prefixcmp(key, \"remote.\") &&\n+            !strncmp(key + 7, start_ref + 13, remote_len)) {\n+\t\tif (config_track == -1\n+\t\t    && !strcmp(key + 7 + remote_len, \".trackintolocalbranches\"))\n+\t\t\tconfig_track = git_config_bool(key, value);\n+\n+\t\telse if (!strcmp(key + 7 + remote_len, \".fetch\"))\n+\t\t\tget_remote_branch_name(value);\n+        }\n+        return 0;\n+}\n+\n+static void set_branch_defaults(const char *name, const char *real_ref, int track)\n+{\n+\tchar key[1024], value[1024];\n+\tconst char *remote_name = real_ref + 13;\n+\tconst char *slash = strchr(remote_name, '/');\n+\n+\tif (!slash)\n+\t\treturn;\n+\n+\tstart_ref = real_ref;\n+\tstart_len = strlen(real_ref);\n+\tremote_len = slash - remote_name;\n+\tconfig_track = track;\n+\tgit_config(get_remote_config);\n+\n+\t/* Change to != 0 to enable this feature by default.  */\n+\tif (config_track == 1 && config_repo) {\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\tif (snprintf(value, sizeof(value), \"%.*s\", remote_len, remote_name)\n+\t\t    > sizeof(value))\n+\t\t\tdie(\"what a long branch name you have!\");\n+\n+\t\tgit_config_set(key, value);\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+}\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 +424,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 +447,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   branches registered using git-remote.  */\n+\tif (real_ref && !prefixcmp(real_ref, \"refs/remotes/\"))\n+\t\tset_branch_defaults(name, real_ref, track);\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,7 +499,7 @@ 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 = -1;\n \tint kinds = REF_LOCAL_BRANCH;\n \tint i;\n \n@@ -412,6 +514,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 +608,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 8018b2c..e1b8416 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -477,6 +477,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/trace.c b/trace.c\nindex 27fef86..ab177fa 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"}]}