Репозиторий Sisyphus
Последнее обновление: 1 октября 2023 | Пакетов: 18631 | Посещений: 37738993
en ru br
Репозитории ALT
S:2.5.8-alt1
5.1: 0.14.1-alt2
4.1: 0.10.1-alt1
4.0: 0.6-alt1
www.altlinux.org/Changes

Группа :: Разработка/Прочее
Пакет: tig

 Главная   Изменения   Спек   Патчи   Sources   Загрузить   Gear   Bugs and FR  Repocop 

Патч: tig-0.14.1-alt2.patch
Скачать


 .gear/rules                                        |    2 +
 .../tags/e60bc8ec8d156270d205dea803dc3bc58dbee82e  |   13 +
 .gear/tags/list                                    |    1 +
 NEWS                                               |   26 +
 manual.txt                                         |   17 +-
 tig.c                                              | 1100 +++++++++++++-------
 tig.spec                                           |  104 ++
 tigrc.5.txt                                        |   21 +-
 8 files changed, 914 insertions(+), 370 deletions(-)
diff --git a/.gear/rules b/.gear/rules
new file mode 100644
index 0000000..64a30ec
--- /dev/null
+++ b/.gear/rules
@@ -0,0 +1,2 @@
+tar: tig-@version@:.
+diff: tig-@version@:. .
diff --git a/.gear/tags/e60bc8ec8d156270d205dea803dc3bc58dbee82e b/.gear/tags/e60bc8ec8d156270d205dea803dc3bc58dbee82e
new file mode 100644
index 0000000..e942a5f
--- /dev/null
+++ b/.gear/tags/e60bc8ec8d156270d205dea803dc3bc58dbee82e
@@ -0,0 +1,13 @@
+object 5ba81063e64cbc0bbb62e086e0818318fba952f2
+type commit
+tag tig-0.14.1
+tagger Jonas Fonseca <fonseca@diku.dk> 1234738920 +0100
+
+tig version 0.14.1
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.9 (GNU/Linux)
+
+iEYEABECAAYFAkmYnugACgkQJfQykjqsXHdIrwCeKFK6/XjNc8kqHHOrXvKvN1Kd
+5jsAniFMFRcqM5muNEzkllTmDORNSNp6
+=ouJG
+-----END PGP SIGNATURE-----
diff --git a/.gear/tags/list b/.gear/tags/list
new file mode 100644
index 0000000..4ec0c14
--- /dev/null
+++ b/.gear/tags/list
@@ -0,0 +1 @@
+e60bc8ec8d156270d205dea803dc3bc58dbee82e tig-0.14.1
diff --git a/NEWS b/NEWS
index 58aafed..492255f 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,32 @@
 Release notes
 =============
 
+tig master
+---------
+
+Incompatibilities:
+
+ - Setting the cursor color no longer automatically sets the text to
+   bold. The old 'lazy' behavior was a bug.
+
+Improvements:
+
+ - Add branch view for choosing which branch to display in the main
+   view. Bound to 'H' by default.
+ - Tree view: sort entries by name, date or author. Toggling is bound to
+   'i' by default, with 'I' controlling whether or not to sort in
+   ascending order.
+ - Make height of the lower view in a split view configurable by setting
+   the 'split-view-height' variable to a number or a percentage.
+   Defaults to 2/3 of the total view height.
+ - Allow multiple text attributes for color commands:
+
+	color cursor white blue underline bold
+
+Bug fixes:
+
+ - Tree view: fix searching.
+
 tig-0.14.1
 ----------
 
diff --git a/manual.txt b/manual.txt
index 85775f3..d73e819 100644
--- a/manual.txt
+++ b/manual.txt
@@ -111,6 +111,9 @@ The blob view::
 The blame view::
 	Displays the file content annotated or blamed by commits.
 
+The branch view::
+	Displays the branches in the repository.
+
 The status view::
 	Displays status of files in the working tree and allows changes to be
 	staged/unstaged as well as adding of untracked files.
@@ -316,6 +319,7 @@ p	Switch to pager view.
 t	Switch to (directory) tree view.
 f	Switch to (file) blob view.
 B	Switch to blame view.
+H	Switch to branch view.
 h	Switch to help view
 S	Switch to status view
 c	Switch to stage view
@@ -426,13 +430,15 @@ z	Stop all background loading. This can be useful if you use \
 	tig in a repository with a long history without limiting \
 	the revision log.
 v	Show version.
+o	Open option menu
 '.'	Toggle line numbers on/off.
 D	Toggle date display on/off.
 A	Toggle author display on/off.
 g	Toggle revision graph visualization on/off.
 F	Toggle reference display on/off (tag and branch names).
 ':'	Open prompt. This allows you to specify what git command \
-	to run. Example `:log -p`
+	to run. Example `:log -p`. You can also use this to jump \
+	to a specific line by typing `:<linenumber>`, e.g. `:80`.
 e	Open file in editor.
 -----------------------------------------------------------------------------
 
@@ -445,11 +451,12 @@ a script or program. They are bound to keys and use information from the
 current browsing state, such as the current commit ID. Tig comes with
 the following built-in external commands:
 
