add got_object_tree_entry_is_symlink() and got_object_resolve_symlinks()
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
diff --git a/include/got_object.h b/include/got_object.h
index b0dceed..fe23bf0 100644
--- a/include/got_object.h
+++ b/include/got_object.h
@@ -212,6 +212,19 @@ struct got_tree_entry *got_tree_entry_get_prev(struct got_tree_object *,
/* Return non-zero if the specified tree entry is a Git submodule. */
int got_object_tree_entry_is_submodule(struct got_tree_entry *);
+/* Return non-zero if the specified tree entry is a symbolic link. */
+int got_object_tree_entry_is_symlink(struct got_tree_entry *);
+
+/*
+ * Resolve an in-repository symlink at the specified path in the tree
+ * corresponding to the specified commit. If the specified path is not
+ * a symlink then set *link_target to NULL.
+ * Otherwise, resolve symlinks recursively and return the final link
+ * target path. The caller must dispose of it with free(3).
+ */
+const struct got_error *got_object_resolve_symlinks(char **, const char *,
+ struct got_object_id *, struct got_repository *);
+
/*
* Compare two trees and indicate whether the entry at the specified path
* differs between them. The path must not be the root path "/"; the function
diff --git a/lib/object.c b/lib/object.c
index 3debd7f..450df8e 100644
--- a/lib/object.c
+++ b/lib/object.c
@@ -32,6 +32,7 @@
#include <sha1.h>
#include <zlib.h>
#include <ctype.h>
+#include <libgen.h>
#include <limits.h>
#include <imsg.h>
#include <time.h>
@@ -872,8 +873,7 @@ got_tree_entry_get_symlink_target(char **link_target, struct got_tree_entry *te,
*link_target = NULL;
- /* S_IFDIR check avoids confusing symlinks with submodules. */
- if ((te->mode & (S_IFDIR | S_IFLNK)) != S_IFLNK)
+ if (!got_object_tree_entry_is_symlink(te))
return got_error(GOT_ERR_TREE_ENTRY_TYPE);
err = got_object_open_as_blob(&blob, repo,
@@ -1820,6 +1820,116 @@ got_object_tree_entry_is_submodule(struct got_tree_entry *te)
return (te->mode & S_IFMT) == (S_IFDIR | S_IFLNK);
}
+int
+got_object_tree_entry_is_symlink(struct got_tree_entry *te)
+{
+ /* S_IFDIR check avoids confusing symlinks with submodules. */
+ return ((te->mode & (S_IFDIR | S_IFLNK)) == S_IFLNK);
+}
+
+static const struct got_error *
+resolve_symlink(char **link_target, const char *path,
+ struct got_object_id *commit_id, struct got_repository *repo)
+{
+ const struct got_error *err = NULL;
+ char *name, *parent_path = NULL;
+ struct got_object_id *tree_obj_id = NULL;
+ struct got_tree_object *tree = NULL;
+ struct got_tree_entry *te = NULL;
+
+ *link_target = NULL;
+
+ name = basename(path);
+ if (name == NULL)
+ return got_error_from_errno2("basename", path);
+
+ err = got_path_dirname(&parent_path, path);
+ if (err)
+ return err;
+
+ err = got_object_id_by_path(&tree_obj_id, repo, commit_id,
+ parent_path);
+ if (err) {
+ if (err->code == GOT_ERR_NO_TREE_ENTRY) {
+ /* Display the complete path in error message. */
+ err = got_error_path(path, err->code);
+ }
+ goto done;
+ }
+
+ err = got_object_open_as_tree(&tree, repo, tree_obj_id);
+ if (err)
+ goto done;
+
+ te = got_object_tree_find_entry(tree, name);
+ if (te == NULL) {
+ err = got_error_path(path, GOT_ERR_NO_TREE_ENTRY);
+ goto done;
+ }
+
+ if (got_object_tree_entry_is_symlink(te)) {
+ err = got_tree_entry_get_symlink_target(link_target, te, repo);
+ if (err)
+ goto done;
+ if (!got_path_is_absolute(*link_target)) {
+ char *abspath;
+ if (asprintf(&abspath, "%s/%s", parent_path,
+ *link_target) == -1) {
+ err = got_error_from_errno("asprintf");
+ goto done;
+ }
+ free(*link_target);
+ *link_target = malloc(PATH_MAX);
+ if (*link_target == NULL) {
+ err = got_error_from_errno("malloc");
+ goto done;
+ }
+ err = got_canonpath(abspath, *link_target, PATH_MAX);
+ free(abspath);
+ if (err)
+ goto done;
+ }
+ }
+done:
+ free(tree_obj_id);
+ if (tree)
+ got_object_tree_close(tree);
+ if (err) {
+ free(*link_target);
+ *link_target = NULL;
+ }
+ return err;
+}
+
+const struct got_error *
+got_object_resolve_symlinks(char **link_target, const char *path,
+ struct got_object_id *commit_id, struct got_repository *repo)
+{
+ const struct got_error *err = NULL;
+ char *next_target = NULL;
+ int max_recursion = 40; /* matches Git */
+
+ *link_target = NULL;
+
+ do {
+ err = resolve_symlink(&next_target,
+ *link_target ? *link_target : path, commit_id, repo);
+ if (err)
+ break;
+ if (next_target) {
+ free(*link_target);
+ if (--max_recursion == 0) {
+ err = got_error_path(path, GOT_ERR_RECURSION);
+ *link_target = NULL;
+ break;
+ }
+ *link_target = next_target;
+ }
+ } while (next_target);
+
+ return err;
+}
+
const struct got_error *
got_traverse_packed_commits(struct got_object_id_queue *traversed_commits,
struct got_object_id *commit_id, const char *path,