Commit 07862c206e443b4b70016804c7280da2e98be6b7

Stefan Sperling 2018-09-15T15:32:22

introduce got_object_tree_path_changed() and use it in 'got log'

diff --git a/got/got.c b/got/got.c
index 9a28bc6..f5d0166 100644
--- a/got/got.c
+++ b/got/got.c
@@ -370,23 +370,43 @@ print_commit(struct got_commit_object *commit, struct got_object_id *id,
 }
 
 static const struct got_error *
-detect_change(int *changed, struct got_object_id *commit_id,
-    struct got_object_id *obj_id, const char *path, struct got_repository *repo)
+detect_change(int *changed, struct got_commit_object *commit,
+    const char *path, struct got_repository *repo)
 {
 	const struct got_error *err = NULL;
-	struct got_object_id *obj_id2;
+	struct got_commit_object *pcommit = NULL;
+	struct got_tree_object *tree = NULL, *ptree = NULL;
+	struct got_object_qid *pid;
 
-	err = got_object_id_by_path(&obj_id2, repo, commit_id, path);
-	if (err) {
-		if (err->code != GOT_ERR_NO_OBJ)
-			return err;
+	*changed = 0;
+
+	err = got_object_open_as_tree(&tree, repo, commit->tree_id);
+	if (err)
+		return err;
+
+	pid = SIMPLEQ_FIRST(&commit->parent_ids);
+	if (pid == NULL) {
 		*changed = 1;
-		return NULL;
+		goto done;
 	}
 
-	*changed = (got_object_id_cmp(obj_id, obj_id2) != 0);
-	free(obj_id2);
-	return NULL;
+	err = got_object_open_as_commit(&pcommit, repo, pid->id);
+	if (err)
+		goto done;
+
+	err = got_object_open_as_tree(&ptree, repo, pcommit->tree_id);
+	if (err)
+		goto done;
+
+	err = got_object_tree_path_changed(changed, tree, ptree, path, repo);
+done:
+	if (tree)
+		got_object_tree_close(tree);
+	if (ptree)
+		got_object_tree_close(ptree);
+	if (pcommit)
+		got_object_commit_close(pcommit);
+	return err;
 }
 
 static const struct got_error *
@@ -396,7 +416,7 @@ print_commits(struct got_object *root_obj, struct got_object_id *root_id,
 {
 	const struct got_error *err;
 	struct got_commit_graph *graph;
-	int ncommits, found_obj = 0;
+	int ncommits, found_path = 0;
 	int is_root_path = (strcmp(path, "/") == 0);
 
 	err = got_commit_graph_open(&graph, root_id, first_parent_traversal,
@@ -433,40 +453,44 @@ print_commits(struct got_object *root_obj, struct got_object_id *root_id,
 			break;
 		if (!is_root_path) {
 			struct got_object_id *obj_id = NULL;
-			struct got_object_qid *pid;
-			int changed = 0;
+			int changed;
 
-			err = got_object_id_by_path(&obj_id, repo, id, path);
+			err = detect_change(&changed, commit, path, repo);
 			if (err) {
-				got_object_commit_close(commit);
-				if (err->code == GOT_ERR_NO_OBJ && found_obj) {
+				if (err->code == GOT_ERR_NO_OBJ) {
 					/*
 					 * History of the path stops here
 					 * on the current commit's branch.
 					 * Keep logging on other branches.
 					 */
 					err = NULL;
-					continue;
-				}
-				break;
-			}
-			found_obj = 1;
-
-			pid = SIMPLEQ_FIRST(&commit->parent_ids);
-			if (pid) {
-				err = detect_change(&changed, pid->id, obj_id,
-				    path, repo);
-				if (err) {
-					free(obj_id);
 					got_object_commit_close(commit);
-					break;
+					continue;
 				}
+				return err;
 			}
-			free(obj_id);
 			if (!changed) {
 				got_object_commit_close(commit);
 				continue;
 			}
+
+			err = got_object_id_by_path(&obj_id, repo, id, path);
+			if (err) {
+				if (err->code == GOT_ERR_NO_OBJ && found_path) {
+					/*
+					 * History of the path stops here
+					 * on the current commit's branch.
+					 * Keep logging on other branches.
+					 */
+					err = NULL;
+					got_object_commit_close(commit);
+					continue;
+				}
+				got_object_commit_close(commit);
+				return err;
+			}
+			found_path = 1;
+			free(obj_id);
 		}
 		err = print_commit(commit, id, repo, show_patch);
 		got_object_commit_close(commit);
diff --git a/include/got_object.h b/include/got_object.h
index 14bfd3e..352d0b0 100644
--- a/include/got_object.h
+++ b/include/got_object.h
@@ -161,6 +161,15 @@ const struct got_tree_entries *got_object_tree_get_entries(
     struct got_tree_object *);
 
 /*
+ * Compare two trees and indicate whether the entry at the specified path
+ * differs. The path must not be the root path "/"; got_object_id_dup() can
+ * be used to compare the tree roots instead.
+ */
+const struct got_error *got_object_tree_path_changed(int *,
+    struct got_tree_object *, struct got_tree_object *, const char *,
+    struct got_repository *);
+
+/*
  * Attempt to open a blob object in a repository.
  * The provided object must be of type GOT_OBJ_TYPE_BLOB.
  * The size_t argument specifies the block size of an associated read buffer.
diff --git a/lib/object.c b/lib/object.c
index 927693e..7ea8b37 100644
--- a/lib/object.c
+++ b/lib/object.c
@@ -786,3 +786,110 @@ done:
 		got_object_tree_close(tree);
 	return err;
 }
+
+const struct got_error *
+got_object_tree_path_changed(int *changed,
+    struct got_tree_object *tree01, struct got_tree_object *tree02,
+    const char *path, struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+	struct got_tree_object *tree1 = NULL, *tree2 = NULL;
+	struct got_tree_entry *te1 = NULL, *te2 = NULL;
+	char *seg, *s, *s0 = NULL;
+	size_t len = strlen(path);
+
+	*changed = 0;
+
+	/* We are expecting an absolute in-repository path. */
+	if (path[0] != '/')
+		return got_error(GOT_ERR_NOT_ABSPATH);
+
+	/* We not do support comparing the root path. */
+	if (path[1] == '\0')
+		return got_error(GOT_ERR_BAD_PATH);
+
+	s0 = strdup(path);
+	if (s0 == NULL) {
+		err = got_error_from_errno();
+		goto done;
+	}
+	err = got_canonpath(path, s0, len + 1);
+	if (err)
+		goto done;
+
+	tree1 = tree01;
+	tree2 = tree02;
+	s = s0;
+	s++; /* skip leading '/' */
+	len--;
+	seg = s;
+	while (len > 0) {
+		struct got_tree_object *next_tree1, *next_tree2;
+
+		if (*s != '/') {
+			s++;
+			len--;
+			if (*s)
+				continue;
+		}
+
+		/* end of path segment */
+		*s = '\0';
+
+		te1 = find_entry_by_name(tree1, seg);
+		if (te1 == NULL) {
+			err = got_error(GOT_ERR_NO_OBJ);
+			goto done;
+		}
+
+		te2 = find_entry_by_name(tree2, seg);
+		if (te2 == NULL) {
+			*changed = 1;
+			goto done;
+		}
+
+		if (te1->mode != te2->mode) {
+			*changed = 1;
+			goto done;
+		}
+
+		if (got_object_id_cmp(te1->id, te2->id) == 0) {
+			*changed = 0;
+			goto done;
+		}
+
+		if (S_ISREG(te1->mode)) { /* final path element */
+			*changed = 1;
+			goto done;
+		}
+
+		if (len == 0)
+			break;
+
+		seg = s + 1;
+		s++;
+		len--;
+		if (*s) {
+			err = got_object_open_as_tree(&next_tree1, repo,
+			    te1->id);
+			te1 = NULL;
+			if (err)
+				goto done;
+			tree1 = next_tree1;
+
+			err = got_object_open_as_tree(&next_tree2, repo,
+			    te2->id);
+			te2 = NULL;
+			if (err)
+				goto done;
+			tree2 = next_tree2;
+		}
+	}
+done:
+	free(s0);
+	if (tree1 != tree01)
+		got_object_tree_close(tree1);
+	if (tree2 != tree02)
+		got_object_tree_close(tree2);
+	return err;
+}