From 4bd18c43d9a8a8fc1aa598926100f6999953ba48 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 17 May 2006 05:55:02 -0400 Subject: [PATCH 01/18] Improve abstraction of ref lock/write. Created 'struct ref_lock' to contain the data necessary to perform a ref update. This change improves writing a ref as the file names are generated only once (rather than twice) and supports following symrefs (up to the maximum depth). Further the ref_lock structure provides room to extend the update API with ref logging. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- fetch.c | 32 ++++---- refs.c | 240 ++++++++++++++++++++++++++------------------------------ refs.h | 24 ++++-- 3 files changed, 146 insertions(+), 150 deletions(-) diff --git a/fetch.c b/fetch.c index 73bde07aea..8bdaacb8e8 100644 --- a/fetch.c +++ b/fetch.c @@ -204,14 +204,14 @@ static int mark_complete(const char *path, const unsigned char *sha1) int pull(char *target) { + struct ref_lock *lock; unsigned char sha1[20]; - int fd = -1; save_commit_buffer = 0; track_object_refs = 0; - if (write_ref && current_ref) { - fd = lock_ref_sha1(write_ref, current_ref); - if (fd < 0) + if (write_ref) { + lock = lock_ref_sha1(write_ref, current_ref, 1); + if (!lock) return -1; } @@ -219,20 +219,22 @@ int pull(char *target) 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); + unlock_ref(lock); return -1; - if (loop()) + } + if (process(lookup_unknown_object(sha1))) { + unlock_ref(lock); return -1; - + } + if (loop()) { + unlock_ref(lock); + return -1; + } + if (write_ref) { - if (current_ref) { - write_ref_sha1(write_ref, fd, sha1); - } else { - write_ref_sha1_unlocked(write_ref, sha1); - } + return write_ref_sha1(lock, sha1, "git fetch"); } return 0; } diff --git a/refs.c b/refs.c index 0f3491f871..91c8c44a15 100644 --- a/refs.c +++ b/refs.c @@ -198,26 +198,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 +205,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 +257,119 @@ 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; + + 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) { + error("Can't read ref %s", path); + unlock_ref(lock); + return NULL; + } + + lock->ref_file = strdup(path); + lock->lock_file = strdup(mkpath("%s.lock", lock->ref_file)); + + 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), + 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); + free(lock); +} + +int write_ref_sha1(struct ref_lock *lock, + const unsigned char *sha1, const char *logmsg) +{ + static char term = '\n'; + + if (!lock) + return -1; + if (!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 (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; } diff --git a/refs.h b/refs.h index fa816c1e9f..b7e9df2fa9 100644 --- a/refs.h +++ b/refs.h @@ -1,6 +1,13 @@ #ifndef REFS_H #define REFS_H +struct ref_lock { + char *ref_file; + char *lock_file; + unsigned char old_sha1[20]; + int lock_fd; +}; + /* * Calls the specified function for each ref file until it returns nonzero, * and returns the value @@ -14,16 +21,17 @@ 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); /** Returns 0 if target has the right format for a ref. **/ extern int check_ref_format(const char *target); From 5b16b090211f549c501f2cd9e8ea1bd227a575c5 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 17 May 2006 05:55:19 -0400 Subject: [PATCH 02/18] Convert update-ref to use ref_lock API. This conversion also adds the '-m' switch to update-ref allowing the caller to record why the ref is changing. At present this is merely copied down into the ref_lock API. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-update-ref.txt | 2 +- update-ref.c | 103 +++++++++++-------------------- 2 files changed, 38 insertions(+), 67 deletions(-) diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt index 475237f19e..f0e710a6f8 100644 --- a/Documentation/git-update-ref.txt +++ b/Documentation/git-update-ref.txt @@ -7,7 +7,7 @@ git-update-ref - update the object name stored in a ref safely SYNOPSIS -------- -'git-update-ref' [] +'git-update-ref' [] [-m ] DESCRIPTION ----------- diff --git a/update-ref.c b/update-ref.c index fd487421cd..a1e6bb90fe 100644 --- a/update-ref.c +++ b/update-ref.c @@ -1,85 +1,56 @@ #include "cache.h" #include "refs.h" -static const char git_update_ref_usage[] = "git-update-ref []"; - -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 [] [-m ]"; 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; } From 6de08ae688b9f2426410add155079e04baff33bd Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 17 May 2006 05:55:40 -0400 Subject: [PATCH 03/18] 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 --- 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(+) create mode 100644 t/t1400-update-ref.sh diff --git a/Documentation/config.txt b/Documentation/config.txt index d1a4bec0d4..e178ee2de1 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 f0e710a6f8..dfbd886979 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 4b7a439253..2386b95e00 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 0248c6d8a5..2ae6153e5e 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 444c99ed6e..2e79eab18d 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 91c8c44a15..4be75a59aa 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 b7e9df2fa9..43831e9be7 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 0000000000..f338c53774 --- /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 From d556fae2c0ea063f15ea4c3678fe931cc426f1ca Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 17 May 2006 05:56:09 -0400 Subject: [PATCH 04/18] Support 'master@2 hours ago' syntax Extended sha1 expressions may now include date specifications which indicate a point in time within the local repository's history. If the ref indicated to the left of '@' has a log in $GIT_DIR/logs/ then the value of the ref at the time indicated by the specification is obtained from the ref's log. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-rev-parse.txt | 6 ++++ refs.c | 53 +++++++++++++++++++++++++++++ refs.h | 3 ++ sha1_name.c | 59 +++++++++++++++++++++++---------- 4 files changed, 104 insertions(+), 17 deletions(-) diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index ab896fcf83..df308c3f5f 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -124,6 +124,12 @@ 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 such as 'yesterday' + (24 hours ago) or '1 month 2 weeks 3 days 1 hour 1 second ago' + 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/). + * A suffix '{caret}' to a revision parameter means the first parent of that commit object. '{caret}' means the th parent (i.e. 'rev{caret}' diff --git a/refs.c b/refs.c index 4be75a59aa..4c99e3729f 100644 --- a/refs.c +++ b/refs.c @@ -429,3 +429,56 @@ int write_ref_sha1(struct ref_lock *lock, 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, *c; + char *tz_c; + int logfd, tz; + struct stat st; + unsigned long date; + + 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); + + rec = logend = logdata + st.st_size; + while (logdata < rec) { + if (logdata < rec && *(rec-1) == '\n') + rec--; + while (logdata < rec && *(rec-1) != '\n') + rec--; + c = rec; + while (c < logend && *c != '>' && *c != '\n') + c++; + if (c == logend || *c == '\n') + die("Log %s is corrupt.", logfile); + date = strtoul(c, NULL, 10); + if (date <= at_time) { + if (get_sha1_hex(rec + 41, sha1)) + die("Log %s is corrupt.", logfile); + munmap((void*)logdata, st.st_size); + return 0; + } + } + + c = logdata; + while (c < logend && *c != '>' && *c != '\n') + c++; + if (c == logend || *c == '\n') + die("Log %s is corrupt.", logfile); + date = strtoul(c, &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; +} diff --git a/refs.h b/refs.h index 43831e9be7..2c854de3fe 100644 --- a/refs.h +++ b/refs.h @@ -34,6 +34,9 @@ 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); diff --git a/sha1_name.c b/sha1_name.c index dc6835520c..3ac3ab4f58 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -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,60 @@ 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, at_mark; + 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 (at_mark = 1; at_mark < len; at_mark++) { + if (str[at_mark] == '@') { + int date_len = len - at_mark - 1; + char *date_spec = xmalloc(date_len + 1); + strncpy(date_spec, str + at_mark + 1, date_len); + date_spec[date_len] = 0; + at_time = approxidate(date_spec); + free(date_spec); + len = at_mark; + } + } + /* 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); From 70e34b2dc88e28f09903b85932ea679992da62ff Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 17 May 2006 18:34:48 -0400 Subject: [PATCH 05/18] Fix ref log parsing so it works properly. The log parser was only ever matching the last log record due to calling strtoul on "> 1136091609" rather than " 1136091609". Also once a match for '@' has been found after the name of the ref there is no point in looking for another '@' within the remaining text. Signed-off-by: Junio C Hamano --- refs.c | 2 +- sha1_name.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/refs.c b/refs.c index 4c99e3729f..ae9825dead 100644 --- a/refs.c +++ b/refs.c @@ -459,7 +459,7 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) c++; if (c == logend || *c == '\n') die("Log %s is corrupt.", logfile); - date = strtoul(c, NULL, 10); + date = strtoul(c + 1, NULL, 10); if (date <= at_time) { if (get_sha1_hex(rec + 41, sha1)) die("Log %s is corrupt.", logfile); diff --git a/sha1_name.c b/sha1_name.c index 3ac3ab4f58..4376cb3928 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -267,6 +267,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) at_time = approxidate(date_spec); free(date_spec); len = at_mark; + break; } } From e52290428b5b6f55cb7bb7a4624751750241d5aa Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 19 May 2006 03:28:19 -0400 Subject: [PATCH 06/18] General ref log reading improvements. Corrected the log starting time displayed in the error message (as it was always showing the epoch due to a bad input to strtoul). Improved the log parser so we only scan backwards towards the '\n' from the end of the prior log; during this scan the last '>' is remembered to improve performance (rather than scanning forward to it). If the log record matched is the last log record in the file only use its new sha1 value if the date matches exactly; otherwise we leave the passed in sha1 alone as it already contains the current value of the ref. This way lookups of dates later than the log end to stick with the current ref value in case the ref was updated without logging. If it looks like someone changed the ref without logging it and we are going to return the sha1 which should have been valid during the missing period then warn the user that there might be log data missing and thus their query result may not be accurate. The check isn't perfect as its just based on comparing the old and new sha1 values between the two log records but its better than not checking at all. Implemented test cases based on git-rev-parse for most of the boundary conditions. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- refs.c | 54 ++++++++++++++++++++++++++---------- t/t1400-update-ref.sh | 64 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 14 deletions(-) mode change 100644 => 100755 t/t1400-update-ref.sh diff --git a/refs.c b/refs.c index ae9825dead..9d37a028c3 100644 --- a/refs.c +++ b/refs.c @@ -432,11 +432,12 @@ int write_ref_sha1(struct ref_lock *lock, int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) { - const char *logfile, *logdata, *logend, *rec, *c; + 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); @@ -448,32 +449,57 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) 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--; - while (logdata < rec && *(rec-1) != '\n') + lastgt = NULL; + while (logdata < rec && *(rec-1) != '\n') { rec--; - c = rec; - while (c < logend && *c != '>' && *c != '\n') - c++; - if (c == logend || *c == '\n') + if (*rec == '>') + lastgt = rec; + } + if (!lastgt) die("Log %s is corrupt.", logfile); - date = strtoul(c + 1, NULL, 10); + date = strtoul(lastgt + 1, &tz_c, 10); if (date <= at_time) { - if (get_sha1_hex(rec + 41, sha1)) - die("Log %s is corrupt.", logfile); + 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; } - c = logdata; - while (c < logend && *c != '>' && *c != '\n') - c++; - if (c == logend || *c == '\n') + rec = logdata; + while (rec < logend && *rec != '>' && *rec != '\n') + rec++; + if (rec == logend || *rec == '\n') die("Log %s is corrupt.", logfile); - date = strtoul(c, &tz_c, 10); + 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); diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh old mode 100644 new mode 100755 index f338c53774..7858d86b93 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -9,6 +9,10 @@ test_description='Test git-update-ref and basic ref logging' Z=0000000000000000000000000000000000000000 A=1111111111111111111111111111111111111111 B=2222222222222222222222222222222222222222 +C=3333333333333333333333333333333333333333 +D=4444444444444444444444444444444444444444 +E=5555555555555555555555555555555555555555 +F=6666666666666666666666666666666666666666 m=refs/heads/master test_expect_success \ @@ -109,4 +113,64 @@ test_expect_success \ 'diff expect .git/logs/$m' rm -f .git/$m .git/logs/$m expect +git-update-ref $m $D +cat >.git/logs/$m < 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:33:01" (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)"' + test_done From c22a7f0fb96d5c742581422452d3d68139b79ad6 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 19 May 2006 03:28:46 -0400 Subject: [PATCH 07/18] Added logs/ directory to repository layout. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/repository-layout.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Documentation/repository-layout.txt b/Documentation/repository-layout.txt index 98fbe7db52..b52dfdc308 100644 --- a/Documentation/repository-layout.txt +++ b/Documentation/repository-layout.txt @@ -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`. From 732232a123e1e61e38babb1c572722bb8a189ba3 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 19 May 2006 03:29:05 -0400 Subject: [PATCH 08/18] Force writing ref if it doesn't exist. Normally we try to skip writing a ref if its value hasn't changed but in the special case that the ref doesn't exist but the new value is going to be 0{40} then force writing the ref anyway. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- refs.c | 4 +++- refs.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/refs.c b/refs.c index 9d37a028c3..31cf27668d 100644 --- a/refs.c +++ b/refs.c @@ -288,6 +288,7 @@ static struct ref_lock* lock_ref_sha1_basic(const char *path, 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; @@ -303,6 +304,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)); + 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); @@ -405,7 +407,7 @@ int write_ref_sha1(struct ref_lock *lock, if (!lock) return -1; - if (!memcmp(lock->old_sha1, sha1, 20)) { + if (!lock->force_write && !memcmp(lock->old_sha1, sha1, 20)) { unlock_ref(lock); return 0; } diff --git a/refs.h b/refs.h index 2c854de3fe..6c946eabcf 100644 --- a/refs.h +++ b/refs.h @@ -7,6 +7,7 @@ struct ref_lock { char *log_file; unsigned char old_sha1[20]; int lock_fd; + int force_write; }; /* From d0740d92beb019a7b02678e5acea79c0ff67e3ee Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 19 May 2006 03:29:26 -0400 Subject: [PATCH 09/18] Log ref updates made by fetch. If a ref is changed by http-fetch, local-fetch or ssh-fetch record the change and the remote URL/name in the log for the ref. This requires loading the config file to check logAllRefUpdates. Also fixed a bug in the ref lock generation; the log file name was not being produced right due to a bad prefix length. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- fetch.c | 17 +++++++++++++++-- fetch.h | 3 +++ http-fetch.c | 2 ++ local-fetch.c | 2 ++ refs.c | 5 +++-- ssh-fetch.c | 2 ++ 6 files changed, 27 insertions(+), 4 deletions(-) diff --git a/fetch.c b/fetch.c index 8bdaacb8e8..fd57684d8f 100644 --- a/fetch.c +++ b/fetch.c @@ -8,6 +8,7 @@ #include "refs.h" const char *write_ref = NULL; +const char *write_ref_log_details = NULL; const unsigned char *current_ref = NULL; @@ -206,13 +207,17 @@ int pull(char *target) { struct ref_lock *lock; 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, current_ref, 1); - if (!lock) + if (!lock) { + error("Can't lock ref %s", write_ref); return -1; + } } if (!get_recover) { @@ -234,7 +239,15 @@ int pull(char *target) } if (write_ref) { - return write_ref_sha1(lock, sha1, "git fetch"); + 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; } diff --git a/fetch.h b/fetch.h index 9837a3d035..0011548de8 100644 --- a/fetch.h +++ b/fetch.h @@ -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; + /* If set, the hash that the current value of write_ref must be. */ extern const unsigned char *current_ref; diff --git a/http-fetch.c b/http-fetch.c index 861644b27e..cc7bd1f367 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -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(); diff --git a/local-fetch.c b/local-fetch.c index fa9e697fd3..ffa4887570 100644 --- a/local-fetch.c +++ b/local-fetch.c @@ -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; diff --git a/refs.c b/refs.c index 31cf27668d..d3ddc82d26 100644 --- a/refs.c +++ b/refs.c @@ -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; @@ -296,7 +298,6 @@ static struct ref_lock* lock_ref_sha1_basic(const char *path, plen = strlen(path) - plen; path = resolve_ref(path, lock->old_sha1, mustexist); if (!path) { - error("Can't read ref %s", path); unlock_ref(lock); return NULL; } @@ -326,7 +327,7 @@ struct ref_lock* lock_ref_sha1(const char *ref, if (check_ref_format(ref)) return NULL; return lock_ref_sha1_basic(git_path("refs/%s", ref), - strlen(ref), old_sha1, mustexist); + 5 + strlen(ref), old_sha1, mustexist); } struct ref_lock* lock_any_ref_for_update(const char *ref, diff --git a/ssh-fetch.c b/ssh-fetch.c index 4eb9e04829..e3067b878e 100644 --- a/ssh-fetch.c +++ b/ssh-fetch.c @@ -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; From cce91a2caef9296bd4a69e51e48f1d679935d868 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 19 May 2006 03:29:43 -0400 Subject: [PATCH 10/18] Change 'master@noon' syntax to 'master@{noon}'. Its ambiguous to parse "master@2006-05-17 18:30:foo" when foo is meant as a file name and ":30" is meant as 30 minutes past 6 pm. Therefore all date specifications in a sha1 expression must now appear within brackets and the ':' splitter used for the path name in a sha1 expression ignores ':' appearing within brackets. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-rev-parse.txt | 11 ++++--- sha1_name.c | 27 ++++++++++------ t/t1400-update-ref.sh | 55 +++++++++++++++++++++++---------- 3 files changed, 62 insertions(+), 31 deletions(-) diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index df308c3f5f..b894694367 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -124,11 +124,12 @@ 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 such as 'yesterday' - (24 hours ago) or '1 month 2 weeks 3 days 1 hour 1 second ago' - 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/). +* 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/). * A suffix '{caret}' to a revision parameter means the first parent of that commit object. '{caret}' means the th parent (i.e. diff --git a/sha1_name.c b/sha1_name.c index 4376cb3928..fbbde1cf7d 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -249,7 +249,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) static const char *warning = "warning: refname '%.*s' is ambiguous.\n"; const char **p, *pathname; char *real_path = NULL; - int refs_found = 0, at_mark; + int refs_found = 0, am; unsigned long at_time = (unsigned long)-1; unsigned char *this_result; unsigned char sha1_from_ref[20]; @@ -257,16 +257,16 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) if (len == 40 && !get_sha1_hex(str, sha1)) return 0; - /* At a given period of time? "@2 hours ago" */ - for (at_mark = 1; at_mark < len; at_mark++) { - if (str[at_mark] == '@') { - int date_len = len - at_mark - 1; + /* 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 + at_mark + 1, date_len); + strncpy(date_spec, str + am + 2, date_len); date_spec[date_len] = 0; at_time = approxidate(date_spec); free(date_spec); - len = at_mark; + len = am; break; } } @@ -482,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; @@ -528,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, diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 7858d86b93..f6b076bafd 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -125,52 +125,75 @@ 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)' \ + 'Query "master@{May 25 2005}" (before history)' \ 'rm -f o e - git-rev-parse --verify "master@May 25 2005" >o 2>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)" \ + "Query master@{2005-05-25} (before history)" \ 'rm -f o e - git-rev-parse --verify master@2005-05-25 >o 2>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)' \ + '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 && + 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)' \ + '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 && + 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)' \ + '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 && + 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:33:01" (middle of history)' \ + '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 && + 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)' \ + '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 && + 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)' \ + 'Query "master@{2005-05-28}" (past end of history)' \ 'rm -f o e - git-rev-parse --verify "master@2005-05-28" >o 2>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' \ + 'cp ../../COPYING COPYING && + git-add COPYING && + GIT_COMMITTER_DATE="2005-05-26 23:30" git-commit -m add -a && + cp ../../Makefile COPYING && + GIT_COMMITTER_DATE="2005-05-26 23:41" git-commit -m change -a' + +test_expect_success \ + 'git-cat-file blob master:COPYING (expect Makefile)' \ + 'git-cat-file blob master:COPYING | diff - ../../Makefile' +test_expect_success \ + 'git-cat-file blob master@{2005-05-26 23:30}:COPYING (expect COPYING)' \ + 'git-cat-file blob "master@{2005-05-26 23:30}:COPYING" \ + | diff - ../../COPYING' +test_expect_success \ + 'git-cat-file blob master@{2005-05-26 23:42}:COPYING (expect Makefile)' \ + 'git-cat-file blob "master@{2005-05-26 23:42}:COPYING" \ + | diff - ../../Makefile' + test_done From 8fe92775f38621f2855fb94e404b9798cdcd98cc Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 19 May 2006 05:15:28 -0400 Subject: [PATCH 11/18] Correct force_write bug in refs.c My earlier attempt at forcing a write for non-existant refs worked; it forced a write for pretty much all refs. This corrects the condition to only force a write for refs which don't exist yet. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- refs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/refs.c b/refs.c index d3ddc82d26..eeb1196ec4 100644 --- a/refs.c +++ b/refs.c @@ -305,7 +305,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)); - lock->force_write = !lstat(lock->ref_file, &st) || errno == ENOENT; + 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); From 7792cc2fa1574b66b69a38a816a9b4bdca5c3731 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 19 May 2006 05:15:50 -0400 Subject: [PATCH 12/18] Change order of -m option to update-ref. The actual position doesn't matter but most people prefer to see options appear before the arguments. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-update-ref.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt index dfbd886979..e062030e91 100644 --- a/Documentation/git-update-ref.txt +++ b/Documentation/git-update-ref.txt @@ -7,7 +7,7 @@ git-update-ref - update the object name stored in a ref safely SYNOPSIS -------- -'git-update-ref' [] [-m ] +'git-update-ref' [-m ] [] DESCRIPTION ----------- From 67644a4d77f55cd1c960d046079d26719f1e7cd6 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 19 May 2006 05:16:18 -0400 Subject: [PATCH 13/18] Include ref log detail in commit, reset, etc. When updating a ref at the direction of the user include a reason why head was changed as part of the ref log (assuming it was enabled). Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- git-am.sh | 2 +- git-applypatch.sh | 2 +- git-branch.sh | 2 +- git-commit.sh | 3 ++- git-reset.sh | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/git-am.sh b/git-am.sh index 33f208cb0b..25c95c24fd 100755 --- a/git-am.sh +++ b/git-am.sh @@ -447,7 +447,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 diff --git a/git-applypatch.sh b/git-applypatch.sh index 12cab1e0d4..e4b09472e1 100755 --- a/git-applypatch.sh +++ b/git-applypatch.sh @@ -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 diff --git a/git-branch.sh b/git-branch.sh index 134e68cf7f..d1e3730761 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -117,4 +117,4 @@ then die "cannot force-update the current branch." fi fi -git update-ref "refs/heads/$branchname" $rev +git update-ref -m "branch: Created from $head" "refs/heads/$branchname" $rev diff --git a/git-commit.sh b/git-commit.sh index 6ef1a9dedc..a4b0a90740 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -676,7 +676,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 diff --git a/git-reset.sh b/git-reset.sh index 0ee3e3e154..296f3b779b 100755 --- a/git-reset.sh +++ b/git-reset.sh @@ -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 ) From 3a4b3f269c4ba1eebd5ab7206ee0915667b20cc8 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 19 May 2006 05:16:58 -0400 Subject: [PATCH 14/18] Create/delete branch ref logs. When crating a new branch offer '-l' as a way for the user to quickly enable ref logging for the new branch. When deleting a branch also delete its ref log. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-branch.txt | 10 ++++++++-- git-branch.sh | 12 +++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 72fb2f89b4..a7bec3c101 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git-branch' [-r] -'git-branch' [-f] [] +'git-branch' [-l] [-f] [] 'git-branch' (-d | -D) ... DESCRIPTION @@ -23,7 +23,8 @@ If no 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, `` 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 "@{yesterday}". + -f:: Force the creation of a new branch even if it means deleting a branch that already exists with the same name. diff --git a/git-branch.sh b/git-branch.sh index d1e3730761..e0501ec23f 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -1,6 +1,6 @@ #!/bin/sh -USAGE='[(-d | -D) ] | [[-f] []] | -r' +USAGE='[-l] [(-d | -D) ] | [[-f] []] | -r' LONG_USAGE='If no arguments, show available branches and mark current branch with a star. If one argument, create a new branch based off of current HEAD. If two arguments, create a new branch based off of .' @@ -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 +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 From 969d326d6b54573e171c903fdf9fdc6b107c18d1 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 19 May 2006 05:17:16 -0400 Subject: [PATCH 15/18] Enable ref log creation in git checkout -b. Switch git checkout -b to use git-update-ref rather than echo and a shell I/O redirection. This is more in line with typical GIT commands and allows -b to be logged according to the normal ref logging rules. Added -l option to allow users to create the ref log at the same time as creating a branch. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-checkout.txt | 7 ++++++- git-checkout.sh | 19 +++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 095128906a..0643943854 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -8,7 +8,7 @@ git-checkout - Checkout and switch to a branch SYNOPSIS -------- [verse] -'git-checkout' [-f] [-b ] [-m] [] +'git-checkout' [-f] [-b [-l]] [-m] [] 'git-checkout' [-m] [] ... DESCRIPTION @@ -37,6 +37,11 @@ OPTIONS -b:: Create a new branch and start it at . +-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 "@{yesterday}". + -m:: If you have local modifications to one or more files that are different between the current branch and the branch to diff --git a/git-checkout.sh b/git-checkout.sh index a11c939c30..360aabf9e3 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -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/$branchname") + touch "$GIT_DIR/logs/refs/heads/$branchname" + fi + git-update-ref -m "checkout: Created from $new_name" "refs/heads/$newbranch" $new || exit branch="$newbranch" fi [ "$branch" ] && From 9c7466fa248608ff8a91f8b4dca48dac7dda02d5 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 24 May 2006 23:33:18 -0400 Subject: [PATCH 16/18] Verify git-commit provides a reflog message. The reflog message from git-commit should include the first line of the commit message as supplied by the user. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- t/t1400-update-ref.sh | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index f6b076bafd..df3e993365 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -178,22 +178,36 @@ rm -f .git/$m .git/logs/$m expect test_expect_success \ 'creating initial files' \ - 'cp ../../COPYING COPYING && - git-add COPYING && + '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 && - cp ../../Makefile COPYING && - GIT_COMMITTER_DATE="2005-05-26 23:41" git-commit -m change -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 < 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:COPYING (expect Makefile)' \ - 'git-cat-file blob master:COPYING | diff - ../../Makefile' + '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}:COPYING (expect COPYING)' \ - 'git-cat-file blob "master@{2005-05-26 23:30}:COPYING" \ - | diff - ../../COPYING' + '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}:COPYING (expect Makefile)' \ - 'git-cat-file blob "master@{2005-05-26 23:42}:COPYING" \ - | diff - ../../Makefile' + '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 From d7fb7a373a8ea4a8ecf6421be92226bab74f474e Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 24 May 2006 23:34:04 -0400 Subject: [PATCH 17/18] Test that git-branch -l works. If the user supplies -l to git-branch when creating a new branch then the new branch's log should be created automatically and the branch creation should be logged in that log. Further if a branch is being deleted and it had a log then also verify that the log was deleted. Test git-checkout -b foo -l for creating a new branch foo with a log and checking out that branch. Fixed git-checkout -b foo -l as the branch variable name was incorrect in the script. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- git-checkout.sh | 4 ++-- t/t3200-branch.sh | 31 ++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/git-checkout.sh b/git-checkout.sh index 360aabf9e3..564117f006 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -197,8 +197,8 @@ fi if [ "$?" -eq 0 ]; then if [ "$newbranch" ]; then if [ "$newbranch_log" ]; then - mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname") - touch "$GIT_DIR/logs/refs/heads/$branchname" + 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" diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index c3de151942..5b04efc89d 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -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 < 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 < 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 From 99bd0f555823f3c3868561af33d85864a90a7d9a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 31 May 2006 15:23:44 -0700 Subject: [PATCH 18/18] fetch.c: do not pass uninitialized lock to unlock_ref(). Signed-off-by: Junio C Hamano --- fetch.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/fetch.c b/fetch.c index ae92d5212e..cc6013e7af 100644 --- a/fetch.c +++ b/fetch.c @@ -203,7 +203,7 @@ static int mark_complete(const char *path, const unsigned char *sha1) int pull(char *target) { - struct ref_lock *lock; + struct ref_lock *lock = NULL; unsigned char sha1[20]; char *msg; int ret; @@ -223,15 +223,18 @@ int pull(char *target) if (interpret_target(target, sha1)) { error("Could not interpret %s as something to pull", target); - unlock_ref(lock); + if (lock) + unlock_ref(lock); return -1; } if (process(lookup_unknown_object(sha1))) { - unlock_ref(lock); + if (lock) + unlock_ref(lock); return -1; } if (loop()) { - unlock_ref(lock); + if (lock) + unlock_ref(lock); return -1; }