Commit Diff


commit - 5d58be1291225e7e6b5febddb818535c9907fd09
commit + 0d6c6ee302022b4b2746cd5a85df1f34f8891b9c
blob - 37523a3f4cb248caf256b0e5a47510f04058befc
blob + 170dd8d78a6e6113a9d9057dfc445245e87596bd
--- got/got.1
+++ got/got.1
@@ -842,6 +842,8 @@ annotations:
 .It * Ta entry is an executable file
 .It $ Ta entry is a Git submodule
 .El
+.Pp
+Symbolic link entries are also annotated with the target path of the link.
 .Pp
 If no
 .Ar path
blob - e8ed8348332436dcca1dd2effed1494c89d79ecd
blob + f14e875454e17d7c6012447918fec9a697f40c10
--- got/got.c
+++ got/got.c
@@ -4299,13 +4299,15 @@ usage_tree(void)
 	exit(1);
 }
 
-static void
+static const struct got_error *
 print_entry(struct got_tree_entry *te, const char *id, const char *path,
-    const char *root_path)
+    const char *root_path, struct got_repository *repo)
 {
+	const struct got_error *err = NULL;
 	int is_root_path = (strcmp(path, root_path) == 0);
 	const char *modestr = "";
 	mode_t mode = got_tree_entry_get_mode(te);
+	char *link_target = NULL;
 
 	path += strlen(root_path);
 	while (path[0] == '/')
@@ -4313,15 +4315,30 @@ print_entry(struct got_tree_entry *te, const char *id,
 
 	if (got_object_tree_entry_is_submodule(te))
 		modestr = "$";
-	else if (S_ISLNK(mode))
+	else if (S_ISLNK(mode)) {
+		int i;
+
+		err = got_tree_entry_get_symlink_target(&link_target, te, repo);
+		if (err)
+			return err;
+		for (i = 0; i < strlen(link_target); i++) {
+			if (!isprint((unsigned char)link_target[i]))
+				link_target[i] = '?';
+		}
+
 		modestr = "@";
+	}
 	else if (S_ISDIR(mode))
 		modestr = "/";
 	else if (mode & S_IXUSR)
 		modestr = "*";
 
-	printf("%s%s%s%s%s\n", id ? id : "", path,
-	    is_root_path ? "" : "/", got_tree_entry_get_name(te), modestr);
+	printf("%s%s%s%s%s%s%s\n", id ? id : "", path,
+	    is_root_path ? "" : "/", got_tree_entry_get_name(te), modestr,
+	    link_target ? " -> ": "", link_target ? link_target : "");
+
+	free(link_target);
+	return NULL;
 }
 
 static const struct got_error *
@@ -4363,8 +4380,10 @@ print_tree(const char *path, struct got_object_id *com
 			}
 			free(id_str);
 		}
-		print_entry(te, id, path, root_path);
+		err = print_entry(te, id, path, root_path, repo);
 		free(id);
