Commit b00d56cde51fc646b2b3055ff80e6f5b2b8cccb1

Stefan Sperling 2018-04-01T19:30:42

add a rudimentary diff command

diff --git a/got/got.c b/got/got.c
index a55e7d9..41ca7c9 100644
--- a/got/got.c
+++ b/got/got.c
@@ -47,9 +47,11 @@ struct cmd {
 __dead void	usage(void);
 __dead void	usage_checkout(void);
 __dead void	usage_log(void);
+__dead void	usage_diff(void);
 
 const struct got_error*		cmd_checkout(int, char *[]);
 const struct got_error*		cmd_log(int, char *[]);
+const struct got_error*		cmd_diff(int, char *[]);
 const struct got_error*		cmd_status(int, char *[]);
 
 struct cmd got_commands[] = {
@@ -57,6 +59,8 @@ struct cmd got_commands[] = {
 	    "check out a new work tree from a repository" },
 	{ "log",	cmd_log,	usage_log,
 	    "show repository history" },
+	{ "diff",	cmd_diff,	usage_diff,
+	    "compare files and directories" },
 #ifdef notyet
 	{ "status",	cmd_status,	usage_status,
 	    "show modification status of files" },
@@ -482,6 +486,187 @@ cmd_log(int argc, char *argv[])
 	return error;
 }
 
+__dead void
+usage_diff(void)
+{
+	fprintf(stderr, "usage: %s diff repository-path object1 object2\n",
+	    getprogname());
+	exit(1);
+}
+
+static const struct got_error *
+diff_blobs(struct got_object *obj1, struct got_object *obj2,
+    struct got_repository *repo)
+{
+	const struct got_error *err;
+	struct got_blob_object *blob1 = NULL, *blob2 = NULL;
+
+	err = got_object_blob_open(&blob1, repo, obj1, 8192);
+	if (err)
+		goto done;
+	err = got_object_blob_open(&blob2, repo, obj2, 81992);
+	if (err)
+		goto done;
+
+	err = got_diff_blob(blob1, blob2, NULL, NULL, stdout);
+done:
+	if (blob1)
+		got_object_blob_close(blob1);
+	if (blob2)
+		got_object_blob_close(blob2);
+	return err;
+}
+
+static const struct got_error *
+diff_trees(struct got_object *obj1, struct got_object *obj2,
+    struct got_repository *repo)
+{
+	const struct got_error *err;
+	struct got_tree_object *tree1 = NULL, *tree2 = NULL;
+
+	err = got_object_tree_open(&tree1, repo, obj1);
+	if (err)
+		goto done;
+	err = got_object_tree_open(&tree2, repo, obj2);
+	if (err)
+		goto done;
+
+	err = got_diff_tree(tree1, tree2, repo, stdout);
+done:
+	if (tree1)
+		got_object_tree_close(tree1);
+	if (tree2)
+		got_object_tree_close(tree2);
+	return err;
+}
+
+static const struct got_error *
+diff_commits(struct got_object *obj1, struct got_object *obj2,
+    struct got_repository *repo)
+{
+	const struct got_error *err;
+	struct got_commit_object *commit1 = NULL, *commit2 = NULL;
+	struct got_object *tree_obj1  = NULL, *tree_obj2 = NULL;
+
+	err = got_object_commit_open(&commit1, repo, obj1);
+	if (err)
+		goto done;
+	err = got_object_commit_open(&commit2, repo, obj2);
+	if (err)
+		goto done;
+
+	err = got_object_open(&tree_obj1, repo, commit1->tree_id);
+	if (err)
+		goto done;
+	err = got_object_open(&tree_obj2, repo, commit2->tree_id);
+	if (err)
+		goto done;
+
+	err = diff_trees(tree_obj1, tree_obj2, repo);
+done:
+	if (tree_obj1)
+		got_object_close(tree_obj1);
+	if (tree_obj2)
+		got_object_close(tree_obj2);
+	if (commit1)
+		got_object_commit_close(commit1);
+	if (commit2)
+		got_object_commit_close(commit2);
+	return err;
+
+}
+
+const struct got_error *
+cmd_diff(int argc, char *argv[])
+{
+	const struct got_error *error;
+	struct got_repository *repo = NULL;
+	struct got_object_id *id1 = NULL, *id2 = NULL;
+	struct got_object *obj1 = NULL, *obj2 = NULL;
+	char *repo_path = NULL;
+	char *obj_id_str1 = NULL, *obj_id_str2 = NULL;
+	int ch;
+
+#ifndef PROFILE
+	if (pledge("stdio rpath wpath cpath", NULL) == -1)
+		err(1, "pledge");
+#endif
+
+	while ((ch = getopt(argc, argv, "")) != -1) {
+		switch (ch) {
+		default:
+			usage();
+			/* NOTREACHED */
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+
+	if (argc == 0) {
+		usage_diff(); /* TODO show local worktree changes */
+	} else if (argc == 3) {
+		repo_path = argv[0];
+		obj_id_str1 = argv[1];
+		obj_id_str2 = argv[2];
+	} else
+		usage_diff();
+
+	error = got_repo_open(&repo, repo_path);
+	if (error != NULL)
+		goto done;
+
+	error = got_object_open_by_id_str(&obj1, repo, obj_id_str1);
+	if (error == NULL) {
+		id1 = got_object_get_id(obj1);
+		if (id1 == NULL)
+			error = got_error_from_errno();
+	}
+	if (error != NULL)
+		goto done;
+
+	error = got_object_open_by_id_str(&obj2, repo, obj_id_str2);
+	if (error == NULL) {
+		id2 = got_object_get_id(obj2);
+		if (id2 == NULL)
+			error = got_error_from_errno();
+	}
+	if (error != NULL)
+		goto done;
+
+	if (got_object_get_type(obj1) != got_object_get_type(obj2)) {
+		error = got_error(GOT_ERR_OBJ_TYPE);
+		goto done;
+	}
+
+	switch (got_object_get_type(obj1)) {
+	case GOT_OBJ_TYPE_BLOB:
+		error = diff_blobs(obj1, obj2, repo);
+		break;
+	case GOT_OBJ_TYPE_TREE:
+		error = diff_trees(obj1, obj2, repo);
+		break;
+	case GOT_OBJ_TYPE_COMMIT:
+		error = diff_commits(obj1, obj2, repo);
+		break;
+	default:
+		error = got_error(GOT_ERR_OBJ_TYPE);
+	}
+
+done:
+	if (obj1)
+		got_object_close(obj1);
+	if (obj2)
+		got_object_close(obj2);
+	if (id1)
+		free(id1);
+	if (id2)
+		free(id2);
+	if (repo)
+		got_repo_close(repo);
+	return error;
+}
+
 #ifdef notyet
 const struct got_error *
 cmd_status(int argc __unused, char *argv[] __unused)