Log ref updates to logs/refs/<ref>

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@spearce.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
This commit is contained in:
Shawn Pearce 2006-05-17 05:55:40 -04:00 committed by Junio C Hamano
parent 5b16b09021
commit 6de08ae688
8 changed files with 210 additions and 0 deletions

View File

@ -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.

View File

@ -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/<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@osdl.org>.

View File

@ -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;

View File

@ -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;

View File

@ -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";

56
refs.c
View File

@ -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);

1
refs.h
View File

@ -4,6 +4,7 @@
struct ref_lock {
char *ref_file;
char *lock_file;
char *log_file;
unsigned char old_sha1[20];
int lock_fd;
};

112
t/t1400-update-ref.sh Normal file
View File

@ -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