diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/refdb.c | 8 | ||||
| -rw-r--r-- | src/refdb.h | 4 | ||||
| -rw-r--r-- | src/refdb_fs.c | 126 | ||||
| -rw-r--r-- | src/refs.c | 80 | 
4 files changed, 172 insertions, 46 deletions
| diff --git a/src/refdb.c b/src/refdb.c index 411423d57..984c3c7f6 100644 --- a/src/refdb.c +++ b/src/refdb.c @@ -167,14 +167,14 @@ void git_refdb_iterator_free(git_reference_iterator *iter)  	iter->free(iter);  } -int git_refdb_write(git_refdb *db, git_reference *ref, int force, const git_signature *who, const char *message) +int git_refdb_write(git_refdb *db, git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old_id, const char *old_target)  {  	assert(db && db->backend);  	GIT_REFCOUNT_INC(db);  	ref->db = db; -	return db->backend->write(db->backend, ref, force, who, message); +	return db->backend->write(db->backend, ref, force, who, message, old_id, old_target);  }  int git_refdb_rename( @@ -201,10 +201,10 @@ int git_refdb_rename(  	return 0;  } -int git_refdb_delete(struct git_refdb *db, const char *ref_name) +int git_refdb_delete(struct git_refdb *db, const char *ref_name, const git_oid *old_id, const char *old_target)  {  	assert(db && db->backend); -	return db->backend->del(db->backend, ref_name); +	return db->backend->del(db->backend, ref_name, old_id, old_target);  }  int git_refdb_reflog_read(git_reflog **out, git_refdb *db,  const char *name) diff --git a/src/refdb.h b/src/refdb.h index 91eecb782..cbad86faf 100644 --- a/src/refdb.h +++ b/src/refdb.h @@ -42,8 +42,8 @@ int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter);  int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter);  void git_refdb_iterator_free(git_reference_iterator *iter); -int git_refdb_write(git_refdb *refdb, git_reference *ref, int force, const git_signature *who, const char *message); -int git_refdb_delete(git_refdb *refdb, const char *ref_name); +int git_refdb_write(git_refdb *refdb, git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old_id, const char *old_target); +int git_refdb_delete(git_refdb *refdb, const char *ref_name, const git_oid *old_id, const char *old_target);  int git_refdb_reflog_read(git_reflog **out, git_refdb *db,  const char *name);  int git_refdb_reflog_write(git_reflog *reflog); diff --git a/src/refdb_fs.c b/src/refdb_fs.c index 89c77c14c..ea758deea 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -688,30 +688,32 @@ static int reference_path_available(  	return 0;  } -static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const git_reference *ref) +static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *name)  { +        int error;  	git_buf ref_path = GIT_BUF_INIT; +	assert(file && backend && name); +  	/* Remove a possibly existing empty directory hierarchy  	 * which name would collide with the reference name  	 */ -	if (git_futils_rmdir_r(ref->name, backend->path, GIT_RMDIR_SKIP_NONEMPTY) < 0) +	if (git_futils_rmdir_r(name, backend->path, GIT_RMDIR_SKIP_NONEMPTY) < 0)  		return -1; -	if (git_buf_joinpath(&ref_path, backend->path, ref->name) < 0) +	if (git_buf_joinpath(&ref_path, backend->path, name) < 0)  		return -1; -	if (git_filebuf_open(file, ref_path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE) < 0) { -		git_buf_free(&ref_path); -		return -1; -	} +	error = git_filebuf_open(file, ref_path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE);  	git_buf_free(&ref_path); -	return 0; +        return error;  }  static int loose_commit(git_filebuf *file, const git_reference *ref)  { +	assert(file && ref); +  	if (ref->type == GIT_REF_OID) {  		char oid[GIT_OID_HEXSZ + 1];  		git_oid_nfmt(oid, sizeof(oid), &ref->target.oid); @@ -928,16 +930,54 @@ static bool should_write_reflog(git_repository *repo, const char *name)  	return 0;  } +static int cmp_old_ref(int *cmp, git_refdb_backend *backend, const char *name, +	const git_oid *old_id, const char *old_target) +{ +	int error = 0; +	git_reference *old_ref = NULL; + +	*cmp = 0; +	/* It "matches" if there is no old value to compare against */ +	if (!old_id && !old_target) +		return 0; + +	if ((error = refdb_fs_backend__lookup(&old_ref, backend, name)) < 0) +		goto out; + +	/* If the types don't match, there's no way the values do */ +	if (old_id && old_ref->type != GIT_REF_OID) { +		*cmp = -1; +		goto out; +	} +	if (old_target && old_ref->type != GIT_REF_SYMBOLIC) { +		*cmp = 1; +		goto out; +	} + +	if (old_id && old_ref->type == GIT_REF_OID) +		*cmp = git_oid_cmp(old_id, &old_ref->target.oid); + +	if (old_target && old_ref->type == GIT_REF_SYMBOLIC) +		*cmp = git__strcmp(old_target, old_ref->target.symbolic); + +out: +	git_reference_free(old_ref); + +	return error; +} +  static int refdb_fs_backend__write(  	git_refdb_backend *_backend,  	const git_reference *ref,  	int force,  	const git_signature *who, -	const char *message) +	const char *message, +	const git_oid *old_id, +	const char *old_target)  {  	refdb_fs_backend *backend = (refdb_fs_backend *)_backend;  	git_filebuf file = GIT_FILEBUF_INIT; -	int error; +	int error = 0, cmp = 0;  	assert(backend); @@ -945,10 +985,19 @@ static int refdb_fs_backend__write(  	if (error < 0)  		return error; -	/* We need to perform the reflog append under the ref's lock */ -	if ((error = loose_lock(&file, backend, ref)) < 0) +	/* We need to perform the reflog append and old value check under the ref's lock */ +	if ((error = loose_lock(&file, backend, ref->name)) < 0)  		return error; +	if ((error = cmp_old_ref(&cmp, _backend, ref->name, old_id, old_target)) < 0) +		goto on_error; + +	if (cmp) { +		giterr_set(GITERR_REFERENCE, "old reference value does not match"); +		error = GIT_EMODIFIED; +		goto on_error; +	} +  	if (should_write_reflog(backend->repo, ref->name) &&  	    (error = reflog_append(backend, ref, who, message)) < 0) {  		git_filebuf_cleanup(&file); @@ -956,20 +1005,40 @@ static int refdb_fs_backend__write(  	}  	return loose_commit(&file, ref); + +on_error: +        git_filebuf_cleanup(&file); +        return error;  }  static int refdb_fs_backend__delete(  	git_refdb_backend *_backend, -	const char *ref_name) +	const char *ref_name, +	const git_oid *old_id, const char *old_target)  {  	refdb_fs_backend *backend = (refdb_fs_backend *)_backend;  	git_buf loose_path = GIT_BUF_INIT; -	size_t pack_pos; -	int error = 0; +	size_t pack_pos +;	git_filebuf file = GIT_FILEBUF_INIT; +	int error = 0, cmp = 0;  	bool loose_deleted = 0;  	assert(backend && ref_name); +	if ((error = loose_lock(&file, backend, ref_name)) < 0) +		return error; + +	error = cmp_old_ref(&cmp, _backend, ref_name, old_id, old_target); +	//git_filebuf_cleanup(&file); +	if (error < 0) +		goto cleanup; + +	if (cmp) { +		giterr_set(GITERR_REFERENCE, "old reference value does not match"); +		error = GIT_EMODIFIED; +		goto cleanup; +	} +  	/* If a loose reference exists, remove it from the filesystem */  	if (git_buf_joinpath(&loose_path, backend->path, ref_name) < 0)  		return -1; @@ -982,14 +1051,14 @@ static int refdb_fs_backend__delete(  	git_buf_free(&loose_path);  	if (error != 0) -		return error; +		goto cleanup; -	if (packed_reload(backend) < 0) -		return -1; +	if ((error = packed_reload(backend)) < 0) +		goto cleanup;  	/* If a packed reference exists, remove it from the packfile and repack */ -	if (git_sortedcache_wlock(backend->refcache) < 0) -		return -1; +	if ((error = git_sortedcache_wlock(backend->refcache)) < 0) +		goto cleanup;  	if (!(error = git_sortedcache_lookup_index(  			&pack_pos, backend->refcache, ref_name))) @@ -997,10 +1066,17 @@ static int refdb_fs_backend__delete(  	git_sortedcache_wunlock(backend->refcache); -	if (error == GIT_ENOTFOUND) -		return loose_deleted ? 0 : ref_error_notfound(ref_name); +	if (error == GIT_ENOTFOUND) { +		error = loose_deleted ? 0 : ref_error_notfound(ref_name); +		goto cleanup; +	} + +	error = packed_write(backend); -	return packed_write(backend); +cleanup: +	git_filebuf_cleanup(&file); + +	return error;  }  static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name); @@ -1026,7 +1102,7 @@ static int refdb_fs_backend__rename(  		(error = refdb_fs_backend__lookup(&old, _backend, old_name)) < 0)  		return error; -	if ((error = refdb_fs_backend__delete(_backend, old_name)) < 0) { +	if ((error = refdb_fs_backend__delete(_backend, old_name, NULL, NULL)) < 0) {  		git_reference_free(old);  		return error;  	} @@ -1037,7 +1113,7 @@ static int refdb_fs_backend__rename(  		return -1;  	} -	if ((error = loose_lock(&file, backend, new)) < 0) { +	if ((error = loose_lock(&file, backend, new->name)) < 0) {  		git_reference_free(new);  		return error;  	} diff --git a/src/refs.c b/src/refs.c index ca5f24ea2..bdf2da37e 100644 --- a/src/refs.c +++ b/src/refs.c @@ -116,7 +116,26 @@ void git_reference_free(git_reference *reference)  int git_reference_delete(git_reference *ref)  { -	return git_refdb_delete(ref->db, ref->name); +	const git_oid *old_id = NULL; +	const char *old_target = NULL; + +	if (ref->type == GIT_REF_OID) +		old_id = &ref->target.oid; +	else +		old_target = ref->target.symbolic; + +	return git_refdb_delete(ref->db, ref->name, old_id, old_target); +} + +int git_reference_remove(git_repository *repo, const char *name) +{ +	git_refdb *db; +	int error; + +	if ((error = git_repository_refdb__weakptr(&db, repo)) < 0) +		return error; + +	return git_refdb_delete(db, name, NULL, NULL);  }  int git_reference_lookup(git_reference **ref_out, @@ -331,7 +350,9 @@ static int reference__create(  	const char *symbolic,  	int force,  	const git_signature *signature, -	const char *log_message) +	const char *log_message, +	const git_oid *old_id, +	const char *old_target)  {  	char normalized[GIT_REFNAME_MAX];  	git_refdb *refdb; @@ -380,7 +401,7 @@ static int reference__create(  	GITERR_CHECK_ALLOC(ref); -	if ((error = git_refdb_write(refdb, ref, force, signature, log_message)) < 0) { +	if ((error = git_refdb_write(refdb, ref, force, signature, log_message, old_id, old_target)) < 0) {  		git_reference_free(ref);  		return error;  	} @@ -406,19 +427,21 @@ static int log_signature(git_signature **out, git_repository *repo)  	return 0;  } -int git_reference_create( +int git_reference_create_matching(  	git_reference **ref_out,  	git_repository *repo,  	const char *name, -	const git_oid *oid, +	const git_oid *id,  	int force,  	const git_signature *signature, -	const char *log_message) +	const char *log_message, +	const git_oid *old_id) +  {  	int error;  	git_signature *who = NULL; -	assert(oid); +	assert(id);  	if (!signature) {  		if ((error = log_signature(&who, repo)) < 0) @@ -428,21 +451,34 @@ int git_reference_create(  	}  	error = reference__create( -		ref_out, repo, name, oid, NULL, force, signature, log_message); +		ref_out, repo, name, id, NULL, force, signature, log_message, old_id, NULL);  	git_signature_free(who);  	return error;  } -int git_reference_symbolic_create( +int git_reference_create(  	git_reference **ref_out,  	git_repository *repo,  	const char *name, -	const char *target, +	const git_oid *id,  	int force,  	const git_signature *signature,  	const char *log_message)  { +        return git_reference_create_matching(ref_out, repo, name, id, force, signature, log_message, NULL); +} + +int git_reference_symbolic_create_matching( +	git_reference **ref_out, +	git_repository *repo, +	const char *name, +	const char *target, +	int force, +	const git_signature *signature, +	const char *log_message, +	const char *old_target) +{  	int error;  	git_signature *who = NULL; @@ -456,12 +492,24 @@ int git_reference_symbolic_create(  	}  	error = reference__create( -		ref_out, repo, name, NULL, target, force, signature, log_message); +		ref_out, repo, name, NULL, target, force, signature, log_message, NULL, old_target);  	git_signature_free(who);  	return error;  } +int git_reference_symbolic_create( +	git_reference **ref_out, +	git_repository *repo, +	const char *name, +	const char *target, +	int force, +	const git_signature *signature, +	const char *log_message) +{ +	return git_reference_symbolic_create_matching(ref_out, repo, name, target, force, signature, log_message, NULL); +} +  static int ensure_is_an_updatable_direct_reference(git_reference *ref)  {  	if (ref->type == GIT_REF_OID) @@ -479,14 +527,16 @@ int git_reference_set_target(  	const char *log_message)  {  	int error; +	git_repository *repo;  	assert(out && ref && id); +	repo = ref->db->repo; +  	if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0)  		return error; -	return git_reference_create( -		out, ref->db->repo, ref->name, id, 1, signature, log_message); +	return git_reference_create_matching(out, repo, ref->name, id, 1, signature, log_message, &ref->target.oid);  }  static int ensure_is_an_updatable_symbolic_reference(git_reference *ref) @@ -512,8 +562,8 @@ int git_reference_symbolic_set_target(  	if ((error = ensure_is_an_updatable_symbolic_reference(ref)) < 0)  		return error; -	return git_reference_symbolic_create( -		out, ref->db->repo, ref->name, target, 1, signature, log_message); +	return git_reference_symbolic_create_matching( +		out, ref->db->repo, ref->name, target, 1, signature, log_message, ref->target.symbolic);  }  static int reference__rename(git_reference **out, git_reference *ref, const char *new_name, int force, | 
