Merge branch 'jk/show-upstream'

* jk/show-upstream:
  branch: show upstream branch when double verbose
  make get_short_ref a public function
  for-each-ref: add "upstream" format field
  for-each-ref: refactor refname handling
  for-each-ref: refactor get_short_ref function
This commit is contained in:
Junio C Hamano 2009-04-12 16:46:42 -07:00
commit 3e52effcf6
7 changed files with 186 additions and 130 deletions

View File

@ -100,7 +100,9 @@ OPTIONS
-v:: -v::
--verbose:: --verbose::
Show sha1 and commit subject line for each head. Show sha1 and commit subject line for each head, along with
relationship to upstream branch (if any). If given twice, print
the name of the upstream branch, as well.
--abbrev=<length>:: --abbrev=<length>::
Alter the sha1's minimum display length in the output listing. Alter the sha1's minimum display length in the output listing.

View File

@ -85,6 +85,11 @@ objectsize::
objectname:: objectname::
The object name (aka SHA-1). The object name (aka SHA-1).
upstream::
The name of a local ref which can be considered ``upstream''
from the displayed ref. Respects `:short` in the same way as
`refname` above.
In addition to the above, for commit and tag objects, the header In addition to the above, for commit and tag objects, the header
field names (`tree`, `parent`, `object`, `type`, and `tag`) can field names (`tree`, `parent`, `object`, `type`, and `tag`) can
be used to specify the value in the header field. be used to specify the value in the header field.

View File

