Merge branch 'mh/split-under-lock'

Further preparatory work on the refs API before the pluggable
backend series can land.

* mh/split-under-lock: (33 commits)
  lock_ref_sha1_basic(): only handle REF_NODEREF mode
  commit_ref_update(): remove the flags parameter
  lock_ref_for_update(): don't resolve symrefs
  lock_ref_for_update(): don't re-read non-symbolic references
  refs: resolve symbolic refs first
  ref_transaction_update(): check refname_is_safe() at a minimum
  unlock_ref(): move definition higher in the file
  lock_ref_for_update(): new function
  add_update(): initialize the whole ref_update
  verify_refname_available(): adjust constness in declaration
  refs: don't dereference on rename
  refs: allow log-only updates
  delete_branches(): use resolve_refdup()
  ref_transaction_commit(): correctly report close_ref() failure
  ref_transaction_create(): disallow recursive pruning
  refs: make error messages more consistent
  lock_ref_sha1_basic(): remove unneeded local variable
  read_raw_ref(): move docstring to header file
  read_raw_ref(): improve docstring
  read_raw_ref(): rename symref argument to referent
  ...
This commit is contained in:
Junio C Hamano 2016-07-25 14:13:32 -07:00
Родитель 08bb3500a2 7a418f3a17
Коммит 6b34ce90a7
10 изменённых файлов: 1018 добавлений и 262 удалений

Просмотреть файл

@ -212,7 +212,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
die(_("Couldn't look up commit object for HEAD"));
}
for (i = 0; i < argc; i++, strbuf_release(&bname)) {
const char *target;
char *target = NULL;
int flags = 0;
strbuf_branchname(&bname, argv[i]);
@ -231,11 +231,11 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
}
}
target = resolve_ref_unsafe(name,
RESOLVE_REF_READING
| RESOLVE_REF_NO_RECURSE
| RESOLVE_REF_ALLOW_BAD_NAME,
sha1, &flags);
target = resolve_refdup(name,
RESOLVE_REF_READING
| RESOLVE_REF_NO_RECURSE
| RESOLVE_REF_ALLOW_BAD_NAME,
sha1, &flags);
if (!target) {
error(remote_branch
? _("remote-tracking branch '%s' not found.")
@ -248,7 +248,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
check_branch_commit(bname.buf, name, sha1, head_rev, kinds,
force)) {
ret = 1;
continue;
goto next;
}
if (delete_ref(name, is_null_sha1(sha1) ? NULL : sha1,
@ -258,7 +258,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
: _("Error deleting branch '%s'"),
bname.buf);
ret = 1;
continue;
goto next;
}
if (!quiet) {
printf(remote_branch
@ -270,6 +270,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
: find_unique_abbrev(sha1, DEFAULT_ABBREV));
}
delete_branch_config(bname.buf);
next:
free(target);
}
free(name);

Просмотреть файл

