diff options
-rw-r--r-- | Documentation/git-reflog.txt | 3 | ||||
-rw-r--r-- | builtin/fsck.c | 4 | ||||
-rw-r--r-- | builtin/reflog.c | 37 | ||||
-rw-r--r-- | dir-iterator.c | 105 | ||||
-rw-r--r-- | dir-iterator.h | 3 | ||||
-rw-r--r-- | refs.c | 27 | ||||
-rw-r--r-- | refs.h | 11 | ||||
-rw-r--r-- | refs/debug.c | 3 | ||||
-rw-r--r-- | refs/files-backend.c | 55 | ||||
-rw-r--r-- | refs/iterator.c | 69 | ||||
-rw-r--r-- | refs/packed-backend.c | 2 | ||||
-rw-r--r-- | refs/ref-cache.c | 2 | ||||
-rw-r--r-- | refs/refs-internal.h | 27 | ||||
-rw-r--r-- | refs/reftable-backend.c | 67 | ||||
-rw-r--r-- | revision.c | 4 | ||||
-rw-r--r-- | t/helper/test-ref-store.c | 18 | ||||
-rwxr-xr-x | t/t0600-reffiles-backend.sh | 24 | ||||
-rwxr-xr-x | t/t1405-main-ref-store.sh | 8 | ||||
-rwxr-xr-x | t/t1406-submodule-ref-store.sh | 8 | ||||
-rwxr-xr-x | t/t1410-reflog.sh | 108 |
20 files changed, 380 insertions, 205 deletions
diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt index ec64cbff4c..a929c52982 100644 --- a/Documentation/git-reflog.txt +++ b/Documentation/git-reflog.txt @@ -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. diff --git a/builtin/fsck.c b/builtin/fsck.c index a7cf94f67e..f892487c9b 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -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; diff --git a/builtin/reflog.c b/builtin/reflog.c index 2c3369fca5..060eb3377e 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -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), diff --git a/dir-iterator.c b/dir-iterator.c index 278b04243a..de619846f2 100644 --- a/dir-iterator.c +++ b/dir-iterator.c @@ -2,11 +2,20 @@ #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; - } - if (is_dot_or_dotdot(de->d_name)) - 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; + } - if (prepare_next_entry_data(iter, de)) { + name = level->entries.items[level->entries_idx++].string; + } + + 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); diff --git a/dir-iterator.h b/dir-iterator.h index 479e1ec784..6d438809b6 100644 --- a/dir-iterator.h +++ b/dir-iterator.h @@ -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: */ @@ -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 @@ cleanup: 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); } @@ -535,11 +535,18 @@ int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_dat 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 diff --git a/refs/debug.c b/refs/debug.c index 634681ca44..c7531b17f0 100644 --- a/refs/debug.c +++ b/refs/debug.c @@ -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); diff --git a/refs/files-backend.c b/refs/files-backend.c index 75dcc21ecb..6f98168a81 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -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] == '.') - continue; - if (ends_with(diter->basename, ".lock")) + if (check_refname_format(diter->basename, + REFNAME_ALLOW_ONELEVEL)) 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); } } diff --git a/refs/iterator.c b/refs/iterator.c index 6b680f610e..9db8b056d5 100644 --- a/refs/iterator.c +++ b/refs/iterator.c @@ -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); diff --git a/refs/packed-backend.c b/refs/packed-backend.c index a499a91c7e..4e826c05ff 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -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); diff --git a/refs/ref-cache.c b/refs/ref-cache.c index a372a00941..9f9797209a 100644 --- a/refs/ref-cache.c +++ b/refs/ref-cache.c @@ -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; diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 83e0f0bba3..a9b6e887f8 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -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; @@ -387,14 +380,21 @@ typedef enum iterator_selection ref_iterator_select_fn( 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 diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index a14f2ad7f4..6c11c4a5e3 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -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 @@ done: 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, diff --git a/revision.c b/revision.c index 2424c9bd67..ac45c6d8f2 100644 --- a/revision.c +++ b/revision.c @@ -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; diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index 702ec1f128..7a0f6cac53 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -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) diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh index 485481d6b4..09e71b2b0f 100755 --- a/t/t0600-reffiles-backend.sh +++ b/t/t0600-reffiles-backend.sh @@ -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 ' diff --git a/t/t1405-main-ref-store.sh b/t/t1405-main-ref-store.sh index 1183232a72..a6bcd62ab6 100755 --- a/t/t1405-main-ref-store.sh +++ b/t/t1405-main-ref-store.sh @@ -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 ' diff --git a/t/t1406-submodule-ref-store.sh b/t/t1406-submodule-ref-store.sh index e6a7f7334b..c01f0f14a1 100755 --- a/t/t1406-submodule-ref-store.sh +++ b/t/t1406-submodule-ref-store.sh @@ -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 ' diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index d2f5f42e67..5bf883f1e3 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -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 |