mirror of
https://github.com/git/git.git
synced 2024-11-24 10:26:17 +08:00
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:
commit
f0679f474a
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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}'
|
||||
|
@ -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>.
|
||||
|
@ -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`.
|
||||
|
1
cache.h
1
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;
|
||||
|
5
config.c
5
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;
|
||||
|
@ -14,6 +14,7 @@ char git_default_name[MAX_GITNAME];
|
||||
int trust_executable_bit = 1;
|
||||
int assume_unchanged = 0;
|
||||
int prefer_symlink_refs = 0;
|
||||
int log_all_ref_updates = 0;
|
||||
int warn_ambiguous_refs = 1;
|
||||
int repository_format_version = 0;
|
||||
char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
|
||||
|
45
fetch.c
45
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;
|
||||
}
|
||||
|
3
fetch.h
3
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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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" ] &&
|
||||
|
@ -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
|
||||
|
@ -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 )
|
||||
|
@ -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();
|
||||
|
||||
|
@ -208,6 +208,7 @@ int main(int argc, char **argv)
|
||||
int arg = 1;
|
||||
|
||||
setup_git_directory();
|
||||
git_config(git_default_config);
|
||||
|
||||
while (arg < argc && argv[arg][0] == '-') {
|
||||
if (argv[arg][1] == 't')
|
||||
@ -239,6 +240,7 @@ int main(int argc, char **argv)
|
||||
usage(local_pull_usage);
|
||||
commit_id = argv[arg];
|
||||
path = argv[arg + 1];
|
||||
write_ref_log_details = path;
|
||||
|
||||
if (pull(commit_id))
|
||||
return 1;
|
||||
|
378
refs.c
378
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 *filename;
|
||||
char *lock_filename;
|
||||
int fd;
|
||||
int retval;
|
||||
if (check_ref_format(ref))
|
||||
return -1;
|
||||
filename = ref_file_name(ref);
|
||||
lock_filename = ref_lock_file_name(ref);
|
||||
if (safe_create_leading_directories(filename))
|
||||
die("unable to create leading directory for %s", filename);
|
||||
fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
|
||||
if (fd < 0) {
|
||||
error("Writing %s", lock_filename);
|
||||
perror("Open");
|
||||
char buf[40];
|
||||
int nr, fd = open(lock->ref_file, O_RDONLY);
|
||||
if (fd < 0 && (mustexist || errno != ENOENT)) {
|
||||
error("Can't verify ref %s", lock->ref_file);
|
||||
unlock_ref(lock);
|
||||
return NULL;
|
||||
}
|
||||
retval = write_ref_file(filename, lock_filename, fd, sha1);
|
||||
free(filename);
|
||||
free(lock_filename);
|
||||
return retval;
|
||||
nr = read(fd, buf, 40);
|
||||
close(fd);
|
||||
if (nr != 40 || get_sha1_hex(buf, lock->old_sha1) < 0) {
|
||||
error("Can't verify ref %s", lock->ref_file);
|
||||
unlock_ref(lock);
|
||||
return NULL;
|
||||
}
|
||||
if (memcmp(lock->old_sha1, old_sha1, 20)) {
|
||||
error("Ref %s is at %s but expected %s", lock->ref_file,
|
||||
sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1));
|
||||
unlock_ref(lock);
|
||||
return NULL;
|
||||
}
|
||||
return lock;
|
||||
}
|
||||
|
||||
static struct ref_lock* lock_ref_sha1_basic(const char *path,
|
||||
int plen,
|
||||
const unsigned char *old_sha1, int mustexist)
|
||||
{
|
||||
struct ref_lock *lock;
|
||||
struct stat st;
|
||||
|
||||
lock = xcalloc(1, sizeof(struct ref_lock));
|
||||
lock->lock_fd = -1;
|
||||
|
||||
plen = strlen(path) - plen;
|
||||
path = resolve_ref(path, lock->old_sha1, mustexist);
|
||||
if (!path) {
|
||||
unlock_ref(lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lock->ref_file = strdup(path);
|
||||
lock->lock_file = strdup(mkpath("%s.lock", lock->ref_file));
|
||||
lock->log_file = strdup(git_path("logs/%s", lock->ref_file + plen));
|
||||
lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT;
|
||||
|
||||
if (safe_create_leading_directories(lock->lock_file))
|
||||
die("unable to create directory for %s", lock->lock_file);
|
||||
lock->lock_fd = open(lock->lock_file,
|
||||
O_WRONLY | O_CREAT | O_EXCL, 0666);
|
||||
if (lock->lock_fd < 0) {
|
||||
error("Couldn't open lock file %s: %s",
|
||||
lock->lock_file, strerror(errno));
|
||||
unlock_ref(lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
|
||||
}
|
||||
|
||||
struct ref_lock* lock_ref_sha1(const char *ref,
|
||||
const unsigned char *old_sha1, int mustexist)
|
||||
{
|
||||
if (check_ref_format(ref))
|
||||
return NULL;
|
||||
return lock_ref_sha1_basic(git_path("refs/%s", ref),
|
||||
5 + strlen(ref), old_sha1, mustexist);
|
||||
}
|
||||
|
||||
struct ref_lock* lock_any_ref_for_update(const char *ref,
|
||||
const unsigned char *old_sha1, int mustexist)
|
||||
{
|
||||
return lock_ref_sha1_basic(git_path("%s", ref),
|
||||
strlen(ref), old_sha1, mustexist);
|
||||
}
|
||||
|
||||
void unlock_ref (struct ref_lock *lock)
|
||||
{
|
||||
if (lock->lock_fd >= 0) {
|
||||
close(lock->lock_fd);
|
||||
unlink(lock->lock_file);
|
||||
}
|
||||
if (lock->ref_file)
|
||||
free(lock->ref_file);
|
||||
if (lock->lock_file)
|
||||
free(lock->lock_file);
|
||||
if (lock->log_file)
|
||||
free(lock->log_file);
|
||||
free(lock);
|
||||
}
|
||||
|
||||
static int log_ref_write(struct ref_lock *lock,
|
||||
const unsigned char *sha1, const char *msg)
|
||||
{
|
||||
int logfd, written, oflags = O_APPEND | O_WRONLY;
|
||||
unsigned maxlen, len;
|
||||
char *logrec;
|
||||
const char *comitter;
|
||||
|
||||
if (log_all_ref_updates) {
|
||||
if (safe_create_leading_directories(lock->log_file) < 0)
|
||||
return error("unable to create directory for %s",
|
||||
lock->log_file);
|
||||
oflags |= O_CREAT;
|
||||
}
|
||||
|
||||
logfd = open(lock->log_file, oflags, 0666);
|
||||
if (logfd < 0) {
|
||||
if (!log_all_ref_updates && errno == ENOENT)
|
||||
return 0;
|
||||
return error("Unable to append to %s: %s",
|
||||
lock->log_file, strerror(errno));
|
||||
}
|
||||
|
||||
setup_ident();
|
||||
comitter = git_committer_info(1);
|
||||
if (msg) {
|
||||
maxlen = strlen(comitter) + strlen(msg) + 2*40 + 5;
|
||||
logrec = xmalloc(maxlen);
|
||||
len = snprintf(logrec, maxlen, "%s %s %s\t%s\n",
|
||||
sha1_to_hex(lock->old_sha1),
|
||||
sha1_to_hex(sha1),
|
||||
comitter,
|
||||
msg);
|
||||
} else {
|
||||
maxlen = strlen(comitter) + 2*40 + 4;
|
||||
logrec = xmalloc(maxlen);
|
||||
len = snprintf(logrec, maxlen, "%s %s %s\n",
|
||||
sha1_to_hex(lock->old_sha1),
|
||||
sha1_to_hex(sha1),
|
||||
comitter);
|
||||
}
|
||||
written = len <= maxlen ? write(logfd, logrec, len) : -1;
|
||||
free(logrec);
|
||||
close(logfd);
|
||||
if (written != len)
|
||||
return error("Unable to append to %s", lock->log_file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int write_ref_sha1(struct ref_lock *lock,
|
||||
const unsigned char *sha1, const char *logmsg)
|
||||
{
|
||||
static char term = '\n';
|
||||
|
||||
if (!lock)
|
||||
return -1;
|
||||
if (!lock->force_write && !memcmp(lock->old_sha1, sha1, 20)) {
|
||||
unlock_ref(lock);
|
||||
return 0;
|
||||
}
|
||||
if (write(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
|
||||
write(lock->lock_fd, &term, 1) != 1
|
||||
|| close(lock->lock_fd) < 0) {
|
||||
error("Couldn't write %s", lock->lock_file);
|
||||
unlock_ref(lock);
|
||||
return -1;
|
||||
}
|
||||
if (log_ref_write(lock, sha1, logmsg) < 0) {
|
||||
unlock_ref(lock);
|
||||
return -1;
|
||||
}
|
||||
if (rename(lock->lock_file, lock->ref_file) < 0) {
|
||||
error("Couldn't set %s", lock->ref_file);
|
||||
unlock_ref(lock);
|
||||
return -1;
|
||||
}
|
||||
lock->lock_fd = -1;
|
||||
unlock_ref(lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
|
||||
{
|
||||
const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
|
||||
char *tz_c;
|
||||
int logfd, tz;
|
||||
struct stat st;
|
||||
unsigned long date;
|
||||
unsigned char logged_sha1[20];
|
||||
|
||||
logfile = git_path("logs/%s", ref);
|
||||
logfd = open(logfile, O_RDONLY, 0);
|
||||
if (logfd < 0)
|
||||
die("Unable to read log %s: %s", logfile, strerror(errno));
|
||||
fstat(logfd, &st);
|
||||
if (!st.st_size)
|
||||
die("Log %s is empty.", logfile);
|
||||
logdata = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);
|
||||
close(logfd);
|
||||
|
||||
lastrec = NULL;
|
||||
rec = logend = logdata + st.st_size;
|
||||
while (logdata < rec) {
|
||||
if (logdata < rec && *(rec-1) == '\n')
|
||||
rec--;
|
||||
lastgt = NULL;
|
||||
while (logdata < rec && *(rec-1) != '\n') {
|
||||
rec--;
|
||||
if (*rec == '>')
|
||||
lastgt = rec;
|
||||
}
|
||||
if (!lastgt)
|
||||
die("Log %s is corrupt.", logfile);
|
||||
date = strtoul(lastgt + 1, &tz_c, 10);
|
||||
if (date <= at_time) {
|
||||
if (lastrec) {
|
||||
if (get_sha1_hex(lastrec, logged_sha1))
|
||||
die("Log %s is corrupt.", logfile);
|
||||
if (get_sha1_hex(rec + 41, sha1))
|
||||
die("Log %s is corrupt.", logfile);
|
||||
if (memcmp(logged_sha1, sha1, 20)) {
|
||||
tz = strtoul(tz_c, NULL, 10);
|
||||
fprintf(stderr,
|
||||
"warning: Log %s has gap after %s.\n",
|
||||
logfile, show_rfc2822_date(date, tz));
|
||||
}
|
||||
} else if (date == at_time) {
|
||||
if (get_sha1_hex(rec + 41, sha1))
|
||||
die("Log %s is corrupt.", logfile);
|
||||
} else {
|
||||
if (get_sha1_hex(rec + 41, logged_sha1))
|
||||
die("Log %s is corrupt.", logfile);
|
||||
if (memcmp(logged_sha1, sha1, 20)) {
|
||||
tz = strtoul(tz_c, NULL, 10);
|
||||
fprintf(stderr,
|
||||
"warning: Log %s unexpectedly ended on %s.\n",
|
||||
logfile, show_rfc2822_date(date, tz));
|
||||
}
|
||||
}
|
||||
munmap((void*)logdata, st.st_size);
|
||||
return 0;
|
||||
}
|
||||
lastrec = rec;
|
||||
}
|
||||
|
||||
rec = logdata;
|
||||
while (rec < logend && *rec != '>' && *rec != '\n')
|
||||
rec++;
|
||||
if (rec == logend || *rec == '\n')
|
||||
die("Log %s is corrupt.", logfile);
|
||||
date = strtoul(rec + 1, &tz_c, 10);
|
||||
tz = strtoul(tz_c, NULL, 10);
|
||||
if (get_sha1_hex(logdata, sha1))
|
||||
die("Log %s is corrupt.", logfile);
|
||||
munmap((void*)logdata, st.st_size);
|
||||
fprintf(stderr, "warning: Log %s only goes back to %s.\n",
|
||||
logfile, show_rfc2822_date(date, tz));
|
||||
return 0;
|
||||
}
|
||||
|
29
refs.h
29
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);
|
||||
|
||||
/** Writes sha1 into the refs file specified, locked with the given fd. **/
|
||||
extern int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1);
|
||||
/** Locks any ref (for 'HEAD' type refs). */
|
||||
extern struct ref_lock* lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int mustexist);
|
||||
|
||||
/** Writes sha1 into the refs file specified. **/
|
||||
extern int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1);
|
||||
/** Release any lock taken but not written. **/
|
||||
extern void unlock_ref (struct ref_lock *lock);
|
||||
|
||||
/** Writes sha1 into the ref specified by the lock. **/
|
||||
extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
|
||||
|
||||
/** Reads log for the value of ref during at_time. **/
|
||||
extern int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1);
|
||||
|
||||
/** Returns 0 if target has the right format for a ref. **/
|
||||
extern int check_ref_format(const char *target);
|
||||
|
73
sha1_name.c
73
sha1_name.c
@ -4,6 +4,7 @@
|
||||
#include "tree.h"
|
||||
#include "blob.h"
|
||||
#include "tree-walk.h"
|
||||
#include "refs.h"
|
||||
|
||||
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
|
||||
{
|
||||
@ -245,36 +246,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,
|
||||
|
@ -132,6 +132,7 @@ int main(int argc, char **argv)
|
||||
if (!prog) prog = "git-ssh-upload";
|
||||
|
||||
setup_git_directory();
|
||||
git_config(git_default_config);
|
||||
|
||||
while (arg < argc && argv[arg][0] == '-') {
|
||||
if (argv[arg][1] == 't') {
|
||||
@ -158,6 +159,7 @@ int main(int argc, char **argv)
|
||||
}
|
||||
commit_id = argv[arg];
|
||||
url = argv[arg + 1];
|
||||
write_ref_log_details = url;
|
||||
|
||||
if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1))
|
||||
return 1;
|
||||
|
213
t/t1400-update-ref.sh
Executable file
213
t/t1400-update-ref.sh
Executable 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
|
@ -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
|
||||
|
103
update-ref.c
103
update-ref.c
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user