@ -301,19 +301,30 @@ static int ref_cmp(const void *r1, const void *r2)
return strcmp(c1->name, c2->name); return strcmp(c1->name, c2->name);
} }
static void fill_tracking_info(struct strbuf *stat, const char *branch_name) static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
int show_upstream_ref)
{ {
int ours, theirs; int ours, theirs;
struct branch *branch = branch_get(branch_name); struct branch *branch = branch_get(branch_name);
if (!stat_tracking_info(branch, &ours, &theirs) || (!ours && !theirs)) if (!stat_tracking_info(branch, &ours, &theirs)) {
if (branch && branch->merge && branch->merge[0]->dst &&
show_upstream_ref)
strbuf_addf(stat, "[%s] ",
shorten_unambiguous_ref(branch->merge[0]->dst));
return; return;
}
strbuf_addch(stat, '[');
if (show_upstream_ref)
strbuf_addf(stat, "%s: ",
shorten_unambiguous_ref(branch->merge[0]->dst));
if (!ours) if (!ours)
strbuf_addf(stat, "[behind %d] ", theirs); strbuf_addf(stat, "behind %d] ", theirs);
else if (!theirs) else if (!theirs)
strbuf_addf(stat, "[ahead %d] ", ours); strbuf_addf(stat, "ahead %d] ", ours);
else else
strbuf_addf(stat, "[ahead %d, behind %d] ", ours, theirs); strbuf_addf(stat, "ahead %d, behind %d] ", ours, theirs);
} }
static int matches_merge_filter(struct commit *commit) static int matches_merge_filter(struct commit *commit)
@ -379,7 +390,7 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
} }
if (item->kind == REF_LOCAL_BRANCH) if (item->kind == REF_LOCAL_BRANCH)
fill_tracking_info(&stat, item->name); fill_tracking_info(&stat, item->name, verbose > 1);
strbuf_addf(&out, " %s %s%s", strbuf_addf(&out, " %s %s%s",
find_unique_abbrev(item->commit->object.sha1, abbrev), find_unique_abbrev(item->commit->object.sha1, abbrev),

View File

@ -8,6 +8,7 @@
#include "blob.h" #include "blob.h"
#include "quote.h" #include "quote.h"
#include "parse-options.h" #include "parse-options.h"
#include "remote.h"
/* Quoting styles */ /* Quoting styles */
#define QUOTE_NONE 0 #define QUOTE_NONE 0
@ -66,6 +67,7 @@ static struct {
{ "subject" }, { "subject" },
{ "body" }, { "body" },
{ "contents" }, { "contents" },
{ "upstream" },
}; };
/* /*
@ -543,109 +545,6 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, v
} }
} }
/*
* generate a format suitable for scanf from a ref_rev_parse_rules
* rule, that is replace the "%.*s" spec with a "%s" spec
*/
static void gen_scanf_fmt(char *scanf_fmt, const char *rule)
{
char *spec;
spec = strstr(rule, "%.*s");
if (!spec || strstr(spec + 4, "%.*s"))
die("invalid rule in ref_rev_parse_rules: %s", rule);
/* copy all until spec */
strncpy(scanf_fmt, rule, spec - rule);
scanf_fmt[spec - rule] = '\0';
/* copy new spec */
strcat(scanf_fmt, "%s");
/* copy remaining rule */
strcat(scanf_fmt, spec + 4);
return;
}
/*
* Shorten the refname to an non-ambiguous form
*/
static char *get_short_ref(struct refinfo *ref)
{
int i;
static char **scanf_fmts;
static int nr_rules;
char *short_name;
/* pre generate scanf formats from ref_rev_parse_rules[] */
if (!nr_rules) {
size_t total_len = 0;
/* the rule list is NULL terminated, count them first */
for (; ref_rev_parse_rules[nr_rules]; nr_rules++)
/* no +1 because strlen("%s") < strlen("%.*s") */
total_len += strlen(ref_rev_parse_rules[nr_rules]);
scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len);
total_len = 0;
for (i = 0; i < nr_rules; i++) {
scanf_fmts[i] = (char *)&scanf_fmts[nr_rules]
+ total_len;
gen_scanf_fmt(scanf_fmts[i], ref_rev_parse_rules[i]);
total_len += strlen(ref_rev_parse_rules[i]);
}
}
/* bail out if there are no rules */
if (!nr_rules)
return ref->refname;
/* buffer for scanf result, at most ref->refname must fit */
short_name = xstrdup(ref->refname);
/* skip first rule, it will always match */
for (i = nr_rules - 1; i > 0 ; --i) {
int j;
int short_name_len;
if (1 != sscanf(ref->refname, scanf_fmts[i], short_name))
continue;
short_name_len = strlen(short_name);
/*
* check if the short name resolves to a valid ref,
* but use only rules prior to the matched one
*/
for (j = 0; j < i; j++) {
const char *rule = ref_rev_parse_rules[j];
unsigned char short_objectname[20];
char refname[PATH_MAX];
/*
* the short name is ambiguous, if it resolves
* (with this previous rule) to a valid ref
* read_ref() returns 0 on success
*/
mksnpath(refname, sizeof(refname),
rule, short_name_len, short_name);
if (!read_ref(refname, short_objectname))
break;
}
/*
* short name is non-ambiguous if all previous rules
* haven't resolved to a valid ref
*/
if (j == i)
return short_name;
}
free(short_name);
return ref->refname;
}
/* /*
* Parse the object referred by ref, and grab needed value. * Parse the object referred by ref, and grab needed value.
*/ */
@ -672,32 +571,49 @@ static void populate_value(struct refinfo *ref)
const char *name = used_atom[i]; const char *name = used_atom[i];
struct atom_value *v = &ref->value[i]; struct atom_value *v = &ref->value[i];
int deref = 0; int deref = 0;
const char *refname;
const char *formatp;
if (*name == '*') { if (*name == '*') {
deref = 1; deref = 1;
name++; name++;
} }
if (!prefixcmp(name, "refname")) {
const char *formatp = strchr(name, ':');
const char *refname = ref->refname;
/* look for "short" refname format */ if (!prefixcmp(name, "refname"))
if (formatp) { refname = ref->refname;
formatp++; else if(!prefixcmp(name, "upstream")) {
if (!strcmp(formatp, "short")) struct branch *branch;
refname = get_short_ref(ref); /* only local branches may have an upstream */
else if (prefixcmp(ref->refname, "refs/heads/"))
die("unknown refname format %s", continue;
formatp); branch = branch_get(ref->refname + 11);
}
if (!deref) if (!branch || !branch->merge || !branch->merge[0] ||
v->s = refname; !branch->merge[0]->dst)
else { continue;
int len = strlen(refname); refname = branch->merge[0]->dst;
char *s = xmalloc(len + 4); }
sprintf(s, "%s^{}", refname); else
v->s = s; continue;
}
formatp = strchr(name, ':');
/* look for "short" refname format */
if (formatp) {
formatp++;
if (!strcmp(formatp, "short"))
refname = shorten_unambiguous_ref(refname);
else
die("unknown %.*s format %s",
(int)(formatp - name), name, formatp);
}
if (!deref)
v->s = refname;
else {
int len = strlen(refname);
char *s = xmalloc(len + 4);
sprintf(s, "%s^{}", refname);
v->s = s;
} }
} }

