git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v0 0/2] git-less: a specialized pager for git-log
@ 2012-03-22 18:42 Hitoshi Mitake
  2012-03-22 18:42 ` [PATCH v0 1/2] " Hitoshi Mitake
                   ` (3 more replies)
  0 siblings, 4 replies; 10+ messages in thread
From: Hitoshi Mitake @ 2012-03-22 18:42 UTC (permalink / raw)
  To: gitster; +Cc: git, Hitoshi Mitake

Hi Junio C Hamano and git people,

I'd like to post this patch series for adding a pager program for git-log,
named "git-less". It seems that less is today's the most popular pager for
git-log. But I don't think that less is confortable pager when its input is come
from git-log. Because less treats its input as a simple text. So less cannot
recognize the end of each commit.

If the pager can recognize the end of each commit, more confortable viewing of
git-log's output will be possible.

So I'd like to introduce git-less, and the modification of git itself to support
this pager.

The main difference between less and git-less is recognition of each commit.
With this feature, git-log specific functionalities can be implemented. e.g.
* move with commit unit. Under git-less, hitting 'h' key means move to previous
  (parent) commit, 'l' means move to next (child) commit.
* commit local regex search. regular expression search limiting its range within
  commit, not entire log. I assigned '\' for commit local forward regex search
  and '?' for commit local regex search.

And other features like yanking commit ID might be implemented simply (not
implemented yet).

For implementing this feature, I had to make modification of git-log
* Changed output for inserting ETX (0x03) between each commit.
* Added new configuration parameter for git-log only pager: "log.pager".
  git-less only focuses on git-log, so it is not suitable for git-grep, etc.

This is the reason why I'm posting this patch. git-less is not git-independent
program. The source code of git-less have to be integrated with the git tree.
I understand this is not so good manner. And the source code is still dirty and
may be buggy, but I believe that this new pager is useful. I'd like to hear your
feedbacks.

Thanks,

Hitoshi Mitake (2):
  git-less: a specialized pager for git-log
  git-less: git side support for git-less

 .gitignore                |    1 +
 Documentation/git-log.txt |    6 +-
 Makefile                  |    5 +
 builtin/log.c             |   32 ++
 cache.h                   |    1 +
 less.c                    |  899 +++++++++++++++++++++++++++++++++++++++++++++
 pager.c                   |    9 +-
 7 files changed, 949 insertions(+), 4 deletions(-)
 create mode 100644 less.c

-- 
1.7.10.rc1.33.g64ff3.dirty

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v0 1/2] git-less: a specialized pager for git-log
  2012-03-22 18:42 [PATCH v0 0/2] git-less: a specialized pager for git-log Hitoshi Mitake
@ 2012-03-22 18:42 ` Hitoshi Mitake
  2012-03-22 18:42 ` [PATCH v0 2/2] git-less: git side support for git-less Hitoshi Mitake
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 10+ messages in thread
From: Hitoshi Mitake @ 2012-03-22 18:42 UTC (permalink / raw)
  To: gitster; +Cc: git, Hitoshi Mitake

This patch adds a specialized pager for git-log: "git-less". Basically git-less
works like ordinal less, but it uses some new keys. The important feature of
this pager is git's commit oriented log viewing.

This is the key mapping of commit oriented operations:

h ... show previous commit
l ... show next commit
g ... goto top of viewing commit
G ... goto bottom of viewing commit
H ... show root of the commit tree
L ... show HEAD of the commit tree
\ ... regex forward search only in viewing commit
! ... regex backward search only in viewing commit

As this mapping shows, commit oriented move and search are implemented.
Especially I like commit local regex searches.

This patch adds the new source file less.c, the implementation of git-less. And
modifies .gitignore and Makefile for ignoring the binary and buliding.

Signed-off-by: Hitoshi Mitake <h.mitake@gmail.com>
---
 .gitignore |    1 +
 Makefile   |    5 +
 less.c     |  899 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 905 insertions(+)
 create mode 100644 less.c

diff --git a/.gitignore b/.gitignore
index 87fcc5f..81dba3a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -163,6 +163,7 @@
 /git-web--browse
 /git-whatchanged
 /git-write-tree
+/git-less
 /git-core-*/?*
 /gitk-git/gitk-wish
 /gitweb/GITWEB-BUILD-OPTIONS
diff --git a/Makefile b/Makefile
index be1957a..48600d5 100644
--- a/Makefile
+++ b/Makefile
@@ -463,6 +463,7 @@ PROGRAM_OBJS += upload-pack.o
 PROGRAM_OBJS += http-backend.o
 PROGRAM_OBJS += sh-i18n--envsubst.o
 PROGRAM_OBJS += credential-store.o
+PROGRAM_OBJS += less.o
 
 # Binary suffix, set to .exe for Windows builds
 X =
@@ -2239,6 +2240,10 @@ git-http-push$X: revision.o http.o http-push.o GIT-LDFLAGS $(GITLIBS)
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
 		$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
+gitless$X: gitless.o
+	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+		$(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) $(LIB_4_CRYPTO)
+
 $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)
 	$(QUIET_LNCP)$(RM) $@ && \
 	ln $< $@ 2>/dev/null || \
