Commit 0a20eee9 by Vicent Martí

Merge pull request #619 from nulltoken/topic/branches

Basic branch management API
parents b78fb64d 4615f0f7
......@@ -4,12 +4,119 @@
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_branch_h__
#define INCLUDE_branch_h__
#ifndef INCLUDE_git_branch_h__
#define INCLUDE_git_branch_h__
struct git_branch {
char *remote; /* TODO: Make this a git_remote */
char *merge;
};
#include "common.h"
#include "types.h"
/**
* @file git2/branch.h
* @brief Git branch parsing routines
* @defgroup git_branch Git branch management
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL
/**
* Create a new branch pointing at a target commit
*
* A new direct reference will be created pointing to
* this target commit. If `force` is true and a reference
* already exists with the given name, it'll be replaced.
*
* @param oid_out Pointer where to store the OID of the target commit.
*
* @param repo Repository where to store the branch.
*
* @param branch_name Name for the branch; this name is
* validated for consistency. It should also not conflict with
* an already existing branch name.
*
* @param target Object to which this branch should point. This object
* must belong to the given `repo` and can either be a git_commit or a
* git_tag. When a git_tag is being passed, it should be dereferencable
* to a git_commit which oid will be used as the target of the branch.
*
* @param force Overwrite existing branch.
*
* @return GIT_SUCCESS or an error code.
* A proper reference is written in the refs/heads namespace
* pointing to the provided target commit.
*/
GIT_EXTERN(int) git_branch_create(
git_oid *oid_out,
git_repository *repo,
const char *branch_name,
const git_object *target,
int force);
/**
* Delete an existing branch reference.
*
* @param repo Repository where lives the branch.
*
* @param branch_name Name of the branch to be deleted;
* this name is validated for consistency.
*
* @param branch_type Type of the considered branch. This should
* be valued with either GIT_BRANCH_LOCAL or GIT_BRANCH_REMOTE.
*
* @return GIT_SUCCESS on success, GIT_ENOTFOUND if the branch
* doesn't exist or an error code.
*/
GIT_EXTERN(int) git_branch_delete(
git_repository *repo,
const char *branch_name,
enum git_branch_type branch_type);
/**
* Fill a list with all the branches in the Repository
*
* The string array will be filled with the names of the
* matching branches; these values are owned by the user and
* should be free'd manually when no longer needed, using
* `git_strarray_free`.
*
* @param branch_names Pointer to a git_strarray structure
* where the branch names will be stored.
*
* @param repo Repository where to find the branches.
*
* @param list_flags Filtering flags for the branch
* listing. Valid values are GIT_BRANCH_LOCAL, GIT_BRANCH_REMOTE
* or a combination of the two.
*
* @return GIT_SUCCESS or an error code.
*/
GIT_EXTERN(int) git_branch_list(
git_strarray *branch_names,
git_repository *repo,
unsigned int list_flags);
/**
* Move/rename an existing branch reference.
*
* @param repo Repository where lives the branch.
*
* @param old_branch_name Current name of the branch to be moved;
* this name is validated for consistency.
*
* @param new_branch_name Target name of the branch once the move
* is performed; this name is validated for consistency.
*
* @param force Overwrite existing branch.
*
* @return GIT_SUCCESS on success, GIT_ENOTFOUND if the branch
* doesn't exist or an error code.
*/
GIT_EXTERN(int) git_branch_move(
git_repository *repo,
const char *old_branch_name,
const char *new_branch_name,
int force);
/** @} */
GIT_END_DECL
#endif
......@@ -274,6 +274,21 @@ GIT_EXTERN(int) git_tag_list_match(
const char *pattern,
git_repository *repo);
/**
* Recursively peel a tag until a non tag git_object
* is met
*
* The retrieved `tag_target` object is owned by the repository
* and should be closed with the `git_object_free` method.
*
* @param tag_target Pointer to the peeled git_object
* @param tag The tag to be processed
* @return GIT_SUCCESS or an error code
*/
GIT_EXTERN(int) git_tag_peel(
git_object **tag_target,
git_tag *tag);
/** @} */
GIT_END_DECL
#endif
......@@ -160,6 +160,11 @@ typedef enum {
GIT_REF_LISTALL = GIT_REF_OID|GIT_REF_SYMBOLIC|GIT_REF_PACKED,
} git_rtype;
/** Basic type of any Git branch. */
typedef enum {
GIT_BRANCH_LOCAL = 1,
GIT_BRANCH_REMOTE = 2,
} git_branch_type;
typedef struct git_refspec git_refspec;
typedef struct git_remote git_remote;
......
/*
* Copyright (C) 2009-2012 the libgit2 contributors
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "commit.h"
#include "branch.h"
#include "tag.h"
static int retrieve_branch_reference(
git_reference **branch_reference_out,
git_repository *repo,
const char *branch_name,
int is_remote)
{
git_reference *branch;
int error = -1;
char *prefix;
git_buf ref_name = GIT_BUF_INIT;
*branch_reference_out = NULL;
prefix = is_remote ? GIT_REFS_REMOTES_DIR : GIT_REFS_HEADS_DIR;
if (git_buf_joinpath(&ref_name, prefix, branch_name) < 0)
goto cleanup;
if ((error = git_reference_lookup(&branch, repo, ref_name.ptr)) < 0) {
giterr_set(GITERR_REFERENCE,
"Cannot locate %s branch '%s'.", is_remote ? "remote-tracking" : "local", branch_name);
goto cleanup;
}
*branch_reference_out = branch;
cleanup:
git_buf_free(&ref_name);
return error;
}
static int create_error_invalid(const char *msg)
{
giterr_set(GITERR_INVALID, "Cannot create branch - %s", msg);
return -1;
}
int git_branch_create(
git_oid *oid_out,
git_repository *repo,
const char *branch_name,
const git_object *target,
int force)
{
git_otype target_type = GIT_OBJ_BAD;
git_object *commit = NULL;
git_reference *branch = NULL;
git_buf canonical_branch_name = GIT_BUF_INIT;
int error = -1;
assert(repo && branch_name && target && oid_out);
if (git_object_owner(target) != repo)
return create_error_invalid("The given target does not belong to this repository");
target_type = git_object_type(target);
switch (target_type)
{
case GIT_OBJ_TAG:
if (git_tag_peel(&commit, (git_tag *)target) < 0)
goto cleanup;
if (git_object_type(commit) != GIT_OBJ_COMMIT) {
create_error_invalid("The given target does not resolve to a commit");
goto cleanup;
}
break;
case GIT_OBJ_COMMIT:
commit = (git_object *)target;
break;
default:
return create_error_invalid("Only git_tag and git_commit objects are valid targets.");
}
if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0)
goto cleanup;
if (git_reference_create_oid(&branch, repo, git_buf_cstr(&canonical_branch_name), git_object_id(commit), force) < 0)
goto cleanup;
git_oid_cpy(oid_out, git_reference_oid(branch));
error = 0;
cleanup:
if (target_type == GIT_OBJ_TAG)
git_object_free(commit);
git_reference_free(branch);
git_buf_free(&canonical_branch_name);
return error;
}
int git_branch_delete(git_repository *repo, const char *branch_name, enum git_branch_type branch_type)
{
git_reference *branch = NULL;
git_reference *head = NULL;
int error;
assert((branch_type == GIT_BRANCH_LOCAL) || (branch_type == GIT_BRANCH_REMOTE));
if ((error = retrieve_branch_reference(&branch, repo, branch_name, branch_type == GIT_BRANCH_REMOTE)) < 0)
goto cleanup;
if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) {
giterr_set(GITERR_REFERENCE, "Cannot locate HEAD.");
error = -1;
goto cleanup;
}
if ((git_reference_type(head) == GIT_REF_SYMBOLIC)
&& (strcmp(git_reference_target(head), git_reference_name(branch)) == 0)) {
giterr_set(GITERR_REFERENCE,
"Cannot delete branch '%s' as it is the current HEAD of the repository.", branch_name);
error = -1;
goto cleanup;
}
return git_reference_delete(branch);
cleanup:
git_reference_free(head);
git_reference_free(branch);
return error;
}
typedef struct {
git_vector *branchlist;
unsigned int branch_type;
} branch_filter_data;
static int branch_list_cb(const char *branch_name, void *payload)
{
branch_filter_data *filter = (branch_filter_data *)payload;
if ((filter->branch_type & GIT_BRANCH_LOCAL && git__prefixcmp(branch_name, GIT_REFS_HEADS_DIR) == 0)
|| (filter->branch_type & GIT_BRANCH_REMOTE && git__prefixcmp(branch_name, GIT_REFS_REMOTES_DIR) == 0))
return git_vector_insert(filter->branchlist, git__strdup(branch_name));
return 0;
}
int git_branch_list(git_strarray *branch_names, git_repository *repo, unsigned int list_flags)
{
int error;
branch_filter_data filter;
git_vector branchlist;
assert(branch_names && repo);
if (git_vector_init(&branchlist, 8, NULL) < 0)
return -1;
filter.branchlist = &branchlist;
filter.branch_type = list_flags;
error = git_reference_foreach(repo, GIT_REF_OID|GIT_REF_PACKED, &branch_list_cb, (void *)&filter);
if (error < 0) {
git_vector_free(&branchlist);
return -1;
}
branch_names->strings = (char **)branchlist.contents;
branch_names->count = branchlist.length;
return 0;
}
int git_branch_move(git_repository *repo, const char *old_branch_name, const char *new_branch_name, int force)
{
git_reference *reference;
git_buf old_reference_name = GIT_BUF_INIT, new_reference_name = GIT_BUF_INIT;
int error;
if (git_buf_joinpath(&old_reference_name, GIT_REFS_HEADS_DIR, old_branch_name) < 0)
return -1;
if (git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name) < 0)
return -1;
if ((error = git_reference_lookup(&reference, repo, git_buf_cstr(&old_reference_name))) < 0)
return error;
return git_reference_rename(reference, git_buf_cstr(&new_reference_name), force);
}
/*
* Copyright (C) 2009-2012 the libgit2 contributors
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_branch_h__
#define INCLUDE_branch_h__
#include "git2/branch.h"
struct git_branch {
char *remote; /* TODO: Make this a git_remote */
char *merge;
};
#endif
......@@ -298,13 +298,20 @@ int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
{
int force = *(int *)opaque;
enum git_directory_removal_type removal_type = *(enum git_directory_removal_type *)opaque;
assert(removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY
|| removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS
|| removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS);
if (git_path_isdir(path->ptr) == true) {
if (git_path_direach(path, _rmdir_recurs_foreach, opaque) < 0)
return -1;
if (p_rmdir(path->ptr) < 0) {
if (removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS && errno == ENOTEMPTY)
return 0;
giterr_set(GITERR_OS, "Could not remove directory '%s'", path->ptr);
return -1;
}
......@@ -312,7 +319,7 @@ static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
return 0;
}
if (force) {
if (removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS) {
if (p_unlink(path->ptr) < 0) {
giterr_set(GITERR_OS, "Could not remove directory. File '%s' cannot be removed", path->ptr);
return -1;
......@@ -321,18 +328,22 @@ static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
return 0;
}
giterr_set(GITERR_OS, "Could not remove directory. File '%s' still present", path->ptr);
return -1;
if (removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY) {
giterr_set(GITERR_OS, "Could not remove directory. File '%s' still present", path->ptr);
return -1;
}
return 0;
}
int git_futils_rmdir_r(const char *path, int force)
int git_futils_rmdir_r(const char *path, enum git_directory_removal_type removal_type)
{
int error;
git_buf p = GIT_BUF_INIT;
error = git_buf_sets(&p, path);
if (!error)
error = _rmdir_recurs_foreach(&force, &p);
error = _rmdir_recurs_foreach(&removal_type, &p);
git_buf_free(&p);
return error;
}
......
......@@ -58,10 +58,25 @@ extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t m
*/
extern int git_futils_mkpath2file(const char *path, const mode_t mode);
typedef enum {
GIT_DIRREMOVAL_EMPTY_HIERARCHY = 0,
GIT_DIRREMOVAL_FILES_AND_DIRS = 1,
GIT_DIRREMOVAL_ONLY_EMPTY_DIRS = 2,
} git_directory_removal_type;
/**
* Remove path and any files and directories beneath it.
*
* @param path Path to to top level directory to process.
*
* @param removal_type GIT_DIRREMOVAL_EMPTY_HIERARCHY to remove a hierarchy
* of empty directories (will fail if any file is found), GIT_DIRREMOVAL_FILES_AND_DIRS
* to remove a hierarchy of files and folders, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS to only remove
* empty directories (no failure on file encounter).
*
* @return 0 on success; -1 on error.
*/
extern int git_futils_rmdir_r(const char *path, int force);
extern int git_futils_rmdir_r(const char *path, enum git_directory_removal_type removal_type);
/**
* Create and open a temporary file with a `_git2_` suffix.
......
......@@ -287,6 +287,15 @@ static int loose_write(git_reference *ref)
if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0)
return -1;
/* Remove a possibly existing empty directory hierarchy
* which name would collide with the reference name
*/
if (git_path_isdir(git_buf_cstr(&ref_path)) &&
(git_futils_rmdir_r(git_buf_cstr(&ref_path), GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0)) {
git_buf_free(&ref_path);
return -1;
}
if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) {
git_buf_free(&ref_path);
return -1;
......
......@@ -451,3 +451,23 @@ int git_tag_list(git_strarray *tag_names, git_repository *repo)
{
return git_tag_list_match(tag_names, "", repo);
}
int git_tag_peel(git_object **tag_target, git_tag *tag)
{
int error;
git_object *target;
assert(tag_target && tag);
if (git_tag_target(&target, tag) < 0)
return -1;
if (git_object_type(target) == GIT_OBJ_TAG) {
error = git_tag_peel(tag_target, (git_tag *)target);
git_object_free(target);
return error;
}
*tag_target = target;
return 0;
}
......@@ -27,7 +27,7 @@ static int add_ref(transport_local *t, const char *name)
const char peeled[] = "^{}";
git_remote_head *head;
git_reference *ref, *resolved_ref;
git_object *obj = NULL;
git_object *obj = NULL, *peeled_tag_target = NULL;
int error = GIT_SUCCESS, peel_len, ret;
head = git__malloc(sizeof(git_remote_head));
......@@ -78,7 +78,11 @@ static int add_ref(transport_local *t, const char *name)
assert(ret < peel_len + 1);
(void)ret;
git_oid_cpy(&head->oid, git_tag_target_oid((git_tag *) obj));
error = git_tag_peel(&peeled_tag_target, (git_tag *) obj);
if (error < 0)
goto out;
git_oid_cpy(&head->oid, git_object_id(peeled_tag_target));
error = git_vector_insert(&t->refs, head);
if (error < GIT_SUCCESS)
......@@ -89,6 +93,7 @@ static int add_ref(transport_local *t, const char *name)
git_reference_free(resolved_ref);
git_object_free(obj);
git_object_free(peeled_tag_target);
if (head && error < GIT_SUCCESS) {
git__free(head->name);
git__free(head);
......
......@@ -30,25 +30,39 @@ void test_core_rmdir__initialize(void)
/* make sure empty dir can be deleted recusively */
void test_core_rmdir__delete_recursive(void)
{
cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, 0));
cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
}
/* make sure non-empty dir cannot be deleted recusively */
void test_core_rmdir__fail_to_delete_non_empty_dir(void)
{
git_buf file = GIT_BUF_INIT;
int fd;
cl_git_pass(git_buf_joinpath(&file, empty_tmp_dir, "/two/file.txt"));
fd = p_creat(file.ptr, 0666);
cl_assert(fd >= 0);
cl_git_mkfile(git_buf_cstr(&file), "dummy");
cl_must_pass(p_close(fd));
cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, 0));
cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
cl_must_pass(p_unlink(file.ptr));
cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, 0));
cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
git_buf_free(&file);
}
void test_core_rmdir__can_skip__non_empty_dir(void)
{
git_buf file = GIT_BUF_INIT;
cl_git_pass(git_buf_joinpath(&file, empty_tmp_dir, "/two/file.txt"));
cl_git_mkfile(git_buf_cstr(&file), "dummy");
cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS));
cl_assert(git_path_exists(git_buf_cstr(&file)) == true);
cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_FILES_AND_DIRS));
cl_assert(git_path_exists(empty_tmp_dir) == false);
git_buf_free(&file);
}
......@@ -71,6 +71,16 @@ static int count_ref__cb(git_remote_head *head, void *payload)
return GIT_SUCCESS;
}
static int ensure_peeled__cb(git_remote_head *head, void *payload)
{
GIT_UNUSED(payload);
if(strcmp(head->name, "refs/tags/test^{}") != 0)
return 0;
return git_oid_streq(&head->oid, "e90810b8df3e80c413d903f631643c716887138d");
}
static void connect_to_local_repository(const char *local_repository)
{
build_local_file_url(&file_path_buf, local_repository);
......@@ -88,7 +98,7 @@ void test_network_remotelocal__retrieve_advertised_references(void)
cl_git_pass(git_remote_ls(remote, &count_ref__cb, &how_many_refs));
cl_assert(how_many_refs == 12); /* 1 HEAD + 9 refs + 2 peeled tags */
cl_assert(how_many_refs == 14); /* 1 HEAD + 6 heads + 1 lightweight tag + 3 annotated tags + 3 peeled target */
}
void test_network_remotelocal__retrieve_advertised_references_from_spaced_repository(void)
......@@ -102,7 +112,17 @@ void test_network_remotelocal__retrieve_advertised_references_from_spaced_reposi
cl_git_pass(git_remote_ls(remote, &count_ref__cb, &how_many_refs));
cl_assert(how_many_refs == 12); /* 1 HEAD */
cl_assert(how_many_refs == 14); /* 1 HEAD + 6 heads + 1 lightweight tag + 3 annotated tags + 3 peeled target */
git_remote_free(remote); /* Disconnect from the "spaced repo" before the cleanup */
remote = NULL;
cl_fixture_cleanup("spaced testrepo.git");
}
void test_network_remotelocal__nested_tags_are_completely_peeled(void)
{
connect_to_local_repository(cl_fixture("testrepo.git"));
cl_git_pass(git_remote_ls(remote, &ensure_peeled__cb, NULL));
}
#include "clar_libgit2.h"
#include "tag.h"
static git_repository *repo;
static git_tag *tag;
static git_object *target;
void test_object_tag_peel__initialize(void)
{
cl_fixture_sandbox("testrepo.git");
cl_git_pass(git_repository_open(&repo, "testrepo.git"));
}
void test_object_tag_peel__cleanup(void)
{
git_tag_free(tag);
git_object_free(target);
git_repository_free(repo);
cl_fixture_cleanup("testrepo.git");
}
static void retrieve_tag_from_oid(git_tag **tag_out, git_repository *repo, const char *sha)
{
git_oid oid;
cl_git_pass(git_oid_fromstr(&oid, sha));
cl_git_pass(git_tag_lookup(tag_out, repo, &oid));
}
void test_object_tag_peel__can_peel_to_a_commit(void)
{
retrieve_tag_from_oid(&tag, repo, "7b4384978d2493e851f9cca7858815fac9b10980");
cl_git_pass(git_tag_peel(&target, tag));
cl_assert(git_object_type(target) == GIT_OBJ_COMMIT);
cl_git_pass(git_oid_streq(git_object_id(target), "e90810b8df3e80c413d903f631643c716887138d"));
}
void test_object_tag_peel__can_peel_several_nested_tags_to_a_commit(void)
{
retrieve_tag_from_oid(&tag, repo, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1");
cl_git_pass(git_tag_peel(&target, tag));
cl_assert(git_object_type(target) == GIT_OBJ_COMMIT);
cl_git_pass(git_oid_streq(git_object_id(target), "e90810b8df3e80c413d903f631643c716887138d"));
}
void test_object_tag_peel__can_peel_to_a_non_commit(void)
{
retrieve_tag_from_oid(&tag, repo, "521d87c1ec3aef9824daf6d96cc0ae3710766d91");
cl_git_pass(git_tag_peel(&target, tag));
cl_assert(git_object_type(target) == GIT_OBJ_BLOB);
cl_git_pass(git_oid_streq(git_object_id(target), "1385f264afb75a56a5bec74243be9b367ba4ca08"));
}
#include "clar_libgit2.h"
#include "refs.h"
#include "branch.h"
static git_repository *repo;
static git_reference *fake_remote;
static git_oid branch_target_oid;
static git_object *target;
void test_refs_branches_create__initialize(void)
{
cl_fixture_sandbox("testrepo.git");
cl_git_pass(git_repository_open(&repo, "testrepo.git"));
}
void test_refs_branches_create__cleanup(void)
{
git_object_free(target);
git_repository_free(repo);
cl_fixture_cleanup("testrepo.git");
}
static void retrieve_target_from_oid(git_object **object_out, git_repository *repo, const char *sha)
{
git_oid oid;
cl_git_pass(git_oid_fromstr(&oid, sha));
cl_git_pass(git_object_lookup(object_out, repo, &oid, GIT_OBJ_ANY));
}
static void retrieve_known_commit(git_object **object, git_repository *repo)
{
retrieve_target_from_oid(object, repo, "e90810b8df3e80c413d903f631643c716887138d");
}
#define NEW_BRANCH_NAME "new-branch-on-the-block"
void test_refs_branches_create__can_create_a_local_branch(void)
{
retrieve_known_commit(&target, repo);
cl_git_pass(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0));
cl_git_pass(git_oid_cmp(&branch_target_oid, git_object_id(target)));
}
void test_refs_branches_create__creating_a_local_branch_triggers_the_creation_of_a_new_direct_reference(void)
{
git_reference *branch;
retrieve_known_commit(&target, repo);
cl_git_fail(git_reference_lookup(&branch, repo, GIT_REFS_HEADS_DIR NEW_BRANCH_NAME));
cl_git_pass(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0));
cl_git_pass(git_reference_lookup(&branch, repo, GIT_REFS_HEADS_DIR NEW_BRANCH_NAME));
cl_assert(git_reference_type(branch) == GIT_REF_OID);
git_reference_free(branch);
}
void test_refs_branches_create__can_not_create_a_branch_if_its_name_collide_with_an_existing_one(void)
{
retrieve_known_commit(&target, repo);
cl_git_fail(git_branch_create(&branch_target_oid, repo, "br2", target, 0));
}
void test_refs_branches_create__can_force_create_over_an_existing_branch(void)
{
retrieve_known_commit(&target, repo);
cl_git_pass(git_branch_create(&branch_target_oid, repo, "br2", target, 1));
cl_git_pass(git_oid_cmp(&branch_target_oid, git_object_id(target)));
}
void test_refs_branches_create__can_not_create_a_branch_pointing_at_an_object_unknown_from_the_repository(void)
{
git_repository *repo2;
/* Open another instance of the same repository */
cl_git_pass(git_repository_open(&repo2, cl_fixture("testrepo.git")));
/* Retrieve a commit object from this different repository */
retrieve_known_commit(&target, repo2);
cl_git_fail(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0));
git_repository_free(repo2);
}
void test_refs_branches_create__creating_a_branch_targeting_a_tag_dereferences_it_to_its_commit(void)
{
/* b25fa35 is a tag, pointing to another tag which points to a commit */
retrieve_target_from_oid(&target, repo, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1");
cl_git_pass(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0));
cl_git_pass(git_oid_streq(&branch_target_oid, "e90810b8df3e80c413d903f631643c716887138d"));
}
void test_refs_branches_create__can_not_create_a_branch_pointing_to_a_non_commit_object(void)
{
/* 53fc32d is the tree of commit e90810b */
retrieve_target_from_oid(&target, repo, "53fc32d17276939fc79ed05badaef2db09990016");
cl_git_fail(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0));
git_object_free(target);
/* 521d87c is an annotated tag pointing to a blob */
retrieve_target_from_oid(&target, repo, "521d87c1ec3aef9824daf6d96cc0ae3710766d91");
cl_git_fail(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0));
}
#include "clar_libgit2.h"
#include "refs.h"
#include "branch.h"
static git_repository *repo;
static git_reference *fake_remote;
void test_refs_branches_delete__initialize(void)
{
git_oid id;
cl_fixture_sandbox("testrepo.git");
cl_git_pass(git_repository_open(&repo, "testrepo.git"));
cl_git_pass(git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"));
cl_git_pass(git_reference_create_oid(&fake_remote, repo, "refs/remotes/nulltoken/master", &id, 0));
}
void test_refs_branches_delete__cleanup(void)
{
git_reference_free(fake_remote);
git_repository_free(repo);
cl_fixture_cleanup("testrepo.git");
}
void test_refs_branches_delete__can_not_delete_a_non_existing_branch(void)
{
cl_git_fail(git_branch_delete(repo, "i-am-not-a-local-branch", GIT_BRANCH_LOCAL));
cl_git_fail(git_branch_delete(repo, "neither/a-remote-one", GIT_BRANCH_REMOTE));
}
void test_refs_branches_delete__can_not_delete_a_branch_pointed_at_by_HEAD(void)
{
git_reference *head;
/* Ensure HEAD targets the local master branch */
cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE));
cl_assert(strcmp("refs/heads/master", git_reference_target(head)) == 0);
git_reference_free(head);
cl_git_fail(git_branch_delete(repo, "master", GIT_BRANCH_LOCAL));
}
void test_refs_branches_delete__can_not_delete_a_branch_if_HEAD_is_missing(void)
{
git_reference *head;
cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE));
git_reference_delete(head);
cl_git_fail(git_branch_delete(repo, "br2", GIT_BRANCH_LOCAL));
}
void test_refs_branches_delete__can_delete_a_branch_pointed_at_by_detached_HEAD(void)
{
git_reference *master, *head;
/* Detach HEAD and make it target the commit that "master" points to */
cl_git_pass(git_reference_lookup(&master, repo, "refs/heads/master"));
cl_git_pass(git_reference_create_oid(&head, repo, "HEAD", git_reference_oid(master), 1));
git_reference_free(head);
git_reference_free(master);
cl_git_pass(git_branch_delete(repo, "master", GIT_BRANCH_LOCAL));
}
void test_refs_branches_delete__can_delete_a_local_branch(void)
{
cl_git_pass(git_branch_delete(repo, "br2", GIT_BRANCH_LOCAL));
}
void test_refs_branches_delete__can_delete_a_remote_branch(void)
{
cl_git_pass(git_branch_delete(repo, "nulltoken/master", GIT_BRANCH_REMOTE));
}
#include "clar_libgit2.h"
#include "refs.h"
#include "branch.h"
static git_repository *repo;
static git_strarray branch_list;
static git_reference *fake_remote;
void test_refs_branches_listall__initialize(void)
{
git_oid id;
cl_fixture_sandbox("testrepo.git");
cl_git_pass(git_repository_open(&repo, "testrepo.git"));
cl_git_pass(git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"));
cl_git_pass(git_reference_create_oid(&fake_remote, repo, "refs/remotes/nulltoken/master", &id, 0));
}
void test_refs_branches_listall__cleanup(void)
{
git_strarray_free(&branch_list);
git_reference_free(fake_remote);
git_repository_free(repo);
cl_fixture_cleanup("testrepo.git");
}
static void assert_retrieval(unsigned int flags, unsigned int expected_count)
{
cl_git_pass(git_branch_list(&branch_list, repo, flags));
cl_assert(branch_list.count == expected_count);
}
void test_refs_branches_listall__retrieve_all_branches(void)
{
assert_retrieval(GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE, 6 + 1);
}
void test_refs_branches_listall__retrieve_remote_branches(void)
{
assert_retrieval(GIT_BRANCH_REMOTE, 1);
}
void test_refs_branches_listall__retrieve_local_branches(void)
{
assert_retrieval(GIT_BRANCH_LOCAL, 6);
}
#include "clar_libgit2.h"
#include "branch.h"
static git_repository *repo;
void test_refs_branches_move__initialize(void)
{
cl_fixture_sandbox("testrepo.git");
cl_git_pass(git_repository_open(&repo, "testrepo.git"));
}
void test_refs_branches_move__cleanup(void)
{
git_repository_free(repo);
cl_fixture_cleanup("testrepo.git");
}
#define NEW_BRANCH_NAME "new-branch-on-the-block"
void test_refs_branches_move__can_move_a_local_branch(void)
{
cl_git_pass(git_branch_move(repo, "br2", NEW_BRANCH_NAME, 0));
}
void test_refs_branches_move__can_move_a_local_branch_to_a_different_namespace(void)
{
/* Downward */
cl_git_pass(git_branch_move(repo, "br2", "somewhere/" NEW_BRANCH_NAME, 0));
/* Upward */
cl_git_pass(git_branch_move(repo, "somewhere/" NEW_BRANCH_NAME, "br2", 0));
}
void test_refs_branches_move__can_move_a_local_branch_to_a_partially_colliding_namespace(void)
{
/* Downward */
cl_git_pass(git_branch_move(repo, "br2", "br2/" NEW_BRANCH_NAME, 0));
/* Upward */
cl_git_pass(git_branch_move(repo, "br2/" NEW_BRANCH_NAME, "br2", 0));
}
void test_refs_branches_move__can_not_move_a_branch_if_its_destination_name_collide_with_an_existing_one(void)
{
cl_git_fail(git_branch_move(repo, "br2", "master", 0));
}
void test_refs_branches_move__can_not_move_a_non_existing_branch(void)
{
cl_git_fail(git_branch_move(repo, "i-am-no-branch", NEW_BRANCH_NAME, 0));
}
void test_refs_branches_move__can_force_move_over_an_existing_branch(void)
{
cl_git_pass(git_branch_move(repo, "br2", "master", 1));
}
void test_refs_branches_move__can_not_move_a_branch_through_its_canonical_name(void)
{
cl_git_fail(git_branch_move(repo, "refs/heads/br2", NEW_BRANCH_NAME, 1));
}
......@@ -110,7 +110,7 @@ static int remove_file_cb(void *data, git_buf *file)
return 0;
if (git_path_isdir(filename))
cl_git_pass(git_futils_rmdir_r(filename, 1));
cl_git_pass(git_futils_rmdir_r(filename, GIT_DIRREMOVAL_FILES_AND_DIRS));
else
cl_git_pass(p_unlink(git_buf_cstr(file)));
......@@ -347,7 +347,7 @@ void test_status_worktree__issue_592_3(void)
repo = cl_git_sandbox_init("issue_592");
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "c"));
cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), 1));
cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt"));
......@@ -377,7 +377,7 @@ void test_status_worktree__issue_592_5(void)
repo = cl_git_sandbox_init("issue_592");
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "t"));
cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), 1));
cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
cl_git_pass(p_mkdir(git_buf_cstr(&path), 0777));
cl_git_pass(git_status_foreach(repo, cb_status__check_592, NULL));
......
......@@ -73,7 +73,7 @@ BEGIN_TEST(read1, "list all tag names from the repository")
must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
must_pass(git_tag_list(&tag_list, repo));
must_be_true(tag_list.count == 3);
must_be_true(tag_list.count == 4);
git_strarray_free(&tag_list);
git_repository_free(repo);
......@@ -98,10 +98,10 @@ exit:
BEGIN_TEST(read2, "list all tag names from the repository matching a specified pattern")
git_repository *repo;
must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
must_pass(ensure_tag_pattern_match(repo, "", 3));
must_pass(ensure_tag_pattern_match(repo, "*", 3));
must_pass(ensure_tag_pattern_match(repo, "", 4));
must_pass(ensure_tag_pattern_match(repo, "*", 4));
must_pass(ensure_tag_pattern_match(repo, "t*", 1));
must_pass(ensure_tag_pattern_match(repo, "*b", 2));
must_pass(ensure_tag_pattern_match(repo, "*b", 3));
must_pass(ensure_tag_pattern_match(repo, "e", 0));
must_pass(ensure_tag_pattern_match(repo, "e90810b", 1));
must_pass(ensure_tag_pattern_match(repo, "e90810[ab]", 1));
......
......@@ -1164,10 +1164,10 @@ BEGIN_TEST(list0, "try to list all the references in our test repo")
printf("# %s\n", ref_list.strings[i]);
}*/
/* We have exactly 9 refs in total if we include the packed ones:
/* We have exactly 10 refs in total if we include the packed ones:
* there is a reference that exists both in the packfile and as
* loose, but we only list it once */
must_be_true(ref_list.count == 9);
must_be_true(ref_list.count == 10);
git_strarray_free(&ref_list);
git_repository_free(repo);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment