Merge branch 'sp/reflog'
authorJunio C Hamano <junkio@cox.net>
Sun, 4 Jun 2006 06:59:03 +0000 (23:59 -0700)
committerJunio C Hamano <junkio@cox.net>
Sun, 4 Jun 2006 06:59:03 +0000 (23:59 -0700)
* 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.

26 files changed:
Documentation/config.txt
Documentation/git-branch.txt
Documentation/git-checkout.txt
Documentation/git-rev-parse.txt
Documentation/git-update-ref.txt
Documentation/repository-layout.txt
cache.h
config.c
environment.c
fetch.c
fetch.h
git-am.sh
git-applypatch.sh
git-branch.sh
git-checkout.sh
git-commit.sh
git-reset.sh
http-fetch.c
local-fetch.c
refs.c
refs.h
sha1_name.c
ssh-fetch.c
t/t1400-update-ref.sh [new file with mode: 0755]
t/t3200-branch.sh
update-ref.c

index cd56afd..c861c6c 100644 (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.
index 0b37e2b..d43ef1d 100644 (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.
index d82efc0..fbdbadc 100644 (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
index c4b0ff5..627cde8 100644 (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}'
index 475237f..e062030 100644 (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>.
index 98fbe7d..b52dfdc 100644 (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`.
diff --git a/cache.h b/cache.h
index f11d5e7..d530af9 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -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;
index 0248c6d..2ae6153 100644 (file)
--- 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;
index 444c99e..2e79eab 100644 (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";
diff --git a/fetch.c b/fetch.c
index f7f8902..cc6013e 100644 (file)
--- 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;
 
 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;
 }
diff --git a/fetch.h b/fetch.h
index 001a6b8..841bb1a 100644 (file)
--- 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;
+
 /* Set to fetch the target tree. */
 extern int get_tree;
 
index 97ec2d0..4232e27 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -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
index 12cab1e..e4b0947 100755 (executable)
@@ -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
index 134e68c..e0501ec 100755 (executable)
@@ -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
index a11c939..564117f 100755 (executable)
@@ -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" ] &&
index 15482d2..6dd04fd 100755 (executable)
@@ -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
index 0ee3e3e..296f3b7 100755 (executable)
@@ -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 )
index 178f1ee..661c909 100644 (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();
 
index fa9e697..ffa4887 100644 (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;
diff --git a/refs.c b/refs.c
index 0f3491f..eeb1196 100644 (file)
--- 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;
@@ -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 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;
+       }
+       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)
 {
-       char *filename;
-       char *lock_filename;
-       int fd;
-       int retval;
        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;
-       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");
+       if (!lock->force_write && !memcmp(lock->old_sha1, sha1, 20)) {
+               unlock_ref(lock);
+               return 0;
        }
-       retval = write_ref_file(filename, lock_filename, fd, sha1);
-       free(filename);
-       free(lock_filename);
-       return retval;
+       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;
 }
diff --git a/refs.h b/refs.h
index fa816c1..6c946ea 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -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);
+
+/** 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);
+
+/** Release any lock taken but not written. **/
+extern void unlock_ref (struct ref_lock *lock);
 
-/** 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);
+/** 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);
 
-/** Writes sha1 into the refs file specified. **/
-extern int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1);
+/** 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);
index dc68355..fbbde1c 100644 (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,
index 4eb9e04..e3067b8 100644 (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;
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
new file mode 100755 (executable)
index 0000000..df3e993
--- /dev/null
@@ -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
index c3de151..5b04efc 100755 (executable)
@@ -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
index fd48742..a1e6bb9 100644 (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;
 }