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_CURSOR 42
85 * stop all background loading
97 #define HELP "(d)iff, (l)og, (m)ain, (q)uit, (v)ersion, (h)elp"
104 struct keymap keymap[] = {
105 /* Cursor navigation */
106 { KEY_UP, REQ_PREV_LINE },
107 { 'k', REQ_PREV_LINE },
108 { KEY_DOWN, REQ_NEXT_LINE },
109 { 'j', REQ_NEXT_LINE },
110 { KEY_HOME, REQ_FIRST_LINE },
111 { KEY_END, REQ_LAST_LINE },
114 { KEY_IC, REQ_SCR_BLINE }, /* scroll field backward a line */
115 { KEY_DC, REQ_SCR_FLINE }, /* scroll field forward a line */
116 { KEY_NPAGE, REQ_SCR_FPAGE }, /* scroll field forward a page */
117 { KEY_PPAGE, REQ_SCR_BPAGE }, /* scroll field backward a page */
118 { 'w', REQ_SCR_FHPAGE }, /* scroll field forward half page */
119 { 's', REQ_SCR_BHPAGE }, /* scroll field backward half page */
125 /* No input from wgetch() with nodelay() enabled. */
128 { KEY_ESC, REQ_QUIT },
131 { 'v', REQ_VERSION },
136 get_request(int request)
140 for (i = 0; i < ARRAY_SIZE(keymap); i++)
141 if (keymap[i].alias == request)
142 return keymap[i].request;
159 int (*read)(struct view *, char *);
160 int (*draw)(struct view *, unsigned int);
161 size_t objsize; /* Size of objects in the line index */
167 unsigned long offset; /* Offset of the window top */
168 unsigned long lineno; /* Current line number */
171 unsigned long lines; /* Total number of lines */
172 void **line; /* Line index */
183 static int pager_draw(struct view *view, unsigned int lineno);
184 static int pager_read(struct view *view, char *line);
186 static int main_draw(struct view *view, unsigned int lineno);
187 static int main_read(struct view *view, char *line);
190 "git log --stat -n1 %s ; echo; " \
191 "git diff --find-copies-harder -B -C %s^ %s"
194 "git log --stat -n100 %s"
197 "git log --stat --pretty=raw %s"
199 /* The status window at the bottom. Used for polling keystrokes. */
200 static WINDOW *status_win;
202 #define SIZEOF_ID 1024
203 #define SIZEOF_VIEWS (REQ_VIEWS - REQ_OFFSET)
205 char head_id[SIZEOF_ID] = "HEAD";
206 char commit_id[SIZEOF_ID] = "HEAD";
208 static unsigned int current_view;
209 static unsigned int nloading;
211 static struct view views[];
212 static struct view *display[];
214 static struct view views[] = {
215 { "diff", DIFF_CMD, commit_id, pager_read, pager_draw, sizeof(char) },
216 { "log", LOG_CMD, head_id, pager_read, pager_draw, sizeof(struct commit) },
217 { "main", MAIN_CMD, head_id, main_read, main_draw },
220 static struct view *display[ARRAY_SIZE(views)];
223 #define foreach_view(view, i) \
224 for (i = 0; i < sizeof(display) && (view = display[i]); i++)
227 redraw_view(struct view *view)
232 wmove(view->win, 0, 0);
234 for (lineno = 0; lineno < view->height; lineno++) {
235 if (!view->draw(view, lineno))
239 redrawwin(view->win);
243 /* FIXME: Fix percentage. */
245 report_position(struct view *view, int all)
247 report(all ? "line %d of %d (%d%%) viewing from %d"
251 view->lines ? view->offset * 100 / view->lines : 0,
256 move_view(struct view *view, int lines)
258 /* The rendering expects the new offset. */
259 view->offset += lines;
261 assert(0 <= view->offset && view->offset < view->lines);
265 int line = lines > 0 ? view->height - lines : 0;
266 int end = line + (lines > 0 ? lines : -lines);
268 wscrl(view->win, lines);
270 for (; line < end; line++) {
271 if (!view->draw(view, line))
276 /* Move current line into the view. */
277 if (view->lineno < view->offset) {
278 view->lineno = view->offset;
281 } else if (view->lineno >= view->offset + view->height) {
282 view->lineno = view->offset + view->height - 1;
283 view->draw(view, view->lineno - view->offset);
286 assert(view->offset <= view->lineno && view->lineno <= view->lines);
288 redrawwin(view->win);
291 report_position(view, lines);
294 scroll_view(struct view *view, int request)
300 lines = view->height;
302 if (view->offset + lines > view->lines)
303 lines = view->lines - view->offset;
305 if (lines == 0 || view->offset + view->height >= view->lines) {
306 report("already at last line");
312 lines = view->height;
314 if (lines > view->offset)
315 lines = view->offset;
318 report("already at first line");
326 move_view(view, lines);
331 navigate_view(struct view *view, int request)
337 if (view->lineno == 0) {
338 report("already at first line");
345 if (view->lineno + 1 >= view->lines) {
346 report("already at last line");
353 steps = -view->lineno;
357 steps = view->lines - view->lineno - 1;
360 view->lineno += steps;
361 view->draw(view, view->lineno - steps - view->offset);
363 if (view->lineno < view->offset ||
364 view->lineno >= view->offset + view->height) {
365 if (steps < 0 && -steps > view->offset) {
366 steps = -view->offset;
368 move_view(view, steps);
372 view->draw(view, view->lineno - view->offset);
374 redrawwin(view->win);
377 report_position(view, view->height);
381 resize_view(struct view *view)
385 getmaxyx(stdscr, lines, cols);
388 mvwin(view->win, 0, 0);
389 wresize(view->win, lines - 1, cols);
392 view->win = newwin(lines - 1, 0, 0, 0);
394 report("failed to create %s view", view->name);
397 scrollok(view->win, TRUE);
400 getmaxyx(view->win, view->height, view->width);
405 begin_update(struct view *view)
412 if (snprintf(buf, sizeof(buf), view->cmd, id, id, id) < sizeof(buf))
413 view->pipe = popen(buf, "r");
419 nodelay(status_win, TRUE);
422 display[current_view] = view;
432 end_update(struct view *view)
434 wattrset(view->win, A_NORMAL);
439 nodelay(status_win, FALSE);
443 update_view(struct view *view)
449 int lines = view->height;
454 /* Only redraw after the first reading session. */
455 /* FIXME: ... and possibly more. */
456 redraw = view->height > view->lines;
458 tmp = realloc(view->line, sizeof(*view->line) * (view->lines + lines));
464 while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
467 linelen = strlen(line);
469 line[linelen - 1] = 0;
471 if (!view->read(view, line))
481 if (ferror(view->pipe)) {
482 report("failed to read %s", view->cmd);
485 } else if (feof(view->pipe)) {
486 report_position(view, 0);
493 report("allocation failure");
502 switch_view(struct view *prev, int request)
504 struct view *view = &views[request - REQ_OFFSET];
505 struct view *displayed;
509 foreach_view (displayed, i) ;
512 report("already in %s view", view->name);
514 report("FIXME: Maximize");
519 foreach_view (displayed, i) {
520 if (view == displayed) {
522 report("new current view");
534 for (i = 0; i < view->lines; i++)
542 if (prev && prev->pipe)
545 if (begin_update(view)) {
549 report("loading...");
556 /* Process a keystroke */
558 view_driver(struct view *view, int key)
560 int request = get_request(key);
569 navigate_view(view, request);
577 scroll_view(view, request);
583 view = switch_view(view, request);
591 foreach_view (view, i) {
594 scroll_view(view, 0);
600 report("version %s", VERSION);
623 #define ATTR(line, attr) { (line), sizeof(line) - 1, (attr) }
631 static struct attr attrs[] = {
632 ATTR("commit ", COLOR_PAIR(COLOR_GREEN)),
633 ATTR("Author: ", COLOR_PAIR(COLOR_CYAN)),
634 ATTR("Date: ", COLOR_PAIR(COLOR_YELLOW)),
635 ATTR("diff --git ", COLOR_PAIR(COLOR_YELLOW)),
636 ATTR("diff-tree ", COLOR_PAIR(COLOR_BLUE)),
637 ATTR("index ", COLOR_PAIR(COLOR_BLUE)),
638 ATTR("-", COLOR_PAIR(COLOR_RED)),
639 ATTR("+", COLOR_PAIR(COLOR_GREEN)),
640 ATTR("@", COLOR_PAIR(COLOR_MAGENTA)),
644 pager_draw(struct view *view, unsigned int lineno)
651 if (view->offset + lineno >= view->lines)
654 line = view->line[view->offset + lineno];
655 if (!line) return FALSE;
657 linelen = strlen(line);
659 for (i = 0; i < ARRAY_SIZE(attrs); i++) {
660 if (linelen < attrs[i].linelen
661 || strncmp(attrs[i].line, line, attrs[i].linelen))
664 attr = attrs[i].attr;
668 if (view->offset + lineno == view->lineno) {
670 strncpy(commit_id, line + 7, SIZEOF_ID);
671 attr = COLOR_PAIR(COLOR_CURSOR) | A_BOLD;
674 wattrset(view->win, attr);
675 //mvwprintw(view->win, lineno, 0, "%4d: %s", view->offset + lineno, line);
676 mvwaddstr(view->win, lineno, 0, line);
682 pager_read(struct view *view, char *line)
684 view->line[view->lines] = strdup(line);
685 if (!view->line[view->lines])
693 main_draw(struct view *view, unsigned int lineno)
695 struct commit *commit;
698 if (view->offset + lineno >= view->lines)
701 commit = view->line[view->offset + lineno];
702 if (!commit) return FALSE;
704 attr = attrs[0].attr;
706 if (view->offset + lineno == view->lineno) {
707 strncpy(commit_id, commit->id, SIZEOF_ID);
708 attr = COLOR_PAIR(COLOR_CURSOR) | A_BOLD;
711 mvwaddch(view->win, lineno, 0, ACS_LTEE);
712 wattrset(view->win, attr);
713 mvwaddstr(view->win, lineno, 2, commit->title);
714 wattrset(view->win, A_NORMAL);
720 main_read(struct view *view, char *line)
722 int linelen = strlen(line);
725 for (i = 0; i < ARRAY_SIZE(attrs); i++) {
726 if (linelen < attrs[i].linelen
727 || strncmp(attrs[i].line, line, attrs[i].linelen))
733 struct commit *commit;
735 commit = calloc(1, sizeof(struct commit));
739 view->line[view->lines++] = commit;
741 strncpy(commit->id, line + 7, 41);
744 struct commit *commit = view->line[view->lines - 1];
746 if (!commit->title[0] &&
748 !strncmp(line, " ", 4) &&
750 strncpy(commit->title, line + 4, sizeof(commit->title));
768 /* FIXME: Shutdown gracefully. */
773 static void die(const char *err, ...)
780 fputs("tig: ", stderr);
781 vfprintf(stderr, err, args);
789 report(const char *msg, ...)
796 wmove(status_win, 0, 0);
799 if (display[current_view])
800 wprintw(status_win, "%s %4s: ", commit_id, display[current_view]->name);
802 vwprintw(status_win, msg, args);
803 wrefresh(status_win);
811 int bg = COLOR_BLACK;
815 if (use_default_colors() != ERR)
818 init_pair(COLOR_BLACK, COLOR_BLACK, bg);
819 init_pair(COLOR_GREEN, COLOR_GREEN, bg);
820 init_pair(COLOR_RED, COLOR_RED, bg);
821 init_pair(COLOR_CYAN, COLOR_CYAN, bg);
822 init_pair(COLOR_WHITE, COLOR_WHITE, bg);
823 init_pair(COLOR_MAGENTA, COLOR_MAGENTA, bg);
824 init_pair(COLOR_BLUE, COLOR_BLUE, bg);
825 init_pair(COLOR_YELLOW, COLOR_YELLOW, bg);
826 init_pair(COLOR_CURSOR, COLOR_WHITE, COLOR_GREEN);
830 main(int argc, char *argv[])
832 int request = REQ_MAIN;
835 signal(SIGINT, quit);
837 initscr(); /* initialize the curses library */
838 nonl(); /* tell curses not to do NL->CR/NL on output */
839 cbreak(); /* take input chars one at a time, no wait for \n */
840 noecho(); /* don't echo input */
841 leaveok(stdscr, TRUE);
847 getmaxyx(stdscr, y, x);
848 status_win = newwin(1, 0, y - 1, 0);
850 die("Failed to create status window");
852 /* Enable keyboard mapping */
853 keypad(status_win, TRUE);
854 wattrset(status_win, COLOR_PAIR(COLOR_GREEN));
856 while (view_driver(display[current_view], request)) {
860 foreach_view (view, i) {
866 /* Refresh, accept single keystroke of input */
867 request = wgetch(status_win);
868 if (request == KEY_RESIZE) {
871 getmaxyx(stdscr, lines, cols);
872 mvwin(status_win, lines - 1, 0);
873 wresize(status_win, 1, cols - 1);
885 * Copyright (c) Jonas Fonseca, 2006
887 * This program is free software; you can redistribute it and/or modify
888 * it under the terms of the GNU General Public License as published by
889 * the Free Software Foundation; either version 2 of the License, or
890 * (at your option) any later version.
894 * link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
895 * link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]