{"thread":{"id":"5336","subject":"[PATCH] branch as a builtin (again)","startedAt":"2006-08-20T21:22:18Z","lastAt":"2006-08-22T07:00:32Z","messageCount":13,"participants":["Kristian Høgsberg","Johannes Schindelin","David Rientjes","Shawn Pearce","Jonas Fonseca","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"25659","messageId":"59ad55d30608201422h4a6d40f7y7782212637380438@mail.gmail.com","threadId":"5336","inReplyTo":null,"subject":"[PATCH] branch as a builtin (again)","fromName":"Kristian Høgsberg","fromEmail":"hoegsberg@gmail.com","sentAt":"2006-08-20T21:22:18Z","receivedAt":"2006-08-20T21:22:18Z","isPatch":true,"sender":{"key":"hoegsberg@gmail.com","avatar":null},"body":"Hi,\n\nI sent a patch to rewrite branch in C and make it a builtin a couple\nof months ago.  Junio had a few comments about the patch that I now\nfinally had the time to address.  One of the problems was that\nmerge-base didn't clean up its state, which has now been fixed.\nHere's the updated version.\n\nSigned-off-by: Kristian Høgsberg <krh@redhat.com>\n\n\ncommit 8153d336ad9643507922932a2ac2277073c66432\nAuthor: Kristian Høgsberg <krh@redhat.com>\nDate:   Sun Aug 20 17:04:14 2006 -0400\n\n    Rewrite branch in C and make it a builtin.\n\ndiff --git a/Makefile b/Makefile\nindex 23cd8a0..adf043e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -149,7 +149,7 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powe\n ### --- END CONFIGURATION SECTION ---\n \n SCRIPT_SH = \\\n-\tgit-bisect.sh git-branch.sh git-checkout.sh \\\n+\tgit-bisect.sh git-checkout.sh \\\n \tgit-cherry.sh git-clean.sh git-clone.sh git-commit.sh \\\n \tgit-fetch.sh \\\n \tgit-ls-remote.sh \\\n@@ -253,6 +253,7 @@ LIB_OBJS = \\\n BUILTIN_OBJS = \\\n \tbuiltin-add.o \\\n \tbuiltin-apply.o \\\n+\tbuiltin-branch.o \\\n \tbuiltin-cat-file.o \\\n \tbuiltin-checkout-index.o \\\n \tbuiltin-check-ref-format.o \\\ndiff --git a/builtin-branch.c b/builtin-branch.c\nnew file mode 100644\nindex 0000000..25c6313\n--- /dev/null\n+++ b/builtin-branch.c\n@@ -0,0 +1,170 @@\n+/*\n+ * Builtin \"git branch\"\n+ *\n+ * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>\n+ * Based on git-branch.sh by Junio C Hamano.\n+ */\n+\n+#include \"cache.h\"\n+#include \"refs.h\"\n+#include \"commit.h\"\n+#include \"builtin.h\"\n+\n+static const char builtin_branch_usage[] =\n+    \"git-branch [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r\";\n+\n+\n+static int remote_only = 0;\n+static const char *head;\n+static unsigned char head_sha1[20];\n+\n+static int in_merge_bases(const unsigned char *sha1,\n+\t\t\t  struct commit *rev1,\n+\t\t\t  struct commit *rev2)\n+{\n+\tstruct commit_list *bases, *b;\n+\n+\tbases = get_merge_bases(rev1, rev2, 1);\n+\tfor (b = bases; b != NULL; b = b->next) {\n+\t\tif (!hashcmp(sha1, b->item->object.sha1)) {\n+\t\t\tfree_commit_list(bases);\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\tfree_commit_list(bases);\n+\treturn 0;\n+}\n+\n+static void delete_branches(int argc, const char **argv, int force)\n+{\n+\tstruct commit *rev1, *rev2;\n+\tunsigned char sha1[20];\n+\tconst char *p, *name;\n+\tint i;\n+\n+\tfor (i = 0; i < argc; i++) {\n+\t\tif (!strcmp(head, argv[i]))\n+\t\t\tdie(\"Cannot delete the branch you are currently on.\");\n+\n+\t\tname = git_path(\"refs/heads/%s\", argv[i]);\n+\t\tp = resolve_ref(name, sha1, 1);\n+\t\tif (p == NULL)\n+\t\t\tdie(\"Branch '%s' not found.\", argv[i]);\n+\n+\t\trev1 = lookup_commit_reference(sha1);\n+\t\trev2 = lookup_commit_reference(head_sha1);\n+\t\tif (!rev1 || !rev2)\n+\t\t\tdie(\"Couldn't look up commit objects.\");\n+\n+\t\t/* This checks wether the merge bases of branch and\n+\t\t * HEAD contains branch -- which means that the HEAD\n+\t\t * contains everything in both.\n+\t\t */\n+\n+\t\tif (!force &&\n+\t\t    !in_merge_bases(sha1, rev1, rev2)) {\n+\t\t\tfprintf(stderr,\n+\t\t\t\t\"The branch '%s' is not a strict subset of your current HEAD.\\n\"\n+\t\t\t\t\"If you are sure you want to delete it, run 'git branch -D %s'.\\n\",\n+\t\t\t\targv[i], argv[i]);\n+\t\t\texit(1);\n+\t\t}\n+\n+\t\tunlink(name);\n+\t\tprintf(\"Deleted branch %s.\\n\", argv[i]);\n+\t}\n+}\n+\n+static int show_reference(const char *refname, const unsigned char *sha1)\n+{\n+\tint is_head = !strcmp(refname, head);\n+\n+\tprintf(\"%c %s\\n\", (is_head ? '*' : ' '), refname);\n+\n+\treturn 0;\n+}\n+\n+static void create_branch (const char *name, const char *start, int force)\n+{\n+\tstruct ref_lock *lock;\n+\tunsigned char sha1[20];\n+\tchar ref[PATH_MAX];\n+\n+\tsnprintf(ref, sizeof ref, \"refs/heads/%s\", name);\n+\tif (check_ref_format(ref + 5))\n+\t\tdie(\"'%s' is not a valid branch name.\", name);\n+\n+\tif (resolve_ref(ref, sha1, 1)) {\n+\t\tif (!force)\n+\t\t\tdie(\"A branch named '%s' already exists.\", name);\n+\t\telse if (!strcmp(head, name))\n+\t\t\tdie(\"Cannot force update the current branch.\");\n+\t}\n+\n+\tif (get_sha1(start, sha1))\n+\t\tdie(\"Not a valid branch point: '%s'\", start);\n+\n+\tlock = lock_any_ref_for_update(ref, NULL, 0);\n+\tif (!lock)\n+\t\tdie(\"Failed to lock ref for update: %s.\", strerror(errno));\n+\tif (write_ref_sha1(lock, sha1, NULL) < 0)\n+\t\tdie(\"Failed to write ref: %s.\", strerror(errno));\n+}\n+\n+int cmd_branch(int argc, const char **argv, const char *prefix)\n+{\n+\tint delete = 0, force_delete = 0, force_create = 0;\n+\tint i, prefix_length;\n+\tconst char *p;\n+\n+\tgit_config(git_default_config);\n+\n+\tfor (i = 1; i < argc; i++) {\n+\t\tconst char *arg = argv[i];\n+\n+\t\tif (arg[0] != '-')\n+\t\t\tbreak;\n+\t\tif (!strcmp(arg, \"--\")) {\n+\t\t\ti++;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-d\")) {\n+\t\t\tdelete = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-D\")) {\n+\t\t\tdelete = 1;\n+\t\t\tforce_delete = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-f\")) {\n+\t\t\tforce_create = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-r\")) {\n+\t\t\tremote_only = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tdie(builtin_branch_usage);\n+\t}\n+\n+\tprefix_length = strlen(git_path(\"refs/heads/\"));\n+\tp = resolve_ref(git_path(\"HEAD\"), head_sha1, 0);\n+\tif (!p)\n+\t\tdie(\"Failed to resolve HEAD as a valid ref\");\n+\thead = strdup(p + prefix_length);\n+\n+\tif (delete)\n+\t\tdelete_branches(argc - i, argv + i, force_delete);\n+\telse if (i == argc && remote_only)\n+\t\tfor_each_remote_ref(show_reference);\n+\telse if (i == argc)\n+\t\tfor_each_branch_ref(show_reference);\n+\telse if (argc - i == 1)\n+\t\tcreate_branch (argv[i], head, force_create);\n+\telse\n+\t\tcreate_branch (argv[i], argv[i + 1], force_create);\n+\n+\treturn 0;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex ade58c4..eb28986 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -15,6 +15,7 @@ extern int write_tree(unsigned char *sha\n \n extern int cmd_add(int argc, const char **argv, const char *prefix);\n extern int cmd_apply(int argc, const char **argv, const char *prefix);\n+extern int cmd_branch(int argc, const char **argv, const char *prefix);\n extern int cmd_cat_file(int argc, const char **argv, const char *prefix);\n extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);\n extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);\ndiff --git a/git-branch.sh b/git-branch.sh\ndeleted file mode 100755\nindex e0501ec..0000000\n--- a/git-branch.sh\n+++ /dev/null\n@@ -1,130 +0,0 @@\n-#!/bin/sh\n-\n-USAGE='[-l] [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r'\n-LONG_USAGE='If no arguments, show available branches and mark current branch with a star.\n-If one argument, create a new branch <branchname> based off of current HEAD.\n-If two arguments, create a new branch <branchname> based off of <start-point>.'\n-\n-SUBDIRECTORY_OK='Yes'\n-. git-sh-setup\n-\n-headref=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||')\n-\n-delete_branch () {\n-    option=\"$1\"\n-    shift\n-    for branch_name\n-    do\n-\tcase \",$headref,\" in\n-\t\",$branch_name,\")\n-\t    die \"Cannot delete the branch you are on.\" ;;\n-\t,,)\n-\t    die \"What branch are you on anyway?\" ;;\n-\tesac\n-\tbranch=$(cat \"$GIT_DIR/refs/heads/$branch_name\") &&\n-\t    branch=$(git-rev-parse --verify \"$branch^0\") ||\n-\t\tdie \"Seriously, what branch are you talking about?\"\n-\tcase \"$option\" in\n-\t-D)\n-\t    ;;\n-\t*)\n-\t    mbs=$(git-merge-base -a \"$branch\" HEAD | tr '\\012' ' ')\n-\t    case \" $mbs \" in\n-\t    *' '$branch' '*)\n-\t\t# the merge base of branch and HEAD contains branch --\n-\t\t# which means that the HEAD contains everything in both.\n-\t\t;;\n-\t    *)\n-\t\techo >&2 \"The branch '$branch_name' is not a strict subset of your current HEAD.\n-If you are sure you want to delete it, run 'git branch -D $branch_name'.\"\n-\t\texit 1\n-\t\t;;\n-\t    esac\n-\t    ;;\n-\tesac\n-\trm -f \"$GIT_DIR/logs/refs/heads/$branch_name\"\n-\trm -f \"$GIT_DIR/refs/heads/$branch_name\"\n-\techo \"Deleted branch $branch_name.\"\n-    done\n-    exit 0\n-}\n-\n-ls_remote_branches () {\n-    git-rev-parse --symbolic --all |\n-    sed -ne 's|^refs/\\(remotes/\\)|\\1|p' |\n-    sort\n-}\n-\n-force=\n-create_log=\n-while case \"$#,$1\" in 0,*) break ;; *,-*) ;; *) break ;; esac\n-do\n-\tcase \"$1\" in\n-\t-d | -D)\n-\t\tdelete_branch \"$@\"\n-\t\texit\n-\t\t;;\n-\t-r)\n-\t\tls_remote_branches\n-\t\texit\n-\t\t;;\n-\t-f)\n-\t\tforce=\"$1\"\n-\t\t;;\n-\t-l)\n-\t\tcreate_log=\"yes\"\n-\t\t;;\n-\t--)\n-\t\tshift\n-\t\tbreak\n-\t\t;;\n-\t-*)\n-\t\tusage\n-\t\t;;\n-\tesac\n-\tshift\n-done\n-\n-case \"$#\" in\n-0)\n-\tgit-rev-parse --symbolic --branches |\n-\tsort |\n-\twhile read ref\n-\tdo\n-\t\tif test \"$headref\" = \"$ref\"\n-\t\tthen\n-\t\t\tpfx='*'\n-\t\telse\n-\t\t\tpfx=' '\n-\t\tfi\n-\t\techo \"$pfx $ref\"\n-\tdone\n-\texit 0 ;;\n-1)\n-\thead=HEAD ;;\n-2)\n-\thead=\"$2^0\" ;;\n-esac\n-branchname=\"$1\"\n-\n-rev=$(git-rev-parse --verify \"$head\") || exit\n-\n-git-check-ref-format \"heads/$branchname\" ||\n-\tdie \"we do not like '$branchname' as a branch name.\"\n-\n-if [ -e \"$GIT_DIR/refs/heads/$branchname\" ]\n-then\n-\tif test '' = \"$force\"\n-\tthen\n-\t\tdie \"$branchname already exists.\"\n-\telif test \"$branchname\" = \"$headref\"\n-\tthen\n-\t\tdie \"cannot force-update the current branch.\"\n-\tfi\n-fi\n-if test \"$create_log\" = 'yes'\n-then\n-\tmkdir -p $(dirname \"$GIT_DIR/logs/refs/heads/$branchname\")\n-\ttouch \"$GIT_DIR/logs/refs/heads/$branchname\"\n-fi\n-git update-ref -m \"branch: Created from $head\" \"refs/heads/$branchname\" $rev\ndiff --git a/git.c b/git.c\nindex 930998b..5738cb4 100644\n--- a/git.c\n+++ b/git.c\n@@ -226,6 +226,7 @@ static void handle_internal_command(int \n \t} commands[] = {\n \t\t{ \"add\", cmd_add, RUN_SETUP },\n \t\t{ \"apply\", cmd_apply },\n+\t\t{ \"branch\", cmd_branch, RUN_SETUP },\n \t\t{ \"cat-file\", cmd_cat_file, RUN_SETUP },\n \t\t{ \"checkout-index\", cmd_checkout_index, RUN_SETUP },\n \t\t{ \"check-ref-format\", cmd_check_ref_format },\n"},{"id":"25666","messageId":"Pine.LNX.4.63.0608210145200.28360@wbgn013.biozentrum.uni-wuerzburg.de","threadId":"5336","inReplyTo":"59ad55d30608201422h4a6d40f7y7782212637380438@mail.gmail.com","subject":"Re: [PATCH] branch as a builtin (again)","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2006-08-20T23:55:56Z","receivedAt":"2006-08-20T23:55:56Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sun, 20 Aug 2006, Kristian Høgsberg wrote:\n\n> Hi,\n> \n> I sent a patch to rewrite branch in C and make it a builtin a couple\n> of months ago.  Junio had a few comments about the patch that I now\n> finally had the time to address.  One of the problems was that\n> merge-base didn't clean up its state, which has now been fixed.\n> Here's the updated version.\n> \n> Signed-off-by: Kristian Høgsberg <krh@redhat.com>\n\nMicro-nit: in delete_branches(), you can assign rev2 outside the loop.\n\nAlso, \"git branch\" no longer sorts the names alphabetically (you could fix \nthis by putting the names into a path_list, which sorts them).\n\nThe \"-l\" option (create a log) seems to be forgotten, but then, I never \nuse that anyway.\n\nAFAICT the rest is fine.\n\nCiao,\nDscho\n"},{"id":"25677","messageId":"Pine.LNX.4.63.0608210045450.17336@chino.corp.google.com","threadId":"5336","inReplyTo":"59ad55d30608201422h4a6d40f7y7782212637380438@mail.gmail.com","subject":"Re: [PATCH] branch as a builtin (again)","fromName":"David Rientjes","fromEmail":"rientjes@google.com","sentAt":"2006-08-21T07:49:41Z","receivedAt":"2006-08-21T07:49:41Z","isPatch":true,"sender":{"key":"rientjes@google.com","avatar":null},"body":"On Sun, 20 Aug 2006, Kristian Høgsberg wrote:\n\n> diff --git a/builtin-branch.c b/builtin-branch.c\n> new file mode 100644\n> index 0000000..25c6313\n> --- /dev/null\n> +++ b/builtin-branch.c\n> @@ -0,0 +1,170 @@\n> +/*\n> + * Builtin \"git branch\"\n> + *\n> + * Copyright (c) 2006 Kristian HÃ¸gsberg <krh@redhat.com>\n> + * Based on git-branch.sh by Junio C Hamano.\n> + */\n> +\n> +#include \"cache.h\"\n> +#include \"refs.h\"\n> +#include \"commit.h\"\n> +#include \"builtin.h\"\n> +\n> +static const char builtin_branch_usage[] =\n> +    \"git-branch [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r\";\n> +\n> +\n> +static int remote_only = 0;\n\nUnnecessary initialization\n\n> +static const char *head;\n> +static unsigned char head_sha1[20];\n> +\n> +static int in_merge_bases(const unsigned char *sha1,\n> +\t\t\t  struct commit *rev1,\n> +\t\t\t  struct commit *rev2)\n> +{\n> +\tstruct commit_list *bases, *b;\n> +\n> +\tbases = get_merge_bases(rev1, rev2, 1);\n> +\tfor (b = bases; b != NULL; b = b->next) {\n> +\t\tif (!hashcmp(sha1, b->item->object.sha1)) {\n> +\t\t\tfree_commit_list(bases);\n> +\t\t\treturn 1;\n> +\t\t}\n> +\t}\n> +\n> +\tfree_commit_list(bases);\n> +\treturn 0;\n\nMake it cleaner for the future:\n{\n\tint ret = 0;\n\t...\n\tfor (b = bases; b; b = b->next) {\n\t\tif (!hashcmp(sha1, b->item->object.sha1)) {\n\t\t\tret = 1;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tfree_commit_list(bases);\n\treturn ret;\n}\n\n> +}\n> +\n> +static void delete_branches(int argc, const char **argv, int force)\n> +{\n> +\tstruct commit *rev1, *rev2;\n> +\tunsigned char sha1[20];\n> +\tconst char *p, *name;\n> +\tint i;\n> +\n> +\tfor (i = 0; i < argc; i++) {\n> +\t\tif (!strcmp(head, argv[i]))\n> +\t\t\tdie(\"Cannot delete the branch you are currently on.\");\n> +\n> +\t\tname = git_path(\"refs/heads/%s\", argv[i]);\n> +\t\tp = resolve_ref(name, sha1, 1);\n> +\t\tif (p == NULL)\n> +\t\t\tdie(\"Branch '%s' not found.\", argv[i]);\n\np is unnecessary:\n\tif (!resolve_ref(name, sha1, 1))\n\t\tdie(...);\n\n> +\n> +\t\trev1 = lookup_commit_reference(sha1);\n> +\t\trev2 = lookup_commit_reference(head_sha1);\n\nAre these both needed inside every iteration?\n\n> +\t\tif (!rev1 || !rev2)\n> +\t\t\tdie(\"Couldn't look up commit objects.\");\n> +\n> +\t\t/* This checks wether the merge bases of branch and\n> +\t\t * HEAD contains branch -- which means that the HEAD\n> +\t\t * contains everything in both.\n> +\t\t */\n> +\n> +\t\tif (!force &&\n> +\t\t    !in_merge_bases(sha1, rev1, rev2)) {\n> +\t\t\tfprintf(stderr,\n> +\t\t\t\t\"The branch '%s' is not a strict subset of your current HEAD.\\n\"\n> +\t\t\t\t\"If you are sure you want to delete it, run 'git branch -D %s'.\\n\",\n> +\t\t\t\targv[i], argv[i]);\n> +\t\t\texit(1);\n> +\t\t}\n> +\n> +\t\tunlink(name);\n> +\t\tprintf(\"Deleted branch %s.\\n\", argv[i]);\n> +\t}\n> +}\n> +\n> +static int show_reference(const char *refname, const unsigned char *sha1)\n> +{\n> +\tint is_head = !strcmp(refname, head);\n> +\n> +\tprintf(\"%c %s\\n\", (is_head ? '*' : ' '), refname);\n> +\n> +\treturn 0;\n\nUnnecessary variable\n\n> +}\n> +\n> +static void create_branch (const char *name, const char *start, int force)\n\nNo space\n\n\t\tDavid"},{"id":"25679","messageId":"20060821080345.GA26191@spearce.org","threadId":"5336","inReplyTo":"Pine.LNX.4.63.0608210045450.17336@chino.corp.google.com","subject":"Re: [PATCH] branch as a builtin (again)","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-08-21T08:03:45Z","receivedAt":"2006-08-21T08:03:45Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"> On Sun, 20 Aug 2006, Kristian Høgsberg wrote:\n> > diff --git a/builtin-branch.c b/builtin-branch.c\n> > +static const char builtin_branch_usage[] =\n> > +    \"git-branch [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r\";\n\nWhat happened to the -l switch to create a reflog for the new branch?\n\n> > +static void delete_branches(int argc, const char **argv, int force)\n\nWhat happened to deleting the reflog when the branch gets deleted?\n\n\nIn both cases please see the existing git-branch documentation and\nshell script.\n\nI applaud the effort of removing some of these smaller shell scripts\nwith more portable C code but at the same time I'd hate to see a\nloss of functionality, especially something that I use!  :-)\n\n-- \nShawn.\n"},{"id":"25685","messageId":"20060821101346.GA527@diku.dk","threadId":"5336","inReplyTo":"59ad55d30608201422h4a6d40f7y7782212637380438@mail.gmail.com","subject":"Re: [PATCH] branch as a builtin (again)","fromName":"Jonas Fonseca","fromEmail":"fonseca@diku.dk","sentAt":"2006-08-21T10:13:46Z","receivedAt":"2006-08-21T10:13:46Z","isPatch":true,"sender":{"key":"fonseca@diku.dk","avatar":"https://gravatar.com/avatar/f82f3ad698717c51873b020c750a92438c820a24056dc39fe4d07baa10a92264?d=mp&s=160"},"body":"Kristian Høgsberg <hoegsberg@gmail.com> wrote Sun, Aug 20, 2006:\n> diff --git a/builtin-branch.c b/builtin-branch.c\n> new file mode 100644\n> index 0000000..25c6313\n> --- /dev/null\n> +++ b/builtin-branch.c\n> @@ -0,0 +1,170 @@\n> +/*\n> + * Builtin \"git branch\"\n> + *\n> + * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>\n> + * Based on git-branch.sh by Junio C Hamano.\n> + */\n> +\n> +#include \"cache.h\"\n> +#include \"refs.h\"\n> +#include \"commit.h\"\n> +#include \"builtin.h\"\n> +\n> +static const char builtin_branch_usage[] =\n> +    \"git-branch [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r\";\n\nThe norm seems to be that the usage string should not be indented.\n\n> +\n> +\n> +static int remote_only = 0;\n\nThis could be local to cmd_branch. \n\n> +static const char *head;\n> +static unsigned char head_sha1[20];\n> +\n> +static int in_merge_bases(const unsigned char *sha1,\n> +\t\t\t  struct commit *rev1,\n> +\t\t\t  struct commit *rev2)\n> +{\n> +\tstruct commit_list *bases, *b;\n> +\n> +\tbases = get_merge_bases(rev1, rev2, 1);\n> +\tfor (b = bases; b != NULL; b = b->next) {\n> +\t\tif (!hashcmp(sha1, b->item->object.sha1)) {\n> +\t\t\tfree_commit_list(bases);\n> +\t\t\treturn 1;\n> +\t\t}\n> +\t}\n> +\n> +\tfree_commit_list(bases);\n> +\treturn 0;\n> +}\n> +\n> +static void delete_branches(int argc, const char **argv, int force)\n> +{\n> +\tstruct commit *rev1, *rev2;\n> +\tunsigned char sha1[20];\n> +\tconst char *p, *name;\n> +\tint i;\n> +\n> +\tfor (i = 0; i < argc; i++) {\n> +\t\tif (!strcmp(head, argv[i]))\n> +\t\t\tdie(\"Cannot delete the branch you are currently on.\");\n> +\n> +\t\tname = git_path(\"refs/heads/%s\", argv[i]);\n> +\t\tp = resolve_ref(name, sha1, 1);\n> +\t\tif (p == NULL)\n> +\t\t\tdie(\"Branch '%s' not found.\", argv[i]);\n> +\n> +\t\trev1 = lookup_commit_reference(sha1);\n> +\t\trev2 = lookup_commit_reference(head_sha1);\n> +\t\tif (!rev1 || !rev2)\n> +\t\t\tdie(\"Couldn't look up commit objects.\");\n> +\n> +\t\t/* This checks wether the merge bases of branch and\n\n\t\t\t       whether\n\n> +\t\t * HEAD contains branch -- which means that the HEAD\n> +\t\t * contains everything in both.\n> +\t\t */\n> +\n> +\t\tif (!force &&\n> +\t\t    !in_merge_bases(sha1, rev1, rev2)) {\n> +\t\t\tfprintf(stderr,\n> +\t\t\t\t\"The branch '%s' is not a strict subset of your current HEAD.\\n\"\n> +\t\t\t\t\"If you are sure you want to delete it, run 'git branch -D %s'.\\n\",\n> +\t\t\t\targv[i], argv[i]);\n> +\t\t\texit(1);\n> +\t\t}\n> +\n> +\t\tunlink(name);\n> +\t\tprintf(\"Deleted branch %s.\\n\", argv[i]);\n> +\t}\n> +}\n> +\n> +static int show_reference(const char *refname, const unsigned char *sha1)\n> +{\n> +\tint is_head = !strcmp(refname, head);\n> +\n> +\tprintf(\"%c %s\\n\", (is_head ? '*' : ' '), refname);\n> +\n> +\treturn 0;\n> +}\n> +\n> +static void create_branch (const char *name, const char *start, int force)\n> +{\n> +\tstruct ref_lock *lock;\n> +\tunsigned char sha1[20];\n> +\tchar ref[PATH_MAX];\n> +\n> +\tsnprintf(ref, sizeof ref, \"refs/heads/%s\", name);\n> +\tif (check_ref_format(ref + 5))\n> +\t\tdie(\"'%s' is not a valid branch name.\", name);\n\nWhy not simply check_ref_format(name)?\n\n> +\n> +\tif (resolve_ref(ref, sha1, 1)) {\n\nAll other places that call resolve_ref passes a ref created with\ngit_path. I don't know if this should too.\n\n> +\t\tif (!force)\n> +\t\t\tdie(\"A branch named '%s' already exists.\", name);\n> +\t\telse if (!strcmp(head, name))\n> +\t\t\tdie(\"Cannot force update the current branch.\");\n> +\t}\n> +\n> +\tif (get_sha1(start, sha1))\n> +\t\tdie(\"Not a valid branch point: '%s'\", start);\n\nMissing punctuation at the end.\n\n> +\n> +\tlock = lock_any_ref_for_update(ref, NULL, 0);\n> +\tif (!lock)\n> +\t\tdie(\"Failed to lock ref for update: %s.\", strerror(errno));\n> +\tif (write_ref_sha1(lock, sha1, NULL) < 0)\n> +\t\tdie(\"Failed to write ref: %s.\", strerror(errno));\n> +}\n> +\n> +int cmd_branch(int argc, const char **argv, const char *prefix)\n> +{\n> +\tint delete = 0, force_delete = 0, force_create = 0;\n> +\tint i, prefix_length;\n> +\tconst char *p;\n> +\n> +\tgit_config(git_default_config);\n> +\n> +\tfor (i = 1; i < argc; i++) {\n> +\t\tconst char *arg = argv[i];\n> +\n> +\t\tif (arg[0] != '-')\n> +\t\t\tbreak;\n> +\t\tif (!strcmp(arg, \"--\")) {\n> +\t\t\ti++;\n> +\t\t\tbreak;\n> +\t\t}\n> +\t\tif (!strcmp(arg, \"-d\")) {\n> +\t\t\tdelete = 1;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\t\tif (!strcmp(arg, \"-D\")) {\n> +\t\t\tdelete = 1;\n> +\t\t\tforce_delete = 1;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\t\tif (!strcmp(arg, \"-f\")) {\n> +\t\t\tforce_create = 1;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\t\tif (!strcmp(arg, \"-r\")) {\n> +\t\t\tremote_only = 1;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\t\tdie(builtin_branch_usage);\n\nPerhaps usage() would be more appropriate here.\n\n> +\t}\n> +\n> +\tprefix_length = strlen(git_path(\"refs/heads/\"));\n> +\tp = resolve_ref(git_path(\"HEAD\"), head_sha1, 0);\n> +\tif (!p)\n> +\t\tdie(\"Failed to resolve HEAD as a valid ref\");\n\nEnding punctuation.\n\n> +\thead = strdup(p + prefix_length);\n> +\n> +\tif (delete)\n> +\t\tdelete_branches(argc - i, argv + i, force_delete);\n> +\telse if (i == argc && remote_only)\n> +\t\tfor_each_remote_ref(show_reference);\n> +\telse if (i == argc)\n> +\t\tfor_each_branch_ref(show_reference);\n> +\telse if (argc - i == 1)\n> +\t\tcreate_branch (argv[i], head, force_create);\n> +\telse\n> +\t\tcreate_branch (argv[i], argv[i + 1], force_create);\n\nIt would be more consistent to leave out the space before the\nparanthesis. Also goes for the implementation of create_branch as\nalready mentioned.\n\n> +\n> +\treturn 0;\n> +}\n\n-- \nJonas Fonseca\n"},{"id":"25704","messageId":"59ad55d30608211312u51a4657eyd52311314a6ee03c@mail.gmail.com","threadId":"5336","inReplyTo":"20060821101346.GA527@diku.dk","subject":"Re: [PATCH] branch as a builtin (again)","fromName":"Kristian Høgsberg","fromEmail":"krh@bitplanet.net","sentAt":"2006-08-21T20:12:41Z","receivedAt":"2006-08-21T20:12:41Z","isPatch":true,"sender":{"key":"krh@bitplanet.net","avatar":"https://gravatar.com/avatar/6ba3f320dfeafb6821e9824d11122fe7157e39c443476c4cf35d6dd830052d1d?d=mp&s=160"},"body":"Thanks to all who reviewed the patch, here's an updated version which\nshould address all issues.  In particular, thanks to Jonas who spotted\nthe missing git_path() before resolve_ref() - this caused git branch\nto always overwrite existing branches because it failed to resolve the\nref.\n\nAs for the missing reflog functionality - when I first did the shell\nto C port, git branch didn't have this feature, and this time I just\nupdated the old patch which is how I forgot about the reflog option.\nThe new patch attached here should have the same reflog behavior as\nthe current shell script.  Shawn, could you check that it has the\ncorrect sematics?  I wasn't sure whether git branch -f foo should\ntruncate the old log or just keep appending.  The shell script does a\n'touch <logfile>' on creation, which means keep appending when\nforcibly overwriting a branch ref, so I kept that behavior.\n\ncheers,\nKristian\n\n\ncommit d5d82b9c4493df467ee31776cadff808563f00b1\nAuthor: Kristian Høgsberg <krh@redhat.com>\nDate:   Sun Aug 20 17:04:14 2006 -0400\n\n    Rewrite branch in C and make it a builtin.\n\ndiff --git a/Makefile b/Makefile\nindex 23cd8a0..adf043e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -149,7 +149,7 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powe\n ### --- END CONFIGURATION SECTION ---\n \n SCRIPT_SH = \\\n-\tgit-bisect.sh git-branch.sh git-checkout.sh \\\n+\tgit-bisect.sh git-checkout.sh \\\n \tgit-cherry.sh git-clean.sh git-clone.sh git-commit.sh \\\n \tgit-fetch.sh \\\n \tgit-ls-remote.sh \\\n@@ -253,6 +253,7 @@ LIB_OBJS = \\\n BUILTIN_OBJS = \\\n \tbuiltin-add.o \\\n \tbuiltin-apply.o \\\n+\tbuiltin-branch.o \\\n \tbuiltin-cat-file.o \\\n \tbuiltin-checkout-index.o \\\n \tbuiltin-check-ref-format.o \\\ndiff --git a/builtin-branch.c b/builtin-branch.c\nnew file mode 100644\nindex 0000000..4e8fa7d\n--- /dev/null\n+++ b/builtin-branch.c\n@@ -0,0 +1,227 @@\n+/*\n+ * Builtin \"git branch\"\n+ *\n+ * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>\n+ * Based on git-branch.sh by Junio C Hamano.\n+ */\n+\n+#include \"cache.h\"\n+#include \"refs.h\"\n+#include \"commit.h\"\n+#include \"builtin.h\"\n+\n+static const char builtin_branch_usage[] =\n+\"git-branch [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r\";\n+\n+\n+static const char *head;\n+static unsigned char head_sha1[20];\n+\n+static int in_merge_bases(const unsigned char *sha1,\n+\t\t\t  struct commit *rev1,\n+\t\t\t  struct commit *rev2)\n+{\n+\tstruct commit_list *bases, *b;\n+\tint ret = 0;\n+\n+\tbases = get_merge_bases(rev1, rev2, 1);\n+\tfor (b = bases; b != NULL; b = b->next) {\n+\t\tif (!hashcmp(sha1, b->item->object.sha1)) {\n+\t\t\tret = 1;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tfree_commit_list(bases);\n+\treturn ret;\n+}\n+\n+static void delete_branches(int argc, const char **argv, int force)\n+{\n+\tstruct commit *rev, *head_rev;\n+\tunsigned char sha1[20];\n+\tconst char *name, *reflog;\n+\tint i;\n+\n+\thead_rev = lookup_commit_reference(head_sha1);\n+\tfor (i = 0; i < argc; i++) {\n+\t\tif (!strcmp(head, argv[i]))\n+\t\t\tdie(\"Cannot delete the branch you are currently on.\");\n+\n+\t\tname = git_path(\"refs/heads/%s\", argv[i]);\n+\t\tif (!resolve_ref(name, sha1, 1))\n+\t\t\tdie(\"Branch '%s' not found.\", argv[i]);\n+\n+\t\trev = lookup_commit_reference(sha1);\n+\t\tif (!rev || !head_rev)\n+\t\t\tdie(\"Couldn't look up commit objects.\");\n+\n+\t\t/* This checks whether the merge bases of branch and\n+\t\t * HEAD contains branch -- which means that the HEAD\n+\t\t * contains everything in both.\n+\t\t */\n+\n+\t\tif (!force &&\n+\t\t    !in_merge_bases(sha1, rev, head_rev)) {\n+\t\t\tfprintf(stderr,\n+\t\t\t\t\"The branch '%s' is not a strict subset of your current HEAD.\\n\"\n+\t\t\t\t\"If you are sure you want to delete it, run 'git branch -D %s'.\\n\",\n+\t\t\t\targv[i], argv[i]);\n+\t\t\texit(1);\n+\t\t}\n+\n+\t\tunlink(name);\n+\n+\t\t/* Unlink reflog if it exists. */\n+\t\treflog = git_path(\"logs/refs/heads/%s\", argv[i]);\n+\t\tunlink(reflog);\n+\n+\t\tprintf(\"Deleted branch %s.\\n\", argv[i]);\n+\t}\n+}\n+\n+static int ref_index, ref_alloc;\n+static char **ref_list;\n+\n+static int append_ref(const char *refname, const unsigned char *sha1)\n+{\n+\tif (ref_index >= ref_alloc) {\n+\t\tref_alloc = ref_alloc > 0 ? ref_alloc * 2 : 16;\n+\t\tref_list = realloc(ref_list, ref_alloc * sizeof (char *));\n+\t}\n+\n+\tref_list[ref_index++] = strdup(refname);\n+\n+\treturn 0;\n+}\n+\n+static int ref_cmp (const void *r1, const void *r2)\n+{\n+\treturn strcmp (*(char **)r1, *(char **)r2);\n+}\n+\n+static void print_ref_list(int remote_only)\n+{\n+\tint i;\n+\n+\tif (remote_only)\n+\t\tfor_each_remote_ref(append_ref);\n+\telse\n+\t\tfor_each_branch_ref(append_ref);\n+\n+\tqsort(ref_list, ref_index, sizeof (char *), ref_cmp);\n+\n+\tfor (i = 0; i < ref_index; i++) {\n+\t\tif (!strcmp(ref_list[i], head))\n+\t\t\tprintf(\"* %s\\n\", ref_list[i]);\n+\t\telse\n+\t\t\tprintf(\"  %s\\n\", ref_list[i]);\n+\t}\n+}\n+\n+static void create_reflog(struct ref_lock *lock)\n+{\n+\tstruct stat stbuf;\n+\tint fd;\n+\n+\tif (!stat(lock->log_file, &stbuf) && S_ISREG(stbuf.st_mode))\n+\t\treturn;\n+\tif (safe_create_leading_directories(lock->log_file) < 0)\n+\t\tdie(\"Unable to create directory for %s.\", lock->log_file);\n+\tfd = open(lock->log_file, O_CREAT | O_TRUNC | O_WRONLY, 0666);\n+\tif (fd < 0)\n+\t\tdie(\"Unable to create ref log %s: %s.\",\n+\t\t    lock->log_file, strerror(errno));\n+\tclose(fd);\n+}\n+\n+static void create_branch(const char *name, const char *start,\n+\t\t\t  int force, int reflog)\n+{\n+\tstruct ref_lock *lock;\n+\tunsigned char sha1[20];\n+\tchar ref[PATH_MAX], msg[PATH_MAX + 20];\n+\n+\tsnprintf(ref, sizeof ref, \"refs/heads/%s\", name);\n+\tif (check_ref_format(ref))\n+\t\tdie(\"'%s' is not a valid branch name.\", name);\n+\n+\tif (resolve_ref(git_path(ref), sha1, 1)) {\n+\t\tif (!force)\n+\t\t\tdie(\"A branch named '%s' already exists.\", name);\n+\t\telse if (!strcmp(head, name))\n+\t\t\tdie(\"Cannot force update the current branch.\");\n+\t}\n+\n+\tif (get_sha1(start, sha1))\n+\t\tdie(\"Not a valid branch point: '%s'.\", start);\n+\n+\tlock = lock_any_ref_for_update(ref, NULL, 0);\n+\tif (!lock)\n+\t\tdie(\"Failed to lock ref for update: %s.\", strerror(errno));\n+\tif (reflog)\n+\t\tcreate_reflog(lock);\n+\tsnprintf(msg, sizeof msg, \"branch: Created from %s\", start);\n+\tif (write_ref_sha1(lock, sha1, msg) < 0)\n+\t\tdie(\"Failed to write ref: %s.\", strerror(errno));\n+}\n+\n+int cmd_branch(int argc, const char **argv, const char *prefix)\n+{\n+\tint delete = 0, force_delete = 0, force_create = 0, remote_only = 0;\n+\tint reflog = 0;\n+\tint i, prefix_length;\n+\tconst char *p;\n+\n+\tgit_config(git_default_config);\n+\n+\tfor (i = 1; i < argc; i++) {\n+\t\tconst char *arg = argv[i];\n+\n+\t\tif (arg[0] != '-')\n+\t\t\tbreak;\n+\t\tif (!strcmp(arg, \"--\")) {\n+\t\t\ti++;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-d\")) {\n+\t\t\tdelete = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-D\")) {\n+\t\t\tdelete = 1;\n+\t\t\tforce_delete = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-f\")) {\n+\t\t\tforce_create = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-r\")) {\n+\t\t\tremote_only = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-l\")) {\n+\t\t\treflog = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tusage(builtin_branch_usage);\n+\t}\n+\n+\tprefix_length = strlen(git_path(\"refs/heads/\"));\n+\tp = resolve_ref(git_path(\"HEAD\"), head_sha1, 0);\n+\tif (!p)\n+\t\tdie(\"Failed to resolve HEAD as a valid ref.\");\n+\thead = strdup(p + prefix_length);\n+\n+\tif (delete)\n+\t\tdelete_branches(argc - i, argv + i, force_delete);\n+\telse if (i == argc)\n+\t\tprint_ref_list(remote_only);\n+\telse if (argc - i == 1)\n+\t\tcreate_branch(argv[i], head, force_create, reflog);\n+\telse\n+\t\tcreate_branch(argv[i], argv[i + 1], force_create, reflog);\n+\n+\treturn 0;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex ade58c4..eb28986 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -15,6 +15,7 @@ extern int write_tree(unsigned char *sha\n \n extern int cmd_add(int argc, const char **argv, const char *prefix);\n extern int cmd_apply(int argc, const char **argv, const char *prefix);\n+extern int cmd_branch(int argc, const char **argv, const char *prefix);\n extern int cmd_cat_file(int argc, const char **argv, const char *prefix);\n extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);\n extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);\ndiff --git a/git-branch.sh b/git-branch.sh\ndeleted file mode 100755\nindex e0501ec..0000000\n--- a/git-branch.sh\n+++ /dev/null\n@@ -1,130 +0,0 @@\n-#!/bin/sh\n-\n-USAGE='[-l] [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r'\n-LONG_USAGE='If no arguments, show available branches and mark current branch with a star.\n-If one argument, create a new branch <branchname> based off of current HEAD.\n-If two arguments, create a new branch <branchname> based off of <start-point>.'\n-\n-SUBDIRECTORY_OK='Yes'\n-. git-sh-setup\n-\n-headref=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||')\n-\n-delete_branch () {\n-    option=\"$1\"\n-    shift\n-    for branch_name\n-    do\n-\tcase \",$headref,\" in\n-\t\",$branch_name,\")\n-\t    die \"Cannot delete the branch you are on.\" ;;\n-\t,,)\n-\t    die \"What branch are you on anyway?\" ;;\n-\tesac\n-\tbranch=$(cat \"$GIT_DIR/refs/heads/$branch_name\") &&\n-\t    branch=$(git-rev-parse --verify \"$branch^0\") ||\n-\t\tdie \"Seriously, what branch are you talking about?\"\n-\tcase \"$option\" in\n-\t-D)\n-\t    ;;\n-\t*)\n-\t    mbs=$(git-merge-base -a \"$branch\" HEAD | tr '\\012' ' ')\n-\t    case \" $mbs \" in\n-\t    *' '$branch' '*)\n-\t\t# the merge base of branch and HEAD contains branch --\n-\t\t# which means that the HEAD contains everything in both.\n-\t\t;;\n-\t    *)\n-\t\techo >&2 \"The branch '$branch_name' is not a strict subset of your current HEAD.\n-If you are sure you want to delete it, run 'git branch -D $branch_name'.\"\n-\t\texit 1\n-\t\t;;\n-\t    esac\n-\t    ;;\n-\tesac\n-\trm -f \"$GIT_DIR/logs/refs/heads/$branch_name\"\n-\trm -f \"$GIT_DIR/refs/heads/$branch_name\"\n-\techo \"Deleted branch $branch_name.\"\n-    done\n-    exit 0\n-}\n-\n-ls_remote_branches () {\n-    git-rev-parse --symbolic --all |\n-    sed -ne 's|^refs/\\(remotes/\\)|\\1|p' |\n-    sort\n-}\n-\n-force=\n-create_log=\n-while case \"$#,$1\" in 0,*) break ;; *,-*) ;; *) break ;; esac\n-do\n-\tcase \"$1\" in\n-\t-d | -D)\n-\t\tdelete_branch \"$@\"\n-\t\texit\n-\t\t;;\n-\t-r)\n-\t\tls_remote_branches\n-\t\texit\n-\t\t;;\n-\t-f)\n-\t\tforce=\"$1\"\n-\t\t;;\n-\t-l)\n-\t\tcreate_log=\"yes\"\n-\t\t;;\n-\t--)\n-\t\tshift\n-\t\tbreak\n-\t\t;;\n-\t-*)\n-\t\tusage\n-\t\t;;\n-\tesac\n-\tshift\n-done\n-\n-case \"$#\" in\n-0)\n-\tgit-rev-parse --symbolic --branches |\n-\tsort |\n-\twhile read ref\n-\tdo\n-\t\tif test \"$headref\" = \"$ref\"\n-\t\tthen\n-\t\t\tpfx='*'\n-\t\telse\n-\t\t\tpfx=' '\n-\t\tfi\n-\t\techo \"$pfx $ref\"\n-\tdone\n-\texit 0 ;;\n-1)\n-\thead=HEAD ;;\n-2)\n-\thead=\"$2^0\" ;;\n-esac\n-branchname=\"$1\"\n-\n-rev=$(git-rev-parse --verify \"$head\") || exit\n-\n-git-check-ref-format \"heads/$branchname\" ||\n-\tdie \"we do not like '$branchname' as a branch name.\"\n-\n-if [ -e \"$GIT_DIR/refs/heads/$branchname\" ]\n-then\n-\tif test '' = \"$force\"\n-\tthen\n-\t\tdie \"$branchname already exists.\"\n-\telif test \"$branchname\" = \"$headref\"\n-\tthen\n-\t\tdie \"cannot force-update the current branch.\"\n-\tfi\n-fi\n-if test \"$create_log\" = 'yes'\n-then\n-\tmkdir -p $(dirname \"$GIT_DIR/logs/refs/heads/$branchname\")\n-\ttouch \"$GIT_DIR/logs/refs/heads/$branchname\"\n-fi\n-git update-ref -m \"branch: Created from $head\" \"refs/heads/$branchname\" $rev\ndiff --git a/git.c b/git.c\nindex 930998b..5738cb4 100644\n--- a/git.c\n+++ b/git.c\n@@ -226,6 +226,7 @@ static void handle_internal_command(int \n \t} commands[] = {\n \t\t{ \"add\", cmd_add, RUN_SETUP },\n \t\t{ \"apply\", cmd_apply },\n+\t\t{ \"branch\", cmd_branch, RUN_SETUP },\n \t\t{ \"cat-file\", cmd_cat_file, RUN_SETUP },\n \t\t{ \"checkout-index\", cmd_checkout_index, RUN_SETUP },\n \t\t{ \"check-ref-format\", cmd_check_ref_format },\n"},{"id":"25706","messageId":"Pine.LNX.4.63.0608211319180.8662@chino.corp.google.com","threadId":"5336","inReplyTo":"59ad55d30608211312u51a4657eyd52311314a6ee03c@mail.gmail.com","subject":"Re: [PATCH] branch as a builtin (again)","fromName":"David Rientjes","fromEmail":"rientjes@google.com","sentAt":"2006-08-21T20:23:18Z","receivedAt":"2006-08-21T20:23:18Z","isPatch":true,"sender":{"key":"rientjes@google.com","avatar":null},"body":"On Mon, 21 Aug 2006, Kristian Høgsberg wrote:\n\n> diff --git a/builtin-branch.c b/builtin-branch.c\n> new file mode 100644\n> index 0000000..4e8fa7d\n> --- /dev/null\n> +++ b/builtin-branch.c\n> @@ -0,0 +1,227 @@\n> +/*\n> + * Builtin \"git branch\"\n> + *\n> + * Copyright (c) 2006 Kristian HÃ¸gsberg <krh@redhat.com>\n> + * Based on git-branch.sh by Junio C Hamano.\n> + */\n> +\n> +#include \"cache.h\"\n> +#include \"refs.h\"\n> +#include \"commit.h\"\n> +#include \"builtin.h\"\n> +\n> +static const char builtin_branch_usage[] =\n> +\"git-branch [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r\";\n> +\n> +\n> +static const char *head;\n> +static unsigned char head_sha1[20];\n> +\n> +static int in_merge_bases(const unsigned char *sha1,\n> +\t\t\t  struct commit *rev1,\n> +\t\t\t  struct commit *rev2)\n> +{\n> +\tstruct commit_list *bases, *b;\n> +\tint ret = 0;\n> +\n> +\tbases = get_merge_bases(rev1, rev2, 1);\n> +\tfor (b = bases; b != NULL; b = b->next) {\n\nfor (b = bases; b; b = b->next) {\n\n> +\t\tif (!hashcmp(sha1, b->item->object.sha1)) {\n> +\t\t\tret = 1;\n> +\t\t\tbreak;\n> +\t\t}\n> +\t}\n> +\n> +\tfree_commit_list(bases);\n> +\treturn ret;\n> +}\n> +\n> +static void delete_branches(int argc, const char **argv, int force)\n> +{\n> +\tstruct commit *rev, *head_rev;\n> +\tunsigned char sha1[20];\n> +\tconst char *name, *reflog;\n> +\tint i;\n> +\n> +\thead_rev = lookup_commit_reference(head_sha1);\n> +\tfor (i = 0; i < argc; i++) {\n> +\t\tif (!strcmp(head, argv[i]))\n> +\t\t\tdie(\"Cannot delete the branch you are currently on.\");\n> +\n> +\t\tname = git_path(\"refs/heads/%s\", argv[i]);\n> +\t\tif (!resolve_ref(name, sha1, 1))\n> +\t\t\tdie(\"Branch '%s' not found.\", argv[i]);\n> +\n> +\t\trev = lookup_commit_reference(sha1);\n> +\t\tif (!rev || !head_rev)\n> +\t\t\tdie(\"Couldn't look up commit objects.\");\n> +\n> +\t\t/* This checks whether the merge bases of branch and\n> +\t\t * HEAD contains branch -- which means that the HEAD\n> +\t\t * contains everything in both.\n> +\t\t */\n> +\n> +\t\tif (!force &&\n> +\t\t    !in_merge_bases(sha1, rev, head_rev)) {\n> +\t\t\tfprintf(stderr,\n> +\t\t\t\t\"The branch '%s' is not a strict subset of your current HEAD.\\n\"\n> +\t\t\t\t\"If you are sure you want to delete it, run 'git branch -D %s'.\\n\",\n> +\t\t\t\targv[i], argv[i]);\n> +\t\t\texit(1);\n> +\t\t}\n> +\n> +\t\tunlink(name);\n> +\n> +\t\t/* Unlink reflog if it exists. */\n> +\t\treflog = git_path(\"logs/refs/heads/%s\", argv[i]);\n> +\t\tunlink(reflog);\n> +\n> +\t\tprintf(\"Deleted branch %s.\\n\", argv[i]);\n> +\t}\n> +}\n> +\n> +static int ref_index, ref_alloc;\n> +static char **ref_list;\n> +\n> +static int append_ref(const char *refname, const unsigned char *sha1)\n> +{\n> +\tif (ref_index >= ref_alloc) {\n> +\t\tref_alloc = ref_alloc > 0 ? ref_alloc * 2 : 16;\n> +\t\tref_list = realloc(ref_list, ref_alloc * sizeof (char *));\n\nNo space\n\n> +\t}\n> +\n> +\tref_list[ref_index++] = strdup(refname);\n> +\n> +\treturn 0;\n> +}\n> +\n> +static int ref_cmp (const void *r1, const void *r2)\n\nNo space\n\n> +{\n> +\treturn strcmp (*(char **)r1, *(char **)r2);\n\nNo space\n\n> +}\n> +\n> +static void print_ref_list(int remote_only)\n> +{\n> +\tint i;\n> +\n> +\tif (remote_only)\n> +\t\tfor_each_remote_ref(append_ref);\n> +\telse\n> +\t\tfor_each_branch_ref(append_ref);\n> +\n> +\tqsort(ref_list, ref_index, sizeof (char *), ref_cmp);\n> +\n\nNo space\n\n> +\tfor (i = 0; i < ref_index; i++) {\n> +\t\tif (!strcmp(ref_list[i], head))\n> +\t\t\tprintf(\"* %s\\n\", ref_list[i]);\n> +\t\telse\n> +\t\t\tprintf(\"  %s\\n\", ref_list[i]);\n> +\t}\n> +}\n> +\n> +static void create_reflog(struct ref_lock *lock)\n> +{\n> +\tstruct stat stbuf;\n> +\tint fd;\n> +\n> +\tif (!stat(lock->log_file, &stbuf) && S_ISREG(stbuf.st_mode))\n> +\t\treturn;\n> +\tif (safe_create_leading_directories(lock->log_file) < 0)\n> +\t\tdie(\"Unable to create directory for %s.\", lock->log_file);\n> +\tfd = open(lock->log_file, O_CREAT | O_TRUNC | O_WRONLY, 0666);\n> +\tif (fd < 0)\n> +\t\tdie(\"Unable to create ref log %s: %s.\",\n> +\t\t    lock->log_file, strerror(errno));\n> +\tclose(fd);\n> +}\n> +\n> +static void create_branch(const char *name, const char *start,\n> +\t\t\t  int force, int reflog)\n> +{\n> +\tstruct ref_lock *lock;\n> +\tunsigned char sha1[20];\n> +\tchar ref[PATH_MAX], msg[PATH_MAX + 20];\n> +\n> +\tsnprintf(ref, sizeof ref, \"refs/heads/%s\", name);\n> +\tif (check_ref_format(ref))\n> +\t\tdie(\"'%s' is not a valid branch name.\", name);\n> +\n> +\tif (resolve_ref(git_path(ref), sha1, 1)) {\n> +\t\tif (!force)\n> +\t\t\tdie(\"A branch named '%s' already exists.\", name);\n> +\t\telse if (!strcmp(head, name))\n> +\t\t\tdie(\"Cannot force update the current branch.\");\n> +\t}\n> +\n> +\tif (get_sha1(start, sha1))\n> +\t\tdie(\"Not a valid branch point: '%s'.\", start);\n> +\n> +\tlock = lock_any_ref_for_update(ref, NULL, 0);\n> +\tif (!lock)\n> +\t\tdie(\"Failed to lock ref for update: %s.\", strerror(errno));\n> +\tif (reflog)\n> +\t\tcreate_reflog(lock);\n> +\tsnprintf(msg, sizeof msg, \"branch: Created from %s\", start);\n> +\tif (write_ref_sha1(lock, sha1, msg) < 0)\n> +\t\tdie(\"Failed to write ref: %s.\", strerror(errno));\n> +}\n> +\n> +int cmd_branch(int argc, const char **argv, const char *prefix)\n> +{\n> +\tint delete = 0, force_delete = 0, force_create = 0, remote_only = 0;\n> +\tint reflog = 0;\n> +\tint i, prefix_length;\n> +\tconst char *p;\n> +\n> +\tgit_config(git_default_config);\n> +\n> +\tfor (i = 1; i < argc; i++) {\n> +\t\tconst char *arg = argv[i];\n> +\n> +\t\tif (arg[0] != '-')\n> +\t\t\tbreak;\n> +\t\tif (!strcmp(arg, \"--\")) {\n> +\t\t\ti++;\n> +\t\t\tbreak;\n> +\t\t}\n> +\t\tif (!strcmp(arg, \"-d\")) {\n> +\t\t\tdelete = 1;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\t\tif (!strcmp(arg, \"-D\")) {\n> +\t\t\tdelete = 1;\n> +\t\t\tforce_delete = 1;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\t\tif (!strcmp(arg, \"-f\")) {\n> +\t\t\tforce_create = 1;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\t\tif (!strcmp(arg, \"-r\")) {\n> +\t\t\tremote_only = 1;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\t\tif (!strcmp(arg, \"-l\")) {\n> +\t\t\treflog = 1;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\t\tusage(builtin_branch_usage);\n> +\t}\n> +\n> +\tprefix_length = strlen(git_path(\"refs/heads/\"));\n> +\tp = resolve_ref(git_path(\"HEAD\"), head_sha1, 0);\n> +\tif (!p)\n> +\t\tdie(\"Failed to resolve HEAD as a valid ref.\");\n> +\thead = strdup(p + prefix_length);\n> +\n> +\tif (delete)\n> +\t\tdelete_branches(argc - i, argv + i, force_delete);\n> +\telse if (i == argc)\n> +\t\tprint_ref_list(remote_only);\n> +\telse if (argc - i == 1)\n> +\t\tcreate_branch(argv[i], head, force_create, reflog);\n> +\telse\n> +\t\tcreate_branch(argv[i], argv[i + 1], force_create, reflog);\n> +\n> +\treturn 0;\n> +}"},{"id":"25707","messageId":"Pine.LNX.4.63.0608212227040.28360@wbgn013.biozentrum.uni-wuerzburg.de","threadId":"5336","inReplyTo":"59ad55d30608211312u51a4657eyd52311314a6ee03c@mail.gmail.com","subject":"Re: [PATCH] branch as a builtin (again)","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2006-08-21T20:27:58Z","receivedAt":"2006-08-21T20:27:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Mon, 21 Aug 2006, Kristian Høgsberg wrote:\n\n> Thanks to all who reviewed the patch, here's an updated version which\n> should address all issues.\n\nI would have preferred the use of path_list instead of rolling your own \nthing with qsort(), but oh well.\n\nRest looks fine to me.\n\nCiao,\nDscho\n"},{"id":"25708","messageId":"20060821204110.GA24465@spearce.org","threadId":"5336","inReplyTo":"59ad55d30608211312u51a4657eyd52311314a6ee03c@mail.gmail.com","subject":"Re: [PATCH] branch as a builtin (again)","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-08-21T20:41:10Z","receivedAt":"2006-08-21T20:41:10Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Kristian H?gsberg <krh@bitplanet.net> wrote:\n> +static void delete_branches(int argc, const char **argv, int force)\n[snip]\n> +\t\tname = git_path(\"refs/heads/%s\", argv[i]);\n> +\t\tif (!resolve_ref(name, sha1, 1))\n> +\t\t\tdie(\"Branch '%s' not found.\", argv[i]);\n[snip]\n> +\t\tunlink(name);\n> +\n> +\t\t/* Unlink reflog if it exists. */\n> +\t\treflog = git_path(\"logs/refs/heads/%s\", argv[i]);\n> +\t\tunlink(reflog);\n\nHmm.  So git-branch.sh doesn't deal with symrefs, eh?  I guess this\nis OK but I'm wondering why not put this code into refs.c to lock\nthe ref (refs.c:lock_ref_sha1) then instead of unlocking it delete\nit and its log (add new function to do this).\n\nThe downside of this is that we'll chase a symref, which means that\nif refs/heads/FOO is a symref to refs/heads/master and the user calls\n`git-branch -D FOO` we'll kill refs/heads/master.  Maybe that's not\nwhat the the user would want to have happen.  :-)\n\n> +static void create_reflog(struct ref_lock *lock)\n> +{\n> +\tstruct stat stbuf;\n> +\tint fd;\n> +\n> +\tif (!stat(lock->log_file, &stbuf) && S_ISREG(stbuf.st_mode))\n> +\t\treturn;\n> +\tif (safe_create_leading_directories(lock->log_file) < 0)\n> +\t\tdie(\"Unable to create directory for %s.\", lock->log_file);\n> +\tfd = open(lock->log_file, O_CREAT | O_TRUNC | O_WRONLY, 0666);\n> +\tif (fd < 0)\n> +\t\tdie(\"Unable to create ref log %s: %s.\",\n> +\t\t    lock->log_file, strerror(errno));\n> +\tclose(fd);\n> +}\n\nThis probably should move into refs.c.  Look at log_ref_write,\nspecifically around the if (log_all_ref_updates).  If this took\nan additional parameter to force creation of the log even if the log\nisn't present and OR'd against log_all_ref_updates then it would\nbe possible to have the refs.c code create the log for you in the\n\"library\" part of GIT.\n\nOr maybe it is better to add this as a flag to the struct ref_lock,\ndefaulting to false and letting the caller set it to true before\ninvoking write_ref_sha1.  I only suggest this because of the number\nof parameters already in play here.\n\n> +static void create_branch(const char *name, const char *start,\n> +\t\t\t  int force, int reflog)\n\nThis all looked correct to me, at least as far as dealing with\nthe reflog.  :-)\n"},{"id":"25710","messageId":"59ad55d30608211345k7956a219m8ab450c6f2023461@mail.gmail.com","threadId":"5336","inReplyTo":"Pine.LNX.4.63.0608211319180.8662@chino.corp.google.com","subject":"Re: [PATCH] branch as a builtin (again)","fromName":"Kristian Høgsberg","fromEmail":"krh@bitplanet.net","sentAt":"2006-08-21T20:45:14Z","receivedAt":"2006-08-21T20:45:14Z","isPatch":true,"sender":{"key":"krh@bitplanet.net","avatar":"https://gravatar.com/avatar/6ba3f320dfeafb6821e9824d11122fe7157e39c443476c4cf35d6dd830052d1d?d=mp&s=160"},"body":"Ok, once more without the spaces.  I have to state that it's against\nmy personal beliefs using pointers as boolean values, but I can go\nwith the flow here.  For extra bonus, I'm using xrealloc instead of\nplain realloc now.\n\ncheers,\nKristian\n"},{"id":"25712","messageId":"59ad55d30608211407v74e1369dw17fae16933c0356f@mail.gmail.com","threadId":"5336","inReplyTo":"Pine.LNX.4.63.0608212227040.28360@wbgn013.biozentrum.uni-wuerzburg.de","subject":"Re: [PATCH] branch as a builtin (again)","fromName":"Kristian Høgsberg","fromEmail":"krh@bitplanet.net","sentAt":"2006-08-21T21:07:43Z","receivedAt":"2006-08-21T21:07:43Z","isPatch":true,"sender":{"key":"krh@bitplanet.net","avatar":"https://gravatar.com/avatar/6ba3f320dfeafb6821e9824d11122fe7157e39c443476c4cf35d6dd830052d1d?d=mp&s=160"},"body":"On 8/21/06, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:\n> Hi,\n>\n> On Mon, 21 Aug 2006, Kristian Hxgsberg wrote:\n>\n> > Thanks to all who reviewed the patch, here's an updated version which\n> > should address all issues.\n>\n> I would have preferred the use of path_list instead of rolling your own\n> thing with qsort(), but oh well.\n\nYeah, I saw that, but since I got flack for computing\nlookup_commit_reference(head_sha1) inside the delete_branches loop, I\ncouldn't possibly risk the performance bottle neck of listing the\nbranches using a O(n^2) insertion sort.\n\nKristian\n"},{"id":"25716","messageId":"Pine.LNX.4.63.0608212323420.28360@wbgn013.biozentrum.uni-wuerzburg.de","threadId":"5336","inReplyTo":"59ad55d30608211337jabd515bra3566fbd0f7ba5a0@mail.gmail.com","subject":"Re: [PATCH] branch as a builtin (again)","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2006-08-21T21:25:23Z","receivedAt":"2006-08-21T21:25:23Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Mon, 21 Aug 2006, Kristian Høgsberg wrote:\n\n> On 8/21/06, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:\n> > Hi,\n> > \n> > On Mon, 21 Aug 2006, Kristian Høgsberg wrote:\n> > \n> > > Thanks to all who reviewed the patch, here's an updated version which\n> > > should address all issues.\n> > \n> > I would have preferred the use of path_list instead of rolling your own\n> > thing with qsort(), but oh well.\n> \n> Yeah, I saw that, but since I got flack for computing\n> lookup_commit_reference(head_sha1) inside the delete_branches loop, I\n> couldn't possibly risk the performance bottle neck of listing the\n> branches using a O(n^2) insertion sort.\n\nInsertion sort, as implemented in path_list, has an average O(n log(n)) \nruntime, and a worst-case O(n^2) runtime. Same as qsort...\n\nBesides, it is not like we are dealing with millions of branches here. And \nusing path_list _would_ make the code shorter.\n\nCiao,\nDscho\n"},{"id":"25727","messageId":"7vveolqmpb.fsf@assigned-by-dhcp.cox.net","threadId":"5336","inReplyTo":"59ad55d30608211345k7956a219m8ab450c6f2023461@mail.gmail.com","subject":"Re: [PATCH] branch as a builtin (again)","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-08-22T07:00:32Z","receivedAt":"2006-08-22T07:00:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Kristian Høgsberg\" <krh@bitplanet.net> writes:\n\n> Ok, once more without the spaces.  I have to state that it's against\n> my personal beliefs using pointers as boolean values, but I can go\n> with the flow here.  For extra bonus, I'm using xrealloc instead of\n> plain realloc now.\n\nMy preferences (pretty much procedural):\n\n - Documentation/SubmittingPatches\n   - Attachments discouraged;\n   - With a proper commit log message;\n   - With a proper signed-off line;\n\n - Names in source encoded in utf8 if needed (I think you got\n   this one right, but application/octet-stream does not give\n   charset information so I cannot really tell).\n\n - No spaces between a function name and open parenthesis.\n\nSome nitpicks.\n\n> static void create_reflog(struct ref_lock *lock)\n> {\n>...\n> }\n\nProbably reflog interface should supply ways to create new ones\n(and delete or truncate existing ones) to users like this\nprogram.  Please work with Shawn Pearce to refactor this part.\n\n> static void create_branch(const char *name, const char *start,\n> \t\t\t  int force, int reflog)\n> {\n> \tstruct ref_lock *lock;\n> \tunsigned char sha1[20];\n> \tchar ref[PATH_MAX], msg[PATH_MAX + 20];\n\nYou are using snprintf so I think it is safe, but I think using\nPATH_MAX for msg length is wrong.  start can be an arbitrary\nextended object name expression (HEAD^12~24^2~4^^2~13...) and\ndoes not have much to do with pathname.\n\n> \tsnprintf(ref, sizeof ref, \"refs/heads/%s\", name);\n\nMaybe barf if snprintf steps over the buffer?\n"}]}