git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] Implement git-branch and git-merge-base as built-ins.
@ 2006-06-08 17:49 Kristian Høgsberg
  2006-06-08 18:53 ` Junio C Hamano
  0 siblings, 1 reply; 2+ messages in thread
From: Kristian Høgsberg @ 2006-06-08 17:49 UTC (permalink / raw)
  To: git

This patch is more or less a straight port of git-branch from shell
script to C.  Branch deletion uses git-merge-base to check if it is safe
to delete a branch, so I changed merge-base.c to export this functionality
as a for_each_merge_base() iterator.  As a side effect, git-merge-base is
now also a built-in command.

---

6b90d7b7af4bf577ccfeebef1f736e75631b052d
 Makefile         |   13 +++--
 builtin-branch.c |  154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 builtin.h        |    2 +
 commit.h         |    5 ++
 git-branch.sh    |  120 ------------------------------------------
 git.c            |    4 +
 merge-base.c     |   34 ++++++++----
 7 files changed, 194 insertions(+), 138 deletions(-)

diff --git a/Makefile b/Makefile
index 5373986..a709e40 100644
--- a/Makefile
+++ b/Makefile
@@ -113,7 +113,7 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powe
 ### --- END CONFIGURATION SECTION ---
 
 SCRIPT_SH = \
-	git-bisect.sh git-branch.sh git-checkout.sh \
+	git-bisect.sh git-checkout.sh \
 	git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \
 	git-fetch.sh \
 	git-format-patch.sh git-ls-remote.sh \
@@ -155,7 +155,7 @@ PROGRAMS = \
 	git-diff-index$X git-diff-stages$X \
 	git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \
 	git-hash-object$X git-index-pack$X git-init-db$X git-local-fetch$X \
-	git-ls-files$X git-ls-tree$X git-mailinfo$X git-merge-base$X \
+	git-ls-files$X git-ls-tree$X git-mailinfo$X \
 	git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \
 	git-peek-remote$X git-prune-packed$X git-read-tree$X \
 	git-receive-pack$X git-rev-parse$X \
@@ -170,7 +170,8 @@ PROGRAMS = \
 
 BUILT_INS = git-log$X git-whatchanged$X git-show$X \
 	git-count-objects$X git-diff$X git-push$X \
-	git-grep$X git-add$X git-rev-list$X git-check-ref-format$X
+	git-grep$X git-add$X git-rev-list$X git-check-ref-format$X \
+	git-branch$X git-merge-base$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -214,11 +215,13 @@ LIB_OBJS = \
 	server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
 	tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
 	fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
+	merge-base.o \
 	$(DIFF_OBJS)
 
 BUILTIN_OBJS = \
-	builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
-	builtin-grep.o builtin-add.o builtin-rev-list.o builtin-check-ref-format.o
+	builtin-log.o builtin-help.o builtin-count.o builtin-diff.o \
+	builtin-push.o builtin-grep.o builtin-add.o builtin-rev-list.o \
+	builtin-check-ref-format.o builtin-branch.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
diff --git a/builtin-branch.c b/builtin-branch.c
new file mode 100644
index 0000000..c7776a3
--- /dev/null
+++ b/builtin-branch.c
@@ -0,0 +1,154 @@
+/*
+ * Builtin "git branch"
+ *
+ * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-branch.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "refs.h"
+#include "commit.h"
+#include "builtin.h"
+
+static const char builtin_branch_usage[] =
+	"git-branch [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r";
+
+
+static int remote_only = 0;
+static const char *head;
+static unsigned char head_sha1[20];
+
+static int find_sha1(struct commit *commit, void *data)
+{
+	return !memcmp(data, commit->object.sha1, sizeof commit->object.sha1);
+}
+
+static void delete_branches(int argc, const char **argv, int force)
+{
+	struct commit *rev1, *rev2;
+	unsigned char sha1[20];
+	const char *p, *name;
+	int i;
+
+	for (i = 0; i < argc; i++) {
+		if (!strcmp(head, argv[i]))
+			die("Cannot delete the branch you are currently on.");
+
+		name = git_path("refs/heads/%s", argv[i]);
+		p = resolve_ref(name, sha1, 1);
+		if (p == NULL)
+			die("Branch '%s' not found.", argv[i]);
+
+		rev1 = lookup_commit_reference(sha1);
+		rev2 = lookup_commit_reference(head_sha1);
+		if (!rev1 || !rev2)
+			die("Couldn't look up commit objects.");
+
+		/* This checks wether the merge bases of branch and
+		 * HEAD contains branch -- which means that the HEAD
+		 * contains everything in both.
+		 */
+
+		if (!force &&
+		    !for_each_merge_base(rev1, rev2, find_sha1, sha1)) {
+			fprintf(stderr,
+				"The branch '%s' is not a strict subset of your current HEAD.\n"
+				"If you are sure you want to delete it, run 'git branch -D %s'.\n",
+				argv[i], argv[i]);
+			exit(1);
+		}
+
+		unlink(name);
+		printf("Deleted branch %s.\n", argv[i]);
+	}
+}
+
+static int show_reference(const char *refname, const unsigned char *sha1)
+{
+	int is_head = !strcmp(refname, head);
+
+	printf("%c %s\n", (is_head ? '*' : ' '), refname);
+
+	return 0;
+}
+
+static void create_branch (const char *name, const char *start, int force)
+{
+	unsigned char sha1[20];
+	char ref[500];
+
+	snprintf (ref, sizeof ref, "heads/%s", name);
+	if (check_ref_format(ref))
+		die("'%s' is not a valid branch name.", name);
+
+	if (resolve_ref(git_path("refs/%s", ref), sha1, 1)) {
+		if (!force)
+			die("A branch named '%s' already exists.", name);
+		else if (!strcmp(head, name))
+			die("Cannot force update the current branch.");
+	}
+
+	if (get_sha1(start, sha1))
+		die("Not a valid branch point: '%s'", start);
+
+	if (write_ref_sha1_unlocked(ref, sha1))
+		die("Failed to create branch: %s.", strerror(errno));
+}
+
+int cmd_branch(int argc, const char **argv, char **envp)
+{
+	int delete = 0, force_delete = 0, force_create = 0;
+	int i, prefix_length;
+	const char *p;
+
+	setup_git_directory();
+	git_config(git_default_config);
+
+	for (i = 1; i < argc; i++) {
+		const char *arg = argv[i];
+
+		if (arg[0] != '-')
+			break;
+		if (!strcmp(arg, "--")) {
+			i++;
+			break;
+		}
+		if (!strcmp(arg, "-d")) {
+			delete = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-D")) {
+			delete = 1;
+			force_delete = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-f")) {
+			force_create = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-r")) {
+			remote_only = 1;
+			continue;
+		}
+		die(builtin_branch_usage);
+	}
+
+	prefix_length = strlen(git_path("refs/heads/"));
+	p = resolve_ref(git_path("HEAD"), head_sha1, 0);
+	if (!p)
+		die("Failed to resolve HEAD as a valid ref");
+	head = strdup(p + prefix_length);
+
+	if (delete)
+		delete_branches(argc - i, argv + i, force_delete);
+	else if (i == argc && remote_only)
+		for_each_remote_ref(show_reference);
+	else if (i == argc)
+		for_each_branch_ref(show_reference);
+	else if (argc - i == 1)
+		create_branch (argv[i], head, force_create);
+	else
+		create_branch (argv[i], argv[i + 1], force_create);
+
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index 78275ea..0105358 100644
--- a/builtin.h
+++ b/builtin.h
@@ -28,5 +28,7 @@ extern int cmd_grep(int argc, const char
 extern int cmd_add(int argc, const char **argv, char **envp);
 extern int cmd_rev_list(int argc, const char **argv, char **envp);
 extern int cmd_check_ref_format(int argc, const char **argv, char **envp);
+extern int cmd_branch(int argc, const char **argv, char **envp);
+extern int cmd_merge_base(int argc, const char **argv, char **envp);
 
 #endif
diff --git a/commit.h b/commit.h
index 8d7514c..b1a518a 100644
--- a/commit.h
+++ b/commit.h
@@ -104,4 +104,9 @@ struct commit_graft *read_graft_line(cha
 int register_commit_graft(struct commit_graft *, int);
 int read_graft_file(const char *graft_file);
 
+/* merge-base.c */
+int for_each_merge_base(struct commit *rev1, struct commit *rev2,
+			int (*fn)(struct commit *commit, void *data),
+			void  *data);
+
 #endif /* COMMIT_H */
diff --git a/git-branch.sh b/git-branch.sh
deleted file mode 100755
index 134e68c..0000000
--- a/git-branch.sh
+++ /dev/null
@@ -1,120 +0,0 @@
-#!/bin/sh
-
-USAGE='[(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r'
-LONG_USAGE='If no arguments, show available branches and mark current branch with a star.
-If one argument, create a new branch <branchname> based off of current HEAD.
-If two arguments, create a new branch <branchname> based off of <start-point>.'
-
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-headref=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||')
-
-delete_branch () {
-    option="$1"
-    shift
-    for branch_name
-    do
-	case ",$headref," in
-	",$branch_name,")
-	    die "Cannot delete the branch you are on." ;;
-	,,)
-	    die "What branch are you on anyway?" ;;
-	esac
-	branch=$(cat "$GIT_DIR/refs/heads/$branch_name") &&
-	    branch=$(git-rev-parse --verify "$branch^0") ||
-		die "Seriously, what branch are you talking about?"
-	case "$option" in
-	-D)
-	    ;;
-	*)
-	    mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ')
-	    case " $mbs " in
-	    *' '$branch' '*)
-		# the merge base of branch and HEAD contains branch --
-		# which means that the HEAD contains everything in both.
-		;;
-	    *)
-		echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD.
-If you are sure you want to delete it, run 'git branch -D $branch_name'."
-		exit 1
-		;;
-	    esac
-	    ;;
-	esac
-	rm -f "$GIT_DIR/refs/heads/$branch_name"
-	echo "Deleted branch $branch_name."
-    done
-    exit 0
-}
-
-ls_remote_branches () {
-    git-rev-parse --symbolic --all |
-    sed -ne 's|^refs/\(remotes/\)|\1|p' |
-    sort
-}
-
-force=
-while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
-do
-	case "$1" in
-	-d | -D)
-		delete_branch "$@"
-		exit
-		;;
-	-r)
-		ls_remote_branches
-		exit
-		;;
-	-f)
-		force="$1"
-		;;
-	--)
-		shift
-		break
-		;;
-	-*)
-		usage
-		;;
-	esac
-	shift
-done
-
-case "$#" in
-0)
-	git-rev-parse --symbolic --branches |
-	sort |
-	while read ref
-	do
-		if test "$headref" = "$ref"
-		then
-			pfx='*'
-		else
-			pfx=' '
-		fi
-		echo "$pfx $ref"
-	done
-	exit 0 ;;
-1)
-	head=HEAD ;;
-2)
-	head="$2^0" ;;
-esac
-branchname="$1"
-
-rev=$(git-rev-parse --verify "$head") || exit
-
-git-check-ref-format "heads/$branchname" ||
-	die "we do not like '$branchname' as a branch name."
-
-if [ -e "$GIT_DIR/refs/heads/$branchname" ]
-then
-	if test '' = "$force"
-	then
-		die "$branchname already exists."
-	elif test "$branchname" = "$headref"
-	then
-		die "cannot force-update the current branch."
-	fi
-fi
-git update-ref "refs/heads/$branchname" $rev
diff --git a/git.c b/git.c
index 7db5cc1..fb66c0e 100644
--- a/git.c
+++ b/git.c
@@ -53,7 +53,9 @@ static void handle_internal_command(int 
 		{ "grep", cmd_grep },
 		{ "add", cmd_add },
 		{ "rev-list", cmd_rev_list },
-		{ "check-ref-format", cmd_check_ref_format }
+		{ "check-ref-format", cmd_check_ref_format },
+		{ "branch", cmd_branch },
+		{ "merge-base", cmd_merge_base }
 	};
 	int i;
 
diff --git a/merge-base.c b/merge-base.c
index 4856ca0..0aa6ed4 100644
--- a/merge-base.c
+++ b/merge-base.c
@@ -1,6 +1,6 @@
-#include <stdlib.h>
 #include "cache.h"
 #include "commit.h"
+#include "builtin.h"
 
 #define PARENT1 1
 #define PARENT2 2
@@ -167,16 +167,16 @@ static void mark_reachable_commits(struc
 	}
 }
 
-static int merge_base(struct commit *rev1, struct commit *rev2)
+int for_each_merge_base(struct commit *rev1, struct commit *rev2,
+			int (*fn)(struct commit *commit, void *data),
+			void *data)
 {
 	struct commit_list *list = NULL;
 	struct commit_list *result = NULL;
 	struct commit_list *tmp = NULL;
 
-	if (rev1 == rev2) {
-		printf("%s\n", sha1_to_hex(rev1->object.sha1));
-		return 0;
-	}
+	if (rev1 == rev2) 
+		return fn(rev1, data);
 
 	parse_commit(rev1);
 	parse_commit(rev2);
@@ -220,12 +220,13 @@ static int merge_base(struct commit *rev
 
 	while (result) {
 		struct commit *commit = result->item;
+		int retval;
 		result = result->next;
 		if (commit->object.flags & UNINTERESTING)
 			continue;
-		printf("%s\n", sha1_to_hex(commit->object.sha1));
-		if (!show_all)
-			return 0;
+		retval = fn(commit, data);
+		if (retval)
+			return retval;
 		commit->object.flags |= UNINTERESTING;
 	}
 	return 0;
@@ -234,7 +235,13 @@ static int merge_base(struct commit *rev
 static const char merge_base_usage[] =
 "git-merge-base [--all] <commit-id> <commit-id>";
 
-int main(int argc, char **argv)
+static int print_merge_base(struct commit *commit, void *data)
+{
+	printf("%s\n", sha1_to_hex(commit->object.sha1));
+	return show_all ? 0 : 1;
+}
+
+int cmd_merge_base(int argc, const char **argv, char **envp)
 {
 	struct commit *rev1, *rev2;
 	unsigned char rev1key[20], rev2key[20];
@@ -243,7 +250,7 @@ int main(int argc, char **argv)
 	git_config(git_default_config);
 
 	while (1 < argc && argv[1][0] == '-') {
-		char *arg = argv[1];
+		const char *arg = argv[1];
 		if (!strcmp(arg, "-a") || !strcmp(arg, "--all"))
 			show_all = 1;
 		else
@@ -260,5 +267,8 @@ int main(int argc, char **argv)
 	rev2 = lookup_commit_reference(rev2key);
 	if (!rev1 || !rev2)
 		return 1;
-	return merge_base(rev1, rev2);
+
+	for_each_merge_base(rev1, rev2, print_merge_base, NULL);
+
+	return 0;
 }

6b90d7b7af4bf577ccfeebef1f736e75631b052d
diff --git a/Makefile b/Makefile
index 5373986..a709e40 100644
--- a/Makefile
+++ b/Makefile
@@ -113,7 +113,7 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powe
 ### --- END CONFIGURATION SECTION ---
 
 SCRIPT_SH = \
-	git-bisect.sh git-branch.sh git-checkout.sh \
+	git-bisect.sh git-checkout.sh \
 	git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \
 	git-fetch.sh \
 	git-format-patch.sh git-ls-remote.sh \
@@ -155,7 +155,7 @@ PROGRAMS = \
 	git-diff-index$X git-diff-stages$X \
 	git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \
 	git-hash-object$X git-index-pack$X git-init-db$X git-local-fetch$X \
-	git-ls-files$X git-ls-tree$X git-mailinfo$X git-merge-base$X \
+	git-ls-files$X git-ls-tree$X git-mailinfo$X \
 	git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \
 	git-peek-remote$X git-prune-packed$X git-read-tree$X \
 	git-receive-pack$X git-rev-parse$X \
@@ -170,7 +170,8 @@ PROGRAMS = \
 
 BUILT_INS = git-log$X git-whatchanged$X git-show$X \
 	git-count-objects$X git-diff$X git-push$X \
-	git-grep$X git-add$X git-rev-list$X git-check-ref-format$X
+	git-grep$X git-add$X git-rev-list$X git-check-ref-format$X \
+	git-branch$X git-merge-base$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -214,11 +215,13 @@ LIB_OBJS = \
 	server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
 	tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
 	fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
+	merge-base.o \
 	$(DIFF_OBJS)
 
 BUILTIN_OBJS = \
-	builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
-	builtin-grep.o builtin-add.o builtin-rev-list.o builtin-check-ref-format.o
+	builtin-log.o builtin-help.o builtin-count.o builtin-diff.o \
+	builtin-push.o builtin-grep.o builtin-add.o builtin-rev-list.o \
+	builtin-check-ref-format.o builtin-branch.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
diff --git a/builtin-branch.c b/builtin-branch.c
new file mode 100644
index 0000000..c7776a3
--- /dev/null
+++ b/builtin-branch.c
@@ -0,0 +1,154 @@
+/*
+ * Builtin "git branch"
+ *
+ * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-branch.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "refs.h"
+#include "commit.h"
+#include "builtin.h"
+
+static const char builtin_branch_usage[] =
+	"git-branch [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r";
+
+
+static int remote_only = 0;
+static const char *head;
+static unsigned char head_sha1[20];
+
+static int find_sha1(struct commit *commit, void *data)
+{
+	return !memcmp(data, commit->object.sha1, sizeof commit->object.sha1);
+}
+
+static void delete_branches(int argc, const char **argv, int force)
+{
+	struct commit *rev1, *rev2;
+	unsigned char sha1[20];
+	const char *p, *name;
+	int i;
+
+	for (i = 0; i < argc; i++) {
+		if (!strcmp(head, argv[i]))
+			die("Cannot delete the branch you are currently on.");
+
+		name = git_path("refs/heads/%s", argv[i]);
+		p = resolve_ref(name, sha1, 1);
+		if (p == NULL)
+			die("Branch '%s' not found.", argv[i]);
+
+		rev1 = lookup_commit_reference(sha1);
+		rev2 = lookup_commit_reference(head_sha1);
+		if (!rev1 || !rev2)
+			die("Couldn't look up commit objects.");
+
+		/* This checks wether the merge bases of branch and
+		 * HEAD contains branch -- which means that the HEAD
+		 * contains everything in both.
+		 */
+
+		if (!force &&
+		    !for_each_merge_base(rev1, rev2, find_sha1, sha1)) {
+			fprintf(stderr,
+				"The branch '%s' is not a strict subset of your current HEAD.\n"
+				"If you are sure you want to delete it, run 'git branch -D %s'.\n",
+				argv[i], argv[i]);
+			exit(1);
+		}
+
+		unlink(name);
+		printf("Deleted branch %s.\n", argv[i]);
+	}
+}
+
+static int show_reference(const char *refname, const unsigned char *sha1)
+{
+	int is_head = !strcmp(refname, head);
+
+	printf("%c %s\n", (is_head ? '*' : ' '), refname);
+
+	return 0;
+}
+
+static void create_branch (const char *name, const char *start, int force)
+{
+	unsigned char sha1[20];
+	char ref[500];
+
+	snprintf (ref, sizeof ref, "heads/%s", name);
+	if (check_ref_format(ref))
+		die("'%s' is not a valid branch name.", name);
+
+	if (resolve_ref(git_path("refs/%s", ref), sha1, 1)) {
+		if (!force)
+			die("A branch named '%s' already exists.", name);
+		else if (!strcmp(head, name))
+			die("Cannot force update the current branch.");
+	}
+
+	if (get_sha1(start, sha1))
+		die("Not a valid branch point: '%s'", start);
+
+	if (write_ref_sha1_unlocked(ref, sha1))
+		die("Failed to create branch: %s.", strerror(errno));
+}
+
+int cmd_branch(int argc, const char **argv, char **envp)
+{
+	int delete = 0, force_delete = 0, force_create = 0;
+	int i, prefix_length;
+	const char *p;
+
+	setup_git_directory();
+	git_config(git_default_config);
+
+	for (i = 1; i < argc; i++) {
+		const char *arg = argv[i];
+
+		if (arg[0] != '-')
+			break;
+		if (!strcmp(arg, "--")) {
+			i++;
+			break;
+		}
+		if (!strcmp(arg, "-d")) {
+			delete = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-D")) {
+			delete = 1;
+			force_delete = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-f")) {
+			force_create = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-r")) {
+			remote_only = 1;
+			continue;
+		}
+		die(builtin_branch_usage);
+	}
+
+	prefix_length = strlen(git_path("refs/heads/"));
+	p = resolve_ref(git_path("HEAD"), head_sha1, 0);
+	if (!p)
+		die("Failed to resolve HEAD as a valid ref");
+	head = strdup(p + prefix_length);
+
+	if (delete)
+		delete_branches(argc - i, argv + i, force_delete);
+	else if (i == argc && remote_only)
+		for_each_remote_ref(show_reference);
+	else if (i == argc)
+		for_each_branch_ref(show_reference);
+	else if (argc - i == 1)
+		create_branch (argv[i], head, force_create);
+	else
+		create_branch (argv[i], argv[i + 1], force_create);
+
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index 78275ea..0105358 100644
--- a/builtin.h
+++ b/builtin.h
@@ -28,5 +28,7 @@ extern int cmd_grep(int argc, const char
 extern int cmd_add(int argc, const char **argv, char **envp);
 extern int cmd_rev_list(int argc, const char **argv, char **envp);
 extern int cmd_check_ref_format(int argc, const char **argv, char **envp);
+extern int cmd_branch(int argc, const char **argv, char **envp);
+extern int cmd_merge_base(int argc, const char **argv, char **envp);
 
 #endif
diff --git a/commit.h b/commit.h
index 8d7514c..b1a518a 100644
--- a/commit.h
+++ b/commit.h
@@ -104,4 +104,9 @@ struct commit_graft *read_graft_line(cha
 int register_commit_graft(struct commit_graft *, int);
 int read_graft_file(const char *graft_file);
 
+/* merge-base.c */
+int for_each_merge_base(struct commit *rev1, struct commit *rev2,
+			int (*fn)(struct commit *commit, void *data),
+			void  *data);
+
 #endif /* COMMIT_H */
diff --git a/git-branch.sh b/git-branch.sh
deleted file mode 100755
index 134e68c..0000000
--- a/git-branch.sh
+++ /dev/null
@@ -1,120 +0,0 @@
-#!/bin/sh
-
-USAGE='[(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r'
-LONG_USAGE='If no arguments, show available branches and mark current branch with a star.
-If one argument, create a new branch <branchname> based off of current HEAD.
-If two arguments, create a new branch <branchname> based off of <start-point>.'
-
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-headref=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||')
-
-delete_branch () {
-    option="$1"
-    shift
-    for branch_name
-    do
-	case ",$headref," in
-	",$branch_name,")
-	    die "Cannot delete the branch you are on." ;;
-	,,)
-	    die "What branch are you on anyway?" ;;
-	esac
-	branch=$(cat "$GIT_DIR/refs/heads/$branch_name") &&
-	    branch=$(git-rev-parse --verify "$branch^0") ||
-		die "Seriously, what branch are you talking about?"
-	case "$option" in
-	-D)
-	    ;;
-	*)
-	    mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ')
-	    case " $mbs " in
-	    *' '$branch' '*)
-		# the merge base of branch and HEAD contains branch --
-		# which means that the HEAD contains everything in both.
-		;;
-	    *)
-		echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD.
-If you are sure you want to delete it, run 'git branch -D $branch_name'."
-		exit 1
-		;;
-	    esac
-	    ;;
-	esac
-	rm -f "$GIT_DIR/refs/heads/$branch_name"
-	echo "Deleted branch $branch_name."
-    done
-    exit 0
-}
-
-ls_remote_branches () {
-    git-rev-parse --symbolic --all |
-    sed -ne 's|^refs/\(remotes/\)|\1|p' |
-    sort
-}
-
-force=
-while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
-do
-	case "$1" in
-	-d | -D)
-		delete_branch "$@"
-		exit
-		;;
-	-r)
-		ls_remote_branches
-		exit
-		;;
-	-f)
-		force="$1"
-		;;
-	--)
-		shift
-		break
-		;;
-	-*)
-		usage
-		;;
-	esac
-	shift
-done
-
-case "$#" in
-0)
-	git-rev-parse --symbolic --branches |
-	sort |
-	while read ref
-	do
-		if test "$headref" = "$ref"
-		then
-			pfx='*'
-		else
-			pfx=' '
-		fi
-		echo "$pfx $ref"
-	done
-	exit 0 ;;
-1)
-	head=HEAD ;;
-2)
-	head="$2^0" ;;
-esac
-branchname="$1"
-
-rev=$(git-rev-parse --verify "$head") || exit
-
-git-check-ref-format "heads/$branchname" ||
-	die "we do not like '$branchname' as a branch name."
-
-if [ -e "$GIT_DIR/refs/heads/$branchname" ]
-then
-	if test '' = "$force"
-	then
-		die "$branchname already exists."
-	elif test "$branchname" = "$headref"
-	then
-		die "cannot force-update the current branch."
-	fi
-fi
-git update-ref "refs/heads/$branchname" $rev
diff --git a/git.c b/git.c
index 7db5cc1..fb66c0e 100644
--- a/git.c
+++ b/git.c
@@ -53,7 +53,9 @@ static void handle_internal_command(int 
 		{ "grep", cmd_grep },
 		{ "add", cmd_add },
 		{ "rev-list", cmd_rev_list },
-		{ "check-ref-format", cmd_check_ref_format }
+		{ "check-ref-format", cmd_check_ref_format },
+		{ "branch", cmd_branch },
+		{ "merge-base", cmd_merge_base }
 	};
 	int i;
 
diff --git a/merge-base.c b/merge-base.c
index 4856ca0..0aa6ed4 100644
--- a/merge-base.c
+++ b/merge-base.c
@@ -1,6 +1,6 @@
-#include <stdlib.h>
 #include "cache.h"
 #include "commit.h"
+#include "builtin.h"
 
 #define PARENT1 1
 #define PARENT2 2
@@ -167,16 +167,16 @@ static void mark_reachable_commits(struc
 	}
 }
 
-static int merge_base(struct commit *rev1, struct commit *rev2)
+int for_each_merge_base(struct commit *rev1, struct commit *rev2,
+			int (*fn)(struct commit *commit, void *data),
+			void *data)
 {
 	struct commit_list *list = NULL;
 	struct commit_list *result = NULL;
 	struct commit_list *tmp = NULL;
 
-	if (rev1 == rev2) {
-		printf("%s\n", sha1_to_hex(rev1->object.sha1));
-		return 0;
-	}
+	if (rev1 == rev2) 
+		return fn(rev1, data);
 
 	parse_commit(rev1);
 	parse_commit(rev2);
@@ -220,12 +220,13 @@ static int merge_base(struct commit *rev
 
 	while (result) {
 		struct commit *commit = result->item;
+		int retval;
 		result = result->next;
 		if (commit->object.flags & UNINTERESTING)
 			continue;
-		printf("%s\n", sha1_to_hex(commit->object.sha1));
-		if (!show_all)
-			return 0;
+		retval = fn(commit, data);
+		if (retval)
+			return retval;
 		commit->object.flags |= UNINTERESTING;
 	}
 	return 0;
@@ -234,7 +235,13 @@ static int merge_base(struct commit *rev
 static const char merge_base_usage[] =
 "git-merge-base [--all] <commit-id> <commit-id>";
 
-int main(int argc, char **argv)
+static int print_merge_base(struct commit *commit, void *data)
+{
+	printf("%s\n", sha1_to_hex(commit->object.sha1));
+	return show_all ? 0 : 1;
+}
+
+int cmd_merge_base(int argc, const char **argv, char **envp)
 {
 	struct commit *rev1, *rev2;
 	unsigned char rev1key[20], rev2key[20];
@@ -243,7 +250,7 @@ int main(int argc, char **argv)
 	git_config(git_default_config);
 
 	while (1 < argc && argv[1][0] == '-') {
-		char *arg = argv[1];
+		const char *arg = argv[1];
 		if (!strcmp(arg, "-a") || !strcmp(arg, "--all"))
 			show_all = 1;
 		else
@@ -260,5 +267,8 @@ int main(int argc, char **argv)
 	rev2 = lookup_commit_reference(rev2key);
 	if (!rev1 || !rev2)
 		return 1;
-	return merge_base(rev1, rev2);
+
+	for_each_merge_base(rev1, rev2, print_merge_base, NULL);
+
+	return 0;
 }

^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH] Implement git-branch and git-merge-base as built-ins.
  2006-06-08 17:49 [PATCH] Implement git-branch and git-merge-base as built-ins Kristian Høgsberg
@ 2006-06-08 18:53 ` Junio C Hamano
  0 siblings, 0 replies; 2+ messages in thread
