7 * tig - text-mode interface for git
13 * tig [options] log [git log options]
14 * tig [options] diff [git diff options]
15 * tig [options] < [git log or git diff output]
19 * Browse changes in a git repository.
28 #define VERSION "tig-0.1"
44 static void die(const char *err
, ...);
45 static void report(const char *msg
, ...);
47 /* Some ascii-shorthands that fit into the ncurses namespace. */
54 /* REQ_* values from form.h is used as a basis for user actions.
55 * Offset new values below relative to MAX_COMMAND from form.h. */
56 REQ_OFFSET
= MAX_COMMAND
,
58 /* Requests for switching between the different views. */
74 /* The request are used for adressing the view array. */
75 #define VIEW_OFFSET(r) ((r) - REQ_OFFSET - 1)
77 #define SIZEOF_ID 1024
79 #define COLOR_TRANSP (-1)
81 #define DATE_FORMAT "%Y-%m-%d %H:%M"
82 #define DATE_COLS (STRING_SIZE("2006-04-29 14:21") + 1)
84 #define ABS(x) ((x) >= 0 ? (x) : -(x))
85 #define MIN(x, y) ((x) < (y) ? (x) : (y))
87 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
88 #define STRING_SIZE(x) (sizeof(x) - 1)
103 static int opt_line_number
;
104 static int opt_request
= REQ_MAIN
;
106 char head_id
[SIZEOF_ID
] = "HEAD";
107 char commit_id
[SIZEOF_ID
] = "HEAD";
109 /* Returns the index of log or diff command or -1 to exit. */
111 parse_options(int argc
, char *argv
[])
115 for (i
= 1; i
< argc
; i
++) {
122 if (!strcmp(opt
, "log")) {
123 opt_request
= REQ_LOG
;
130 } else if (!strcmp(opt
, "diff")) {
131 opt_request
= REQ_DIFF
;
136 * Start up in log view.
138 } else if (!strcmp(opt
, "-l")) {
139 opt_request
= REQ_LOG
;
143 * Start up in diff view.
145 } else if (!strcmp(opt
, "-d")) {
146 opt_request
= REQ_DIFF
;
149 * -n, --line-number::
150 * Prefix line numbers in log and diff view.
152 } else if (!strcmp(opt
, "-n") ||
153 !strcmp(opt
, "--line-number")) {
158 * Show version and exit.
160 } else if (!strcmp(opt
, "-v") ||
161 !strcmp(opt
, "--version")) {
162 printf("tig version %s\n", VERSION
);
167 * Commit reference, symbolic or raw SHA1 ID.
169 } else if (opt
[0] && opt
[0] != '-') {
170 strncpy(head_id
, opt
, SIZEOF_ID
);
171 strncpy(commit_id
, opt
, SIZEOF_ID
);
174 die("unknown command '%s'", opt
);
183 * Line-oriented content detection.
228 #define LINE(type, line, fg, bg, attr) \
229 { LINE_##type, (line), STRING_SIZE(line), (fg), (bg), (attr) }
231 static struct line_info line_info
[] = {
233 LINE(DIFF
, "diff --git ", COLOR_YELLOW
, COLOR_TRANSP
, 0),
234 LINE(INDEX
, "index ", COLOR_BLUE
, COLOR_TRANSP
, 0),
235 LINE(DIFF_CHUNK
, "@@", COLOR_MAGENTA
, COLOR_TRANSP
, 0),
236 LINE(DIFF_ADD
, "+", COLOR_GREEN
, COLOR_TRANSP
, 0),
237 LINE(DIFF_DEL
, "-", COLOR_RED
, COLOR_TRANSP
, 0),
238 LINE(DIFF_OLDMODE
, "old mode ", COLOR_YELLOW
, COLOR_TRANSP
, 0),
239 LINE(DIFF_NEWMODE
, "new mode ", COLOR_YELLOW
, COLOR_TRANSP
, 0),
240 LINE(DIFF_COPY
, "copy ", COLOR_YELLOW
, COLOR_TRANSP
, 0),
241 LINE(DIFF_RENAME
, "rename ", COLOR_YELLOW
, COLOR_TRANSP
, 0),
242 LINE(DIFF_SIM
, "similarity ", COLOR_YELLOW
, COLOR_TRANSP
, 0),
243 LINE(DIFF_DISSIM
, "dissimilarity ", COLOR_YELLOW
, COLOR_TRANSP
, 0),
245 /* Pretty print commit header */
246 LINE(AUTHOR
, "Author: ", COLOR_CYAN
, COLOR_TRANSP
, 0),
247 LINE(MERGE
, "Merge: ", COLOR_BLUE
, COLOR_TRANSP
, 0),
248 LINE(DATE
, "Date: ", COLOR_YELLOW
, COLOR_TRANSP
, 0),
250 /* Raw commit header */
251 LINE(COMMIT
, "commit ", COLOR_GREEN
, COLOR_TRANSP
, 0),
252 LINE(PARENT
, "parent ", COLOR_BLUE
, COLOR_TRANSP
, 0),
253 LINE(TREE
, "tree ", COLOR_BLUE
, COLOR_TRANSP
, 0),
254 LINE(AUTHOR_IDENT
, "author ", COLOR_CYAN
, COLOR_TRANSP
, 0),
255 LINE(COMMITTER
, "committer ", COLOR_MAGENTA
, COLOR_TRANSP
, 0),
258 LINE(DIFF_TREE
, "diff-tree ", COLOR_BLUE
, COLOR_TRANSP
, 0),
259 LINE(SIGNOFF
, " Signed-off-by", COLOR_YELLOW
, COLOR_TRANSP
, 0),
262 LINE(DEFAULT
, "", COLOR_TRANSP
, COLOR_TRANSP
, A_NORMAL
),
263 LINE(CURSOR
, "", COLOR_WHITE
, COLOR_GREEN
, A_BOLD
),
264 LINE(STATUS
, "", COLOR_GREEN
, COLOR_TRANSP
, 0),
265 LINE(TITLE
, "", COLOR_YELLOW
, COLOR_BLUE
, A_BOLD
),
266 LINE(MAIN_DATE
, "", COLOR_BLUE
, COLOR_TRANSP
, 0),
267 LINE(MAIN_AUTHOR
, "", COLOR_GREEN
, COLOR_TRANSP
, 0),
268 LINE(MAIN_COMMIT
, "", COLOR_TRANSP
, COLOR_TRANSP
, 0),
269 LINE(MAIN_DELIM
, "", COLOR_MAGENTA
, COLOR_TRANSP
, 0),
272 static struct line_info
*
273 get_line_info(char *line
)
275 int linelen
= strlen(line
);
278 for (i
= 0; i
< ARRAY_SIZE(line_info
); i
++) {
279 if (linelen
< line_info
[i
].linelen
280 || strncasecmp(line_info
[i
].line
, line
, line_info
[i
].linelen
))
283 return &line_info
[i
];
289 static enum line_type
290 get_line_type(char *line
)
292 struct line_info
*info
= get_line_info(line
);
294 return info ? info
->type
: LINE_DEFAULT
;
298 get_line_attr(enum line_type type
)
302 for (i
= 0; i
< ARRAY_SIZE(line_info
); i
++)
303 if (line_info
[i
].type
== type
)
304 return COLOR_PAIR(line_info
[i
].type
) | line_info
[i
].attr
;
312 int transparent_bg
= COLOR_BLACK
;
313 int transparent_fg
= COLOR_WHITE
;
318 if (use_default_colors() != ERR
) {
323 for (i
= 0; i
< ARRAY_SIZE(line_info
); i
++) {
324 struct line_info
*info
= &line_info
[i
];
325 int bg
= info
->bg
== COLOR_TRANSP ? transparent_bg
: info
->bg
;
326 int fg
= info
->fg
== COLOR_TRANSP ? transparent_fg
: info
->fg
;
328 init_pair(info
->type
, fg
, bg
);
346 * stop all background loading
357 #define HELP "(d)iff, (l)og, (m)ain, (q)uit, (v)ersion, (h)elp"
364 struct keymap keymap
[] = {
365 /* Cursor navigation */
366 { KEY_UP
, REQ_PREV_LINE
},
367 { 'k', REQ_PREV_LINE
},
368 { KEY_DOWN
, REQ_NEXT_LINE
},
369 { 'j', REQ_NEXT_LINE
},
370 { KEY_HOME
, REQ_FIRST_LINE
},
371 { KEY_END
, REQ_LAST_LINE
},
372 { KEY_NPAGE
, REQ_NEXT_PAGE
},
373 { KEY_PPAGE
, REQ_PREV_PAGE
},
376 { KEY_IC
, REQ_SCR_BLINE
}, /* scroll field backward a line */
377 { KEY_DC
, REQ_SCR_FLINE
}, /* scroll field forward a line */
378 { 's', REQ_SCR_FPAGE
}, /* scroll field forward a page */
379 { 'w', REQ_SCR_BPAGE
}, /* scroll field backward a page */
385 { 'n', REQ_LINE_NUMBER
},
387 /* No input from wgetch() with nodelay() enabled. */
390 { KEY_ESC
, REQ_QUIT
},
393 { 'v', REQ_VERSION
},
398 get_request(int request
)
402 for (i
= 0; i
< ARRAY_SIZE(keymap
); i
++)
403 if (keymap
[i
].alias
== request
)
404 return keymap
[i
].request
;
421 int (*read
)(struct view
*, char *);
422 int (*draw
)(struct view
*, unsigned int);
423 size_t objsize
; /* Size of objects in the line index */
429 unsigned long offset
; /* Offset of the window top */
430 unsigned long lineno
; /* Current line number */
433 unsigned long lines
; /* Total number of lines */
434 void **line
; /* Line index */
440 static int pager_draw(struct view
*view
, unsigned int lineno
);
441 static int pager_read(struct view
*view
, char *line
);
443 static int main_draw(struct view
*view
, unsigned int lineno
);
444 static int main_read(struct view
*view
, char *line
);
447 "git log --stat -n1 %s ; echo; " \
448 "git diff --find-copies-harder -B -C %s^ %s"
451 "git log --stat -n100 %s"
454 "git log --stat --pretty=raw %s"
456 /* The status window is used for polling keystrokes. */
457 static WINDOW
*status_win
;
458 static WINDOW
*title_win
;
460 /* The number of loading views. Controls when nodelay should be in effect when
461 * polling user input. */
462 static unsigned int nloading
;
464 static struct view views
[] = {
465 { "diff", DIFF_CMD
, commit_id
, pager_read
, pager_draw
, sizeof(char) },
466 { "log", LOG_CMD
, head_id
, pager_read
, pager_draw
, sizeof(char) },
467 { "main", MAIN_CMD
, head_id
, main_read
, main_draw
, sizeof(struct commit
) },
470 /* The display array of active views and the index of the current view. */
471 static struct view
*display
[ARRAY_SIZE(views
)];
472 static unsigned int current_view
;
474 #define foreach_view(view, i) \
475 for (i = 0; i < sizeof(display) && (view = display[i]); i++)
479 redraw_view(struct view
*view
)
484 wmove(view
->win
, 0, 0);
486 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
487 if (!view
->draw(view
, lineno
))
491 redrawwin(view
->win
);
496 resize_view(struct view
*view
)
500 getmaxyx(stdscr
, lines
, cols
);
503 mvwin(view
->win
, 0, 0);
504 wresize(view
->win
, lines
- 2, cols
);
507 view
->win
= newwin(lines
- 2, 0, 0, 0);
509 report("failed to create %s view", view
->name
);
512 scrollok(view
->win
, TRUE
);
515 getmaxyx(view
->win
, view
->height
, view
->width
);
518 /* FIXME: Fix percentage. */
520 report_position(struct view
*view
, int all
)
522 report(all ?
"line %d of %d (%d%%)"
526 view
->lines ?
(view
->lineno
+ 1) * 100 / view
->lines
: 0);
531 move_view(struct view
*view
, int lines
)
533 /* The rendering expects the new offset. */
534 view
->offset
+= lines
;
536 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
539 if (view
->height
< ABS(lines
)) {
543 int line
= lines
> 0 ? view
->height
- lines
: 0;
544 int end
= line
+ (lines
> 0 ? lines
: -lines
);
546 wscrl(view
->win
, lines
);
548 for (; line
< end
; line
++) {
549 if (!view
->draw(view
, line
))
554 /* Move current line into the view. */
555 if (view
->lineno
< view
->offset
) {
556 view
->lineno
= view
->offset
;
559 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
560 view
->lineno
= view
->offset
+ view
->height
- 1;
561 view
->draw(view
, view
->lineno
- view
->offset
);
564 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
566 redrawwin(view
->win
);
569 report_position(view
, lines
);
573 scroll_view(struct view
*view
, int request
)
579 lines
= view
->height
;
581 if (view
->offset
+ lines
> view
->lines
)
582 lines
= view
->lines
- view
->offset
;
584 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
585 report("already at last line");
591 lines
= view
->height
;
593 if (lines
> view
->offset
)
594 lines
= view
->offset
;
597 report("already at first line");
605 move_view(view
, lines
);
609 navigate_view(struct view
*view
, int request
)
615 steps
= -view
->lineno
;
619 steps
= view
->lines
- view
->lineno
- 1;
623 steps
= view
->height
> view
->lineno
624 ?
-view
->lineno
: -view
->height
;
628 steps
= view
->lineno
+ view
->height
>= view
->lines
629 ? view
->lines
- view
->lineno
- 1 : view
->height
;
641 if (steps
<= 0 && view
->lineno
== 0) {
642 report("already at first line");
645 } else if (steps
>= 0 && view
->lineno
+ 1 == view
->lines
) {
646 report("already at last line");
650 /* Move the current line */
651 view
->lineno
+= steps
;
652 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
654 /* Repaint the old "current" line if we be scrolling */
655 if (ABS(steps
) < view
->height
)
656 view
->draw(view
, view
->lineno
- steps
- view
->offset
);
658 /* Check whether the view needs to be scrolled */
659 if (view
->lineno
< view
->offset
||
660 view
->lineno
>= view
->offset
+ view
->height
) {
661 if (steps
< 0 && -steps
> view
->offset
) {
662 steps
= -view
->offset
;
664 } else if (steps
> 0) {
665 if (view
->lineno
== view
->lines
- 1 &&
666 view
->lines
> view
->height
) {
667 steps
= view
->lines
- view
->offset
- 1;
668 if (steps
>= view
->height
)
669 steps
-= view
->height
- 1;
673 move_view(view
, steps
);
677 /* Draw the current line */
678 view
->draw(view
, view
->lineno
- view
->offset
);
680 redrawwin(view
->win
);
683 report_position(view
, view
->height
);
689 begin_update(struct view
*view
)
696 if (snprintf(buf
, sizeof(buf
), view
->cmd
, id
, id
, id
) < sizeof(buf
))
697 view
->pipe
= popen(buf
, "r");
703 nodelay(status_win
, TRUE
);
706 display
[current_view
] = view
;
716 end_update(struct view
*view
)
718 wattrset(view
->win
, A_NORMAL
);
723 nodelay(status_win
, FALSE
);
727 update_view(struct view
*view
)
733 int lines
= view
->height
;
738 /* Only redraw after the first reading session. */
739 /* FIXME: ... and possibly more. */
740 redraw
= view
->height
> view
->lines
;
742 tmp
= realloc(view
->line
, sizeof(*view
->line
) * (view
->lines
+ lines
));
748 while ((line
= fgets(buffer
, sizeof(buffer
), view
->pipe
))) {
751 linelen
= strlen(line
);
753 line
[linelen
- 1] = 0;
755 if (!view
->read(view
, line
))
763 /* FIXME: This causes flickering. Draw incrementally. */
767 if (ferror(view
->pipe
)) {
768 report("failed to read %s: %s", view
->cmd
, strerror(errno
));
771 } else if (feof(view
->pipe
)) {
772 report_position(view
, 0);
779 report("allocation failure");
788 switch_view(struct view
*prev
, int request
)
790 struct view
*view
= &views
[VIEW_OFFSET(request
)];
791 struct view
*displayed
;
795 foreach_view (displayed
, i
) ;
798 report("already in %s view", view
->name
);
800 report("FIXME: Maximize");
805 foreach_view (displayed
, i
) {
806 if (view
== displayed
) {
808 report("new current view");
820 for (i
= 0; i
< view
->lines
; i
++)
828 if (prev
&& prev
->pipe
)
831 if (begin_update(view
)) {
835 report("loading...");
842 /* Process a keystroke */
844 view_driver(struct view
*view
, int key
)
846 int request
= get_request(key
);
857 navigate_view(view
, request
);
865 scroll_view(view
, request
);
871 view
= switch_view(view
, request
);
874 case REQ_LINE_NUMBER
:
875 opt_line_number
= !opt_line_number
;
884 foreach_view (view
, i
) {
887 scroll_view(view
, 0);
893 report("version %s", VERSION
);
917 pager_draw(struct view
*view
, unsigned int lineno
)
924 if (view
->offset
+ lineno
>= view
->lines
)
927 line
= view
->line
[view
->offset
+ lineno
];
928 type
= get_line_type(line
);
930 if (view
->offset
+ lineno
== view
->lineno
) {
931 if (type
== LINE_COMMIT
)
932 strncpy(commit_id
, line
+ 7, SIZEOF_ID
);
936 attr
= get_line_attr(type
);
937 wattrset(view
->win
, attr
);
939 linelen
= strlen(line
);
940 linelen
= MIN(linelen
, view
->width
);
942 if (opt_line_number
) {
943 wmove(view
->win
, lineno
, 0);
944 lineno
+= view
->offset
+ 1;
945 if (lineno
== 1 || (lineno
% 10) == 0)
946 wprintw(view
->win
, "%4d: ", lineno
);
948 wprintw(view
->win
, " : ", lineno
);
952 waddstr(view
->win
, " ");
955 char *tab
= strchr(line
, '\t');
958 waddnstr(view
->win
, line
, tab
- line
);
960 waddstr(view
->win
, line
);
964 waddstr(view
->win
, line
);
967 /* No empty lines makes cursor drawing and clearing implicit. */
969 line
= " ", linelen
= 1;
970 mvwaddnstr(view
->win
, lineno
, 0, line
, linelen
);
977 pager_read(struct view
*view
, char *line
)
979 view
->line
[view
->lines
] = strdup(line
);
980 if (!view
->line
[view
->lines
])
988 main_draw(struct view
*view
, unsigned int lineno
)
991 struct commit
*commit
;
996 if (view
->offset
+ lineno
>= view
->lines
)
999 commit
= view
->line
[view
->offset
+ lineno
];
1000 if (!*commit
->author
)
1003 if (view
->offset
+ lineno
== view
->lineno
) {
1004 strncpy(commit_id
, commit
->id
, SIZEOF_ID
);
1007 type
= LINE_MAIN_COMMIT
;
1010 wmove(view
->win
, lineno
, cols
);
1011 wattrset(view
->win
, get_line_attr(LINE_MAIN_DATE
));
1013 timelen
= strftime(buf
, sizeof(buf
), DATE_FORMAT
, &commit
->time
);
1014 waddnstr(view
->win
, buf
, timelen
);
1015 waddstr(view
->win
, " ");
1018 wmove(view
->win
, lineno
, cols
);
1019 wattrset(view
->win
, get_line_attr(LINE_MAIN_AUTHOR
));
1021 if (strlen(commit
->author
) > 19) {
1022 waddnstr(view
->win
, commit
->author
, 18);
1023 wattrset(view
->win
, get_line_attr(LINE_MAIN_DELIM
));
1024 waddch(view
->win
, '~');
1026 waddstr(view
->win
, commit
->author
);
1030 wattrset(view
->win
, A_NORMAL
);
1031 mvwaddch(view
->win
, lineno
, cols
, ACS_LTEE
);
1032 wattrset(view
->win
, get_line_attr(type
));
1033 mvwaddstr(view
->win
, lineno
, cols
+ 2, commit
->title
);
1034 wattrset(view
->win
, A_NORMAL
);
1039 /* Reads git log --pretty=raw output and parses it into the commit struct. */
1041 main_read(struct view
*view
, char *line
)
1043 enum line_type type
= get_line_type(line
);
1044 struct commit
*commit
;
1048 commit
= calloc(1, sizeof(struct commit
));
1052 line
+= STRING_SIZE("commit ");
1054 view
->line
[view
->lines
++] = commit
;
1055 strncpy(commit
->id
, line
, sizeof(commit
->id
));
1058 case LINE_AUTHOR_IDENT
:
1060 char *ident
= line
+ STRING_SIZE("author ");
1061 char *end
= strchr(ident
, '<');
1064 for (; end
> ident
&& isspace(end
[-1]); end
--) ;
1068 commit
= view
->line
[view
->lines
- 1];
1069 strncpy(commit
->author
, ident
, sizeof(commit
->author
));
1071 /* Parse epoch and timezone */
1073 char *secs
= strchr(end
+ 1, '>');
1077 if (!secs
|| secs
[1] != ' ')
1081 time
= (time_t) atol(secs
);
1082 zone
= strchr(secs
, ' ');
1083 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700")) {
1087 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
1088 tz
+= ('0' - zone
[2]) * 60 * 60;
1089 tz
+= ('0' - zone
[3]) * 60;
1090 tz
+= ('0' - zone
[4]) * 60;
1097 gmtime_r(&time
, &commit
->time
);
1102 /* Fill in the commit title */
1103 commit
= view
->line
[view
->lines
- 1];
1104 if (!commit
->title
[0] &&
1105 !strncmp(line
, " ", 4) &&
1107 strncpy(commit
->title
, line
+ 4, sizeof(commit
->title
));
1127 /* FIXME: Shutdown gracefully. */
1132 static void die(const char *err
, ...)
1138 va_start(args
, err
);
1139 fputs("tig: ", stderr
);
1140 vfprintf(stderr
, err
, args
);
1141 fputs("\n", stderr
);
1148 report(const char *msg
, ...)
1153 wmove(title_win
, 0, 0);
1154 wprintw(title_win
, "commit %s", commit_id
);
1155 wrefresh(title_win
);
1157 va_start(args
, msg
);
1160 wmove(status_win
, 0, 0);
1163 if (display
[current_view
])
1164 wprintw(status_win
, "%s %4s: ", commit_id
, display
[current_view
]->name
);
1166 vwprintw(status_win
, msg
, args
);
1167 wrefresh(status_win
);
1173 main(int argc
, char *argv
[])
1179 signal(SIGINT
, quit
);
1181 git_cmd
= parse_options(argc
, argv
);
1185 request
= opt_request
;
1187 initscr(); /* initialize the curses library */
1188 nonl(); /* tell curses not to do NL->CR/NL on output */
1189 cbreak(); /* take input chars one at a time, no wait for \n */
1190 noecho(); /* don't echo input */
1191 leaveok(stdscr
, TRUE
);
1197 getmaxyx(stdscr
, y
, x
);
1198 status_win
= newwin(1, 0, y
- 1, 0);
1200 die("Failed to create status window");
1202 title_win
= newwin(1, 0, y
- 2, 0);
1204 die("Failed to create title window");
1206 /* Enable keyboard mapping */
1207 keypad(status_win
, TRUE
);
1208 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
1209 wbkgdset(title_win
, get_line_attr(LINE_TITLE
));
1211 while (view_driver(display
[current_view
], request
)) {
1215 foreach_view (view
, i
) {
1221 /* Refresh, accept single keystroke of input */
1222 request
= wgetch(status_win
);
1223 if (request
== KEY_RESIZE
) {
1226 getmaxyx(stdscr
, lines
, cols
);
1228 mvwin(status_win
, lines
- 1, 0);
1229 wresize(status_win
, 1, cols
- 1);
1231 mvwin(title_win
, lines
- 2, 0);
1232 wresize(title_win
, 1, cols
- 1);
1244 * Known bugs and problems:
1246 * Redrawing of the main view while loading::
1247 * If only part of a commit has been parsed not all fields will be visible
1248 * or even redrawn when the whole commit have loaded. This can be
1249 * triggered when continuously moving to the last line. Use 'r' to redraw
1254 * Features that should be explored.
1256 * - Proper command line handling; ability to take the command that should be
1261 * - Internal command line (exmode-inspired) which allows to specify what git
1262 * log or git diff command to run. Example:
1266 * - Proper resizing support. I am yet to figure out whether catching SIGWINCH
1267 * is preferred over using ncurses' built-in support for resizing.
1273 * Copyright (c) Jonas Fonseca, 2006
1275 * This program is free software; you can redistribute it and/or modify
1276 * it under the terms of the GNU General Public License as published by
1277 * the Free Software Foundation; either version 2 of the License, or
1278 * (at your option) any later version.
1283 * link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
1284 * link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
1285 * gitk(1): git repository browser written using tcl/tk,
1286 * gitview(1): git repository browser written using python/gtk.