Merge branch 'sp/reflog'

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

View file

@ -70,6 +70,14 @@ core.preferSymlinkRefs::
This is sometimes needed to work with old scripts that This is sometimes needed to work with old scripts that
expect HEAD to be a symbolic link. 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:: core.repositoryFormatVersion::
Internal variable identifying the repository format and layout Internal variable identifying the repository format and layout
version. version.

View file

@ -9,7 +9,7 @@ SYNOPSIS
-------- --------
[verse] [verse]
'git-branch' [-r] 'git-branch' [-r]
'git-branch' [-f] <branchname> [<start-point>] 'git-branch' [-l] [-f] <branchname> [<start-point>]
'git-branch' (-d | -D) <branchname>... 'git-branch' (-d | -D) <branchname>...
DESCRIPTION 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. equal to that of the currently checked out branch.
With a `-d` or `-D` option, `<branchname>` will be deleted. You may 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 OPTIONS
@ -34,6 +35,11 @@ OPTIONS
-D:: -D::
Delete a branch irrespective of its index status. 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:: -f::
Force the creation of a new branch even if it means deleting Force the creation of a new branch even if it means deleting
a branch that already exists with the same name. a branch that already exists with the same name.

View file

@ -8,7 +8,7 @@ git-checkout - Checkout and switch to a branch
SYNOPSIS SYNOPSIS
-------- --------
[verse] [verse]
'git-checkout' [-f] [-b <new_branch>] [-m] [<branch>] 'git-checkout' [-f] [-b <new_branch> [-l]] [-m] [<branch>]
'git-checkout' [-m] [<branch>] <paths>... 'git-checkout' [-m] [<branch>] <paths>...
DESCRIPTION DESCRIPTION
@ -40,6 +40,11 @@ OPTIONS
by gitlink:git-check-ref-format[1]. Some of these checks by gitlink:git-check-ref-format[1]. Some of these checks
may restrict the characters allowed in a branch name. 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:: -m::
If you have local modifications to one or more files that If you have local modifications to one or more files that
are different between the current branch and the branch to are different between the current branch and the branch to

View file

