Merge branch 'sp/reflog'

* sp/reflog:
  fetch.c: do not pass uninitialized lock to unlock_ref().
  Test that git-branch -l works.
  Verify git-commit provides a reflog message.
  Enable ref log creation in git checkout -b.
  Create/delete branch ref logs.
  Include ref log detail in commit, reset, etc.
  Change order of -m option to update-ref.
  Correct force_write bug in refs.c
  Change 'master@noon' syntax to 'master@{noon}'.
  Log ref updates made by fetch.
  Force writing ref if it doesn't exist.
  Added logs/ directory to repository layout.
  General ref log reading improvements.
  Fix ref log parsing so it works properly.
  Support 'master@2 hours ago' syntax
  Log ref updates to logs/refs/<ref>
  Convert update-ref to use ref_lock API.
  Improve abstraction of ref lock/write.
This commit is contained in:
Junio C Hamano 2006-06-03 23:59:03 -07:00
commit f0679f474a
26 changed files with 757 additions and 244 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

@ -9,7 +9,7 @@ SYNOPSIS
--------
[verse]
'git-branch' [-r]
'git-branch' [-f] <branchname> [<start-point>]
'git-branch' [-l] [-f] <branchname> [<start-point>]
'git-branch' (-d | -D) <branchname>...
DESCRIPTION
@ -23,7 +23,8 @@ If no <start-point> is given, the branch will be created with a head
equal to that of the currently checked out branch.
With a `-d` or `-D` option, `<branchname>` will be deleted. You may
specify more than one branch for deletion.
specify more than one branch for deletion. If the branch currently
has a ref log then the ref log will also be deleted.
OPTIONS
@ -34,6 +35,11 @@ OPTIONS
-D::
Delete a branch irrespective of its index status.
-l::
Create the branch's ref log. This activates recording of
all changes to made the branch ref, enabling use of date
based sha1 expressions such as "<branchname>@{yesterday}".
-f::
Force the creation of a new branch even if it means deleting
a branch that already exists with the same name.

View file

@ -8,7 +8,7 @@ git-checkout - Checkout and switch to a branch
SYNOPSIS
--------
[verse]
'git-checkout' [-f] [-b <new_branch>] [-m] [<branch>]
'git-checkout' [-f] [-b <new_branch> [-l]] [-m] [<branch>]
'git-checkout' [-m] [<branch>] <paths>...
DESCRIPTION
@ -40,6 +40,11 @@ OPTIONS
by gitlink:git-check-ref-format[1]. Some of these checks
may restrict the characters allowed in a branch name.
-l::
Create the new branch's ref log. This activates recording of
all changes to made the branch ref, enabling use of date
based sha1 expressions such as "<branchname>@{yesterday}".
-m::
If you have local modifications to one or more files that
are different between the current branch and the branch to

View file

