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)]