From 6de08ae688b9f2426410add155079e04baff33bd Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 17 May 2006 05:55:40 -0400 Subject: 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 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/" 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'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 4b7a439..2386b95 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 0248c6d..2ae6153 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 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_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 b7e9df2..43831e9 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 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 < 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 -- cgit v0.10.2-6-g49f6