Commit Diff


commit - 14e5d4dcac6e70e8ce1dc7434f4e4f5aa6dbf963
commit + b72f483a5d3d0699d6ff221cd44f8853eded0a32
blob - f755ddbe785f844e0f4a12a7a9a92106f64fdd5f
blob + 6c2d08f4242ad98d6f9d4ce5c88fb3a806671493
--- got/got.1
+++ got/got.1
@@ -159,15 +159,13 @@ If this directory is a
 .Nm
 work tree, use the repository path associated with this work tree.
 .El
-.It Cm diff [ Fl C Ar number ] [ Ar repository-path ] Ar object1 Ar object2
-Display the differences between two objects in the repository.
-Each
+.It Cm diff [ Fl C Ar number ] [ Fl r Ar repository-path ] [ Ar object1 Ar object2 ]
+Display differences between blobs in the repository and files in the
+work tree, or between two specified objects in a repository.
+If specified, each
 .Ar object
 argument is a SHA1 hash which corresponds to the object.
 Both objects must be of the same type (blobs, trees, or commits).
-If the
-.Ar repository path
-is omitted, use the current working directory.
 .Pp
 The options for
 .Cm got diff
@@ -176,6 +174,13 @@ are as follows:
 .It Fl C Ar number
 Set the number of context lines shown in the diff.
 By default, 3 lines of context are shown.
+.It Fl r Ar repository-path
+Use the repository at the specified path.
+If not specified, assume the repository is located at or above the current
+working directory.
+If this directory is a
+.Nm
+work tree, use the repository path associated with this work tree.
 .El
 .It Cm blame [ Fl c Ar commit ] [ Fl r Ar repository-path ] Ar path
 Display line-by-line history of a file at the specified path.
blob - d7e55d3b7bbc5195d9c895b68af79a5dd25b7e1e
blob + 096c8f7fac1d8b3fd9d1ea2bc25096b07b9ee4ae
--- got/got.c
+++ got/got.c
@@ -939,17 +939,69 @@ done:
 __dead static void
 usage_diff(void)
 {
-	fprintf(stderr, "usage: %s diff [-C number] [repository-path] "
-	    "object1 object2\n", getprogname());
+	fprintf(stderr, "usage: %s diff [-C number] [-r repository-path] "
+	    "[object1 object2]\n", getprogname());
 	exit(1);
 }
 
+struct print_diff_arg {
+	struct got_repository *repo;
+	struct got_worktree *worktree;
+	int diff_context;
+};
+
 static const struct got_error *
