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 <peff@peff.net> Signed-off-by: Cornelius Weig <cornelius.weig@tngtech.com> Reviewed-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Cornelius Weig committed Jan 27, 2017 at 11:09 UTC 341fb28621201c5e6c9d3fee5baf7c532fa8a618
14 files changed +88 -20
Documentation/config.txt
+2
@@ -521,6 +521,8 @@ core.logAllRefUpdates::
521 file is automatically created for branch heads (i.e. under
522 `refs/heads/`), remote refs (i.e. under `refs/remotes/`),
523 note refs (i.e. under `refs/notes/`), and the symbolic ref `HEAD`.
524 + If it is set to `always`, then a missing reflog is automatically
525 + created for any ref under `refs/`.
526 +
527 This information can be used to determine what commit
528 was the tip of a branch "2 days ago".
Documentation/git-tag.txt
+2 -1
@@ -150,7 +150,8 @@ This option is only applicable when listing tags without annotation lines.
150 'strip' removes both whitespace and commentary.
151
152 --create-reflog::
153 - Create a reflog for the tag.
153 + Create a reflog for the tag. To globally enable reflogs for tags, see
154 + `core.logAllRefUpdates` in linkgit:git-config[1].
155
156 <tagname>::
157 The name of the tag to create, delete, or describe.
branch.c
+1 -1
@@ -298,7 +298,7 @@ void create_branch(const char *name, const char *start_name,
298 start_name);
299
300 if (reflog)
301 - log_all_ref_updates = 1;
301 + log_all_ref_updates = LOG_REFS_NORMAL;
302
303 if (!dont_change_ref) {
304 struct ref_transaction *transaction;
builtin/checkout.c
+7 -4
@@ -612,22 +612,25 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
612 const char *old_desc, *reflog_msg;
613 if (opts->new_branch) {
614 if (opts->new_orphan_branch) {
615 - if (opts->new_branch_log && !log_all_ref_updates) {
615 + char *refname;
616 +
617 + refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch);
618 + if (opts->new_branch_log &&
619 + !should_autocreate_reflog(refname)) {
620 int ret;
617 - char *refname;
621 struct strbuf err = STRBUF_INIT;
622
620 - refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch);
623 ret = safe_create_reflog(refname, 1, &err);
622 - free(refname);
624 if (ret) {
625 fprintf(stderr, _("Can not do reflog for '%s': %s\n"),
626 opts->new_orphan_branch, err.buf);
627 strbuf_release(&err);
628 + free(refname);
629 return;
630 }
631 strbuf_release(&err);
632 }
633 + free(refname);
634 }
635 else
636 create_branch(opts->new_branch, new->name,
builtin/init-db.c
+1 -1
@@ -262,7 +262,7 @@ static int create_default_files(const char *template_path,
262 const char *work_tree = get_git_work_tree();
263 git_config_set("core.bare", "false");
264 /* allow template config file to override the default */
265 - if (log_all_ref_updates == -1)
265 + if (log_all_ref_updates == LOG_REFS_UNSET)
266 git_config_set("core.logallrefupdates", "true");
267 if (needs_work_tree_config(original_git_dir, work_tree))
268 git_config_set("core.worktree", work_tree);
cache.h
+8 -1
@@ -660,7 +660,6 @@ extern int minimum_abbrev, default_abbrev;
660 extern int ignore_case;
661 extern int assume_unchanged;
662 extern int prefer_symlink_refs;
663 -extern int log_all_ref_updates;
663 extern int warn_ambiguous_refs;
664 extern int warn_on_object_refname_ambiguity;
665 extern const char *apply_default_whitespace;
@@ -728,6 +727,14 @@ enum hide_dotfiles_type {
727 };
728 extern enum hide_dotfiles_type hide_dotfiles;
729
730 +enum log_refs_config {
731 + LOG_REFS_UNSET = -1,
732 + LOG_REFS_NONE = 0,
733 + LOG_REFS_NORMAL,
734 + LOG_REFS_ALWAYS
735 +};
736 +extern enum log_refs_config log_all_ref_updates;
737 +
738 enum branch_track {
739 BRANCH_TRACK_UNSPECIFIED = -1,
740 BRANCH_TRACK_NEVER = 0,
config.c
+6 -1
@@ -826,7 +826,12 @@ static int git_default_core_config(const char *var, const char *value)
826 }
827
828 if (!strcmp(var, "core.logallrefupdates")) {
829 - log_all_ref_updates = git_config_bool(var, value);
829 + if (value && !strcasecmp(value, "always"))
830 + log_all_ref_updates = LOG_REFS_ALWAYS;
831 + else if (git_config_bool(var, value))
832 + log_all_ref_updates = LOG_REFS_NORMAL;
833 + else
834 + log_all_ref_updates = LOG_REFS_NONE;
835 return 0;
836 }
837
environment.c
+1 -1
@@ -21,7 +21,6 @@ int ignore_case;
21 int assume_unchanged;
22 int prefer_symlink_refs;
23 int is_bare_repository_cfg = -1; /* unspecified */
24 -int log_all_ref_updates = -1; /* unspecified */
24 int warn_ambiguous_refs = 1;
25 int warn_on_object_refname_ambiguity = 1;
26 int ref_paranoia = -1;
@@ -64,6 +63,7 @@ int merge_log_config = -1;
63 int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
64 unsigned long pack_size_limit_cfg;
65 enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
66 +enum log_refs_config log_all_ref_updates = LOG_REFS_UNSET;
67
68 #ifndef PROTECT_HFS_DEFAULT
69 #define PROTECT_HFS_DEFAULT 0
refs.c
+10 -5
@@ -638,12 +638,17 @@ int copy_reflog_msg(char *buf, const char *msg)
638
639 int should_autocreate_reflog(const char *refname)
640 {
641 - if (!log_all_ref_updates)
641 + switch (log_all_ref_updates) {
642 + case LOG_REFS_ALWAYS:
643 + return 1;
644 + case LOG_REFS_NORMAL:
645 + return starts_with(refname, "refs/heads/") ||
646 + starts_with(refname, "refs/remotes/") ||
647 + starts_with(refname, "refs/notes/") ||
648 + !strcmp(refname, "HEAD");
649 + default:
650 return 0;
643 - return starts_with(refname, "refs/heads/") ||
644 - starts_with(refname, "refs/remotes/") ||
645 - starts_with(refname, "refs/notes/") ||
646 - !strcmp(refname, "HEAD");
651 + }
652 }
653
654 int is_branch(const char *refname)
refs.h
+2
@@ -64,6 +64,8 @@ int read_ref(const char *refname, unsigned char *sha1);
64
65 int ref_exists(const char *refname);
66
67 +int should_autocreate_reflog(const char *refname);
68 +
69 int is_branch(const char *refname);
70
71 extern int refs_init_db(struct strbuf *err);
refs/files-backend.c
+3 -3
@@ -2682,7 +2682,7 @@ static int files_rename_ref(struct ref_store *ref_store,
2682 }
2683
2684 flag = log_all_ref_updates;
2685 - log_all_ref_updates = 0;
2685 + log_all_ref_updates = LOG_REFS_NONE;
2686 if (write_ref_to_lockfile(lock, orig_sha1, &err) ||
2687 commit_ref_update(refs, lock, orig_sha1, NULL, &err)) {
2688 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,
2835 {
2836 int logfd, result, oflags = O_APPEND | O_WRONLY;
2837
2838 - if (log_all_ref_updates < 0)
2839 - log_all_ref_updates = !is_bare_repository();
2838 + if (log_all_ref_updates == LOG_REFS_UNSET)
2839 + log_all_ref_updates = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL;
2840
2841 result = log_ref_setup(refname, logfile, err, flags & REF_FORCE_CREATE_REFLOG);
2842
refs/refs-internal.h
-2
@@ -133,8 +133,6 @@ int verify_refname_available(const char *newname,
133 */
134 int copy_reflog_msg(char *buf, const char *msg);
135
136 -int should_autocreate_reflog(const char *refname);
137 -
136 /**
137 * Information needed for a single ref update. Set new_sha1 to the new
138 * value or to null_sha1 to delete the ref. To check the old value
t/t1400-update-ref.sh
+37
@@ -93,6 +93,42 @@ test_expect_success 'update-ref creates reflogs with --create-reflog' '
93 git reflog exists $outside
94 '
95
96 +test_expect_success 'core.logAllRefUpdates=true does not create reflog by default' '
97 + test_config core.logAllRefUpdates true &&
98 + test_when_finished "git update-ref -d $outside" &&
99 + git update-ref $outside $A &&
100 + git rev-parse $A >expect &&
101 + git rev-parse $outside >actual &&
102 + test_cmp expect actual &&
103 + test_must_fail git reflog exists $outside
104 +'
105 +
106 +test_expect_success 'core.logAllRefUpdates=always creates reflog by default' '
107 + test_config core.logAllRefUpdates always &&
108 + test_when_finished "git update-ref -d $outside" &&
109 + git update-ref $outside $A &&
110 + git rev-parse $A >expect &&
111 + git rev-parse $outside >actual &&
112 + test_cmp expect actual &&
113 + git reflog exists $outside
114 +'
115 +
116 +test_expect_success 'core.logAllRefUpdates=always creates no reflog for ORIG_HEAD' '
117 + test_config core.logAllRefUpdates always &&
118 + git update-ref ORIG_HEAD $A &&
119 + test_must_fail git reflog exists ORIG_HEAD
120 +'
121 +
122 +test_expect_success '--no-create-reflog overrides core.logAllRefUpdates=always' '
123 + test_config core.logAllRefUpdates true &&
124 + test_when_finished "git update-ref -d $outside" &&
125 + git update-ref --no-create-reflog $outside $A &&
126 + git rev-parse $A >expect &&
127 + git rev-parse $outside >actual &&
128 + test_cmp expect actual &&
129 + test_must_fail git reflog exists $outside
130 +'
131 +
132 test_expect_success \
133 "create $m (by HEAD)" \
134 "git update-ref HEAD $A &&
@@ -501,6 +537,7 @@ test_expect_success 'stdin does not create reflogs by default' '
537 '
538
539 test_expect_success 'stdin creates reflogs with --create-reflog' '
540 + test_when_finished "git update-ref -d $outside" &&
541 echo "create $outside $m" >stdin &&
542 git update-ref --create-reflog --stdin <stdin &&
543 git rev-parse $m >expect &&
t/t7004-tag.sh
+8
@@ -71,6 +71,7 @@ test_expect_success 'creating a tag for an unknown revision should fail' '
71
72 # commit used in the tests, test_tick is also called here to freeze the date:
73 test_expect_success 'creating a tag using default HEAD should succeed' '
74 + test_config core.logAllRefUpdates true &&
75 test_tick &&
76 echo foo >foo &&
77 git add foo &&
@@ -90,6 +91,13 @@ test_expect_success '--create-reflog does not create reflog on failure' '
91 test_must_fail git reflog exists refs/tags/mytag
92 '
93
94 +test_expect_success 'option core.logAllRefUpdates=always creates reflog' '
95 + test_when_finished "git tag -d tag_with_reflog" &&
96 + test_config core.logAllRefUpdates always &&
97 + git tag tag_with_reflog &&
98 + git reflog exists refs/tags/tag_with_reflog
99 +'
100 +
101 test_expect_success 'listing all tags if one exists should succeed' '
102 git tag -l &&
103 git tag