{"thread":{"id":"4180","subject":"[RFC 4/5] Log ref updates to logs/refs/<ref>","startedAt":"2006-05-17T09:55:40Z","lastAt":"2006-05-17T09:55:40Z","messageCount":1,"participants":["Shawn Pearce"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"20126","messageId":"20060517095540.GE28529@spearce.org","threadId":"4180","inReplyTo":null,"subject":"[RFC 4/5] Log ref updates to logs/refs/<ref>","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-05-17T09:55:40Z","receivedAt":"2006-05-17T09:55:40Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"If config parameter core.logAllRefUpdates is true or the log\nfile already exists then append a line to \".git/logs/refs/<ref>\"\nwhenever git-update-ref <ref> is executed.  Each log line contains\nthe following information:\n\n  oldsha1 <SP> newsha1 <SP> committer <LF>\n\nwhere committer is the current user, date, time and timezone in\nthe standard GIT ident format.  If the caller is unable to append\nto the log file then git-update-ref will fail without updating <ref>.\n\nAn optional message may be included in the log line with the -m flag.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n\n---\n\n Documentation/config.txt         |    8 +++\n Documentation/git-update-ref.txt |   26 +++++++++\n cache.h                          |    1 \n config.c                         |    5 ++\n environment.c                    |    1 \n refs.c                           |   56 +++++++++++++++++++\n refs.h                           |    1 \n t/t1400-update-ref.sh            |  112 ++++++++++++++++++++++++++++++++++++++\n 8 files changed, 210 insertions(+), 0 deletions(-)\n create mode 100644 t/t1400-update-ref.sh\n\n1b91f255386d1120bdcb434603993156c9fdde71\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex d1a4bec..e178ee2 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -70,6 +70,14 @@ core.preferSymlinkRefs::\n \tThis is sometimes needed to work with old scripts that\n \texpect HEAD to be a symbolic link.\n \n+core.logAllRefUpdates::\n+\tIf true, `git-update-ref` will append a line to\n+\t\"$GIT_DIR/logs/<ref>\" listing the new SHA1 and the date/time\n+\tof the update.\tIf the file does not exist it will be\n+\tcreated automatically.\tThis information can be used to\n+\tdetermine what commit was the tip of a branch \"2 days ago\".\n+\tThis value is false by default (no logging).\n+\n core.repositoryFormatVersion::\n \tInternal variable identifying the repository format and layout\n \tversion.\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex f0e710a..dfbd886 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -49,6 +49,32 @@ for reading but not for writing (so we'l\n ref symlink to some other tree, if you have copied a whole\n archive by creating a symlink tree).\n \n+Logging Updates\n+---------------\n+If config parameter \"core.logAllRefUpdates\" is true or the file\n+\"$GIT_DIR/logs/<ref>\" exists then `git-update-ref` will append\n+a line to the log file \"$GIT_DIR/logs/<ref>\" (dereferencing all\n+symbolic refs before creating the log name) describing the change\n+in ref value.  Log lines are formatted as:\n+\n+    . oldsha1 SP newsha1 SP committer LF\n++\n+Where \"oldsha1\" is the 40 character hexadecimal value previously\n+stored in <ref>, \"newsha1\" is the 40 character hexadecimal value of\n+<newvalue> and \"committer\" is the committer's name, email address\n+and date in the standard GIT committer ident format.\n+\n+Optionally with -m:\n+\n+    . oldsha1 SP newsha1 SP committer TAB message LF\n++\n+Where all fields are as described above and \"message\" is the\n+value supplied to the -m option.\n+\n+An update will fail (without changing <ref>) if the current user is\n+unable to create a new log file, append to the existing log file\n+or does not have committer information available.\n+\n Author\n ------\n Written by Linus Torvalds <torvalds@osdl.org>.\ndiff --git a/cache.h b/cache.h\nindex b1300cd..82adbba 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -171,6 +171,7 @@ extern void rollback_index_file(struct c\n extern int trust_executable_bit;\n extern int assume_unchanged;\n extern int prefer_symlink_refs;\n+extern int log_all_ref_updates;\n extern int warn_ambiguous_refs;\n extern int diff_rename_limit_default;\n extern int shared_repository;\ndiff --git a/config.c b/config.c\nindex 0248c6d..2ae6153 100644\n--- a/config.c\n+++ b/config.c\n@@ -269,6 +269,11 @@ int git_default_config(const char *var, \n \t\treturn 0;\n \t}\n \n+\tif (!strcmp(var, \"core.logallrefupdates\")) {\n+\t\tlog_all_ref_updates = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \tif (!strcmp(var, \"core.warnambiguousrefs\")) {\n \t\twarn_ambiguous_refs = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/environment.c b/environment.c\nindex 444c99e..2e79eab 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -14,6 +14,7 @@ char git_default_name[MAX_GITNAME];\n int trust_executable_bit = 1;\n int assume_unchanged = 0;\n int prefer_symlink_refs = 0;\n+int log_all_ref_updates = 0;\n int warn_ambiguous_refs = 1;\n int repository_format_version = 0;\n char git_commit_encoding[MAX_ENCODING_LENGTH] = \"utf-8\";\ndiff --git a/refs.c b/refs.c\nindex 91c8c44..4be75a5 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -302,6 +302,7 @@ static struct ref_lock* lock_ref_sha1_ba\n \n \tlock->ref_file = strdup(path);\n \tlock->lock_file = strdup(mkpath(\"%s.lock\", lock->ref_file));\n+\tlock->log_file = strdup(git_path(\"logs/%s\", lock->ref_file + plen));\n \n \tif (safe_create_leading_directories(lock->lock_file))\n \t\tdie(\"unable to create directory for %s\", lock->lock_file);\n@@ -343,9 +344,60 @@ void unlock_ref (struct ref_lock *lock)\n \t\tfree(lock->ref_file);\n \tif (lock->lock_file)\n \t\tfree(lock->lock_file);\n+\tif (lock->log_file)\n+\t\tfree(lock->log_file);\n \tfree(lock);\n }\n \n+static int log_ref_write(struct ref_lock *lock,\n+\tconst unsigned char *sha1, const char *msg)\n+{\n+\tint logfd, written, oflags = O_APPEND | O_WRONLY;\n+\tunsigned maxlen, len;\n+\tchar *logrec;\n+\tconst char *comitter;\n+\n+\tif (log_all_ref_updates) {\n+\t\tif (safe_create_leading_directories(lock->log_file) < 0)\n+\t\t\treturn error(\"unable to create directory for %s\",\n+\t\t\t\tlock->log_file);\n+\t\toflags |= O_CREAT;\n+\t}\n+\n+\tlogfd = open(lock->log_file, oflags, 0666);\n+\tif (logfd < 0) {\n+\t\tif (!log_all_ref_updates && errno == ENOENT)\n+\t\t\treturn 0;\n+\t\treturn error(\"Unable to append to %s: %s\",\n+\t\t\tlock->log_file, strerror(errno));\n+\t}\n+\n+\tsetup_ident();\n+\tcomitter = git_committer_info(1);\n+\tif (msg) {\n+\t\tmaxlen = strlen(comitter) + strlen(msg) + 2*40 + 5;\n+\t\tlogrec = xmalloc(maxlen);\n+\t\tlen = snprintf(logrec, maxlen, \"%s %s %s\\t%s\\n\",\n+\t\t\tsha1_to_hex(lock->old_sha1),\n+\t\t\tsha1_to_hex(sha1),\n+\t\t\tcomitter,\n+\t\t\tmsg);\n+\t} else {\n+\t\tmaxlen = strlen(comitter) + 2*40 + 4;\n+\t\tlogrec = xmalloc(maxlen);\n+\t\tlen = snprintf(logrec, maxlen, \"%s %s %s\\n\",\n+\t\t\tsha1_to_hex(lock->old_sha1),\n+\t\t\tsha1_to_hex(sha1),\n+\t\t\tcomitter);\n+\t}\n+\twritten = len <= maxlen ? write(logfd, logrec, len) : -1;\n+\tfree(logrec);\n+\tclose(logfd);\n+\tif (written != len)\n+\t\treturn error(\"Unable to append to %s\", lock->log_file);\n+\treturn 0;\n+}\n+\n int write_ref_sha1(struct ref_lock *lock,\n \tconst unsigned char *sha1, const char *logmsg)\n {\n@@ -364,6 +416,10 @@ int write_ref_sha1(struct ref_lock *lock\n \t\tunlock_ref(lock);\n \t\treturn -1;\n \t}\n+\tif (log_ref_write(lock, sha1, logmsg) < 0) {\n+\t\tunlock_ref(lock);\n+\t\treturn -1;\n+\t}\n \tif (rename(lock->lock_file, lock->ref_file) < 0) {\n \t\terror(\"Couldn't set %s\", lock->ref_file);\n \t\tunlock_ref(lock);\ndiff --git a/refs.h b/refs.h\nindex b7e9df2..43831e9 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -4,6 +4,7 @@ #define REFS_H\n struct ref_lock {\n \tchar *ref_file;\n \tchar *lock_file;\n+\tchar *log_file;\n \tunsigned char old_sha1[20];\n \tint lock_fd;\n };\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nnew file mode 100644\nindex 0000000..f338c53\n--- /dev/null\n+++ b/t/t1400-update-ref.sh\n@@ -0,0 +1,112 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2006 Shawn Pearce\n+#\n+\n+test_description='Test git-update-ref and basic ref logging'\n+. ./test-lib.sh\n+\n+Z=0000000000000000000000000000000000000000\n+A=1111111111111111111111111111111111111111\n+B=2222222222222222222222222222222222222222\n+m=refs/heads/master\n+\n+test_expect_success \\\n+\t\"create $m\" \\\n+\t'git-update-ref $m $A &&\n+\t test $A = $(cat .git/$m)'\n+test_expect_success \\\n+\t\"create $m\" \\\n+\t'git-update-ref $m $B $A &&\n+\t test $B = $(cat .git/$m)'\n+rm -f .git/$m\n+\n+test_expect_success \\\n+\t\"create $m (by HEAD)\" \\\n+\t'git-update-ref HEAD $A &&\n+\t test $A = $(cat .git/$m)'\n+test_expect_success \\\n+\t\"create $m (by HEAD)\" \\\n+\t'git-update-ref HEAD $B $A &&\n+\t test $B = $(cat .git/$m)'\n+rm -f .git/$m\n+\n+test_expect_failure \\\n+\t'(not) create HEAD with old sha1' \\\n+\t'git-update-ref HEAD $A $B'\n+test_expect_failure \\\n+\t\"(not) prior created .git/$m\" \\\n+\t'test -f .git/$m'\n+rm -f .git/$m\n+\n+test_expect_success \\\n+\t\"create HEAD\" \\\n+\t'git-update-ref HEAD $A'\n+test_expect_failure \\\n+\t'(not) change HEAD with wrong SHA1' \\\n+\t'git-update-ref HEAD $B $Z'\n+test_expect_failure \\\n+\t\"(not) changed .git/$m\" \\\n+\t'test $B = $(cat .git/$m)'\n+rm -f .git/$m\n+\n+mkdir -p .git/logs/refs/heads\n+touch .git/logs/refs/heads/master\n+test_expect_success \\\n+\t\"create $m (logged by touch)\" \\\n+\t'GIT_COMMITTER_DATE=\"2005-05-26 23:30\" \\\n+\t git-update-ref HEAD $A -m \"Initial Creation\" &&\n+\t test $A = $(cat .git/$m)'\n+test_expect_success \\\n+\t\"update $m (logged by touch)\" \\\n+\t'GIT_COMMITTER_DATE=\"2005-05-26 23:31\" \\\n+\t git-update-ref HEAD $B $A -m \"Switch\" &&\n+\t test $B = $(cat .git/$m)'\n+test_expect_success \\\n+\t\"set $m (logged by touch)\" \\\n+\t'GIT_COMMITTER_DATE=\"2005-05-26 23:41\" \\\n+\t git-update-ref HEAD $A &&\n+\t test $A = $(cat .git/$m)'\n+\n+cat >expect <<EOF\n+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000\tInitial Creation\n+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150260 +0000\tSwitch\n+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000\n+EOF\n+test_expect_success \\\n+\t\"verifying $m's log\" \\\n+\t'diff expect .git/logs/$m'\n+rm -rf .git/$m .git/logs expect\n+\n+test_expect_success \\\n+\t'enable core.logAllRefUpdates' \\\n+\t'git-repo-config core.logAllRefUpdates true &&\n+\t test true = $(git-repo-config --bool --get core.logAllRefUpdates)'\n+\n+test_expect_success \\\n+\t\"create $m (logged by config)\" \\\n+\t'GIT_COMMITTER_DATE=\"2005-05-26 23:32\" \\\n+\t git-update-ref HEAD $A -m \"Initial Creation\" &&\n+\t test $A = $(cat .git/$m)'\n+test_expect_success \\\n+\t\"update $m (logged by config)\" \\\n+\t'GIT_COMMITTER_DATE=\"2005-05-26 23:33\" \\\n+\t git-update-ref HEAD $B $A -m \"Switch\" &&\n+\t test $B = $(cat .git/$m)'\n+test_expect_success \\\n+\t\"set $m (logged by config)\" \\\n+\t'GIT_COMMITTER_DATE=\"2005-05-26 23:43\" \\\n+\t git-update-ref HEAD $A &&\n+\t test $A = $(cat .git/$m)'\n+\n+cat >expect <<EOF\n+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000\tInitial Creation\n+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 +0000\tSwitch\n+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 +0000\n+EOF\n+test_expect_success \\\n+\t\"verifying $m's log\" \\\n+\t'diff expect .git/logs/$m'\n+rm -f .git/$m .git/logs/$m expect\n+\n+test_done\n-- \n1.3.2.g7278\n"}]}