[RFC 4/5] Log ref updates to logs/refs/<ref>

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



If config parameter core.logAllRefUpdates is true or the log
file already exists then append a line to ".git/logs/refs/<ref>"
whenever git-update-ref <ref> is executed.  Each log line contains
the following information:

  oldsha1 <SP> newsha1 <SP> committer <LF>

where committer is the current user, date, time and timezone in
the standard GIT ident format.  If the caller is unable to append
to the log file then git-update-ref will fail without updating <ref>.

An optional message may be included in the log line with the -m flag.

Signed-off-by: Shawn O. Pearce <spearce@xxxxxxxxxxx>

---

 Documentation/config.txt         |    8 +++
 Documentation/git-update-ref.txt |   26 +++++++++
 cache.h                          |    1 
 config.c                         |    5 ++
 environment.c                    |    1 
 refs.c                           |   56 +++++++++++++++++++
 refs.h                           |    1 
 t/t1400-update-ref.sh            |  112 ++++++++++++++++++++++++++++++++++++++
 8 files changed, 210 insertions(+), 0 deletions(-)
 create mode 100644 t/t1400-update-ref.sh

1b91f255386d1120bdcb434603993156c9fdde71
diff --git a/Documentation/config.txt b/Documentation/config.txt
index d1a4bec..e178ee2 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -70,6 +70,14 @@ core.preferSymlinkRefs::
 	This is sometimes needed to work with old scripts that
 	expect HEAD to be a symbolic link.
 
+core.logAllRefUpdates::
+	If true, `git-update-ref` will append a line to
+	"$GIT_DIR/logs/<ref>" listing the new SHA1 and the date/time
+	of the update.	If the file does not exist it will be
+	created automatically.	This information can be used to
+	determine what commit was the tip of a branch "2 days ago".
+	This value is false by default (no logging).
+
 core.repositoryFormatVersion::
 	Internal variable identifying the repository format and layout
 	version.
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index f0e710a..dfbd886 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -49,6 +49,32 @@ for reading but not for writing (so we'l
 ref symlink to some other tree, if you have copied a whole
 archive by creating a symlink tree).
 
+Logging Updates
+---------------
+If config parameter "core.logAllRefUpdates" is true or the file
+"$GIT_DIR/logs/<ref>" exists then `git-update-ref` will append
+a line to the log file "$GIT_DIR/logs/<ref>" (dereferencing all
+symbolic refs before creating the log name) describing the change
+in ref value.  Log lines are formatted as:
+
+    . oldsha1 SP newsha1 SP committer LF
++
+Where "oldsha1" is the 40 character hexadecimal value previously
+stored in <ref>, "newsha1" is the 40 character hexadecimal value of
+<newvalue> and "committer" is the committer's name, email address
+and date in the standard GIT committer ident format.
+
+Optionally with -m:
+
+    . oldsha1 SP newsha1 SP committer TAB message LF
++
+Where all fields are as described above and "message" is the
+value supplied to the -m option.
+
+An update will fail (without changing <ref>) if the current user is
+unable to create a new log file, append to the existing log file
+or does not have committer information available.
+
 Author
 ------
 Written by Linus Torvalds <torvalds@xxxxxxxx>.