diff --git a/less.c b/less.c
new file mode 100644
index 0000000..9848eca
--- /dev/null
+++ b/less.c
@@ -0,0 +1,899 @@
+/*
+ * git-less - a specialized pager for git-log
+ *
+ * Copyright (C) 2012 Hitoshi Mitake <h.mitake@gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <signal.h>
+#include <errno.h>
+
+#include <assert.h>
+
+#include <regex.h>
+
+extern int errno;
+
+#define die(fmt, arg...)						\
+	do {                                                            \
+		fprintf(stderr, "line %d: fatal error, " fmt "\n",	\
+			__LINE__, ##arg);				\
+		fprintf(stderr, "errno: %s\n", strerror(errno));	\
+		exit(1);						\
+	} while (0)
+
+#define ETX 0x03
+
+static void *xalloc(size_t size)
+{
+	void *ret;
+
+	ret = calloc(sizeof(char), size);
+	if (!ret)
+		die("memory allocation failed");
+
+	return ret;
+}
+
+static void *xrealloc(void *ptr, size_t size)
+{
+	void *ret;
+
+	assert(size);
+	ret = realloc(ptr, size);
+	if (!ret)
+		die("memory allocation failed");
+
+	return ret;
+}
+
+static int ret_nl_index(char *s)
+{
+	int i;
+
+	for (i = 0; s[i] != '\n'; i++);
+	return i;
+}
+
+static int stdin_fd = 0, tty_fd, debug_fd;
+static unsigned int row, col;
+static int running = 1;
+static int searching;
+
+#define LINES_INIT_SIZE 128
+
+enum {
+	STATE_DEFAULT,
+	STATE_INPUT_SEARCH_QUERY,
+	STATE_SEARCHING_QUERY
+};
+static int state = STATE_DEFAULT;
+
+#define BOTTOM_MESSAGE_INIT_SIZE 32
+static char *bottom_message;
+static int bottom_message_size = BOTTOM_MESSAGE_INIT_SIZE;
+
+#define MATCH_ARRAY_INIT_SIZE 32
+static regmatch_t *match_array;
+static int match_array_size = MATCH_ARRAY_INIT_SIZE;
+
+#define bmprintf(fmt, arg...)						\
+	do {                                                            \
+		snprintf(bottom_message, bottom_message_size,		\
+			fmt, ##arg);					\
+	} while (0)
+
+#ifdef GIT_LESS_DEBUG
+
+#define debug_printf(fmt, arg...)					\
+	do {                                                            \
+		char dbg_msg[1024];					\
+		int w, wbyte, len;					\
+									\
+		bzero(dbg_msg, 1024);					\
+		len = snprintf(dbg_msg, 1024,				\
+			fmt, ##arg);					\
+									\
+		wbyte = 0;						\
+		while ((w = write(debug_fd, dbg_msg + wbyte,		\
+						len - wbyte))) {	\
+			if (wbyte < 0)					\
+				die("failed write() on debug_fd");	\
+									\
+			wbyte += w;					\
+			if (wbyte == len)				\
+				break;					\
+		}							\
+	} while (0)
+
+#else
+
+#define debug_printf(fmt, arg...) do { } while (0)
+
+#endif	/* GIT_LESS_DEBUG */
+
+static void update_row_col(void)
+{
+	struct winsize size;
+
+	bzero(&size, sizeof(struct winsize));
+	ioctl(tty_fd, TIOCGWINSZ, (void *)&size);
+
+	row = size.ws_row - 1;
+	col = size.ws_col;
+
+	if (bottom_message_size - 1 < col) {
+		bottom_message_size = col + 1;
+		bottom_message = xrealloc(bottom_message, bottom_message_size);
+	}
+
+	if (match_array_size < col) {
+		match_array_size = col;
+		match_array = xrealloc(match_array,
+				match_array_size * sizeof(regmatch_t));
+	}
+}
+
+static struct termios attr;
+
+static void init_tty(void)
+{
+	tty_fd = open("/dev/tty", O_RDONLY);
+	if (tty_fd < 0)
+		die("open()ing /dev/tty");
+
+	bzero(&attr, sizeof(struct termios));
+	tcgetattr(tty_fd, &attr);
+	attr.c_lflag &= ~ICANON;
+	attr.c_lflag &= ~ECHO;
+	tcsetattr(tty_fd, TCSANOW, &attr);
+
+	update_row_col();
+}
+
+static char *logbuf;
+static int logbuf_size, logbuf_used;
+#define LOGBUF_INIT_SIZE 1024
+
+static int last_etx;
+
+struct commit {
+	unsigned int *lines;	/* array of index of logbuf */
+	int lines_size;
+
+	int nr_lines, head_line;
+
+	/*
+	 * caution:
+	 * prev means previous commit of the commit object,
+	 * next means next commit of the commit object.
+	 */
+	struct commit *prev, *next;
+};
+
+/* head: HEAD, root: root of the commit tree */
+static struct commit *head, *root;
+/* current: current displaying commit, tail: tail of the read commits */
+static struct commit *current, *tail;
+
+static regex_t *re_compiled;
+
+static void update_terminal(void)
+{
+	int i, j, print;
+	char *line;
+
+	printf("\033[2J\033[0;0J");
+
+	/* FIXME: first new line should be eliminated in git-log */
+	if (current != head && !current->head_line)
+		current->head_line = 1;
+
+	for (i = current->head_line, print = 0;
+	     i < current->head_line + row
+		     && i < current->nr_lines; i++, print++) {
+		line = &logbuf[current->lines[i]];
+
+		if (state == STATE_SEARCHING_QUERY) {
+			int ret, mi, nli = ret_nl_index(line);
+			int rev = 0;
+
+			line[nli] = '\0';
+			ret = regexec(re_compiled, line,
+				match_array_size, match_array, 0);
+			line[nli] = '\n';
+
+			if (ret)
+				goto normal_print;
+
+			for (mi = j = 0; j < col && line[j] != '\n'; j++) {
+				if (j == match_array[mi].rm_so) {
+					printf("\033[7m");
+					rev = 1;
+				} else if (j == match_array[mi].rm_eo) {
+					printf("\033[0m");
+					rev = 0;
+
+					mi++;
+				}
+
+				if (match_array[mi].rm_so
+					== match_array[mi].rm_eo) {
+					printf("\033[0m");
+					rev = 0;
+
+					mi++;
+				}
+
+				putchar(line[j]);
+			}
+
+			if (rev)
+				printf("\033[0m");
+		} else {
+		normal_print:
+			for (j = 0; j < col && line[j] != '\n'; j++)
+				putchar(line[j]);
+		}
+		putchar('\n');
+	}
+
+	while (i++ < current->head_line + row)
+		putchar('\n');
+
+	if (current->nr_lines <= current->head_line + row)
+		printf("\033[7m100%%\033[0m");
+	else
+		printf("\033[7m% .0f%%\033[0m",
+			(float)(current->head_line + row)
+			/ current->nr_lines * 100.0);
+
+	printf("\033[7m %s\033[0m", bottom_message);
+	fflush(stdout);
+}
+
+static void signal_handler(int signum)
+{
+	switch (signum) {
+	case SIGWINCH:
+		update_row_col();
+		update_terminal();
+		break;
+
+	case SIGINT:
+		if (searching)
+			searching = 0;
+		else
+			running = 0;
+		break;
+
+	default:
+		die("unknown signal: %d", signum);
+		break;
+	}
+}
+
+static int init_sighandler(void)
+{
+	struct sigaction act;
+
+	bzero(&act, sizeof(struct sigaction));
+	act.sa_handler = signal_handler;
+	sigaction(SIGWINCH, &act, NULL);
+	sigaction(SIGINT, &act, NULL);
+
+	return 0;
+}
+
+static void init_commit(struct commit *c, int first_index)
+{
+	int i, line_head;
+
+	c->lines_size = LINES_INIT_SIZE;
+	c->lines = xalloc(c->lines_size * sizeof(int));
+
+	line_head = first_index;
+
+	for (i = first_index; logbuf[i] != ETX; i++) {
+		if (logbuf[i] != '\n')
+			continue;
+
+		c->lines[c->nr_lines++] = line_head;
+		line_head = i + 1;
+
+		if (c->lines_size == c->nr_lines) {
+			c->lines_size <<= 1;
+			c->lines = xrealloc(c->lines,
+					c->lines_size * sizeof(int));
+		}
+	}
+}
+
+static int contain_etx(int begin, int end)
+{
+	int i;
+
+	for (i = begin; i < end; i++)
+		if (logbuf[i] == (char)ETX) return i;
+
+	return -1;
+}
+
+static void read_head(void)
+{
+	int prev_logbuf_used = 0;
+
+	do {
+		int rbyte;
+
+		if (logbuf_used == logbuf_size) {
+			logbuf_size <<= 1;
+			logbuf = xrealloc(logbuf, logbuf_size);
+		}
+
+		rbyte = read(stdin_fd, &logbuf[logbuf_used],
+			logbuf_size - logbuf_used);
+
+		if (rbyte < 0) {
+			if (errno == EINTR)
+				continue;
+			else
+				die("read() failed");
+		}
+
+		if (!rbyte)
+			exit(0); /* no input */
+
+		prev_logbuf_used = logbuf_used;
+		logbuf_used += rbyte;
+	} while ((last_etx =
+			contain_etx(prev_logbuf_used, logbuf_used)) == -1);
+
+	head = xalloc(sizeof(struct commit));
+	init_commit(head, 0);
+
+	tail = current = head;
+}
+
+static void read_commit(void)
+{
+	int prev_last_etx = last_etx;
+	int prev_logbuf_used, first_logbuf_used;
+	struct commit *new_commit;
+
+	static int read_end;
+
+	if (read_end)
+		return;
+
+	if (last_etx + 1 < logbuf_used) {
+		unsigned int tmp_etx;
+
+		tmp_etx = contain_etx(last_etx + 1, logbuf_used);
+		if (tmp_etx != -1) {
+			last_etx = tmp_etx;
+			goto skip_read;
+		}
+	}
+
+	prev_logbuf_used = 0;
+	first_logbuf_used = logbuf_used;
+
+	do {
+		int rbyte;
+
+		if (logbuf_used == logbuf_size) {
+			logbuf_size <<= 1;
+			logbuf = xrealloc(logbuf, logbuf_size);
+		}
+
+		rbyte = read(stdin_fd, &logbuf[logbuf_used],
+			logbuf_size - logbuf_used);
+
+		if (rbyte < 0) {
+			if (errno == EINTR)
+				continue;
+			else
+				die("read() failed");
+		}
+
+		if (!rbyte) {
+			read_end = 1;
+			if (first_logbuf_used == logbuf_used)
+				return;
+		}
+
+		prev_logbuf_used = logbuf_used;
+		logbuf_used += rbyte;
+	} while ((last_etx =
+			contain_etx(prev_logbuf_used, logbuf_used)) == -1);
+
+skip_read:
+
+	new_commit = xalloc(sizeof(struct commit));
+
+	assert(!tail->prev);
+	tail->prev = new_commit;
+	new_commit->next = tail;
+
+	tail = new_commit;
+
+	init_commit(new_commit, prev_last_etx + 1);
+}
+
+static int show_prev_commit(char cmd)
+{
+	if (!current->prev) {
+		read_commit();
+
+		if (!current->prev)
+			return 0;
+	}
+
+	current = current->prev;
+	return 1;
+}
+
+static int show_next_commit(char cmd)
+{
+	if (!current->next) {
+		assert(current == head);
+		return 0;
+	}
+
+	current = current->next;
+	return 1;
+}
+
+static int forward_line(char cmd)
+{
+	if (current->head_line + row < current->nr_lines) {
+		current->head_line++;
+		return 1;
+	}
+
+	return 0;
+}
+
+static int backward_line(char cmd)
+{
+	if (0 < current->head_line) {
+		current->head_line--;
+		return 1;
+	}
+
+	return 0;
+}
+
+static int goto_top(char cmd)
+{
+	if (!current->head_line)
+		return 0;
+
+	current->head_line = 0;
+	return 1;
+}
+
+static int goto_bottom(char cmd)
+{
+	if (current->nr_lines < row)
+		return 0;
+
+	current->head_line = current->nr_lines - row;
+	return 1;
+}
+
+static int forward_page(char cmd)
+{
+	if (current->nr_lines < current->head_line + row)
+		return 0;
+
+	current->head_line += row;
+	return 1;
+}
+
+static int backward_page(char cmd)
+{
+	if (!current->head_line)
+		return 0;
+
+	current->head_line -= row;
+	if (current->head_line < 0)
+		current->head_line = 0;
+
+	return 1;
+}
+
+static int show_root(char cmd)
+{
+	if (root) {
+		current = root;
+		return 1;
+	}
+
+	do {
+		if (!current->prev)
+			read_commit();
+		if (!current->prev)
+			break;
+		current = current->prev;
+	} while (1);
+
+	assert(!root);
+	root = current;
+
+	return 1;
+}
+
+static int show_head(char cmd)
+{
+	if (current == head)
+		return 0;
+
+	current = head;
+	return 1;
+}
+
+#define QUERY_SIZE 128
+static char query[QUERY_SIZE + 1];
+static int query_used;
+
+static int match_line(char *line)
+{
+	if (!regexec(re_compiled, line, 0, NULL, REG_NOTEOL))
+		return 1;
+
+	return 0;
+}
+
+static int match_commit(struct commit *c, int direction, int prog)
+{
+	int i = c->head_line;
+	int nli, result;
+	char *line;
+
+	if (prog) {
+		if (direction) {
+			if (c->nr_lines <= i - 1)
+				return 0;
+		} else {
+			if (!i)
+				return 0;
+		}
+
+		i += direction ? 1 : -1;
+	}
+
+	do {
+		line = &logbuf[c->lines[i]];
+		nli = ret_nl_index(line);
+
+		line[nli] = '\0';
+		result = match_line(line);
+		line[nli] = '\n';
+
+		if (result) {
+			c->head_line = i;
+			return 1;
+		}
+
+		i += direction ? 1 : -1;
+	} while (direction ? i < c->nr_lines : 0 <= i);
+
+	return 0;
+}
+
+static int do_search(int direction, int global, int prog)
+{
+	int result;
+	struct commit *p;
+
+	assert(!searching);
+	searching = 1;
+
+	result = match_commit(current, direction, prog);
+	if (result || !global)
+		goto no_match;
+
+	if (direction) {
+		if (!current->prev)
+			read_commit();
+
+		if (current->prev)
+			p = current->prev;
+		else
+			goto no_match;
+	} else {
+		if (current->next)
+			p = current->next;
+		else
+			goto no_match;
+	}
+
+	do {
+		if (direction)
+			p->head_line = 0;
+		else
+			p->head_line = p->nr_lines - 1;
+
+		result = match_commit(p, direction, prog);
+		if (result)
+			goto matched;
+
+		if (direction && !p->prev)
+			read_commit();
+	} while (searching && (direction ? (p = p->prev) : (p = p->next)));
+
+	goto no_match;
+
+matched:
+	current = p;
+no_match:
+	searching = 0;
+
+	return result;
+}
+
+static int current_direction, current_global;
+
+#define update_query_bm()	do {					\
+		bmprintf("\033[7m%s %s search:\033[0m %s",		\
+			current_direction ? "forward" : "backward",	\
+			current_global ? "global" : "local",		\
+			query);						\
+									\
+	} while (0)
+
+static int _search(int key, int direction, int global)
+{
+	current_direction = direction;
+	current_global = global;
+
+	switch (state) {
+	case STATE_DEFAULT:
+	case STATE_SEARCHING_QUERY:
+		query_used = 0;
+		bzero(query, QUERY_SIZE);
+
+		update_query_bm();
+		state = STATE_INPUT_SEARCH_QUERY;
+		break;
+
+	case STATE_INPUT_SEARCH_QUERY:
+		if (query_used + 1 == QUERY_SIZE) {
+			bmprintf("search query is too long!");
+			state = STATE_DEFAULT;
+
+			goto end;
+		}
+
+		if (key == '\n')
+			state = STATE_SEARCHING_QUERY;
+		else {
+			query[query_used++] = (char)key;
+			update_query_bm();
+		}
+	end:
+		break;
+
+	default:
+		die("invalid or unknown state: %d", state);
+		break;
+	}
+
+	if (state == STATE_SEARCHING_QUERY) {
+		if (re_compiled)
+			regfree(re_compiled);
+		else
+			re_compiled = xalloc(sizeof(regex_t));
+		regcomp(re_compiled, query, REG_ICASE);
+
+		if (!do_search(direction, global, 0))
+			bmprintf("not found: %s", query);
+		else
+			update_query_bm();
+	}
+
+	return 1;
+}
+
+static int search(int direction, int global)
+{
+	return _search(-1, direction, global);
+}
+
+static int search_global_forward(char cmd)
+{
+	return search(1, 1);
+}
+
+static int search_global_backward(char cmd)
+{
+	return search(0, 1);
+}
+
+static int search_local_forward(char cmd)
+{
+	return search(1, 0);
+}
+
+static int search_local_backward(char cmd)
+{
+	return search(0, 0);
+}
+
+static int search_progress(char cmd)
+{
+	if (state != STATE_SEARCHING_QUERY)
+		return 0;
+
+	if (!do_search(cmd == 'n' ?
+			current_direction : !current_direction,
+			current_global, 1))
+		bmprintf("not found: %s", query);
+	else
+		update_query_bm();
+
+	return 1;
+}
+
+static int input_query(char key)
+{
+	if (key == (char)0x7f) {
+		/* backspace */
+		if (!query_used)
+			return 0;
+
+		query[--query_used] = '\0';
+		update_query_bm();
+
+		return 1;
+	} else if (key == (char)0x1b) {
+		/* escape */
+		regfree(re_compiled);
+		free(re_compiled);
+		re_compiled = NULL;
+
+		query_used = 0;
+		bzero(query, QUERY_SIZE);
+
+		bzero(bottom_message, bottom_message_size);
+		state = STATE_DEFAULT;
+		return 1;
+	}
+
+	return _search(key, current_direction, current_global);
+}
+
+static int nop(char cmd)
+{
+	return 0;
+}
+
+static int quit(char cmd)
+{
+	running = 0;
+	return 0;
+}
+
+struct key_cmd {
+	char key;
+	int (*op)(char);
+};
+
+static struct key_cmd valid_ops[] = {
+	{ 'h', show_prev_commit },
+	{ 'j', forward_line },
+	{ 'k', backward_line },
+	{ 'l', show_next_commit },
+	{ 'q', quit },
+	{ 'g', goto_top },
+	{ 'G', goto_bottom },
+	{ ' ', forward_page },
+	{ 'J', forward_page },
+	{ 'K', backward_page },
+	{ 'H', show_root },
+	{ 'L', show_head },
+	{ '/', search_global_forward },
+	{ '?', search_global_backward },
+	{ '\\', search_local_forward },
+	{ '!', search_local_backward },
+	{ 'n', search_progress },
+	{ 'p', search_progress },
+
+	{ '\0', NULL },
+};
+
+static int (*ops_array[256])(char);
+
+int main(void)
+{
+	int i;
+	char cmd;
+
+#ifdef GIT_LESS_DEBUG
+
+#define DEBUG_FIFO_NAME "/tmp/git-less-debug"
+	if (mkfifo(DEBUG_FIFO_NAME, S_IRWXU))
+		die("failed to create named fifo for debugging");
+
+	debug_fd = open(DEBUG_FIFO_NAME, O_RDWR);
+	if (debug_fd < 0)
+		die("failed to open() named fifo for debugging");
+
+#endif
+
+	bottom_message = xalloc(bottom_message_size);
+	match_array = xalloc(match_array_size * sizeof(regmatch_t));
+
+	init_tty();
+	init_sighandler();
+
+	logbuf_size = LOGBUF_INIT_SIZE;
+	logbuf = xalloc(logbuf_size);
+	logbuf_used = 0;
+	read_head();
+
+	update_terminal();
+
+	for (i = 0; i < 256; i++)
+		ops_array[i] = nop;
+
+	for (i = 0; valid_ops[i].key != '\0'; i++)
+		ops_array[(int)valid_ops[i].key] = valid_ops[i].op;
+
+	while (running) {
+		int ret;
+
+		ret = read(tty_fd, &cmd, 1);
+		if (ret == -1 && errno == EINTR) {
+			if (!running)
+				break;
+
+			errno = 0;
+			continue;
+		}
+
+		if (ret != 1)
+			die("reading key input failed");
+
+		if (state == STATE_INPUT_SEARCH_QUERY)
+			ret = input_query(cmd);
+		else
+			ret = ops_array[(int)cmd](cmd);
+
+		if (ret)
+			update_terminal();
+	}
+
+	printf("\n");
+
+#ifdef GIT_LESS_DEBUG
+	unlink(DEBUG_FIFO_NAME);
+#endif
+
+	return 0;
+}
-- 
1.7.10.rc1.33.g64ff3.dirty

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v0 2/2] git-less: git side support for git-less
  2012-03-22 18:42 [PATCH v0 0/2] git-less: a specialized pager for git-log Hitoshi Mitake
  2012-03-22 18:42 ` [PATCH v0 1/2] " Hitoshi Mitake
@ 2012-03-22 18:42 ` Hitoshi Mitake
  2012-03-22 19:00 ` [PATCH v0 0/2] git-less: a specialized pager for git-log Junio C Hamano
  2012-03-24  4:43 ` Nguyen Thai Ngoc Duy
  3 siblings, 0 replies; 10+ messages in thread