@ -124,6 +124,13 @@ syntax.
happen to have both heads/master and tags/master, you can
explicitly say 'heads/master' to tell git which one you mean.
* A suffix '@' followed by a date specification enclosed in a brace
pair (e.g. '\{yesterday\}', '\{1 month 2 weeks 3 days 1 hour 1
second ago\}' or '\{1979-02-26 18:30:00\}') to specify the value
of the ref at a prior point in time. This suffix may only be
used immediately following a ref name and the ref must have an
existing log ($GIT_DIR/logs/<ref>).
* A suffix '{caret}' to a revision parameter means the first parent of
that commit object. '{caret}<n>' means the <n>th parent (i.e.
'rev{caret}'

View file

@ -7,7 +7,7 @@ git-update-ref - update the object name stored in a ref safely
SYNOPSIS
--------
'git-update-ref' <ref> <newvalue> [<oldvalue>]
'git-update-ref' [-m <reason>] <ref> <newvalue> [<oldvalue>]
DESCRIPTION
-----------
@ -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

@ -128,3 +128,14 @@ remotes::
Stores shorthands to be used to give URL and default
refnames to interact with remote repository to `git
fetch`, `git pull` and `git push` commands.
logs::
Records of changes made to refs are stored in this
directory. See the documentation on git-update-ref
for more information.
logs/refs/heads/`name`::
Records all changes made to the branch tip named `name`.
logs/refs/tags/`name`::
Records all changes made to the tag named `name`.

View file

@ -179,6 +179,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";

45
fetch.c
View file

@ -8,6 +8,7 @@
#include "refs.h"
const char *write_ref = NULL;
const char *write_ref_log_details = NULL;
int get_tree = 0;
int get_history = 0;
@ -202,23 +203,51 @@ static int mark_complete(const char *path, const unsigned char *sha1)
int pull(char *target)
{
struct ref_lock *lock = NULL;
unsigned char sha1[20];
char *msg;
int ret;
save_commit_buffer = 0;
track_object_refs = 0;
if (write_ref) {
lock = lock_ref_sha1(write_ref, NULL, 0);
if (!lock) {
error("Can't lock ref %s", write_ref);
return -1;
}
}
if (!get_recover)
for_each_ref(mark_complete);
if (interpret_target(target, sha1))
return error("Could not interpret %s as something to pull",
target);
if (process(lookup_unknown_object(sha1)))
if (interpret_target(target, sha1)) {
error("Could not interpret %s as something to pull", target);
if (lock)
unlock_ref(lock);
return -1;
if (loop())
}
if (process(lookup_unknown_object(sha1))) {
if (lock)
unlock_ref(lock);
return -1;
if (write_ref)
write_ref_sha1_unlocked(write_ref, sha1);
}
if (loop()) {
if (lock)
unlock_ref(lock);
return -1;
}
if (write_ref) {
if (write_ref_log_details) {
msg = xmalloc(strlen(write_ref_log_details) + 12);
sprintf(msg, "fetch from %s", write_ref_log_details);
} else
msg = NULL;
ret = write_ref_sha1(lock, sha1, msg ? msg : "fetch (unknown)");
if (msg)
free(msg);
return ret;
}
return 0;
}

View file

@ -25,6 +25,9 @@ extern int fetch_ref(char *ref, unsigned char *sha1);
/* If set, the ref filename to write the target value to. */
extern const char *write_ref;
/* If set additional text will appear in the ref log. */
extern const char *write_ref_log_details;
/* Set to fetch the target tree. */
extern int get_tree;

View file

@ -413,7 +413,7 @@ do
parent=$(git-rev-parse --verify HEAD) &&
commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") &&
echo Committed: $commit &&
git-update-ref HEAD $commit $parent ||
git-update-ref -m "am: $SUBJECT" HEAD $commit $parent ||
stop_here $this
if test -x "$GIT_DIR"/hooks/post-applypatch

View file

@ -204,7 +204,7 @@ echo Wrote tree $tree
parent=$(git-rev-parse --verify HEAD) &&
commit=$(git-commit-tree $tree -p $parent <"$final") || exit 1
echo Committed: $commit
git-update-ref HEAD $commit $parent || exit
git-update-ref -m "applypatch: $SUBJECT" HEAD $commit $parent || exit
if test -x "$GIT_DIR"/hooks/post-applypatch
then

View file

@ -1,6 +1,6 @@
#!/bin/sh
USAGE='[(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r'
USAGE='[-l] [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r'
LONG_USAGE='If no arguments, show available branches and mark current branch with a star.
If one argument, create a new branch <branchname> based off of current HEAD.
If two arguments, create a new branch <branchname> based off of <start-point>.'
@ -42,6 +42,7 @@ If you are sure you want to delete it, run 'git branch -D $branch_name'."
esac
;;
esac
rm -f "$GIT_DIR/logs/refs/heads/$branch_name"
rm -f "$GIT_DIR/refs/heads/$branch_name"
echo "Deleted branch $branch_name."
done
@ -55,6 +56,7 @@ ls_remote_branches () {
}
force=
create_log=
while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
do
case "$1" in
@ -69,6 +71,9 @@ do
-f)
force="$1"
;;
-l)
create_log="yes"
;;
--)
shift
break
@ -117,4 +122,9 @@ then
die "cannot force-update the current branch."
fi
fi
git update-ref "refs/heads/$branchname" $rev
if test "$create_log" = 'yes'
then
mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname")
touch "$GIT_DIR/logs/refs/heads/$branchname"
fi
git update-ref -m "branch: Created from $head" "refs/heads/$branchname" $rev

View file

