mirror of
https://github.com/git/git
synced 2024-08-28 20:20:07 +00: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
|
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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}'
|
||||||
|
|
|
@ -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>.
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
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 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;
|
||||||
|
|
5
config.c
5
config.c
|
@ -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;
|
||||||
|
|
|
@ -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
45
fetch.c
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
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. */
|
/* 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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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" ] &&
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 )
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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
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);
|
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
29
refs.h
|
@ -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);
|
||||||
|
|
73
sha1_name.c
73
sha1_name.c
|
@ -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,
|
||||||
|
|
|
@ -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
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' \
|
'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
|
||||||
|
|
103
update-ref.c
103
update-ref.c
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue