tree: Add git_tree_frompath() which, given a relative path to a tree entry, retrieves the tree object containing this tree entry
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
diff --git a/include/git2/tree.h b/include/git2/tree.h
index d781ea1..8d638f7 100644
--- a/include/git2/tree.h
+++ b/include/git2/tree.h
@@ -268,6 +268,20 @@ GIT_EXTERN(void) git_treebuilder_filter(git_treebuilder *bld, int (*filter)(cons
*/
GIT_EXTERN(int) git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld);
+/**
+ * Retrieve the tree object containing a tree entry, given
+ * a relative path to this tree entry
+ *
+ * The returned tree is owned by the repository and
+ * should be closed with the `git_object_close` method.
+ *
+ * @param parent_out Pointer where to store the parent tree
+ * @param root A previously loaded tree which will be the root of the relative path
+ * @param treeentry_path Path to the tree entry from which to extract the last tree object
+ * @return GIT_SUCCESS on success; GIT_ENOTFOUND if the path does not lead to an
+ * entry, GIT_EINVALIDPATH or an error code
+ */
+GIT_EXTERN(int) git_tree_frompath(git_tree **parent_out, git_tree *root, const char *treeentry_path);
/** @} */
GIT_END_DECL
#endif
diff --git a/src/tree.c b/src/tree.c
index 0acf74e..3801df1 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -559,4 +559,49 @@ void git_treebuilder_free(git_treebuilder *bld)
free(bld);
}
+static int tree_frompath(git_tree **parent_out, git_tree *root, const char *treeentry_path, int offset)
+{
+ char *slash_pos = NULL;
+ const git_tree_entry* entry;
+ int error = GIT_SUCCESS;
+ git_tree *subtree;
+
+ if (!*(treeentry_path + offset))
+ return git__rethrow(GIT_EINVALIDPATH, "Invalid relative path to a tree entry '%s'.", treeentry_path);
+
+ slash_pos = (char *)strchr(treeentry_path + offset, '/');
+
+ if (slash_pos == NULL)
+ return git_tree_lookup(parent_out, root->object.repo, git_object_id((const git_object *)root));
+
+ if (slash_pos == treeentry_path + offset)
+ return git__rethrow(GIT_EINVALIDPATH, "Invalid relative path to a tree entry '%s'.", treeentry_path);
+
+ *slash_pos = '\0';
+
+ entry = git_tree_entry_byname(root, treeentry_path + offset);
+
+ if (slash_pos != NULL)
+ *slash_pos = '/';
+
+ if (entry == NULL)
+ return git__rethrow(GIT_ENOTFOUND, "No tree entry can be found from the given tree and relative path '%s'.", treeentry_path);
+ if ((error = git_tree_lookup(&subtree, root->object.repo, &entry->oid)) < GIT_SUCCESS)
+ return error;
+
+ error = tree_frompath(parent_out, subtree, treeentry_path, slash_pos - treeentry_path + 1);
+
+ git_tree_close(subtree);
+ return error;
+}
+
+int git_tree_frompath(git_tree **parent_out, git_tree *root, const char *treeentry_path)
+{
+ char buffer[GIT_PATH_MAX];
+
+ assert(root && treeentry_path);
+
+ strcpy(buffer, treeentry_path);
+ return tree_frompath(parent_out, root, buffer, 0);
+}
diff --git a/tests-clay/clay.h b/tests-clay/clay.h
index 7e653cf..bc4267b 100644
--- a/tests-clay/clay.h
+++ b/tests-clay/clay.h
@@ -88,6 +88,11 @@ extern void test_network_remotes__parsing(void);
extern void test_network_remotes__refspec_parsing(void);
extern void test_network_remotes__fnmatch(void);
extern void test_network_remotes__transform(void);
+extern void test_object_tree_frompath__initialize(void);
+extern void test_object_tree_frompath__cleanup(void);
+extern void test_object_tree_frompath__retrieve_tree_from_path_to_treeentry(void);
+extern void test_object_tree_frompath__fail_when_processing_an_unknown_tree_segment(void);
+extern void test_object_tree_frompath__fail_when_processing_an_invalid_path(void);
extern void test_status_single__hash_single_file(void);
extern void test_status_worktree__initialize(void);
extern void test_status_worktree__cleanup(void);
diff --git a/tests-clay/clay_main.c b/tests-clay/clay_main.c
index dcd9ae8..da90872 100644
--- a/tests-clay/clay_main.c
+++ b/tests-clay/clay_main.c
@@ -687,9 +687,12 @@ static const struct clay_func _all_callbacks[] = {
{"refspec_parsing", &test_network_remotes__refspec_parsing, 8},
{"fnmatch", &test_network_remotes__fnmatch, 8},
{"transform", &test_network_remotes__transform, 8},
- {"hash_single_file", &test_status_single__hash_single_file, 9},
- {"whole_repository", &test_status_worktree__whole_repository, 10},
- {"empty_repository", &test_status_worktree__empty_repository, 10}
+ {"retrieve_tree_from_path_to_treeentry", &test_object_tree_frompath__retrieve_tree_from_path_to_treeentry, 9},
+ {"fail_when_processing_an_unknown_tree_segment", &test_object_tree_frompath__fail_when_processing_an_unknown_tree_segment, 9},
+ {"fail_when_processing_an_invalid_path", &test_object_tree_frompath__fail_when_processing_an_invalid_path, 9},
+ {"hash_single_file", &test_status_single__hash_single_file, 10},
+ {"whole_repository", &test_status_worktree__whole_repository, 11},
+ {"empty_repository", &test_status_worktree__empty_repository, 11}
};
static const struct clay_suite _all_suites[] = {
@@ -748,26 +751,32 @@ static const struct clay_suite _all_suites[] = {
&_all_callbacks[23], 4
},
{
+ "object::tree::frompath",
+ {"initialize", &test_object_tree_frompath__initialize, 9},
+ {"cleanup", &test_object_tree_frompath__cleanup, 9},
+ &_all_callbacks[27], 3
+ },
+ {
"status::single",
{NULL, NULL, 0},
{NULL, NULL, 0},
- &_all_callbacks[27], 1
+ &_all_callbacks[30], 1
},
{
"status::worktree",
- {"initialize", &test_status_worktree__initialize, 10},
- {"cleanup", &test_status_worktree__cleanup, 10},
- &_all_callbacks[28], 2
+ {"initialize", &test_status_worktree__initialize, 11},
+ {"cleanup", &test_status_worktree__cleanup, 11},
+ &_all_callbacks[31], 2
}
};
-static const char _suites_str[] = "core::dirent, core::filebuf, core::oid, core::path, core::rmdir, core::string, core::strtol, core::vector, network::remotes, status::single, status::worktree";
+static const char _suites_str[] = "core::dirent, core::filebuf, core::oid, core::path, core::rmdir, core::string, core::strtol, core::vector, network::remotes, object::tree::frompath, status::single, status::worktree";
int _MAIN_CC main(int argc, char *argv[])
{
return clay_test(
argc, argv, _suites_str,
- _all_callbacks, 30,
- _all_suites, 11
+ _all_callbacks, 33,
+ _all_suites, 12
);
}
diff --git a/tests-clay/object/tree/frompath.c b/tests-clay/object/tree/frompath.c
new file mode 100644
index 0000000..33a76e8
--- /dev/null
+++ b/tests-clay/object/tree/frompath.c
@@ -0,0 +1,76 @@
+#include "clay_libgit2.h"
+
+#define REPOSITORY_FOLDER "testrepo.git"
+
+static git_repository *repo;
+const char *tree_with_subtrees_oid = "ae90f12eea699729ed24555e40b9fd669da12a12";
+static git_tree *tree;
+
+void test_object_tree_frompath__initialize(void)
+{
+ git_oid id;
+
+ cl_fixture_sandbox(REPOSITORY_FOLDER);
+ cl_git_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
+ cl_assert(repo != NULL);
+
+ cl_git_pass(git_oid_fromstr(&id, tree_with_subtrees_oid));
+ cl_git_pass(git_tree_lookup(&tree, repo, &id));
+ cl_assert(tree != NULL);
+}
+
+void test_object_tree_frompath__cleanup(void)
+{
+ git_tree_close(tree);
+ git_repository_free(repo);
+}
+
+static void assert_tree_from_path(git_tree *root, const char *path, git_error expected_result, const char *expected_raw_oid)
+{
+ git_tree *containing_tree = NULL;
+
+ cl_assert(git_tree_frompath(&containing_tree, root, path) == expected_result);
+
+ if (containing_tree == NULL && expected_result != GIT_SUCCESS)
+ return;
+
+ cl_assert(containing_tree != NULL && expected_result == GIT_SUCCESS);
+
+ cl_assert(git_oid_streq(git_object_id((const git_object *)containing_tree), expected_raw_oid) == GIT_SUCCESS);
+
+ git_tree_close(containing_tree);
+}
+
+void test_object_tree_frompath__retrieve_tree_from_path_to_treeentry(void)
+{
+ /* Will return self if given a one path segment... */
+ assert_tree_from_path(tree, "README", GIT_SUCCESS, tree_with_subtrees_oid);
+
+ /* ...even one that lead to a non existent tree entry. */
+ assert_tree_from_path(tree, "i-do-not-exist.txt", GIT_SUCCESS, tree_with_subtrees_oid);
+
+ /* Will return fgh tree oid given this following path... */
+ assert_tree_from_path(tree, "ab/de/fgh/1.txt", GIT_SUCCESS, "3259a6bd5b57fb9c1281bb7ed3167b50f224cb54");
+
+ /* ... and ab tree oid given this one. */
+ assert_tree_from_path(tree, "ab/de", GIT_SUCCESS, "f1425cef211cc08caa31e7b545ffb232acb098c3");
+
+ /* Will succeed if given a valid path which leads to a tree entry which doesn't exist */
+ assert_tree_from_path(tree, "ab/de/fgh/i-do-not-exist.txt", GIT_SUCCESS, "3259a6bd5b57fb9c1281bb7ed3167b50f224cb54");
+}
+
+void test_object_tree_frompath__fail_when_processing_an_unknown_tree_segment(void)
+{
+ assert_tree_from_path(tree, "nope/de/fgh/1.txt", GIT_ENOTFOUND, NULL);
+ assert_tree_from_path(tree, "ab/me-neither/fgh/2.txt", GIT_ENOTFOUND, NULL);
+}
+
+void test_object_tree_frompath__fail_when_processing_an_invalid_path(void)
+{
+ assert_tree_from_path(tree, "/", GIT_EINVALIDPATH, NULL);
+ assert_tree_from_path(tree, "/ab", GIT_EINVALIDPATH, NULL);
+ assert_tree_from_path(tree, "/ab/de", GIT_EINVALIDPATH, NULL);
+ assert_tree_from_path(tree, "ab/", GIT_EINVALIDPATH, NULL);
+ assert_tree_from_path(tree, "ab//de", GIT_EINVALIDPATH, NULL);
+ assert_tree_from_path(tree, "ab/de/", GIT_EINVALIDPATH, NULL);
+}