99
refs.c
View File

@ -1657,3 +1657,102 @@ struct ref *find_ref_by_name(const struct ref *list, const char *name)
return (struct ref *)list; return (struct ref *)list;
return NULL; return NULL;
} }
/*
* generate a format suitable for scanf from a ref_rev_parse_rules
* rule, that is replace the "%.*s" spec with a "%s" spec
*/
static void gen_scanf_fmt(char *scanf_fmt, const char *rule)
{
char *spec;
spec = strstr(rule, "%.*s");
if (!spec || strstr(spec + 4, "%.*s"))
die("invalid rule in ref_rev_parse_rules: %s", rule);
/* copy all until spec */
strncpy(scanf_fmt, rule, spec - rule);
scanf_fmt[spec - rule] = '\0';
/* copy new spec */
strcat(scanf_fmt, "%s");
/* copy remaining rule */
strcat(scanf_fmt, spec + 4);
return;
}
char *shorten_unambiguous_ref(const char *ref)
{
int i;
static char **scanf_fmts;
static int nr_rules;
char *short_name;
/* pre generate scanf formats from ref_rev_parse_rules[] */
if (!nr_rules) {
size_t total_len = 0;
/* the rule list is NULL terminated, count them first */
for (; ref_rev_parse_rules[nr_rules]; nr_rules++)
/* no +1 because strlen("%s") < strlen("%.*s") */
total_len += strlen(ref_rev_parse_rules[nr_rules]);
scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len);
total_len = 0;
for (i = 0; i < nr_rules; i++) {
scanf_fmts[i] = (char *)&scanf_fmts[nr_rules]
+ total_len;
gen_scanf_fmt(scanf_fmts[i], ref_rev_parse_rules[i]);
total_len += strlen(ref_rev_parse_rules[i]);
}
}
/* bail out if there are no rules */
if (!nr_rules)
return xstrdup(ref);
/* buffer for scanf result, at most ref must fit */
short_name = xstrdup(ref);
/* skip first rule, it will always match */
for (i = nr_rules - 1; i > 0 ; --i) {
int j;
int short_name_len;
if (1 != sscanf(ref, scanf_fmts[i], short_name))
continue;
short_name_len = strlen(short_name);
/*
* check if the short name resolves to a valid ref,
* but use only rules prior to the matched one
*/
for (j = 0; j < i; j++) {
const char *rule = ref_rev_parse_rules[j];
unsigned char short_objectname[20];
char refname[PATH_MAX];
/*
* the short name is ambiguous, if it resolves
* (with this previous rule) to a valid ref
* read_ref() returns 0 on success
*/
mksnpath(refname, sizeof(refname),
rule, short_name_len, short_name);
if (!read_ref(refname, short_objectname))
break;
}
/*
* short name is non-ambiguous if all previous rules
* haven't resolved to a valid ref
*/
if (j == i)
return short_name;
}
free(short_name);
return xstrdup(ref);
}