@ -5,10 +5,13 @@ SUBDIRECTORY_OK=Sometimes
. git-sh-setup
old=$(git-rev-parse HEAD)
old_name=HEAD
new=
new_name=
force=
branch=
newbranch=
newbranch_log=
merge=
while [ "$#" != "0" ]; do
arg="$1"
@ -24,6 +27,9 @@ while [ "$#" != "0" ]; do
git-check-ref-format "heads/$newbranch" ||
die "git checkout: we do not like '$newbranch' as a branch name."
;;
"-l")
newbranch_log=1
;;
"-f")
force=1
;;
@ -44,6 +50,7 @@ while [ "$#" != "0" ]; do
exit 1
fi
new="$rev"
new_name="$arg^0"
if [ -f "$GIT_DIR/refs/heads/$arg" ]; then
branch="$arg"
fi
@ -51,9 +58,11 @@ while [ "$#" != "0" ]; do
then
# checking out selected paths from a tree-ish.
new="$rev"
new_name="$arg^{tree}"
branch=
else
new=
new_name=
branch=
set x "$arg" "$@"
shift
@ -114,7 +123,7 @@ then
cd "$cdup"
fi
[ -z "$new" ] && new=$old
[ -z "$new" ] && new=$old && new_name="$old_name"
# If we don't have an old branch that we're switching to,
# and we don't have a new branch name for the target we
@ -187,9 +196,11 @@ fi
#
if [ "$?" -eq 0 ]; then
if [ "$newbranch" ]; then
leading=`expr "refs/heads/$newbranch" : '\(.*\)/'` &&
mkdir -p "$GIT_DIR/$leading" &&
echo $new >"$GIT_DIR/refs/heads/$newbranch" || exit
if [ "$newbranch_log" ]; then
mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$newbranch")
touch "$GIT_DIR/logs/refs/heads/$newbranch"
fi
git-update-ref -m "checkout: Created from $new_name" "refs/heads/$newbranch" $new || exit
branch="$newbranch"
fi
[ "$branch" ] &&

View file

@ -713,7 +713,8 @@ then
rm -f "$TMP_INDEX"
fi &&
commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) &&
git-update-ref HEAD $commit $current &&
rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
git-update-ref -m "commit: $rlogm" HEAD $commit $current &&
rm -f -- "$GIT_DIR/MERGE_HEAD" &&
if test -f "$NEXT_INDEX"
then

View file

@ -48,7 +48,7 @@ then
else
rm -f "$GIT_DIR/ORIG_HEAD"
fi
git-update-ref HEAD "$rev"
git-update-ref -m "reset $reset_type $@" HEAD "$rev"
case "$reset_type" in
--hard )

View file