@ -124,6 +124,13 @@ syntax.
happen to have both heads/master and tags/master, you can happen to have both heads/master and tags/master, you can
explicitly say 'heads/master' to tell git which one you mean. 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 * A suffix '{caret}' to a revision parameter means the first parent of
that commit object. '{caret}<n>' means the <n>th parent (i.e. that commit object. '{caret}<n>' means the <n>th parent (i.e.
'rev{caret}' 'rev{caret}'

View file

@ -7,7 +7,7 @@ git-update-ref - update the object name stored in a ref safely
SYNOPSIS SYNOPSIS
-------- --------
'git-update-ref' <ref> <newvalue> [<oldvalue>] 'git-update-ref' [-m <reason>] <ref> <newvalue> [<oldvalue>]
DESCRIPTION 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 ref symlink to some other tree, if you have copied a whole
archive by creating a symlink tree). 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 Author
------ ------
Written by Linus Torvalds <torvalds@osdl.org>. Written by Linus Torvalds <torvalds@osdl.org>.

View file

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

View file

@ -179,6 +179,7 @@ extern void rollback_index_file(struct cache_file *);
extern int trust_executable_bit; extern int trust_executable_bit;
extern int assume_unchanged; extern int assume_unchanged;
extern int prefer_symlink_refs; extern int prefer_symlink_refs;
extern int log_all_ref_updates;
extern int warn_ambiguous_refs; extern int warn_ambiguous_refs;
extern int diff_rename_limit_default; extern int diff_rename_limit_default;
extern int shared_repository; extern int shared_repository;

View file

@ -269,6 +269,11 @@ int git_default_config(const char *var, const char *value)
return 0; return 0;
} }
if (!strcmp(var, "core.logallrefupdates")) {
log_all_ref_updates = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "core.warnambiguousrefs")) { if (!strcmp(var, "core.warnambiguousrefs")) {
warn_ambiguous_refs = git_config_bool(var, value); warn_ambiguous_refs = git_config_bool(var, value);
return 0; return 0;

View file

@ -14,6 +14,7 @@ char git_default_name[MAX_GITNAME];
int trust_executable_bit = 1; int trust_executable_bit = 1;
int assume_unchanged = 0; int assume_unchanged = 0;
int prefer_symlink_refs = 0; int prefer_symlink_refs = 0;
int log_all_ref_updates = 0;
int warn_ambiguous_refs = 1; int warn_ambiguous_refs = 1;
int repository_format_version = 0; int repository_format_version = 0;
char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";

45
fetch.c
View file

@ -8,6 +8,7 @@
#include "refs.h" #include "refs.h"
const char *write_ref = NULL; const char *write_ref = NULL;
const char *write_ref_log_details = NULL;
int get_tree = 0; int get_tree = 0;
int get_history = 0; int get_history = 0;
@ -202,23 +203,51 @@ static int mark_complete(const char *path, const unsigned char *sha1)
int pull(char *target) int pull(char *target)
{ {
struct ref_lock *lock = NULL;
unsigned char sha1[20]; unsigned char sha1[20];
char *msg;
int ret;
save_commit_buffer = 0; save_commit_buffer = 0;
track_object_refs = 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) if (!get_recover)
for_each_ref(mark_complete); for_each_ref(mark_complete);
if (interpret_target(target, sha1)) if (interpret_target(target, sha1)) {
return error("Could not interpret %s as something to pull", error("Could not interpret %s as something to pull", target);
target); if (lock)
if (process(lookup_unknown_object(sha1))) unlock_ref(lock);
return -1; return -1;
if (loop()) }
if (process(lookup_unknown_object(sha1))) {
if (lock)
unlock_ref(lock);
return -1; return -1;
}
if (write_ref) if (loop()) {
write_ref_sha1_unlocked(write_ref, sha1); 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; return 0;
} }

View file

@ -25,6 +25,9 @@ extern int fetch_ref(char *ref, unsigned char *sha1);
/* If set, the ref filename to write the target value to. */ /* If set, the ref filename to write the target value to. */
extern const char *write_ref; 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. */ /* Set to fetch the target tree. */
extern int get_tree; extern int get_tree;

View file

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

View file

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

View file

@ -1,6 +1,6 @@
#!/bin/sh #!/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. 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 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>.' 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
;; ;;
esac esac
rm -f "$GIT_DIR/logs/refs/heads/$branch_name"
rm -f "$GIT_DIR/refs/heads/$branch_name" rm -f "$GIT_DIR/refs/heads/$branch_name"
echo "Deleted branch $branch_name." echo "Deleted branch $branch_name."
done done
@ -55,6 +56,7 @@ ls_remote_branches () {
} }
force= force=
create_log=
while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
do do
case "$1" in case "$1" in
@ -69,6 +71,9 @@ do
-f) -f)
force="$1" force="$1"
;; ;;
-l)
create_log="yes"
;;
--) --)
shift shift
break break
@ -117,4 +122,9 @@ then
die "cannot force-update the current branch." die "cannot force-update the current branch."
fi fi
fi fi
git update-ref "refs/heads/$branchname" $rev if test "$create_log" = 'yes'
then
mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname")
touch "$GIT_DIR/logs/refs/heads/$branchname"
fi
git update-ref -m "branch: Created from $head" "refs/heads/$branchname" $rev

View file

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

View file

@ -713,7 +713,8 @@ then
rm -f "$TMP_INDEX" rm -f "$TMP_INDEX"
fi && fi &&
commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) && 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" && rm -f -- "$GIT_DIR/MERGE_HEAD" &&
if test -f "$NEXT_INDEX" if test -f "$NEXT_INDEX"
then then

View file

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

View file

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

View file

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

378
refs.c
View file

