{"thread":{"id":"4439","subject":"[PATCH] Implement git-branch and git-merge-base as built-ins.","startedAt":"2006-06-08T17:49:46Z","lastAt":"2006-06-08T18:53:48Z","messageCount":2,"participants":["Kristian Høgsberg","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"21443","messageId":"4488633A.5060409@bitplanet.net","threadId":"4439","inReplyTo":null,"subject":"[PATCH] Implement git-branch and git-merge-base as built-ins.","fromName":"Kristian Høgsberg","fromEmail":"krh@bitplanet.net","sentAt":"2006-06-08T17:49:46Z","receivedAt":"2006-06-08T17:49:46Z","isPatch":true,"sender":{"key":"krh@bitplanet.net","avatar":"https://gravatar.com/avatar/6ba3f320dfeafb6821e9824d11122fe7157e39c443476c4cf35d6dd830052d1d?d=mp&s=160"},"body":"This patch is more or less a straight port of git-branch from shell\nscript to C.  Branch deletion uses git-merge-base to check if it is safe\nto delete a branch, so I changed merge-base.c to export this functionality\nas a for_each_merge_base() iterator.  As a side effect, git-merge-base is\nnow also a built-in command.\n\n---\n\n6b90d7b7af4bf577ccfeebef1f736e75631b052d\n Makefile         |   13 +++--\n builtin-branch.c |  154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++\n builtin.h        |    2 +\n commit.h         |    5 ++\n git-branch.sh    |  120 ------------------------------------------\n git.c            |    4 +\n merge-base.c     |   34 ++++++++----\n 7 files changed, 194 insertions(+), 138 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 5373986..a709e40 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -113,7 +113,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-format-patch.sh git-ls-remote.sh \\\n@@ -155,7 +155,7 @@ PROGRAMS = \\\n \tgit-diff-index$X git-diff-stages$X \\\n \tgit-diff-tree$X git-fetch-pack$X git-fsck-objects$X \\\n \tgit-hash-object$X git-index-pack$X git-init-db$X git-local-fetch$X \\\n-\tgit-ls-files$X git-ls-tree$X git-mailinfo$X git-merge-base$X \\\n+\tgit-ls-files$X git-ls-tree$X git-mailinfo$X \\\n \tgit-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \\\n \tgit-peek-remote$X git-prune-packed$X git-read-tree$X \\\n \tgit-receive-pack$X git-rev-parse$X \\\n@@ -170,7 +170,8 @@ PROGRAMS = \\\n \n BUILT_INS = git-log$X git-whatchanged$X git-show$X \\\n \tgit-count-objects$X git-diff$X git-push$X \\\n-\tgit-grep$X git-add$X git-rev-list$X git-check-ref-format$X\n+\tgit-grep$X git-add$X git-rev-list$X git-check-ref-format$X \\\n+\tgit-branch$X git-merge-base$X\n \n # what 'all' will build and 'install' will install, in gitexecdir\n ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)\n@@ -214,11 +215,13 @@ LIB_OBJS = \\\n \tserver-info.o setup.o sha1_file.o sha1_name.o strbuf.o \\\n \ttag.o tree.o usage.o config.o environment.o ctype.o copy.o \\\n \tfetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \\\n+\tmerge-base.o \\\n \t$(DIFF_OBJS)\n \n BUILTIN_OBJS = \\\n-\tbuiltin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \\\n-\tbuiltin-grep.o builtin-add.o builtin-rev-list.o builtin-check-ref-format.o\n+\tbuiltin-log.o builtin-help.o builtin-count.o builtin-diff.o \\\n+\tbuiltin-push.o builtin-grep.o builtin-add.o builtin-rev-list.o \\\n+\tbuiltin-check-ref-format.o builtin-branch.o\n \n GITLIBS = $(LIB_FILE) $(XDIFF_LIB)\n LIBS = $(GITLIBS) -lz\ndiff --git a/builtin-branch.c b/builtin-branch.c\nnew file mode 100644\nindex 0000000..c7776a3\n--- /dev/null\n+++ b/builtin-branch.c\n@@ -0,0 +1,154 @@\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+\t\"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 find_sha1(struct commit *commit, void *data)\n+{\n+\treturn !memcmp(data, commit->object.sha1, sizeof commit->object.sha1);\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    !for_each_merge_base(rev1, rev2, find_sha1, sha1)) {\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+\tunsigned char sha1[20];\n+\tchar ref[500];\n+\n+\tsnprintf (ref, sizeof ref, \"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(\"refs/%s\", 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+\tif (write_ref_sha1_unlocked(ref, sha1))\n+\t\tdie(\"Failed to create branch: %s.\", strerror(errno));\n+}\n+\n+int cmd_branch(int argc, const char **argv, char **envp)\n+{\n+\tint delete = 0, force_delete = 0, force_create = 0;\n+\tint i, prefix_length;\n+\tconst char *p;\n+\n+\tsetup_git_directory();\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 78275ea..0105358 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -28,5 +28,7 @@ extern int cmd_grep(int argc, const char\n extern int cmd_add(int argc, const char **argv, char **envp);\n extern int cmd_rev_list(int argc, const char **argv, char **envp);\n extern int cmd_check_ref_format(int argc, const char **argv, char **envp);\n+extern int cmd_branch(int argc, const char **argv, char **envp);\n+extern int cmd_merge_base(int argc, const char **argv, char **envp);\n \n #endif\ndiff --git a/commit.h b/commit.h\nindex 8d7514c..b1a518a 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -104,4 +104,9 @@ struct commit_graft *read_graft_line(cha\n int register_commit_graft(struct commit_graft *, int);\n int read_graft_file(const char *graft_file);\n \n+/* merge-base.c */\n+int for_each_merge_base(struct commit *rev1, struct commit *rev2,\n+\t\t\tint (*fn)(struct commit *commit, void *data),\n+\t\t\tvoid  *data);\n+\n #endif /* COMMIT_H */\ndiff --git a/git-branch.sh b/git-branch.sh\ndeleted file mode 100755\nindex 134e68c..0000000\n--- a/git-branch.sh\n+++ /dev/null\n@@ -1,120 +0,0 @@\n-#!/bin/sh\n-\n-USAGE='[(-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/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-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--)\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-git update-ref \"refs/heads/$branchname\" $rev\ndiff --git a/git.c b/git.c\nindex 7db5cc1..fb66c0e 100644\n--- a/git.c\n+++ b/git.c\n@@ -53,7 +53,9 @@ static void handle_internal_command(int \n \t\t{ \"grep\", cmd_grep },\n \t\t{ \"add\", cmd_add },\n \t\t{ \"rev-list\", cmd_rev_list },\n-\t\t{ \"check-ref-format\", cmd_check_ref_format }\n+\t\t{ \"check-ref-format\", cmd_check_ref_format },\n+\t\t{ \"branch\", cmd_branch },\n+\t\t{ \"merge-base\", cmd_merge_base }\n \t};\n \tint i;\n \ndiff --git a/merge-base.c b/merge-base.c\nindex 4856ca0..0aa6ed4 100644\n--- a/merge-base.c\n+++ b/merge-base.c\n@@ -1,6 +1,6 @@\n-#include <stdlib.h>\n #include \"cache.h\"\n #include \"commit.h\"\n+#include \"builtin.h\"\n \n #define PARENT1 1\n #define PARENT2 2\n@@ -167,16 +167,16 @@ static void mark_reachable_commits(struc\n \t}\n }\n \n-static int merge_base(struct commit *rev1, struct commit *rev2)\n+int for_each_merge_base(struct commit *rev1, struct commit *rev2,\n+\t\t\tint (*fn)(struct commit *commit, void *data),\n+\t\t\tvoid *data)\n {\n \tstruct commit_list *list = NULL;\n \tstruct commit_list *result = NULL;\n \tstruct commit_list *tmp = NULL;\n \n-\tif (rev1 == rev2) {\n-\t\tprintf(\"%s\\n\", sha1_to_hex(rev1->object.sha1));\n-\t\treturn 0;\n-\t}\n+\tif (rev1 == rev2) \n+\t\treturn fn(rev1, data);\n \n \tparse_commit(rev1);\n \tparse_commit(rev2);\n@@ -220,12 +220,13 @@ static int merge_base(struct commit *rev\n \n \twhile (result) {\n \t\tstruct commit *commit = result->item;\n+\t\tint retval;\n \t\tresult = result->next;\n \t\tif (commit->object.flags & UNINTERESTING)\n \t\t\tcontinue;\n-\t\tprintf(\"%s\\n\", sha1_to_hex(commit->object.sha1));\n-\t\tif (!show_all)\n-\t\t\treturn 0;\n+\t\tretval = fn(commit, data);\n+\t\tif (retval)\n+\t\t\treturn retval;\n \t\tcommit->object.flags |= UNINTERESTING;\n \t}\n \treturn 0;\n@@ -234,7 +235,13 @@ static int merge_base(struct commit *rev\n static const char merge_base_usage[] =\n \"git-merge-base [--all] <commit-id> <commit-id>\";\n \n-int main(int argc, char **argv)\n+static int print_merge_base(struct commit *commit, void *data)\n+{\n+\tprintf(\"%s\\n\", sha1_to_hex(commit->object.sha1));\n+\treturn show_all ? 0 : 1;\n+}\n+\n+int cmd_merge_base(int argc, const char **argv, char **envp)\n {\n \tstruct commit *rev1, *rev2;\n \tunsigned char rev1key[20], rev2key[20];\n@@ -243,7 +250,7 @@ int main(int argc, char **argv)\n \tgit_config(git_default_config);\n \n \twhile (1 < argc && argv[1][0] == '-') {\n-\t\tchar *arg = argv[1];\n+\t\tconst char *arg = argv[1];\n \t\tif (!strcmp(arg, \"-a\") || !strcmp(arg, \"--all\"))\n \t\t\tshow_all = 1;\n \t\telse\n@@ -260,5 +267,8 @@ int main(int argc, char **argv)\n \trev2 = lookup_commit_reference(rev2key);\n \tif (!rev1 || !rev2)\n \t\treturn 1;\n-\treturn merge_base(rev1, rev2);\n+\n+\tfor_each_merge_base(rev1, rev2, print_merge_base, NULL);\n+\n+\treturn 0;\n }\n\n6b90d7b7af4bf577ccfeebef1f736e75631b052d\ndiff --git a/Makefile b/Makefile\nindex 5373986..a709e40 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -113,7 +113,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-format-patch.sh git-ls-remote.sh \\\n@@ -155,7 +155,7 @@ PROGRAMS = \\\n \tgit-diff-index$X git-diff-stages$X \\\n \tgit-diff-tree$X git-fetch-pack$X git-fsck-objects$X \\\n \tgit-hash-object$X git-index-pack$X git-init-db$X git-local-fetch$X \\\n-\tgit-ls-files$X git-ls-tree$X git-mailinfo$X git-merge-base$X \\\n+\tgit-ls-files$X git-ls-tree$X git-mailinfo$X \\\n \tgit-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \\\n \tgit-peek-remote$X git-prune-packed$X git-read-tree$X \\\n \tgit-receive-pack$X git-rev-parse$X \\\n@@ -170,7 +170,8 @@ PROGRAMS = \\\n \n BUILT_INS = git-log$X git-whatchanged$X git-show$X \\\n \tgit-count-objects$X git-diff$X git-push$X \\\n-\tgit-grep$X git-add$X git-rev-list$X git-check-ref-format$X\n+\tgit-grep$X git-add$X git-rev-list$X git-check-ref-format$X \\\n+\tgit-branch$X git-merge-base$X\n \n # what 'all' will build and 'install' will install, in gitexecdir\n ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)\n@@ -214,11 +215,13 @@ LIB_OBJS = \\\n \tserver-info.o setup.o sha1_file.o sha1_name.o strbuf.o \\\n \ttag.o tree.o usage.o config.o environment.o ctype.o copy.o \\\n \tfetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \\\n+\tmerge-base.o \\\n \t$(DIFF_OBJS)\n \n BUILTIN_OBJS = \\\n-\tbuiltin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \\\n-\tbuiltin-grep.o builtin-add.o builtin-rev-list.o builtin-check-ref-format.o\n+\tbuiltin-log.o builtin-help.o builtin-count.o builtin-diff.o \\\n+\tbuiltin-push.o builtin-grep.o builtin-add.o builtin-rev-list.o \\\n+\tbuiltin-check-ref-format.o builtin-branch.o\n \n GITLIBS = $(LIB_FILE) $(XDIFF_LIB)\n LIBS = $(GITLIBS) -lz\ndiff --git a/builtin-branch.c b/builtin-branch.c\nnew file mode 100644\nindex 0000000..c7776a3\n--- /dev/null\n+++ b/builtin-branch.c\n@@ -0,0 +1,154 @@\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+\t\"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 find_sha1(struct commit *commit, void *data)\n+{\n+\treturn !memcmp(data, commit->object.sha1, sizeof commit->object.sha1);\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    !for_each_merge_base(rev1, rev2, find_sha1, sha1)) {\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+\tunsigned char sha1[20];\n+\tchar ref[500];\n+\n+\tsnprintf (ref, sizeof ref, \"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(\"refs/%s\", 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+\tif (write_ref_sha1_unlocked(ref, sha1))\n+\t\tdie(\"Failed to create branch: %s.\", strerror(errno));\n+}\n+\n+int cmd_branch(int argc, const char **argv, char **envp)\n+{\n+\tint delete = 0, force_delete = 0, force_create = 0;\n+\tint i, prefix_length;\n+\tconst char *p;\n+\n+\tsetup_git_directory();\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 78275ea..0105358 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -28,5 +28,7 @@ extern int cmd_grep(int argc, const char\n extern int cmd_add(int argc, const char **argv, char **envp);\n extern int cmd_rev_list(int argc, const char **argv, char **envp);\n extern int cmd_check_ref_format(int argc, const char **argv, char **envp);\n+extern int cmd_branch(int argc, const char **argv, char **envp);\n+extern int cmd_merge_base(int argc, const char **argv, char **envp);\n \n #endif\ndiff --git a/commit.h b/commit.h\nindex 8d7514c..b1a518a 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -104,4 +104,9 @@ struct commit_graft *read_graft_line(cha\n int register_commit_graft(struct commit_graft *, int);\n int read_graft_file(const char *graft_file);\n \n+/* merge-base.c */\n+int for_each_merge_base(struct commit *rev1, struct commit *rev2,\n+\t\t\tint (*fn)(struct commit *commit, void *data),\n+\t\t\tvoid  *data);\n+\n #endif /* COMMIT_H */\ndiff --git a/git-branch.sh b/git-branch.sh\ndeleted file mode 100755\nindex 134e68c..0000000\n--- a/git-branch.sh\n+++ /dev/null\n@@ -1,120 +0,0 @@\n-#!/bin/sh\n-\n-USAGE='[(-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/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-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--)\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-git update-ref \"refs/heads/$branchname\" $rev\ndiff --git a/git.c b/git.c\nindex 7db5cc1..fb66c0e 100644\n--- a/git.c\n+++ b/git.c\n@@ -53,7 +53,9 @@ static void handle_internal_command(int \n \t\t{ \"grep\", cmd_grep },\n \t\t{ \"add\", cmd_add },\n \t\t{ \"rev-list\", cmd_rev_list },\n-\t\t{ \"check-ref-format\", cmd_check_ref_format }\n+\t\t{ \"check-ref-format\", cmd_check_ref_format },\n+\t\t{ \"branch\", cmd_branch },\n+\t\t{ \"merge-base\", cmd_merge_base }\n \t};\n \tint i;\n \ndiff --git a/merge-base.c b/merge-base.c\nindex 4856ca0..0aa6ed4 100644\n--- a/merge-base.c\n+++ b/merge-base.c\n@@ -1,6 +1,6 @@\n-#include <stdlib.h>\n #include \"cache.h\"\n #include \"commit.h\"\n+#include \"builtin.h\"\n \n #define PARENT1 1\n #define PARENT2 2\n@@ -167,16 +167,16 @@ static void mark_reachable_commits(struc\n \t}\n }\n \n-static int merge_base(struct commit *rev1, struct commit *rev2)\n+int for_each_merge_base(struct commit *rev1, struct commit *rev2,\n+\t\t\tint (*fn)(struct commit *commit, void *data),\n+\t\t\tvoid *data)\n {\n \tstruct commit_list *list = NULL;\n \tstruct commit_list *result = NULL;\n \tstruct commit_list *tmp = NULL;\n \n-\tif (rev1 == rev2) {\n-\t\tprintf(\"%s\\n\", sha1_to_hex(rev1->object.sha1));\n-\t\treturn 0;\n-\t}\n+\tif (rev1 == rev2) \n+\t\treturn fn(rev1, data);\n \n \tparse_commit(rev1);\n \tparse_commit(rev2);\n@@ -220,12 +220,13 @@ static int merge_base(struct commit *rev\n \n \twhile (result) {\n \t\tstruct commit *commit = result->item;\n+\t\tint retval;\n \t\tresult = result->next;\n \t\tif (commit->object.flags & UNINTERESTING)\n \t\t\tcontinue;\n-\t\tprintf(\"%s\\n\", sha1_to_hex(commit->object.sha1));\n-\t\tif (!show_all)\n-\t\t\treturn 0;\n+\t\tretval = fn(commit, data);\n+\t\tif (retval)\n+\t\t\treturn retval;\n \t\tcommit->object.flags |= UNINTERESTING;\n \t}\n \treturn 0;\n@@ -234,7 +235,13 @@ static int merge_base(struct commit *rev\n static const char merge_base_usage[] =\n \"git-merge-base [--all] <commit-id> <commit-id>\";\n \n-int main(int argc, char **argv)\n+static int print_merge_base(struct commit *commit, void *data)\n+{\n+\tprintf(\"%s\\n\", sha1_to_hex(commit->object.sha1));\n+\treturn show_all ? 0 : 1;\n+}\n+\n+int cmd_merge_base(int argc, const char **argv, char **envp)\n {\n \tstruct commit *rev1, *rev2;\n \tunsigned char rev1key[20], rev2key[20];\n@@ -243,7 +250,7 @@ int main(int argc, char **argv)\n \tgit_config(git_default_config);\n \n \twhile (1 < argc && argv[1][0] == '-') {\n-\t\tchar *arg = argv[1];\n+\t\tconst char *arg = argv[1];\n \t\tif (!strcmp(arg, \"-a\") || !strcmp(arg, \"--all\"))\n \t\t\tshow_all = 1;\n \t\telse\n@@ -260,5 +267,8 @@ int main(int argc, char **argv)\n \trev2 = lookup_commit_reference(rev2key);\n \tif (!rev1 || !rev2)\n \t\treturn 1;\n-\treturn merge_base(rev1, rev2);\n+\n+\tfor_each_merge_base(rev1, rev2, print_merge_base, NULL);\n+\n+\treturn 0;\n }\n"},{"id":"21447","messageId":"7vverbsclf.fsf@assigned-by-dhcp.cox.net","threadId":"4439","inReplyTo":"4488633A.5060409@bitplanet.net","subject":"Re: [PATCH] Implement git-branch and git-merge-base as built-ins.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-06-08T18:53:48Z","receivedAt":"2006-06-08T18:53:48Z","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> This patch is more or less a straight port of git-branch from shell\n> script to C.  Branch deletion uses git-merge-base to check if it is safe\n> to delete a branch, so I changed merge-base.c to export this functionality\n> as a for_each_merge_base() iterator.  As a side effect, git-merge-base is\n> now also a built-in command.\n\nFirst, some lighter-weight comments:\n\n (0) Somehow your diff have two copies of everything.  How did\n     you prepare this message I wonder...?\n\n (1) Sign your work, please.\n\n (2) I would have preferred a patch to do merge-base and another\n     patch to do branch.  That way, we could do merge-base\n     without doing branch if we wanted to.\n\nBut the patch is wrong.  Your for-each-merge-base cannot be\ncalled more than once, but delete-branches does.\n\nThe merge-base program as implemented currently is written with\nthe assumption that it is called only once, and leaves its\nworking state in parsed commit objects all over the place.  In\norder to make the second and subsequent call to work correctly,\nyou need to clean the flags up.\n\nAs a demonstration, with this function appended to your\nmerge-base.c and making it a built-in \"merge-base-bogo\":\n\nint cmd_merge_base_bogo(int argc, const char **argv, char **envp)\n{\n\tstruct commit *rev1, *rev2;\n\tunsigned char rev1key[20], rev2key[20];\n\tint errors = 0;\n\n\tsetup_git_directory();\n\tgit_config(git_default_config);\n\n\twhile (1 < argc && argv[1][0] == '-') {\n\t\tconst char *arg = argv[1];\n\t\tif (!strcmp(arg, \"-a\") || !strcmp(arg, \"--all\"))\n\t\t\tshow_all = 1;\n\t\telse\n\t\t\tusage(merge_base_usage);\n\t\targc--; argv++;\n\t}\n\tfor (; 3 <= argc; argc -= 2, argv += 2) {\n\t\tif (get_sha1(argv[1], rev1key) ||\n\t\t    !(rev1 = lookup_commit_reference(rev1key))) {\n\t\t\terror(\"Not a valid object name %s\", argv[1]);\n\t\t\terrors++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (get_sha1(argv[2], rev2key) ||\n\t\t    !(rev2 = lookup_commit_reference(rev2key))) {\n\t\t\terror(\"Not a valid object name %s\", argv[2]);\n\t\t\terrors++;\n\t\t\tcontinue;\n\t\t}\n\t\tfor_each_merge_base(rev1, rev2, print_merge_base, NULL);\n\t}\n\tif (1 < argc) {\n\t\terror(\"Trailing argument %s not used\", argv[1]);\n\t\terrors++;\n\t}\n\treturn !!errors;\n}\n\nHere is what happens.\n\n: gitster; ./git merge-base-bogo 66ae0c77 ced9456a\n262a6ef76a1dde97ab50d79fa5cd6d3f9f125765\n: gitster; ./git merge-base-bogo 89719209 262a6ef7\naa6bf0eb6489d652c5877d65160ed33c857afa74\n: gitster; ./git merge-base-bogo 66ae0c77 ced9456a 89719209 262a6ef7\n262a6ef76a1dde97ab50d79fa5cd6d3f9f125765\n262a6ef76a1dde97ab50d79fa5cd6d3f9f125765\n\nThis is because the invocation for the second pair does not\nstart with a clean slate, and is affected by the leftover states\nfrom the computation for the first pair.  If you swap the\narguments, you sometimes get correct result by accident, like this:\n\n: gitster; ./git merge-base-bogo 89719209 262a6ef7 66ae0c77 ced9456a\naa6bf0eb6489d652c5877d65160ed33c857afa74\n262a6ef76a1dde97ab50d79fa5cd6d3f9f125765\n\nIncidentally, this is why I haven't done \"A...B\" revision syntax\nextension to mean \"^$(git merge-base A B) B\".\n"}]}