summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVicent Martí <tanoku@gmail.com>2012-04-11 03:43:30 -0700
committerVicent Martí <tanoku@gmail.com>2012-04-11 03:43:30 -0700
commit0a20eee9536e8db0ab8d91f426f971f7151aaefa (patch)
tree7b57890c272e66c0f47ed64753eb5cec31f2bffa /src
parentb78fb64d2f5c1b9e2e834e05e042271147c8d188 (diff)
parent4615f0f71ba849adef08f7a677842af3e0ee3d53 (diff)
downloadlibgit2-0a20eee9536e8db0ab8d91f426f971f7151aaefa.tar.gz
Merge pull request #619 from nulltoken/topic/branches
Basic branch management API
Diffstat (limited to 'src')
-rw-r--r--src/branch.c198
-rw-r--r--src/branch.h17
-rw-r--r--src/fileops.c23
-rw-r--r--src/fileops.h17
-rw-r--r--src/refs.c9
-rw-r--r--src/tag.c20
-rw-r--r--src/transports/local.c9
7 files changed, 284 insertions, 9 deletions
diff --git a/src/branch.c b/src/branch.c
new file mode 100644
index 000000000..5efb05b92
--- /dev/null
+++ b/src/branch.c
@@ -0,0 +1,198 @@
+/*
+ * 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);
+}
diff --git a/src/branch.h b/src/branch.h
new file mode 100644
index 000000000..d0e5abc8b
--- /dev/null
+++ b/src/branch.h
@@ -0,0 +1,17 @@
+/*
+ * 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
diff --git a/src/fileops.c b/src/fileops.c
index b3bb3890e..9da1bf789 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -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;
}
diff --git a/src/fileops.h b/src/fileops.h
index 865b3c9b0..9cc2d1699 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -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.
diff --git a/src/refs.c b/src/refs.c
index ed364cf90..fb23a0ef8 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -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;
diff --git a/src/tag.c b/src/tag.c
index a5089e71c..cfd2c7081 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -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;
+}
diff --git a/src/transports/local.c b/src/transports/local.c
index eb24db0fd..6cf8ed9d6 100644
--- a/src/transports/local.c
+++ b/src/transports/local.c
@@ -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);