tree: Add traversal in post-order
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
diff --git a/include/git2/tree.h b/include/git2/tree.h
index 8d638f7..68d8235 100644
--- a/include/git2/tree.h
+++ b/include/git2/tree.h
@@ -282,6 +282,36 @@ GIT_EXTERN(int) git_treebuilder_write(git_oid *oid, git_repository *repo, git_tr
* entry, GIT_EINVALIDPATH or an error code
*/
GIT_EXTERN(int) git_tree_frompath(git_tree **parent_out, git_tree *root, const char *treeentry_path);
+
+/** Callback for the tree traversal method */
+typedef int (*git_treewalk_cb)(const char *root, git_tree_entry *entry);
+
+/** Tree traversal modes */
+enum git_treewalk_mode {
+ GIT_TREEWALK_PRE = 0, /* Pre-order */
+ GIT_TREEWALK_POST = 1, /* Post-order */
+};
+
+/**
+ * Traverse the entries in a tree and its subtrees in
+ * post or pre order
+ *
+ * The entries will be traversed in the specified order,
+ * children subtrees will be automatically loaded as required,
+ * and the `callback` will be called once per entry with
+ * the current (relative) root for the entry and the entry
+ * data itself.
+ *
+ * If the callback returns a negative value, the passed entry
+ * will be skiped on the traversal.
+ *
+ * @param tree The tree to walk
+ * @param callback Function to call on each tree entry
+ * @param mode Traversal mode (pre or post-order)
+ * @return GIT_SUCCESS or an error code
+ */
+GIT_EXTERN(int) git_tree_walk(git_tree *walk, git_treewalk_cb callback, int mode);
+
/** @} */
GIT_END_DECL
#endif
diff --git a/src/tree.c b/src/tree.c
index 00aefc2..77034fa 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -15,6 +15,8 @@
#define MAX_FILEMODE 0777777
#define MAX_FILEMODE_BYTES 6
+#define ENTRY_IS_TREE(e) ((e)->attr & 040000)
+
static int valid_attributes(const int attributes)
{
return attributes >= 0 && attributes <= MAX_FILEMODE;
@@ -31,8 +33,8 @@ static int entry_sort_cmp(const void *a, const void *b)
const git_tree_entry *entry_b = (const git_tree_entry *)(b);
return git_futils_cmp_path(
- entry_a->filename, entry_a->filename_len, entry_a->attr & 040000,
- entry_b->filename, entry_b->filename_len, entry_b->attr & 040000);
+ entry_a->filename, entry_a->filename_len, ENTRY_IS_TREE(entry_a),
+ entry_b->filename, entry_b->filename_len, ENTRY_IS_TREE(entry_b));
}
@@ -654,3 +656,53 @@ int git_tree_frompath(git_tree **parent_out, git_tree *root, const char *treeent
strcpy(buffer, treeentry_path);
return tree_frompath(parent_out, root, buffer, 0);
}
+
+static int tree_walk_post(git_tree *tree, git_treewalk_cb callback, char *root, size_t root_len)
+{
+ int error;
+ unsigned int i;
+
+ for (i = 0; i < tree->entries.length; ++i) {
+ git_tree_entry *entry = tree->entries.contents[i];
+
+ root[root_len] = '\0';
+
+ if (callback(root, entry) < 0)
+ continue;
+
+ if (ENTRY_IS_TREE(entry)) {
+ git_tree *subtree;
+
+ if ((error = git_tree_lookup(&subtree, tree->object.repo, &entry->oid)) < 0)
+ return error;
+
+ strcpy(root + root_len, entry->filename);
+ root[root_len + entry->filename_len] = '/';
+
+ tree_walk_post(subtree, callback, root, root_len + entry->filename_len + 1);
+
+ git_tree_close(subtree);
+ }
+ }
+
+ return GIT_SUCCESS;
+}
+
+int git_tree_walk(git_tree *tree, git_treewalk_cb callback, int mode)
+{
+ char root_path[GIT_PATH_MAX];
+
+ root_path[0] = '\0';
+ switch (mode) {
+ case GIT_TREEWALK_POST:
+ return tree_walk_post(tree, callback, root_path, 0);
+
+ case GIT_TREEWALK_PRE:
+ return git__throw(GIT_ENOTIMPLEMENTED,
+ "Preorder tree walking is still not implemented");
+
+ default:
+ return git__throw(GIT_EINVALIDARGS,
+ "Invalid walking mode for tree walk");
+ }
+}