@ -142,6 +142,8 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
namelen = strlen(de->d_name); namelen = strlen(de->d_name);
if (namelen > 255) if (namelen > 255)
continue; continue;
if (namelen>5 && !strcmp(de->d_name+namelen-5,".lock"))
continue;
memcpy(path + baselen, de->d_name, namelen+1); memcpy(path + baselen, de->d_name, namelen+1);
if (stat(git_path("%s", path), &st) < 0) if (stat(git_path("%s", path), &st) < 0)
continue; 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); 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) int get_ref_sha1(const char *ref, unsigned char *sha1)
{ {
if (check_ref_format(ref)) 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); 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/"; * Make sure "ref" is something reasonable to have under ".git/refs/";
* We do not like it if: * We do not like it if:
@ -365,25 +259,255 @@ int check_ref_format(const char *ref)
} }
} }
int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1) static struct ref_lock* verify_lock(struct ref_lock *lock,
const unsigned char *old_sha1, int mustexist)
{ {
char *filename; char buf[40];
char *lock_filename; int nr, fd = open(lock->ref_file, O_RDONLY);
int fd; if (fd < 0 && (mustexist || errno != ENOENT)) {
int retval; error("Can't verify ref %s", lock->ref_file);
if (check_ref_format(ref)) unlock_ref(lock);
return -1; return NULL;
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");
} }
retval = write_ref_file(filename, lock_filename, fd, sha1); nr = read(fd, buf, 40);
free(filename); close(fd);
free(lock_filename); if (nr != 40 || get_sha1_hex(buf, lock->old_sha1) < 0) {
return retval; error("Can't verify ref %s", lock->ref_file);
unlock_ref(lock);
return NULL;
}
if (memcmp(lock->old_sha1, old_sha1, 20)) {
error("Ref %s is at %s but expected %s", lock->ref_file,
sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1));
unlock_ref(lock);
return NULL;
}
return lock;
}
static struct ref_lock* lock_ref_sha1_basic(const char *path,
int plen,
const unsigned char *old_sha1, int mustexist)
{
struct ref_lock *lock;
struct stat st;
lock = xcalloc(1, sizeof(struct ref_lock));
lock->lock_fd = -1;
plen = strlen(path) - plen;
path = resolve_ref(path, lock->old_sha1, mustexist);
if (!path) {
unlock_ref(lock);
return NULL;
}
lock->ref_file = strdup(path);
lock->lock_file = strdup(mkpath("%s.lock", lock->ref_file));
lock->log_file = strdup(git_path("logs/%s", lock->ref_file + plen));
lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT;
if (safe_create_leading_directories(lock->lock_file))
die("unable to create directory for %s", lock->lock_file);
lock->lock_fd = open(lock->lock_file,
O_WRONLY | O_CREAT | O_EXCL, 0666);
if (lock->lock_fd < 0) {
error("Couldn't open lock file %s: %s",
lock->lock_file, strerror(errno));
unlock_ref(lock);
return NULL;
}
return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
}
struct ref_lock* lock_ref_sha1(const char *ref,
const unsigned char *old_sha1, int mustexist)
{
if (check_ref_format(ref))
return NULL;
return lock_ref_sha1_basic(git_path("refs/%s", ref),
5 + strlen(ref), old_sha1, mustexist);
}
struct ref_lock* lock_any_ref_for_update(const char *ref,
const unsigned char *old_sha1, int mustexist)
{
return lock_ref_sha1_basic(git_path("%s", ref),
strlen(ref), old_sha1, mustexist);
}
void unlock_ref (struct ref_lock *lock)
{
if (lock->lock_fd >= 0) {
close(lock->lock_fd);
unlink(lock->lock_file);
}
if (lock->ref_file)
free(lock->ref_file);
if (lock->lock_file)
free(lock->lock_file);
if (lock->log_file)
free(lock->log_file);
free(lock);
}
static int log_ref_write(struct ref_lock *lock,
const unsigned char *sha1, const char *msg)
{
int logfd, written, oflags = O_APPEND | O_WRONLY;
unsigned maxlen, len;
char *logrec;
const char *comitter;
if (log_all_ref_updates) {
if (safe_create_leading_directories(lock->log_file) < 0)
return error("unable to create directory for %s",
lock->log_file);
oflags |= O_CREAT;
}
logfd = open(lock->log_file, oflags, 0666);
if (logfd < 0) {
if (!log_all_ref_updates && errno == ENOENT)
return 0;
return error("Unable to append to %s: %s",
lock->log_file, strerror(errno));
}
setup_ident();
comitter = git_committer_info(1);
if (msg) {
maxlen = strlen(comitter) + strlen(msg) + 2*40 + 5;
logrec = xmalloc(maxlen);
len = snprintf(logrec, maxlen, "%s %s %s\t%s\n",
sha1_to_hex(lock->old_sha1),
sha1_to_hex(sha1),
comitter,
msg);
} else {
maxlen = strlen(comitter) + 2*40 + 4;
logrec = xmalloc(maxlen);
len = snprintf(logrec, maxlen, "%s %s %s\n",
sha1_to_hex(lock->old_sha1),
sha1_to_hex(sha1),
comitter);
}
written = len <= maxlen ? write(logfd, logrec, len) : -1;
free(logrec);
close(logfd);
if (written != len)
return error("Unable to append to %s", lock->log_file);
return 0;
}
int write_ref_sha1(struct ref_lock *lock,
const unsigned char *sha1, const char *logmsg)
{
static char term = '\n';
if (!lock)
return -1;
if (!lock->force_write && !memcmp(lock->old_sha1, sha1, 20)) {
unlock_ref(lock);
return 0;
}
if (write(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
write(lock->lock_fd, &term, 1) != 1
|| close(lock->lock_fd) < 0) {
error("Couldn't write %s", lock->lock_file);
unlock_ref(lock);
return -1;
}
if (log_ref_write(lock, sha1, logmsg) < 0) {
unlock_ref(lock);
return -1;
}
if (rename(lock->lock_file, lock->ref_file) < 0) {
error("Couldn't set %s", lock->ref_file);
unlock_ref(lock);
return -1;
}
lock->lock_fd = -1;
unlock_ref(lock);
return 0;
}
int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
{
const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
char *tz_c;
int logfd, tz;
struct stat st;
unsigned long date;
unsigned char logged_sha1[20];
logfile = git_path("logs/%s", ref);
logfd = open(logfile, O_RDONLY, 0);
if (logfd < 0)
die("Unable to read log %s: %s", logfile, strerror(errno));
fstat(logfd, &st);
if (!st.st_size)
die("Log %s is empty.", logfile);
logdata = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);
close(logfd);
lastrec = NULL;
rec = logend = logdata + st.st_size;
while (logdata < rec) {
if (logdata < rec && *(rec-1) == '\n')
rec--;
lastgt = NULL;
while (logdata < rec && *(rec-1) != '\n') {
rec--;
if (*rec == '>')
lastgt = rec;
}
if (!lastgt)
die("Log %s is corrupt.", logfile);
date = strtoul(lastgt + 1, &tz_c, 10);
if (date <= at_time) {
if (lastrec) {
if (get_sha1_hex(lastrec, logged_sha1))
die("Log %s is corrupt.", logfile);
if (get_sha1_hex(rec + 41, sha1))
die("Log %s is corrupt.", logfile);
if (memcmp(logged_sha1, sha1, 20)) {
tz = strtoul(tz_c, NULL, 10);
fprintf(stderr,
"warning: Log %s has gap after %s.\n",
logfile, show_rfc2822_date(date, tz));
}
} else if (date == at_time) {
if (get_sha1_hex(rec + 41, sha1))
die("Log %s is corrupt.", logfile);
} else {
if (get_sha1_hex(rec + 41, logged_sha1))
die("Log %s is corrupt.", logfile);
if (memcmp(logged_sha1, sha1, 20)) {
tz = strtoul(tz_c, NULL, 10);
fprintf(stderr,
"warning: Log %s unexpectedly ended on %s.\n",
logfile, show_rfc2822_date(date, tz));
}
}
munmap((void*)logdata, st.st_size);
return 0;
}
lastrec = rec;
}
rec = logdata;
while (rec < logend && *rec != '>' && *rec != '\n')
rec++;
if (rec == logend || *rec == '\n')
die("Log %s is corrupt.", logfile);
date = strtoul(rec + 1, &tz_c, 10);
tz = strtoul(tz_c, NULL, 10);
if (get_sha1_hex(logdata, sha1))
die("Log %s is corrupt.", logfile);
munmap((void*)logdata, st.st_size);
fprintf(stderr, "warning: Log %s only goes back to %s.\n",
logfile, show_rfc2822_date(date, tz));
return 0;
} }