@ -1223,6 +1223,7 @@ int main(int argc, char **argv)
int rc = 0;
setup_git_directory();
git_config(git_default_config);
while (arg < argc && argv[arg][0] == '-') {
if (argv[arg][1] == 't') {
@ -1249,6 +1250,7 @@ int main(int argc, char **argv)
}
commit_id = argv[arg];
url = argv[arg + 1];
write_ref_log_details = url;
http_init();

View file

@ -208,6 +208,7 @@ int main(int argc, char **argv)
int arg = 1;
setup_git_directory();
git_config(git_default_config);
while (arg < argc && argv[arg][0] == '-') {
if (argv[arg][1] == 't')
@ -239,6 +240,7 @@ int main(int argc, char **argv)
usage(local_pull_usage);
commit_id = argv[arg];
path = argv[arg + 1];
write_ref_log_details = path;
if (pull(commit_id))
return 1;

378
refs.c
View file

@ -142,6 +142,8 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
namelen = strlen(de->d_name);
if (namelen > 255)
continue;
if (namelen>5 && !strcmp(de->d_name+namelen-5,".lock"))
continue;
memcpy(path + baselen, de->d_name, namelen+1);
if (stat(git_path("%s", path), &st) < 0)
continue;
@ -198,26 +200,6 @@ int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1))
return do_for_each_ref("refs/remotes", fn, 13);
}
static char *ref_file_name(const char *ref)
{
char *base = get_refs_directory();
int baselen = strlen(base);
int reflen = strlen(ref);
char *ret = xmalloc(baselen + 2 + reflen);
sprintf(ret, "%s/%s", base, ref);
return ret;
}
static char *ref_lock_file_name(const char *ref)
{
char *base = get_refs_directory();
int baselen = strlen(base);
int reflen = strlen(ref);
char *ret = xmalloc(baselen + 7 + reflen);
sprintf(ret, "%s/%s.lock", base, ref);
return ret;
}
int get_ref_sha1(const char *ref, unsigned char *sha1)
{
if (check_ref_format(ref))
@ -225,94 +207,6 @@ int get_ref_sha1(const char *ref, unsigned char *sha1)
return read_ref(git_path("refs/%s", ref), sha1);
}
static int lock_ref_file(const char *filename, const char *lock_filename,
const unsigned char *old_sha1)
{
int fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
unsigned char current_sha1[20];
int retval;
if (fd < 0) {
return error("Couldn't open lock file for %s: %s",
filename, strerror(errno));
}
retval = read_ref(filename, current_sha1);
if (old_sha1) {
if (retval) {
close(fd);
unlink(lock_filename);
return error("Could not read the current value of %s",
filename);
}
if (memcmp(current_sha1, old_sha1, 20)) {
close(fd);
unlink(lock_filename);
error("The current value of %s is %s",
filename, sha1_to_hex(current_sha1));
return error("Expected %s",
sha1_to_hex(old_sha1));
}
} else {
if (!retval) {
close(fd);
unlink(lock_filename);
return error("Unexpectedly found a value of %s for %s",
sha1_to_hex(current_sha1), filename);
}
}
return fd;
}
int lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
{
char *filename;
char *lock_filename;
int retval;
if (check_ref_format(ref))
return -1;
filename = ref_file_name(ref);
lock_filename = ref_lock_file_name(ref);
retval = lock_ref_file(filename, lock_filename, old_sha1);
free(filename);
free(lock_filename);
return retval;
}
static int write_ref_file(const char *filename,
const char *lock_filename, int fd,
const unsigned char *sha1)
{
char *hex = sha1_to_hex(sha1);
char term = '\n';
if (write(fd, hex, 40) < 40 ||
write(fd, &term, 1) < 1) {
error("Couldn't write %s", filename);
close(fd);
return -1;
}
close(fd);
rename(lock_filename, filename);
return 0;
}
int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1)
{
char *filename;
char *lock_filename;
int retval;
if (fd < 0)
return -1;
if (check_ref_format(ref))
return -1;
filename = ref_file_name(ref);
lock_filename = ref_lock_file_name(ref);
if (safe_create_leading_directories(filename))
die("unable to create leading directory for %s", filename);
retval = write_ref_file(filename, lock_filename, fd, sha1);
free(filename);
free(lock_filename);
return retval;
}
/*
* Make sure "ref" is something reasonable to have under ".git/refs/";
* We do not like it if:
@ -365,25 +259,255 @@ int check_ref_format(const char *ref)
}
}
int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1)
static struct ref_lock* verify_lock(struct ref_lock *lock,
const unsigned char *old_sha1, int mustexist)
{
char *filename;
char *lock_filename;
int fd;
int retval;
if (check_ref_format(ref))
return -1;
filename = ref_file_name(ref);
lock_filename = ref_lock_file_name(ref);
if (safe_create_leading_directories(filename))
die("unable to create leading directory for %s", filename);
fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
if (fd < 0) {
error("Writing %s", lock_filename);
perror("Open");
char buf[40];
int nr, fd = open(lock->ref_file, O_RDONLY);
if (fd < 0 && (mustexist || errno != ENOENT)) {
error("Can't verify ref %s", lock->ref_file);
unlock_ref(lock);
return NULL;
}
retval = write_ref_file(filename, lock_filename, fd, sha1);
free(filename);
free(lock_filename);
return retval;
nr = read(fd, buf, 40);
close(fd);
if (nr != 40 || get_sha1_hex(buf, lock->old_sha1) < 0) {
error("Can't verify ref %s", lock->ref_file);
unlock_ref(lock);
return NULL;
}
if (memcmp(lock->old_sha1, old_sha1, 20)) {
error("Ref %s is at %s but expected %s", lock->ref_file,
sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1));
unlock_ref(lock);
return NULL;
}
return lock;
}
static struct ref_lock* lock_ref_sha1_basic(const char *path,
int plen,
const unsigned char *old_sha1, int mustexist)
{
struct ref_lock *lock;
struct stat st;
lock = xcalloc(1, sizeof(struct ref_lock));
lock->lock_fd = -1;
plen = strlen(path) - plen;
path = resolve_ref(path, lock->old_sha1, mustexist);
if (!path) {
unlock_ref(lock);
return NULL;
}
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));
lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT;
if (safe_create_leading_directories(lock->lock_file))
die("unable to create directory for %s", lock->lock_file);
lock->lock_fd = open(lock->lock_file,
O_WRONLY | O_CREAT | O_EXCL, 0666);
if (lock->lock_fd < 0) {
error("Couldn't open lock file %s: %s",
lock->lock_file, strerror(errno));
unlock_ref(lock);
return NULL;
}
return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
}
struct ref_lock* lock_ref_sha1(const char *ref,
const unsigned char *old_sha1, int mustexist)
{
if (check_ref_format(ref))
return NULL;
return lock_ref_sha1_basic(git_path("refs/%s", ref),
5 + strlen(ref), old_sha1, mustexist);
}
struct ref_lock* lock_any_ref_for_update(const char *ref,
const unsigned char *old_sha1, int mustexist)
{
return lock_ref_sha1_basic(git_path("%s", ref),
strlen(ref), old_sha1, mustexist);
}
void unlock_ref (struct ref_lock *lock)
{
if (lock->lock_fd >= 0) {
close(lock->lock_fd);
unlink(lock->lock_file);
}
if (lock->ref_file)
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)
{
static char term = '\n';
if (!lock)
return -1;
if (!lock->force_write && !memcmp(lock->old_sha1, sha1, 20)) {
unlock_ref(lock);
return 0;
}
if (write(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
write(lock->lock_fd, &term, 1) != 1
|| close(lock->lock_fd) < 0) {
error("Couldn't write %s", lock->lock_file);
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);
return -1;
}
lock->lock_fd = -1;
unlock_ref(lock);
return 0;
}
int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
{
const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
char *tz_c;
int logfd, tz;
struct stat st;
unsigned long date;
unsigned char logged_sha1[20];
logfile = git_path("logs/%s", ref);
logfd = open(logfile, O_RDONLY, 0);
if (logfd < 0)
die("Unable to read log %s: %s", logfile, strerror(errno));
fstat(logfd, &st);
if (!st.st_size)
die("Log %s is empty.", logfile);
logdata = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);
close(logfd);
lastrec = NULL;
rec = logend = logdata + st.st_size;
while (logdata < rec) {
if (logdata < rec && *(rec-1) == '\n')
rec--;
lastgt = NULL;
while (logdata < rec && *(rec-1) != '\n') {
rec--;
if (*rec == '>')
lastgt = rec;
}
if (!lastgt)
die("Log %s is corrupt.", logfile);
date = strtoul(lastgt + 1, &tz_c, 10);
if (date <= at_time) {
if (lastrec) {
if (get_sha1_hex(lastrec, logged_sha1))
die("Log %s is corrupt.", logfile);
if (get_sha1_hex(rec + 41, sha1))
die("Log %s is corrupt.", logfile);
if (memcmp(logged_sha1, sha1, 20)) {
tz = strtoul(tz_c, NULL, 10);
fprintf(stderr,
"warning: Log %s has gap after %s.\n",
logfile, show_rfc2822_date(date, tz));
}
} else if (date == at_time) {
if (get_sha1_hex(rec + 41, sha1))
die("Log %s is corrupt.", logfile);
} else {
if (get_sha1_hex(rec + 41, logged_sha1))
die("Log %s is corrupt.", logfile);
if (memcmp(logged_sha1, sha1, 20)) {
tz = strtoul(tz_c, NULL, 10);
fprintf(stderr,
"warning: Log %s unexpectedly ended on %s.\n",
logfile, show_rfc2822_date(date, tz));
}
}
munmap((void*)logdata, st.st_size);
return 0;
}
lastrec = rec;
}
rec = logdata;
while (rec < logend && *rec != '>' && *rec != '\n')
rec++;
if (rec == logend || *rec == '\n')
die("Log %s is corrupt.", logfile);
date = strtoul(rec + 1, &tz_c, 10);
tz = strtoul(tz_c, NULL, 10);
if (get_sha1_hex(logdata, sha1))
die("Log %s is corrupt.", logfile);
munmap((void*)logdata, st.st_size);
fprintf(stderr, "warning: Log %s only goes back to %s.\n",
logfile, show_rfc2822_date(date, tz));
return 0;
}

29
refs.h
View file

@ -1,6 +1,15 @@
#ifndef REFS_H
#define REFS_H
struct ref_lock {
char *ref_file;
char *lock_file;
char *log_file;
unsigned char old_sha1[20];
int lock_fd;
int force_write;
};
/*
* Calls the specified function for each ref file until it returns nonzero,
* and returns the value
@ -14,16 +23,20 @@ extern int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *
/** Reads the refs file specified into sha1 **/
extern int get_ref_sha1(const char *ref, unsigned char *sha1);
/** Locks ref and returns the fd to give to write_ref_sha1() if the ref
* has the given value currently; otherwise, returns -1.
**/
extern int lock_ref_sha1(const char *ref, const unsigned char *old_sha1);
/** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
extern struct ref_lock* lock_ref_sha1(const char *ref, const unsigned char *old_sha1, int mustexist);
/** Writes sha1 into the refs file specified, locked with the given fd. **/
extern int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1);
/** Locks any ref (for 'HEAD' type refs). */
extern struct ref_lock* lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int mustexist);
/** Writes sha1 into the refs file specified. **/
extern int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1);
/** Release any lock taken but not written. **/
extern void unlock_ref (struct ref_lock *lock);
/** Writes sha1 into the ref specified by the lock. **/
extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
/** Reads log for the value of ref during at_time. **/
extern int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1);
/** Returns 0 if target has the right format for a ref. **/
extern int check_ref_format(const char *target);

