[PATCH] colored diff.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



With configuration option diff.usecolor, this colorizes the diff
output.

Signed-off-by: Junio C Hamano <junkio@xxxxxxx>
---
 * I am not happy with the initialization, especially the part
   that calls git_config(git_diff_config) from git.c, and I am
   not that interested in this myself, but some people seem to
   like colors, so this will be in "pu" for now.

 cache.h    |    3 +-
 diff-lib.c |  101 +++++++++++++++++++++++++++++++++++++++++++++++++++++-------
 diff.h     |    2 +
 git.c      |    3 +-
 pager.c    |   17 ++++++----
 5 files changed, 104 insertions(+), 22 deletions(-)

diff --git a/cache.h b/cache.h
index 69801b0..e6aa297 100644
--- a/cache.h
+++ b/cache.h
@@ -169,7 +169,6 @@ extern int trust_executable_bit;
 extern int assume_unchanged;
 extern int only_use_symrefs;
 extern int warn_ambiguous_refs;
-extern int diff_rename_limit_default;
 extern int shared_repository;
 extern const char *apply_default_whitespace;
 
@@ -359,6 +358,6 @@ extern int receive_unpack_pack(int fd[2]
 extern int receive_keep_pack(int fd[2], const char *me, int quiet);
 
 /* pager.c */
-extern void setup_pager(void);
+extern int setup_pager(void);
 
 #endif /* CACHE_H */
diff --git a/diff-lib.c b/diff-lib.c
index 0a832c3..86a2561 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -12,7 +12,8 @@ #include "xdiff-interface.h"
 
 static int use_size_cache;
 
-int diff_rename_limit_default = -1;
+static int diff_rename_limit_default = -1;
+static int diff_use_color = 0;
 
 int git_diff_config(const char *var, const char *value)
 {
@@ -20,10 +21,31 @@ int git_diff_config(const char *var, con
 		diff_rename_limit_default = git_config_int(var, value);
 		return 0;
 	}
-
+	if (!strcmp(var, "diff.usecolor")) {
+		diff_use_color = git_config_bool(var, value);
+		return 0;
+	}
 	return git_default_config(var, value);
 }
 
+static const char *diff_colors[] = {
+	"\033[0;0m",
+	"\033[1;35m",
+	"\033[1;31m",
+	"\033[1;34m",
+};
+
+void diff_setup_colors(void)
+{
+	static int inited;
+	if (!inited) {
+		if (diff_use_color && !isatty(1))
+			diff_use_color = 0;
+		inited++;
+		git_config(git_diff_config);
+	}
+}
+
 static char *quote_one(const char *str)
 {
 	int needlen;
@@ -176,23 +198,61 @@ static int fill_mmfile(mmfile_t *mf, str
 }
 
 struct emit_callback {
+	struct xdiff_emit_state xm;
+	int nparents;
 	const char **label_path;
 };
 
-static int fn_out(void *priv, mmbuffer_t *mb, int nbuf)
+enum color_diff {
+	DIFF_PLAIN = 0,
+	DIFF_METAINFO = 1,
+	DIFF_FILE_OLD = 2,
+	DIFF_FILE_NEW = 3,
+};
+
+static void color_diff(enum color_diff ix)
+{
+	diff_setup_colors();
+	if (diff_use_color)
+		fputs(diff_colors[ix], stdout);
+}
+
+static void fn_out_consume(void *priv, char *line, unsigned long len)
 {
 	int i;
 	struct emit_callback *ecbdata = priv;
-
 	if (ecbdata->label_path[0]) {
+		color_diff(DIFF_METAINFO);
 		printf("--- %s\n", ecbdata->label_path[0]);
+		color_diff(DIFF_METAINFO);
 		printf("+++ %s\n", ecbdata->label_path[1]);
 		ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
 	}
-	for (i = 0; i < nbuf; i++)
-		if (!fwrite(mb[i].ptr, mb[i].size, 1, stdout))
-			return -1;
-	return 0;
+
+	/* This is not really necessary for now because
+	 * this codepath only deals with two-way diffs.
+	 */
+	for (i = 0; i < len && line[i] == '@'; i++)
+		;
+	if (2 <= i && i < len && line[i] == ' ') {
+		ecbdata->nparents = i - 1;
+		color_diff(DIFF_METAINFO);
+	}
+	else if (len < ecbdata->nparents)
+		color_diff(DIFF_PLAIN);
+	else {
+		int nparents = ecbdata->nparents;
+		int color = DIFF_PLAIN;
+		for (i = 0; i < nparents && len; i++) {
+			if (line[i] == '-')
+				color = DIFF_FILE_OLD;
+			else if (line[i] == '+')
+				color = DIFF_FILE_NEW;
+		}
+		color_diff(color);
+	}
+	fwrite(line, len, 1, stdout);
+	color_diff(DIFF_PLAIN);
 }
 
 struct diffstat_t {
@@ -359,25 +419,37 @@ static void builtin_diff(const char *nam
 	b_two = quote_two("b/", name_b);
 	lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
 	lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
+
+	color_diff(DIFF_METAINFO);
 	printf("diff --git %s %s\n", a_one, b_two);
 	if (lbl[0][0] == '/') {
 		/* /dev/null */
+		color_diff(DIFF_METAINFO);
 		printf("new file mode %06o\n", two->mode);
-		if (xfrm_msg && xfrm_msg[0])
+		if (xfrm_msg && xfrm_msg[0]) {
+			color_diff(DIFF_METAINFO);
 			puts(xfrm_msg);
+		}
 	}
 	else if (lbl[1][0] == '/') {
+		color_diff(DIFF_METAINFO);
 		printf("deleted file mode %06o\n", one->mode);
-		if (xfrm_msg && xfrm_msg[0])
+		if (xfrm_msg && xfrm_msg[0]) {
+			color_diff(DIFF_METAINFO);
 			puts(xfrm_msg);
+		}
 	}
 	else {
 		if (one->mode != two->mode) {
+			color_diff(DIFF_METAINFO);
 			printf("old mode %06o\n", one->mode);
+			color_diff(DIFF_METAINFO);
 			printf("new mode %06o\n", two->mode);
 		}
-		if (xfrm_msg && xfrm_msg[0])
+		if (xfrm_msg && xfrm_msg[0]) {
+			color_diff(DIFF_METAINFO);
 			puts(xfrm_msg);
+		}
 		/*
 		 * we do not run diff between different kind
 		 * of objects.
@@ -385,6 +457,7 @@ static void builtin_diff(const char *nam
 		if ((one->mode ^ two->mode) & S_IFMT)
 			goto free_ab_and_return;
 		if (complete_rewrite) {
+			color_diff(DIFF_PLAIN);
 			emit_rewrite_diff(name_a, name_b, one, two);
 			goto free_ab_and_return;
 		}
@@ -403,7 +476,9 @@ static void builtin_diff(const char *nam
 		xdemitcb_t ecb;
 		struct emit_callback ecbdata;
 
+		memset(&ecbdata, 0, sizeof(ecbdata));
 		ecbdata.label_path = lbl;
+		ecbdata.nparents = 0;
 		xpp.flags = XDF_NEED_MINIMAL;
 		xecfg.ctxlen = 3;
 		xecfg.flags = XDL_EMIT_FUNCNAMES;
@@ -413,12 +488,14 @@ static void builtin_diff(const char *nam
 			xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
 		else if (!strncmp(diffopts, "-u", 2))
 			xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
-		ecb.outf = fn_out;
+		ecb.outf = xdiff_outf;
 		ecb.priv = &ecbdata;
+		ecbdata.xm.consume = fn_out_consume;
 		xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
 	}
 
  free_ab_and_return:
+	color_diff(DIFF_PLAIN);
 	free(a_one);
 	free(b_two);
 	return;
diff --git a/diff.h b/diff.h
index 52fff66..7cb5708 100644
--- a/diff.h
+++ b/diff.h
@@ -168,4 +168,6 @@ #define DIFF_STATUS_FILTER_BROKEN	'B'
 
 extern const char *diff_unique_abbrev(const unsigned char *, int);
 
+extern void diff_setup_colors(void);
+
 #endif /* DIFF_H */
diff --git a/git.c b/git.c
index 40b7e42..6742de7 100644
--- a/git.c
+++ b/git.c
@@ -290,7 +290,8 @@ static int cmd_log_wc(int argc, const ch
 		die("unrecognized argument: %s", argv[1]);
 
 	prepare_revision_walk(rev);
-	setup_pager();
+	if (!setup_pager())
+		diff_setup_colors();
 	while ((commit = get_revision(rev)) != NULL) {
 		log_tree_commit(rev, commit);
 		free(commit->buffer);
diff --git a/pager.c b/pager.c
index b063353..1fb7103 100644
--- a/pager.c
+++ b/pager.c
@@ -10,26 +10,29 @@ static void run_pager(const char *pager)
 	execlp(pager, pager, NULL);
 }
 
-void setup_pager(void)
+/* Returns 0 if the caller can assume the output stream
+ * is like a terminal (either bare terminal or a pager.
+ */
+int setup_pager(void)
 {
 	pid_t pid;
 	int fd[2];
 	const char *pager = getenv("PAGER");
 
 	if (!isatty(1))
-		return;
+		return 1;
 	if (!pager)
 		pager = "less";
 	else if (!*pager || !strcmp(pager, "cat"))
-		return;
+		return 0;
 
 	if (pipe(fd) < 0)
-		return;
+		return -1;
 	pid = fork();
 	if (pid < 0) {
 		close(fd[0]);
 		close(fd[1]);
-		return;
+		return -1;
 	}
 
 	/* return in the child */
@@ -37,7 +40,7 @@ void setup_pager(void)
 		dup2(fd[1], 1);
 		close(fd[0]);
 		close(fd[1]);
-		return;
+		return 0;
 	}
 
 	/* The original process turns into the PAGER */
@@ -45,7 +48,7 @@ void setup_pager(void)
 	close(fd[0]);
 	close(fd[1]);
 
-	setenv("LESS", "-S", 0);
+	setenv("LESS", "-RS", 0);
 	run_pager(pager);
 	exit(255);
 }

-
: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]