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()` directlymaint
						commit
						510a27e9e4
					
				|  | @ -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. | ||||
|  |  | |||
|  | @ -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; | ||||
|  | ||||
|  |  | |||
|  | @ -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), | ||||
|  |  | |||
|  | @ -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) { | ||||
| 				continue; | ||||
| 			} else if (ret > 0) { | ||||
| 				if (pop_level(iter) == 0) | ||||
| 					return dir_iterator_abort(dir_iterator); | ||||
| 			} | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 		if (is_dot_or_dotdot(de->d_name)) | ||||
| 			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); | ||||
|  |  | |||
|  | @ -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
								
								
								
								
							
							
						
						
									
										27
									
								
								refs.c
								
								
								
								
							|  | @ -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); | ||||
| } | ||||
|  |  | |||
							
								
								
									
										11
									
								
								refs.h
								
								
								
								
							
							
						
						
									
										11
									
								
								refs.h
								
								
								
								
							|  | @ -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 | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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); | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  |  | |||
|  | @ -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; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		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); | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -221,13 +221,19 @@ 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, | ||||
| 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) | ||||
| { | ||||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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 | ||||
| ' | ||||
|  |  | |||
|  | @ -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 | ||||
| ' | ||||
|  |  | |||
|  | @ -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 | ||||
| ' | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Junio C Hamano
						Junio C Hamano