View file

@ -4,6 +4,7 @@
#include "tree.h"
#include "blob.h"
#include "tree-walk.h"
#include "refs.h"
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
{
@ -245,36 +246,61 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
"refs/remotes/%.*s/HEAD",
NULL
};
const char **p;
const char *warning = "warning: refname '%.*s' is ambiguous.\n";
char *pathname;
int already_found = 0;
static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
const char **p, *pathname;
char *real_path = NULL;
int refs_found = 0, am;
unsigned long at_time = (unsigned long)-1;
unsigned char *this_result;
unsigned char sha1_from_ref[20];
if (len == 40 && !get_sha1_hex(str, sha1))
return 0;
/* At a given period of time? "@{2 hours ago}" */
for (am = 1; am < len - 1; am++) {
if (str[am] == '@' && str[am+1] == '{' && str[len-1] == '}') {
int date_len = len - am - 3;
char *date_spec = xmalloc(date_len + 1);
strncpy(date_spec, str + am + 2, date_len);
date_spec[date_len] = 0;
at_time = approxidate(date_spec);
free(date_spec);
len = am;
break;
}
}
/* Accept only unambiguous ref paths. */
if (ambiguous_path(str, len))
return -1;
for (p = fmt; *p; p++) {
this_result = already_found ? sha1_from_ref : sha1;
pathname = git_path(*p, len, str);
if (!read_ref(pathname, this_result)) {
if (warn_ambiguous_refs) {
if (already_found)
fprintf(stderr, warning, len, str);
already_found++;
}
else
return 0;
this_result = refs_found ? sha1_from_ref : sha1;
pathname = resolve_ref(git_path(*p, len, str), this_result, 1);
if (pathname) {
if (!refs_found++)
real_path = strdup(pathname);
if (!warn_ambiguous_refs)
break;
}
}
if (already_found)
return 0;
return -1;
if (!refs_found)
return -1;
if (warn_ambiguous_refs && refs_found > 1)
fprintf(stderr, warning, len, str);
if (at_time != (unsigned long)-1) {
read_ref_at(
real_path + strlen(git_path(".")) - 1,
at_time,
sha1);
}
free(real_path);
return 0;
}
static int get_sha1_1(const char *name, int len, unsigned char *sha1);
@ -456,7 +482,7 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1)
*/
int get_sha1(const char *name, unsigned char *sha1)
{
int ret;
int ret, bracket_depth;
unsigned unused;
int namelen = strlen(name);
const char *cp;
@ -502,8 +528,15 @@ int get_sha1(const char *name, unsigned char *sha1)
}
return -1;
}
cp = strchr(name, ':');
if (cp) {
for (cp = name, bracket_depth = 0; *cp; cp++) {
if (*cp == '{')
bracket_depth++;
else if (bracket_depth && *cp == '}')
bracket_depth--;
else if (!bracket_depth && *cp == ':')
break;
}
if (*cp == ':') {
unsigned char tree_sha1[20];
if (!get_sha1_1(name, cp-name, tree_sha1))
return get_tree_entry(tree_sha1, cp+1, sha1,

View file

@ -132,6 +132,7 @@ int main(int argc, char **argv)
if (!prog) prog = "git-ssh-upload";
setup_git_directory();
git_config(git_default_config);
while (arg < argc && argv[arg][0] == '-') {
if (argv[arg][1] == 't') {
@ -158,6 +159,7 @@ int main(int argc, char **argv)
}
commit_id = argv[arg];
url = argv[arg + 1];
write_ref_log_details = url;
if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1))
return 1;

213
t/t1400-update-ref.sh Executable file
View file

@ -0,0 +1,213 @@
#!/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
C=3333333333333333333333333333333333333333
D=4444444444444444444444444444444444444444
E=5555555555555555555555555555555555555555
F=6666666666666666666666666666666666666666
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
git-update-ref $m $D
cat >.git/logs/$m <<EOF
$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500
EOF
ed="Thu, 26 May 2005 18:32:00 -0500"
gd="Thu, 26 May 2005 18:33:00 -0500"
ld="Thu, 26 May 2005 18:43:00 -0500"
test_expect_success \
'Query "master@{May 25 2005}" (before history)' \
'rm -f o e
git-rev-parse --verify "master@{May 25 2005}" >o 2>e &&
test $C = $(cat o) &&
test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
test_expect_success \
"Query master@{2005-05-25} (before history)" \
'rm -f o e
git-rev-parse --verify master@{2005-05-25} >o 2>e &&
test $C = $(cat o) &&
echo test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
test_expect_success \
'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \
'rm -f o e
git-rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
test $C = $(cat o) &&
test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
test_expect_success \
'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \
'rm -f o e
git-rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
test $A = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \
'rm -f o e
git-rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
test $B = $(cat o) &&
test "warning: Log .git/logs/$m has gap after $gd." = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:38:00}" (middle of history)' \
'rm -f o e
git-rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
test $Z = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \
'rm -f o e
git-rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
test $E = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-28}" (past end of history)' \
'rm -f o e
git-rev-parse --verify "master@{2005-05-28}" >o 2>e &&
test $D = $(cat o) &&
test "warning: Log .git/logs/$m unexpectedly ended on $ld." = "$(cat e)"'
rm -f .git/$m .git/logs/$m expect
test_expect_success \
'creating initial files' \
'echo TEST >F &&
git-add F &&
GIT_AUTHOR_DATE="2005-05-26 23:30" \
GIT_COMMITTER_DATE="2005-05-26 23:30" git-commit -m add -a &&
h_TEST=$(git-rev-parse --verify HEAD)
echo The other day this did not work. >M &&
echo And then Bob told me how to fix it. >>M &&
echo OTHER >F &&
GIT_AUTHOR_DATE="2005-05-26 23:41" \
GIT_COMMITTER_DATE="2005-05-26 23:41" git-commit -F M -a &&
h_OTHER=$(git-rev-parse --verify HEAD)
rm -f M'
cat >expect <<EOF
$Z $h_TEST $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 commit: add
$h_TEST $h_OTHER $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000 commit: The other day this did not work.
EOF
test_expect_success \
'git-commit logged updates' \
'diff expect .git/logs/$m'
unset h_TEST h_OTHER
test_expect_success \
'git-cat-file blob master:F (expect OTHER)' \
'test OTHER = $(git-cat-file blob master:F)'
test_expect_success \
'git-cat-file blob master@{2005-05-26 23:30}:F (expect TEST)' \
'test TEST = $(git-cat-file blob "master@{2005-05-26 23:30}:F")'
test_expect_success \
'git-cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \
'test OTHER = $(git-cat-file blob "master@{2005-05-26 23:42}:F")'
test_done

