commit - 659dc16e2bd9edb402c4334bd05ef4dae6bde8a0
commit + e40622f412c8f937c81a8f76aa780647ea7392bf
blob - b0dceed9f15dbec807d84c8744cbe9fce22faa76
blob + fe23bf04f80b2f9cb6cd0a084b66aa3656096f18
--- include/got_object.h
+++ include/got_object.h
/* 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
* got_object_id_cmp() should be used instead to compare the tree roots.
blob - 3debd7f31957602ee2f82263bfad921ca75edf76
blob + 450df8e2ab748c7c4c138067469d18252790cac4
--- lib/object.c
+++ lib/object.c
#include <sha1.h>
#include <zlib.h>
#include <ctype.h>
+#include <libgen.h>
#include <limits.h>
#include <imsg.h>
#include <time.h>
*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,
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,
struct got_repository *repo)