Merge branch 'ps/reflog-list' into HEAD

"git reflog" learned a "list" subcommand that enumerates known reflogs.

* ps/reflog-list:
  builtin/reflog: introduce subcommand to list reflogs
  refs: stop resolving ref corresponding to reflogs
  refs: drop unused params from the reflog iterator callback
  refs: always treat iterators as ordered
  refs/files: sort merged worktree and common reflogs
  refs/files: sort reflogs returned by the reflog iterator
  dir-iterator: support iteration in sorted order
  dir-iterator: pass name to `prepare_next_entry_data()` directly
This commit is contained in:
Junio C Hamano 2024-03-01 14:38:55 -08:00
commit 510a27e9e4
20 changed files with 380 additions and 205 deletions

View file

@ -10,6 +10,7 @@ SYNOPSIS
--------
[verse]
'git reflog' [show] [<log-options>] [<ref>]
'git reflog list'
'git reflog expire' [--expire=<time>] [--expire-unreachable=<time>]
[--rewrite] [--updateref] [--stale-fix]
[--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]
@ -39,6 +40,8 @@ actions, and in addition the `HEAD` reflog records branch switching.
`git reflog show` is an alias for `git log -g --abbrev-commit
--pretty=oneline`; see linkgit:git-log[1] for more information.
The "list" subcommand lists all refs which have a corresponding reflog.
The "expire" subcommand prunes older reflog entries. Entries older
than `expire` time, or entries older than `expire-unreachable` time
and not reachable from the current tip, are removed from the reflog.

View file

