Commit 45f2b7a43ffe77bac3acbf21a041b56f03842ba8

Patrick Steinhardt 2015-10-21T11:48:02

worktree: implement `git_worktree_list` Add new module for working trees with the `git_worktree_list` function. The function lists names for all working trees of a certain repository.

diff --git a/include/git2/worktree.h b/include/git2/worktree.h
new file mode 100644
index 0000000..c09fa32
--- /dev/null
+++ b/include/git2/worktree.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_git_worktree_h__
+#define INCLUDE_git_worktree_h__
+
+#include "common.h"
+#include "types.h"
+#include "strarray.h"
+
+/**
+ * @file git2/worktrees.h
+ * @brief Git worktree related functions
+ * @defgroup git_commit Git worktree related functions
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * List names of linked working trees
+ *
+ * The returned list should be released with `git_strarray_free`
+ * when no longer needed.
+ *
+ * @param out pointer to the array of working tree names
+ * @param repo the repo to use when listing working trees
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_worktree_list(git_strarray *out, git_repository *repo);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/src/worktree.c b/src/worktree.c
new file mode 100644
index 0000000..28d895d
--- /dev/null
+++ b/src/worktree.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "git2/worktree.h"
+
+#include "common.h"
+#include "repository.h"
+
+static bool is_worktree_dir(git_buf *dir)
+{
+	return git_path_contains_file(dir, "commondir")
+		&& git_path_contains_file(dir, "gitdir")
+		&& git_path_contains_file(dir, "HEAD");
+}
+
+int git_worktree_list(git_strarray *wts, git_repository *repo)
+{
+	git_vector worktrees = GIT_VECTOR_INIT;
+	git_buf path = GIT_BUF_INIT;
+	char *worktree;
+	unsigned i, len;
+	int error;
+
+	assert(wts && repo);
+
+	wts->count = 0;
+	wts->strings = NULL;
+
+	if ((error = git_buf_printf(&path, "%s/worktrees/", repo->commondir)) < 0)
+		goto exit;
+	if (!git_path_exists(path.ptr) || git_path_is_empty_dir(path.ptr))
+		goto exit;
+	if ((error = git_path_dirload(&worktrees, path.ptr, path.size, 0x0)) < 0)
+		goto exit;
+
+	len = path.size;
+
+	git_vector_foreach(&worktrees, i, worktree) {
+		git_buf_truncate(&path, len);
+		git_buf_puts(&path, worktree);
+
+		if (!is_worktree_dir(&path)) {
+			git_vector_remove(&worktrees, i);
+			git__free(worktree);
+		}
+	}
+
+	wts->strings = (char **)git_vector_detach(&wts->count, NULL, &worktrees);
+
+exit:
+	git_buf_free(&path);
+
+	return error;
+}
diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c
new file mode 100644
index 0000000..3acae88
--- /dev/null
+++ b/tests/worktree/worktree.c
@@ -0,0 +1,107 @@
+#include "clar_libgit2.h"
+#include "worktree_helpers.h"
+
+#include "git2/worktree.h"
+#include "repository.h"
+
+#define COMMON_REPO "testrepo"
+#define WORKTREE_REPO "testrepo-worktree"
+
+static worktree_fixture fixture =
+	WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
+
+void test_worktree_worktree__initialize(void)
+{
+	setup_fixture_worktree(&fixture);
+}
+
+void test_worktree_worktree__cleanup(void)
+{
+	cleanup_fixture_worktree(&fixture);
+}
+
+void test_worktree_worktree__list(void)
+{
+	git_strarray wts;
+
+	cl_git_pass(git_worktree_list(&wts, fixture.repo));
+	cl_assert_equal_i(wts.count, 1);
+	cl_assert_equal_s(wts.strings[0], "testrepo-worktree");
+
+	git_strarray_free(&wts);
+}
+
+void test_worktree_worktree__list_with_invalid_worktree_dirs(void)
+{
+	const char *filesets[3][2] = {
+		{ "gitdir", "commondir" },
+		{ "gitdir", "HEAD" },
+		{ "HEAD", "commondir" },
+	};
+	git_buf path = GIT_BUF_INIT;
+	git_strarray wts;
+	unsigned i, j, len;
+
+	cl_git_pass(git_buf_printf(&path, "%s/worktrees/invalid",
+		    fixture.repo->commondir));
+	cl_git_pass(p_mkdir(path.ptr, 0755));
+
+	len = path.size;
+
+	for (i = 0; i < ARRAY_SIZE(filesets); i++) {
+
+		for (j = 0; j < ARRAY_SIZE(filesets[i]); j++) {
+			git_buf_truncate(&path, len);
+			cl_git_pass(git_buf_joinpath(&path, path.ptr, filesets[i][j]));
+			cl_git_pass(p_close(p_creat(path.ptr, 0644)));
+		}
+
+		cl_git_pass(git_worktree_list(&wts, fixture.worktree));
+		cl_assert_equal_i(wts.count, 1);
+		cl_assert_equal_s(wts.strings[0], "testrepo-worktree");
+		git_strarray_free(&wts);
+
+		for (j = 0; j < ARRAY_SIZE(filesets[i]); j++) {
+			git_buf_truncate(&path, len);
+			cl_git_pass(git_buf_joinpath(&path, path.ptr, filesets[i][j]));
+			p_unlink(path.ptr);
+		}
+	}
+
+	git_buf_free(&path);
+}
+
+void test_worktree_worktree__list_in_worktree_repo(void)
+{
+	git_strarray wts;
+
+	cl_git_pass(git_worktree_list(&wts, fixture.worktree));
+	cl_assert_equal_i(wts.count, 1);
+	cl_assert_equal_s(wts.strings[0], "testrepo-worktree");
+
+	git_strarray_free(&wts);
+}
+
+void test_worktree_worktree__list_bare(void)
+{
+	git_repository *repo;
+	git_strarray wts;
+
+	repo = cl_git_sandbox_init("testrepo.git");
+	cl_git_pass(git_worktree_list(&wts, repo));
+	cl_assert_equal_i(wts.count, 0);
+
+	git_repository_free(repo);
+}
+
+void test_worktree_worktree__list_without_worktrees(void)
+{
+	git_repository *repo;
+	git_strarray wts;
+
+	repo = cl_git_sandbox_init("testrepo2");
+	cl_git_pass(git_worktree_list(&wts, repo));
+	cl_assert_equal_i(wts.count, 0);
+
+	git_repository_free(repo);
+}