From d0c93194ecde2886d4e90d7006c998ea9592d0a0 Mon Sep 17 00:00:00 2001 From: Cornelius Weig Date: Fri, 27 Jan 2017 11:09:46 +0100 Subject: [PATCH 1/4] config: add markup to core.logAllRefUpdates doc Signed-off-by: Cornelius Weig Signed-off-by: Junio C Hamano --- Documentation/config.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index af2ae4cc02..c7d8a01083 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -517,10 +517,10 @@ core.logAllRefUpdates:: "`$GIT_DIR/logs/`", by appending the new and old SHA-1, the date/time and the reason of the update, but only when the file exists. If this configuration - variable is set to true, missing "`$GIT_DIR/logs/`" + variable is set to `true`, missing "`$GIT_DIR/logs/`" file is automatically created for branch heads (i.e. under - refs/heads/), remote refs (i.e. under refs/remotes/), - note refs (i.e. under refs/notes/), and the symbolic ref HEAD. + `refs/heads/`), remote refs (i.e. under `refs/remotes/`), + note refs (i.e. under `refs/notes/`), and the symbolic ref `HEAD`. + This information can be used to determine what commit was the tip of a branch "2 days ago". From 341fb28621201c5e6c9d3fee5baf7c532fa8a618 Mon Sep 17 00:00:00 2001 From: Cornelius Weig Date: Fri, 27 Jan 2017 11:09:47 +0100 Subject: [PATCH 2/4] refs: add option core.logAllRefUpdates = always When core.logallrefupdates is true, we only create a new reflog for refs that are under certain well-known hierarchies. The reason is that we know that some hierarchies (like refs/tags) are not meant to change, and that unknown hierarchies might not want reflogs at all (e.g., a hypothetical refs/foo might be meant to change often and drop old history immediately). However, sometimes it is useful to override this decision and simply log for all refs, because the safety and audit trail is more important than the performance implications of keeping the log around. This patch introduces a new "always" mode for the core.logallrefupdates option which will log updates to everything under refs/, regardless where in the hierarchy it is (we still will not log things like ORIG_HEAD and FETCH_HEAD, which are known to be transient). Based-on-patch-by: Jeff King Signed-off-by: Cornelius Weig Reviewed-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/config.txt | 2 ++ Documentation/git-tag.txt | 3 ++- branch.c | 2 +- builtin/checkout.c | 11 +++++++---- builtin/init-db.c | 2 +- cache.h | 9 ++++++++- config.c | 7 ++++++- environment.c | 2 +- refs.c | 15 ++++++++++----- refs.h | 2 ++ refs/files-backend.c | 6 +++--- refs/refs-internal.h | 2 -- t/t1400-update-ref.sh | 37 +++++++++++++++++++++++++++++++++++++ t/t7004-tag.sh | 8 ++++++++ 14 files changed, 88 insertions(+), 20 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index c7d8a01083..d1fab67ca6 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -521,6 +521,8 @@ core.logAllRefUpdates:: file is automatically created for branch heads (i.e. under `refs/heads/`), remote refs (i.e. under `refs/remotes/`), note refs (i.e. under `refs/notes/`), and the symbolic ref `HEAD`. + If it is set to `always`, then a missing reflog is automatically + created for any ref under `refs/`. + This information can be used to determine what commit was the tip of a branch "2 days ago". diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 5055a96823..2ac25a9bb3 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -150,7 +150,8 @@ This option is only applicable when listing tags without annotation lines. 'strip' removes both whitespace and commentary. --create-reflog:: - Create a reflog for the tag. + Create a reflog for the tag. To globally enable reflogs for tags, see + `core.logAllRefUpdates` in linkgit:git-config[1]. :: The name of the tag to create, delete, or describe. diff --git a/branch.c b/branch.c index c431cbf6a9..b955d4f316 100644 --- a/branch.c +++ b/branch.c @@ -298,7 +298,7 @@ void create_branch(const char *name, const char *start_name, start_name); if (reflog) - log_all_ref_updates = 1; + log_all_ref_updates = LOG_REFS_NORMAL; if (!dont_change_ref) { struct ref_transaction *transaction; diff --git a/builtin/checkout.c b/builtin/checkout.c index bfe685c198..569912c3ab 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -612,22 +612,25 @@ static void update_refs_for_switch(const struct checkout_opts *opts, const char *old_desc, *reflog_msg; if (opts->new_branch) { if (opts->new_orphan_branch) { - if (opts->new_branch_log && !log_all_ref_updates) { + char *refname; + + refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch); + if (opts->new_branch_log && + !should_autocreate_reflog(refname)) { int ret; - char *refname; struct strbuf err = STRBUF_INIT; - refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch); ret = safe_create_reflog(refname, 1, &err); - free(refname); if (ret) { fprintf(stderr, _("Can not do reflog for '%s': %s\n"), opts->new_orphan_branch, err.buf); strbuf_release(&err); + free(refname); return; } strbuf_release(&err); } + free(refname); } else create_branch(opts->new_branch, new->name, diff --git a/builtin/init-db.c b/builtin/init-db.c index 76d68fad00..1d4d6a0078 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -262,7 +262,7 @@ static int create_default_files(const char *template_path, const char *work_tree = get_git_work_tree(); git_config_set("core.bare", "false"); /* allow template config file to override the default */ - if (log_all_ref_updates == -1) + if (log_all_ref_updates == LOG_REFS_UNSET) git_config_set("core.logallrefupdates", "true"); if (needs_work_tree_config(original_git_dir, work_tree)) git_config_set("core.worktree", work_tree); diff --git a/cache.h b/cache.h index 00a029af36..96eeaaf7fb 100644 --- a/cache.h +++ b/cache.h @@ -660,7 +660,6 @@ extern int minimum_abbrev, default_abbrev; extern int ignore_case; extern int assume_unchanged; extern int prefer_symlink_refs; -extern int log_all_ref_updates; extern int warn_ambiguous_refs; extern int warn_on_object_refname_ambiguity; extern const char *apply_default_whitespace; @@ -728,6 +727,14 @@ enum hide_dotfiles_type { }; extern enum hide_dotfiles_type hide_dotfiles; +enum log_refs_config { + LOG_REFS_UNSET = -1, + LOG_REFS_NONE = 0, + LOG_REFS_NORMAL, + LOG_REFS_ALWAYS +}; +extern enum log_refs_config log_all_ref_updates; + enum branch_track { BRANCH_TRACK_UNSPECIFIED = -1, BRANCH_TRACK_NEVER = 0, diff --git a/config.c b/config.c index b680f79732..c6b874a7bf 100644 --- a/config.c +++ b/config.c @@ -826,7 +826,12 @@ static int git_default_core_config(const char *var, const char *value) } if (!strcmp(var, "core.logallrefupdates")) { - log_all_ref_updates = git_config_bool(var, value); + if (value && !strcasecmp(value, "always")) + log_all_ref_updates = LOG_REFS_ALWAYS; + else if (git_config_bool(var, value)) + log_all_ref_updates = LOG_REFS_NORMAL; + else + log_all_ref_updates = LOG_REFS_NONE; return 0; } diff --git a/environment.c b/environment.c index 8a83101d04..c07fb17fb7 100644 --- a/environment.c +++ b/environment.c @@ -21,7 +21,6 @@ int ignore_case; int assume_unchanged; int prefer_symlink_refs; int is_bare_repository_cfg = -1; /* unspecified */ -int log_all_ref_updates = -1; /* unspecified */ int warn_ambiguous_refs = 1; int warn_on_object_refname_ambiguity = 1; int ref_paranoia = -1; @@ -64,6 +63,7 @@ int merge_log_config = -1; int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */ unsigned long pack_size_limit_cfg; enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY; +enum log_refs_config log_all_ref_updates = LOG_REFS_UNSET; #ifndef PROTECT_HFS_DEFAULT #define PROTECT_HFS_DEFAULT 0 diff --git a/refs.c b/refs.c index 9bd0bc177b..cd36b64ed9 100644 --- a/refs.c +++ b/refs.c @@ -638,12 +638,17 @@ int copy_reflog_msg(char *buf, const char *msg) int should_autocreate_reflog(const char *refname) { - if (!log_all_ref_updates) + switch (log_all_ref_updates) { + case LOG_REFS_ALWAYS: + return 1; + case LOG_REFS_NORMAL: + return starts_with(refname, "refs/heads/") || + starts_with(refname, "refs/remotes/") || + starts_with(refname, "refs/notes/") || + !strcmp(refname, "HEAD"); + default: return 0; - return starts_with(refname, "refs/heads/") || - starts_with(refname, "refs/remotes/") || - starts_with(refname, "refs/notes/") || - !strcmp(refname, "HEAD"); + } } int is_branch(const char *refname) diff --git a/refs.h b/refs.h index 6947843913..9fbff90e79 100644 --- a/refs.h +++ b/refs.h @@ -64,6 +64,8 @@ int read_ref(const char *refname, unsigned char *sha1); int ref_exists(const char *refname); +int should_autocreate_reflog(const char *refname); + int is_branch(const char *refname); extern int refs_init_db(struct strbuf *err); diff --git a/refs/files-backend.c b/refs/files-backend.c index f9023939d5..14b17a63f8 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -2682,7 +2682,7 @@ static int files_rename_ref(struct ref_store *ref_store, } flag = log_all_ref_updates; - log_all_ref_updates = 0; + log_all_ref_updates = LOG_REFS_NONE; if (write_ref_to_lockfile(lock, orig_sha1, &err) || commit_ref_update(refs, lock, orig_sha1, NULL, &err)) { error("unable to write current sha1 into %s: %s", oldrefname, err.buf); @@ -2835,8 +2835,8 @@ static int log_ref_write_1(const char *refname, const unsigned char *old_sha1, { int logfd, result, oflags = O_APPEND | O_WRONLY; - if (log_all_ref_updates < 0) - log_all_ref_updates = !is_bare_repository(); + if (log_all_ref_updates == LOG_REFS_UNSET) + log_all_ref_updates = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL; result = log_ref_setup(refname, logfile, err, flags & REF_FORCE_CREATE_REFLOG); diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 708b26082a..25444cf5b0 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -133,8 +133,6 @@ int verify_refname_available(const char *newname, */ int copy_reflog_msg(char *buf, const char *msg); -int should_autocreate_reflog(const char *refname); - /** * Information needed for a single ref update. Set new_sha1 to the new * value or to null_sha1 to delete the ref. To check the old value diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index d4fb977060..b9084cacb2 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -93,6 +93,42 @@ test_expect_success 'update-ref creates reflogs with --create-reflog' ' git reflog exists $outside ' +test_expect_success 'core.logAllRefUpdates=true does not create reflog by default' ' + test_config core.logAllRefUpdates true && + test_when_finished "git update-ref -d $outside" && + git update-ref $outside $A && + git rev-parse $A >expect && + git rev-parse $outside >actual && + test_cmp expect actual && + test_must_fail git reflog exists $outside +' + +test_expect_success 'core.logAllRefUpdates=always creates reflog by default' ' + test_config core.logAllRefUpdates always && + test_when_finished "git update-ref -d $outside" && + git update-ref $outside $A && + git rev-parse $A >expect && + git rev-parse $outside >actual && + test_cmp expect actual && + git reflog exists $outside +' + +test_expect_success 'core.logAllRefUpdates=always creates no reflog for ORIG_HEAD' ' + test_config core.logAllRefUpdates always && + git update-ref ORIG_HEAD $A && + test_must_fail git reflog exists ORIG_HEAD +' + +test_expect_success '--no-create-reflog overrides core.logAllRefUpdates=always' ' + test_config core.logAllRefUpdates true && + test_when_finished "git update-ref -d $outside" && + git update-ref --no-create-reflog $outside $A && + git rev-parse $A >expect && + git rev-parse $outside >actual && + test_cmp expect actual && + test_must_fail git reflog exists $outside +' + test_expect_success \ "create $m (by HEAD)" \ "git update-ref HEAD $A && @@ -501,6 +537,7 @@ test_expect_success 'stdin does not create reflogs by default' ' ' test_expect_success 'stdin creates reflogs with --create-reflog' ' + test_when_finished "git update-ref -d $outside" && echo "create $outside $m" >stdin && git update-ref --create-reflog --stdin expect && diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 1cfa8a21d2..1bf622de23 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -71,6 +71,7 @@ test_expect_success 'creating a tag for an unknown revision should fail' ' # commit used in the tests, test_tick is also called here to freeze the date: test_expect_success 'creating a tag using default HEAD should succeed' ' + test_config core.logAllRefUpdates true && test_tick && echo foo >foo && git add foo && @@ -90,6 +91,13 @@ test_expect_success '--create-reflog does not create reflog on failure' ' test_must_fail git reflog exists refs/tags/mytag ' +test_expect_success 'option core.logAllRefUpdates=always creates reflog' ' + test_when_finished "git tag -d tag_with_reflog" && + test_config core.logAllRefUpdates always && + git tag tag_with_reflog && + git reflog exists refs/tags/tag_with_reflog +' + test_expect_success 'listing all tags if one exists should succeed' ' git tag -l && git tag From b1421a43d5e28d71dc89b99877c3fbcb845aae08 Mon Sep 17 00:00:00 2001 From: Cornelius Weig Date: Fri, 27 Jan 2017 11:09:48 +0100 Subject: [PATCH 3/4] update-ref: add test cases for bare repository The default behavior of update-ref to create reflogs differs in repositories with worktree and bare ones. The existing tests cover only the behavior of repositories with worktree. This commit adds tests that assert the correct behavior in bare repositories for update-ref. Two cases are covered: - If core.logAllRefUpdates is not set, no reflogs should be created - If core.logAllRefUpdates is true, reflogs should be created Signed-off-by: Cornelius Weig Signed-off-by: Junio C Hamano --- t/t1400-update-ref.sh | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index b9084cacb2..b0ffc0b573 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -8,23 +8,33 @@ test_description='Test git update-ref and basic ref logging' Z=$_z40 -test_expect_success setup ' +m=refs/heads/master +n_dir=refs/heads/gu +n=$n_dir/fixes +outside=refs/foo +bare=bare-repo +create_test_commits () +{ + prfx="$1" for name in A B C D E F do test_tick && T=$(git write-tree) && sha1=$(echo $name | git commit-tree $T) && - eval $name=$sha1 + eval $prfx$name=$sha1 done +} +test_expect_success setup ' + create_test_commits "" && + mkdir $bare && + cd $bare && + git init --bare && + create_test_commits "bare" && + cd - ' -m=refs/heads/master -n_dir=refs/heads/gu -n=$n_dir/fixes -outside=refs/foo - test_expect_success \ "create $m" \ "git update-ref $m $A && @@ -93,6 +103,25 @@ test_expect_success 'update-ref creates reflogs with --create-reflog' ' git reflog exists $outside ' +test_expect_success 'creates no reflog in bare repository' ' + git -C $bare update-ref $m $bareA && + git -C $bare rev-parse $bareA >expect && + git -C $bare rev-parse $m >actual && + test_cmp expect actual && + test_must_fail git -C $bare reflog exists $m +' + +test_expect_success 'core.logAllRefUpdates=true creates reflog in bare repository' ' + test_when_finished "git -C $bare config --unset core.logAllRefUpdates && \ + rm $bare/logs/$m" && + git -C $bare config core.logAllRefUpdates true && + git -C $bare update-ref $m $bareB && + git -C $bare rev-parse $bareB >expect && + git -C $bare rev-parse $m >actual && + test_cmp expect actual && + git -C $bare reflog exists $m +' + test_expect_success 'core.logAllRefUpdates=true does not create reflog by default' ' test_config core.logAllRefUpdates true && test_when_finished "git update-ref -d $outside" && From 67c70bd930538890d8eccace72fb0e0a0e565447 Mon Sep 17 00:00:00 2001 From: Cornelius Weig Date: Wed, 1 Feb 2017 23:07:27 +0100 Subject: [PATCH 4/4] doc: add note about ignoring '--no-create-reflog' The commands git-branch and git-tag accept the '--create-reflog' option, and create reflog even when core.logallrefupdates configuration is explicitly set not to. On the other hand, the negated form '--no-create-reflog' is accepted as a valid option but has no effect (other than overriding an earlier '--create-reflog' on the command line). This silent noop may puzzle users. To communicate that this is a known limitation, add a short note in the manuals for git-branch and git-tag. Signed-off-by: Cornelius Weig Helped-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-branch.txt | 3 +++ Documentation/git-tag.txt | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 5516a47b54..28d46cc03b 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -91,6 +91,9 @@ OPTIONS based sha1 expressions such as "@\{yesterday}". Note that in non-bare repositories, reflogs are usually enabled by default by the `core.logallrefupdates` config option. + The negated form `--no-create-reflog` only overrides an earlier + `--create-reflog`, but currently does not negate the setting of + `core.logallrefupdates`. -f:: --force:: diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 2ac25a9bb3..1d2e9a05f6 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -152,6 +152,9 @@ This option is only applicable when listing tags without annotation lines. --create-reflog:: Create a reflog for the tag. To globally enable reflogs for tags, see `core.logAllRefUpdates` in linkgit:git-config[1]. + The negated form `--no-create-reflog` only overrides an earlier + `--create-reflog`, but currently does not negate the setting of + `core.logallrefupdates`. :: The name of the tag to create, delete, or describe.