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