From: Shawn Pearce Date: Wed, 17 May 2006 09:55:40 +0000 (-0400) Subject: Log ref updates to logs/refs/ X-Git-Tag: v1.4.0-rc1~11^2~17 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=6de08ae688b9f2426410add155079e04baff33bd;p=git.git Log ref updates to logs/refs/ If config parameter core.logAllRefUpdates is true or the log file already exists then append a line to ".git/logs/refs/" whenever git-update-ref is executed. Each log line contains the following information: oldsha1 newsha1 committer 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 . An optional message may be included in the log line with the -m flag. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- diff --git a/Documentation/config.txt b/Documentation/config.txt index d1a4bec0d..e178ee2de 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/" 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 f0e710a6f..dfbd88697 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'll never write through a 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/" exists then `git-update-ref` will append +a line to the log file "$GIT_DIR/logs/" (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 , "newsha1" is the 40 character hexadecimal value of + 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 ) 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 . diff --git a/cache.h b/cache.h index 4b7a43925..2386b95e0 100644 --- a/cache.h +++ b/cache.h @@ -170,6 +170,7 @@ extern void rollback_index_file(struct cache_file *); 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 0248c6d8a..2ae6153e5 100644 --- a/config.c +++ b/config.c @@ -269,6 +269,11 @@ int git_default_config(const char *var, const char *value) 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 444c99ed6..2e79eab18 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 91c8c44a1..4be75a59a 100644 --- a/refs.c +++ b/refs.c @@ -302,6 +302,7 @@ static struct ref_lock* lock_ref_sha1_basic(const char *path, 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 b7e9df2fa..43831e9be 100644 --- a/refs.h +++ b/refs.h @@ -4,6 +4,7 @@ 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 000000000..f338c5377 --- /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 < 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 < 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