@ -509,9 +509,7 @@ static int fsck_handle_reflog_ent(struct object_id *ooid, struct object_id *noid
return 0;
}
static int fsck_handle_reflog(const char *logname,
const struct object_id *oid UNUSED,
int flag UNUSED, void *cb_data)
static int fsck_handle_reflog(const char *logname, void *cb_data)
{
struct strbuf refname = STRBUF_INIT;

View file

@ -7,11 +7,15 @@
#include "wildmatch.h"
#include "worktree.h"
#include "reflog.h"
#include "refs.h"
#include "parse-options.h"
#define BUILTIN_REFLOG_SHOW_USAGE \
N_("git reflog [show] [<log-options>] [<ref>]")
#define BUILTIN_REFLOG_LIST_USAGE \
N_("git reflog list")
#define BUILTIN_REFLOG_EXPIRE_USAGE \
N_("git reflog expire [--expire=<time>] [--expire-unreachable=<time>]\n" \
" [--rewrite] [--updateref] [--stale-fix]\n" \
@ -29,6 +33,11 @@ static const char *const reflog_show_usage[] = {
NULL,
};
static const char *const reflog_list_usage[] = {
BUILTIN_REFLOG_LIST_USAGE,
NULL,
};
static const char *const reflog_expire_usage[] = {
BUILTIN_REFLOG_EXPIRE_USAGE,
NULL
@ -46,6 +55,7 @@ static const char *const reflog_exists_usage[] = {
static const char *const reflog_usage[] = {
BUILTIN_REFLOG_SHOW_USAGE,
BUILTIN_REFLOG_LIST_USAGE,
BUILTIN_REFLOG_EXPIRE_USAGE,
BUILTIN_REFLOG_DELETE_USAGE,
BUILTIN_REFLOG_EXISTS_USAGE,
@ -60,8 +70,7 @@ struct worktree_reflogs {
struct string_list reflogs;
};
static int collect_reflog(const char *ref, const struct object_id *oid UNUSED,
int flags UNUSED, void *cb_data)
static int collect_reflog(const char *ref, void *cb_data)
{
struct worktree_reflogs *cb = cb_data;
struct worktree *worktree = cb->worktree;
@ -238,6 +247,29 @@ static int cmd_reflog_show(int argc, const char **argv, const char *prefix)
return cmd_log_reflog(argc, argv, prefix);
}
static int show_reflog(const char *refname, void *cb_data UNUSED)
{
printf("%s\n", refname);
return 0;
}
static int cmd_reflog_list(int argc, const char **argv, const char *prefix)
{
struct option options[] = {
OPT_END()
};
struct ref_store *ref_store;
argc = parse_options(argc, argv, prefix, options, reflog_list_usage, 0);
if (argc)
return error(_("%s does not accept arguments: '%s'"),
"list", argv[0]);
ref_store = get_main_ref_store(the_repository);
return refs_for_each_reflog(ref_store, show_reflog, NULL);
}
static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
{
struct cmd_reflog_expire_cb cmd = { 0 };
@ -417,6 +449,7 @@ int cmd_reflog(int argc, const char **argv, const char *prefix)
parse_opt_subcommand_fn *fn = NULL;
struct option options[] = {
OPT_SUBCOMMAND("show", &fn, cmd_reflog_show),
OPT_SUBCOMMAND("list", &fn, cmd_reflog_list),
OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire),
OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete),
OPT_SUBCOMMAND("exists", &fn, cmd_reflog_exists),

View file

@ -2,10 +2,19 @@
#include "dir.h"
#include "iterator.h"
#include "dir-iterator.h"
#include "string-list.h"
struct dir_iterator_level {
DIR *dir;
/*
* The directory entries of the current level. This list will only be
* populated when the iterator is ordered. In that case, `dir` will be
* set to `NULL`.
*/
struct string_list entries;
size_t entries_idx;
/*
* The length of the directory part of path at this level
* (including a trailing '/'):
@ -43,6 +52,31 @@ struct dir_iterator_int {
unsigned int flags;
};
static int next_directory_entry(DIR *dir, const char *path,
struct dirent **out)
{
struct dirent *de;
repeat:
errno = 0;
de = readdir(dir);
if (!de) {
if (errno) {
warning_errno("error reading directory '%s'",
path);
return -1;
}
return 1;
}
if (is_dot_or_dotdot(de->d_name))
goto repeat;
*out = de;
return 0;
}
/*
* Push a level in the iter stack and initialize it with information from
* the directory pointed by iter->base->path. It is assumed that this
@ -72,6 +106,35 @@ static int push_level(struct dir_iterator_int *iter)
return -1;
}
string_list_init_dup(&level->entries);
level->entries_idx = 0;
/*
* When the iterator is sorted we read and sort all directory entries
* directly.
*/
if (iter->flags & DIR_ITERATOR_SORTED) {
struct dirent *de;
while (1) {
int ret = next_directory_entry(level->dir, iter->base.path.buf, &de);
if (ret < 0) {
if (errno != ENOENT &&
iter->flags & DIR_ITERATOR_PEDANTIC)
return -1;
continue;
} else if (ret > 0) {
break;
}
string_list_append(&level->entries, de->d_name);
}
string_list_sort(&level->entries);
closedir(level->dir);
level->dir = NULL;
}
return 0;
}
@ -88,21 +151,22 @@ static int pop_level(struct dir_iterator_int *iter)
warning_errno("error closing directory '%s'",
iter->base.path.buf);
level->dir = NULL;
string_list_clear(&level->entries, 0);
return --iter->levels_nr;
}
/*
* Populate iter->base with the necessary information on the next iteration
* entry, represented by the given dirent de. Return 0 on success and -1
* entry, represented by the given name. Return 0 on success and -1
* otherwise, setting errno accordingly.
*/
static int prepare_next_entry_data(struct dir_iterator_int *iter,
struct dirent *de)
const char *name)
{
int err, saved_errno;
strbuf_addstr(&iter->base.path, de->d_name);
strbuf_addstr(&iter->base.path, name);
/*
* We have to reset these because the path strbuf might have
* been realloc()ed at the previous strbuf_addstr().
@ -139,27 +203,34 @@ int dir_iterator_advance(struct dir_iterator *dir_iterator)
struct dirent *de;
struct dir_iterator_level *level =
&iter->levels[iter->levels_nr - 1];
const char *name;
strbuf_setlen(&iter->base.path, level->prefix_len);
errno = 0;
de = readdir(level->dir);
if (!de) {
if (errno) {
warning_errno("error reading directory '%s'",
iter->base.path.buf);
if (level->dir) {
int ret = next_directory_entry(level->dir, iter->base.path.buf, &de);
if (ret < 0) {
if (iter->flags & DIR_ITERATOR_PEDANTIC)
goto error_out;
} else if (pop_level(iter) == 0) {
return dir_iterator_abort(dir_iterator);
continue;
} else if (ret > 0) {
if (pop_level(iter) == 0)
return dir_iterator_abort(dir_iterator);
continue;
}
continue;
name = de->d_name;
} else {
if (level->entries_idx >= level->entries.nr) {
if (pop_level(iter) == 0)
return dir_iterator_abort(dir_iterator);
continue;
}
name = level->entries.items[level->entries_idx++].string;
}
if (is_dot_or_dotdot(de->d_name))
continue;
if (prepare_next_entry_data(iter, de)) {
if (prepare_next_entry_data(iter, name)) {
if (errno != ENOENT && iter->flags & DIR_ITERATOR_PEDANTIC)
goto error_out;
continue;
@ -188,6 +259,8 @@ int dir_iterator_abort(struct dir_iterator *dir_iterator)
warning_errno("error closing directory '%s'",
iter->base.path.buf);
}
string_list_clear(&level->entries, 0);
}
free(iter->levels);

View file

@ -54,8 +54,11 @@
* and ITER_ERROR is returned immediately. In both cases, a meaningful
* warning is emitted. Note: ENOENT errors are always ignored so that
* the API users may remove files during iteration.
*
* - DIR_ITERATOR_SORTED: sort directory entries alphabetically.
*/
#define DIR_ITERATOR_PEDANTIC (1 << 0)
#define DIR_ITERATOR_SORTED (1 << 1)
struct dir_iterator {
/* The current path: */

27
refs.c
View file

@ -1594,10 +1594,6 @@ struct ref_iterator *refs_ref_iterator_begin(
if (trim)
iter = prefix_ref_iterator_begin(iter, "", trim);
/* Sanity check for subclasses: */
if (!iter->ordered)
BUG("reference iterator is not ordered");
return iter;
}
@ -2516,18 +2512,33 @@ int refs_verify_refname_available(struct ref_store *refs,
return ret;
}
int refs_for_each_reflog(struct ref_store *refs, each_ref_fn fn, void *cb_data)
struct do_for_each_reflog_help {
each_reflog_fn *fn;
void *cb_data;
};
static int do_for_each_reflog_helper(struct repository *r UNUSED,
const char *refname,
const struct object_id *oid UNUSED,
int flags,
void *cb_data)
{
struct do_for_each_reflog_help *hp = cb_data;
return hp->fn(refname, hp->cb_data);
}
int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_data)
{
struct ref_iterator *iter;
struct do_for_each_ref_help hp = { fn, cb_data };
struct do_for_each_reflog_help hp = { fn, cb_data };
iter = refs->be->reflog_iterator_begin(refs);
return do_for_each_repo_ref_iterator(the_repository, iter,
do_for_each_ref_helper, &hp);
do_for_each_reflog_helper, &hp);
}
int for_each_reflog(each_ref_fn fn, void *cb_data)
int for_each_reflog(each_reflog_fn fn, void *cb_data)
{
return refs_for_each_reflog(get_main_ref_store(the_repository), fn, cb_data);
}

11
refs.h
View file

@ -534,12 +534,19 @@ int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_dat
/* youngest entry first */
int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data);
/*
* The signature for the callback function for the {refs_,}for_each_reflog()
* functions below. The memory pointed to by the refname argument is only
* guaranteed to be valid for the duration of a single callback invocation.
*/
typedef int each_reflog_fn(const char *refname, void *cb_data);
/*
* Calls the specified function for each reflog file until it returns nonzero,
* and returns the value. Reflog file order is unspecified.
*/
int refs_for_each_reflog(struct ref_store *refs, each_ref_fn fn, void *cb_data);
int for_each_reflog(each_ref_fn fn, void *cb_data);
int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_data);
int for_each_reflog(each_reflog_fn fn, void *cb_data);
#define REFNAME_ALLOW_ONELEVEL 1
#define REFNAME_REFSPEC_PATTERN 2

View file

@ -181,7 +181,6 @@ static int debug_ref_iterator_advance(struct ref_iterator *ref_iterator)
trace_printf_key(&trace_refs, "iterator_advance: %s (0)\n",
diter->iter->refname);
diter->base.ordered = diter->iter->ordered;
diter->base.refname = diter->iter->refname;
diter->base.oid = diter->iter->oid;
diter->base.flags = diter->iter->flags;
@ -222,7 +221,7 @@ debug_ref_iterator_begin(struct ref_store *ref_store, const char *prefix,
drefs->refs->be->iterator_begin(drefs->refs, prefix,
exclude_patterns, flags);
struct debug_ref_iterator *diter = xcalloc(1, sizeof(*diter));
base_ref_iterator_init(&diter->base, &debug_ref_iterator_vtable, 1);
base_ref_iterator_init(&diter->base, &debug_ref_iterator_vtable);
diter->iter = res;
trace_printf_key(&trace_refs, "ref_iterator_begin: \"%s\" (0x%x)\n",
prefix, flags);

View file

@ -879,8 +879,7 @@ static struct ref_iterator *files_ref_iterator_begin(
CALLOC_ARRAY(iter, 1);
ref_iterator = &iter->base;
base_ref_iterator_init(ref_iterator, &files_ref_iterator_vtable,
overlay_iter->ordered);
base_ref_iterator_init(ref_iterator, &files_ref_iterator_vtable);
iter->iter0 = overlay_iter;
iter->repo = ref_store->repo;
iter->flags = flags;
@ -2116,10 +2115,8 @@ static int files_for_each_reflog_ent(struct ref_store *ref_store,
struct files_reflog_iterator {
struct ref_iterator base;
struct ref_store *ref_store;
struct dir_iterator *dir_iterator;
struct object_id oid;
};
static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator)
@ -2130,25 +2127,13 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator)
int ok;
while ((ok = dir_iterator_advance(diter)) == ITER_OK) {
int flags;
if (!S_ISREG(diter->st.st_mode))
continue;
if (diter->basename[0] == '.')
if (check_refname_format(diter->basename,
REFNAME_ALLOW_ONELEVEL))
continue;
if (ends_with(diter->basename, ".lock"))
continue;
if (!refs_resolve_ref_unsafe(iter->ref_store,
diter->relative_path, 0,
&iter->oid, &flags)) {
error("bad ref for %s", diter->path.buf);
continue;
}
iter->base.refname = diter->relative_path;
iter->base.oid = &iter->oid;
iter->base.flags = flags;
return ITER_OK;
}
@ -2193,7 +2178,7 @@ static struct ref_iterator *reflog_iterator_begin(struct ref_store *ref_store,
strbuf_addf(&sb, "%s/logs", gitdir);
diter = dir_iterator_begin(sb.buf, 0);
diter = dir_iterator_begin(sb.buf, DIR_ITERATOR_SORTED);
if (!diter) {
strbuf_release(&sb);
return empty_ref_iterator_begin();
@ -2202,7 +2187,7 @@ static struct ref_iterator *reflog_iterator_begin(struct ref_store *ref_store,
CALLOC_ARRAY(iter, 1);
ref_iterator = &iter->base;
base_ref_iterator_init(ref_iterator, &files_reflog_iterator_vtable, 0);
base_ref_iterator_init(ref_iterator, &files_reflog_iterator_vtable);
iter->dir_iterator = diter;
iter->ref_store = ref_store;
strbuf_release(&sb);
@ -2210,32 +2195,6 @@ static struct ref_iterator *reflog_iterator_begin(struct ref_store *ref_store,
return ref_iterator;
}
static enum iterator_selection reflog_iterator_select(
struct ref_iterator *iter_worktree,
struct ref_iterator *iter_common,
void *cb_data UNUSED)
{
if (iter_worktree) {
/*
* We're a bit loose here. We probably should ignore
* common refs if they are accidentally added as
* per-worktree refs.
*/
return ITER_SELECT_0;
} else if (iter_common) {
if (parse_worktree_ref(iter_common->refname, NULL, NULL,
NULL) == REF_WORKTREE_SHARED)
return ITER_SELECT_1;
/*
* The main ref store may contain main worktree's
* per-worktree refs, which should be ignored
*/
return ITER_SKIP_1;
} else
return ITER_DONE;
}
static struct ref_iterator *files_reflog_iterator_begin(struct ref_store *ref_store)
{
struct files_ref_store *refs =
@ -2246,9 +2205,9 @@ static struct ref_iterator *files_reflog_iterator_begin(struct ref_store *ref_st
return reflog_iterator_begin(ref_store, refs->gitcommondir);
} else {
return merge_ref_iterator_begin(
0, reflog_iterator_begin(ref_store, refs->base.gitdir),
reflog_iterator_begin(ref_store, refs->base.gitdir),
reflog_iterator_begin(ref_store, refs->gitcommondir),
reflog_iterator_select, refs);
ref_iterator_select, refs);
}
}

View file

@ -25,11 +25,9 @@ int ref_iterator_abort(struct ref_iterator *ref_iterator)
}
void base_ref_iterator_init(struct ref_iterator *iter,
struct ref_iterator_vtable *vtable,
int ordered)
struct ref_iterator_vtable *vtable)
{
iter->vtable = vtable;
iter->ordered = !!ordered;
iter->refname = NULL;
iter->oid = NULL;
iter->flags = 0;
@ -74,7 +72,7 @@ struct ref_iterator *empty_ref_iterator_begin(void)
struct empty_ref_iterator *iter = xcalloc(1, sizeof(*iter));
struct ref_iterator *ref_iterator = &iter->base;
base_ref_iterator_init(ref_iterator, &empty_ref_iterator_vtable, 1);
base_ref_iterator_init(ref_iterator, &empty_ref_iterator_vtable);
return ref_iterator;
}
@ -98,6 +96,49 @@ struct merge_ref_iterator {
struct ref_iterator **current;
};
enum iterator_selection ref_iterator_select(struct ref_iterator *iter_worktree,
struct ref_iterator *iter_common,
void *cb_data UNUSED)
{
if (iter_worktree && !iter_common) {
/*
* Return the worktree ref if there are no more common refs.
*/
return ITER_SELECT_0;
} else if (iter_common) {
/*
* In case we have pending worktree and common refs we need to
* yield them based on their lexicographical order. Worktree
* refs that have the same name as common refs shadow the
* latter.
*/
if (iter_worktree) {
int cmp = strcmp(iter_worktree->refname,
iter_common->refname);
if (cmp < 0)
return ITER_SELECT_0;
else if (!cmp)
return ITER_SELECT_0_SKIP_1;
}
/*
* We now know that the lexicographically-next ref is a common
* ref. When the common ref is a shared one we return it.
*/
if (parse_worktree_ref(iter_common->refname, NULL, NULL,
NULL) == REF_WORKTREE_SHARED)
return ITER_SELECT_1;
/*
* Otherwise, if the common ref is a per-worktree ref we skip
* it because it would belong to the main worktree, not ours.
*/
return ITER_SKIP_1;
} else {
return ITER_DONE;
}
}
static int merge_ref_iterator_advance(struct ref_iterator *ref_iterator)
{
struct merge_ref_iterator *iter =
@ -207,7 +248,6 @@ static struct ref_iterator_vtable merge_ref_iterator_vtable = {
};
struct ref_iterator *merge_ref_iterator_begin(
int ordered,
struct ref_iterator *iter0, struct ref_iterator *iter1,
ref_iterator_select_fn *select, void *cb_data)
{
@ -222,7 +262,7 @@ struct ref_iterator *merge_ref_iterator_begin(
* references through only if they exist in both iterators.
*/
base_ref_iterator_init(ref_iterator, &merge_ref_iterator_vtable, ordered);
base_ref_iterator_init(ref_iterator, &merge_ref_iterator_vtable);
iter->iter0 = iter0;
iter->iter1 = iter1;
iter->select = select;
@ -271,12 +311,9 @@ struct ref_iterator *overlay_ref_iterator_begin(
} else if (is_empty_ref_iterator(back)) {
ref_iterator_abort(back);
return front;
} else if (!front->ordered || !back->ordered) {
BUG("overlay_ref_iterator requires ordered inputs");
}
return merge_ref_iterator_begin(1, front, back,
overlay_iterator_select, NULL);
return merge_ref_iterator_begin(front, back, overlay_iterator_select, NULL);
}
struct prefix_ref_iterator {
@ -315,16 +352,12 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)
if (cmp > 0) {
/*
* If the source iterator is ordered, then we
* As the source iterator is ordered, we
* can stop the iteration as soon as we see a
* refname that comes after the prefix:
*/
if (iter->iter0->ordered) {
ok = ref_iterator_abort(iter->iter0);
break;
} else {
continue;
}
ok = ref_iterator_abort(iter->iter0);
break;
}
if (iter->trim) {
@ -396,7 +429,7 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,
CALLOC_ARRAY(iter, 1);
ref_iterator = &iter->base;
base_ref_iterator_init(ref_iterator, &prefix_ref_iterator_vtable, iter0->ordered);
base_ref_iterator_init(ref_iterator, &prefix_ref_iterator_vtable);
iter->iter0 = iter0;
iter->prefix = xstrdup(prefix);

View file

@ -1111,7 +1111,7 @@ static struct ref_iterator *packed_ref_iterator_begin(
CALLOC_ARRAY(iter, 1);
ref_iterator = &iter->base;
base_ref_iterator_init(ref_iterator, &packed_ref_iterator_vtable, 1);
base_ref_iterator_init(ref_iterator, &packed_ref_iterator_vtable);
if (exclude_patterns)
populate_excluded_jump_list(iter, snapshot, exclude_patterns);

View file

@ -486,7 +486,7 @@ struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache,
CALLOC_ARRAY(iter, 1);
ref_iterator = &iter->base;
base_ref_iterator_init(ref_iterator, &cache_ref_iterator_vtable, 1);
base_ref_iterator_init(ref_iterator, &cache_ref_iterator_vtable);
ALLOC_GROW(iter->levels, 10, iter->levels_alloc);
iter->levels_nr = 1;

View file

@ -312,13 +312,6 @@ enum do_for_each_ref_flags {
*/
struct ref_iterator {
struct ref_iterator_vtable *vtable;
/*
* Does this `ref_iterator` iterate over references in order
* by refname?
*/
unsigned int ordered : 1;
const char *refname;
const struct object_id *oid;
unsigned int flags;
@ -386,15 +379,22 @@ typedef enum iterator_selection ref_iterator_select_fn(
struct ref_iterator *iter0, struct ref_iterator *iter1,
void *cb_data);
/*
* An implementation of ref_iterator_select_fn that merges worktree and common
* refs. Per-worktree refs from the common iterator are ignored, worktree refs
* override common refs. Refs are selected lexicographically.
*/
enum iterator_selection ref_iterator_select(struct ref_iterator *iter_worktree,
struct ref_iterator *iter_common,
void *cb_data);
/*
* Iterate over the entries from iter0 and iter1, with the values
* interleaved as directed by the select function. The iterator takes
* ownership of iter0 and iter1 and frees them when the iteration is
* over. A derived class should set `ordered` to 1 or 0 based on
* whether it generates its output in order by reference name.
* over.
*/
struct ref_iterator *merge_ref_iterator_begin(
int ordered,
struct ref_iterator *iter0, struct ref_iterator *iter1,
ref_iterator_select_fn *select, void *cb_data);
@ -423,8 +423,6 @@ struct ref_iterator *overlay_ref_iterator_begin(
* As an convenience to callers, if prefix is the empty string and
* trim is zero, this function returns iter0 directly, without
* wrapping it.
*
* The resulting ref_iterator is ordered if iter0 is.
*/
struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,
const char *prefix,
@ -435,14 +433,11 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,
/*
* Base class constructor for ref_iterators. Initialize the
* ref_iterator part of iter, setting its vtable pointer as specified.
* `ordered` should be set to 1 if the iterator will iterate over
* references in order by refname; otherwise it should be set to 0.
* This is meant to be called only by the initializers of derived
* classes.
*/
void base_ref_iterator_init(struct ref_iterator *iter,
struct ref_iterator_vtable *vtable,
int ordered);
struct ref_iterator_vtable *vtable);
/*
* Base class destructor for ref_iterators. Destroy the ref_iterator

View file

@ -479,7 +479,7 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_
int ret;
iter = xcalloc(1, sizeof(*iter));
base_ref_iterator_init(&iter->base, &reftable_ref_iterator_vtable, 1);
base_ref_iterator_init(&iter->base, &reftable_ref_iterator_vtable);
iter->prefix = prefix;
iter->base.oid = &iter->oid;
iter->flags = flags;
@ -504,49 +504,6 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_
return iter;
}
static enum iterator_selection iterator_select(struct ref_iterator *iter_worktree,
struct ref_iterator *iter_common,
void *cb_data UNUSED)
{
if (iter_worktree && !iter_common) {
/*
* Return the worktree ref if there are no more common refs.
*/
return ITER_SELECT_0;
} else if (iter_common) {
/*
* In case we have pending worktree and common refs we need to
* yield them based on their lexicographical order. Worktree
* refs that have the same name as common refs shadow the
* latter.
*/
if (iter_worktree) {
int cmp = strcmp(iter_worktree->refname,
iter_common->refname);
if (cmp < 0)
return ITER_SELECT_0;
else if (!cmp)
return ITER_SELECT_0_SKIP_1;
}
/*
* We now know that the lexicographically-next ref is a common
* ref. When the common ref is a shared one we return it.
*/
if (parse_worktree_ref(iter_common->refname, NULL, NULL,
NULL) == REF_WORKTREE_SHARED)
return ITER_SELECT_1;
/*
* Otherwise, if the common ref is a per-worktree ref we skip
* it because it would belong to the main worktree, not ours.
*/
return ITER_SKIP_1;
} else {
return ITER_DONE;
}
}
static struct ref_iterator *reftable_be_iterator_begin(struct ref_store *ref_store,
const char *prefix,
const char **exclude_patterns,
@ -575,8 +532,8 @@ static struct ref_iterator *reftable_be_iterator_begin(struct ref_store *ref_sto
* single iterator.
*/
worktree_iter = ref_iterator_for_stack(refs, refs->worktree_stack, prefix, flags);
return merge_ref_iterator_begin(1, &worktree_iter->base, &main_iter->base,
iterator_select, NULL);
return merge_ref_iterator_begin(&worktree_iter->base, &main_iter->base,
ref_iterator_select, NULL);
}
static int reftable_be_read_raw_ref(struct ref_store *ref_store,
@ -1637,7 +1594,6 @@ struct reftable_reflog_iterator {
struct reftable_ref_store *refs;
struct reftable_iterator iter;
struct reftable_log_record log;
struct object_id oid;
char *last_name;
int err;
};
@ -1648,8 +1604,6 @@ static int reftable_reflog_iterator_advance(struct ref_iterator *ref_iterator)
(struct reftable_reflog_iterator *)ref_iterator;
while (!iter->err) {
int flags;
iter->err = reftable_iterator_next_log(&iter->iter, &iter->log);
if (iter->err)
break;
@ -1662,17 +1616,13 @@ static int reftable_reflog_iterator_advance(struct ref_iterator *ref_iterator)
if (iter->last_name && !strcmp(iter->log.refname, iter->last_name))
continue;
if (!refs_resolve_ref_unsafe(&iter->refs->base, iter->log.refname,
0, &iter->oid, &flags)) {
error(_("bad ref for %s"), iter->log.refname);
if (check_refname_format(iter->log.refname,
REFNAME_ALLOW_ONELEVEL))
continue;
}
free(iter->last_name);
iter->last_name = xstrdup(iter->log.refname);
iter->base.refname = iter->log.refname;
iter->base.oid = &iter->oid;
iter->base.flags = flags;
break;
}
@ -1723,9 +1673,8 @@ static struct reftable_reflog_iterator *reflog_iterator_for_stack(struct reftabl
int ret;
iter = xcalloc(1, sizeof(*iter));
base_ref_iterator_init(&iter->base, &reftable_reflog_iterator_vtable, 1);
base_ref_iterator_init(&iter->base, &reftable_reflog_iterator_vtable);
iter->refs = refs;
iter->base.oid = &iter->oid;
ret = refs->err;
if (ret)
@ -1758,8 +1707,8 @@ static struct ref_iterator *reftable_be_reflog_iterator_begin(struct ref_store *
worktree_iter = reflog_iterator_for_stack(refs, refs->worktree_stack);
return merge_ref_iterator_begin(1, &worktree_iter->base, &main_iter->base,
iterator_select, NULL);
return merge_ref_iterator_begin(&worktree_iter->base, &main_iter->base,
ref_iterator_select, NULL);
}
static int yield_log_record(struct reftable_log_record *log,

View file

@ -1686,9 +1686,7 @@ static int handle_one_reflog_ent(struct object_id *ooid, struct object_id *noid,
return 0;
}
static int handle_one_reflog(const char *refname_in_wt,
const struct object_id *oid UNUSED,
int flag UNUSED, void *cb_data)
static int handle_one_reflog(const char *refname_in_wt, void *cb_data)
{
struct all_refs_cb *cb = cb_data;
struct strbuf refname = STRBUF_INIT;

View file

@ -221,15 +221,21 @@ static int cmd_verify_ref(struct ref_store *refs, const char **argv)
return ret;
}
static int each_reflog(const char *refname, void *cb_data UNUSED)
{
printf("%s\n", refname);
return 0;
}
static int cmd_for_each_reflog(struct ref_store *refs,
const char **argv UNUSED)
{
return refs_for_each_reflog(refs, each_ref, NULL);
return refs_for_each_reflog(refs, each_reflog, NULL);
}
static int each_reflog(struct object_id *old_oid, struct object_id *new_oid,
const char *committer, timestamp_t timestamp,
int tz, const char *msg, void *cb_data UNUSED)
static int each_reflog_ent(struct object_id *old_oid, struct object_id *new_oid,
const char *committer, timestamp_t timestamp,
int tz, const char *msg, void *cb_data UNUSED)
{
printf("%s %s %s %" PRItime " %+05d%s%s", oid_to_hex(old_oid),
oid_to_hex(new_oid), committer, timestamp, tz,
@ -241,14 +247,14 @@ static int cmd_for_each_reflog_ent(struct ref_store *refs, const char **argv)
{
const char *refname = notnull(*argv++, "refname");
return refs_for_each_reflog_ent(refs, refname, each_reflog, refs);
return refs_for_each_reflog_ent(refs, refname, each_reflog_ent, refs);
}
static int cmd_for_each_reflog_ent_reverse(struct ref_store *refs, const char **argv)
{
const char *refname = notnull(*argv++, "refname");
return refs_for_each_reflog_ent_reverse(refs, refname, each_reflog, refs);
return refs_for_each_reflog_ent_reverse(refs, refname, each_reflog_ent, refs);
}
static int cmd_reflog_exists(struct ref_store *refs, const char **argv)

View file

@ -287,23 +287,23 @@ test_expect_success 'for_each_reflog()' '
mkdir -p .git/worktrees/wt/logs/refs/bisect &&
echo $ZERO_OID > .git/worktrees/wt/logs/refs/bisect/wt-random &&
$RWT for-each-reflog | cut -d" " -f 2- | sort >actual &&
$RWT for-each-reflog >actual &&
cat >expected <<-\EOF &&
HEAD 0x1
PSEUDO-WT 0x0
refs/bisect/wt-random 0x0
refs/heads/main 0x0
refs/heads/wt-main 0x0
HEAD
PSEUDO-WT
refs/bisect/wt-random
refs/heads/main
refs/heads/wt-main
EOF
test_cmp expected actual &&
$RMAIN for-each-reflog | cut -d" " -f 2- | sort >actual &&
$RMAIN for-each-reflog >actual &&
cat >expected <<-\EOF &&
HEAD 0x1
PSEUDO-MAIN 0x0
refs/bisect/random 0x0
refs/heads/main 0x0
refs/heads/wt-main 0x0
HEAD
PSEUDO-MAIN
refs/bisect/random
refs/heads/main
refs/heads/wt-main
EOF
test_cmp expected actual
'

View file

@ -68,11 +68,11 @@ test_expect_success 'verify_ref(new-main)' '
'
test_expect_success 'for_each_reflog()' '
$RUN for-each-reflog | sort -k2 | cut -d" " -f 2- >actual &&
$RUN for-each-reflog >actual &&
cat >expected <<-\EOF &&
HEAD 0x1
refs/heads/main 0x0
refs/heads/new-main 0x0
HEAD
refs/heads/main
refs/heads/new-main
EOF
test_cmp expected actual
'

View file

@ -63,11 +63,11 @@ test_expect_success 'verify_ref(new-main)' '
'
test_expect_success 'for_each_reflog()' '
$RUN for-each-reflog | sort | cut -d" " -f 2- >actual &&
$RUN for-each-reflog >actual &&
cat >expected <<-\EOF &&
HEAD 0x1
refs/heads/main 0x0
refs/heads/new-main 0x0
HEAD
refs/heads/main
refs/heads/new-main
EOF
test_cmp expected actual
'

View file

@ -436,4 +436,112 @@ test_expect_success 'empty reflog' '
test_must_be_empty err
'
test_expect_success 'list reflogs' '
test_when_finished "rm -rf repo" &&
git init repo &&
(
cd repo &&
git reflog list >actual &&
test_must_be_empty actual &&
test_commit A &&
cat >expect <<-EOF &&
HEAD
refs/heads/main
EOF
git reflog list >actual &&
test_cmp expect actual &&
git branch b &&
cat >expect <<-EOF &&
HEAD
refs/heads/b
refs/heads/main
EOF
git reflog list >actual &&
test_cmp expect actual
)
'
test_expect_success 'list reflogs with worktree' '
test_when_finished "rm -rf repo" &&
git init repo &&
(
cd repo &&
test_commit A &&
git worktree add wt &&
git -c core.logAllRefUpdates=always \
update-ref refs/worktree/main HEAD &&
git -c core.logAllRefUpdates=always \
update-ref refs/worktree/per-worktree HEAD &&
git -c core.logAllRefUpdates=always -C wt \
update-ref refs/worktree/per-worktree HEAD &&
git -c core.logAllRefUpdates=always -C wt \
update-ref refs/worktree/worktree HEAD &&
cat >expect <<-EOF &&
HEAD
refs/heads/main
refs/heads/wt
refs/worktree/main
refs/worktree/per-worktree
EOF
git reflog list >actual &&
test_cmp expect actual &&
cat >expect <<-EOF &&
HEAD
refs/heads/main
refs/heads/wt
refs/worktree/per-worktree
refs/worktree/worktree
EOF
git -C wt reflog list >actual &&
test_cmp expect actual
)
'
test_expect_success 'reflog list returns error with additional args' '
cat >expect <<-EOF &&
error: list does not accept arguments: ${SQ}bogus${SQ}
EOF
test_must_fail git reflog list bogus 2>err &&
test_cmp expect err
'
test_expect_success 'reflog for symref with unborn target can be listed' '
test_when_finished "rm -rf repo" &&
git init repo &&
(
cd repo &&
test_commit A &&
git symbolic-ref HEAD refs/heads/unborn &&
cat >expect <<-EOF &&
HEAD
refs/heads/main
EOF
git reflog list >actual &&
test_cmp expect actual
)
'
test_expect_success 'reflog with invalid object ID can be listed' '
test_when_finished "rm -rf repo" &&
git init repo &&
(
cd repo &&
test_commit A &&
test-tool ref-store main update-ref msg refs/heads/missing \
$(test_oid deadbeef) "$ZERO_OID" REF_SKIP_OID_VERIFICATION &&
cat >expect <<-EOF &&
HEAD
refs/heads/main
refs/heads/missing
EOF
git reflog list >actual &&
test_cmp expect actual
)
'
test_done