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:
commit
3e52effcf6
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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),
|
||||||
|
@ -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
99
refs.c
@ -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
1
refs.h
@ -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);
|
||||||
|
@ -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)"
|
||||||
'
|
'
|
||||||
|
Loading…
Reference in New Issue
Block a user