From: Hitoshi Mitake @ 2012-03-22 18:42 UTC (permalink / raw)
  To: gitster; +Cc: git, Hitoshi Mitake

This patch modifies three parts of git.
1. git-log ... insert ETX(0x03) between each commit for the detection the boarder
   of commits by git-less.
2. pager selection ... current git uses single pager program for every command.
   I designed git-less for git-log so it is not suitable for other commands like
   git-grep, etc.
3. new configuration parameter for specifying git-less as a pager of git-log

(I think these three changes are one logical unit.)

The most important commit is 2. As described above, current git supports only
one pager and this is not good for git-less because git-less only assumes
git-log. So I modified setup_pager() in pager.c to separate pager selection
mechanism and pager setup mechanism.

As described in 3, git-log specific pager can be specified with the new string
typed parameter "log.pager". git-less can be set as the pager like this config:
[log]
	pager = git-less

The description of this new parameter is also added in the help of git-log.

Signed-off-by: Hitoshi Mitake <h.mitake@gmail.com>
---
 Documentation/git-log.txt |    6 +++++-
 builtin/log.c             |   32 ++++++++++++++++++++++++++++++++
 cache.h                   |    1 +
 pager.c                   |    9 ++++++---
 4 files changed, 44 insertions(+), 4 deletions(-)

diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index 249fc87..f2e07f4 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -177,7 +177,11 @@ notes.displayRef::
 	or 'GIT_NOTES_REF', to read notes from when showing commit
 	messages with the 'log' family of commands.  See
 	linkgit:git-notes[1].
-+
+
+log.pager::
+       Pager program for git-log. Currently this option only assumes
+       "git-less".
+
 May be an unabbreviated ref name or a glob and may be specified
 multiple times.  A warning will be issued for refs that do not exist,
 but a glob that does not match any refs is silently ignored.
diff --git a/builtin/log.c b/builtin/log.c
index 8a47012..2ef3ada 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -88,6 +88,22 @@ static void cmd_log_init_defaults(struct rev_info *rev)
 		rev->date_mode = parse_date_format(default_date_mode);
 }
 
+static int use_git_less;
+
+static int setup_log_pager(const char *myname)
+{
+	if (strcmp(myname, "log")) {
+		use_git_less = 0;
+		return 0;
+	}
+
+	if (!use_git_less)
+		return 0;
+
+	__setup_pager("git-less");
+	return 1;
+}
+
 static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
 			 struct rev_info *rev, struct setup_revision_opt *opt)
 {
@@ -149,6 +165,10 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
 		rev->show_decorations = 1;
 		load_ref_decorations(decoration_style);
 	}