@ -1003,6 +1003,11 @@ int adjust_shared_perm(const char *path);
* directory while we were working. To be robust against this kind of
* race, callers might want to try invoking the function again when it
* returns SCLD_VANISHED.
*
* safe_create_leading_directories() temporarily changes path while it
* is working but restores it before returning.
* safe_create_leading_directories_const() doesn't modify path, even
* temporarily.
*/
enum scld_error {
SCLD_OK = 0,

23
dir.h
Просмотреть файл

@ -262,9 +262,32 @@ extern int is_empty_dir(const char *dir);
extern void setup_standard_excludes(struct dir_struct *dir);
/* Constants for remove_dir_recursively: */
/*
* If a non-directory is found within path, stop and return an error.
* (In this case some empty directories might already have been
* removed.)
*/
#define REMOVE_DIR_EMPTY_ONLY 01
/*
* If any Git work trees are found within path, skip them without
* considering it an error.
*/
#define REMOVE_DIR_KEEP_NESTED_GIT 02
/* Remove the contents of path, but leave path itself. */
#define REMOVE_DIR_KEEP_TOPLEVEL 04
/*
* Remove path and its contents, recursively. flags is a combination
* of the above REMOVE_DIR_* constants. Return 0 on success.
*
* This function uses path as temporary scratch space, but restores it
* before returning.
*/
extern int remove_dir_recursively(struct strbuf *path, int flag);
/* tries to remove the path with empty directories along it, ignores ENOENT */

76
refs.c
Просмотреть файл

@ -120,25 +120,33 @@ int check_refname_format(const char *refname, int flags)
int refname_is_safe(const char *refname)
{
if (starts_with(refname, "refs/")) {
const char *rest;
if (skip_prefix(refname, "refs/", &rest)) {
char *buf;
int result;
size_t restlen = strlen(rest);
/* rest must not be empty, or start or end with "/" */
if (!restlen || *rest == '/' || rest[restlen - 1] == '/')
return 0;
buf = xmallocz(strlen(refname));
/*
* Does the refname try to escape refs/?
* For example: refs/foo/../bar is safe but refs/foo/../../bar
* is not.
*/
result = !normalize_path_copy(buf, refname + strlen("refs/"));
buf = xmallocz(restlen);
result = !normalize_path_copy(buf, rest) && !strcmp(buf, rest);
free(buf);
return result;
}
while (*refname) {
do {
if (!isupper(*refname) && *refname != '_')
return 0;
refname++;
}
} while (*refname);
return 1;
}
@ -496,7 +504,7 @@ static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
filename = git_path("%s", pseudoref);
fd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR);
if (fd < 0) {
strbuf_addf(err, "Could not open '%s' for writing: %s",
strbuf_addf(err, "could not open '%s' for writing: %s",
filename, strerror(errno));
return -1;
}
@ -507,14 +515,14 @@ static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
if (read_ref(pseudoref, actual_old_sha1))
die("could not read ref '%s'", pseudoref);
if (hashcmp(actual_old_sha1, old_sha1)) {
strbuf_addf(err, "Unexpected sha1 when writing %s", pseudoref);
strbuf_addf(err, "unexpected sha1 when writing '%s'", pseudoref);
rollback_lock_file(&lock);
goto done;
}
}
if (write_in_full(fd, buf.buf, buf.len) != buf.len) {
strbuf_addf(err, "Could not write to '%s'", filename);
strbuf_addf(err, "could not write to '%s'", filename);
rollback_lock_file(&lock);
goto done;
}
@ -758,13 +766,33 @@ void ref_transaction_free(struct ref_transaction *transaction)
free(transaction);
}
static struct ref_update *add_update(struct ref_transaction *transaction,
const char *refname)
struct ref_update *ref_transaction_add_update(
struct ref_transaction *transaction,
const char *refname, unsigned int flags,
const unsigned char *new_sha1,
const unsigned char *old_sha1,
const char *msg)
{
struct ref_update *update;
if (transaction->state != REF_TRANSACTION_OPEN)
die("BUG: update called for transaction that is not open");
if ((flags & REF_ISPRUNING) && !(flags & REF_NODEREF))
die("BUG: REF_ISPRUNING set without REF_NODEREF");
FLEX_ALLOC_STR(update, refname, refname);
ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
transaction->updates[transaction->nr++] = update;
update->flags = flags;
if (flags & REF_HAVE_NEW)
hashcpy(update->new_sha1, new_sha1);
if (flags & REF_HAVE_OLD)
hashcpy(update->old_sha1, old_sha1);
if (msg)
update->msg = xstrdup(msg);
return update;
}
@ -775,32 +803,20 @@ int ref_transaction_update(struct ref_transaction *transaction,
unsigned int flags, const char *msg,
struct strbuf *err)
{
struct ref_update *update;
assert(err);
if (transaction->state != REF_TRANSACTION_OPEN)
die("BUG: update called for transaction that is not open");
if (new_sha1 && !is_null_sha1(new_sha1) &&
check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
strbuf_addf(err, "refusing to update ref with bad name %s",
if ((new_sha1 && !is_null_sha1(new_sha1)) ?
check_refname_format(refname, REFNAME_ALLOW_ONELEVEL) :
!refname_is_safe(refname)) {
strbuf_addf(err, "refusing to update ref with bad name '%s'",
refname);
return -1;
}
update = add_update(transaction, refname);
if (new_sha1) {
hashcpy(update->new_sha1, new_sha1);
flags |= REF_HAVE_NEW;
}
if (old_sha1) {
hashcpy(update->old_sha1, old_sha1);
flags |= REF_HAVE_OLD;
}
update->flags = flags;
if (msg)
update->msg = xstrdup(msg);
flags |= (new_sha1 ? REF_HAVE_NEW : 0) | (old_sha1 ? REF_HAVE_OLD : 0);
ref_transaction_add_update(transaction, refname, flags,
new_sha1, old_sha1, msg);
return 0;
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -15,7 +15,7 @@
/*
* Used as a flag in ref_update::flags when a loose ref is being
* pruned.
* pruned. This flag must only be used when REF_NODEREF is set.
*/
#define REF_ISPRUNING 0x04
@ -42,6 +42,19 @@
* value to ref_update::flags
*/
/*
* Used as a flag in ref_update::flags when we want to log a ref
* update but not actually perform it. This is used when a symbolic
* ref update is split up.
*/
#define REF_LOG_ONLY 0x80
/*
* Internal flag, meaning that the containing ref_update was via an
* update to HEAD.
*/
#define REF_UPDATE_VIA_HEAD 0x100
/*
* Return true iff refname is minimally safe. "Safe" here means that
* deleting a loose reference by this name will not do any damage, for
@ -109,8 +122,8 @@ enum peel_status peel_object(const unsigned char *name, unsigned char *sha1);
* extras and skip must be sorted.
*/
int verify_refname_available(const char *newname,
struct string_list *extras,
struct string_list *skip,
const struct string_list *extras,
const struct string_list *skip,
struct strbuf *err);
/*
@ -130,26 +143,58 @@ int should_autocreate_reflog(const char *refname);
* not exist before update.
*/
struct ref_update {
/*
* If (flags & REF_HAVE_NEW), set the reference to this value:
*/
unsigned char new_sha1[20];
/*
* If (flags & REF_HAVE_OLD), check that the reference
* previously had this value:
*/
unsigned char old_sha1[20];
/*
* One or more of REF_HAVE_NEW, REF_HAVE_OLD, REF_NODEREF,
* REF_DELETING, and REF_ISPRUNING:
* REF_DELETING, REF_ISPRUNING, REF_LOG_ONLY, and
* REF_UPDATE_VIA_HEAD:
*/
unsigned int flags;
struct ref_lock *lock;
int type;
unsigned int type;
char *msg;
/*
* If this ref_update was split off of a symref update via
* split_symref_update(), then this member points at that
* update. This is used for two purposes:
* 1. When reporting errors, we report the refname under which
* the update was originally requested.
* 2. When we read the old value of this reference, we
* propagate it back to its parent update for recording in
* the latter's reflog.
*/
struct ref_update *parent_update;
const char refname[FLEX_ARRAY];
};
/*
* Add a ref_update with the specified properties to transaction, and
* return a pointer to the new object. This function does not verify
* that refname is well-formed. new_sha1 and old_sha1 are only
* dereferenced if the REF_HAVE_NEW and REF_HAVE_OLD bits,
* respectively, are set in flags.
*/
struct ref_update *ref_transaction_add_update(
struct ref_transaction *transaction,
const char *refname, unsigned int flags,
const unsigned char *new_sha1,
const unsigned char *old_sha1,
const char *msg);
/*
* Transaction states.
* OPEN: The transaction is in a valid state and can accept new updates.
@ -209,7 +254,45 @@ int rename_ref_available(const char *oldname, const char *newname);
int do_for_each_ref(const char *submodule, const char *base,
each_ref_fn fn, int trim, int flags, void *cb_data);
/*
* Read the specified reference from the filesystem or packed refs
* file, non-recursively. Set type to describe the reference, and:
*
* - If refname is the name of a normal reference, fill in sha1
* (leaving referent unchanged).
*
* - If refname is the name of a symbolic reference, write the full
* name of the reference to which it refers (e.g.
* "refs/heads/master") to referent and set the REF_ISSYMREF bit in
* type (leaving sha1 unchanged). The caller is responsible for
* validating that referent is a valid reference name.
*
* WARNING: refname might be used as part of a filename, so it is
* important from a security standpoint that it be safe in the sense
* of refname_is_safe(). Moreover, for symrefs this function sets
* referent to whatever the repository says, which might not be a
* properly-formatted or even safe reference name. NEITHER INPUT NOR
* OUTPUT REFERENCE NAMES ARE VALIDATED WITHIN THIS FUNCTION.
*
* Return 0 on success. If the ref doesn't exist, set errno to ENOENT
* and return -1. If the ref exists but is neither a symbolic ref nor
* a sha1, it is broken; set REF_ISBROKEN in type, set errno to
* EINVAL, and return -1. If there is another error reading the ref,
* set errno appropriately and return -1.
*
* Backend-specific flags might be set in type as well, regardless of
* outcome.
*
* It is OK for refname to point into referent. If so:
*
* - if the function succeeds with REF_ISSYMREF, referent will be
* overwritten and the memory formerly pointed to by it might be
* changed or even freed.
*
* - in all other cases, referent will be untouched, and therefore
* refname will still be valid and unchanged.
*/
int read_raw_ref(const char *refname, unsigned char *sha1,
struct strbuf *symref, unsigned int *flags);
struct strbuf *referent, unsigned int *type);
#endif /* REFS_REFS_INTERNAL_H */

Просмотреть файл

@ -23,7 +23,7 @@ test_expect_success setup '
m=refs/heads/master
n_dir=refs/heads/gu
n=$n_dir/fixes
outside=foo
outside=refs/foo
test_expect_success \
"create $m" \
@ -479,7 +479,7 @@ test_expect_success 'stdin fails with duplicate refs' '
create $a $m
EOF
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: Multiple updates for ref '"'"'$a'"'"' not allowed." err
grep "fatal: multiple updates for ref '"'"'$a'"'"' not allowed." err
'
test_expect_success 'stdin create ref works' '
@ -880,7 +880,7 @@ test_expect_success 'stdin -z fails option with unknown name' '
test_expect_success 'stdin -z fails with duplicate refs' '
printf $F "create $a" "$m" "create $b" "$m" "create $a" "$m" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: Multiple updates for ref '"'"'$a'"'"' not allowed." err
grep "fatal: multiple updates for ref '"'"'$a'"'"' not allowed." err
'
test_expect_success 'stdin -z create ref works' '
@ -1102,6 +1102,41 @@ test_expect_success 'stdin -z delete refs works with packed and loose refs' '
test_must_fail git rev-parse --verify -q $c
'
test_expect_success 'fails with duplicate HEAD update' '
git branch target1 $A &&
git checkout target1 &&
cat >stdin <<-EOF &&
update refs/heads/target1 $C
option no-deref
update HEAD $B
EOF
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: multiple updates for '\''HEAD'\'' (including one via its referent .refs/heads/target1.) are not allowed" err &&
echo "refs/heads/target1" >expect &&
git symbolic-ref HEAD >actual &&
test_cmp expect actual &&
echo "$A" >expect &&
git rev-parse refs/heads/target1 >actual &&
test_cmp expect actual
'
test_expect_success 'fails with duplicate ref update via symref' '
git branch target2 $A &&
git symbolic-ref refs/heads/symref2 refs/heads/target2 &&
cat >stdin <<-EOF &&
update refs/heads/target2 $C
update refs/heads/symref2 $B
EOF
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: multiple updates for '\''refs/heads/target2'\'' (including one via symref .refs/heads/symref2.) are not allowed" err &&
echo "refs/heads/target2" >expect &&
git symbolic-ref refs/heads/symref2 >actual &&
test_cmp expect actual &&
echo "$A" >expect &&
git rev-parse refs/heads/target2 >actual &&
test_cmp expect actual
'
run_with_limited_open_files () {
(ulimit -n 32 && "$@")
}

Просмотреть файл

@ -28,7 +28,9 @@ Q="'"
test_expect_success 'setup' '
git commit --allow-empty -m Initial &&
C=$(git rev-parse HEAD)
C=$(git rev-parse HEAD) &&
git commit --allow-empty -m Second &&
D=$(git rev-parse HEAD)
'
@ -104,4 +106,76 @@ test_expect_success 'one new ref is a simple prefix of another' '
'
test_expect_success 'empty directory should not fool rev-parse' '
prefix=refs/e-rev-parse &&
git update-ref $prefix/foo $C &&
git pack-refs --all &&
mkdir -p .git/$prefix/foo/bar/baz &&
echo "$C" >expected &&
git rev-parse $prefix/foo >actual &&
test_cmp expected actual
'
test_expect_success 'empty directory should not fool for-each-ref' '
prefix=refs/e-for-each-ref &&
git update-ref $prefix/foo $C &&
git for-each-ref $prefix >expected &&
git pack-refs --all &&
mkdir -p .git/$prefix/foo/bar/baz &&
git for-each-ref $prefix >actual &&
test_cmp expected actual
'
test_expect_success 'empty directory should not fool create' '
prefix=refs/e-create &&
mkdir -p .git/$prefix/foo/bar/baz &&
printf "create %s $C\n" $prefix/foo |
git update-ref --stdin
'
test_expect_success 'empty directory should not fool verify' '
prefix=refs/e-verify &&
git update-ref $prefix/foo $C &&
git pack-refs --all &&
mkdir -p .git/$prefix/foo/bar/baz &&
printf "verify %s $C\n" $prefix/foo |
git update-ref --stdin
'
test_expect_success 'empty directory should not fool 1-arg update' '
prefix=refs/e-update-1 &&
git update-ref $prefix/foo $C &&
git pack-refs --all &&
mkdir -p .git/$prefix/foo/bar/baz &&
printf "update %s $D\n" $prefix/foo |
git update-ref --stdin
'
test_expect_success 'empty directory should not fool 2-arg update' '
prefix=refs/e-update-2 &&
git update-ref $prefix/foo $C &&
git pack-refs --all &&
mkdir -p .git/$prefix/foo/bar/baz &&
printf "update %s $D $C\n" $prefix/foo |
git update-ref --stdin
'
test_expect_success 'empty directory should not fool 0-arg delete' '
prefix=refs/e-delete-0 &&
git update-ref $prefix/foo $C &&
git pack-refs --all &&
mkdir -p .git/$prefix/foo/bar/baz &&
printf "delete %s\n" $prefix/foo |
git update-ref --stdin
'
test_expect_success 'empty directory should not fool 1-arg delete' '
prefix=refs/e-delete-1 &&
git update-ref $prefix/foo $C &&
git pack-refs --all &&
mkdir -p .git/$prefix/foo/bar/baz &&
printf "delete %s $C\n" $prefix/foo |
git update-ref --stdin
'
test_done

Просмотреть файл

@ -285,7 +285,7 @@ test_expect_success 'update-ref -d cannot delete non-ref in .git dir' '
echo precious >expect &&
test_must_fail git update-ref -d my-private-file >output 2>error &&
test_must_be_empty output &&
test_i18ngrep -e "cannot lock .*: unable to resolve reference" error &&
test_i18ngrep -e "refusing to update ref with bad name" error &&
test_cmp expect .git/my-private-file
'

Просмотреть файл

@ -79,6 +79,15 @@ test_expect_success 'git branch -m dumps usage' '
test_i18ngrep "branch name required" err
'
test_expect_success 'git branch -m m broken_symref should work' '
test_when_finished "git branch -D broken_symref" &&
git branch -l m &&
git symbolic-ref refs/heads/broken_symref refs/heads/i_am_broken &&
git branch -m m broken_symref &&
git reflog exists refs/heads/broken_symref &&
test_must_fail git reflog exists refs/heads/i_am_broken
'
test_expect_success 'git branch -m m m/m should work' '
git branch -l m &&
git branch -m m m/m &&