+print_diff(void *arg, unsigned char status, const char *path,
+    struct got_object_id *id)
+{
+	struct print_diff_arg *a = arg;
+	const struct got_error *err = NULL;
+	struct got_blob_object *blob1 = NULL;
+	FILE *f2 = NULL;
+	char *abspath = NULL;
+	struct stat sb;
+
+	if (status != GOT_STATUS_MODIFIY)
+		return NULL;
+
+	err = got_object_open_as_blob(&blob1, a->repo, id, 8192);
+	if (err)
+		goto done;
+
+	if (asprintf(&abspath, "%s/%s",
+	    got_worktree_get_root_path(a->worktree), path) == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	f2 = fopen(abspath, "r");
+	if (f2 == NULL) {
+		err = got_error_from_errno();
+		goto done;
+	}
+	if (lstat(abspath, &sb) == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	err = got_diff_blob_file(blob1, f2, sb.st_size, path, a->diff_context,
+	    stdout);
+done:
+	if (blob1)
+		got_object_blob_close(blob1);
+	if (f2)
+		fclose(f2);
+	free(abspath);
+	return err;
+}
+
+static const struct got_error *
 cmd_diff(int argc, char *argv[])
 {
 	const struct got_error *error;
 	struct got_repository *repo = NULL;
-	char *repo_path = NULL;
+	struct got_worktree *worktree = NULL;
+	char *cwd = NULL, *repo_path = NULL;
 	struct got_object_id *id1 = NULL, *id2 = NULL;
 	char *id_str1 = NULL, *id_str2 = NULL;
 	int type1, type2;
@@ -962,13 +1014,18 @@ cmd_diff(int argc, char *argv[])
 		err(1, "pledge");
 #endif
 
-	while ((ch = getopt(argc, argv, "C:")) != -1) {
+	while ((ch = getopt(argc, argv, "C:r:")) != -1) {
 		switch (ch) {
 		case 'C':
 			diff_context = strtonum(optarg, 1, INT_MAX, &errstr);
 			if (errstr != NULL)
 				err(1, "-C option %s", errstr);
 			break;
+		case 'r':
+			repo_path = realpath(optarg, NULL);
+			if (repo_path == NULL)
+				err(1, "-r option");
+			break;
 		default:
 			usage();
 			/* NOTREACHED */
@@ -978,24 +1035,35 @@ cmd_diff(int argc, char *argv[])
 	argc -= optind;
 	argv += optind;
 
+	cwd = getcwd(NULL, 0);
+	if (cwd == NULL) {
+		error = got_error_from_errno();
+		goto done;
+	}
 	if (argc == 0) {
-		usage_diff(); /* TODO show local worktree changes */
-	} else if (argc == 2) {
-		repo_path = getcwd(NULL, 0);
+		if (repo_path)
+			errx(1,
+			    "-r option can't be used when diffing a work tree");
+		error = got_worktree_open(&worktree, cwd);
+		if (error)
+			goto done;
+		repo_path = strdup(got_worktree_get_repo_path(worktree));
 		if (repo_path == NULL)
 			return got_error_from_errno();
+	} else if (argc == 2) {
 		id_str1 = argv[0];
 		id_str2 = argv[1];
-	} else if (argc == 3) {
-		repo_path = realpath(argv[0], NULL);
-		if (repo_path == NULL)
-			return got_error_from_errno();
-		id_str1 = argv[1];
-		id_str2 = argv[2];
 	} else
 		usage_diff();
 
-	error = apply_unveil(repo_path, NULL);
+	if (repo_path == NULL) {
+		repo_path = getcwd(NULL, 0);
+		if (repo_path == NULL)
+			return got_error_from_errno();
+	}
+
+	error = apply_unveil(repo_path,
+	    worktree ? got_worktree_get_root_path(worktree) : NULL);
 	if (error)
 		goto done;
 
@@ -1004,6 +1072,25 @@ cmd_diff(int argc, char *argv[])
 	if (error != NULL)
 		goto done;
 
+	if (worktree) {
+		struct print_diff_arg arg;
+		char *id_str;
+		error = got_object_id_str(&id_str,
+		    got_worktree_get_base_commit_id(worktree));
+		if (error)
+			goto done;
+		arg.repo = repo;
+		arg.worktree = worktree;
+		arg.diff_context = diff_context;
+
+		printf("diff %s %s\n", id_str,
+		    got_worktree_get_root_path(worktree));
+		error = got_worktree_status(worktree, repo, print_diff,
+		    &arg, check_cancelled, NULL);
+		free(id_str);
+		goto done;
+	}
+
 	error = got_object_resolve_id_str(&id1, repo, id_str1);
 	if (error)
 		goto done;
@@ -1047,6 +1134,8 @@ cmd_diff(int argc, char *argv[])
 done:
 	free(id1);
 	free(id2);
+	if (worktree)
+		got_worktree_close(worktree);
 	if (repo) {
 		const struct got_error *repo_error;
 		repo_error = got_repo_close(repo);
@@ -1432,10 +1521,12 @@ usage_status(void)
 	exit(1);
 }
 
-static void
-print_status(void *arg, unsigned char status, const char *path)
+static const struct got_error *
+print_status(void *arg, unsigned char status, const char *path,
+    struct got_object_id *id)
 {
 	printf("%c  %s\n", status, path);
+	return NULL;
 }
 
 static const struct got_error *
blob - 01e0ac643704c52d2defe4e6e2bffdc0b8b5f8e4
blob + f80b6b9455be8bea47ca7815749d16720a267a51
--- include/got_diff.h
+++ include/got_diff.h
@@ -25,6 +25,16 @@ const struct got_error *got_diff_blob(struct got_blob_
     struct got_blob_object *, const char *, const char *, int, FILE *);
 
 /*
+ * Compute the differences between a blob and a file and write unified diff
+ * text to the provided output file. The file's size must be provided, as
+ * well as a const char * diff header label which identifies the file.
+ * The number of context lines to show in the diff must be specified as well.
+ */
+const struct got_error *
+got_diff_blob_file(struct got_blob_object *, FILE *, size_t, const char *, int,
+    FILE *);
+
+/*
  * Compute the differences between two trees and write unified diff text
  * to the provided output FILE. Two const char * diff header labels may
  * be provided which will be used to identify each blob in the diff output.
blob - 768ca6a234213e1f696abed31c0a4266f2e2e612
blob + 7f9026a9b8d5c96a24479753a750d7e3d7ab7eec
--- include/got_worktree.h
+++ include/got_worktree.h
@@ -83,7 +83,7 @@ struct got_reference *got_worktree_get_head_ref(struct
 /*
  * Get the current base commit ID of a worktree.
  */
-const struct got_object_id *got_worktree_get_base_commit_id(struct got_worktree *);
+struct got_object_id *got_worktree_get_base_commit_id(struct got_worktree *);
 
 /*
  * Set the base commit Id of a worktree.
@@ -111,7 +111,8 @@ const struct got_error *got_worktree_checkout_files(st
     got_worktree_cancel_cb, void *);
 
 /* A callback function which is invoked to report a path's status. */
-typedef void (*got_worktree_status_cb)(void *, unsigned char, const char *);
+typedef const struct got_error *(*got_worktree_status_cb)(void *,
+    unsigned char, const char *, struct got_object_id *);
 
 /*
  * Report the status of paths in the work tree.
blob - c89351f48f0b6b7aad4de60bfc866f18b5f6fd63
blob + a3da580ba00997c724ca5eb3cd2126aab7442ebd
--- lib/diff.c
+++ lib/diff.c
@@ -126,6 +126,63 @@ got_diff_blob(struct got_blob_object *blob1, struct go
 }
 
 const struct got_error *
+got_diff_blob_file(struct got_blob_object *blob1, FILE *f2, size_t size2,
+    const char *label2, int diff_context, FILE *outfile)
+{
+	struct got_diff_state ds;
+	struct got_diff_args args;
+	const struct got_error *err = NULL;
+	FILE *f1 = NULL;
+	char hex1[SHA1_DIGEST_STRING_LENGTH];
+	char *idstr1 = NULL;
+	size_t size1;
+	int res, flags = 0;
+
+	size1 = 0;
+	if (blob1) {
+		f1 = got_opentemp();
+		if (f1 == NULL)
+			return got_error_from_errno();
+		idstr1 = got_object_blob_id_str(blob1, hex1, sizeof(hex1));
+		err = got_object_blob_dump_to_file(&size1, NULL, f1, blob1);
+		if (err)
+			goto done;
+	} else {
+		flags |= D_EMPTY1;
+		idstr1 = "/dev/null";
+	}
+
+	if (f2 == NULL)
+		flags |= D_EMPTY2;
+
+	memset(&ds, 0, sizeof(ds));
+	/* XXX should stat buffers be passed in args instead of ds? */
+	ds.stb1.st_mode = S_IFREG;
+	if (blob1)
+		ds.stb1.st_size = size1;
+	ds.stb1.st_mtime = 0; /* XXX */
+
+	ds.stb2.st_mode = S_IFREG;
+	ds.stb2.st_size = size2;
+	ds.stb2.st_mtime = 0; /* XXX */
+
+	memset(&args, 0, sizeof(args));
+	args.diff_format = D_UNIFIED;
+	args.label[0] = label2;
+	args.label[1] = label2;
+	args.diff_context = diff_context;
+	flags |= D_PROTOTYPE;
+
+	fprintf(outfile, "blob - %s\n", idstr1);
+	fprintf(outfile, "file + %s\n", label2);
+	err = got_diffreg(&res, f1, f2, flags, &args, &ds, outfile, NULL);
+done:
+	if (f1)
+		fclose(f1);
+	return err;
+}
+
+const struct got_error *
 got_diff_blob_lines_changed(struct got_diff_changes **changes,
     struct got_blob_object *blob1, struct got_blob_object *blob2)
 {
blob - 708cf625a91485cd69e6b44a4ed2699c28850ec7
blob + c0ccc0d1604c7cfb8c922ce2a76a686a2443fc8d
--- lib/worktree.c
+++ lib/worktree.c
@@ -470,7 +470,7 @@ got_worktree_get_head_ref(struct got_worktree *worktre
 	return got_ref_dup(worktree->head_ref);
 }
 
-const struct got_object_id *
+struct got_object_id *
 got_worktree_get_base_commit_id(struct got_worktree *worktree)
 {
 	return worktree->base_commit_id;
@@ -1039,8 +1039,11 @@ status_old_new(void *arg, struct got_fileindex_entry *
 	}
 
 	err = get_file_status(&status, ie, abspath, a->repo);
-	if (err == NULL && status != GOT_STATUS_NO_CHANGE)
-		(*a->status_cb)(a->status_arg, status, ie->path);
+	if (err == NULL && status != GOT_STATUS_NO_CHANGE) {
+		struct got_object_id id;
+		memcpy(id.sha1, ie->blob_sha1, SHA1_DIGEST_LENGTH);
+		err = (*a->status_cb)(a->status_arg, status, ie->path, &id);
+	}
 	free(abspath);
 	return err;
 }
@@ -1049,13 +1052,16 @@ static const struct got_error *
 status_old(void *arg, struct got_fileindex_entry *ie, const char *parent_path)
 {
 	struct diff_dir_cb_arg *a = arg;
-	(*a->status_cb)(a->status_arg, GOT_STATUS_MISSING, ie->path);
-	return NULL;
+	struct got_object_id id;
+	memcpy(id.sha1, ie->blob_sha1, SHA1_DIGEST_LENGTH);
+	return (*a->status_cb)(a->status_arg, GOT_STATUS_MISSING, ie->path,
+	    &id);
 }
 
 static const struct got_error *
 status_new(void *arg, struct dirent *de, const char *parent_path)
 {
+	const struct got_error *err = NULL;
 	struct diff_dir_cb_arg *a = arg;
 	char *path = NULL;
 
@@ -1069,10 +1075,11 @@ status_new(void *arg, struct dirent *de, const char *p
 		path = de->d_name;
 	}
 
-	(*a->status_cb)(a->status_arg, GOT_STATUS_UNVERSIONED, path);
+	err = (*a->status_cb)(a->status_arg, GOT_STATUS_UNVERSIONED, path,
+	    NULL);
 	if (parent_path[0])
 		free(path);
-	return NULL;
+	return err;
 }
 
 const struct got_error *