Merge branch 'jg/tag-contains'

* jg/tag-contains:
  git-tag: Add --contains option
  Make has_commit() non-static
  Make opt_parse_with_commit() non-static
This commit is contained in:
Junio C Hamano 2009-01-31 18:07:59 -08:00
commit b37f26d8a2
8 changed files with 185 additions and 38 deletions

View File

@ -12,7 +12,7 @@ SYNOPSIS
'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] 'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]
<name> [<commit> | <object>] <name> [<commit> | <object>]
'git tag' -d <name>... 'git tag' -d <name>...
'git tag' [-n[<num>]] -l [<pattern>] 'git tag' [-n[<num>]] -l [--contains <commit>] [<pattern>]
'git tag' -v <name>... 'git tag' -v <name>...
DESCRIPTION DESCRIPTION
@ -68,6 +68,9 @@ OPTIONS
List tags with names that match the given pattern (or all if no pattern is given). List tags with names that match the given pattern (or all if no pattern is given).
Typing "git tag" without arguments, also lists all tags. Typing "git tag" without arguments, also lists all tags.
--contains <commit>::
Only list tags which contain the specified commit.
-m <msg>:: -m <msg>::
Use the given tag message (instead of prompting). Use the given tag message (instead of prompting).
If multiple `-m` options are given, their values are If multiple `-m` options are given, their values are

View File