From: Junio C Hamano @ 2006-06-08 18:53 UTC (permalink / raw)
  To: Kristian Høgsberg; +Cc: git

Kristian Høgsberg <krh@bitplanet.net> writes:

> This patch is more or less a straight port of git-branch from shell
> script to C.  Branch deletion uses git-merge-base to check if it is safe
> to delete a branch, so I changed merge-base.c to export this functionality
> as a for_each_merge_base() iterator.  As a side effect, git-merge-base is
> now also a built-in command.

First, some lighter-weight comments:

 (0) Somehow your diff have two copies of everything.  How did
     you prepare this message I wonder...?

 (1) Sign your work, please.

 (2) I would have preferred a patch to do merge-base and another
     patch to do branch.  That way, we could do merge-base
     without doing branch if we wanted to.

But the patch is wrong.  Your for-each-merge-base cannot be
called more than once, but delete-branches does.

The merge-base program as implemented currently is written with
the assumption that it is called only once, and leaves its
working state in parsed commit objects all over the place.  In
order to make the second and subsequent call to work correctly,
you need to clean the flags up.

As a demonstration, with this function appended to your
merge-base.c and making it a built-in "merge-base-bogo":

int cmd_merge_base_bogo(int argc, const char **argv, char **envp)
{
	struct commit *rev1, *rev2;
	unsigned char rev1key[20], rev2key[20];
	int errors = 0;

	setup_git_directory();
	git_config(git_default_config);

	while (1 < argc && argv[1][0] == '-') {
		const char *arg = argv[1];
		if (!strcmp(arg, "-a") || !strcmp(arg, "--all"))
			show_all = 1;
		else
			usage(merge_base_usage);
		argc--; argv++;
	}
	for (; 3 <= argc; argc -= 2, argv += 2) {
		if (get_sha1(argv[1], rev1key) ||
		    !(rev1 = lookup_commit_reference(rev1key))) {
			error("Not a valid object name %s", argv[1]);
			errors++;
			continue;
		}
		if (get_sha1(argv[2], rev2key) ||
		    !(rev2 = lookup_commit_reference(rev2key))) {
			error("Not a valid object name %s", argv[2]);
			errors++;
			continue;
		}
		for_each_merge_base(rev1, rev2, print_merge_base, NULL);
	}
	if (1 < argc) {
		error("Trailing argument %s not used", argv[1]);
		errors++;
	}
	return !!errors;
}

