From: "Kristian Høgsberg" <krh@bitplanet.net>
To: "Jonas Fonseca" <fonseca@diku.dk>, "Shawn Pearce" <spearce@spearce.org>
Cc: git@vger.kernel.org
Subject: Re: [PATCH] branch as a builtin (again)
Date: Mon, 21 Aug 2006 16:12:41 -0400 [thread overview]
Message-ID: <59ad55d30608211312u51a4657eyd52311314a6ee03c@mail.gmail.com> (raw)
In-Reply-To: <20060821101346.GA527@diku.dk>
[-- Attachment #1: Type: text/plain, Size: 905 bytes --]
Thanks to all who reviewed the patch, here's an updated version which
should address all issues. In particular, thanks to Jonas who spotted
the missing git_path() before resolve_ref() - this caused git branch
to always overwrite existing branches because it failed to resolve the
ref.
As for the missing reflog functionality - when I first did the shell
to C port, git branch didn't have this feature, and this time I just
updated the old patch which is how I forgot about the reflog option.
The new patch attached here should have the same reflog behavior as
the current shell script. Shawn, could you check that it has the
correct sematics? I wasn't sure whether git branch -f foo should
truncate the old log or just keep appending. The shell script does a
'touch <logfile>' on creation, which means keep appending when
forcibly overwriting a branch ref, so I kept that behavior.
cheers,
Kristian
[-- Attachment #2: builtin-branch.patch --]
[-- Type: application/octet-stream, Size: 10364 bytes --]
commit d5d82b9c4493df467ee31776cadff808563f00b1
Author: Kristian Høgsberg <krh@redhat.com>
Date: Sun Aug 20 17:04:14 2006 -0400
Rewrite branch in C and make it a builtin.
diff --git a/Makefile b/Makefile
index 23cd8a0..adf043e 100644
--- a/Makefile
+++ b/Makefile
@@ -149,7 +149,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-ls-remote.sh \
@@ -253,6 +253,7 @@ LIB_OBJS = \
BUILTIN_OBJS = \
builtin-add.o \
builtin-apply.o \
+ builtin-branch.o \
builtin-cat-file.o \
builtin-checkout-index.o \
builtin-check-ref-format.o \
diff --git a/builtin-branch.c b/builtin-branch.c
new file mode 100644
index 0000000..4e8fa7d
--- /dev/null
+++ b/builtin-branch.c
@@ -0,0 +1,227 @@
+/*
+ * 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 const char *head;
+static unsigned char head_sha1[20];
+
+static int in_merge_bases(const unsigned char *sha1,
+ struct commit *rev1,
+ struct commit *rev2)
+{
+ struct commit_list *bases, *b;
+ int ret = 0;
+
+ bases = get_merge_bases(rev1, rev2, 1);
+ for (b = bases; b != NULL; b = b->next) {
+ if (!hashcmp(sha1, b->item->object.sha1)) {
+ ret = 1;
+ break;
+ }
+ }
+
+ free_commit_list(bases);
+ return ret;
+}
+
+static void delete_branches(int argc, const char **argv, int force)
+{
+ struct commit *rev, *head_rev;
+ unsigned char sha1[20];
+ const char *name, *reflog;
+ int i;
+
+ head_rev = lookup_commit_reference(head_sha1);
+ 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]);
+ if (!resolve_ref(name, sha1, 1))
+ die("Branch '%s' not found.", argv[i]);
+
+ rev = lookup_commit_reference(sha1);
+ if (!rev || !head_rev)
+ die("Couldn't look up commit objects.");
+
+ /* This checks whether the merge bases of branch and
+ * HEAD contains branch -- which means that the HEAD
+ * contains everything in both.
+ */
+
+ if (!force &&
+ !in_merge_bases(sha1, rev, head_rev)) {
+ 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);
+
+ /* Unlink reflog if it exists. */
+ reflog = git_path("logs/refs/heads/%s", argv[i]);
+ unlink(reflog);
+
+ printf("Deleted branch %s.\n", argv[i]);
+ }
+}
+
+static int ref_index, ref_alloc;
+static char **ref_list;
+
+static int append_ref(const char *refname, const unsigned char *sha1)
+{
+ if (ref_index >= ref_alloc) {
+ ref_alloc = ref_alloc > 0 ? ref_alloc * 2 : 16;
+ ref_list = realloc(ref_list, ref_alloc * sizeof (char *));
+ }
+
+ ref_list[ref_index++] = strdup(refname);
+
+ return 0;
+}
+
+static int ref_cmp (const void *r1, const void *r2)
+{
+ return strcmp (*(char **)r1, *(char **)r2);
+}
+
+static void print_ref_list(int remote_only)
+{
+ int i;
+
+ if (remote_only)
+ for_each_remote_ref(append_ref);
+ else
+ for_each_branch_ref(append_ref);
+
+ qsort(ref_list, ref_index, sizeof (char *), ref_cmp);
+
+ for (i = 0; i < ref_index; i++) {
+ if (!strcmp(ref_list[i], head))
+ printf("* %s\n", ref_list[i]);
+ else
+ printf(" %s\n", ref_list[i]);
+ }
+}
+
+static void create_reflog(struct ref_lock *lock)
+{
+ struct stat stbuf;
+ int fd;
+
+ if (!stat(lock->log_file, &stbuf) && S_ISREG(stbuf.st_mode))
+ return;
+ if (safe_create_leading_directories(lock->log_file) < 0)
+ die("Unable to create directory for %s.", lock->log_file);
+ fd = open(lock->log_file, O_CREAT | O_TRUNC | O_WRONLY, 0666);
+ if (fd < 0)
+ die("Unable to create ref log %s: %s.",
+ lock->log_file, strerror(errno));
+ close(fd);
+}
+
+static void create_branch(const char *name, const char *start,
+ int force, int reflog)
+{
+ struct ref_lock *lock;
+ unsigned char sha1[20];
+ char ref[PATH_MAX], msg[PATH_MAX + 20];
+
+ snprintf(ref, sizeof ref, "refs/heads/%s", name);
+ if (check_ref_format(ref))
+ die("'%s' is not a valid branch name.", name);
+
+ if (resolve_ref(git_path(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);
+
+ lock = lock_any_ref_for_update(ref, NULL, 0);
+ if (!lock)
+ die("Failed to lock ref for update: %s.", strerror(errno));
+ if (reflog)
+ create_reflog(lock);
+ snprintf(msg, sizeof msg, "branch: Created from %s", start);
+ if (write_ref_sha1(lock, sha1, msg) < 0)
+ die("Failed to write ref: %s.", strerror(errno));
+}
+
+int cmd_branch(int argc, const char **argv, const char *prefix)
+{
+ int delete = 0, force_delete = 0, force_create = 0, remote_only = 0;
+ int reflog = 0;
+ int i, prefix_length;
+ const char *p;
+
+ 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;
+ }
+ if (!strcmp(arg, "-l")) {
+ reflog = 1;
+ continue;
+ }
+ usage(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)
+ print_ref_list(remote_only);
+ else if (argc - i == 1)
+ create_branch(argv[i], head, force_create, reflog);
+ else
+ create_branch(argv[i], argv[i + 1], force_create, reflog);
+
+ return 0;
+}
diff --git a/builtin.h b/builtin.h
index ade58c4..eb28986 100644
--- a/builtin.h
+++ b/builtin.h
@@ -15,6 +15,7 @@ extern int write_tree(unsigned char *sha
extern int cmd_add(int argc, const char **argv, const char *prefix);
extern int cmd_apply(int argc, const char **argv, const char *prefix);
+extern int cmd_branch(int argc, const char **argv, const char *prefix);
extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
diff --git a/git-branch.sh b/git-branch.sh
deleted file mode 100755
index e0501ec..0000000
--- a/git-branch.sh
+++ /dev/null
@@ -1,130 +0,0 @@
-#!/bin/sh
-
-USAGE='[-l] [(-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/logs/refs/heads/$branch_name"
- 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=
-create_log=
-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"
- ;;
- -l)
- create_log="yes"
- ;;
- --)
- 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
-if test "$create_log" = 'yes'
-then
- mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname")
- touch "$GIT_DIR/logs/refs/heads/$branchname"
-fi
-git update-ref -m "branch: Created from $head" "refs/heads/$branchname" $rev
diff --git a/git.c b/git.c
index 930998b..5738cb4 100644
--- a/git.c
+++ b/git.c
@@ -226,6 +226,7 @@ static void handle_internal_command(int
} commands[] = {
{ "add", cmd_add, RUN_SETUP },
{ "apply", cmd_apply },
+ { "branch", cmd_branch, RUN_SETUP },
{ "cat-file", cmd_cat_file, RUN_SETUP },
{ "checkout-index", cmd_checkout_index, RUN_SETUP },
{ "check-ref-format", cmd_check_ref_format },
next prev parent reply other threads:[~2006-08-21 20:12 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2006-08-20 21:22 [PATCH] branch as a builtin (again) Kristian Høgsberg
2006-08-20 23:55 ` Johannes Schindelin
2006-08-21 7:49 ` David Rientjes
2006-08-21 8:03 ` Shawn Pearce
2006-08-21 10:13 ` Jonas Fonseca
2006-08-21 20:12 ` Kristian Høgsberg [this message]
2006-08-21 20:23 ` David Rientjes
2006-08-21 20:45 ` Kristian Høgsberg
2006-08-22 7:00 ` Junio C Hamano
2006-08-21 20:27 ` Johannes Schindelin
2006-08-21 21:07 ` Kristian Høgsberg
[not found] ` <59ad55d30608211337jabd515bra3566fbd0f7ba5a0@mail.gmail.com>
2006-08-21 21:25 ` Johannes Schindelin
2006-08-21 20:41 ` Shawn Pearce
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=59ad55d30608211312u51a4657eyd52311314a6ee03c@mail.gmail.com \
--to=krh@bitplanet.net \
--cc=fonseca@diku.dk \
--cc=git@vger.kernel.org \
--cc=spearce@spearce.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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).