{"thread":{"id":"30027","subject":"[PATCH v0 0/2] git-less: a specialized pager for git-log","startedAt":"2012-03-22T18:42:22Z","lastAt":"2012-03-30T14:23:01Z","messageCount":10,"participants":["Hitoshi Mitake","Junio C Hamano","Andreas Ericsson","Alex Plotnick","Nguyen Thai Ngoc Duy","Jeff Epler"],"isPatch":true,"patchVersion":0,"patchTotal":2},"messages":[{"id":"187487","messageId":"1332441744-5142-1-git-send-email-h.mitake@gmail.com","threadId":"30027","inReplyTo":null,"subject":"[PATCH v0 0/2] git-less: a specialized pager for git-log","fromName":"Hitoshi Mitake","fromEmail":"h.mitake@gmail.com","sentAt":"2012-03-22T18:42:22Z","receivedAt":"2012-03-22T18:42:22Z","isPatch":true,"sender":{"key":"h.mitake@gmail.com","avatar":null},"body":"Hi Junio C Hamano and git people,\n\nI'd like to post this patch series for adding a pager program for git-log,\nnamed \"git-less\". It seems that less is today's the most popular pager for\ngit-log. But I don't think that less is confortable pager when its input is come\nfrom git-log. Because less treats its input as a simple text. So less cannot\nrecognize the end of each commit.\n\nIf the pager can recognize the end of each commit, more confortable viewing of\ngit-log's output will be possible.\n\nSo I'd like to introduce git-less, and the modification of git itself to support\nthis pager.\n\nThe main difference between less and git-less is recognition of each commit.\nWith this feature, git-log specific functionalities can be implemented. e.g.\n* move with commit unit. Under git-less, hitting 'h' key means move to previous\n  (parent) commit, 'l' means move to next (child) commit.\n* commit local regex search. regular expression search limiting its range within\n  commit, not entire log. I assigned '\\' for commit local forward regex search\n  and '?' for commit local regex search.\n\nAnd other features like yanking commit ID might be implemented simply (not\nimplemented yet).\n\nFor implementing this feature, I had to make modification of git-log\n* Changed output for inserting ETX (0x03) between each commit.\n* Added new configuration parameter for git-log only pager: \"log.pager\".\n  git-less only focuses on git-log, so it is not suitable for git-grep, etc.\n\nThis is the reason why I'm posting this patch. git-less is not git-independent\nprogram. The source code of git-less have to be integrated with the git tree.\nI understand this is not so good manner. And the source code is still dirty and\nmay be buggy, but I believe that this new pager is useful. I'd like to hear your\nfeedbacks.\n\nThanks,\n\nHitoshi Mitake (2):\n  git-less: a specialized pager for git-log\n  git-less: git side support for git-less\n\n .gitignore                |    1 +\n Documentation/git-log.txt |    6 +-\n Makefile                  |    5 +\n builtin/log.c             |   32 ++\n cache.h                   |    1 +\n less.c                    |  899 +++++++++++++++++++++++++++++++++++++++++++++\n pager.c                   |    9 +-\n 7 files changed, 949 insertions(+), 4 deletions(-)\n create mode 100644 less.c\n\n-- \n1.7.10.rc1.33.g64ff3.dirty\n"},{"id":"187488","messageId":"1332441744-5142-2-git-send-email-h.mitake@gmail.com","threadId":"30027","inReplyTo":"1332441744-5142-1-git-send-email-h.mitake@gmail.com","subject":"[PATCH v0 1/2] git-less: a specialized pager for git-log","fromName":"Hitoshi Mitake","fromEmail":"h.mitake@gmail.com","sentAt":"2012-03-22T18:42:23Z","receivedAt":"2012-03-22T18:42:23Z","isPatch":true,"sender":{"key":"h.mitake@gmail.com","avatar":null},"body":"This patch adds a specialized pager for git-log: \"git-less\". Basically git-less\nworks like ordinal less, but it uses some new keys. The important feature of\nthis pager is git's commit oriented log viewing.\n\nThis is the key mapping of commit oriented operations:\n\nh ... show previous commit\nl ... show next commit\ng ... goto top of viewing commit\nG ... goto bottom of viewing commit\nH ... show root of the commit tree\nL ... show HEAD of the commit tree\n\\ ... regex forward search only in viewing commit\n! ... regex backward search only in viewing commit\n\nAs this mapping shows, commit oriented move and search are implemented.\nEspecially I like commit local regex searches.\n\nThis patch adds the new source file less.c, the implementation of git-less. And\nmodifies .gitignore and Makefile for ignoring the binary and buliding.\n\nSigned-off-by: Hitoshi Mitake <h.mitake@gmail.com>\n---\n .gitignore |    1 +\n Makefile   |    5 +\n less.c     |  899 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 905 insertions(+)\n create mode 100644 less.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 87fcc5f..81dba3a 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -163,6 +163,7 @@\n /git-web--browse\n /git-whatchanged\n /git-write-tree\n+/git-less\n /git-core-*/?*\n /gitk-git/gitk-wish\n /gitweb/GITWEB-BUILD-OPTIONS\ndiff --git a/Makefile b/Makefile\nindex be1957a..48600d5 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -463,6 +463,7 @@ PROGRAM_OBJS += upload-pack.o\n PROGRAM_OBJS += http-backend.o\n PROGRAM_OBJS += sh-i18n--envsubst.o\n PROGRAM_OBJS += credential-store.o\n+PROGRAM_OBJS += less.o\n \n # Binary suffix, set to .exe for Windows builds\n X =\n@@ -2239,6 +2240,10 @@ git-http-push$X: revision.o http.o http-push.o GIT-LDFLAGS $(GITLIBS)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n \t\t$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)\n \n+gitless$X: gitless.o\n+\t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n+\t\t$(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) $(LIB_4_CRYPTO)\n+\n $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)\n \t$(QUIET_LNCP)$(RM) $@ && \\\n \tln $< $@ 2>/dev/null || \\\ndiff --git a/less.c b/less.c\nnew file mode 100644\nindex 0000000..9848eca\n--- /dev/null\n+++ b/less.c\n@@ -0,0 +1,899 @@\n+/*\n+ * git-less - a specialized pager for git-log\n+ *\n+ * Copyright (C) 2012 Hitoshi Mitake <h.mitake@gmail.com>\n+ *\n+ *  This program is free software; you can redistribute it and/or modify\n+ *  it under the terms of the GNU General Public License as published by\n+ *  the Free Software Foundation; either version 2 of the License, or\n+ *  (at your option) any later version.\n+ *\n+ *  This program is distributed in the hope that it will be useful,\n+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+ *  GNU General Public License for more details.\n+ *\n+ *  You should have received a copy of the GNU General Public License\n+ *  along with this program; if not, write to the Free Software\n+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n+ */\n+\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+\n+#include <unistd.h>\n+#include <sys/types.h>\n+#include <fcntl.h>\n+#include <sys/stat.h>\n+#include <termios.h>\n+#include <sys/ioctl.h>\n+#include <signal.h>\n+#include <errno.h>\n+\n+#include <assert.h>\n+\n+#include <regex.h>\n+\n+extern int errno;\n+\n+#define die(fmt, arg...)\t\t\t\t\t\t\\\n+\tdo {                                                            \\\n+\t\tfprintf(stderr, \"line %d: fatal error, \" fmt \"\\n\",\t\\\n+\t\t\t__LINE__, ##arg);\t\t\t\t\\\n+\t\tfprintf(stderr, \"errno: %s\\n\", strerror(errno));\t\\\n+\t\texit(1);\t\t\t\t\t\t\\\n+\t} while (0)\n+\n+#define ETX 0x03\n+\n+static void *xalloc(size_t size)\n+{\n+\tvoid *ret;\n+\n+\tret = calloc(sizeof(char), size);\n+\tif (!ret)\n+\t\tdie(\"memory allocation failed\");\n+\n+\treturn ret;\n+}\n+\n+static void *xrealloc(void *ptr, size_t size)\n+{\n+\tvoid *ret;\n+\n+\tassert(size);\n+\tret = realloc(ptr, size);\n+\tif (!ret)\n+\t\tdie(\"memory allocation failed\");\n+\n+\treturn ret;\n+}\n+\n+static int ret_nl_index(char *s)\n+{\n+\tint i;\n+\n+\tfor (i = 0; s[i] != '\\n'; i++);\n+\treturn i;\n+}\n+\n+static int stdin_fd = 0, tty_fd, debug_fd;\n+static unsigned int row, col;\n+static int running = 1;\n+static int searching;\n+\n+#define LINES_INIT_SIZE 128\n+\n+enum {\n+\tSTATE_DEFAULT,\n+\tSTATE_INPUT_SEARCH_QUERY,\n+\tSTATE_SEARCHING_QUERY\n+};\n+static int state = STATE_DEFAULT;\n+\n+#define BOTTOM_MESSAGE_INIT_SIZE 32\n+static char *bottom_message;\n+static int bottom_message_size = BOTTOM_MESSAGE_INIT_SIZE;\n+\n+#define MATCH_ARRAY_INIT_SIZE 32\n+static regmatch_t *match_array;\n+static int match_array_size = MATCH_ARRAY_INIT_SIZE;\n+\n+#define bmprintf(fmt, arg...)\t\t\t\t\t\t\\\n+\tdo {                                                            \\\n+\t\tsnprintf(bottom_message, bottom_message_size,\t\t\\\n+\t\t\tfmt, ##arg);\t\t\t\t\t\\\n+\t} while (0)\n+\n+#ifdef GIT_LESS_DEBUG\n+\n+#define debug_printf(fmt, arg...)\t\t\t\t\t\\\n+\tdo {                                                            \\\n+\t\tchar dbg_msg[1024];\t\t\t\t\t\\\n+\t\tint w, wbyte, len;\t\t\t\t\t\\\n+\t\t\t\t\t\t\t\t\t\\\n+\t\tbzero(dbg_msg, 1024);\t\t\t\t\t\\\n+\t\tlen = snprintf(dbg_msg, 1024,\t\t\t\t\\\n+\t\t\tfmt, ##arg);\t\t\t\t\t\\\n+\t\t\t\t\t\t\t\t\t\\\n+\t\twbyte = 0;\t\t\t\t\t\t\\\n+\t\twhile ((w = write(debug_fd, dbg_msg + wbyte,\t\t\\\n+\t\t\t\t\t\tlen - wbyte))) {\t\\\n+\t\t\tif (wbyte < 0)\t\t\t\t\t\\\n+\t\t\t\tdie(\"failed write() on debug_fd\");\t\\\n+\t\t\t\t\t\t\t\t\t\\\n+\t\t\twbyte += w;\t\t\t\t\t\\\n+\t\t\tif (wbyte == len)\t\t\t\t\\\n+\t\t\t\tbreak;\t\t\t\t\t\\\n+\t\t}\t\t\t\t\t\t\t\\\n+\t} while (0)\n+\n+#else\n+\n+#define debug_printf(fmt, arg...) do { } while (0)\n+\n+#endif\t/* GIT_LESS_DEBUG */\n+\n+static void update_row_col(void)\n+{\n+\tstruct winsize size;\n+\n+\tbzero(&size, sizeof(struct winsize));\n+\tioctl(tty_fd, TIOCGWINSZ, (void *)&size);\n+\n+\trow = size.ws_row - 1;\n+\tcol = size.ws_col;\n+\n+\tif (bottom_message_size - 1 < col) {\n+\t\tbottom_message_size = col + 1;\n+\t\tbottom_message = xrealloc(bottom_message, bottom_message_size);\n+\t}\n+\n+\tif (match_array_size < col) {\n+\t\tmatch_array_size = col;\n+\t\tmatch_array = xrealloc(match_array,\n+\t\t\t\tmatch_array_size * sizeof(regmatch_t));\n+\t}\n+}\n+\n+static struct termios attr;\n+\n+static void init_tty(void)\n+{\n+\ttty_fd = open(\"/dev/tty\", O_RDONLY);\n+\tif (tty_fd < 0)\n+\t\tdie(\"open()ing /dev/tty\");\n+\n+\tbzero(&attr, sizeof(struct termios));\n+\ttcgetattr(tty_fd, &attr);\n+\tattr.c_lflag &= ~ICANON;\n+\tattr.c_lflag &= ~ECHO;\n+\ttcsetattr(tty_fd, TCSANOW, &attr);\n+\n+\tupdate_row_col();\n+}\n+\n+static char *logbuf;\n+static int logbuf_size, logbuf_used;\n+#define LOGBUF_INIT_SIZE 1024\n+\n+static int last_etx;\n+\n+struct commit {\n+\tunsigned int *lines;\t/* array of index of logbuf */\n+\tint lines_size;\n+\n+\tint nr_lines, head_line;\n+\n+\t/*\n+\t * caution:\n+\t * prev means previous commit of the commit object,\n+\t * next means next commit of the commit object.\n+\t */\n+\tstruct commit *prev, *next;\n+};\n+\n+/* head: HEAD, root: root of the commit tree */\n+static struct commit *head, *root;\n+/* current: current displaying commit, tail: tail of the read commits */\n+static struct commit *current, *tail;\n+\n+static regex_t *re_compiled;\n+\n+static void update_terminal(void)\n+{\n+\tint i, j, print;\n+\tchar *line;\n+\n+\tprintf(\"\\033[2J\\033[0;0J\");\n+\n+\t/* FIXME: first new line should be eliminated in git-log */\n+\tif (current != head && !current->head_line)\n+\t\tcurrent->head_line = 1;\n+\n+\tfor (i = current->head_line, print = 0;\n+\t     i < current->head_line + row\n+\t\t     && i < current->nr_lines; i++, print++) {\n+\t\tline = &logbuf[current->lines[i]];\n+\n+\t\tif (state == STATE_SEARCHING_QUERY) {\n+\t\t\tint ret, mi, nli = ret_nl_index(line);\n+\t\t\tint rev = 0;\n+\n+\t\t\tline[nli] = '\\0';\n+\t\t\tret = regexec(re_compiled, line,\n+\t\t\t\tmatch_array_size, match_array, 0);\n+\t\t\tline[nli] = '\\n';\n+\n+\t\t\tif (ret)\n+\t\t\t\tgoto normal_print;\n+\n+\t\t\tfor (mi = j = 0; j < col && line[j] != '\\n'; j++) {\n+\t\t\t\tif (j == match_array[mi].rm_so) {\n+\t\t\t\t\tprintf(\"\\033[7m\");\n+\t\t\t\t\trev = 1;\n+\t\t\t\t} else if (j == match_array[mi].rm_eo) {\n+\t\t\t\t\tprintf(\"\\033[0m\");\n+\t\t\t\t\trev = 0;\n+\n+\t\t\t\t\tmi++;\n+\t\t\t\t}\n+\n+\t\t\t\tif (match_array[mi].rm_so\n+\t\t\t\t\t== match_array[mi].rm_eo) {\n+\t\t\t\t\tprintf(\"\\033[0m\");\n+\t\t\t\t\trev = 0;\n+\n+\t\t\t\t\tmi++;\n+\t\t\t\t}\n+\n+\t\t\t\tputchar(line[j]);\n+\t\t\t}\n+\n+\t\t\tif (rev)\n+\t\t\t\tprintf(\"\\033[0m\");\n+\t\t} else {\n+\t\tnormal_print:\n+\t\t\tfor (j = 0; j < col && line[j] != '\\n'; j++)\n+\t\t\t\tputchar(line[j]);\n+\t\t}\n+\t\tputchar('\\n');\n+\t}\n+\n+\twhile (i++ < current->head_line + row)\n+\t\tputchar('\\n');\n+\n+\tif (current->nr_lines <= current->head_line + row)\n+\t\tprintf(\"\\033[7m100%%\\033[0m\");\n+\telse\n+\t\tprintf(\"\\033[7m% .0f%%\\033[0m\",\n+\t\t\t(float)(current->head_line + row)\n+\t\t\t/ current->nr_lines * 100.0);\n+\n+\tprintf(\"\\033[7m %s\\033[0m\", bottom_message);\n+\tfflush(stdout);\n+}\n+\n+static void signal_handler(int signum)\n+{\n+\tswitch (signum) {\n+\tcase SIGWINCH:\n+\t\tupdate_row_col();\n+\t\tupdate_terminal();\n+\t\tbreak;\n+\n+\tcase SIGINT:\n+\t\tif (searching)\n+\t\t\tsearching = 0;\n+\t\telse\n+\t\t\trunning = 0;\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tdie(\"unknown signal: %d\", signum);\n+\t\tbreak;\n+\t}\n+}\n+\n+static int init_sighandler(void)\n+{\n+\tstruct sigaction act;\n+\n+\tbzero(&act, sizeof(struct sigaction));\n+\tact.sa_handler = signal_handler;\n+\tsigaction(SIGWINCH, &act, NULL);\n+\tsigaction(SIGINT, &act, NULL);\n+\n+\treturn 0;\n+}\n+\n+static void init_commit(struct commit *c, int first_index)\n+{\n+\tint i, line_head;\n+\n+\tc->lines_size = LINES_INIT_SIZE;\n+\tc->lines = xalloc(c->lines_size * sizeof(int));\n+\n+\tline_head = first_index;\n+\n+\tfor (i = first_index; logbuf[i] != ETX; i++) {\n+\t\tif (logbuf[i] != '\\n')\n+\t\t\tcontinue;\n+\n+\t\tc->lines[c->nr_lines++] = line_head;\n+\t\tline_head = i + 1;\n+\n+\t\tif (c->lines_size == c->nr_lines) {\n+\t\t\tc->lines_size <<= 1;\n+\t\t\tc->lines = xrealloc(c->lines,\n+\t\t\t\t\tc->lines_size * sizeof(int));\n+\t\t}\n+\t}\n+}\n+\n+static int contain_etx(int begin, int end)\n+{\n+\tint i;\n+\n+\tfor (i = begin; i < end; i++)\n+\t\tif (logbuf[i] == (char)ETX) return i;\n+\n+\treturn -1;\n+}\n+\n+static void read_head(void)\n+{\n+\tint prev_logbuf_used = 0;\n+\n+\tdo {\n+\t\tint rbyte;\n+\n+\t\tif (logbuf_used == logbuf_size) {\n+\t\t\tlogbuf_size <<= 1;\n+\t\t\tlogbuf = xrealloc(logbuf, logbuf_size);\n+\t\t}\n+\n+\t\trbyte = read(stdin_fd, &logbuf[logbuf_used],\n+\t\t\tlogbuf_size - logbuf_used);\n+\n+\t\tif (rbyte < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\telse\n+\t\t\t\tdie(\"read() failed\");\n+\t\t}\n+\n+\t\tif (!rbyte)\n+\t\t\texit(0); /* no input */\n+\n+\t\tprev_logbuf_used = logbuf_used;\n+\t\tlogbuf_used += rbyte;\n+\t} while ((last_etx =\n+\t\t\tcontain_etx(prev_logbuf_used, logbuf_used)) == -1);\n+\n+\thead = xalloc(sizeof(struct commit));\n+\tinit_commit(head, 0);\n+\n+\ttail = current = head;\n+}\n+\n+static void read_commit(void)\n+{\n+\tint prev_last_etx = last_etx;\n+\tint prev_logbuf_used, first_logbuf_used;\n+\tstruct commit *new_commit;\n+\n+\tstatic int read_end;\n+\n+\tif (read_end)\n+\t\treturn;\n+\n+\tif (last_etx + 1 < logbuf_used) {\n+\t\tunsigned int tmp_etx;\n+\n+\t\ttmp_etx = contain_etx(last_etx + 1, logbuf_used);\n+\t\tif (tmp_etx != -1) {\n+\t\t\tlast_etx = tmp_etx;\n+\t\t\tgoto skip_read;\n+\t\t}\n+\t}\n+\n+\tprev_logbuf_used = 0;\n+\tfirst_logbuf_used = logbuf_used;\n+\n+\tdo {\n+\t\tint rbyte;\n+\n+\t\tif (logbuf_used == logbuf_size) {\n+\t\t\tlogbuf_size <<= 1;\n+\t\t\tlogbuf = xrealloc(logbuf, logbuf_size);\n+\t\t}\n+\n+\t\trbyte = read(stdin_fd, &logbuf[logbuf_used],\n+\t\t\tlogbuf_size - logbuf_used);\n+\n+\t\tif (rbyte < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\telse\n+\t\t\t\tdie(\"read() failed\");\n+\t\t}\n+\n+\t\tif (!rbyte) {\n+\t\t\tread_end = 1;\n+\t\t\tif (first_logbuf_used == logbuf_used)\n+\t\t\t\treturn;\n+\t\t}\n+\n+\t\tprev_logbuf_used = logbuf_used;\n+\t\tlogbuf_used += rbyte;\n+\t} while ((last_etx =\n+\t\t\tcontain_etx(prev_logbuf_used, logbuf_used)) == -1);\n+\n+skip_read:\n+\n+\tnew_commit = xalloc(sizeof(struct commit));\n+\n+\tassert(!tail->prev);\n+\ttail->prev = new_commit;\n+\tnew_commit->next = tail;\n+\n+\ttail = new_commit;\n+\n+\tinit_commit(new_commit, prev_last_etx + 1);\n+}\n+\n+static int show_prev_commit(char cmd)\n+{\n+\tif (!current->prev) {\n+\t\tread_commit();\n+\n+\t\tif (!current->prev)\n+\t\t\treturn 0;\n+\t}\n+\n+\tcurrent = current->prev;\n+\treturn 1;\n+}\n+\n+static int show_next_commit(char cmd)\n+{\n+\tif (!current->next) {\n+\t\tassert(current == head);\n+\t\treturn 0;\n+\t}\n+\n+\tcurrent = current->next;\n+\treturn 1;\n+}\n+\n+static int forward_line(char cmd)\n+{\n+\tif (current->head_line + row < current->nr_lines) {\n+\t\tcurrent->head_line++;\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int backward_line(char cmd)\n+{\n+\tif (0 < current->head_line) {\n+\t\tcurrent->head_line--;\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int goto_top(char cmd)\n+{\n+\tif (!current->head_line)\n+\t\treturn 0;\n+\n+\tcurrent->head_line = 0;\n+\treturn 1;\n+}\n+\n+static int goto_bottom(char cmd)\n+{\n+\tif (current->nr_lines < row)\n+\t\treturn 0;\n+\n+\tcurrent->head_line = current->nr_lines - row;\n+\treturn 1;\n+}\n+\n+static int forward_page(char cmd)\n+{\n+\tif (current->nr_lines < current->head_line + row)\n+\t\treturn 0;\n+\n+\tcurrent->head_line += row;\n+\treturn 1;\n+}\n+\n+static int backward_page(char cmd)\n+{\n+\tif (!current->head_line)\n+\t\treturn 0;\n+\n+\tcurrent->head_line -= row;\n+\tif (current->head_line < 0)\n+\t\tcurrent->head_line = 0;\n+\n+\treturn 1;\n+}\n+\n+static int show_root(char cmd)\n+{\n+\tif (root) {\n+\t\tcurrent = root;\n+\t\treturn 1;\n+\t}\n+\n+\tdo {\n+\t\tif (!current->prev)\n+\t\t\tread_commit();\n+\t\tif (!current->prev)\n+\t\t\tbreak;\n+\t\tcurrent = current->prev;\n+\t} while (1);\n+\n+\tassert(!root);\n+\troot = current;\n+\n+\treturn 1;\n+}\n+\n+static int show_head(char cmd)\n+{\n+\tif (current == head)\n+\t\treturn 0;\n+\n+\tcurrent = head;\n+\treturn 1;\n+}\n+\n+#define QUERY_SIZE 128\n+static char query[QUERY_SIZE + 1];\n+static int query_used;\n+\n+static int match_line(char *line)\n+{\n+\tif (!regexec(re_compiled, line, 0, NULL, REG_NOTEOL))\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n+static int match_commit(struct commit *c, int direction, int prog)\n+{\n+\tint i = c->head_line;\n+\tint nli, result;\n+\tchar *line;\n+\n+\tif (prog) {\n+\t\tif (direction) {\n+\t\t\tif (c->nr_lines <= i - 1)\n+\t\t\t\treturn 0;\n+\t\t} else {\n+\t\t\tif (!i)\n+\t\t\t\treturn 0;\n+\t\t}\n+\n+\t\ti += direction ? 1 : -1;\n+\t}\n+\n+\tdo {\n+\t\tline = &logbuf[c->lines[i]];\n+\t\tnli = ret_nl_index(line);\n+\n+\t\tline[nli] = '\\0';\n+\t\tresult = match_line(line);\n+\t\tline[nli] = '\\n';\n+\n+\t\tif (result) {\n+\t\t\tc->head_line = i;\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\ti += direction ? 1 : -1;\n+\t} while (direction ? i < c->nr_lines : 0 <= i);\n+\n+\treturn 0;\n+}\n+\n+static int do_search(int direction, int global, int prog)\n+{\n+\tint result;\n+\tstruct commit *p;\n+\n+\tassert(!searching);\n+\tsearching = 1;\n+\n+\tresult = match_commit(current, direction, prog);\n+\tif (result || !global)\n+\t\tgoto no_match;\n+\n+\tif (direction) {\n+\t\tif (!current->prev)\n+\t\t\tread_commit();\n+\n+\t\tif (current->prev)\n+\t\t\tp = current->prev;\n+\t\telse\n+\t\t\tgoto no_match;\n+\t} else {\n+\t\tif (current->next)\n+\t\t\tp = current->next;\n+\t\telse\n+\t\t\tgoto no_match;\n+\t}\n+\n+\tdo {\n+\t\tif (direction)\n+\t\t\tp->head_line = 0;\n+\t\telse\n+\t\t\tp->head_line = p->nr_lines - 1;\n+\n+\t\tresult = match_commit(p, direction, prog);\n+\t\tif (result)\n+\t\t\tgoto matched;\n+\n+\t\tif (direction && !p->prev)\n+\t\t\tread_commit();\n+\t} while (searching && (direction ? (p = p->prev) : (p = p->next)));\n+\n+\tgoto no_match;\n+\n+matched:\n+\tcurrent = p;\n+no_match:\n+\tsearching = 0;\n+\n+\treturn result;\n+}\n+\n+static int current_direction, current_global;\n+\n+#define update_query_bm()\tdo {\t\t\t\t\t\\\n+\t\tbmprintf(\"\\033[7m%s %s search:\\033[0m %s\",\t\t\\\n+\t\t\tcurrent_direction ? \"forward\" : \"backward\",\t\\\n+\t\t\tcurrent_global ? \"global\" : \"local\",\t\t\\\n+\t\t\tquery);\t\t\t\t\t\t\\\n+\t\t\t\t\t\t\t\t\t\\\n+\t} while (0)\n+\n+static int _search(int key, int direction, int global)\n+{\n+\tcurrent_direction = direction;\n+\tcurrent_global = global;\n+\n+\tswitch (state) {\n+\tcase STATE_DEFAULT:\n+\tcase STATE_SEARCHING_QUERY:\n+\t\tquery_used = 0;\n+\t\tbzero(query, QUERY_SIZE);\n+\n+\t\tupdate_query_bm();\n+\t\tstate = STATE_INPUT_SEARCH_QUERY;\n+\t\tbreak;\n+\n+\tcase STATE_INPUT_SEARCH_QUERY:\n+\t\tif (query_used + 1 == QUERY_SIZE) {\n+\t\t\tbmprintf(\"search query is too long!\");\n+\t\t\tstate = STATE_DEFAULT;\n+\n+\t\t\tgoto end;\n+\t\t}\n+\n+\t\tif (key == '\\n')\n+\t\t\tstate = STATE_SEARCHING_QUERY;\n+\t\telse {\n+\t\t\tquery[query_used++] = (char)key;\n+\t\t\tupdate_query_bm();\n+\t\t}\n+\tend:\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tdie(\"invalid or unknown state: %d\", state);\n+\t\tbreak;\n+\t}\n+\n+\tif (state == STATE_SEARCHING_QUERY) {\n+\t\tif (re_compiled)\n+\t\t\tregfree(re_compiled);\n+\t\telse\n+\t\t\tre_compiled = xalloc(sizeof(regex_t));\n+\t\tregcomp(re_compiled, query, REG_ICASE);\n+\n+\t\tif (!do_search(direction, global, 0))\n+\t\t\tbmprintf(\"not found: %s\", query);\n+\t\telse\n+\t\t\tupdate_query_bm();\n+\t}\n+\n+\treturn 1;\n+}\n+\n+static int search(int direction, int global)\n+{\n+\treturn _search(-1, direction, global);\n+}\n+\n+static int search_global_forward(char cmd)\n+{\n+\treturn search(1, 1);\n+}\n+\n+static int search_global_backward(char cmd)\n+{\n+\treturn search(0, 1);\n+}\n+\n+static int search_local_forward(char cmd)\n+{\n+\treturn search(1, 0);\n+}\n+\n+static int search_local_backward(char cmd)\n+{\n+\treturn search(0, 0);\n+}\n+\n+static int search_progress(char cmd)\n+{\n+\tif (state != STATE_SEARCHING_QUERY)\n+\t\treturn 0;\n+\n+\tif (!do_search(cmd == 'n' ?\n+\t\t\tcurrent_direction : !current_direction,\n+\t\t\tcurrent_global, 1))\n+\t\tbmprintf(\"not found: %s\", query);\n+\telse\n+\t\tupdate_query_bm();\n+\n+\treturn 1;\n+}\n+\n+static int input_query(char key)\n+{\n+\tif (key == (char)0x7f) {\n+\t\t/* backspace */\n+\t\tif (!query_used)\n+\t\t\treturn 0;\n+\n+\t\tquery[--query_used] = '\\0';\n+\t\tupdate_query_bm();\n+\n+\t\treturn 1;\n+\t} else if (key == (char)0x1b) {\n+\t\t/* escape */\n+\t\tregfree(re_compiled);\n+\t\tfree(re_compiled);\n+\t\tre_compiled = NULL;\n+\n+\t\tquery_used = 0;\n+\t\tbzero(query, QUERY_SIZE);\n+\n+\t\tbzero(bottom_message, bottom_message_size);\n+\t\tstate = STATE_DEFAULT;\n+\t\treturn 1;\n+\t}\n+\n+\treturn _search(key, current_direction, current_global);\n+}\n+\n+static int nop(char cmd)\n+{\n+\treturn 0;\n+}\n+\n+static int quit(char cmd)\n+{\n+\trunning = 0;\n+\treturn 0;\n+}\n+\n+struct key_cmd {\n+\tchar key;\n+\tint (*op)(char);\n+};\n+\n+static struct key_cmd valid_ops[] = {\n+\t{ 'h', show_prev_commit },\n+\t{ 'j', forward_line },\n+\t{ 'k', backward_line },\n+\t{ 'l', show_next_commit },\n+\t{ 'q', quit },\n+\t{ 'g', goto_top },\n+\t{ 'G', goto_bottom },\n+\t{ ' ', forward_page },\n+\t{ 'J', forward_page },\n+\t{ 'K', backward_page },\n+\t{ 'H', show_root },\n+\t{ 'L', show_head },\n+\t{ '/', search_global_forward },\n+\t{ '?', search_global_backward },\n+\t{ '\\\\', search_local_forward },\n+\t{ '!', search_local_backward },\n+\t{ 'n', search_progress },\n+\t{ 'p', search_progress },\n+\n+\t{ '\\0', NULL },\n+};\n+\n+static int (*ops_array[256])(char);\n+\n+int main(void)\n+{\n+\tint i;\n+\tchar cmd;\n+\n+#ifdef GIT_LESS_DEBUG\n+\n+#define DEBUG_FIFO_NAME \"/tmp/git-less-debug\"\n+\tif (mkfifo(DEBUG_FIFO_NAME, S_IRWXU))\n+\t\tdie(\"failed to create named fifo for debugging\");\n+\n+\tdebug_fd = open(DEBUG_FIFO_NAME, O_RDWR);\n+\tif (debug_fd < 0)\n+\t\tdie(\"failed to open() named fifo for debugging\");\n+\n+#endif\n+\n+\tbottom_message = xalloc(bottom_message_size);\n+\tmatch_array = xalloc(match_array_size * sizeof(regmatch_t));\n+\n+\tinit_tty();\n+\tinit_sighandler();\n+\n+\tlogbuf_size = LOGBUF_INIT_SIZE;\n+\tlogbuf = xalloc(logbuf_size);\n+\tlogbuf_used = 0;\n+\tread_head();\n+\n+\tupdate_terminal();\n+\n+\tfor (i = 0; i < 256; i++)\n+\t\tops_array[i] = nop;\n+\n+\tfor (i = 0; valid_ops[i].key != '\\0'; i++)\n+\t\tops_array[(int)valid_ops[i].key] = valid_ops[i].op;\n+\n+\twhile (running) {\n+\t\tint ret;\n+\n+\t\tret = read(tty_fd, &cmd, 1);\n+\t\tif (ret == -1 && errno == EINTR) {\n+\t\t\tif (!running)\n+\t\t\t\tbreak;\n+\n+\t\t\terrno = 0;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (ret != 1)\n+\t\t\tdie(\"reading key input failed\");\n+\n+\t\tif (state == STATE_INPUT_SEARCH_QUERY)\n+\t\t\tret = input_query(cmd);\n+\t\telse\n+\t\t\tret = ops_array[(int)cmd](cmd);\n+\n+\t\tif (ret)\n+\t\t\tupdate_terminal();\n+\t}\n+\n+\tprintf(\"\\n\");\n+\n+#ifdef GIT_LESS_DEBUG\n+\tunlink(DEBUG_FIFO_NAME);\n+#endif\n+\n+\treturn 0;\n+}\n-- \n1.7.10.rc1.33.g64ff3.dirty\n"},{"id":"187489","messageId":"1332441744-5142-3-git-send-email-h.mitake@gmail.com","threadId":"30027","inReplyTo":"1332441744-5142-1-git-send-email-h.mitake@gmail.com","subject":"[PATCH v0 2/2] git-less: git side support for git-less","fromName":"Hitoshi Mitake","fromEmail":"h.mitake@gmail.com","sentAt":"2012-03-22T18:42:24Z","receivedAt":"2012-03-22T18:42:24Z","isPatch":true,"sender":{"key":"h.mitake@gmail.com","avatar":null},"body":"This patch modifies three parts of git.\n1. git-log ... insert ETX(0x03) between each commit for the detection the boarder\n   of commits by git-less.\n2. pager selection ... current git uses single pager program for every command.\n   I designed git-less for git-log so it is not suitable for other commands like\n   git-grep, etc.\n3. new configuration parameter for specifying git-less as a pager of git-log\n\n(I think these three changes are one logical unit.)\n\nThe most important commit is 2. As described above, current git supports only\none pager and this is not good for git-less because git-less only assumes\ngit-log. So I modified setup_pager() in pager.c to separate pager selection\nmechanism and pager setup mechanism.\n\nAs described in 3, git-log specific pager can be specified with the new string\ntyped parameter \"log.pager\". git-less can be set as the pager like this config:\n[log]\n\tpager = git-less\n\nThe description of this new parameter is also added in the help of git-log.\n\nSigned-off-by: Hitoshi Mitake <h.mitake@gmail.com>\n---\n Documentation/git-log.txt |    6 +++++-\n builtin/log.c             |   32 ++++++++++++++++++++++++++++++++\n cache.h                   |    1 +\n pager.c                   |    9 ++++++---\n 4 files changed, 44 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-log.txt b/Documentation/git-log.txt\nindex 249fc87..f2e07f4 100644\n--- a/Documentation/git-log.txt\n+++ b/Documentation/git-log.txt\n@@ -177,7 +177,11 @@ notes.displayRef::\n \tor 'GIT_NOTES_REF', to read notes from when showing commit\n \tmessages with the 'log' family of commands.  See\n \tlinkgit:git-notes[1].\n-+\n+\n+log.pager::\n+       Pager program for git-log. Currently this option only assumes\n+       \"git-less\".\n+\n May be an unabbreviated ref name or a glob and may be specified\n multiple times.  A warning will be issued for refs that do not exist,\n but a glob that does not match any refs is silently ignored.\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 8a47012..2ef3ada 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -88,6 +88,22 @@ static void cmd_log_init_defaults(struct rev_info *rev)\n \t\trev->date_mode = parse_date_format(default_date_mode);\n }\n \n+static int use_git_less;\n+\n+static int setup_log_pager(const char *myname)\n+{\n+\tif (strcmp(myname, \"log\")) {\n+\t\tuse_git_less = 0;\n+\t\treturn 0;\n+\t}\n+\n+\tif (!use_git_less)\n+\t\treturn 0;\n+\n+\t__setup_pager(\"git-less\");\n+\treturn 1;\n+}\n+\n static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,\n \t\t\t struct rev_info *rev, struct setup_revision_opt *opt)\n {\n@@ -149,6 +165,10 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,\n \t\trev->show_decorations = 1;\n \t\tload_ref_decorations(decoration_style);\n \t}\n+\n+\tif (setup_log_pager(argv[0]))\n+\t\treturn;\n+\n \tsetup_pager();\n }\n \n@@ -314,6 +334,9 @@ static int cmd_log_walk(struct rev_info *rev)\n \t\t\tsaved_nrl = rev->diffopt.needed_rename_limit;\n \t\tif (rev->diffopt.degraded_cc_to_c)\n \t\t\tsaved_dcctc = 1;\n+\n+\t\tif (use_git_less)\n+\t\t\tprintf(\"\\003\");\n \t}\n \trev->diffopt.degraded_cc_to_c = saved_dcctc;\n \trev->diffopt.needed_rename_limit = saved_nrl;\n@@ -349,6 +372,15 @@ static int git_log_config(const char *var, const char *value, void *cb)\n \t}\n \tif (!prefixcmp(var, \"color.decorate.\"))\n \t\treturn parse_decorate_color_config(var, 15, value);\n+\tif (!strcmp(var, \"log.pager\")) {\n+\t\tif (!strcmp(value, \"git-less\")) {\n+\t\t\tuse_git_less = 1;\n+\t\t\treturn 0;\n+\t\t} else {\n+\t\t\tfprintf(stderr, \"unknown pager for git-log: %s\\n\", value);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n \n \treturn git_diff_ui_config(var, value, cb);\n }\ndiff --git a/cache.h b/cache.h\nindex e5e1aa4..e5e57ff 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1185,6 +1185,7 @@ static inline ssize_t write_str_in_full(int fd, const char *str)\n \n /* pager.c */\n extern void setup_pager(void);\n+extern void __setup_pager(const char *pager);\n extern const char *pager_program;\n extern int pager_in_use(void);\n extern int pager_use_color;\ndiff --git a/pager.c b/pager.c\nindex 05584de..45fe9ea 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -69,10 +69,8 @@ const char *git_pager(int stdout_is_tty)\n \treturn pager;\n }\n \n-void setup_pager(void)\n+void __setup_pager(const char *pager)\n {\n-\tconst char *pager = git_pager(isatty(1));\n-\n \tif (!pager)\n \t\treturn;\n \n@@ -110,6 +108,11 @@ void setup_pager(void)\n \tatexit(wait_for_pager);\n }\n \n+void setup_pager(void)\n+{\n+\t__setup_pager(git_pager(isatty(1)));\n+}\n+\n int pager_in_use(void)\n {\n \tconst char *env;\n-- \n1.7.10.rc1.33.g64ff3.dirty\n"},{"id":"187498","messageId":"7v1uokxxzh.fsf@alter.siamese.dyndns.org","threadId":"30027","inReplyTo":"1332441744-5142-1-git-send-email-h.mitake@gmail.com","subject":"Re: [PATCH v0 0/2] git-less: a specialized pager for git-log","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-03-22T19:00:02Z","receivedAt":"2012-03-22T19:00:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Hitoshi Mitake <h.mitake@gmail.com> writes:\n\n> If the pager can recognize the end of each commit, more confortable viewing of\n> git-log's output will be possible.\n\nI think people just use \"/^commit .*<RET>\" once and then nagivate with \"n\"\n(and to change direction, \"?<RET>\") for this.\n\n> For implementing this feature, I had to make modification of git-log\n\nI do not think the change to \"log\" is necessary nor worth it.  If the\npager is so specialized to be tied to git, it should learn to recognize\nthe output from \"git\", namely, \"git log\" and \"git log -p\" would never have\na line that begins with \"^commit \" that is not the beginning of one record\nfor a commit.\n\nIf you are not comfortable using \"less\", perhaps a program like \"tig\" that\nis very specialized for working with git might help you better.\n"},{"id":"187559","messageId":"4F6C4B59.8060102@op5.se","threadId":"30027","inReplyTo":"7v1uokxxzh.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v0 0/2] git-less: a specialized pager for git-log","fromName":"Andreas Ericsson","fromEmail":"ae@op5.se","sentAt":"2012-03-23T10:07:21Z","receivedAt":"2012-03-23T10:07:21Z","isPatch":true,"sender":{"key":"ae@op5.se","avatar":"https://gravatar.com/avatar/426e89595c75a8f5252dd0c989e5fabe5bcac616e68557427ad9aef6b0ca342a?d=mp&s=160"},"body":"On 03/22/2012 08:00 PM, Junio C Hamano wrote:\n> Hitoshi Mitake<h.mitake@gmail.com>  writes:\n> \n>> If the pager can recognize the end of each commit, more confortable viewing of\n>> git-log's output will be possible.\n> \n> I think people just use \"/^commit .*<RET>\" once and then nagivate with \"n\"\n> (and to change direction, \"?<RET>\") for this.\n> \n\nOr capital N.\n\nIt's mostly useful when one wants to view the patch as well as the message,\nso an alias like\n\n    logp = !PAGER='less -p ^commit' git log -p\n\nshould work well, and also make LISP nerds giggle.\n\n-- \nAndreas Ericsson                   andreas.ericsson@op5.se\nOP5 AB                             www.op5.se\nTel: +46 8-230225                  Fax: +46 8-230231\n\nConsidering the successes of the wars on alcohol, poverty, drugs and\nterror, I think we should give some serious thought to declaring war\non peace.\n"},{"id":"187582","messageId":"CAMO-S2gUaek83ACtLLBsLFYBpHuj=ePZRPzEPav+uP6igjN7oA@mail.gmail.com","threadId":"30027","inReplyTo":"7v1uokxxzh.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v0 0/2] git-less: a specialized pager for git-log","fromName":"Hitoshi Mitake","fromEmail":"h.mitake@gmail.com","sentAt":"2012-03-23T17:15:16Z","receivedAt":"2012-03-23T17:15:16Z","isPatch":true,"sender":{"key":"h.mitake@gmail.com","avatar":null},"body":"On Fri, Mar 23, 2012 at 04:00, Junio C Hamano <gitster@pobox.com> wrote:\n> Hitoshi Mitake <h.mitake@gmail.com> writes:\n>\n>> If the pager can recognize the end of each commit, more confortable viewing of\n>> git-log's output will be possible.\n>\n> I think people just use \"/^commit .*<RET>\" once and then nagivate with \"n\"\n> (and to change direction, \"?<RET>\") for this.\n\nYes, but less cannot search regex with specified range of text\n(in this case, from the beginning to the end of the commit).\n\n>\n>> For implementing this feature, I had to make modification of git-log\n>\n> I do not think the change to \"log\" is necessary nor worth it.  If the\n> pager is so specialized to be tied to git, it should learn to recognize\n> the output from \"git\", namely, \"git log\" and \"git log -p\" would never have\n> a line that begins with \"^commit \" that is not the beginning of one record\n> for a commit.\n\nThis rule is useful for detection of each commit from pager. I didn't\nhave to modify git-log... With this rule, the pager can be independent from\nthe tree of git, thanks!\n\n>\n> If you are not comfortable using \"less\", perhaps a program like \"tig\" that\n> is very specialized for working with git might help you better.\n>\n\nThanks for your information. I didn't know about tig. I'd like to test it or\nmodify my pager for more comfortable git-log viewing.\n\nThanks for your feedback,\n-- \nHitoshi Mitake\nh.mitake@gmail.com\n"},{"id":"187583","messageId":"CAMO-S2hZXX-RSN+KQZ=a+dSbkAhziqvF4S_iSH+TqqtcFRiypA@mail.gmail.com","threadId":"30027","inReplyTo":"4F6C4B59.8060102@op5.se","subject":"Re: [PATCH v0 0/2] git-less: a specialized pager for git-log","fromName":"Hitoshi Mitake","fromEmail":"h.mitake@gmail.com","sentAt":"2012-03-23T17:18:36Z","receivedAt":"2012-03-23T17:18:36Z","isPatch":true,"sender":{"key":"h.mitake@gmail.com","avatar":null},"body":"On Fri, Mar 23, 2012 at 19:07, Andreas Ericsson <ae@op5.se> wrote:\n> On 03/22/2012 08:00 PM, Junio C Hamano wrote:\n>> Hitoshi Mitake<h.mitake@gmail.com>  writes:\n>>\n>>> If the pager can recognize the end of each commit, more confortable viewing of\n>>> git-log's output will be possible.\n>>\n>> I think people just use \"/^commit .*<RET>\" once and then nagivate with \"n\"\n>> (and to change direction, \"?<RET>\") for this.\n>>\n>\n> Or capital N.\n>\n> It's mostly useful when one wants to view the patch as well as the message,\n> so an alias like\n>\n>    logp = !PAGER='less -p ^commit' git log -p\n\nYes, moving is not so large problem. But less cannot search regex with\nspecified limit. Sometime I want to search string e.g. \"something\" only in\nthe commit I'm focusing on.\n\n>\n> should work well, and also make LISP nerds giggle.\n>\n\np? :)\n\n> --\n> Andreas Ericsson                   andreas.ericsson@op5.se\n> OP5 AB                             www.op5.se\n> Tel: +46 8-230225                  Fax: +46 8-230231\n>\n> Considering the successes of the wars on alcohol, poverty, drugs and\n> terror, I think we should give some serious thought to declaring war\n> on peace.\n\n\n\n-- \nHitoshi Mitake\nh.mitake@gmail.com\n"},{"id":"187610","messageId":"87obrn6o9x.fsf@sol.localdomain","threadId":"30027","inReplyTo":"4F6C4B59.8060102@op5.se","subject":"Re: [PATCH v0 0/2] git-less: a specialized pager for git-log","fromName":"Alex Plotnick","fromEmail":"shrike@netaxs.com","sentAt":"2012-03-23T20:44:10Z","receivedAt":"2012-03-23T20:44:10Z","isPatch":true,"sender":{"key":"shrike@netaxs.com","avatar":null},"body":"I have on occasion also wished to view log messages (especially with\npatches) one at a time. And although I agree that a dedicated pager is\noverkill, perhaps a small script wouldn't be.\n\nThe attached script will eventually run afoul of argument length limits,\nbut seems to work well otherwise. If you're using less(1), you can step\nthrough the files using :n, :p, and :x.\n\nCheers,\n\n    -- Alex\n\n\n#!/bin/sh\n\n# Use csplit(1) to split a git log into one commit per file, then run the\n# pager on the split files.\n\n. $(git --exec-path)/git-sh-setup\n\nSPLIT_DIR=\"$GIT_DIR/split-$$\"\nCSPLIT=\"csplit -s -k\"\nif csplit --help 2>/dev/null | sane_grep -- \"-z\" >/dev/null;\nthen\n\tCSPLIT=\"$CSPLIT -z\"\nfi\n\n_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'\n_x40=\"$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40\"\n_infty=1000000\n\nmkdir \"$SPLIT_DIR\" && cd \"$SPLIT_DIR\" || exit 1\ntrap 'rm -rf \"$SPLIT_DIR\"' 0 1 2 3 15\ngit log \"$@\" | $CSPLIT - \"/^commit $_x40/\" \"{$_infty}\" 2>/dev/null\ngit_pager \"$SPLIT_DIR\"/xx*\n"},{"id":"187625","messageId":"CACsJy8Ctyujq+F0qSe_=ddHxWLUaD6xAtwCZBvySFi275_P2Cg@mail.gmail.com","threadId":"30027","inReplyTo":"1332441744-5142-1-git-send-email-h.mitake@gmail.com","subject":"Re: [PATCH v0 0/2] git-less: a specialized pager for git-log","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-03-24T04:43:46Z","receivedAt":"2012-03-24T04:43:46Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, Mar 23, 2012 at 1:42 AM, Hitoshi Mitake <h.mitake@gmail.com> wrote:\n> Hi Junio C Hamano and git people,\n>\n> I'd like to post this patch series for adding a pager program for git-log,\n> named \"git-less\". It seems that less is today's the most popular pager for\n> git-log. But I don't think that less is confortable pager when its input is come\n> from git-log. Because less treats its input as a simple text. So less cannot\n> recognize the end of each commit.\n\nHow does it compare to tig?\n-- \nDuy\n"},{"id":"188161","messageId":"20120330142301.GB10343@unpythonic.net","threadId":"30027","inReplyTo":"87obrn6o9x.fsf@sol.localdomain","subject":"Re: [PATCH v0 0/2] git-less: a specialized pager for git-log","fromName":"Jeff Epler","fromEmail":"jepler@unpythonic.net","sentAt":"2012-03-30T14:23:01Z","receivedAt":"2012-03-30T14:23:01Z","isPatch":true,"sender":{"key":"jepler@unpythonic.net","avatar":"https://avatars.githubusercontent.com/u/1517291?v=4"},"body":"Hmm, let's say you'd like to view the last 10 commits in a specialized\nprogram that divides them up and provides good search capabilities:\n    mutt -f <(git format-patch --stdout -10)\none shortcoming is that the whole git format-patch process must finish\nbefore mutt will begin showing the mailbox .. otherwise, this seems\npretty awesome.\n\nJeff\n"}]}