Here is what happens.

: gitster; ./git merge-base-bogo 66ae0c77 ced9456a
262a6ef76a1dde97ab50d79fa5cd6d3f9f125765
: gitster; ./git merge-base-bogo 89719209 262a6ef7
aa6bf0eb6489d652c5877d65160ed33c857afa74
: gitster; ./git merge-base-bogo 66ae0c77 ced9456a 89719209 262a6ef7
262a6ef76a1dde97ab50d79fa5cd6d3f9f125765
262a6ef76a1dde97ab50d79fa5cd6d3f9f125765

This is because the invocation for the second pair does not
start with a clean slate, and is affected by the leftover states
from the computation for the first pair.  If you swap the
arguments, you sometimes get correct result by accident, like this:

: gitster; ./git merge-base-bogo 89719209 262a6ef7 66ae0c77 ced9456a
aa6bf0eb6489d652c5877d65160ed33c857afa74
262a6ef76a1dde97ab50d79fa5cd6d3f9f125765

Incidentally, this is why I haven't done "A...B" revision syntax
extension to mean "^$(git merge-base A B) B".

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2006-06-08 18:53 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2006-06-08 17:49 [PATCH] Implement git-branch and git-merge-base as built-ins Kristian Høgsberg
2006-06-08 18:53 ` Junio C Hamano

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).