-`-------`--------------------------------------------------------------------
-Key	Action
+`-------`-------`------------------------------------------------------------
+Keymap	Key	Action
 -----------------------------------------------------------------------------
-C	git cherry-pick %(commit)
-G	git gc
+main	C	git cherry-pick %(commit)
+status	C	git commit
+generic	G	git gc
 -----------------------------------------------------------------------------
 
 [[refspec]]
diff --git a/tig.c b/tig.c
index 180661a..e52185e 100644
--- a/tig.c
+++ b/tig.c
@@ -68,11 +68,11 @@ static void __NORETURN die(const char *err, ...);
 static void warn(const char *msg, ...);
 static void report(const char *msg, ...);
 static void set_nonblocking_input(bool loading);
-static int load_refs(void);
 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
 
 #define ABS(x)		((x) >= 0  ? (x) : -(x))
 #define MIN(x, y)	((x) < (y) ? (x) :  (y))
+#define MAX(x, y)	((x) > (y) ? (x) :  (y))
 
 #define ARRAY_SIZE(x)	(sizeof(x) / sizeof(x[0]))
 #define STRING_SIZE(x)	(sizeof(x) - 1)
@@ -104,15 +104,9 @@ static size_t utf8_length(const char **string, size_t col, int *width, size_t ma
 #define DATE_FORMAT	"%Y-%m-%d %H:%M"
 #define DATE_COLS	STRING_SIZE("2006-04-29 14:21 ")
 
-#define AUTHOR_COLS	20
 #define ID_COLS		8
 
-/* The default interval between line numbers. */
-#define NUMBER_INTERVAL	5
-
-#define TAB_SIZE	8
-
-#define	SCALE_SPLIT_VIEW(height)	((height) * 2 / 3)
+#define MIN_VIEW_HEIGHT 4
 
 #define NULL_ID		"0000000000000000000000000000000000000000"
 
@@ -129,17 +123,24 @@ static size_t utf8_length(const char **string, size_t col, int *width, size_t ma
 
 
 struct ref {
-	char *name;		/* Ref name; tag or head names are shortened. */
 	char id[SIZEOF_REV];	/* Commit SHA1 ID */
 	unsigned int head:1;	/* Is it the current HEAD? */
 	unsigned int tag:1;	/* Is it a tag? */
 	unsigned int ltag:1;	/* If so, is the tag local? */
 	unsigned int remote:1;	/* Is it a remote ref? */
 	unsigned int tracked:1;	/* Is it the remote for the current HEAD? */
-	unsigned int next:1;	/* For ref lists: are there more refs? */
+	char name[1];		/* Ref name; tag or head names are shortened. */
 };
 
-static struct ref **get_refs(const char *id);
+struct ref_list {
+	char id[SIZEOF_REV];	/* Commit SHA1 ID */
+	size_t size;		/* Number of refs. */
+	struct ref **refs;	/* References for this ID. */
+};
+
+static struct ref_list *get_ref_list(const char *id);
+static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
+static int load_refs(void);
 
 enum format_flags {
 	FORMAT_ALL,		/* Perform replacement in all arguments. */
@@ -161,6 +162,35 @@ typedef enum input_status (*input_handler)(void *data, char *buf, int c);
 static char *prompt_input(const char *prompt, input_handler handler, void *data);
 static bool prompt_yesno(const char *prompt);
 
+struct menu_item {
+	int hotkey;
+	const char *text;
+	void *data;
+};
+
+static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
+
+/*
+ * Allocation helpers ... Entering macro hell to never be seen again.
+ */
+
+#define DEFINE_ALLOCATOR(name, type, chunk_size)				\
+static type *									\
+name(type **mem, size_t size, size_t increase)					\
+{										\
+	size_t num_chunks = (size + chunk_size - 1) / chunk_size;		\
+	size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
+	type *tmp = *mem;							\
+										\
+	if (mem == NULL || num_chunks != num_chunks_new) {			\
+		tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type));	\
+		if (tmp)							\
+			*mem = tmp;						\
+	}									\
+										\
+	return tmp;								\
+}
+
 /*
  * String helpers
  */
@@ -309,6 +339,17 @@ suffixcmp(const char *str, int slen, const char *suffix)
 }
 
 
+static const char *
+mkdate(const time_t *time)
+{
+	static char buf[DATE_COLS + 1];
+	struct tm tm;
+
+	gmtime_r(time, &tm);
+	return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
+}
+
+
 static bool
 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
 {
@@ -594,20 +635,14 @@ io_read(struct io *io, void *buf, size_t bufsize)
 	} while (1);
 }
 
+DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
+
 static char *
 io_get(struct io *io, int c, bool can_read)
 {
 	char *eol;
 	ssize_t readsize;
 
-	if (!io->buf) {
-		io->buf = io->bufpos = malloc(BUFSIZ);
-		if (!io->buf)
-			return NULL;
-		io->bufalloc = BUFSIZ;
-		io->bufsize = 0;
-	}
-
 	while (TRUE) {
 		if (io->bufsize > 0) {
 			eol = memchr(io->bufpos, c, io->bufsize);
@@ -636,6 +671,12 @@ io_get(struct io *io, int c, bool can_read)
 		if (io->bufsize > 0 && io->bufpos > io->buf)
 			memmove(io->buf, io->bufpos, io->bufsize);
 
+		if (io->bufalloc == io->bufsize) {
+			if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
+				return NULL;
+			io->bufalloc += BUFSIZ;
+		}
+
 		io->bufpos = io->buf;
 		readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
 		if (io_error(io))
@@ -667,14 +708,14 @@ io_write(struct io *io, const void *buf, size_t bufsize)
 static bool
 io_read_buf(struct io *io, char buf[], size_t bufsize)
 {
-	bool error;
+	char *result = io_get(io, '\n', TRUE);
 
-	io->buf = io->bufpos = buf;
-	io->bufalloc = bufsize;
-	error = !io_get(io, '\n', TRUE) && io_error(io);
-	io->buf = NULL;
+	if (result) {
+		result = chomp_string(result);
+		string_ncopy_do(buf, bufsize, result, strlen(result));
+	}
 
-	return done_io(io) || error;
+	return done_io(io) && result;
 }
 
 static bool
@@ -747,6 +788,7 @@ run_io_load(const char **argv, const char *separators,
 	REQ_(VIEW_TREE,		"Show tree view"), \
 	REQ_(VIEW_BLOB,		"Show blob view"), \
 	REQ_(VIEW_BLAME,	"Show blame view"), \
+	REQ_(VIEW_BRANCH,	"Show branch view"), \
 	REQ_(VIEW_HELP,		"Show help page"), \
 	REQ_(VIEW_PAGER,	"Show pager view"), \
 	REQ_(VIEW_STATUS,	"Show status view"), \
@@ -792,11 +834,14 @@ run_io_load(const char **argv, const char *separators,
 	REQ_(FIND_PREV,		"Find previous search match"), \
 	\
 	REQ_GROUP("Option manipulation") \
+	REQ_(OPTIONS,		"Open option menu"), \
 	REQ_(TOGGLE_LINENO,	"Toggle line numbers"), \
 	REQ_(TOGGLE_DATE,	"Toggle date display"), \
 	REQ_(TOGGLE_AUTHOR,	"Toggle author display"), \
 	REQ_(TOGGLE_REV_GRAPH,	"Toggle revision graph visualization"), \
 	REQ_(TOGGLE_REFS,	"Toggle reference display (tags/branches)"), \
+	REQ_(TOGGLE_SORT_ORDER,	"Toggle ascending/descending sort order"), \
+	REQ_(TOGGLE_SORT_FIELD,	"Toggle field to sort by"), \
 	\
 	REQ_GROUP("Misc") \
 	REQ_(PROMPT,		"Bring up the prompt"), \
@@ -861,10 +906,11 @@ static bool opt_line_number		= FALSE;
 static bool opt_line_graphics		= TRUE;
 static bool opt_rev_graph		= FALSE;
 static bool opt_show_refs		= TRUE;
-static int opt_num_interval		= NUMBER_INTERVAL;
+static int opt_num_interval		= 5;
 static double opt_hscroll		= 0.50;
-static int opt_tab_size			= TAB_SIZE;
-static int opt_author_cols		= AUTHOR_COLS-1;
+static double opt_scale_split_view	= 2.0 / 3.0;
+static int opt_tab_size			= 8;
+static int opt_author_cols		= 19;
 static char opt_path[SIZEOF_STR]	= "";
 static char opt_file[SIZEOF_STR]	= "";
 static char opt_ref[SIZEOF_REF]		= "";
@@ -1059,6 +1105,7 @@ static const struct keybinding default_keybindings[] = {
 	{ 't',		REQ_VIEW_TREE },
 	{ 'f',		REQ_VIEW_BLOB },
 	{ 'B',		REQ_VIEW_BLAME },
+	{ 'H',		REQ_VIEW_BRANCH },
 	{ 'p',		REQ_VIEW_PAGER },
 	{ 'h',		REQ_VIEW_HELP },
 	{ 'S',		REQ_VIEW_STATUS },
@@ -1104,11 +1151,14 @@ static const struct keybinding default_keybindings[] = {
 	{ 'z',		REQ_STOP_LOADING },
 	{ 'v',		REQ_SHOW_VERSION },
 	{ 'r',		REQ_SCREEN_REDRAW },
+	{ 'o',		REQ_OPTIONS },
 	{ '.',		REQ_TOGGLE_LINENO },
 	{ 'D',		REQ_TOGGLE_DATE },
 	{ 'A',		REQ_TOGGLE_AUTHOR },
 	{ 'g',		REQ_TOGGLE_REV_GRAPH },
 	{ 'F',		REQ_TOGGLE_REFS },
+	{ 'I',		REQ_TOGGLE_SORT_ORDER },
+	{ 'i',		REQ_TOGGLE_SORT_FIELD },
 	{ ':',		REQ_PROMPT },
 	{ 'u',		REQ_STATUS_UPDATE },
 	{ '!',		REQ_STATUS_REVERT },
@@ -1126,6 +1176,7 @@ static const struct keybinding default_keybindings[] = {
 	KEYMAP_(TREE), \
 	KEYMAP_(BLOB), \
 	KEYMAP_(BLAME), \
+	KEYMAP_(BRANCH), \
 	KEYMAP_(PAGER), \
 	KEYMAP_(HELP), \
 	KEYMAP_(STATUS), \
@@ -1293,6 +1344,8 @@ struct run_request {
 static struct run_request *run_request;
 static size_t run_requests;
 
+DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
+
 static enum request
 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
 {
@@ -1301,11 +1354,9 @@ add_run_request(enum keymap keymap, int key, int argc, const char **argv)
 	if (argc >= ARRAY_SIZE(req->argv) - 1)
 		return REQ_NONE;
 
-	req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
-	if (!req)
+	if (!realloc_run_requests(&run_request, run_requests, 1))
 		return REQ_NONE;
 
-	run_request = req;
 	req = &run_request[run_requests];
 	req->keymap = keymap;
 	req->key = key;
@@ -1329,6 +1380,7 @@ static void
 add_builtin_run_requests(void)
 {
 	const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
+	const char *commit[] = { "git", "commit", NULL };
 	const char *gc[] = { "git", "gc", NULL };
 	struct {
 		enum keymap keymap;
@@ -1337,6 +1389,7 @@ add_builtin_run_requests(void)
 		const char **argv;
 	} reqs[] = {
 		{ KEYMAP_MAIN,	  'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
+		{ KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
 		{ KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
 	};
 	int i;
@@ -1435,7 +1488,7 @@ option_color_command(int argc, const char *argv[])
 {
 	struct line_info *info;
 
-	if (argc != 3 && argc != 4) {
+	if (argc < 3) {
 		config_msg = "Wrong number of arguments given to color command";
 		return ERR;
 	}
@@ -1462,9 +1515,15 @@ option_color_command(int argc, const char *argv[])
 		return ERR;
 	}
 
-	if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
-		config_msg = "Unknown attribute";
-		return ERR;
+	info->attr = 0;
+	while (argc-- > 3) {
+		int attr;
+
+		if (!set_attribute(&attr, argv[argc])) {
+			config_msg = "Unknown attribute";
+			return ERR;
+		}
+		info->attr |= attr;
 	}
 
 	return OK;
@@ -1537,6 +1596,9 @@ option_set_command(int argc, const char *argv[])
 	if (!strcmp(argv[0], "horizontal-scroll"))
 		return parse_step(&opt_hscroll, argv[2]);
 
+	if (!strcmp(argv[0], "split-view-height"))
+		return parse_step(&opt_scale_split_view, argv[2]);
+
 	if (!strcmp(argv[0], "tab-size"))
 		return parse_int(&opt_tab_size, argv[2], 1, 1024);
 
@@ -1552,7 +1614,7 @@ static int
 option_bind_command(int argc, const char *argv[])
 {
 	enum request request;
-	int keymap;
+	int keymap = -1;
 	int key;
 
 	if (argc < 3) {
@@ -1763,7 +1825,6 @@ struct view {
 	/* Buffering */
 	size_t lines;		/* Total number of lines */
 	struct line *line;	/* Line index */
-	size_t line_alloc;	/* Total number of allocated lines */
 	unsigned int digits;	/* Number of digits in the lines member. */
 
 	/* Drawing */
@@ -1808,6 +1869,7 @@ static struct view_ops pager_ops;
 static struct view_ops stage_ops;
 static struct view_ops status_ops;
 static struct view_ops tree_ops;
+static struct view_ops branch_ops;
 
 #define VIEW_STR(name, env, ref, ops, map, git) \
 	{ name, #env, ref, ops, map, git }
@@ -1823,6 +1885,7 @@ static struct view views[] = {
 	VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
 	VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
 	VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
+	VIEW_(BRANCH, "branch",	&branch_ops, TRUE,  ref_head),
 	VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
 	VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
 	VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
@@ -1960,15 +2023,9 @@ draw_field(struct view *view, enum line_type type, const char *text, int len, bo
 }
 
 static bool
-draw_date(struct view *view, struct tm *time)
+draw_date(struct view *view, time_t *time)
 {
-	char buf[DATE_COLS];
-	char *date;
-	int timelen = 0;
-
-	if (time)
-		timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
-	date = timelen ? buf : NULL;
+	const char *date = mkdate(time);
 
 	return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
 }
@@ -2171,6 +2228,15 @@ update_view_title(struct view *view)
 	wnoutrefresh(view->title);
 }
 
+static int
+apply_step(double step, int value)
+{
+	if (step >= 1)
+		return (int) step;
+	value *= step + 0.01;
+	return value ? value : 1;
+}
+
 static void
 resize_display(void)
 {
@@ -2188,7 +2254,9 @@ resize_display(void)
 	if (view != base) {
 		/* Horizontal split. */
 		view->width   = base->width;
-		view->height  = SCALE_SPLIT_VIEW(base->height);
+		view->height  = apply_step(opt_scale_split_view, base->height);
+		view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
+		view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
 		base->height -= view->height;
 
 		/* Make room for the title bar. */
@@ -2245,6 +2313,23 @@ toggle_view_option(bool *option, const char *help)
 }
 
 static void
+open_option_menu(void)
+{
+	const struct menu_item menu[] = {
+		{ '.', "line numbers", &opt_line_number },
+		{ 'D', "date display", &opt_date },
+		{ 'A', "author display", &opt_author },
+		{ 'g', "revision graph display", &opt_rev_graph },
+		{ 'F', "reference display", &opt_show_refs },
+		{ 0 }
+	};
+	int selected = 0;
+
+	if (prompt_menu("Toggle option", menu, &selected))
+		toggle_view_option(menu[selected].data, menu[selected].text);
+}
+
+static void
 maximize_view(struct view *view)
 {
 	memset(display, 0, sizeof(display));
@@ -2284,15 +2369,6 @@ goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
 	return FALSE;
 }
 
-static int
-apply_step(double step, int value)
-{
-	if (step >= 1)
-		return (int) step;
-	value *= step + 0.01;
-	return value ? value : 1;
-}
-
 /* Scrolling backend */
 static void
 do_scroll_view(struct view *view, int lines)
@@ -2497,6 +2573,19 @@ move_view(struct view *view, enum request request)
 
 static void search_view(struct view *view, enum request request);
 
+static bool
+grep_text(struct view *view, const char *text[])
+{
+	regmatch_t pmatch;
+	size_t i;
+
+	for (i = 0; text[i]; i++)
+		if (*text[i] &&
+		    regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
+			return TRUE;
+	return FALSE;
+}
+
 static void
 select_view_line(struct view *view, unsigned long lineno)
 {
@@ -2613,7 +2702,6 @@ reset_view(struct view *view)
 	view->yoffset = 0;
 	view->lines  = 0;
 	view->lineno = 0;
-	view->line_alloc = 0;
 	view->vid[0] = 0;
 	view->update_secs = 0;
 }
@@ -2784,36 +2872,6 @@ begin_update(struct view *view, bool refresh)
 	return TRUE;
 }
 
-#define ITEM_CHUNK_SIZE 256
-static void *
-realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
-{
-	size_t num_chunks = *size / ITEM_CHUNK_SIZE;
-	size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
-
-	if (mem == NULL || num_chunks != num_chunks_new) {
-		*size = num_chunks_new * ITEM_CHUNK_SIZE;
-		mem = realloc(mem, *size * item_size);
-	}
-
-	return mem;
-}
-
-static struct line *
-realloc_lines(struct view *view, size_t line_size)
-{
-	size_t alloc = view->line_alloc;
-	struct line *tmp = realloc_items(view->line, &alloc, line_size,
-					 sizeof(*view->line));
-
-	if (!tmp)
-		return NULL;
-
-	view->line = tmp;
-	view->line_alloc = alloc;
-	return view->line;
-}
-
 static bool
 update_view(struct view *view)
 {
@@ -2904,12 +2962,14 @@ update_view(struct view *view)
 	return TRUE;
 }
 
+DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
+
 static struct line *
 add_line_data(struct view *view, void *data, enum line_type type)
 {
 	struct line *line;
 
-	if (!realloc_lines(view, view->lines + 1))
+	if (!realloc_lines(&view->line, view->lines, 1))
 		return NULL;
 
 	line = &view->line[view->lines++];
@@ -3110,6 +3170,7 @@ view_driver(struct view *view, enum request request)
 		if (view == VIEW(REQ_VIEW_STATUS) ||
 		    view == VIEW(REQ_VIEW_MAIN) ||
 		    view == VIEW(REQ_VIEW_LOG) ||
+		    view == VIEW(REQ_VIEW_BRANCH) ||
 		    view == VIEW(REQ_VIEW_STAGE))
 			request = REQ_REFRESH;
 		else
@@ -3190,6 +3251,7 @@ view_driver(struct view *view, enum request request)
 	case REQ_VIEW_LOG:
 	case REQ_VIEW_TREE:
 	case REQ_VIEW_HELP:
+	case REQ_VIEW_BRANCH:
 		open_view(view, request, OPEN_DEFAULT);
 		break;
 
@@ -3204,7 +3266,9 @@ view_driver(struct view *view, enum request request)
 		   (view == VIEW(REQ_VIEW_STAGE) &&
 		     view->parent == VIEW(REQ_VIEW_STATUS)) ||
 		   (view == VIEW(REQ_VIEW_BLOB) &&
-		     view->parent == VIEW(REQ_VIEW_TREE))) {
+		     view->parent == VIEW(REQ_VIEW_TREE)) ||
+		   (view == VIEW(REQ_VIEW_MAIN) &&
+		     view->parent == VIEW(REQ_VIEW_BRANCH))) {
 			int line;
 
 			view = view->parent;
@@ -3246,6 +3310,10 @@ view_driver(struct view *view, enum request request)
 			maximize_view(view);
 		break;
 
+	case REQ_OPTIONS:
+		open_option_menu();
+		break;
+
 	case REQ_TOGGLE_LINENO:
 		toggle_view_option(&opt_line_number, "line numbers");
 		break;
@@ -3266,6 +3334,11 @@ view_driver(struct view *view, enum request request)
 		toggle_view_option(&opt_show_refs, "reference display");
 		break;
 
+	case REQ_TOGGLE_SORT_FIELD:
+	case REQ_TOGGLE_SORT_ORDER:
+		report("Sorting is not yet supported for the %s view", view->name);
+		break;
+
 	case REQ_SEARCH:
 	case REQ_SEARCH_BACK:
 		search_view(view, request);
@@ -3328,6 +3401,80 @@ view_driver(struct view *view, enum request request)
  * View backend utilities
  */
 
+enum sort_field {
+	ORDERBY_NAME,
+	ORDERBY_DATE,
+	ORDERBY_AUTHOR,
+};
+
+struct sort_state {
+	const enum sort_field *fields;
+	size_t size, current;
+	bool reverse;
+};
+
+#define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
+#define get_sort_field(state) ((state).fields[(state).current])
+#define sort_order(state, result) ((state).reverse ? -(result) : (result))
+
+static void
+sort_view(struct view *view, enum request request, struct sort_state *state,
+	  int (*compare)(const void *, const void *))
+{
+	switch (request) {
+	case REQ_TOGGLE_SORT_FIELD:
+		state->current = (state->current + 1) % state->size;
+		break;
+
+	case REQ_TOGGLE_SORT_ORDER:
+		state->reverse = !state->reverse;
+		break;
+	default:
+		die("Not a sort request");
+	}
+
+	qsort(view->line, view->lines, sizeof(*view->line), compare);
+	redraw_view(view);
+}
+
+DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
+
+/* Small author cache to reduce memory consumption. It uses binary
+ * search to lookup or find place to position new entries. No entries
+ * are ever freed. */
+static const char *
+get_author(const char *name)
+{
+	static const char **authors;
+	static size_t authors_size;
+	int from = 0, to = authors_size - 1;
+
+	while (from <= to) {
+		size_t pos = (to + from) / 2;
+		int cmp = strcmp(name, authors[pos]);
+
+		if (!cmp)
+			return authors[pos];
+
+		if (cmp < 0)
+			to = pos - 1;
+		else
+			from = pos + 1;
+	}
+
+	if (!realloc_authors(&authors, authors_size, 1))
+		return NULL;
+	name = strdup(name);
+	if (!name)
+		return NULL;
+
+	memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
+	authors[from] = name;
+	authors_size++;
+
+	return name;
+}
+
 static void
 parse_timezone(time_t *time, const char *zone)
 {
@@ -3348,7 +3495,7 @@ parse_timezone(time_t *time, const char *zone)
  *	author  <email@address.tld> 1138474660 +0100
  */
 static void
-parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
+parse_author_line(char *ident, const char **author, time_t *time)
 {
 	char *nameend = strchr(ident, '<');
 	char *emailend = strchr(ident, '>');
@@ -3363,37 +3510,53 @@ parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
 			ident = "Unknown";
 	}
 
-	string_ncopy_do(author, authorsize, ident, strlen(ident));
+	*author = get_author(ident);
 
 	/* Parse epoch and timezone */
 	if (emailend && emailend[1] == ' ') {
 		char *secs = emailend + 2;
 		char *zone = strchr(secs, ' ');
-		time_t time = (time_t) atol(secs);
 
-		if (zone && strlen(zone) == STRING_SIZE(" +0700"))
-			parse_timezone(&time, zone + 1);
+		*time = (time_t) atol(secs);
 
-		gmtime_r(&time, tm);
+		if (zone && strlen(zone) == STRING_SIZE(" +0700"))
+			parse_timezone(time, zone + 1);
 	}
 }
 
-static enum input_status
-select_commit_parent_handler(void *data, char *buf, int c)
+static bool
+open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
 {
-	size_t parents = *(size_t *) data;
-	int parent = 0;
+	char rev[SIZEOF_REV];
+	const char *revlist_argv[] = {
+		"git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
+	};
+	struct menu_item *items;
+	char text[SIZEOF_STR];
+	bool ok = TRUE;
+	int i;
 
-	if (!isdigit(c))
-		return INPUT_SKIP;
+	items = calloc(*parents + 1, sizeof(*items));
+	if (!items)
+		return FALSE;
 
-	if (*buf)
-		parent = atoi(buf) * 10;
-	parent += c - '0';
+	for (i = 0; i < *parents; i++) {
+		string_copy_rev(rev, &buf[SIZEOF_REV * i]);
+		if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
+		    !(items[i].text = strdup(text))) {
+			ok = FALSE;
+			break;
+		}
+	}
 
-	if (parent > parents)
-		return INPUT_SKIP;
-	return INPUT_OK;
+	if (ok) {
+		*parents = 0;
+		ok = prompt_menu("Select parent", items, parents);
+	}
+	for (i = 0; items[i].text; i++)
+		free((char *) items[i].text);
+	free(items);
+	return ok;
 }
 
 static bool
@@ -3401,13 +3564,13 @@ select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
 {
 	char buf[SIZEOF_STR * 4];
 	const char *revlist_argv[] = {
-		"git", "rev-list", "-1", "--parents", id, "--", path, NULL
+		"git", "log", "--no-color", "-1",
+			"--pretty=format:%P", id, "--", path, NULL
 	};
 	int parents;
 
 	if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
-	    !*chomp_string(buf) ||
-	    (parents = (strlen(buf) / 40) - 1) < 0) {
+	    (parents = strlen(buf) / 40) < 0) {
 		report("Failed to get parent information");
 		return FALSE;
 
@@ -3419,17 +3582,8 @@ select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
 		return FALSE;
 	}
 
-	if (parents > 1) {
-		char prompt[SIZEOF_STR];
-		char *result;
-
-		if (!string_format(prompt, "Which parent? [1..%d] ", parents))
-			return FALSE;
-		result = prompt_input(prompt, select_commit_parent_handler, &parents);
-		if (!result)
-			return FALSE;
-		parents = atoi(result);
-	}
+	if (parents > 1 && !open_commit_parent_menu(buf, &parents))
+		return FALSE;
 
 	string_copy_rev(rev, &buf[41 * parents]);
 	return TRUE;
@@ -3456,13 +3610,9 @@ static bool
 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
 {
 	const char *describe_argv[] = { "git", "describe", commit_id, NULL };
-	char refbuf[SIZEOF_STR];
-	char *ref = NULL;
+	char ref[SIZEOF_STR];
 
-	if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
-		ref = chomp_string(refbuf);
-
-	if (!ref || !*ref)
+	if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
 		return TRUE;
 
 	/* This is the only fatal call, since it can "corrupt" the buffer. */
@@ -3477,22 +3627,22 @@ add_pager_refs(struct view *view, struct line *line)
 {
 	char buf[SIZEOF_STR];
 	char *commit_id = (char *)line->data + STRING_SIZE("commit ");
-	struct ref **refs;
-	size_t bufpos = 0, refpos = 0;
+	struct ref_list *list;
+	size_t bufpos = 0, i;
 	const char *sep = "Refs: ";
 	bool is_tag = FALSE;
 
 	assert(line->type == LINE_COMMIT);
 
-	refs = get_refs(commit_id);
-	if (!refs) {
+	list = get_ref_list(commit_id);
+	if (!list) {
 		if (view == VIEW(REQ_VIEW_DIFF))
 			goto try_add_describe_ref;
 		return;
 	}
 
-	do {
-		struct ref *ref = refs[refpos];
+	for (i = 0; i < list->size; i++) {
+		struct ref *ref = list->refs[i];
 		const char *fmt = ref->tag    ? "%s[%s]" :
 		                  ref->remote ? "%s<%s>" : "%s%s";
 
@@ -3501,7 +3651,7 @@ add_pager_refs(struct view *view, struct line *line)
 		sep = ", ";
 		if (ref->tag)
 			is_tag = TRUE;
-	} while (refs[refpos++]->next);
+	}
 
 	if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
 try_add_describe_ref:
@@ -3568,16 +3718,9 @@ pager_request(struct view *view, enum request request, struct line *line)
 static bool
 pager_grep(struct view *view, struct line *line)
 {
-	regmatch_t pmatch;
-	char *text = line->data;
-
-	if (!*text)
-		return FALSE;
+	const char *text[] = { line->data, NULL };
 
-	if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
-		return FALSE;
-
-	return TRUE;
+	return grep_text(view, text);
 }
 
 static void
@@ -3795,26 +3938,57 @@ push_tree_stack_entry(const char *name, unsigned long lineno)
 struct tree_entry {
 	char id[SIZEOF_REV];
 	mode_t mode;
-	struct tm time;			/* Date from the author ident. */
-	char author[75];		/* Author of the commit. */
+	time_t time;			/* Date from the author ident. */
+	const char *author;		/* Author of the commit. */
 	char name[1];
 };
 
 static const char *
-tree_path(struct line *line)
+tree_path(const struct line *line)
 {
 	return ((struct tree_entry *) line->data)->name;
 }
 
-
 static int
-tree_compare_entry(struct line *line1, struct line *line2)
+tree_compare_entry(const struct line *line1, const struct line *line2)
 {
 	if (line1->type != line2->type)
 		return line1->type == LINE_TREE_DIR ? -1 : 1;
 	return strcmp(tree_path(line1), tree_path(line2));
 }
 
+static const enum sort_field tree_sort_fields[] = {
+	ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
+};
+static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
+
+static int
+tree_compare(const void *l1, const void *l2)
+{
+	const struct line *line1 = (const struct line *) l1;
+	const struct line *line2 = (const struct line *) l2;
+	const struct tree_entry *entry1 = ((const struct line *) l1)->data;
+	const struct tree_entry *entry2 = ((const struct line *) l2)->data;
+
+	if (line1->type == LINE_TREE_HEAD)
+		return -1;
+	if (line2->type == LINE_TREE_HEAD)
+		return 1;
+
+	switch (get_sort_field(tree_sort_state)) {
+	case ORDERBY_DATE:
+		return sort_order(tree_sort_state, entry1->time - entry2->time);
+
+	case ORDERBY_AUTHOR:
+		return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
+
+	case ORDERBY_NAME:
+	default:
+		return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
+	}
+}
+
+
 static struct line *
 tree_entry(struct view *view, enum line_type type, const char *path,
 	   const char *mode, const char *id)
@@ -3839,8 +4013,8 @@ tree_entry(struct view *view, enum line_type type, const char *path,
 static bool
 tree_read_date(struct view *view, char *text, bool *read_date)
 {
-	static char author_name[SIZEOF_STR];
-	static struct tm author_time;
+	static const char *author_name;
+	static time_t author_time;
 
 	if (!text && *read_date) {
 		*read_date = FALSE;
@@ -3873,7 +4047,7 @@ tree_read_date(struct view *view, char *text, bool *read_date)
 
 	} else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
 		parse_author_line(text + STRING_SIZE("author "),
-				  author_name, sizeof(author_name), &author_time);
+				  &author_name, &author_time);
 
 	} else if (*text == ':') {
 		char *pos;
@@ -3896,12 +4070,12 @@ tree_read_date(struct view *view, char *text, bool *read_date)
 			struct line *line = &view->line[i];
 			struct tree_entry *entry = line->data;
 
-			annotated += !!*entry->author;
-			if (*entry->author || strcmp(entry->name, text))
+			annotated += !!entry->author;
+			if (entry->author || strcmp(entry->name, text))
 				continue;
 
-			string_copy(entry->author, author_name);
-			memcpy(&entry->time, &author_time, sizeof(entry->time));
+			entry->author = author_name;
+			entry->time = author_time;
 			line->dirty = 1;
 			break;
 		}
@@ -3989,7 +4163,7 @@ tree_draw(struct view *view, struct line *line, unsigned int lineno)
 		if (opt_author && draw_author(view, entry->author))
 			return TRUE;
 
-		if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
+		if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
 			return TRUE;
 	}
 	if (draw_text(view, line->type, entry->name, TRUE))
@@ -4038,6 +4212,11 @@ tree_request(struct view *view, enum request request, struct line *line)
 		}
 		return REQ_NONE;
 
+	case REQ_TOGGLE_SORT_FIELD:
+	case REQ_TOGGLE_SORT_ORDER:
+		sort_view(view, request, &tree_sort_state, tree_compare);
+		return REQ_NONE;
+
 	case REQ_PARENT:
 		if (!*opt_path) {
 			/* quit view if at top of tree */
@@ -4093,6 +4272,20 @@ tree_request(struct view *view, enum request request, struct line *line)
 	return REQ_NONE;
 }
 
+static bool
+tree_grep(struct view *view, struct line *line)
+{
+	struct tree_entry *entry = line->data;
+	const char *text[] = {
+		entry->name,
+		opt_author ? entry->author : "",
+		opt_date ? mkdate(&entry->time) : "",
+		NULL
+	};
+
+	return grep_text(view, text);
+}
+
 static void
 tree_select(struct view *view, struct line *line)
 {
@@ -4120,7 +4313,7 @@ static struct view_ops tree_ops = {
 	tree_read,
 	tree_draw,
 	tree_request,
-	pager_grep,
+	tree_grep,
 	tree_select,
 };
 
@@ -4185,8 +4378,8 @@ static const char *blame_cat_file_argv[] = {
 struct blame_commit {
 	char id[SIZEOF_REV];		/* SHA1 ID. */
 	char title[128];		/* First line of the commit message. */
-	char author[75];		/* Author of the commit. */
-	struct tm time;			/* Date from the author ident. */
+	const char *author;		/* Author of the commit. */
+	time_t time;			/* Date from the author ident. */
 	char filename[128];		/* Name of file. */
 	bool has_previous;		/* Was a "previous" line detected. */
 };
@@ -4338,7 +4531,6 @@ blame_read(struct view *view, char *line)
 {
 	static struct blame_commit *commit = NULL;
 	static int blamed = 0;
-	static time_t author_time;
 	static bool read_file = TRUE;
 
 	if (read_file)
@@ -4363,14 +4555,13 @@ blame_read(struct view *view, char *line)
 			      view->lines ? blamed * 100 / view->lines : 0);
 
 	} else if (match_blame_header("author ", &line)) {
-		string_ncopy(commit->author, line, strlen(line));
+		commit->author = get_author(line);
 
 	} else if (match_blame_header("author-time ", &line)) {
-		author_time = (time_t) atol(line);
+		commit->time = (time_t) atol(line);
 
 	} else if (match_blame_header("author-tz ", &line)) {
-		parse_timezone(&author_time, line);
-		gmtime_r(&author_time, &commit->time);
+		parse_timezone(&commit->time, line);
 
 	} else if (match_blame_header("summary ", &line)) {
 		string_ncopy(commit->title, line, strlen(line));
@@ -4390,7 +4581,7 @@ static bool
 blame_draw(struct view *view, struct line *line, unsigned int lineno)
 {
 	struct blame *blame = line->data;
-	struct tm *time = NULL;
+	time_t *time = NULL;
 	const char *id = NULL, *author = NULL;
 	char text[SIZEOF_STR];
 
@@ -4538,27 +4729,16 @@ blame_grep(struct view *view, struct line *line)
 {
 	struct blame *blame = line->data;
 	struct blame_commit *commit = blame->commit;
-	regmatch_t pmatch;
-
-#define MATCH(text, on)							\
-	(on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
-
-	if (commit) {
-		char buf[DATE_COLS + 1];
-
-		if (MATCH(commit->title, 1) ||
-		    MATCH(commit->author, opt_author) ||
-		    MATCH(commit->id, opt_date))
-			return TRUE;
-
-		if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
-		    MATCH(buf, 1))
-			return TRUE;
-	}
-
-	return MATCH(blame->text, 1);
+	const char *text[] = {
+		blame->text,
+		commit ? commit->title : "",
+		commit ? commit->id : "",
+		commit && opt_author ? commit->author : "",
+		commit && opt_date ? mkdate(&commit->time) : "",
+		NULL
+	};
 
-#undef MATCH
+	return grep_text(view, text);
 }
 
 static void
@@ -4588,6 +4768,191 @@ static struct view_ops blame_ops = {
 };
 
 /*
+ * Branch backend
+ */
+
+struct branch {
+	const char *author;		/* Author of the last commit. */
+	time_t time;			/* Date of the last activity. */
+	struct ref *ref;		/* Name and commit ID information. */
+};
+
+static const enum sort_field branch_sort_fields[] = {
+	ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
+};
+static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
+
+static int
+branch_compare(const void *l1, const void *l2)
+{
+	const struct branch *branch1 = ((const struct line *) l1)->data;
+	const struct branch *branch2 = ((const struct line *) l2)->data;
+
+	switch (get_sort_field(branch_sort_state)) {
+	case ORDERBY_DATE:
+		return sort_order(branch_sort_state, branch1->time - branch2->time);
+
+	case ORDERBY_AUTHOR:
+		return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
+
+	case ORDERBY_NAME:
+	default:
+		return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
+	}
+}
+
+static bool
+branch_draw(struct view *view, struct line *line, unsigned int lineno)
+{
+	struct branch *branch = line->data;
+	enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
+
+	if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
+		return TRUE;
+
+	if (opt_author && draw_author(view, branch->author))
+		return TRUE;
+
+	draw_text(view, type, branch->ref->name, TRUE);
+	return TRUE;
+}
+
+static enum request
+branch_request(struct view *view, enum request request, struct line *line)
+{
+	switch (request) {
+	case REQ_REFRESH:
+		load_refs();
+		open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
+		return REQ_NONE;
+
+	case REQ_TOGGLE_SORT_FIELD:
+	case REQ_TOGGLE_SORT_ORDER:
+		sort_view(view, request, &branch_sort_state, branch_compare);
+		return REQ_NONE;
+
+	case REQ_ENTER:
+		open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
+		return REQ_NONE;
+
+	default:
+		return request;
+	}
+}
+
+static bool
+branch_read(struct view *view, char *line)
+{
+	static char id[SIZEOF_REV];
+	struct branch *reference;
+	size_t i;
+
+	if (!line)
+		return TRUE;
+
+	switch (get_line_type(line)) {
+	case LINE_COMMIT:
+		string_copy_rev(id, line + STRING_SIZE("commit "));
+		return TRUE;
+
+	case LINE_AUTHOR:
+		for (i = 0, reference = NULL; i < view->lines; i++) {
+			struct branch *branch = view->line[i].data;
+
+			if (strcmp(branch->ref->id, id))
+				continue;
+
+			view->line[i].dirty = TRUE;
+			if (reference) {
+				branch->author = reference->author;
+				branch->time = reference->time;
+				continue;
+			}
+
+			parse_author_line(line + STRING_SIZE("author "),
+					  &branch->author, &branch->time);
+			reference = branch;
+		}
+		return TRUE;
+
+	default:
+		return TRUE;
+	}
+
+}
+
+static bool
+branch_open_visitor(void *data, struct ref *ref)
+{
+	struct view *view = data;
+	struct branch *branch;
+
+	if (ref->tag || ref->ltag || ref->remote)
+		return TRUE;
+
+	branch = calloc(1, sizeof(*branch));
+	if (!branch)
+		return FALSE;
+
+	branch->ref = ref;
+	return !!add_line_data(view, branch, LINE_DEFAULT);
+}
+
+static bool
+branch_open(struct view *view)
+{
+	const char *branch_log[] = {
+		"git", "log", "--no-color", "--pretty=raw",
+			"--simplify-by-decoration", "--all", NULL
+	};
+
+	if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
+		report("Failed to load branch data");
+		return TRUE;
+	}
+
+	setup_update(view, view->id);
+	foreach_ref(branch_open_visitor, view);
+	view->p_restore = TRUE;
+
+	return TRUE;
+}
+
+static bool
+branch_grep(struct view *view, struct line *line)
+{
+	struct branch *branch = line->data;
+	const char *text[] = {
+		branch->ref->name,
+		branch->author,
+		NULL
+	};
+
+	return grep_text(view, text);
+}
+
+static void
+branch_select(struct view *view, struct line *line)
+{
+	struct branch *branch = line->data;
+
+	string_copy_rev(view->ref, branch->ref->id);
+	string_copy_rev(ref_commit, branch->ref->id);
+	string_copy_rev(ref_head, branch->ref->id);
+}
+
+static struct view_ops branch_ops = {
+	"branch",
+	NULL,
+	branch_open,
+	branch_read,
+	branch_draw,
+	branch_request,
+	branch_grep,
+	branch_select,
+};
+
+/*
  * Status backend
  */
 
@@ -4611,6 +4976,8 @@ static enum line_type stage_line_type;
 static size_t stage_chunks;
 static int *stage_chunk;
 
+DEFINE_ALLOCATOR(realloc_ints, int, 32)
+
 /* This should work even for the "On branch" line. */
 static inline bool
 status_has_none(struct view *view, struct line *line)
@@ -4811,7 +5178,7 @@ status_update_onbranch(void)
 			if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
 			    io_open(&io, buf) &&
 			    io_read_buf(&io, buf, sizeof(buf))) {
-				head = chomp_string(buf);
+				head = buf;
 				if (!prefixcmp(head, "refs/heads/"))
 					head += STRING_SIZE("refs/heads/");
 			}
@@ -5118,7 +5485,7 @@ status_update_files(struct view *view, struct line *line)
 	struct line *pos = view->line + view->lines;
 	int files = 0;
 	int file, done;
-	int cursor_y, cursor_x;
+	int cursor_y = -1, cursor_x = -1;
 
 	if (!status_update_prepare(&io, line->type))
 		return FALSE;
@@ -5318,29 +5685,12 @@ static bool
 status_grep(struct view *view, struct line *line)
 {
 	struct status *status = line->data;
-	enum { S_STATUS, S_NAME, S_END } state;
-	char buf[2] = "?";
-	regmatch_t pmatch;
-
-	if (!status)
-		return FALSE;
-
-	for (state = S_STATUS; state < S_END; state++) {
-		const char *text;
 
-		switch (state) {
-		case S_NAME:	text = status->new.name;	break;
-		case S_STATUS:
-			buf[0] = status->status;
-			text = buf;
-			break;
+	if (status) {
+		const char buf[2] = { status->status, 0 };
+		const char *text[] = { status->new.name, buf, NULL };
 
-		default:
-			return FALSE;
-		}
-
-		if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
-			return TRUE;
+		return grep_text(view, text);
 	}
 
 	return FALSE;
@@ -5482,21 +5832,15 @@ stage_next(struct view *view, struct line *line)
 	int i;
 
 	if (!stage_chunks) {
-		static size_t alloc = 0;
-		int *tmp;
-
 		for (line = view->line; line < view->line + view->lines; line++) {
 			if (line->type != LINE_DIFF_CHUNK)
 				continue;
 
-			tmp = realloc_items(stage_chunk, &alloc,
-					    stage_chunks, sizeof(*tmp));
-			if (!tmp) {
+			if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
 				report("Allocation failure");
 				return;
 			}
 
-			stage_chunk = tmp;
 			stage_chunk[stage_chunks++] = line - view->line;
 		}
 	}
@@ -5609,9 +5953,9 @@ static struct view_ops stage_ops = {
 struct commit {
 	char id[SIZEOF_REV];		/* SHA1 ID. */
 	char title[128];		/* First line of the commit message. */
-	char author[75];		/* Author of the commit. */
-	struct tm time;			/* Date from the author ident. */
-	struct ref **refs;		/* Repository references. */
+	const char *author;		/* Author of the commit. */
+	time_t time;			/* Date from the author ident. */
+	struct ref_list *refs;		/* Repository references. */
 	chtype graph[SIZEOF_REVGRAPH];	/* Ancestry chain graphics. */
 	size_t graph_size;		/* The width of the graph array. */
 	bool has_parents;		/* Rewritten --parents seen. */
@@ -5836,7 +6180,7 @@ main_draw(struct view *view, struct line *line, unsigned int lineno)
 {
 	struct commit *commit = line->data;
 
-	if (!*commit->author)
+	if (!commit->author)
 		return FALSE;
 
 	if (opt_date && draw_date(view, &commit->time))
@@ -5850,32 +6194,33 @@ main_draw(struct view *view, struct line *line, unsigned int lineno)
 		return TRUE;
 
 	if (opt_show_refs && commit->refs) {
-		size_t i = 0;
+		size_t i;
 
-		do {
+		for (i = 0; i < commit->refs->size; i++) {
+			struct ref *ref = commit->refs->refs[i];
 			enum line_type type;
 
-			if (commit->refs[i]->head)
+			if (ref->head)
 				type = LINE_MAIN_HEAD;
-			else if (commit->refs[i]->ltag)
+			else if (ref->ltag)
 				type = LINE_MAIN_LOCAL_TAG;
-			else if (commit->refs[i]->tag)
+			else if (ref->tag)
 				type = LINE_MAIN_TAG;
-			else if (commit->refs[i]->tracked)
+			else if (ref->tracked)
 				type = LINE_MAIN_TRACKED;
-			else if (commit->refs[i]->remote)
+			else if (ref->remote)
 				type = LINE_MAIN_REMOTE;
 			else
 				type = LINE_MAIN_REF;
 
 			if (draw_text(view, type, "[", TRUE) ||
-			    draw_text(view, type, commit->refs[i]->name, TRUE) ||
+			    draw_text(view, type, ref->name, TRUE) ||
 			    draw_text(view, type, "]", TRUE))
 				return TRUE;
 
 			if (draw_text(view, LINE_DEFAULT, " ", TRUE))
 				return TRUE;
-		} while (commit->refs[i++]->next);
+		}
 	}
 
 	draw_text(view, LINE_DEFAULT, commit->title, TRUE);
@@ -5898,7 +6243,7 @@ main_read(struct view *view, char *line)
 		if (view->lines > 0) {
 			commit = view->line[view->lines - 1].data;
 			view->line[view->lines - 1].dirty = 1;
-			if (!*commit->author) {
+			if (!commit->author) {
 				view->lines--;
 				free(commit);
 				graph->commit = NULL;
@@ -5924,7 +6269,7 @@ main_read(struct view *view, char *line)
 		}
 
 		string_copy_rev(commit->id, line);
-		commit->refs = get_refs(commit->id);
+		commit->refs = get_ref_list(commit->id);
 		graph->commit = commit;
 		add_line_data(view, commit, LINE_MAIN_COMMIT);
 
@@ -5949,8 +6294,7 @@ main_read(struct view *view, char *line)
 
 	case LINE_AUTHOR:
 		parse_author_line(line + STRING_SIZE("author "),
-				  commit->author, sizeof(commit->author),
-				  &commit->time);
+				  &commit->author, &commit->time);
 		update_rev_graph(view, graph);
 		graph = graph->next;
 		break;
@@ -6002,17 +6346,18 @@ main_request(struct view *view, enum request request, struct line *line)
 }
 
 static bool
-grep_refs(struct ref **refs, regex_t *regex)
+grep_refs(struct ref_list *list, regex_t *regex)
 {
 	regmatch_t pmatch;
-	size_t i = 0;
+	size_t i;
 
-	if (!refs)
+	if (!opt_show_refs || !list)
 		return FALSE;
-	do {
-		if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
+
+	for (i = 0; i < list->size; i++) {
+		if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
 			return TRUE;
-	} while (refs[i++]->next);
+	}
 
 	return FALSE;
 }
@@ -6021,42 +6366,14 @@ static bool
 main_grep(struct view *view, struct line *line)
 {
 	struct commit *commit = line->data;
-	enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
-	char buf[DATE_COLS + 1];
-	regmatch_t pmatch;
-
-	for (state = S_TITLE; state < S_END; state++) {
-		char *text;
-
-		switch (state) {
-		case S_TITLE:	text = commit->title;	break;
-		case S_AUTHOR:
-			if (!opt_author)
-				continue;
-			text = commit->author;
-			break;
-		case S_DATE:
-			if (!opt_date)
-				continue;
-			if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
-				continue;
-			text = buf;
-			break;
-		case S_REFS:
-			if (!opt_show_refs)
-				continue;
-			if (grep_refs(commit->refs, view->regex) == TRUE)
-				return TRUE;
-			continue;
-		default:
-			return FALSE;
-		}
-
-		if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
-			return TRUE;
-	}
+	const char *text[] = {
+		commit->title,
+		opt_author ? commit->author : "",
+		opt_date ? mkdate(&commit->time) : "",
+		NULL
+	};
 
-	return FALSE;
+	return grep_text(view, text) || grep_refs(commit->refs, view->regex);
 }
 
 static void
@@ -6522,18 +6839,80 @@ read_prompt(const char *prompt)
 	return prompt_input(prompt, read_prompt_handler, NULL);
 }
 
+static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
+{
+	enum input_status status = INPUT_OK;
+	int size = 0;
+
+	while (items[size].text)
+		size++;
+
+	while (status == INPUT_OK) {
+		const struct menu_item *item = &items[*selected];
+		int key;
+		int i;
+
+		mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
+			  prompt, *selected + 1, size);
+		if (item->hotkey)
+			wprintw(status_win, "[%c] ", (char) item->hotkey);
+		wprintw(status_win, "%s", item->text);
+		wclrtoeol(status_win);
+
+		key = get_input(COLS - 1);
+		switch (key) {
+		case KEY_RETURN:
+		case KEY_ENTER:
+		case '\n':
+			status = INPUT_STOP;
+			break;
+
+		case KEY_LEFT:
+		case KEY_UP:
+			*selected = *selected - 1;
+			if (*selected < 0)
+				*selected = size - 1;
+			break;
+
+		case KEY_RIGHT:
+		case KEY_DOWN:
+			*selected = (*selected + 1) % size;
+			break;
+
+		case KEY_ESC:
+			status = INPUT_CANCEL;
+			break;
+
+		default:
+			for (i = 0; items[i].text; i++)
+				if (items[i].hotkey == key) {
+					*selected = i;
+					status = INPUT_STOP;
+					break;
+				}
+		}
+	}
+
+	/* Clear the status window */
+	status_empty = FALSE;
+	report("");
+
+	return status != INPUT_CANCEL;
+}
+
 /*
  * Repository properties
  */
 
-static struct ref *refs = NULL;
-static size_t refs_alloc = 0;
+static struct ref **refs = NULL;
 static size_t refs_size = 0;
 
-/* Id <-> ref store */
-static struct ref ***id_refs = NULL;
-static size_t id_refs_alloc = 0;
-static size_t id_refs_size = 0;
+static struct ref_list **ref_lists = NULL;
+static size_t ref_lists_size = 0;
+
+DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
+DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
+DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
 
 static int
 compare_refs(const void *ref1_, const void *ref2_)
@@ -6554,76 +6933,63 @@ compare_refs(const void *ref1_, const void *ref2_)
 	return strcmp(ref1->name, ref2->name);
 }
 
-static struct ref **
-get_refs(const char *id)
+static void
+foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
 {
-	struct ref ***tmp_id_refs;
-	struct ref **ref_list = NULL;
-	size_t ref_list_alloc = 0;
-	size_t ref_list_size = 0;
 	size_t i;
 
-	for (i = 0; i < id_refs_size; i++)
-		if (!strcmp(id, id_refs[i][0]->id))
-			return id_refs[i];
-
-	tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
-				    sizeof(*id_refs));
-	if (!tmp_id_refs)
-		return NULL;
-
-	id_refs = tmp_id_refs;
-
-	for (i = 0; i < refs_size; i++) {
-		struct ref **tmp;
+	for (i = 0; i < refs_size; i++)
+		if (!visitor(data, refs[i]))
+			break;
+}
 
-		if (strcmp(id, refs[i].id))
-			continue;
+static struct ref_list *
+get_ref_list(const char *id)
+{
+	struct ref_list *list;
+	size_t i;
 
-		tmp = realloc_items(ref_list, &ref_list_alloc,
-				    ref_list_size + 1, sizeof(*ref_list));
-		if (!tmp) {
-			if (ref_list)
-				free(ref_list);
-			return NULL;
-		}
+	for (i = 0; i < ref_lists_size; i++)
+		if (!strcmp(id, ref_lists[i]->id))
+			return ref_lists[i];
 
-		ref_list = tmp;
-		ref_list[ref_list_size] = &refs[i];
-		/* XXX: The properties of the commit chains ensures that we can
-		 * safely modify the shared ref. The repo references will
-		 * always be similar for the same id. */
-		ref_list[ref_list_size]->next = 1;
+	if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
+		return NULL;
+	list = calloc(1, sizeof(*list));
+	if (!list)
+		return NULL;
 
-		ref_list_size++;
+	for (i = 0; i < refs_size; i++) {
+		if (!strcmp(id, refs[i]->id) &&
+		    realloc_refs_list(&list->refs, list->size, 1))
+			list->refs[list->size++] = refs[i];
 	}
 
-	if (ref_list) {
-		qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
-		ref_list[ref_list_size - 1]->next = 0;
-		id_refs[id_refs_size++] = ref_list;
+	if (!list->refs) {
+		free(list);
+		return NULL;
 	}
 
-	return ref_list;
+	qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
+	ref_lists[ref_lists_size++] = list;
+	return list;
 }
 
 static int
 read_ref(char *id, size_t idlen, char *name, size_t namelen)
 {
-	struct ref *ref;
+	struct ref *ref = NULL;
 	bool tag = FALSE;
 	bool ltag = FALSE;
 	bool remote = FALSE;
 	bool tracked = FALSE;
-	bool check_replace = FALSE;
 	bool head = FALSE;
+	int from = 0, to = refs_size - 1;
 
 	if (!prefixcmp(name, "refs/tags/")) {
 		if (!suffixcmp(name, namelen, "^{}")) {
 			namelen -= 3;
 			name[namelen] = 0;
-			if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
-				check_replace = TRUE;
 		} else {
 			ltag = TRUE;
 		}
@@ -6648,27 +7014,38 @@ read_ref(char *id, size_t idlen, char *name, size_t namelen)
 		return OK;
 	}
 
-	if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
-		/* it's an annotated tag, replace the previous SHA1 with the
-		 * resolved commit id; relies on the fact git-ls-remote lists
-		 * the commit id of an annotated tag right before the commit id
-		 * it points to. */
-		refs[refs_size - 1].ltag = ltag;
-		string_copy_rev(refs[refs_size - 1].id, id);
+	/* If we are reloading or it's an annotated tag, replace the
+	 * previous SHA1 with the resolved commit id; relies on the fact
+	 * git-ls-remote lists the commit id of an annotated tag right
+	 * before the commit id it points to. */
+	while (from <= to) {
+		size_t pos = (to + from) / 2;
+		int cmp = strcmp(name, refs[pos]->name);
 
-		return OK;
+		if (!cmp) {
+			ref = refs[pos];
+			break;
+		}
+
+		if (cmp < 0)
+			to = pos - 1;
+		else
+			from = pos + 1;
 	}
-	refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
-	if (!refs)
-		return ERR;
 
-	ref = &refs[refs_size++];
-	ref->name = malloc(namelen + 1);
-	if (!ref->name)
-		return ERR;
+	if (!ref) {
+		if (!realloc_refs(&refs, refs_size, 1))
+			return ERR;
+		ref = calloc(1, sizeof(*ref) + namelen);
+		if (!ref)
+			return ERR;
+		memmove(refs + from + 1, refs + from,
+			(refs_size - from) * sizeof(*refs));
+		refs[from] = ref;
+		strncpy(ref->name, name, namelen);
+		refs_size++;
+	}
 
-	strncpy(ref->name, name, namelen);
-	ref->name[namelen] = 0;
 	ref->head = head;
 	ref->tag = tag;
 	ref->ltag = ltag;
@@ -6682,10 +7059,14 @@ read_ref(char *id, size_t idlen, char *name, size_t namelen)
 static int
 load_refs(void)
 {
+	const char *head_argv[] = {
+		"git", "symbolic-ref", "HEAD", NULL
+	};
 	static const char *ls_remote_argv[SIZEOF_ARG] = {
 		"git", "ls-remote", opt_git_dir, NULL
 	};
 	static bool init = FALSE;
+	size_t i;
 
 	if (!init) {
 		argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
@@ -6695,12 +7076,31 @@ load_refs(void)
 	if (!*opt_git_dir)
 		return OK;
 
-	while (refs_size > 0)
-		free(refs[--refs_size].name);
-	while (id_refs_size > 0)
-		free(id_refs[--id_refs_size]);
+	if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
+	    !prefixcmp(opt_head, "refs/heads/")) {
+		char *offset = opt_head + STRING_SIZE("refs/heads/");
+
+		memmove(opt_head, offset, strlen(offset) + 1);
+	}
+
+	for (i = 0; i < refs_size; i++)
+		refs[i]->id[0] = 0;
 
-	return run_io_load(ls_remote_argv, "\t", read_ref);
+	if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
+		return ERR;
+
+	/* Update the ref lists to reflect changes. */
+	for (i = 0; i < ref_lists_size; i++) {
+		struct ref_list *list = ref_lists[i];
+		size_t old, new;
+
+		for (old = new = 0; old < list->size; old++)
+			if (!strcmp(list->id, list->refs[old]->id))
+				list->refs[new++] = list->refs[old];
+		list->size = new;
+	}
+
+	return OK;
 }
 
 static void
@@ -6837,23 +7237,11 @@ read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
 static int
 load_repo_info(void)
 {
-	const char *head_argv[] = {
-		"git", "symbolic-ref", "HEAD", NULL
-	};
 	const char *rev_parse_argv[] = {
 		"git", "rev-parse", "--git-dir", "--is-inside-work-tree",
 			"--show-cdup", "--show-prefix", NULL
 	};
 
-	if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
-		chomp_string(opt_head);
-		if (!prefixcmp(opt_head, "refs/heads/")) {
-			char *offset = opt_head + STRING_SIZE("refs/heads/");
-
-			memmove(opt_head, offset, strlen(offset) + 1);
-		}
-	}
-
 	return run_io_load(rev_parse_argv, "=", read_repo_info);
 }
 
diff --git a/tig.spec b/tig.spec
new file mode 100644
index 0000000..bc91a49
--- /dev/null
+++ b/tig.spec
@@ -0,0 +1,104 @@
+Name: tig
+Version: 0.14.1
+Release: alt2
+
+Summary: text-mode interface for git
+License: GPL
+Group: Development/Other
+
+URL: http://jonas.nitro.dk/tig/
+Source: tig-%version.tar
+Patch: tig-%version-%release.patch
+
+Requires: git-core
+
+# Automatically added by buildreq on Mon Jul 20 2009
+BuildRequires: asciidoc libncursesw-devel python-modules-encodings xmlto
+
+%description
+Tig is a git repository browser that additionally can act as a pager
+for output from various git commands.
+
+When browsing repositories, it uses the underlying git commands to
+present the user with various views, such as summarized revision log
+and showing the commit with the log message, diffstat, and the diff.
+
+Using it as a pager, it will display input from stdin and colorize it.
+
+%prep
+%setup -q
+%patch -p1
+
+%build
+make sysconfdir=/etc CFLAGS='%optflags' LDLIBS=-lncursesw clean tig \
+	tig.1 tigrc.5 tig.1.html tigrc.5.html manual.html README.html NEWS.html
+
+%install
+install -pD -m755 tig %buildroot%_bindir/tig
+install -pD -m644 tig.1 %buildroot%_man1dir/tig.1
+install -pD -m644 tigrc.5 %buildroot%_man5dir/tigrc.5
+
+%files
+%doc *.html
+%_bindir/tig
+%_man1dir/tig.1*
+%_man5dir/tigrc.5*
+
+%changelog
+* Mon Jul 20 2009 Alexey Tourbin <at@altlinux.ru> 0.14.1-alt2
+- tig-0.14.1-29-g1e69632
+
+* Wed Apr 08 2009 Alexey Tourbin <at@altlinux.ru> 0.14.1-alt1
+- 0.14 -> 0.14.1
+
+* Sat Feb 14 2009 Alexey Tourbin <at@altlinux.ru> 0.14-alt2
+- packaged NEWS.html
+
+* Sat Feb 14 2009 Alexey Tourbin <at@altlinux.ru> 0.14-alt1
+- 0.12.1 -> 0.14
+
+* Sat Oct 11 2008 Alexey Tourbin <at@altlinux.ru> 0.12.1-alt1
+- 0.12 -> 0.12.1
+
+* Tue Sep 16 2008 Alexey Tourbin <at@altlinux.ru> 0.12-alt1
+- 0.11 -> 0.12
+
+* Tue Apr 15 2008 Alexey Tourbin <at@altlinux.ru> 0.11-alt1
+- 0.10.1 -> 0.11
+
+* Fri Mar 21 2008 Alexey Tourbin <at@altlinux.ru> 0.10.1-alt1
+- 0.9.1 -> 0.10.1
+- built with libncursesw
+
+* Sun Oct 28 2007 Alexey Tourbin <at@altlinux.ru> 0.9.1-alt1
+- updated to tig-0.9.1
+
+* Sat Jul 21 2007 Alexey Tourbin <at@altlinux.ru> 0.8-alt1
+- updated to tig-0.8-12-g56b6ea5
+
+* Sat Jun 16 2007 Alexey Tourbin <at@altlinux.ru> 0.7-alt1
+- updated to tig-0.7-18-gd1858de
+
+* Wed Mar 28 2007 Alexey Tourbin <at@altlinux.ru> 0.6-alt1
+- updated to tig-0.6-1-ga1d2855
+
+* Thu Feb 22 2007 Alexey Tourbin <at@altlinux.ru> 0.5-alt5
+- updated to tig-0.5-16-ge15ec88 (improved handling of remotes)
+
+* Sun Jan 07 2007 Alexey Tourbin <at@altlinux.ru> 0.5-alt4
+- updated to tig-0.5-g337d737
+
+* Tue Oct 31 2006 Alexey Tourbin <at@altlinux.ru> 0.5-alt3
+- new snapshot, fixes SEGV in tree view
+
+* Wed Oct 18 2006 Alexey Tourbin <at@altlinux.ru> 0.5-alt2
+- in main view, fix handling of headlines that start with whitespace
+
+* Tue Oct 03 2006 Alexey Tourbin <at@altlinux.ru> 0.5-alt1
+- new version
+
+* Sat Sep 09 2006 Alexey Tourbin <at@altlinux.ru> 0.4-alt1
+- new snapshot
+
+* Thu Jul 06 2006 Alexey Tourbin <at@altlinux.ru> 0.3-alt1
+- initial revision
diff --git a/tigrc.5.txt b/tigrc.5.txt
index a633e83..1376232 100644
--- a/tigrc.5.txt
+++ b/tigrc.5.txt
@@ -9,11 +9,9 @@ tigrc - tig configuration file
 SYNOPSIS
 --------
 [verse]
-.............................................................................
 *set*   'variable' *=* 'value'
 *bind*  'keymap' 'key' 'action'
 *color* 'area' 'fgcolor' 'bgcolor' '[attributes]'
-.............................................................................
 
 
 DESCRIPTION
@@ -38,9 +36,7 @@ A few selective variables can be configured via the set command. The syntax
 is:
 
 [verse]
-..............................................................................
 *set* variables *=* value
-..............................................................................
 
 Examples:
 
@@ -125,6 +121,14 @@ The following variables can be set:
 	always ensured that at least one column is scrolled. The default is to
 	scroll '50%' of the view width.
 
+'split-view-height' (mixed)::
+
+	Height of the lower view in a split view. Can be specified either as
+	the number of rows, e.g. '5', or as a percentage of the view height,
+	e.g. '80%', where the maximum is 100%. It is always ensured that the
+	smaller of the views is at least four rows high. The default is a view
+	height of '66%'.
+
 'commit-encoding' (string)::
 
 	The encoding used for commits. The default is UTF-8. Not this option
@@ -138,9 +142,7 @@ Using bind commands keys can be mapped to an action when pressed in a given
 key map. The syntax is:
 
 [verse]
-..............................................................................
 *bind* 'keymap' 'key' 'action'
-..............................................................................
 
 Examples:
 
@@ -176,7 +178,8 @@ built-in keybindings.
 Keymaps::
 
 Valid keymaps are: *main*, *diff*, *log*, *help*, *pager*, *status*, *stage*,
-and *generic*.  Use *generic* to set key mapping in all keymaps.
+*tree*, *blob*, *blame*, *branch*, and *generic*.  Use *generic* to set key
+mapping in all keymaps.
 
 Key values::
 
@@ -248,6 +251,7 @@ view-log		Show log view
 view-tree		Show tree view
 view-blob		Show blob view
 view-blame		Show blame view
+view-branch		Show branch view
 view-status		Show status view
 view-stage		Show stage view
 view-pager		Show pager view
@@ -333,6 +337,7 @@ screen-redraw		Redraw the screen
 screen-resize		Resize the screen
 show-version		Show version information
 stop-loading		Stop all loading views
+options			Open options menu
 toggle-lineno		Toggle line numbers
 toggle-date		Toggle date display
 toggle-author		Toggle author display
@@ -352,9 +357,7 @@ background combinations to certain areas. Optionally, an attribute can be
 given as the last parameter. The syntax is:
 
 [verse]
-..............................................................................
 *color* 'area' 'fgcolor' 'bgcolor' '[attributes]'
-..............................................................................
 
 Examples:
 
 
дизайн и разработка: Vladimir Lettiev aka crux © 2004-2005, Andrew Avramenko aka liks © 2007-2008
текущий майнтейнер: Michael Shigorin