29
refs.h
View file

@ -1,6 +1,15 @@
#ifndef REFS_H #ifndef REFS_H
#define 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, * Calls the specified function for each ref file until it returns nonzero,
* and returns the value * 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 **/ /** Reads the refs file specified into sha1 **/
extern int get_ref_sha1(const char *ref, unsigned char *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 /** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
* has the given value currently; otherwise, returns -1. extern struct ref_lock* lock_ref_sha1(const char *ref, const unsigned char *old_sha1, int mustexist);
**/
extern int lock_ref_sha1(const char *ref, const unsigned char *old_sha1);
/** Writes sha1 into the refs file specified, locked with the given fd. **/ /** Locks any ref (for 'HEAD' type refs). */
extern int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1); 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. **/ /** Release any lock taken but not written. **/
extern int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1); 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. **/ /** Returns 0 if target has the right format for a ref. **/
extern int check_ref_format(const char *target); extern int check_ref_format(const char *target);

View file

@ -4,6 +4,7 @@
#include "tree.h" #include "tree.h"
#include "blob.h" #include "blob.h"
#include "tree-walk.h" #include "tree-walk.h"
#include "refs.h"
static int find_short_object_filename(int len, const char *name, unsigned char *sha1) 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", "refs/remotes/%.*s/HEAD",
NULL NULL
}; };
const char **p; static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
const char *warning = "warning: refname '%.*s' is ambiguous.\n"; const char **p, *pathname;
char *pathname; char *real_path = NULL;
int already_found = 0; int refs_found = 0, am;
unsigned long at_time = (unsigned long)-1;
unsigned char *this_result; unsigned char *this_result;
unsigned char sha1_from_ref[20]; unsigned char sha1_from_ref[20];
if (len == 40 && !get_sha1_hex(str, sha1)) if (len == 40 && !get_sha1_hex(str, sha1))
return 0; 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. */ /* Accept only unambiguous ref paths. */
if (ambiguous_path(str, len)) if (ambiguous_path(str, len))
return -1; return -1;
for (p = fmt; *p; p++) { for (p = fmt; *p; p++) {
this_result = already_found ? sha1_from_ref : sha1; this_result = refs_found ? sha1_from_ref : sha1;
pathname = git_path(*p, len, str); pathname = resolve_ref(git_path(*p, len, str), this_result, 1);
if (!read_ref(pathname, this_result)) { if (pathname) {
if (warn_ambiguous_refs) { if (!refs_found++)
if (already_found) real_path = strdup(pathname);
fprintf(stderr, warning, len, str); if (!warn_ambiguous_refs)
already_found++; break;
}
else
return 0;
} }
} }
if (already_found)
return 0; if (!refs_found)
return -1; 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); 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 get_sha1(const char *name, unsigned char *sha1)
{ {
int ret; int ret, bracket_depth;
unsigned unused; unsigned unused;
int namelen = strlen(name); int namelen = strlen(name);
const char *cp; const char *cp;
@ -502,8 +528,15 @@ int get_sha1(const char *name, unsigned char *sha1)
} }
return -1; return -1;
} }
cp = strchr(name, ':'); for (cp = name, bracket_depth = 0; *cp; cp++) {
if (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]; unsigned char tree_sha1[20];
if (!get_sha1_1(name, cp-name, tree_sha1)) if (!get_sha1_1(name, cp-name, tree_sha1))
return get_tree_entry(tree_sha1, cp+1, sha1, return get_tree_entry(tree_sha1, cp+1, sha1,

View file

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

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

@ -0,0 +1,213 @@
#!/bin/sh
#
# Copyright (c) 2006 Shawn Pearce
#
test_description='Test git-update-ref and basic ref logging'
. ./test-lib.sh
Z=0000000000000000000000000000000000000000
A=1111111111111111111111111111111111111111
B=2222222222222222222222222222222222222222
C=3333333333333333333333333333333333333333
D=4444444444444444444444444444444444444444
E=5555555555555555555555555555555555555555
F=6666666666666666666666666666666666666666
m=refs/heads/master
test_expect_success \
"create $m" \
'git-update-ref $m $A &&
test $A = $(cat .git/$m)'
test_expect_success \
"create $m" \
'git-update-ref $m $B $A &&
test $B = $(cat .git/$m)'
rm -f .git/$m
test_expect_success \
"create $m (by HEAD)" \
'git-update-ref HEAD $A &&
test $A = $(cat .git/$m)'
test_expect_success \
"create $m (by HEAD)" \
'git-update-ref HEAD $B $A &&
test $B = $(cat .git/$m)'
rm -f .git/$m
test_expect_failure \
'(not) create HEAD with old sha1' \
'git-update-ref HEAD $A $B'
test_expect_failure \
"(not) prior created .git/$m" \
'test -f .git/$m'
rm -f .git/$m
test_expect_success \
"create HEAD" \
'git-update-ref HEAD $A'
test_expect_failure \
'(not) change HEAD with wrong SHA1' \
'git-update-ref HEAD $B $Z'
test_expect_failure \
"(not) changed .git/$m" \
'test $B = $(cat .git/$m)'
rm -f .git/$m
mkdir -p .git/logs/refs/heads
touch .git/logs/refs/heads/master
test_expect_success \
"create $m (logged by touch)" \
'GIT_COMMITTER_DATE="2005-05-26 23:30" \
git-update-ref HEAD $A -m "Initial Creation" &&
test $A = $(cat .git/$m)'
test_expect_success \
"update $m (logged by touch)" \
'GIT_COMMITTER_DATE="2005-05-26 23:31" \
git-update-ref HEAD $B $A -m "Switch" &&
test $B = $(cat .git/$m)'
test_expect_success \
"set $m (logged by touch)" \
'GIT_COMMITTER_DATE="2005-05-26 23:41" \
git-update-ref HEAD $A &&
test $A = $(cat .git/$m)'
cat >expect <<EOF
$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 Initial Creation
$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150260 +0000 Switch
$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000
EOF
test_expect_success \
"verifying $m's log" \
'diff expect .git/logs/$m'
rm -rf .git/$m .git/logs expect
test_expect_success \
'enable core.logAllRefUpdates' \
'git-repo-config core.logAllRefUpdates true &&
test true = $(git-repo-config --bool --get core.logAllRefUpdates)'
test_expect_success \
"create $m (logged by config)" \
'GIT_COMMITTER_DATE="2005-05-26 23:32" \
git-update-ref HEAD $A -m "Initial Creation" &&
test $A = $(cat .git/$m)'
test_expect_success \
"update $m (logged by config)" \
'GIT_COMMITTER_DATE="2005-05-26 23:33" \
git-update-ref HEAD $B $A -m "Switch" &&
test $B = $(cat .git/$m)'
test_expect_success \
"set $m (logged by config)" \
'GIT_COMMITTER_DATE="2005-05-26 23:43" \
git-update-ref HEAD $A &&
test $A = $(cat .git/$m)'
cat >expect <<EOF
$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000 Initial Creation
$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 +0000 Switch
$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 +0000
EOF
test_expect_success \
"verifying $m's log" \
'diff expect .git/logs/$m'
rm -f .git/$m .git/logs/$m expect
git-update-ref $m $D
cat >.git/logs/$m <<EOF
$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500
EOF
ed="Thu, 26 May 2005 18:32:00 -0500"
gd="Thu, 26 May 2005 18:33:00 -0500"
ld="Thu, 26 May 2005 18:43:00 -0500"
test_expect_success \
'Query "master@{May 25 2005}" (before history)' \
'rm -f o e
git-rev-parse --verify "master@{May 25 2005}" >o 2>e &&
test $C = $(cat o) &&
test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
test_expect_success \
"Query master@{2005-05-25} (before history)" \
'rm -f o e
git-rev-parse --verify master@{2005-05-25} >o 2>e &&
test $C = $(cat o) &&
echo test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
test_expect_success \
'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \
'rm -f o e
git-rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
test $C = $(cat o) &&
test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
test_expect_success \
'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \
'rm -f o e
git-rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
test $A = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \
'rm -f o e
git-rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
test $B = $(cat o) &&
test "warning: Log .git/logs/$m has gap after $gd." = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:38:00}" (middle of history)' \
'rm -f o e
git-rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
test $Z = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \
'rm -f o e
git-rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
test $E = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-28}" (past end of history)' \
'rm -f o e
git-rev-parse --verify "master@{2005-05-28}" >o 2>e &&
test $D = $(cat o) &&
test "warning: Log .git/logs/$m unexpectedly ended on $ld." = "$(cat e)"'
rm -f .git/$m .git/logs/$m expect
test_expect_success \
'creating initial files' \
'echo TEST >F &&
git-add F &&
GIT_AUTHOR_DATE="2005-05-26 23:30" \
GIT_COMMITTER_DATE="2005-05-26 23:30" git-commit -m add -a &&
h_TEST=$(git-rev-parse --verify HEAD)
echo The other day this did not work. >M &&
echo And then Bob told me how to fix it. >>M &&
echo OTHER >F &&
GIT_AUTHOR_DATE="2005-05-26 23:41" \
GIT_COMMITTER_DATE="2005-05-26 23:41" git-commit -F M -a &&
h_OTHER=$(git-rev-parse --verify HEAD)
rm -f M'
cat >expect <<EOF
$Z $h_TEST $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 commit: add
$h_TEST $h_OTHER $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000 commit: The other day this did not work.
EOF
test_expect_success \
'git-commit logged updates' \
'diff expect .git/logs/$m'
unset h_TEST h_OTHER
test_expect_success \
'git-cat-file blob master:F (expect OTHER)' \
'test OTHER = $(git-cat-file blob master:F)'
test_expect_success \
'git-cat-file blob master@{2005-05-26 23:30}:F (expect TEST)' \
'test TEST = $(git-cat-file blob "master@{2005-05-26 23:30}:F")'
test_expect_success \
'git-cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \
'test OTHER = $(git-cat-file blob "master@{2005-05-26 23:42}:F")'
test_done

View file

@ -14,7 +14,8 @@ test_expect_success \
'prepare an trivial repository' \ 'prepare an trivial repository' \
'echo Hello > A && 'echo Hello > A &&
git-update-index --add 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 \ test_expect_success \
'git branch --help should return success now.' \ '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 should create a branch' \
'git-branch a/b/c && test -f .git/refs/heads/a/b/c' '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 test_done

View file

@ -1,85 +1,56 @@
#include "cache.h" #include "cache.h"
#include "refs.h" #include "refs.h"
static const char git_update_ref_usage[] = "git-update-ref <refname> <value> [<oldval>]"; static const char git_update_ref_usage[] =
"git-update-ref <refname> <value> [<oldval>] [-m <reason>]";
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;
}
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
char *hex; const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
const char *refname, *value, *oldval, *path; struct ref_lock *lock;
char *lockpath; unsigned char sha1[20], oldsha1[20];
unsigned char sha1[20], oldsha1[20], currsha1[20]; int i;
int fd, written;
setup_git_directory(); setup_git_directory();
git_config(git_default_config); 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); usage(git_update_ref_usage);
refname = argv[1];
value = argv[2];
oldval = argv[3];
if (get_sha1(value, sha1)) if (get_sha1(value, sha1))
die("%s: not a valid SHA1", value); die("%s: not a valid SHA1", value);
memset(oldsha1, 0, 20); memset(oldsha1, 0, 20);
if (oldval && get_sha1(oldval, oldsha1)) if (oldval && get_sha1(oldval, oldsha1))
die("%s: not a valid old SHA1", oldval); die("%s: not a valid old SHA1", oldval);
path = resolve_ref(git_path("%s", refname), currsha1, !!oldval); lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, 0);
if (!path) if (!lock)
die("No such ref: %s", refname); return 1;
if (write_ref_sha1(lock, sha1, msg) < 0)
if (oldval) { return 1;
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);
}
return 0; return 0;
} }