+
+	if (setup_log_pager(argv[0]))
+		return;
+
 	setup_pager();
 }
 
@@ -314,6 +334,9 @@ static int cmd_log_walk(struct rev_info *rev)
 			saved_nrl = rev->diffopt.needed_rename_limit;
 		if (rev->diffopt.degraded_cc_to_c)
 			saved_dcctc = 1;
+
+		if (use_git_less)
+			printf("\003");
 	}
 	rev->diffopt.degraded_cc_to_c = saved_dcctc;
 	rev->diffopt.needed_rename_limit = saved_nrl;
@@ -349,6 +372,15 @@ static int git_log_config(const char *var, const char *value, void *cb)
 	}
 	if (!prefixcmp(var, "color.decorate."))
 		return parse_decorate_color_config(var, 15, value);
+	if (!strcmp(var, "log.pager")) {
+		if (!strcmp(value, "git-less")) {
+			use_git_less = 1;
+			return 0;
+		} else {
+			fprintf(stderr, "unknown pager for git-log: %s\n", value);
+			return -1;
+		}
+	}
 
 	return git_diff_ui_config(var, value, cb);
 }
diff --git a/cache.h b/cache.h
index e5e1aa4..e5e57ff 100644
--- a/cache.h
+++ b/cache.h
@@ -1185,6 +1185,7 @@ static inline ssize_t write_str_in_full(int fd, const char *str)
 
 /* pager.c */
 extern void setup_pager(void);
