7 * tig - text-mode interface for git
13 * tig log [git log options]
14 * tig diff [git diff options]
15 * tig < [git log or git diff output]
19 * Browse changes in a git repository.
45 static void die(const char *err, ...);
46 static void report(const char *msg, ...);
48 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
54 #define REQ_OFFSET (MAX_COMMAND + 1)
56 /* Requests for switching between the different views. */
57 #define REQ_DIFF (REQ_OFFSET + 0)
58 #define REQ_LOG (REQ_OFFSET + 1)
59 #define REQ_MAIN (REQ_OFFSET + 2)
60 #define REQ_VIEWS (REQ_OFFSET + 3)
62 #define REQ_QUIT (REQ_OFFSET + 11)
63 #define REQ_VERSION (REQ_OFFSET + 12)
64 #define REQ_STOP (REQ_OFFSET + 13)
65 #define REQ_UPDATE (REQ_OFFSET + 14)
66 #define REQ_REDRAW (REQ_OFFSET + 15)
67 #define REQ_FIRST_LINE (REQ_OFFSET + 16)
68 #define REQ_LAST_LINE (REQ_OFFSET + 17)
70 #define COLOR_TRANSP (-1)
102 #define LINE(type, line, fg, bg, attr) \
103 { LINE_##type, (line), sizeof(line) - 1, (fg), (bg), (attr) }
105 static struct line_info line_info[] = {
106 LINE(AUTHOR, "Author: ", COLOR_CYAN, COLOR_TRANSP, 0),
107 //LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_TRANSP, 0),
108 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_TRANSP, 0),
109 LINE(DATE, "Date: ", COLOR_YELLOW, COLOR_TRANSP, 0),
110 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_TRANSP, 0),
111 LINE(DIFF_CHUNK, "@", COLOR_MAGENTA, COLOR_TRANSP, 0),
112 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_TRANSP, 0),
113 LINE(DIFF, "diff --git ", COLOR_YELLOW, COLOR_TRANSP, 0),
114 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_TRANSP, 0),
115 LINE(INDEX, "index ", COLOR_BLUE, COLOR_TRANSP, 0),
116 LINE(PARENT, "parent ", COLOR_GREEN, COLOR_TRANSP, 0),
117 LINE(TREE, "tree ", COLOR_GREEN, COLOR_TRANSP, 0),
119 LINE(UNKNOWN, "", COLOR_TRANSP, COLOR_TRANSP, A_NORMAL),
121 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD),
122 LINE(STATUS, "", COLOR_GREEN, COLOR_TRANSP, 0),
123 LINE(TITLE, "", COLOR_YELLOW, COLOR_BLUE, A_BOLD),
126 static struct line_info *
127 get_line_info(char *line)
129 int linelen = strlen(line);
132 for (i = 0; i < ARRAY_SIZE(line_info); i++) {
133 if (linelen < line_info[i].linelen
134 || strncmp(line_info[i].line, line, line_info[i].linelen))
137 return &line_info[i];
143 static enum line_type
144 get_line_type(char *line)
146 struct line_info *info = get_line_info(line);
148 return info ? info->type : LINE_UNKNOWN;
152 get_line_attr(enum line_type type)
156 for (i = 0; i < ARRAY_SIZE(line_info); i++)
157 if (line_info[i].type == type)
158 return COLOR_PAIR(line_info[i].type) | line_info[i].attr;
166 int transparent_bg = COLOR_BLACK;
167 int transparent_fg = COLOR_WHITE;
172 if (use_default_colors() != ERR) {
177 for (i = 0; i < ARRAY_SIZE(line_info); i++) {
178 struct line_info *info = &line_info[i];
179 int bg = info->bg == COLOR_TRANSP ? transparent_bg : info->bg;
180 int fg = info->fg == COLOR_TRANSP ? transparent_fg : info->fg;
182 init_pair(info->type, fg, bg);
202 * stop all background loading
214 #define HELP "(d)iff, (l)og, (m)ain, (q)uit, (v)ersion, (h)elp"
221 struct keymap keymap[] = {
222 /* Cursor navigation */
223 { KEY_UP, REQ_PREV_LINE },
224 { 'k', REQ_PREV_LINE },
225 { KEY_DOWN, REQ_NEXT_LINE },
226 { 'j', REQ_NEXT_LINE },
227 { KEY_HOME, REQ_FIRST_LINE },
228 { KEY_END, REQ_LAST_LINE },
229 { KEY_NPAGE, REQ_NEXT_PAGE },
230 { KEY_PPAGE, REQ_PREV_PAGE },
233 { KEY_IC, REQ_SCR_BLINE }, /* scroll field backward a line */
234 { KEY_DC, REQ_SCR_FLINE }, /* scroll field forward a line */
235 { 's', REQ_SCR_FPAGE }, /* scroll field forward a page */
236 { 'w', REQ_SCR_BPAGE }, /* scroll field backward a page */
242 /* No input from wgetch() with nodelay() enabled. */
245 { KEY_ESC, REQ_QUIT },
248 { 'v', REQ_VERSION },
253 get_request(int request)
257 for (i = 0; i < ARRAY_SIZE(keymap); i++)
258 if (keymap[i].alias == request)
259 return keymap[i].request;
276 int (*read)(struct view *, char *);
277 int (*draw)(struct view *, unsigned int);
278 size_t objsize; /* Size of objects in the line index */
284 unsigned long offset; /* Offset of the window top */
285 unsigned long lineno; /* Current line number */
288 unsigned long lines; /* Total number of lines */
289 void **line; /* Line index */
300 static int pager_draw(struct view *view, unsigned int lineno);
301 static int pager_read(struct view *view, char *line);
303 static int main_draw(struct view *view, unsigned int lineno);
304 static int main_read(struct view *view, char *line);
307 "git log --stat -n1 %s ; echo; " \
308 "git diff --find-copies-harder -B -C %s^ %s"
311 "git log --stat -n100 %s"
314 "git log --stat --pretty=raw %s"
316 /* The status window at the bottom. Used for polling keystrokes. */
317 static WINDOW *status_win;
319 static WINDOW *title_win;
321 #define SIZEOF_ID 1024
322 #define SIZEOF_VIEWS (REQ_VIEWS - REQ_OFFSET)
324 char head_id[SIZEOF_ID] = "HEAD";
325 char commit_id[SIZEOF_ID] = "HEAD";
327 static unsigned int current_view;
328 static unsigned int nloading;
330 static struct view views[];
331 static struct view *display[];
333 static struct view views[] = {
334 { "diff", DIFF_CMD, commit_id, pager_read, pager_draw, sizeof(char) },
335 { "log", LOG_CMD, head_id, pager_read, pager_draw, sizeof(char) },
336 { "main", MAIN_CMD, head_id, main_read, main_draw, sizeof(struct commit) },
339 static struct view *display[ARRAY_SIZE(views)];
342 #define foreach_view(view, i) \
343 for (i = 0; i < sizeof(display) && (view = display[i]); i++)
346 redraw_view(struct view *view)
351 wmove(view->win, 0, 0);
353 for (lineno = 0; lineno < view->height; lineno++) {
354 if (!view->draw(view, lineno))
358 redrawwin(view->win);
362 /* FIXME: Fix percentage. */
364 report_position(struct view *view, int all)
366 report(all ? "line %d of %d (%d%%) viewing from %d"
370 view->lines ? view->offset * 100 / view->lines : 0,
376 move_view(struct view *view, int lines)
378 /* The rendering expects the new offset. */
379 view->offset += lines;
381 assert(0 <= view->offset && view->offset < view->lines);
385 int line = lines > 0 ? view->height - lines : 0;
386 int end = line + (lines > 0 ? lines : -lines);
388 wscrl(view->win, lines);
390 for (; line < end; line++) {
391 if (!view->draw(view, line))
396 /* Move current line into the view. */
397 if (view->lineno < view->offset) {
398 view->lineno = view->offset;
401 } else if (view->lineno >= view->offset + view->height) {
402 view->lineno = view->offset + view->height - 1;
403 view->draw(view, view->lineno - view->offset);
406 assert(view->offset <= view->lineno && view->lineno <= view->lines);
408 redrawwin(view->win);
411 report_position(view, lines);
415 scroll_view(struct view *view, int request)
421 lines = view->height;
423 if (view->offset + lines > view->lines)
424 lines = view->lines - view->offset;
426 if (lines == 0 || view->offset + view->height >= view->lines) {
427 report("already at last line");
433 lines = view->height;
435 if (lines > view->offset)
436 lines = view->offset;
439 report("already at first line");
447 move_view(view, lines);
452 navigate_view(struct view *view, int request)
458 steps = -view->lineno;
462 steps = view->lines - view->lineno - 1;
466 steps = view->height > view->lineno
467 ? -view->lineno : -view->height;
471 steps = view->lineno + view->height >= view->lines
472 ? view->lines - view->lineno - 1 : view->height;
484 if (steps < 0 && view->lineno == 0) {
485 report("already at first line");
488 } else if (steps > 0 && view->lineno + 1 >= view->lines) {
489 report("already at last line");
493 view->lineno += steps;
494 view->draw(view, view->lineno - steps - view->offset);
496 if (view->lineno < view->offset ||
497 view->lineno >= view->offset + view->height) {
498 if (steps < 0 && -steps > view->offset) {
499 steps = -view->offset;
500 } else if (steps > 0 && steps > view->height) {
501 steps -= view->height - 1;
504 move_view(view, steps);
508 view->draw(view, view->lineno - view->offset);
510 redrawwin(view->win);
513 report_position(view, view->height);
517 resize_view(struct view *view)
521 getmaxyx(stdscr, lines, cols);
524 mvwin(view->win, 0, 0);
525 wresize(view->win, lines - 2, cols);
528 view->win = newwin(lines - 2, 0, 0, 0);
530 report("failed to create %s view", view->name);
533 scrollok(view->win, TRUE);
536 getmaxyx(view->win, view->height, view->width);
541 begin_update(struct view *view)
548 if (snprintf(buf, sizeof(buf), view->cmd, id, id, id) < sizeof(buf))
549 view->pipe = popen(buf, "r");
555 nodelay(status_win, TRUE);
558 display[current_view] = view;
568 end_update(struct view *view)
570 wattrset(view->win, A_NORMAL);
575 nodelay(status_win, FALSE);
579 update_view(struct view *view)
585 int lines = view->height;
590 /* Only redraw after the first reading session. */
591 /* FIXME: ... and possibly more. */
592 redraw = view->height > view->lines;
594 tmp = realloc(view->line, sizeof(*view->line) * (view->lines + lines));
600 while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
603 linelen = strlen(line);
605 line[linelen - 1] = 0;
607 if (!view->read(view, line))
617 if (ferror(view->pipe)) {
618 report("failed to read %s", view->cmd);
621 } else if (feof(view->pipe)) {
622 report_position(view, 0);
629 report("allocation failure");
638 switch_view(struct view *prev, int request)
640 struct view *view = &views[request - REQ_OFFSET];
641 struct view *displayed;
645 foreach_view (displayed, i) ;
648 report("already in %s view", view->name);
650 report("FIXME: Maximize");
655 foreach_view (displayed, i) {
656 if (view == displayed) {
658 report("new current view");
670 for (i = 0; i < view->lines; i++)
678 if (prev && prev->pipe)
681 if (begin_update(view)) {
685 report("loading...");
692 /* Process a keystroke */
694 view_driver(struct view *view, int key)
696 int request = get_request(key);
707 navigate_view(view, request);
715 scroll_view(view, request);
721 view = switch_view(view, request);
729 foreach_view (view, i) {
732 scroll_view(view, 0);
738 report("version %s", VERSION);
762 pager_draw(struct view *view, unsigned int lineno)
768 if (view->offset + lineno >= view->lines)
771 line = view->line[view->offset + lineno];
772 type = get_line_type(line);
774 if (view->offset + lineno == view->lineno) {
775 if (type == LINE_COMMIT)
776 strncpy(commit_id, line + 7, SIZEOF_ID);
780 attr = get_line_attr(type);
781 wattrset(view->win, attr);
782 //mvwprintw(view->win, lineno, 0, "%4d: %s", view->offset + lineno, line);
783 mvwaddstr(view->win, lineno, 0, line);
789 pager_read(struct view *view, char *line)
791 view->line[view->lines] = strdup(line);
792 if (!view->line[view->lines])
800 main_draw(struct view *view, unsigned int lineno)
802 struct commit *commit;
805 if (view->offset + lineno >= view->lines)
808 commit = view->line[view->offset + lineno];
809 if (!commit) return FALSE;
811 if (view->offset + lineno == view->lineno) {
812 strncpy(commit_id, commit->id, SIZEOF_ID);
818 mvwaddch(view->win, lineno, 0, ACS_LTEE);
819 wattrset(view->win, get_line_attr(type));
820 mvwaddstr(view->win, lineno, 2, commit->title);
821 wattrset(view->win, A_NORMAL);
827 main_read(struct view *view, char *line)
829 enum line_type type = get_line_type(line);
830 struct commit *commit;
834 commit = calloc(1, sizeof(struct commit));
838 view->line[view->lines++] = commit;
839 strncpy(commit->id, line + 7, 41);
843 commit = view->line[view->lines - 1];
844 if (!commit->title[0] &&
845 !strncmp(line, " ", 4) &&
847 strncpy(commit->title, line + 4, sizeof(commit->title));
867 /* FIXME: Shutdown gracefully. */
872 static void die(const char *err, ...)
879 fputs("tig: ", stderr);
880 vfprintf(stderr, err, args);
888 report(const char *msg, ...)
895 wmove(status_win, 0, 0);
898 if (display[current_view])
899 wprintw(status_win, "%s %4s: ", commit_id, display[current_view]->name);
901 vwprintw(status_win, msg, args);
902 wrefresh(status_win);
907 wmove(title_win, 0, 0);
908 wprintw(title_win, "commit %s", commit_id);
914 main(int argc, char *argv[])
916 int request = REQ_MAIN;
919 signal(SIGINT, quit);
921 initscr(); /* initialize the curses library */
922 nonl(); /* tell curses not to do NL->CR/NL on output */
923 cbreak(); /* take input chars one at a time, no wait for \n */
924 noecho(); /* don't echo input */
925 leaveok(stdscr, TRUE);
931 getmaxyx(stdscr, y, x);
932 status_win = newwin(1, 0, y - 1, 0);
934 die("Failed to create status window");
936 title_win = newwin(1, 0, y - 2, 0);
938 die("Failed to create title window");
940 /* Enable keyboard mapping */
941 keypad(status_win, TRUE);
942 wbkgdset(status_win, get_line_attr(LINE_STATUS));
943 wbkgdset(title_win, get_line_attr(LINE_TITLE));
945 while (view_driver(display[current_view], request)) {
949 foreach_view (view, i) {
955 /* Refresh, accept single keystroke of input */
956 request = wgetch(status_win);
957 if (request == KEY_RESIZE) {
960 getmaxyx(stdscr, lines, cols);
962 mvwin(status_win, lines - 1, 0);
963 wresize(status_win, 1, cols - 1);
965 mvwin(title_win, lines - 2, 0);
966 wresize(title_win, 1, cols - 1);
978 * Copyright (c) Jonas Fonseca, 2006
980 * This program is free software; you can redistribute it and/or modify
981 * it under the terms of the GNU General Public License as published by
982 * the Free Software Foundation; either version 2 of the License, or
983 * (at your option) any later version.
987 * link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
988 * link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]