diff --git a/cache.h b/cache.h
index b1300cd..82adbba 100644
--- a/cache.h
+++ b/cache.h
@@ -171,6 +171,7 @@ extern void rollback_index_file(struct c
 extern int trust_executable_bit;
 extern int assume_unchanged;
 extern int prefer_symlink_refs;
+extern int log_all_ref_updates;
 extern int warn_ambiguous_refs;
 extern int diff_rename_limit_default;
 extern int shared_repository;
diff --git a/config.c b/config.c
index 0248c6d..2ae6153 100644
--- a/config.c
+++ b/config.c
@@ -269,6 +269,11 @@ int git_default_config(const char *var, 
 		return 0;
 	}
 
+	if (!strcmp(var, "core.logallrefupdates")) {
+		log_all_ref_updates = git_config_bool(var, value);
+		return 0;
+	}
+
 	if (!strcmp(var, "core.warnambiguousrefs")) {
 		warn_ambiguous_refs = git_config_bool(var, value);
 		return 0;
diff --git a/environment.c b/environment.c
index 444c99e..2e79eab 100644
--- a/environment.c
+++ b/environment.c
@@ -14,6 +14,7 @@ char git_default_name[MAX_GITNAME];
 int trust_executable_bit = 1;
 int assume_unchanged = 0;
 int prefer_symlink_refs = 0;
+int log_all_ref_updates = 0;
 int warn_ambiguous_refs = 1;
 int repository_format_version = 0;
 char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
diff --git a/refs.c b/refs.c
index 91c8c44..4be75a5 100644
--- a/refs.c
+++ b/refs.c
@@ -302,6 +302,7 @@ static struct ref_lock* lock_ref_sha1_ba
 
 	lock->ref_file = strdup(path);
 	lock->lock_file = strdup(mkpath("%s.lock", lock->ref_file));
+	lock->log_file = strdup(git_path("logs/%s", lock->ref_file + plen));
 
 	if (safe_create_leading_directories(lock->lock_file))
 		die("unable to create directory for %s", lock->lock_file);
@@ -343,9 +344,60 @@ void unlock_ref (struct ref_lock *lock)
 		free(lock->ref_file);
 	if (lock->lock_file)
 		free(lock->lock_file);
+	if (lock->log_file)
+		free(lock->log_file);
 	free(lock);
 }
 
+static int log_ref_write(struct ref_lock *lock,
+	const unsigned char *sha1, const char *msg)
+{
+	int logfd, written, oflags = O_APPEND | O_WRONLY;
+	unsigned maxlen, len;
+	char *logrec;
+	const char *comitter;
+
+	if (log_all_ref_updates) {
+		if (safe_create_leading_directories(lock->log_file) < 0)
+			return error("unable to create directory for %s",
+				lock->log_file);
+		oflags |= O_CREAT;
+	}
+
+	logfd = open(lock->log_file, oflags, 0666);
+	if (logfd < 0) {
+		if (!log_all_ref_updates && errno == ENOENT)
+			return 0;
+		return error("Unable to append to %s: %s",
+			lock->log_file, strerror(errno));
+	}
+
+	setup_ident();
+	comitter = git_committer_info(1);
+	if (msg) {
+		maxlen = strlen(comitter) + strlen(msg) + 2*40 + 5;
+		logrec = xmalloc(maxlen);
+		len = snprintf(logrec, maxlen, "%s %s %s\t%s\n",
+			sha1_to_hex(lock->old_sha1),
+			sha1_to_hex(sha1),
+			comitter,
+			msg);
+	} else {
+		maxlen = strlen(comitter) + 2*40 + 4;
+		logrec = xmalloc(maxlen);
+		len = snprintf(logrec, maxlen, "%s %s %s\n",
+			sha1_to_hex(lock->old_sha1),
+			sha1_to_hex(sha1),
+			comitter);
+	}
+	written = len <= maxlen ? write(logfd, logrec, len) : -1;
+	free(logrec);
+	close(logfd);
+	if (written != len)
+		return error("Unable to append to %s", lock->log_file);
+	return 0;
+}
+
 int write_ref_sha1(struct ref_lock *lock,
 	const unsigned char *sha1, const char *logmsg)
 {
@@ -364,6 +416,10 @@ int write_ref_sha1(struct ref_lock *lock
 		unlock_ref(lock);
 		return -1;
 	}
+	if (log_ref_write(lock, sha1, logmsg) < 0) {
+		unlock_ref(lock);
+		return -1;
+	}
 	if (rename(lock->lock_file, lock->ref_file) < 0) {
 		error("Couldn't set %s", lock->ref_file);
 		unlock_ref(lock);
diff --git a/refs.h b/refs.h
index b7e9df2..43831e9 100644
--- a/refs.h
+++ b/refs.h
@@ -4,6 +4,7 @@ #define REFS_H
 struct ref_lock {
 	char *ref_file;
 	char *lock_file;
+	char *log_file;
 	unsigned char old_sha1[20];
 	int lock_fd;
 };
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
new file mode 100644
index 0000000..f338c53
--- /dev/null
+++ b/t/t1400-update-ref.sh
@@ -0,0 +1,112 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Shawn Pearce
+#
+
+test_description='Test git-update-ref and basic ref logging'
+. ./test-lib.sh
+
+Z=0000000000000000000000000000000000000000
+A=1111111111111111111111111111111111111111
+B=2222222222222222222222222222222222222222
+m=refs/heads/master
+
+test_expect_success \
+	"create $m" \
+	'git-update-ref $m $A &&
+	 test $A = $(cat .git/$m)'
+test_expect_success \
+	"create $m" \
+	'git-update-ref $m $B $A &&
+	 test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+test_expect_success \
+	"create $m (by HEAD)" \
+	'git-update-ref HEAD $A &&
+	 test $A = $(cat .git/$m)'
+test_expect_success \
+	"create $m (by HEAD)" \
+	'git-update-ref HEAD $B $A &&
+	 test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+test_expect_failure \
+	'(not) create HEAD with old sha1' \
+	'git-update-ref HEAD $A $B'
+test_expect_failure \
+	"(not) prior created .git/$m" \
+	'test -f .git/$m'
+rm -f .git/$m
+
+test_expect_success \
+	"create HEAD" \
+	'git-update-ref HEAD $A'
+test_expect_failure \
+	'(not) change HEAD with wrong SHA1' \
+	'git-update-ref HEAD $B $Z'
+test_expect_failure \
+	"(not) changed .git/$m" \
+	'test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+mkdir -p .git/logs/refs/heads
+touch .git/logs/refs/heads/master
+test_expect_success \
+	"create $m (logged by touch)" \
+	'GIT_COMMITTER_DATE="2005-05-26 23:30" \
+	 git-update-ref HEAD $A -m "Initial Creation" &&
+	 test $A = $(cat .git/$m)'
+test_expect_success \
+	"update $m (logged by touch)" \
+	'GIT_COMMITTER_DATE="2005-05-26 23:31" \
+	 git-update-ref HEAD $B $A -m "Switch" &&
+	 test $B = $(cat .git/$m)'
+test_expect_success \
+	"set $m (logged by touch)" \
+	'GIT_COMMITTER_DATE="2005-05-26 23:41" \
+	 git-update-ref HEAD $A &&
+	 test $A = $(cat .git/$m)'
+
+cat >expect <<EOF
+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000	Initial Creation
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150260 +0000	Switch
+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000
+EOF
+test_expect_success \
+	"verifying $m's log" \
+	'diff expect .git/logs/$m'
+rm -rf .git/$m .git/logs expect
+
+test_expect_success \
+	'enable core.logAllRefUpdates' \
+	'git-repo-config core.logAllRefUpdates true &&
+	 test true = $(git-repo-config --bool --get core.logAllRefUpdates)'
+
+test_expect_success \
+	"create $m (logged by config)" \
+	'GIT_COMMITTER_DATE="2005-05-26 23:32" \
+	 git-update-ref HEAD $A -m "Initial Creation" &&
+	 test $A = $(cat .git/$m)'
+test_expect_success \
+	"update $m (logged by config)" \
+	'GIT_COMMITTER_DATE="2005-05-26 23:33" \
+	 git-update-ref HEAD $B $A -m "Switch" &&
+	 test $B = $(cat .git/$m)'
+test_expect_success \
+	"set $m (logged by config)" \
+	'GIT_COMMITTER_DATE="2005-05-26 23:43" \
+	 git-update-ref HEAD $A &&
+	 test $A = $(cat .git/$m)'
+
+cat >expect <<EOF
+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000	Initial Creation
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 +0000	Switch
+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 +0000
+EOF
+test_expect_success \
+	"verifying $m's log" \
+	'diff expect .git/logs/$m'
+rm -f .git/$m .git/logs/$m expect
+
+test_done
-- 
1.3.2.g7278

-
: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]