@ -14,14 +14,50 @@
@@ -14,14 +14,50 @@
#include "git-compat-util.h"
#include "version.h"
#include "trailer.h"
#include "wt-status.h"
static struct ref_msg {
const char *gone;
const char *ahead;
const char *behind;
const char *ahead_behind;
} msgs = {
/* Untranslated plumbing messages: */
"gone",
"ahead %d",
"behind %d",
"ahead %d, behind %d"
};
void setup_ref_filter_porcelain_msg(void)
{
msgs.gone = _("gone");
msgs.ahead = _("ahead %d");
msgs.behind = _("behind %d");
msgs.ahead_behind = _("ahead %d, behind %d");
}
typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
typedef enum { COMPARE_EQUAL, COMPARE_UNEQUAL, COMPARE_NONE } cmp_status;
struct align {
align_type position;
unsigned int width;
};
struct if_then_else {
cmp_status cmp_status;
const char *str;
unsigned int then_atom_seen : 1,
else_atom_seen : 1,
condition_satisfied : 1;
};
struct refname_atom {
enum { R_NORMAL, R_SHORT, R_LSTRIP, R_RSTRIP } option;
int lstrip, rstrip;
};
/*
* An atom is a valid field atom listed below, possibly prefixed with
* a "*" to denote deref_tag().
@ -38,13 +74,24 @@ static struct used_atom {
@@ -38,13 +74,24 @@ static struct used_atom {
union {
char color[COLOR_MAXLEN];
struct align align;
enum { RR_NORMAL, RR_SHORTEN, RR_TRACK, RR_TRACKSHORT }
remote_ref;
struct {
enum { RR_REF, RR_TRACK, RR_TRACKSHORT } option;
struct refname_atom refname;
unsigned int nobracket : 1;
} remote_ref;
struct {
enum { C_BARE, C_BODY, C_BODY_DEP, C_LINES, C_SIG, C_SUB, C_TRAILERS } option;
unsigned int nlines;
} contents;
enum { O_FULL, O_SHORT } objectname;
struct {
cmp_status cmp_status;
const char *str;
} if_then_else;
struct {
enum { O_FULL, O_LENGTH, O_SHORT } option;
unsigned int length;
} objectname;
struct refname_atom refname;
} u;
} *used_atom;
static int used_atom_cnt, need_tagged, need_symref;
@ -58,18 +105,58 @@ static void color_atom_parser(struct used_atom *atom, const char *color_value)
@@ -58,18 +105,58 @@ static void color_atom_parser(struct used_atom *atom, const char *color_value)
die(_("unrecognized color: %%(color:%s)"), color_value);
}
static void remote_ref_atom_parser(struct used_atom *atom, const char *arg)
static void refname_atom_parser_internal(struct refname_atom *atom,
const char *arg, const char *name)
{
if (!arg)
atom->u.remote_ref = RR_NORMAL;
atom->option = R_NORMAL;
else if (!strcmp(arg, "short"))
atom->u.remote_ref = RR_SHORTEN;
else if (!strcmp(arg, "track"))
atom->u.remote_ref = RR_TRACK;
else if (!strcmp(arg, "trackshort"))
atom->u.remote_ref = RR_TRACKSHORT;
else
die(_("unrecognized format: %%(%s)"), atom->name);
atom->option = R_SHORT;
else if (skip_prefix(arg, "lstrip=", &arg) ||
skip_prefix(arg, "strip=", &arg)) {
atom->option = R_LSTRIP;
if (strtol_i(arg, 10, &atom->lstrip))
die(_("Integer value expected refname:lstrip=%s"), arg);
} else if (skip_prefix(arg, "rstrip=", &arg)) {
atom->option = R_RSTRIP;
if (strtol_i(arg, 10, &atom->rstrip))
die(_("Integer value expected refname:rstrip=%s"), arg);
} else
die(_("unrecognized %%(%s) argument: %s"), name, arg);
}
static void remote_ref_atom_parser(struct used_atom *atom, const char *arg)
{
struct string_list params = STRING_LIST_INIT_DUP;
int i;
if (!arg) {
atom->u.remote_ref.option = RR_REF;
refname_atom_parser_internal(&atom->u.remote_ref.refname,
arg, atom->name);
return;
}
atom->u.remote_ref.nobracket = 0;
string_list_split(¶ms, arg, ',', -1);
for (i = 0; i < params.nr; i++) {
const char *s = params.items[i].string;
if (!strcmp(s, "track"))
atom->u.remote_ref.option = RR_TRACK;
else if (!strcmp(s, "trackshort"))
atom->u.remote_ref.option = RR_TRACKSHORT;
else if (!strcmp(s, "nobracket"))
atom->u.remote_ref.nobracket = 1;
else {
atom->u.remote_ref.option = RR_REF;
refname_atom_parser_internal(&atom->u.remote_ref.refname,
arg, atom->name);
}
}
string_list_clear(¶ms, 0);
}
static void body_atom_parser(struct used_atom *atom, const char *arg)
@ -116,13 +203,25 @@ static void contents_atom_parser(struct used_atom *atom, const char *arg)
@@ -116,13 +203,25 @@ static void contents_atom_parser(struct used_atom *atom, const char *arg)
static void objectname_atom_parser(struct used_atom *atom, const char *arg)
{
if (!arg)
atom->u.objectname = O_FULL;
atom->u.objectname.option = O_FULL;
else if (!strcmp(arg, "short"))
atom->u.objectname = O_SHORT;
else
atom->u.objectname.option = O_SHORT;
else if (skip_prefix(arg, "short=", &arg)) {
atom->u.objectname.option = O_LENGTH;
if (strtoul_ui(arg, 10, &atom->u.objectname.length) ||
atom->u.objectname.length == 0)
die(_("positive value expected objectname:short=%s"), arg);
if (atom->u.objectname.length < MINIMUM_ABBREV)
atom->u.objectname.length = MINIMUM_ABBREV;
} else
die(_("unrecognized %%(objectname) argument: %s"), arg);
}
static void refname_atom_parser(struct used_atom *atom, const char *arg)
{
return refname_atom_parser_internal(&atom->u.refname, arg, atom->name);
}
static align_type parse_align_position(const char *s)
{
if (!strcmp(s, "right"))
@ -173,12 +272,27 @@ static void align_atom_parser(struct used_atom *atom, const char *arg)
@@ -173,12 +272,27 @@ static void align_atom_parser(struct used_atom *atom, const char *arg)
string_list_clear(¶ms, 0);
}
static void if_atom_parser(struct used_atom *atom, const char *arg)
{
if (!arg) {
atom->u.if_then_else.cmp_status = COMPARE_NONE;
return;
} else if (skip_prefix(arg, "equals=", &atom->u.if_then_else.str)) {
atom->u.if_then_else.cmp_status = COMPARE_EQUAL;
} else if (skip_prefix(arg, "notequals=", &atom->u.if_then_else.str)) {
atom->u.if_then_else.cmp_status = COMPARE_UNEQUAL;
} else {
die(_("unrecognized %%(if) argument: %s"), arg);
}
}
static struct {
const char *name;
cmp_type cmp_type;
void (*parser)(struct used_atom *atom, const char *arg);
} valid_atom[] = {
{ "refname" },
{ "refname" , FIELD_STR, refname_atom_parser },
{ "objecttype" },
{ "objectsize", FIELD_ULONG },
{ "objectname", FIELD_STR, objectname_atom_parser },
@ -208,12 +322,15 @@ static struct {
@@ -208,12 +322,15 @@ static struct {
{ "contents", FIELD_STR, contents_atom_parser },
{ "upstream", FIELD_STR, remote_ref_atom_parser },
{ "push", FIELD_STR, remote_ref_atom_parser },
{ "symref" },
{ "symref", FIELD_STR, refname_atom_parser },
{ "flag" },
{ "HEAD" },
{ "color", FIELD_STR, color_atom_parser },
{ "align", FIELD_STR, align_atom_parser },
{ "end" },
{ "if", FIELD_STR, if_atom_parser },
{ "then" },
{ "else" },
};
#define REF_FORMATTING_STATE_INIT { 0, NULL }
@ -221,7 +338,7 @@ static struct {
@@ -221,7 +338,7 @@ static struct {
struct ref_formatting_stack {
struct ref_formatting_stack *prev;
struct strbuf output;
void (*at_end)(struct ref_formatting_stack *stack);
void (*at_end)(struct ref_formatting_stack **stack);
void *at_end_data;
};
@ -232,11 +349,9 @@ struct ref_formatting_state {
@@ -232,11 +349,9 @@ struct ref_formatting_state {
struct atom_value {
const char *s;
union {
struct align align;
} u;
void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);
unsigned long ul; /* used for sorting when not FIELD_STR */
struct used_atom *atom;
};
/*
@ -293,7 +408,7 @@ int parse_ref_filter_atom(const char *atom, const char *ep)
@@ -293,7 +408,7 @@ int parse_ref_filter_atom(const char *atom, const char *ep)
valid_atom[i].parser(&used_atom[at], arg);
if (*atom == '*')
need_tagged = 1;
if (!strcmp(used_atom[at].name, "symref"))
if (!strcmp(valid_atom[i].name, "symref"))
need_symref = 1;
return at;
}
@ -354,13 +469,14 @@ static void pop_stack_element(struct ref_formatting_stack **stack)
@@ -354,13 +469,14 @@ static void pop_stack_element(struct ref_formatting_stack **stack)
*stack = prev;
}
static void end_align_handler(struct ref_formatting_stack *stack)
static void end_align_handler(struct ref_formatting_stack **stack)
{
struct align *align = (struct align *)stack->at_end_data;
struct ref_formatting_stack *cur = *stack;
struct align *align = (struct align *)cur->at_end_data;
struct strbuf s = STRBUF_INIT;
strbuf_utf8_align(&s, align->position, align->width, stack->output.buf);
strbuf_swap(&stack->output, &s);
strbuf_utf8_align(&s, align->position, align->width, cur->output.buf);
strbuf_swap(&cur->output, &s);
strbuf_release(&s);
}
@ -371,7 +487,115 @@ static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_s
@@ -371,7 +487,115 @@ static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_s
push_stack_element(&state->stack);
new = state->stack;
new->at_end = end_align_handler;
new->at_end_data = &atomv->u.align;
new->at_end_data = &atomv->atom->u.align;
}
static void if_then_else_handler(struct ref_formatting_stack **stack)
{
struct ref_formatting_stack *cur = *stack;
struct ref_formatting_stack *prev = cur->prev;
struct if_then_else *if_then_else = (struct if_then_else *)cur->at_end_data;
if (!if_then_else->then_atom_seen)
die(_("format: %%(if) atom used without a %%(then) atom"));
if (if_then_else->else_atom_seen) {
/*
* There is an %(else) atom: we need to drop one state from the
* stack, either the %(else) branch if the condition is satisfied, or
* the %(then) branch if it isn't.
*/
if (if_then_else->condition_satisfied) {
strbuf_reset(&cur->output);
pop_stack_element(&cur);
} else {
strbuf_swap(&cur->output, &prev->output);
strbuf_reset(&cur->output);
pop_stack_element(&cur);
}
} else if (!if_then_else->condition_satisfied) {
/*
* No %(else) atom: just drop the %(then) branch if the
* condition is not satisfied.
*/
strbuf_reset(&cur->output);
}
*stack = cur;
free(if_then_else);
}
static void if_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
{
struct ref_formatting_stack *new;
struct if_then_else *if_then_else = xcalloc(sizeof(struct if_then_else), 1);
if_then_else->str = atomv->atom->u.if_then_else.str;
if_then_else->cmp_status = atomv->atom->u.if_then_else.cmp_status;
push_stack_element(&state->stack);
new = state->stack;
new->at_end = if_then_else_handler;
new->at_end_data = if_then_else;
}
static int is_empty(const char *s)
{
while (*s != '\0') {
if (!isspace(*s))
return 0;
s++;
}
return 1;
}
static void then_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
{
struct ref_formatting_stack *cur = state->stack;
struct if_then_else *if_then_else = NULL;
if (cur->at_end == if_then_else_handler)
if_then_else = (struct if_then_else *)cur->at_end_data;
if (!if_then_else)
die(_("format: %%(then) atom used without an %%(if) atom"));
if (if_then_else->then_atom_seen)
die(_("format: %%(then) atom used more than once"));
if (if_then_else->else_atom_seen)
die(_("format: %%(then) atom used after %%(else)"));
if_then_else->then_atom_seen = 1;
/*
* If the 'equals' or 'notequals' attribute is used then
* perform the required comparison. If not, only non-empty
* strings satisfy the 'if' condition.
*/
if (if_then_else->cmp_status == COMPARE_EQUAL) {
if (!strcmp(if_then_else->str, cur->output.buf))
if_then_else->condition_satisfied = 1;
} else if (if_then_else->cmp_status == COMPARE_UNEQUAL) {
if (strcmp(if_then_else->str, cur->output.buf))
if_then_else->condition_satisfied = 1;
} else if (cur->output.len && !is_empty(cur->output.buf))
if_then_else->condition_satisfied = 1;
strbuf_reset(&cur->output);
}
static void else_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
{
struct ref_formatting_stack *prev = state->stack;
struct if_then_else *if_then_else = NULL;
if (prev->at_end == if_then_else_handler)
if_then_else = (struct if_then_else *)prev->at_end_data;
if (!if_then_else)
die(_("format: %%(else) atom used without an %%(if) atom"));
if (!if_then_else->then_atom_seen)
die(_("format: %%(else) atom used without a %%(then) atom"));
if (if_then_else->else_atom_seen)
die(_("format: %%(else) atom used more than once"));
if_then_else->else_atom_seen = 1;
push_stack_element(&state->stack);
state->stack->at_end_data = prev->at_end_data;
state->stack->at_end = prev->at_end;
}
static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
@ -381,14 +605,17 @@ static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_sta
@@ -381,14 +605,17 @@ static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_sta
if (!current->at_end)
die(_("format: %%(end) atom used without corresponding atom"));
current->at_end(current);
current->at_end(&state->stack);
/* Stack may have been popped within at_end(), hence reset the current pointer */
current = state->stack;
/*
* Perform quote formatting when the stack element is that of
* a supporting atom. If nested then perform quote formatting
* only on the topmost supporting atom.
*/
if (!state->stack->prev->prev) {
if (!current->prev->prev) {
quote_formatting(&s, current->output.buf, state->quote_style);
strbuf_swap(¤t->output, &s);
}
@ -465,12 +692,15 @@ static int grab_objectname(const char *name, const unsigned char *sha1,
@@ -465,12 +692,15 @@ static int grab_objectname(const char *name, const unsigned char *sha1,
struct atom_value *v, struct used_atom *atom)
{
if (starts_with(name, "objectname")) {
if (atom->u.objectname == O_SHORT) {
if (atom->u.objectname.option == O_SHORT) {
v->s = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
return 1;
} else if (atom->u.objectname == O_FULL) {
} else if (atom->u.objectname.option == O_FULL) {
v->s = xstrdup(sha1_to_hex(sha1));
return 1;
} else if (atom->u.objectname.option == O_LENGTH) {
v->s = xstrdup(find_unique_abbrev(sha1, atom->u.objectname.length));
return 1;
} else
die("BUG: unknown %%(objectname) option");
}
@ -887,50 +1117,108 @@ static inline char *copy_advance(char *dst, const char *src)
@@ -887,50 +1117,108 @@ static inline char *copy_advance(char *dst, const char *src)
return dst;
}
static const char *strip_ref_components(const char *refname, const char *nr_arg)
static const char *lstrip_ref_components(const char *refname, int len)
{
char *end;
long nr = strtol(nr_arg, &end, 10);
long remaining = nr;
long remaining = len;
const char *start = refname;
if (nr < 1 || *end != '\0')
die(_(":strip= requires a positive integer argument"));
if (len < 0) {
int i;
const char *p = refname;
/* Find total no of '/' separated path-components */
for (i = 0; p[i]; p[i] == '/' ? i++ : *p++)
;
/*
* The number of components we need to strip is now
* the total minus the components to be left (Plus one
* because we count the number of '/', but the number
* of components is one more than the no of '/').
*/
remaining = i + len + 1;
}
while (remaining) {
while (remaining > 0) {
switch (*start++) {
case '\0':
die(_("ref '%s' does not have %ld components to :strip"),
refname, nr);
return "";
case '/':
remaining--;
break;
}
}
return start;
}
static const char *rstrip_ref_components(const char *refname, int len)
{
long remaining = len;
char *start = xstrdup(refname);
if (len < 0) {
int i;
const char *p = refname;
/* Find total no of '/' separated path-components */
for (i = 0; p[i]; p[i] == '/' ? i++ : *p++)
;
/*
* The number of components we need to strip is now
* the total minus the components to be left (Plus one
* because we count the number of '/', but the number
* of components is one more than the no of '/').
*/
remaining = i + len + 1;
}
while (remaining-- > 0) {
char *p = strrchr(start, '/');
if (p == NULL)
return "";
else
p[0] = '\0';
}
return start;
}
static const char *show_ref(struct refname_atom *atom, const char *refname)
{
if (atom->option == R_SHORT)
return shorten_unambiguous_ref(refname, warn_ambiguous_refs);
else if (atom->option == R_LSTRIP)
return lstrip_ref_components(refname, atom->lstrip);
else if (atom->option == R_RSTRIP)
return rstrip_ref_components(refname, atom->rstrip);
else
return refname;
}
static void fill_remote_ref_details(struct used_atom *atom, const char *refname,
struct branch *branch, const char **s)
{
int num_ours, num_theirs;
if (atom->u.remote_ref == RR_SHORTEN)
*s = shorten_unambiguous_ref(refname, warn_ambiguous_refs);
else if (atom->u.remote_ref == RR_TRACK) {
if (atom->u.remote_ref.option == RR_REF)
*s = show_ref(&atom->u.remote_ref.refname, refname);
else if (atom->u.remote_ref.option == RR_TRACK) {
if (stat_tracking_info(branch, &num_ours,
&num_theirs, NULL))
return;
if (!num_ours && !num_theirs)
&num_theirs, NULL)) {
*s = xstrdup(msgs.gone);
} else if (!num_ours && !num_theirs)
*s = "";
else if (!num_ours)
*s = xstrfmt("[behind %d]", num_theirs);
*s = xstrfmt(msgs.behind, num_theirs);
else if (!num_theirs)
*s = xstrfmt("[ahead %d]", num_ours);
*s = xstrfmt(msgs.ahead, num_ours);
else
*s = xstrfmt("[ahead %d, behind %d]",
*s = xstrfmt(msgs.ahead_behind,
num_ours, num_theirs);
} else if (atom->u.remote_ref == RR_TRACKSHORT) {
if (!atom->u.remote_ref.nobracket && *s[0]) {
const char *to_free = *s;
*s = xstrfmt("[%s]", *s);
free((void *)to_free);
}
} else if (atom->u.remote_ref.option == RR_TRACKSHORT) {
if (stat_tracking_info(branch, &num_ours,
&num_theirs, NULL))
return;
@ -943,8 +1231,56 @@ static void fill_remote_ref_details(struct used_atom *atom, const char *refname,
@@ -943,8 +1231,56 @@ static void fill_remote_ref_details(struct used_atom *atom, const char *refname,
*s = ">";
else
*s = "<>";
} else /* RR_NORMAL */
*s = refname;
} else
die("BUG: unhandled RR_* enum");
}
char *get_head_description(void)
{
struct strbuf desc = STRBUF_INIT;
struct wt_status_state state;
memset(&state, 0, sizeof(state));
wt_status_get_state(&state, 1);
if (state.rebase_in_progress ||
state.rebase_interactive_in_progress)
strbuf_addf(&desc, _("(no branch, rebasing %s)"),
state.branch);
else if (state.bisect_in_progress)
strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
state.branch);
else if (state.detached_from) {
if (state.detached_at)
/* TRANSLATORS: make sure this matches
"HEAD detached at " in wt-status.c */
strbuf_addf(&desc, _("(HEAD detached at %s)"),
state.detached_from);
else
/* TRANSLATORS: make sure this matches
"HEAD detached from " in wt-status.c */
strbuf_addf(&desc, _("(HEAD detached from %s)"),
state.detached_from);
}
else
strbuf_addstr(&desc, _("(no branch)"));
free(state.branch);
free(state.onto);
free(state.detached_from);
return strbuf_detach(&desc, NULL);
}
static const char *get_symref(struct used_atom *atom, struct ref_array_item *ref)
{
if (!ref->symref)
return "";
else
return show_ref(&atom->u.refname, ref->symref);
}
static const char *get_refname(struct used_atom *atom, struct ref_array_item *ref)
{
if (ref->kind & FILTER_REFS_DETACHED_HEAD)
return get_head_description();
return show_ref(&atom->u.refname, ref->refname);
}
/*
@ -975,10 +1311,10 @@ static void populate_value(struct ref_array_item *ref)
@@ -975,10 +1311,10 @@ static void populate_value(struct ref_array_item *ref)
struct atom_value *v = &ref->value[i];
int deref = 0;
const char *refname;
const char *formatp;
struct branch *branch = NULL;
v->handler = append_atom;
v->atom = atom;
if (*name == '*') {
deref = 1;
@ -986,9 +1322,9 @@ static void populate_value(struct ref_array_item *ref)
@@ -986,9 +1322,9 @@ static void populate_value(struct ref_array_item *ref)
}
if (starts_with(name, "refname"))
refname = ref->refname;
refname = get_refname(atom, ref);
else if (starts_with(name, "symref"))
refname = ref->symref ? ref->symref : "";
refname = get_symref(atom, ref);
else if (starts_with(name, "upstream")) {
const char *branch_name;
/* only local branches may have an upstream */
@ -1043,30 +1379,27 @@ static void populate_value(struct ref_array_item *ref)
@@ -1043,30 +1379,27 @@ static void populate_value(struct ref_array_item *ref)
v->s = " ";
continue;
} else if (starts_with(name, "align")) {
v->u.align = atom->u.align;
v->handler = align_atom_handler;
continue;
} else if (!strcmp(name, "end")) {
v->handler = end_atom_handler;
continue;
} else if (starts_with(name, "if")) {
const char *s;
if (skip_prefix(name, "if:", &s))
v->s = xstrdup(s);
v->handler = if_atom_handler;
continue;
} else if (!strcmp(name, "then")) {
v->handler = then_atom_handler;
continue;
} else if (!strcmp(name, "else")) {
v->handler = else_atom_handler;
continue;
} else
continue;
formatp = strchr(name, ':');
if (formatp) {
const char *arg;
formatp++;
if (!strcmp(formatp, "short"))
refname = shorten_unambiguous_ref(refname,
warn_ambiguous_refs);
else if (skip_prefix(formatp, "strip=", &arg))
refname = strip_ref_components(refname, arg);
else
die(_("unknown %.*s format %s"),
(int)(formatp - name), name, formatp);
}
if (!deref)
v->s = refname;
else
@ -1635,10 +1968,10 @@ static void append_literal(const char *cp, const char *ep, struct ref_formatting
@@ -1635,10 +1968,10 @@ static void append_literal(const char *cp, const char *ep, struct ref_formatting
}
}
void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)
void format_ref_array_item(struct ref_array_item *info, const char *format,
int quote_style, struct strbuf *final_buf)
{
const char *cp, *sp, *ep;
struct strbuf *final_buf;
struct ref_formatting_state state = REF_FORMATTING_STATE_INIT;
state.quote_style = quote_style;
@ -1668,9 +2001,17 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
@@ -1668,9 +2001,17 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
}
if (state.stack->prev)
die(_("format: %%(end) atom missing"));
final_buf = &state.stack->output;
fwrite(final_buf->buf, 1, final_buf->len, stdout);
strbuf_addbuf(final_buf, &state.stack->output);
pop_stack_element(&state.stack);
}
void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)
{
struct strbuf final_buf = STRBUF_INIT;
format_ref_array_item(info, format, quote_style, &final_buf);
fwrite(final_buf.buf, 1, final_buf.len, stdout);
strbuf_release(&final_buf);
putchar('\n');
}