+extern void __setup_pager(const char *pager);
 extern const char *pager_program;
 extern int pager_in_use(void);
 extern int pager_use_color;
diff --git a/pager.c b/pager.c
index 05584de..45fe9ea 100644
--- a/pager.c
+++ b/pager.c
@@ -69,10 +69,8 @@ const char *git_pager(int stdout_is_tty)
 	return pager;
 }
 
-void setup_pager(void)
+void __setup_pager(const char *pager)
 {
-	const char *pager = git_pager(isatty(1));
-
 	if (!pager)
 		return;
 
@@ -110,6 +108,11 @@ void setup_pager(void)
 	atexit(wait_for_pager);
 }
 
+void setup_pager(void)
+{
+	__setup_pager(git_pager(isatty(1)));
+}
+
 int pager_in_use(void)
 {
 	const char *env;
-- 
1.7.10.rc1.33.g64ff3.dirty

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH v0 0/2] git-less: a specialized pager for git-log
  2012-03-22 18:42 [PATCH v0 0/2] git-less: a specialized pager for git-log Hitoshi Mitake
  2012-03-22 18:42 ` [PATCH v0 1/2] " Hitoshi Mitake
  2012-03-22 18:42 ` [PATCH v0 2/2] git-less: git side support for git-less Hitoshi Mitake