View file

@ -14,7 +14,8 @@ test_expect_success \
'prepare an trivial repository' \
'echo Hello > A &&
git-update-index --add A &&
git-commit -m "Initial commit."'
git-commit -m "Initial commit." &&
HEAD=$(git-rev-parse --verify HEAD)'
test_expect_success \
'git branch --help should return success now.' \
@ -32,4 +33,32 @@ test_expect_success \
'git branch a/b/c should create a branch' \
'git-branch a/b/c && test -f .git/refs/heads/a/b/c'
cat >expect <<EOF
0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from HEAD
EOF
test_expect_success \
'git branch -l d/e/f should create a branch and a log' \
'GIT_COMMITTER_DATE="2005-05-26 23:30" \
git-branch -l d/e/f &&
test -f .git/refs/heads/d/e/f &&
test -f .git/logs/refs/heads/d/e/f &&
diff expect .git/logs/refs/heads/d/e/f'
test_expect_success \
'git branch -d d/e/f should delete a branch and a log' \
'git-branch -d d/e/f &&
test ! -f .git/refs/heads/d/e/f &&
test ! -f .git/logs/refs/heads/d/e/f'
cat >expect <<EOF
0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 checkout: Created from master^0
EOF
test_expect_success \
'git checkout -b g/h/i -l should create a branch and a log' \
'GIT_COMMITTER_DATE="2005-05-26 23:30" \
git-checkout -b g/h/i -l master &&
test -f .git/refs/heads/g/h/i &&
test -f .git/logs/refs/heads/g/h/i &&
diff expect .git/logs/refs/heads/g/h/i'
test_done

