зеркало из https://github.com/microsoft/git.git
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:
Коммит
3e52effcf6
|
@ -100,7 +100,9 @@ OPTIONS
|
|||
|
||||
-v::
|
||||
--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>::
|
||||
Alter the sha1's minimum display length in the output listing.
|
||||
|
|
|
@ -85,6 +85,11 @@ objectsize::
|
|||
objectname::
|
||||
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
|
||||
field names (`tree`, `parent`, `object`, `type`, and `tag`) can
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
strbuf_addch(stat, '[');
|
||||
if (show_upstream_ref)
|
||||
strbuf_addf(stat, "%s: ",
|
||||
shorten_unambiguous_ref(branch->merge[0]->dst));
|
||||
if (!ours)
|
||||
strbuf_addf(stat, "[behind %d] ", theirs);
|
||||
strbuf_addf(stat, "behind %d] ", theirs);
|
||||
else if (!theirs)
|
||||
strbuf_addf(stat, "[ahead %d] ", ours);
|
||||
strbuf_addf(stat, "ahead %d] ", ours);
|
||||
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)
|
||||
|
@ -379,7 +390,7 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
|
|||
}
|
||||
|
||||
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",
|
||||
find_unique_abbrev(item->commit->object.sha1, abbrev),
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "blob.h"
|
||||
#include "quote.h"
|
||||
#include "parse-options.h"
|
||||
#include "remote.h"
|
||||
|
||||
/* Quoting styles */
|
||||
#define QUOTE_NONE 0
|
||||
|
@ -66,6 +67,7 @@ static struct {
|
|||
{ "subject" },
|
||||
{ "body" },
|
||||
{ "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.
|
||||
*/
|
||||
|
@ -672,32 +571,49 @@ static void populate_value(struct refinfo *ref)
|
|||
const char *name = used_atom[i];
|
||||
struct atom_value *v = &ref->value[i];
|
||||
int deref = 0;
|
||||
const char *refname;
|
||||
const char *formatp;
|
||||
|
||||
if (*name == '*') {
|
||||
deref = 1;
|
||||
name++;
|
||||
}
|
||||
if (!prefixcmp(name, "refname")) {
|
||||
const char *formatp = strchr(name, ':');
|
||||
const char *refname = ref->refname;
|
||||
|
||||
/* look for "short" refname format */
|
||||
if (formatp) {
|
||||
formatp++;
|
||||
if (!strcmp(formatp, "short"))
|
||||
refname = get_short_ref(ref);
|
||||
else
|
||||
die("unknown refname format %s",
|
||||
formatp);
|
||||
}
|
||||
if (!prefixcmp(name, "refname"))
|
||||
refname = ref->refname;
|
||||
else if(!prefixcmp(name, "upstream")) {
|
||||
struct branch *branch;
|
||||
/* only local branches may have an upstream */
|
||||
if (prefixcmp(ref->refname, "refs/heads/"))
|
||||
continue;
|
||||
branch = branch_get(ref->refname + 11);
|
||||
|
||||
if (!deref)
|
||||
v->s = refname;
|
||||
else {
|
||||
int len = strlen(refname);
|
||||
char *s = xmalloc(len + 4);
|
||||
sprintf(s, "%s^{}", refname);
|
||||
v->s = s;
|
||||
}
|
||||
if (!branch || !branch->merge || !branch->merge[0] ||
|
||||
!branch->merge[0]->dst)
|
||||
continue;
|
||||
refname = branch->merge[0]->dst;
|
||||
}
|
||||
else
|
||||
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 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 const char *prettify_ref(const struct ref *ref);
|
||||
extern char *shorten_unambiguous_ref(const char *ref);
|
||||
|
||||
/** rename ref, return 0 on success **/
|
||||
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
|
||||
'
|
||||
|
||||
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() {
|
||||
case "$1" in
|
||||
head) ref=refs/heads/master ;;
|
||||
|
@ -39,6 +46,7 @@ test_atom() {
|
|||
}
|
||||
|
||||
test_atom head refname refs/heads/master
|
||||
test_atom head upstream refs/remotes/origin/master
|
||||
test_atom head objecttype commit
|
||||
test_atom head objectsize 171
|
||||
test_atom head objectname 67a36f10722846e891fbada1ba48ed035de75581
|
||||
|
@ -68,6 +76,7 @@ test_atom head contents 'Initial
|
|||
'
|
||||
|
||||
test_atom tag refname refs/tags/testtag
|
||||
test_atom tag upstream ''
|
||||
test_atom tag objecttype tag
|
||||
test_atom tag objectsize 154
|
||||
test_atom tag objectname 98b46b1d36e5b07909de1b3886224e3e81e87322
|
||||
|
@ -203,6 +212,7 @@ test_expect_success 'Check format "rfc2822" date fields output' '
|
|||
|
||||
cat >expected <<\EOF
|
||||
refs/heads/master
|
||||
refs/remotes/origin/master
|
||||
refs/tags/testtag
|
||||
EOF
|
||||
|
||||
|
@ -214,6 +224,7 @@ test_expect_success 'Verify ascending sort' '
|
|||
|
||||
cat >expected <<\EOF
|
||||
refs/tags/testtag
|
||||
refs/remotes/origin/master
|
||||
refs/heads/master
|
||||
EOF
|
||||
|
||||
|
@ -224,6 +235,7 @@ test_expect_success 'Verify descending sort' '
|
|||
|
||||
cat >expected <<\EOF
|
||||
'refs/heads/master'
|
||||
'refs/remotes/origin/master'
|
||||
'refs/tags/testtag'
|
||||
EOF
|
||||
|
||||
|
@ -244,6 +256,7 @@ test_expect_success 'Quoting style: python' '
|
|||
|
||||
cat >expected <<\EOF
|
||||
"refs/heads/master"
|
||||
"refs/remotes/origin/master"
|
||||
"refs/tags/testtag"
|
||||
EOF
|
||||
|
||||
|
@ -273,6 +286,15 @@ test_expect_success 'Check short refname format' '
|
|||
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_must_fail git for-each-ref --format="%(refname:INVALID)"
|
||||
'
|
||||
|
|
Загрузка…
Ссылка в новой задаче