@ 2012-03-22 19:00 ` Junio C Hamano
  2012-03-23 10:07   ` Andreas Ericsson
  2012-03-23 17:15   ` Hitoshi Mitake
  2012-03-24  4:43 ` Nguyen Thai Ngoc Duy
  3 siblings, 2 replies; 10+ messages in thread
From: Junio C Hamano @ 2012-03-22 19:00 UTC (permalink / raw)
  To: Hitoshi Mitake; +Cc: git

Hitoshi Mitake <h.mitake@gmail.com> writes:

> If the pager can recognize the end of each commit, more confortable viewing of
> git-log's output will be possible.

I think people just use "/^commit .*<RET>" once and then nagivate with "n"
(and to change direction, "?<RET>") for this.

> For implementing this feature, I had to make modification of git-log

I do not think the change to "log" is necessary nor worth it.  If the
pager is so specialized to be tied to git, it should learn to recognize
the output from "git", namely, "git log" and "git log -p" would never have
a line that begins with "^commit " that is not the beginning of one record
for a commit.

If you are not comfortable using "less", perhaps a program like "tig" that
is very specialized for working with git might help you better.

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v0 0/2] git-less: a specialized pager for git-log
  2012-03-22 19:00 ` [PATCH v0 0/2] git-less: a specialized pager for git-log Junio C Hamano
@ 2012-03-23 10:07   ` Andreas Ericsson
  2012-03-23 17:18     ` Hitoshi Mitake
  2012-03-23 20:44     ` Alex Plotnick
  2012-03-23 17:15   ` Hitoshi Mitake
  1 sibling, 2 replies; 10+ messages in thread
From: Andreas Ericsson @ 2012-03-23 10:07 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Hitoshi Mitake, git

On 03/22/2012 08:00 PM, Junio C Hamano wrote:
> Hitoshi Mitake<h.mitake@gmail.com>  writes:
> 
>> If the pager can recognize the end of each commit, more confortable viewing of
>> git-log's output will be possible.
> 
> I think people just use "/^commit .*<RET>" once and then nagivate with "n"
> (and to change direction, "?<RET>") for this.
> 

Or capital N.

It's mostly useful when one wants to view the patch as well as the message,
so an alias like

    logp = !PAGER='less -p ^commit' git log -p

should work well, and also make LISP nerds giggle.

-- 
Andreas Ericsson                   andreas.ericsson@op5.se
OP5 AB                             www.op5.se
Tel: +46 8-230225                  Fax: +46 8-230231

Considering the successes of the wars on alcohol, poverty, drugs and
terror, I think we should give some serious thought to declaring war
on peace.

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v0 0/2] git-less: a specialized pager for git-log
  2012-03-22 19:00 ` [PATCH v0 0/2] git-less: a specialized pager for git-log Junio C Hamano
  2012-03-23 10:07   ` Andreas Ericsson
@ 2012-03-23 17:15   ` Hitoshi Mitake
  1 sibling, 0 replies; 10+ messages in thread
From: Hitoshi Mitake @ 2012-03-23 17:15 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Fri, Mar 23, 2012 at 04:00, Junio C Hamano <gitster@pobox.com> wrote:
> Hitoshi Mitake <h.mitake@gmail.com> writes:
>
>> If the pager can recognize the end of each commit, more confortable viewing of
>> git-log's output will be possible.
>
> I think people just use "/^commit .*<RET>" once and then nagivate with "n"
> (and to change direction, "?<RET>") for this.

Yes, but less cannot search regex with specified range of text
(in this case, from the beginning to the end of the commit).

>
>> For implementing this feature, I had to make modification of git-log
>
> I do not think the change to "log" is necessary nor worth it.  If the
> pager is so specialized to be tied to git, it should learn to recognize
> the output from "git", namely, "git log" and "git log -p" would never have
> a line that begins with "^commit " that is not the beginning of one record
> for a commit.

This rule is useful for detection of each commit from pager. I didn't
have to modify git-log... With this rule, the pager can be independent from
the tree of git, thanks!

>
> If you are not comfortable using "less", perhaps a program like "tig" that
> is very specialized for working with git might help you better.
>

Thanks for your information. I didn't know about tig. I'd like to test it or
modify my pager for more comfortable git-log viewing.