View file

@ -1,85 +1,56 @@
#include "cache.h"
#include "refs.h"
static const char git_update_ref_usage[] = "git-update-ref <refname> <value> [<oldval>]";
static int re_verify(const char *path, unsigned char *oldsha1, unsigned char *currsha1)
{
char buf[40];
int fd = open(path, O_RDONLY), nr;
if (fd < 0)
return -1;
nr = read(fd, buf, 40);
close(fd);
if (nr != 40 || get_sha1_hex(buf, currsha1) < 0)
return -1;
return memcmp(oldsha1, currsha1, 20) ? -1 : 0;
}
static const char git_update_ref_usage[] =
"git-update-ref <refname> <value> [<oldval>] [-m <reason>]";
int main(int argc, char **argv)
{
char *hex;
const char *refname, *value, *oldval, *path;
char *lockpath;
unsigned char sha1[20], oldsha1[20], currsha1[20];
int fd, written;
const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
struct ref_lock *lock;
unsigned char sha1[20], oldsha1[20];
int i;
setup_git_directory();
git_config(git_default_config);
if (argc < 3 || argc > 4)
for (i = 1; i < argc; i++) {
if (!strcmp("-m", argv[i])) {
if (i+1 >= argc)
usage(git_update_ref_usage);
msg = argv[++i];
if (!*msg)
die("Refusing to perform update with empty message.");
if (strchr(msg, '\n'))
die("Refusing to perform update with \\n in message.");
continue;
}
if (!refname) {
refname = argv[i];
continue;
}
if (!value) {
value = argv[i];
continue;
}
if (!oldval) {
oldval = argv[i];
continue;
}
}
if (!refname || !value)
usage(git_update_ref_usage);
refname = argv[1];
value = argv[2];
oldval = argv[3];
if (get_sha1(value, sha1))
die("%s: not a valid SHA1", value);
memset(oldsha1, 0, 20);
if (oldval && get_sha1(oldval, oldsha1))
die("%s: not a valid old SHA1", oldval);
path = resolve_ref(git_path("%s", refname), currsha1, !!oldval);
if (!path)
die("No such ref: %s", refname);
if (oldval) {
if (memcmp(currsha1, oldsha1, 20))
die("Ref %s is at %s but expected %s", refname, sha1_to_hex(currsha1), sha1_to_hex(oldsha1));
/* Nothing to do? */
if (!memcmp(oldsha1, sha1, 20))
exit(0);
}
path = strdup(path);
lockpath = mkpath("%s.lock", path);
if (safe_create_leading_directories(lockpath) < 0)
die("Unable to create all of %s", lockpath);
fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
if (fd < 0)
die("Unable to create %s", lockpath);
hex = sha1_to_hex(sha1);
hex[40] = '\n';
written = write(fd, hex, 41);
close(fd);
if (written != 41) {
unlink(lockpath);
die("Unable to write to %s", lockpath);
}
/*
* Re-read the ref after getting the lock to verify
*/
if (oldval && re_verify(path, oldsha1, currsha1) < 0) {
unlink(lockpath);
die("Ref lock failed");
}
/*
* Finally, replace the old ref with the new one
*/
if (rename(lockpath, path) < 0) {
unlink(lockpath);
die("Unable to create %s", path);
}
lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, 0);
if (!lock)
return 1;
if (write_ref_sha1(lock, sha1, msg) < 0)
return 1;
return 0;
}