+		if (err)
+			goto done;
 
 		if (recurse && S_ISDIR(got_tree_entry_get_mode(te))) {
 			char *child_path;
blob - d17ff2d8455425992dbcd98fe8e0d53acfd2c208
blob + 50fe098796823909a7aa2c98d1b90d66536e434c
--- include/got_error.h
+++ include/got_error.h
@@ -140,6 +140,7 @@
 #define GOT_ERR_NO_REMOTE	123
 #define GOT_ERR_FETCH_NO_BRANCH	124
 #define GOT_ERR_FETCH_BAD_REF	125
+#define GOT_ERR_TREE_ENTRY_TYPE	126
 
 static const struct got_error {
 	int code;
@@ -286,6 +287,7 @@ static const struct got_error {
 	{ GOT_ERR_NO_REMOTE, "remote repository not found" },
 	{ GOT_ERR_FETCH_NO_BRANCH, "could not find any branches to fetch" },
 	{ GOT_ERR_FETCH_BAD_REF, "reference cannot be fetched" },
+	{ GOT_ERR_TREE_ENTRY_TYPE, "unexpected tree entry type" },
 };
 
 /*
blob - 461975117c98f1bb74df15d9317223d1940e4c6d
blob + 54c7093a4d0bc68cfd3ef1939b7c80f71106e777
--- include/got_object.h
+++ include/got_object.h
@@ -191,6 +191,13 @@ const char *got_tree_entry_get_name(struct got_tree_en
 /* Get the object ID of a tree entry. */
 struct got_object_id *got_tree_entry_get_id(struct got_tree_entry *);
 
+/*
+ * Get a string containing the target path of a given a symlink tree entry.
+ * The caller should dispose of it with free(3).
+ */
+const struct got_error *got_tree_entry_get_symlink_target(char **,
+    struct got_tree_entry *, struct got_repository *);
+
 /* Get the index of a tree entry. */
 int got_tree_entry_get_index(struct got_tree_entry *);
 
blob - 60884259fffeeadac52f16b3ef8d210656e4c539
blob + 9a3beff5204483df08335c4ff3dfe9f49015cf50
--- lib/object.c
+++ lib/object.c
@@ -860,6 +860,42 @@ struct got_object_id *
 got_tree_entry_get_id(struct got_tree_entry *te)
 {
 	return &te->id;
+}
+
+const struct got_error *
+got_tree_entry_get_symlink_target(char **link_target, struct got_tree_entry *te,
+    struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+	struct got_blob_object *blob = NULL;
+	size_t len;
+
+	*link_target = NULL;
+
+	/* S_IFDIR check avoids confusing symlinks with submodules. */
+	if ((te->mode & (S_IFDIR | S_IFLNK)) != S_IFLNK)
+		return got_error(GOT_ERR_TREE_ENTRY_TYPE);
+
+	err = got_object_open_as_blob(&blob, repo,
+	    got_tree_entry_get_id(te), PATH_MAX);
+	if (err)
+		return err;
+	
+	err = got_object_blob_read_block(&len, blob);
+	if (err)
+		goto done;
+
+	*link_target = malloc(len + 1);
+	if (*link_target == NULL) {
+		err = got_error_from_errno("malloc");
+		goto done;
+	}
+	memcpy(*link_target, got_object_blob_get_read_buf(blob), len);
+	(*link_target)[len] = '\0';
+done:
+	if (blob)
+		got_object_blob_close(blob);
+	return err;
 }
 
 int
blob - 1b3bc07688dabd1f64ab5796add19f17d307dfc1
blob + 93119866bfc2b30573ae6680ae8c5a0ea8bd5e5b
--- tog/tog.1
+++ tog/tog.1
@@ -303,6 +303,8 @@ Displayed tree entries may carry one of the following 
 .It * Ta entry is an executable file
 .It $ Ta entry is a Git submodule
 .El
+.Pp
+Symbolic link entries are also annotated with the target path of the link.
 .Pp
 The key bindings for
 .Cm tog tree
blob - de21c04b04cf92a585fa3fd019dce29f5c235ff7
blob + 37110e98ad018c151bf660c62fd637570da2df3a
--- tog/tog.c
+++ tog/tog.c
@@ -18,6 +18,7 @@
 #include <sys/stat.h>
 #include <sys/ioctl.h>
 
+#include <ctype.h>
 #include <errno.h>
 #define _XOPEN_SOURCE_EXTENDED
 #include <curses.h>
@@ -4622,7 +4623,7 @@ draw_tree_entries(struct tog_view *view,
     struct got_tree_entry **selected_entry, int *ndisplayed,
     const char *label, int show_ids, const char *parent_path,
     struct got_tree_object *tree, int selected, int limit,
-    int isroot, struct tog_colors *colors)
+    int isroot, struct tog_colors *colors, struct got_repository *repo)
 {
 	const struct got_error *err = NULL;
 	struct got_tree_entry *te;
@@ -4693,7 +4694,7 @@ draw_tree_entries(struct tog_view *view,
 
 	nentries = got_object_tree_get_nentries(tree);
 	for (i = got_tree_entry_get_index(te); i < nentries; i++) {
-		char *line = NULL, *id_str = NULL;
+		char *line = NULL, *id_str = NULL, *link_target = NULL;
 		const char *modestr = "";
 		mode_t mode;
 
@@ -4709,18 +4710,35 @@ draw_tree_entries(struct tog_view *view,
 		}
 		if (got_object_tree_entry_is_submodule(te))
 			modestr = "$";
-		else if (S_ISLNK(mode))
+		else if (S_ISLNK(mode)) {
+			int i;
+
+			err = got_tree_entry_get_symlink_target(&link_target,
+			    te, repo);
+			if (err) {
+				free(id_str);
+				return err;
+			}
+			for (i = 0; i < strlen(link_target); i++) {
+				if (!isprint((unsigned char)link_target[i]))
+					link_target[i] = '?';
+			}
 			modestr = "@";
+		}
 		else if (S_ISDIR(mode))
 			modestr = "/";
 		else if (mode & S_IXUSR)
 			modestr = "*";
-		if (asprintf(&line, "%s  %s%s", id_str ? id_str : "",
-		    got_tree_entry_get_name(te), modestr) == -1) {
+		if (asprintf(&line, "%s  %s%s%s%s", id_str ? id_str : "",
+		    got_tree_entry_get_name(te), modestr,
+		    link_target ? " -> ": "",
+		    link_target ? link_target : "") == -1) {
 			free(id_str);
+			free(link_target);
 			return got_error_from_errno("asprintf");
 		}
 		free(id_str);
+		free(link_target);
 		err = format_line(&wline, &width, line, view->ncols, 0);
 		if (err) {
 			free(line);
@@ -5121,7 +5139,7 @@ show_tree_view(struct tog_view *view)
 	    &s->last_displayed_entry, &s->selected_entry,
 	    &s->ndisplayed, s->tree_label, s->show_ids, parent_path,
 	    s->tree, s->selected, view->nlines, s->tree == s->root,
-	    &s->colors);
+	    &s->colors, s->repo);
 	free(parent_path);
 
 	view_vborder(view);