Thanks for your feedback,
-- 
Hitoshi Mitake
h.mitake@gmail.com

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v0 0/2] git-less: a specialized pager for git-log
  2012-03-23 10:07   ` Andreas Ericsson
@ 2012-03-23 17:18     ` Hitoshi Mitake
  2012-03-23 20:44     ` Alex Plotnick
  1 sibling, 0 replies; 10+ messages in thread
From: Hitoshi Mitake @ 2012-03-23 17:18 UTC (permalink / raw)
  To: Andreas Ericsson; +Cc: Junio C Hamano, git

On Fri, Mar 23, 2012 at 19:07, Andreas Ericsson <ae@op5.se> wrote:
> On 03/22/2012 08:00 PM, Junio C Hamano wrote:
>> Hitoshi Mitake<h.mitake@gmail.com>  writes:
>>
>>> If the pager can recognize the end of each commit, more confortable viewing of
>>> git-log's output will be possible.
>>
>> I think people just use "/^commit .*<RET>" once and then nagivate with "n"
>> (and to change direction, "?<RET>") for this.
>>
>
> Or capital N.
>
> It's mostly useful when one wants to view the patch as well as the message,
> so an alias like
>
>    logp = !PAGER='less -p ^commit' git log -p

Yes, moving is not so large problem. But less cannot search regex with
specified limit. Sometime I want to search string e.g. "something" only in
the commit I'm focusing on.

>
> should work well, and also make LISP nerds giggle.
>

p? :)

> --
> Andreas Ericsson                   andreas.ericsson@op5.se
> OP5 AB                             www.op5.se
> Tel: +46 8-230225                  Fax: +46 8-230231
>
> Considering the successes of the wars on alcohol, poverty, drugs and
> terror, I think we should give some serious thought to declaring war
> on peace.



-- 
Hitoshi Mitake
h.mitake@gmail.com

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v0 0/2] git-less: a specialized pager for git-log
  2012-03-23 10:07   ` Andreas Ericsson
  2012-03-23 17:18     ` Hitoshi Mitake
@ 2012-03-23 20:44     ` Alex Plotnick
  2012-03-30 14:23       ` Jeff Epler
  1 sibling, 1 reply; 10+ messages in thread
From: Alex Plotnick @ 2012-03-23 20:44 UTC (permalink / raw)
  To: git

[-- Attachment #1: Type: text/plain, Size: 394 bytes --]

I have on occasion also wished to view log messages (especially with
patches) one at a time. And although I agree that a dedicated pager is
overkill, perhaps a small script wouldn't be.

The attached script will eventually run afoul of argument length limits,
but seems to work well otherwise. If you're using less(1), you can step
through the files using :n, :p, and :x.

Cheers,

    -- Alex

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: View git log as one file per commit --]
[-- Type: text/x-shellscript, Size: 584 bytes --]

#!/bin/sh

# Use csplit(1) to split a git log into one commit per file, then run the
# pager on the split files.

. $(git --exec-path)/git-sh-setup

SPLIT_DIR="$GIT_DIR/split-$$"
CSPLIT="csplit -s -k"
if csplit --help 2>/dev/null | sane_grep -- "-z" >/dev/null;
then
	CSPLIT="$CSPLIT -z"
fi

_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
_infty=1000000

mkdir "$SPLIT_DIR" && cd "$SPLIT_DIR" || exit 1
trap 'rm -rf "$SPLIT_DIR"' 0 1 2 3 15
git log "$@" | $CSPLIT - "/^commit $_x40/" "{$_infty}" 2>/dev/null
git_pager "$SPLIT_DIR"/xx*

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v0 0/2] git-less: a specialized pager for git-log
  2012-03-22 18:42 [PATCH v0 0/2] git-less: a specialized pager for git-log Hitoshi Mitake
                   ` (2 preceding siblings ...)
  2012-03-22 19:00 ` [PATCH v0 0/2] git-less: a specialized pager for git-log Junio C Hamano
@ 2012-03-24  4:43 ` Nguyen Thai Ngoc Duy
  3 siblings, 0 replies; 10+ messages in thread
From: Nguyen Thai Ngoc Duy @ 2012-03-24  4:43 UTC (permalink / raw)
  To: Hitoshi Mitake; +Cc: gitster, git

On Fri, Mar 23, 2012 at 1:42 AM, Hitoshi Mitake <h.mitake@gmail.com> wrote:
> Hi Junio C Hamano and git people,
>
> I'd like to post this patch series for adding a pager program for git-log,
> named "git-less". It seems that less is today's the most popular pager for
> git-log. But I don't think that less is confortable pager when its input is come
> from git-log. Because less treats its input as a simple text. So less cannot
> recognize the end of each commit.

How does it compare to tig?
-- 
Duy

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v0 0/2] git-less: a specialized pager for git-log
  2012-03-23 20:44     ` Alex Plotnick
@ 2012-03-30 14:23       ` Jeff Epler
  0 siblings, 0 replies; 10+ messages in thread
From: Jeff Epler @ 2012-03-30 14:23 UTC (permalink / raw)
  To: Alex Plotnick; +Cc: git

Hmm, let's say you'd like to view the last 10 commits in a specialized
program that divides them up and provides good search capabilities:
    mutt -f <(git format-patch --stdout -10)
one shortcoming is that the whole git format-patch process must finish
before mutt will begin showing the mailbox .. otherwise, this seems
pretty awesome.

Jeff

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2012-03-30 14:23 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-03-22 18:42 [PATCH v0 0/2] git-less: a specialized pager for git-log Hitoshi Mitake
2012-03-22 18:42 ` [PATCH v0 1/2] " Hitoshi Mitake
2012-03-22 18:42 ` [PATCH v0 2/2] git-less: git side support for git-less Hitoshi Mitake
2012-03-22 19:00 ` [PATCH v0 0/2] git-less: a specialized pager for git-log Junio C Hamano
2012-03-23 10:07   ` Andreas Ericsson
2012-03-23 17:18     ` Hitoshi Mitake
2012-03-23 20:44     ` Alex Plotnick
2012-03-30 14:23       ` Jeff Epler
2012-03-23 17:15   ` Hitoshi Mitake
2012-03-24  4:43 ` Nguyen Thai Ngoc Duy

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).