Implement git-branch and git-merge-base as built-ins.
PATCH 1/1 Implement git-branch and git-merge-base as built-ins.
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(-)
Makefile +0 -0
@@ -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
builtin-branch.c new file +154 -0
154 lines not on this page · raw message
builtin.h +0 -0
@@ -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
commit.h +0 -0
@@ -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 */
git-branch.sh deleted +0 -120
120 lines not on this page · raw message
git.c +0 -0
@@ -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;
merge-base.c +0 -1
@@ -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
Makefile +0 -0
@@ -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
builtin-branch.c new file +154 -0
154 lines not on this page · raw message
builtin.h +0 -0
@@ -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
commit.h +0 -0
@@ -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 */
git-branch.sh deleted +0 -120
120 lines not on this page · raw message
git.c +0 -0
@@ -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;
merge-base.c +0 -1
@@ -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;
}
Quoted 5 lines · Kristian Høgsberg
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.cto export this functionality as afor_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".