@ -193,21 +193,6 @@ struct ref_list {
int kinds; int kinds;
}; };
static int has_commit(struct commit *commit, struct commit_list *with_commit)
{
if (!with_commit)
return 1;
while (with_commit) {
struct commit *other;
other = with_commit->item;
with_commit = with_commit->next;
if (in_merge_bases(other, &commit, 1))
return 1;
}
return 0;
}
static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
{ {
struct ref_list *ref_list = (struct ref_list*)(cb_data); struct ref_list *ref_list = (struct ref_list*)(cb_data);
@ -231,7 +216,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
return error("branch '%s' does not point at a commit", refname); return error("branch '%s' does not point at a commit", refname);
/* Filter with with_commit if specified */ /* Filter with with_commit if specified */
if (!has_commit(commit, ref_list->with_commit)) if (!is_descendant_of(commit, ref_list->with_commit))
return 0; return 0;
/* Don't add types the caller doesn't want */ /* Don't add types the caller doesn't want */
@ -401,7 +386,8 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str
qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
detached = (detached && (kinds & REF_LOCAL_BRANCH)); detached = (detached && (kinds & REF_LOCAL_BRANCH));
if (detached && head_commit && has_commit(head_commit, with_commit)) { if (detached && head_commit &&
is_descendant_of(head_commit, with_commit)) {
struct ref_item item; struct ref_item item;
item.name = xstrdup("(no branch)"); item.name = xstrdup("(no branch)");
item.kind = REF_LOCAL_BRANCH; item.kind = REF_LOCAL_BRANCH;
@ -466,22 +452,6 @@ static void rename_branch(const char *oldname, const char *newname, int force)
strbuf_release(&newsection); strbuf_release(&newsection);
} }
static int opt_parse_with_commit(const struct option *opt, const char *arg, int unset)
{
unsigned char sha1[20];
struct commit *commit;
if (!arg)
return -1;
if (get_sha1(arg, sha1))
die("malformed object name %s", arg);
commit = lookup_commit_reference(sha1);
if (!commit)
die("no such commit %s", arg);
commit_list_insert(commit, opt->value);
return 0;
}
static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset) static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
{ {
merge_filter = ((opt->long_name[0] == 'n') merge_filter = ((opt->long_name[0] == 'n')
@ -517,13 +487,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPTION_CALLBACK, 0, "contains", &with_commit, "commit", OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
"print only branches that contain the commit", "print only branches that contain the commit",
PARSE_OPT_LASTARG_DEFAULT, PARSE_OPT_LASTARG_DEFAULT,
opt_parse_with_commit, (intptr_t)"HEAD", parse_opt_with_commit, (intptr_t)"HEAD",
}, },
{ {
OPTION_CALLBACK, 0, "with", &with_commit, "commit", OPTION_CALLBACK, 0, "with", &with_commit, "commit",
"print only branches that contain the commit", "print only branches that contain the commit",
PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT, PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
opt_parse_with_commit, (intptr_t) "HEAD", parse_opt_with_commit, (intptr_t) "HEAD",
}, },
OPT__ABBREV(&abbrev), OPT__ABBREV(&abbrev),

View File

@ -26,6 +26,7 @@ static char signingkey[1000];
struct tag_filter { struct tag_filter {
const char *pattern; const char *pattern;
int lines; int lines;
struct commit_list *with_commit;
}; };
#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" #define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
@ -42,6 +43,16 @@ static int show_reference(const char *refname, const unsigned char *sha1,
char *buf, *sp, *eol; char *buf, *sp, *eol;
size_t len; size_t len;
if (filter->with_commit) {
struct commit *commit;
commit = lookup_commit_reference_gently(sha1, 1);
if (!commit)
return 0;
if (!is_descendant_of(commit, filter->with_commit))
return 0;
}
if (!filter->lines) { if (!filter->lines) {
printf("%s\n", refname); printf("%s\n", refname);
return 0; return 0;
@ -79,7 +90,8 @@ static int show_reference(const char *refname, const unsigned char *sha1,
return 0; return 0;
} }
static int list_tags(const char *pattern, int lines) static int list_tags(const char *pattern, int lines,
struct commit_list *with_commit)
{ {
struct tag_filter filter; struct tag_filter filter;
@ -88,6 +100,7 @@ static int list_tags(const char *pattern, int lines)
filter.pattern = pattern; filter.pattern = pattern;
filter.lines = lines; filter.lines = lines;
filter.with_commit = with_commit;
for_each_tag_ref(show_reference, (void *) &filter); for_each_tag_ref(show_reference, (void *) &filter);
@ -360,6 +373,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
list = 0, delete = 0, verify = 0; list = 0, delete = 0, verify = 0;
const char *msgfile = NULL, *keyid = NULL; const char *msgfile = NULL, *keyid = NULL;
struct msg_arg msg = { 0, STRBUF_INIT }; struct msg_arg msg = { 0, STRBUF_INIT };
struct commit_list *with_commit = NULL;
struct option options[] = { struct option options[] = {
OPT_BOOLEAN('l', NULL, &list, "list tag names"), OPT_BOOLEAN('l', NULL, &list, "list tag names"),
{ OPTION_INTEGER, 'n', NULL, &lines, NULL, { OPTION_INTEGER, 'n', NULL, &lines, NULL,
@ -378,6 +392,14 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
OPT_STRING('u', NULL, &keyid, "key-id", OPT_STRING('u', NULL, &keyid, "key-id",
"use another key to sign the tag"), "use another key to sign the tag"),
OPT_BOOLEAN('f', NULL, &force, "replace the tag if exists"), OPT_BOOLEAN('f', NULL, &force, "replace the tag if exists"),
OPT_GROUP("Tag listing options"),
{
OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
"print only tags that contain the commit",
PARSE_OPT_LASTARG_DEFAULT,
parse_opt_with_commit, (intptr_t)"HEAD",
},
OPT_END() OPT_END()
}; };
@ -402,9 +424,12 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (list + delete + verify > 1) if (list + delete + verify > 1)
usage_with_options(git_tag_usage, options); usage_with_options(git_tag_usage, options);
if (list) if (list)
return list_tags(argv[0], lines == -1 ? 0 : lines); return list_tags(argv[0], lines == -1 ? 0 : lines,
with_commit);
if (lines != -1) if (lines != -1)
die("-n option is only allowed with -l."); die("-n option is only allowed with -l.");
if (with_commit)
die("--contains option is only allowed with -l.");
if (delete) if (delete)
return for_each_tag_name(argv, delete_tag); return for_each_tag_name(argv, delete_tag);
if (verify) if (verify)

View File

@ -705,6 +705,21 @@ struct commit_list *get_merge_bases(struct commit *one, struct commit *two,
return get_merge_bases_many(one, 1, &two, cleanup); return get_merge_bases_many(one, 1, &two, cleanup);
} }
int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
{
if (!with_commit)
return 1;
while (with_commit) {
struct commit *other;
other = with_commit->item;
with_commit = with_commit->next;
if (in_merge_bases(other, &commit, 1))
return 1;
}
return 0;
}
int in_merge_bases(struct commit *commit, struct commit **reference, int num) int in_merge_bases(struct commit *commit, struct commit **reference, int num)
{ {
struct commit_list *bases, *b; struct commit_list *bases, *b;

View File

@ -133,6 +133,7 @@ extern int is_repository_shallow(void);
extern struct commit_list *get_shallow_commits(struct object_array *heads, extern struct commit_list *get_shallow_commits(struct object_array *heads,
int depth, int shallow_flag, int not_shallow_flag); int depth, int shallow_flag, int not_shallow_flag);
int is_descendant_of(struct commit *, struct commit_list *);
int in_merge_bases(struct commit *, struct commit **, int); int in_merge_bases(struct commit *, struct commit **, int);
extern int interactive_add(int argc, const char **argv, const char *prefix); extern int interactive_add(int argc, const char **argv, const char *prefix);

View File

@ -1,6 +1,7 @@
#include "git-compat-util.h" #include "git-compat-util.h"
#include "parse-options.h" #include "parse-options.h"
#include "cache.h" #include "cache.h"
#include "commit.h"
#define OPT_SHORT 1 #define OPT_SHORT 1
#define OPT_UNSET 2 #define OPT_UNSET 2
@ -506,6 +507,22 @@ int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
return 0; return 0;
} }
int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
{
unsigned char sha1[20];
struct commit *commit;
if (!arg)
return -1;
if (get_sha1(arg, sha1))
return error("malformed object name %s", arg);
commit = lookup_commit_reference(sha1);
if (!commit)
return error("no such commit %s", arg);
commit_list_insert(commit, opt->value);
return 0;
}
/* /*
* This should really be OPTION_FILENAME type as a part of * This should really be OPTION_FILENAME type as a part of
* parse_options that take prefix to do this while parsing. * parse_options that take prefix to do this while parsing.

View File

@ -151,6 +151,7 @@ extern int parse_options_end(struct parse_opt_ctx_t *ctx);
extern int parse_opt_abbrev_cb(const struct option *, const char *, int); extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
extern int parse_opt_approxidate_cb(const struct option *, const char *, int); extern int parse_opt_approxidate_cb(const struct option *, const char *, int);
extern int parse_opt_verbosity_cb(const struct option *, const char *, int); extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
extern int parse_opt_with_commit(const struct option *, const char *, int);
#define OPT__VERBOSE(var) OPT_BOOLEAN('v', "verbose", (var), "be verbose") #define OPT__VERBOSE(var) OPT_BOOLEAN('v', "verbose", (var), "be verbose")
#define OPT__QUIET(var) OPT_BOOLEAN('q', "quiet", (var), "be quiet") #define OPT__QUIET(var) OPT_BOOLEAN('q', "quiet", (var), "be quiet")

View File

@ -1090,6 +1090,121 @@ test_expect_success 'filename for the message is relative to cwd' '
git cat-file tag tag-from-subdir-2 | grep "in sub directory" git cat-file tag tag-from-subdir-2 | grep "in sub directory"
' '
# create a few more commits to test --contains
hash1=$(git rev-parse HEAD)
test_expect_success 'creating second commit and tag' '
echo foo-2.0 >foo &&
git add foo &&
git commit -m second
git tag v2.0
'
hash2=$(git rev-parse HEAD)
test_expect_success 'creating third commit without tag' '
echo foo-dev >foo &&
git add foo &&
git commit -m third
'
hash3=$(git rev-parse HEAD)
# simple linear checks of --continue
cat > expected <<EOF
v0.2.1
v1.0
v1.0.1
v1.1.3
v2.0
EOF
test_expect_success 'checking that first commit is in all tags (hash)' "
git tag -l --contains $hash1 v* >actual
test_cmp expected actual
"
# other ways of specifying the commit
test_expect_success 'checking that first commit is in all tags (tag)' "
git tag -l --contains v1.0 v* >actual
test_cmp expected actual
"
test_expect_success 'checking that first commit is in all tags (relative)' "
git tag -l --contains HEAD~2 v* >actual
test_cmp expected actual
"
cat > expected <<EOF
v2.0
EOF
test_expect_success 'checking that second commit only has one tag' "
git tag -l --contains $hash2 v* >actual
test_cmp expected actual
"
cat > expected <<EOF
EOF
test_expect_success 'checking that third commit has no tags' "
git tag -l --contains $hash3 v* >actual
test_cmp expected actual
"
# how about a simple merge?
test_expect_success 'creating simple branch' '
git branch stable v2.0 &&
git checkout stable &&
echo foo-3.0 > foo &&
git commit foo -m fourth
git tag v3.0
'
hash4=$(git rev-parse HEAD)
cat > expected <<EOF
v3.0
EOF
test_expect_success 'checking that branch head only has one tag' "
git tag -l --contains $hash4 v* >actual
test_cmp expected actual
"
test_expect_success 'merging original branch into this branch' '
git merge --strategy=ours master &&
git tag v4.0
'
cat > expected <<EOF
v4.0
EOF
test_expect_success 'checking that original branch head has one tag now' "
git tag -l --contains $hash3 v* >actual
test_cmp expected actual
"
cat > expected <<EOF
v0.2.1
v1.0
v1.0.1
v1.1.3
v2.0
v3.0
v4.0
EOF
test_expect_success 'checking that initial commit is in all tags' "
git tag -l --contains $hash1 v* >actual
test_cmp expected actual
"
# mixing modes and options: # mixing modes and options:
test_expect_success 'mixing incompatibles modes and options is forbidden' ' test_expect_success 'mixing incompatibles modes and options is forbidden' '