1
refs.h
View File

@ -81,6 +81,7 @@ extern int for_each_reflog(each_ref_fn, void *);
extern int check_ref_format(const char *target); extern int check_ref_format(const char *target);
extern const char *prettify_ref(const struct ref *ref); extern const char *prettify_ref(const struct ref *ref);
extern char *shorten_unambiguous_ref(const char *ref);
/** rename ref, return 0 on success **/ /** rename ref, return 0 on success **/
extern int rename_ref(const char *oldref, const char *newref, const char *logmsg); extern int rename_ref(const char *oldref, const char *newref, const char *logmsg);

View File

@ -26,6 +26,13 @@ test_expect_success 'Create sample commit with known timestamp' '
git tag -a -m "Tagging at $datestamp" testtag git tag -a -m "Tagging at $datestamp" testtag
' '
test_expect_success 'Create upstream config' '
git update-ref refs/remotes/origin/master master &&
git remote add origin nowhere &&
git config branch.master.remote origin &&
git config branch.master.merge refs/heads/master
'
test_atom() { test_atom() {
case "$1" in case "$1" in
head) ref=refs/heads/master ;; head) ref=refs/heads/master ;;
@ -39,6 +46,7 @@ test_atom() {
} }
test_atom head refname refs/heads/master test_atom head refname refs/heads/master
test_atom head upstream refs/remotes/origin/master
test_atom head objecttype commit test_atom head objecttype commit
test_atom head objectsize 171 test_atom head objectsize 171
test_atom head objectname 67a36f10722846e891fbada1ba48ed035de75581 test_atom head objectname 67a36f10722846e891fbada1ba48ed035de75581
@ -68,6 +76,7 @@ test_atom head contents 'Initial
' '
test_atom tag refname refs/tags/testtag test_atom tag refname refs/tags/testtag
test_atom tag upstream ''
test_atom tag objecttype tag test_atom tag objecttype tag
test_atom tag objectsize 154 test_atom tag objectsize 154
test_atom tag objectname 98b46b1d36e5b07909de1b3886224e3e81e87322 test_atom tag objectname 98b46b1d36e5b07909de1b3886224e3e81e87322
@ -203,6 +212,7 @@ test_expect_success 'Check format "rfc2822" date fields output' '
cat >expected <<\EOF cat >expected <<\EOF
refs/heads/master refs/heads/master
refs/remotes/origin/master
refs/tags/testtag refs/tags/testtag
EOF EOF
@ -214,6 +224,7 @@ test_expect_success 'Verify ascending sort' '
cat >expected <<\EOF cat >expected <<\EOF
refs/tags/testtag refs/tags/testtag
refs/remotes/origin/master
refs/heads/master refs/heads/master
EOF EOF
@ -224,6 +235,7 @@ test_expect_success 'Verify descending sort' '
cat >expected <<\EOF cat >expected <<\EOF
'refs/heads/master' 'refs/heads/master'
'refs/remotes/origin/master'
'refs/tags/testtag' 'refs/tags/testtag'
EOF EOF
@ -244,6 +256,7 @@ test_expect_success 'Quoting style: python' '
cat >expected <<\EOF cat >expected <<\EOF
"refs/heads/master" "refs/heads/master"
"refs/remotes/origin/master"
"refs/tags/testtag" "refs/tags/testtag"
EOF EOF
@ -273,6 +286,15 @@ test_expect_success 'Check short refname format' '
test_cmp expected actual test_cmp expected actual
' '
cat >expected <<EOF
origin/master
EOF
test_expect_success 'Check short upstream format' '
git for-each-ref --format="%(upstream:short)" refs/heads >actual &&
test_cmp expected actual
'
test_expect_success 'Check for invalid refname format' ' test_expect_success 'Check for invalid refname format' '
test_must_fail git for-each-ref --format="%(refname:INVALID)" test_must_fail git for-each-ref --format="%(refname:INVALID)"
' '