mirror of
https://github.com/git/git.git
synced 2026-01-18 06:34:21 +00:00
Merge branch 'jc/ref-locking' into next
* jc/ref-locking: Teach receive-pack about ref-log update a few Porcelain-ish for ref lock safety. update-ref: -d flag and ref creation safety. Clean-up lock-ref implementation
This commit is contained in:
@@ -7,7 +7,7 @@ git-update-ref - update the object name stored in a ref safely
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
'git-update-ref' [-m <reason>] <ref> <newvalue> [<oldvalue>]
|
||||
'git-update-ref' [-m <reason>] (-d <ref> <oldvalue> | <ref> <newvalue> [<oldvalue>])
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
@@ -20,7 +20,9 @@ possibly dereferencing the symbolic refs, after verifying that
|
||||
the current value of the <ref> matches <oldvalue>.
|
||||
E.g. `git-update-ref refs/heads/master <newvalue> <oldvalue>`
|
||||
updates the master branch head to <newvalue> only if its current
|
||||
value is <oldvalue>.
|
||||
value is <oldvalue>. You can specify 40 "0" or an empty string
|
||||
as <oldvalue> to make sure that the ref you are creating does
|
||||
not exist.
|
||||
|
||||
It also allows a "ref" file to be a symbolic pointer to another
|
||||
ref file by starting with the four-byte header sequence of
|
||||
@@ -49,6 +51,10 @@ 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).
|
||||
|
||||
With `-d` flag, it deletes the named <ref> after verifying it
|
||||
still contains <oldvalue>.
|
||||
|
||||
|
||||
Logging Updates
|
||||
---------------
|
||||
If config parameter "core.logAllRefUpdates" is true or the file
|
||||
|
||||
@@ -53,7 +53,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1,
|
||||
/* make sure nobody touched the ref, and unlink */
|
||||
static void prune_ref(struct ref_to_prune *r)
|
||||
{
|
||||
struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1, 1);
|
||||
struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
|
||||
|
||||
if (lock) {
|
||||
unlink(git_path("%s", r->name));
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
#include "builtin.h"
|
||||
|
||||
static const char git_update_ref_usage[] =
|
||||
"git-update-ref <refname> <value> [<oldval>] [-m <reason>]";
|
||||
"git-update-ref [-m <reason>] (-d <refname> <value> | <refname> <value> [<oldval>])";
|
||||
|
||||
int cmd_update_ref(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
|
||||
struct ref_lock *lock;
|
||||
unsigned char sha1[20], oldsha1[20];
|
||||
int i;
|
||||
int i, delete;
|
||||
|
||||
delete = 0;
|
||||
setup_ident();
|
||||
git_config(git_default_config);
|
||||
|
||||
@@ -26,6 +27,10 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
|
||||
die("Refusing to perform update with \\n in message.");
|
||||
continue;
|
||||
}
|
||||
if (!strcmp("-d", argv[i])) {
|
||||
delete = 1;
|
||||
continue;
|
||||
}
|
||||
if (!refname) {
|
||||
refname = argv[i];
|
||||
continue;
|
||||
@@ -44,11 +49,18 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
|
||||
|
||||
if (get_sha1(value, sha1))
|
||||
die("%s: not a valid SHA1", value);
|
||||
|
||||
if (delete) {
|
||||
if (oldval)
|
||||
usage(git_update_ref_usage);
|
||||
return delete_ref(refname, sha1);
|
||||
}
|
||||
|
||||
hashclr(oldsha1);
|
||||
if (oldval && get_sha1(oldval, oldsha1))
|
||||
if (oldval && *oldval && get_sha1(oldval, oldsha1))
|
||||
die("%s: not a valid old SHA1", oldval);
|
||||
|
||||
lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, 0);
|
||||
lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL);
|
||||
if (!lock)
|
||||
return 1;
|
||||
if (write_ref_sha1(lock, sha1, msg) < 0)
|
||||
|
||||
1
cache.h
1
cache.h
@@ -179,6 +179,7 @@ struct lock_file {
|
||||
extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
|
||||
extern int commit_lock_file(struct lock_file *);
|
||||
extern void rollback_lock_file(struct lock_file *);
|
||||
extern int delete_ref(const char *, unsigned char *sha1);
|
||||
|
||||
/* Environment bits from configuration mechanism */
|
||||
extern int use_legacy_headers;
|
||||
|
||||
2
fetch.c
2
fetch.c
@@ -266,7 +266,7 @@ int pull(int targets, char **target, const char **write_ref,
|
||||
if (!write_ref || !write_ref[i])
|
||||
continue;
|
||||
|
||||
lock[i] = lock_ref_sha1(write_ref[i], NULL, 0);
|
||||
lock[i] = lock_ref_sha1(write_ref[i], NULL);
|
||||
if (!lock[i]) {
|
||||
error("Can't lock ref %s", write_ref[i]);
|
||||
goto unlock_and_fail;
|
||||
|
||||
@@ -42,8 +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"
|
||||
git update-ref -d "refs/heads/$branch_name" "$branch"
|
||||
echo "Deleted branch $branch_name."
|
||||
done
|
||||
exit 0
|
||||
@@ -122,6 +121,7 @@ then
|
||||
done
|
||||
fi
|
||||
|
||||
prev=''
|
||||
if git-show-ref --verify --quiet -- "refs/heads/$branchname"
|
||||
then
|
||||
if test '' = "$force"
|
||||
@@ -131,10 +131,11 @@ then
|
||||
then
|
||||
die "cannot force-update the current branch."
|
||||
fi
|
||||
prev=`git rev-parse --verify "refs/heads/$branchname"`
|
||||
fi
|
||||
if test "$create_log" = 'yes'
|
||||
then
|
||||
mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname")
|
||||
touch "$GIT_DIR/logs/refs/heads/$branchname"
|
||||
fi
|
||||
git update-ref -m "branch: Created from $head" "refs/heads/$branchname" $rev
|
||||
git update-ref -m "branch: Created from $head" "refs/heads/$branchname" "$rev" "$prev"
|
||||
|
||||
@@ -547,15 +547,15 @@ then
|
||||
PARENTS=$(git-cat-file commit HEAD |
|
||||
sed -n -e '/^$/q' -e 's/^parent /-p /p')
|
||||
fi
|
||||
current=$(git-rev-parse --verify HEAD)
|
||||
current="$(git-rev-parse --verify HEAD)"
|
||||
else
|
||||
if [ -z "$(git-ls-files)" ]; then
|
||||
echo >&2 Nothing to commit
|
||||
exit 1
|
||||
fi
|
||||
PARENTS=""
|
||||
current=
|
||||
rloga='commit (initial)'
|
||||
current=''
|
||||
fi
|
||||
|
||||
if test -z "$no_edit"
|
||||
@@ -631,7 +631,7 @@ then
|
||||
fi &&
|
||||
commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) &&
|
||||
rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
|
||||
git-update-ref -m "$rloga: $rlogm" HEAD $commit $current &&
|
||||
git-update-ref -m "$rloga: $rlogm" HEAD $commit "$current" &&
|
||||
rm -f -- "$GIT_DIR/MERGE_HEAD" &&
|
||||
if test -f "$NEXT_INDEX"
|
||||
then
|
||||
|
||||
@@ -63,8 +63,11 @@ done
|
||||
|
||||
name="$1"
|
||||
[ "$name" ] || usage
|
||||
if [ -e "$GIT_DIR/refs/tags/$name" -a -z "$force" ]; then
|
||||
die "tag '$name' already exists"
|
||||
prev=0000000000000000000000000000000000000000
|
||||
if test -e "$GIT_DIR/refs/tags/$name"
|
||||
then
|
||||
test -n "$force" || die "tag '$name' already exists"
|
||||
prev=`git rev-parse "refs/tags/$name"`
|
||||
fi
|
||||
shift
|
||||
git-check-ref-format "tags/$name" ||
|
||||
@@ -109,4 +112,4 @@ fi
|
||||
|
||||
leading=`expr "refs/tags/$name" : '\(.*\)/'` &&
|
||||
mkdir -p "$GIT_DIR/$leading" &&
|
||||
echo $object > "$GIT_DIR/refs/tags/$name"
|
||||
GIT_DIR="$GIT_DIR" git update-ref "refs/tags/$name" "$object" "$prev"
|
||||
|
||||
@@ -43,34 +43,6 @@ struct command {
|
||||
|
||||
static struct command *commands;
|
||||
|
||||
static int is_all_zeroes(const char *hex)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < 40; i++)
|
||||
if (*hex++ != '0')
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int verify_old_ref(const char *name, char *hex_contents)
|
||||
{
|
||||
int fd, ret;
|
||||
char buffer[60];
|
||||
|
||||
if (is_all_zeroes(hex_contents))
|
||||
return 0;
|
||||
fd = open(name, O_RDONLY);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
ret = read(fd, buffer, 40);
|
||||
close(fd);
|
||||
if (ret != 40)
|
||||
return -1;
|
||||
if (memcmp(buffer, hex_contents, 40))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char update_hook[] = "hooks/update";
|
||||
|
||||
static int run_update_hook(const char *refname,
|
||||
@@ -107,8 +79,8 @@ static int update(struct command *cmd)
|
||||
const char *name = cmd->ref_name;
|
||||
unsigned char *old_sha1 = cmd->old_sha1;
|
||||
unsigned char *new_sha1 = cmd->new_sha1;
|
||||
char new_hex[60], *old_hex, *lock_name;
|
||||
int newfd, namelen, written;
|
||||
char new_hex[41], old_hex[41];
|
||||
struct ref_lock *lock;
|
||||
|
||||
cmd->error_string = NULL;
|
||||
if (!strncmp(name, "refs/", 5) && check_ref_format(name + 5)) {
|
||||
@@ -117,13 +89,8 @@ static int update(struct command *cmd)
|
||||
name);
|
||||
}
|
||||
|
||||
namelen = strlen(name);
|
||||
lock_name = xmalloc(namelen + 10);
|
||||
memcpy(lock_name, name, namelen);
|
||||
memcpy(lock_name + namelen, ".lock", 6);
|
||||
|
||||
strcpy(new_hex, sha1_to_hex(new_sha1));
|
||||
old_hex = sha1_to_hex(old_sha1);
|
||||
strcpy(old_hex, sha1_to_hex(old_sha1));
|
||||
if (!has_sha1_file(new_sha1)) {
|
||||
cmd->error_string = "bad pack";
|
||||
return error("unpack should have generated %s, "
|
||||
@@ -144,47 +111,20 @@ static int update(struct command *cmd)
|
||||
return error("denying non-fast forward;"
|
||||
" you should pull first");
|
||||
}
|
||||
safe_create_leading_directories(lock_name);
|
||||
|
||||
newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0666);
|
||||
if (newfd < 0) {
|
||||
cmd->error_string = "can't lock";
|
||||
return error("unable to create %s (%s)",
|
||||
lock_name, strerror(errno));
|
||||
}
|
||||
|
||||
/* Write the ref with an ending '\n' */
|
||||
new_hex[40] = '\n';
|
||||
new_hex[41] = 0;
|
||||
written = write(newfd, new_hex, 41);
|
||||
/* Remove the '\n' again */
|
||||
new_hex[40] = 0;
|
||||
|
||||
close(newfd);
|
||||
if (written != 41) {
|
||||
unlink(lock_name);
|
||||
cmd->error_string = "can't write";
|
||||
return error("unable to write %s", lock_name);
|
||||
}
|
||||
if (verify_old_ref(name, old_hex) < 0) {
|
||||
unlink(lock_name);
|
||||
cmd->error_string = "raced";
|
||||
return error("%s changed during push", name);
|
||||
}
|
||||
if (run_update_hook(name, old_hex, new_hex)) {
|
||||
unlink(lock_name);
|
||||
cmd->error_string = "hook declined";
|
||||
return error("hook declined to update %s", name);
|
||||
}
|
||||
else if (rename(lock_name, name) < 0) {
|
||||
unlink(lock_name);
|
||||
cmd->error_string = "can't rename";
|
||||
return error("unable to replace %s", name);
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
|
||||
return 0;
|
||||
|
||||
lock = lock_any_ref_for_update(name, old_sha1);
|
||||
if (!lock) {
|
||||
cmd->error_string = "failed to lock";
|
||||
return error("failed to lock %s", name);
|
||||
}
|
||||
write_ref_sha1(lock, new_sha1, "push");
|
||||
|
||||
fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char update_post_hook[] = "hooks/post-update";
|
||||
@@ -335,9 +275,11 @@ int main(int argc, char **argv)
|
||||
if (!dir)
|
||||
usage(receive_pack_usage);
|
||||
|
||||
if(!enter_repo(dir, 0))
|
||||
if (!enter_repo(dir, 0))
|
||||
die("'%s': unable to chdir or not a git archive", dir);
|
||||
|
||||
git_config(git_default_config);
|
||||
|
||||
write_head_info();
|
||||
|
||||
/* EOF */
|
||||
|
||||
39
refs.c
39
refs.c
@@ -378,6 +378,32 @@ int get_ref_sha1(const char *ref, unsigned char *sha1)
|
||||
return read_ref(mkpath("refs/%s", ref), sha1);
|
||||
}
|
||||
|
||||
int delete_ref(const char *refname, unsigned char *sha1)
|
||||
{
|
||||
struct ref_lock *lock;
|
||||
int err, i, ret = 0;
|
||||
|
||||
lock = lock_any_ref_for_update(refname, sha1);
|
||||
if (!lock)
|
||||
return 1;
|
||||
i = strlen(lock->lk->filename) - 5; /* .lock */
|
||||
lock->lk->filename[i] = 0;
|
||||
err = unlink(lock->lk->filename);
|
||||
if (err) {
|
||||
ret = 1;
|
||||
error("unlink(%s) failed: %s",
|
||||
lock->lk->filename, strerror(errno));
|
||||
}
|
||||
lock->lk->filename[i] = '.';
|
||||
|
||||
err = unlink(lock->log_file);
|
||||
if (err && errno != ENOENT)
|
||||
fprintf(stderr, "warning: unlink(%s) failed: %s",
|
||||
lock->log_file, strerror(errno));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure "ref" is something reasonable to have under ".git/refs/";
|
||||
* We do not like it if:
|
||||
@@ -447,12 +473,13 @@ static struct ref_lock *verify_lock(struct ref_lock *lock,
|
||||
return lock;
|
||||
}
|
||||
|
||||
static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int mustexist)
|
||||
static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1)
|
||||
{
|
||||
char *ref_file;
|
||||
const char *orig_ref = ref;
|
||||
struct ref_lock *lock;
|
||||
struct stat st;
|
||||
int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
|
||||
|
||||
lock = xcalloc(1, sizeof(struct ref_lock));
|
||||
lock->lock_fd = -1;
|
||||
@@ -480,20 +507,18 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
|
||||
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)
|
||||
struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
|
||||
{
|
||||
char refpath[PATH_MAX];
|
||||
if (check_ref_format(ref))
|
||||
return NULL;
|
||||
strcpy(refpath, mkpath("refs/%s", ref));
|
||||
return lock_ref_sha1_basic(refpath, old_sha1, mustexist);
|
||||
return lock_ref_sha1_basic(refpath, old_sha1);
|
||||
}
|
||||
|
||||
struct ref_lock *lock_any_ref_for_update(const char *ref,
|
||||
const unsigned char *old_sha1, int mustexist)
|
||||
struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1)
|
||||
{
|
||||
return lock_ref_sha1_basic(ref, old_sha1, mustexist);
|
||||
return lock_ref_sha1_basic(ref, old_sha1);
|
||||
}
|
||||
|
||||
void unlock_ref(struct ref_lock *lock)
|
||||
|
||||
4
refs.h
4
refs.h
@@ -27,10 +27,10 @@ extern int for_each_remote_ref(each_ref_fn, void *);
|
||||
extern int get_ref_sha1(const char *ref, unsigned char *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);
|
||||
extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_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);
|
||||
extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1);
|
||||
|
||||
/** Release any lock taken but not written. **/
|
||||
extern void unlock_ref(struct ref_lock *lock);
|
||||
|
||||
Reference in New Issue
Block a user