1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
37 #include <sys/select.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
63 #define __NORETURN __attribute__((__noreturn__))
68 static void __NORETURN
die(const char *err
, ...);
69 static void warn(const char *msg
, ...);
70 static void report(const char *msg
, ...);
71 static void set_nonblocking_input(bool loading
);
72 static size_t utf8_length(const char **string
, size_t col
, int *width
, size_t max_width
, int *trimmed
, bool reserve
);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define MAX(x, y) ((x) > (y) ? (x) : (y))
78 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x) (sizeof(x) - 1)
81 #define SIZEOF_STR 1024 /* Default string size. */
82 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG 32 /* Default argument array size. */
88 #define REVGRAPH_INIT 'I'
89 #define REVGRAPH_MERGE 'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND '^'
94 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT (-1)
99 #define ICONV_NONE ((iconv_t) -1)
101 #define ICONV_CONST /* nothing */
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT "%Y-%m-%d %H:%M"
106 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
111 #define MIN_VIEW_HEIGHT 4
113 #define NULL_ID "0000000000000000000000000000000000000000"
115 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
117 /* Some ASCII-shorthands fitted into the ncurses namespace. */
119 #define KEY_RETURN '\r'
124 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
125 unsigned int head
:1; /* Is it the current HEAD? */
126 unsigned int tag
:1; /* Is it a tag? */
127 unsigned int ltag
:1; /* If so, is the tag local? */
128 unsigned int remote
:1; /* Is it a remote ref? */
129 unsigned int tracked
:1; /* Is it the remote for the current HEAD? */
130 char name
[1]; /* Ref name; tag or head names are shortened. */
134 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
135 size_t size
; /* Number of refs. */
136 struct ref
**refs
; /* References for this ID. */
139 static struct ref_list
*get_ref_list(const char *id
);
140 static void foreach_ref(bool (*visitor
)(void *data
, struct ref
*ref
), void *data
);
141 static int load_refs(void);
144 FORMAT_ALL
, /* Perform replacement in all arguments. */
145 FORMAT_DASH
, /* Perform replacement up until "--". */
146 FORMAT_NONE
/* No replacement should be performed. */
149 static bool format_argv(const char *dst
[], const char *src
[], enum format_flags flags
);
158 typedef enum input_status (*input_handler
)(void *data
, char *buf
, int c
);
160 static char *prompt_input(const char *prompt
, input_handler handler
, void *data
);
161 static bool prompt_yesno(const char *prompt
);
169 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
);
172 * Allocation helpers ... Entering macro hell to never be seen again.
175 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
177 name(type **mem, size_t size, size_t increase) \
179 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
180 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
183 if (mem == NULL || num_chunks != num_chunks_new) { \
184 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
197 string_ncopy_do(char *dst
, size_t dstlen
, const char *src
, size_t srclen
)
199 if (srclen
> dstlen
- 1)
202 strncpy(dst
, src
, srclen
);
206 /* Shorthands for safely copying into a fixed buffer. */
208 #define string_copy(dst, src) \
209 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
211 #define string_ncopy(dst, src, srclen) \
212 string_ncopy_do(dst, sizeof(dst), src, srclen)
214 #define string_copy_rev(dst, src) \
215 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
217 #define string_add(dst, from, src) \
218 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
221 string_expand(char *dst
, size_t dstlen
, const char *src
, int tabsize
)
225 for (size
= pos
= 0; size
< dstlen
- 1 && src
[pos
]; pos
++) {
226 if (src
[pos
] == '\t') {
227 size_t expanded
= tabsize
- (size
% tabsize
);
229 if (expanded
+ size
>= dstlen
- 1)
230 expanded
= dstlen
- size
- 1;
231 memcpy(dst
+ size
, " ", expanded
);
234 dst
[size
++] = src
[pos
];
242 chomp_string(char *name
)
246 while (isspace(*name
))
249 namelen
= strlen(name
) - 1;
250 while (namelen
> 0 && isspace(name
[namelen
]))
257 string_nformat(char *buf
, size_t bufsize
, size_t *bufpos
, const char *fmt
, ...)
260 size_t pos
= bufpos
? *bufpos
: 0;
263 pos
+= vsnprintf(buf
+ pos
, bufsize
- pos
, fmt
, args
);
269 return pos
>= bufsize
? FALSE
: TRUE
;
272 #define string_format(buf, fmt, args...) \
273 string_nformat(buf, sizeof(buf), NULL, fmt, args)
275 #define string_format_from(buf, from, fmt, args...) \
276 string_nformat(buf, sizeof(buf), from, fmt, args)
279 string_enum_compare(const char *str1
, const char *str2
, int len
)
283 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
285 /* Diff-Header == DIFF_HEADER */
286 for (i
= 0; i
< len
; i
++) {
287 if (toupper(str1
[i
]) == toupper(str2
[i
]))
290 if (string_enum_sep(str1
[i
]) &&
291 string_enum_sep(str2
[i
]))
294 return str1
[i
] - str2
[i
];
306 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
309 map_enum_do(const struct enum_map
*map
, size_t map_size
, int *value
, const char *name
)
311 size_t namelen
= strlen(name
);
314 for (i
= 0; i
< map_size
; i
++)
315 if (namelen
== map
[i
].namelen
&&
316 !string_enum_compare(name
, map
[i
].name
, namelen
)) {
317 *value
= map
[i
].value
;
324 #define map_enum(attr, map, name) \
325 map_enum_do(map, ARRAY_SIZE(map), attr, name)
327 #define prefixcmp(str1, str2) \
328 strncmp(str1, str2, STRING_SIZE(str2))
331 suffixcmp(const char *str
, int slen
, const char *suffix
)
333 size_t len
= slen
>= 0 ? slen
: strlen(str
);
334 size_t suffixlen
= strlen(suffix
);
336 return suffixlen
< len
? strcmp(str
+ len
- suffixlen
, suffix
) : -1;
341 * What value of "tz" was in effect back then at "time" in the
344 static int local_tzoffset(time_t time
)
348 int offset
, eastwest
;
351 localtime_r(&t
, &tm
);
352 t_local
= mktime(&tm
);
356 offset
= t
- t_local
;
359 offset
= t_local
- t
;
361 offset
/= 60; /* in minutes */
362 offset
= (offset
% 60) + ((offset
/ 60) * 100);
363 return offset
* eastwest
;
374 string_date(const time_t *time
, enum date date
)
376 static char buf
[DATE_COLS
+ 1];
377 static const struct enum_map reldate
[] = {
378 { "second", 1, 60 * 2 },
379 { "minute", 60, 60 * 60 * 2 },
380 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
381 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
382 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
383 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
387 if (date
== DATE_RELATIVE
) {
389 time_t date
= *time
+ local_tzoffset(*time
);
393 gettimeofday(&now
, NULL
);
394 seconds
= now
.tv_sec
< date
? date
- now
.tv_sec
: now
.tv_sec
- date
;
395 for (i
= 0; i
< ARRAY_SIZE(reldate
); i
++) {
396 if (seconds
>= reldate
[i
].value
)
399 seconds
/= reldate
[i
].namelen
;
400 if (!string_format(buf
, "%ld %s%s %s",
401 seconds
, reldate
[i
].name
,
402 seconds
> 1 ? "s" : "",
403 now
.tv_sec
>= date
? "ago" : "ahead"))
410 return strftime(buf
, sizeof(buf
), DATE_FORMAT
, &tm
) ? buf
: NULL
;
415 argv_from_string(const char *argv
[SIZEOF_ARG
], int *argc
, char *cmd
)
419 while (*cmd
&& *argc
< SIZEOF_ARG
&& (valuelen
= strcspn(cmd
, " \t"))) {
420 bool advance
= cmd
[valuelen
] != 0;
423 argv
[(*argc
)++] = chomp_string(cmd
);
424 cmd
= chomp_string(cmd
+ valuelen
+ advance
);
427 if (*argc
< SIZEOF_ARG
)
429 return *argc
< SIZEOF_ARG
;
433 argv_from_env(const char **argv
, const char *name
)
435 char *env
= argv
? getenv(name
) : NULL
;
440 if (env
&& !argv_from_string(argv
, &argc
, env
))
441 die("Too many arguments in the `%s` environment variable", name
);
446 * Executing external commands.
450 IO_FD
, /* File descriptor based IO. */
451 IO_BG
, /* Execute command in the background. */
452 IO_FG
, /* Execute command with same std{in,out,err}. */
453 IO_RD
, /* Read only fork+exec IO. */
454 IO_WR
, /* Write only fork+exec IO. */
455 IO_AP
, /* Append fork+exec output to file. */
459 enum io_type type
; /* The requested type of pipe. */
460 const char *dir
; /* Directory from which to execute. */
461 pid_t pid
; /* Pipe for reading or writing. */
462 int pipe
; /* Pipe end for reading or writing. */
463 int error
; /* Error status. */
464 const char *argv
[SIZEOF_ARG
]; /* Shell command arguments. */
465 char *buf
; /* Read buffer. */
466 size_t bufalloc
; /* Allocated buffer size. */
467 size_t bufsize
; /* Buffer content size. */
468 char *bufpos
; /* Current buffer position. */
469 unsigned int eof
:1; /* Has end of file been reached. */
473 reset_io(struct io
*io
)
477 io
->buf
= io
->bufpos
= NULL
;
478 io
->bufalloc
= io
->bufsize
= 0;
484 init_io(struct io
*io
, const char *dir
, enum io_type type
)
492 init_io_rd(struct io
*io
, const char *argv
[], const char *dir
,
493 enum format_flags flags
)
495 init_io(io
, dir
, IO_RD
);
496 return format_argv(io
->argv
, argv
, flags
);
500 io_open(struct io
*io
, const char *fmt
, ...)
502 char name
[SIZEOF_STR
] = "";
506 init_io(io
, NULL
, IO_FD
);
509 fits
= vsnprintf(name
, sizeof(name
), fmt
, args
) < sizeof(name
);
513 io
->error
= ENAMETOOLONG
;
516 io
->pipe
= *name
? open(name
, O_RDONLY
) : STDIN_FILENO
;
519 return io
->pipe
!= -1;
523 kill_io(struct io
*io
)
525 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
529 done_io(struct io
*io
)
540 pid_t waiting
= waitpid(pid
, &status
, 0);
545 report("waitpid failed (%s)", strerror(errno
));
549 return waiting
== pid
&&
550 !WIFSIGNALED(status
) &&
552 !WEXITSTATUS(status
);
559 start_io(struct io
*io
)
561 int pipefds
[2] = { -1, -1 };
563 if (io
->type
== IO_FD
)
566 if ((io
->type
== IO_RD
|| io
->type
== IO_WR
) &&
569 else if (io
->type
== IO_AP
)
570 pipefds
[1] = io
->pipe
;
572 if ((io
->pid
= fork())) {
573 if (pipefds
[!(io
->type
== IO_WR
)] != -1)
574 close(pipefds
[!(io
->type
== IO_WR
)]);
576 io
->pipe
= pipefds
[!!(io
->type
== IO_WR
)];
581 if (io
->type
!= IO_FG
) {
582 int devnull
= open("/dev/null", O_RDWR
);
583 int readfd
= io
->type
== IO_WR
? pipefds
[0] : devnull
;
584 int writefd
= (io
->type
== IO_RD
|| io
->type
== IO_AP
)
585 ? pipefds
[1] : devnull
;
587 dup2(readfd
, STDIN_FILENO
);
588 dup2(writefd
, STDOUT_FILENO
);
589 dup2(devnull
, STDERR_FILENO
);
592 if (pipefds
[0] != -1)
594 if (pipefds
[1] != -1)
598 if (io
->dir
&& *io
->dir
&& chdir(io
->dir
) == -1)
599 die("Failed to change directory: %s", strerror(errno
));
601 execvp(io
->argv
[0], (char *const*) io
->argv
);
602 die("Failed to execute program: %s", strerror(errno
));
605 if (pipefds
[!!(io
->type
== IO_WR
)] != -1)
606 close(pipefds
[!!(io
->type
== IO_WR
)]);
611 run_io(struct io
*io
, const char **argv
, const char *dir
, enum io_type type
)
613 init_io(io
, dir
, type
);
614 if (!format_argv(io
->argv
, argv
, FORMAT_NONE
))
620 run_io_do(struct io
*io
)
622 return start_io(io
) && done_io(io
);
626 run_io_bg(const char **argv
)
630 init_io(&io
, NULL
, IO_BG
);
631 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
633 return run_io_do(&io
);
637 run_io_fg(const char **argv
, const char *dir
)
641 init_io(&io
, dir
, IO_FG
);
642 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
644 return run_io_do(&io
);
648 run_io_append(const char **argv
, enum format_flags flags
, int fd
)
652 init_io(&io
, NULL
, IO_AP
);
654 if (format_argv(io
.argv
, argv
, flags
))
655 return run_io_do(&io
);
661 run_io_rd(struct io
*io
, const char **argv
, const char *dir
, enum format_flags flags
)
663 return init_io_rd(io
, argv
, dir
, flags
) && start_io(io
);
667 io_eof(struct io
*io
)
673 io_error(struct io
*io
)
679 io_strerror(struct io
*io
)
681 return strerror(io
->error
);
685 io_can_read(struct io
*io
)
687 struct timeval tv
= { 0, 500 };
691 FD_SET(io
->pipe
, &fds
);
693 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
697 io_read(struct io
*io
, void *buf
, size_t bufsize
)
700 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
702 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
704 else if (readsize
== -1)
706 else if (readsize
== 0)
712 DEFINE_ALLOCATOR(realloc_io_buf
, char, BUFSIZ
)
715 io_get(struct io
*io
, int c
, bool can_read
)
721 if (io
->bufsize
> 0) {
722 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
724 char *line
= io
->bufpos
;
727 io
->bufpos
= eol
+ 1;
728 io
->bufsize
-= io
->bufpos
- line
;
735 io
->bufpos
[io
->bufsize
] = 0;
745 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
746 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
748 if (io
->bufalloc
== io
->bufsize
) {
749 if (!realloc_io_buf(&io
->buf
, io
->bufalloc
, BUFSIZ
))
751 io
->bufalloc
+= BUFSIZ
;
754 io
->bufpos
= io
->buf
;
755 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
758 io
->bufsize
+= readsize
;
763 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
767 while (!io_error(io
) && written
< bufsize
) {
770 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
771 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
779 return written
== bufsize
;
783 io_read_buf(struct io
*io
, char buf
[], size_t bufsize
)
785 char *result
= io_get(io
, '\n', TRUE
);
788 result
= chomp_string(result
);
789 string_ncopy_do(buf
, bufsize
, result
, strlen(result
));
792 return done_io(io
) && result
;
796 run_io_buf(const char **argv
, char buf
[], size_t bufsize
)
800 return run_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
801 && io_read_buf(&io
, buf
, bufsize
);
805 io_load(struct io
*io
, const char *separators
,
806 int (*read_property
)(char *, size_t, char *, size_t))
814 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
819 name
= chomp_string(name
);
820 namelen
= strcspn(name
, separators
);
824 value
= chomp_string(name
+ namelen
+ 1);
825 valuelen
= strlen(value
);
832 state
= read_property(name
, namelen
, value
, valuelen
);
835 if (state
!= ERR
&& io_error(io
))
843 run_io_load(const char **argv
, const char *separators
,
844 int (*read_property
)(char *, size_t, char *, size_t))
848 return init_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
849 ? io_load(&io
, separators
, read_property
) : ERR
;
858 /* XXX: Keep the view request first and in sync with views[]. */ \
859 REQ_GROUP("View switching") \
860 REQ_(VIEW_MAIN, "Show main view"), \
861 REQ_(VIEW_DIFF, "Show diff view"), \
862 REQ_(VIEW_LOG, "Show log view"), \
863 REQ_(VIEW_TREE, "Show tree view"), \
864 REQ_(VIEW_BLOB, "Show blob view"), \
865 REQ_(VIEW_BLAME, "Show blame view"), \
866 REQ_(VIEW_BRANCH, "Show branch view"), \
867 REQ_(VIEW_HELP, "Show help page"), \
868 REQ_(VIEW_PAGER, "Show pager view"), \
869 REQ_(VIEW_STATUS, "Show status view"), \
870 REQ_(VIEW_STAGE, "Show stage view"), \
872 REQ_GROUP("View manipulation") \
873 REQ_(ENTER, "Enter current line and scroll"), \
874 REQ_(NEXT, "Move to next"), \
875 REQ_(PREVIOUS, "Move to previous"), \
876 REQ_(PARENT, "Move to parent"), \
877 REQ_(VIEW_NEXT, "Move focus to next view"), \
878 REQ_(REFRESH, "Reload and refresh"), \
879 REQ_(MAXIMIZE, "Maximize the current view"), \
880 REQ_(VIEW_CLOSE, "Close the current view"), \
881 REQ_(QUIT, "Close all views and quit"), \
883 REQ_GROUP("View specific requests") \
884 REQ_(STATUS_UPDATE, "Update file status"), \
885 REQ_(STATUS_REVERT, "Revert file changes"), \
886 REQ_(STATUS_MERGE, "Merge file using external tool"), \
887 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
889 REQ_GROUP("Cursor navigation") \
890 REQ_(MOVE_UP, "Move cursor one line up"), \
891 REQ_(MOVE_DOWN, "Move cursor one line down"), \
892 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
893 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
894 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
895 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
897 REQ_GROUP("Scrolling") \
898 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
899 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
900 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
901 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
902 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
903 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
905 REQ_GROUP("Searching") \
906 REQ_(SEARCH, "Search the view"), \
907 REQ_(SEARCH_BACK, "Search backwards in the view"), \
908 REQ_(FIND_NEXT, "Find next search match"), \
909 REQ_(FIND_PREV, "Find previous search match"), \
911 REQ_GROUP("Option manipulation") \
912 REQ_(OPTIONS, "Open option menu"), \
913 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
914 REQ_(TOGGLE_DATE, "Toggle date display"), \
915 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
916 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
917 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
918 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
919 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
920 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
923 REQ_(PROMPT, "Bring up the prompt"), \
924 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
925 REQ_(SHOW_VERSION, "Show version information"), \
926 REQ_(STOP_LOADING, "Stop all loading views"), \
927 REQ_(EDIT, "Open in editor"), \
928 REQ_(NONE, "Do nothing")
931 /* User action requests. */
933 #define REQ_GROUP(help)
934 #define REQ_(req, help) REQ_##req
936 /* Offset all requests to avoid conflicts with ncurses getch values. */
937 REQ_OFFSET
= KEY_MAX
+ 1,
944 struct request_info
{
945 enum request request
;
951 static const struct request_info req_info
[] = {
952 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
953 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
960 get_request(const char *name
)
962 int namelen
= strlen(name
);
965 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
966 if (req_info
[i
].namelen
== namelen
&&
967 !string_enum_compare(req_info
[i
].name
, name
, namelen
))
968 return req_info
[i
].request
;
978 /* Option and state variables. */
979 static enum date opt_date
= DATE_DEFAULT
;
980 static bool opt_author
= TRUE
;
981 static bool opt_line_number
= FALSE
;
982 static bool opt_line_graphics
= TRUE
;
983 static bool opt_rev_graph
= FALSE
;
984 static bool opt_show_refs
= TRUE
;
985 static int opt_num_interval
= 5;
986 static double opt_hscroll
= 0.50;
987 static double opt_scale_split_view
= 2.0 / 3.0;
988 static int opt_tab_size
= 8;
989 static int opt_author_cols
= 19;
990 static char opt_path
[SIZEOF_STR
] = "";
991 static char opt_file
[SIZEOF_STR
] = "";
992 static char opt_ref
[SIZEOF_REF
] = "";
993 static char opt_head
[SIZEOF_REF
] = "";
994 static char opt_head_rev
[SIZEOF_REV
] = "";
995 static char opt_remote
[SIZEOF_REF
] = "";
996 static char opt_encoding
[20] = "UTF-8";
997 static bool opt_utf8
= TRUE
;
998 static char opt_codeset
[20] = "UTF-8";
999 static iconv_t opt_iconv
= ICONV_NONE
;
1000 static char opt_search
[SIZEOF_STR
] = "";
1001 static char opt_cdup
[SIZEOF_STR
] = "";
1002 static char opt_prefix
[SIZEOF_STR
] = "";
1003 static char opt_git_dir
[SIZEOF_STR
] = "";
1004 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
1005 static char opt_editor
[SIZEOF_STR
] = "";
1006 static FILE *opt_tty
= NULL
;
1008 #define is_initial_commit() (!*opt_head_rev)
1009 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1010 #define mkdate(time) string_date(time, opt_date)
1014 * Line-oriented content detection.
1018 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1019 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1020 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1021 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1022 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1023 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1024 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1025 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1026 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1027 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1028 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1029 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1030 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1031 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1032 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1033 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1034 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1035 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1036 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1037 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1038 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1039 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1040 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1041 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1042 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1043 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1044 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1045 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1046 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1047 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1048 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1049 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1050 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1051 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1052 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1053 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1054 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1055 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1056 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1057 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1058 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1059 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1060 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1061 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1062 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1063 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1064 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1065 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1066 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1067 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1068 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1069 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1070 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1071 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1072 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1073 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1074 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1077 #define LINE(type, line, fg, bg, attr) \
1085 const char *name
; /* Option name. */
1086 int namelen
; /* Size of option name. */
1087 const char *line
; /* The start of line to match. */
1088 int linelen
; /* Size of string to match. */
1089 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1092 static struct line_info line_info
[] = {
1093 #define LINE(type, line, fg, bg, attr) \
1094 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1099 static enum line_type
1100 get_line_type(const char *line
)
1102 int linelen
= strlen(line
);
1103 enum line_type type
;
1105 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1106 /* Case insensitive search matches Signed-off-by lines better. */
1107 if (linelen
>= line_info
[type
].linelen
&&
1108 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1111 return LINE_DEFAULT
;
1115 get_line_attr(enum line_type type
)
1117 assert(type
< ARRAY_SIZE(line_info
));
1118 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1121 static struct line_info
*
1122 get_line_info(const char *name
)
1124 size_t namelen
= strlen(name
);
1125 enum line_type type
;
1127 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1128 if (namelen
== line_info
[type
].namelen
&&
1129 !string_enum_compare(line_info
[type
].name
, name
, namelen
))
1130 return &line_info
[type
];
1138 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1139 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1140 enum line_type type
;
1144 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1145 default_bg
= COLOR_BLACK
;
1146 default_fg
= COLOR_WHITE
;
1149 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1150 struct line_info
*info
= &line_info
[type
];
1151 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1152 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1154 init_pair(type
, fg
, bg
);
1159 enum line_type type
;
1162 unsigned int selected
:1;
1163 unsigned int dirty
:1;
1164 unsigned int cleareol
:1;
1165 unsigned int other
:16;
1167 void *data
; /* User data */
1177 enum request request
;
1180 static const struct keybinding default_keybindings
[] = {
1181 /* View switching */
1182 { 'm', REQ_VIEW_MAIN
},
1183 { 'd', REQ_VIEW_DIFF
},
1184 { 'l', REQ_VIEW_LOG
},
1185 { 't', REQ_VIEW_TREE
},
1186 { 'f', REQ_VIEW_BLOB
},
1187 { 'B', REQ_VIEW_BLAME
},
1188 { 'H', REQ_VIEW_BRANCH
},
1189 { 'p', REQ_VIEW_PAGER
},
1190 { 'h', REQ_VIEW_HELP
},
1191 { 'S', REQ_VIEW_STATUS
},
1192 { 'c', REQ_VIEW_STAGE
},
1194 /* View manipulation */
1195 { 'q', REQ_VIEW_CLOSE
},
1196 { KEY_TAB
, REQ_VIEW_NEXT
},
1197 { KEY_RETURN
, REQ_ENTER
},
1198 { KEY_UP
, REQ_PREVIOUS
},
1199 { KEY_DOWN
, REQ_NEXT
},
1200 { 'R', REQ_REFRESH
},
1201 { KEY_F(5), REQ_REFRESH
},
1202 { 'O', REQ_MAXIMIZE
},
1204 /* Cursor navigation */
1205 { 'k', REQ_MOVE_UP
},
1206 { 'j', REQ_MOVE_DOWN
},
1207 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1208 { KEY_END
, REQ_MOVE_LAST_LINE
},
1209 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1210 { ' ', REQ_MOVE_PAGE_DOWN
},
1211 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1212 { 'b', REQ_MOVE_PAGE_UP
},
1213 { '-', REQ_MOVE_PAGE_UP
},
1216 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1217 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1218 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1219 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1220 { 'w', REQ_SCROLL_PAGE_UP
},
1221 { 's', REQ_SCROLL_PAGE_DOWN
},
1224 { '/', REQ_SEARCH
},
1225 { '?', REQ_SEARCH_BACK
},
1226 { 'n', REQ_FIND_NEXT
},
1227 { 'N', REQ_FIND_PREV
},
1231 { 'z', REQ_STOP_LOADING
},
1232 { 'v', REQ_SHOW_VERSION
},
1233 { 'r', REQ_SCREEN_REDRAW
},
1234 { 'o', REQ_OPTIONS
},
1235 { '.', REQ_TOGGLE_LINENO
},
1236 { 'D', REQ_TOGGLE_DATE
},
1237 { 'A', REQ_TOGGLE_AUTHOR
},
1238 { 'g', REQ_TOGGLE_REV_GRAPH
},
1239 { 'F', REQ_TOGGLE_REFS
},
1240 { 'I', REQ_TOGGLE_SORT_ORDER
},
1241 { 'i', REQ_TOGGLE_SORT_FIELD
},
1242 { ':', REQ_PROMPT
},
1243 { 'u', REQ_STATUS_UPDATE
},
1244 { '!', REQ_STATUS_REVERT
},
1245 { 'M', REQ_STATUS_MERGE
},
1246 { '@', REQ_STAGE_NEXT
},
1247 { ',', REQ_PARENT
},
1251 #define KEYMAP_INFO \
1266 #define KEYMAP_(name) KEYMAP_##name
1271 static const struct enum_map keymap_table
[] = {
1272 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1277 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1279 struct keybinding_table
{
1280 struct keybinding
*data
;
1284 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1287 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1289 struct keybinding_table
*table
= &keybindings
[keymap
];
1291 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1293 die("Failed to allocate keybinding");
1294 table
->data
[table
->size
].alias
= key
;
1295 table
->data
[table
->size
++].request
= request
;
1298 /* Looks for a key binding first in the given map, then in the generic map, and
1299 * lastly in the default keybindings. */
1301 get_keybinding(enum keymap keymap
, int key
)
1305 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1306 if (keybindings
[keymap
].data
[i
].alias
== key
)
1307 return keybindings
[keymap
].data
[i
].request
;
1309 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1310 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1311 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1313 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1314 if (default_keybindings
[i
].alias
== key
)
1315 return default_keybindings
[i
].request
;
1317 return (enum request
) key
;
1326 static const struct key key_table
[] = {
1327 { "Enter", KEY_RETURN
},
1329 { "Backspace", KEY_BACKSPACE
},
1331 { "Escape", KEY_ESC
},
1332 { "Left", KEY_LEFT
},
1333 { "Right", KEY_RIGHT
},
1335 { "Down", KEY_DOWN
},
1336 { "Insert", KEY_IC
},
1337 { "Delete", KEY_DC
},
1339 { "Home", KEY_HOME
},
1341 { "PageUp", KEY_PPAGE
},
1342 { "PageDown", KEY_NPAGE
},
1352 { "F10", KEY_F(10) },
1353 { "F11", KEY_F(11) },
1354 { "F12", KEY_F(12) },
1358 get_key_value(const char *name
)
1362 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1363 if (!strcasecmp(key_table
[i
].name
, name
))
1364 return key_table
[i
].value
;
1366 if (strlen(name
) == 1 && isprint(*name
))
1373 get_key_name(int key_value
)
1375 static char key_char
[] = "'X'";
1376 const char *seq
= NULL
;
1379 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1380 if (key_table
[key
].value
== key_value
)
1381 seq
= key_table
[key
].name
;
1385 isprint(key_value
)) {
1386 key_char
[1] = (char) key_value
;
1390 return seq
? seq
: "(no key)";
1394 append_key(char *buf
, size_t *pos
, const struct keybinding
*keybinding
)
1396 const char *sep
= *pos
> 0 ? ", " : "";
1397 const char *keyname
= get_key_name(keybinding
->alias
);
1399 return string_nformat(buf
, BUFSIZ
, pos
, "%s%s", sep
, keyname
);
1403 append_keymap_request_keys(char *buf
, size_t *pos
, enum request request
,
1404 enum keymap keymap
, bool all
)
1408 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1409 if (keybindings
[keymap
].data
[i
].request
== request
) {
1410 if (!append_key(buf
, pos
, &keybindings
[keymap
].data
[i
]))
1420 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1423 get_keys(enum keymap keymap
, enum request request
, bool all
)
1425 static char buf
[BUFSIZ
];
1431 if (!append_keymap_request_keys(buf
, &pos
, request
, keymap
, all
))
1432 return "Too many keybindings!";
1433 if (pos
> 0 && !all
)
1436 if (keymap
!= KEYMAP_GENERIC
) {
1437 /* Only the generic keymap includes the default keybindings when
1438 * listing all keys. */
1442 if (!append_keymap_request_keys(buf
, &pos
, request
, KEYMAP_GENERIC
, all
))
1443 return "Too many keybindings!";
1448 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1449 if (default_keybindings
[i
].request
== request
) {
1450 if (!append_key(buf
, &pos
, &default_keybindings
[i
]))
1451 return "Too many keybindings!";
1460 struct run_request
{
1463 const char *argv
[SIZEOF_ARG
];
1466 static struct run_request
*run_request
;
1467 static size_t run_requests
;
1469 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1472 add_run_request(enum keymap keymap
, int key
, int argc
, const char **argv
)
1474 struct run_request
*req
;
1476 if (argc
>= ARRAY_SIZE(req
->argv
) - 1)
1479 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1482 req
= &run_request
[run_requests
];
1483 req
->keymap
= keymap
;
1485 req
->argv
[0] = NULL
;
1487 if (!format_argv(req
->argv
, argv
, FORMAT_NONE
))
1490 return REQ_NONE
+ ++run_requests
;
1493 static struct run_request
*
1494 get_run_request(enum request request
)
1496 if (request
<= REQ_NONE
)
1498 return &run_request
[request
- REQ_NONE
- 1];
1502 add_builtin_run_requests(void)
1504 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1505 const char *commit
[] = { "git", "commit", NULL
};
1506 const char *gc
[] = { "git", "gc", NULL
};
1513 { KEYMAP_MAIN
, 'C', ARRAY_SIZE(cherry_pick
) - 1, cherry_pick
},
1514 { KEYMAP_STATUS
, 'C', ARRAY_SIZE(commit
) - 1, commit
},
1515 { KEYMAP_GENERIC
, 'G', ARRAY_SIZE(gc
) - 1, gc
},
1519 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1522 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argc
, reqs
[i
].argv
);
1523 if (req
!= REQ_NONE
)
1524 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1529 * User config file handling.
1532 static int config_lineno
;
1533 static bool config_errors
;
1534 static const char *config_msg
;
1536 static const struct enum_map color_map
[] = {
1537 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1549 static const struct enum_map attr_map
[] = {
1550 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1557 ATTR_MAP(UNDERLINE
),
1560 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1562 static int parse_step(double *opt
, const char *arg
)
1565 if (!strchr(arg
, '%'))
1568 /* "Shift down" so 100% and 1 does not conflict. */
1569 *opt
= (*opt
- 1) / 100;
1572 config_msg
= "Step value larger than 100%";
1577 config_msg
= "Invalid step value";
1584 parse_int(int *opt
, const char *arg
, int min
, int max
)
1586 int value
= atoi(arg
);
1588 if (min
<= value
&& value
<= max
) {
1593 config_msg
= "Integer value out of bound";
1598 set_color(int *color
, const char *name
)
1600 if (map_enum(color
, color_map
, name
))
1602 if (!prefixcmp(name
, "color"))
1603 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1607 /* Wants: object fgcolor bgcolor [attribute] */
1609 option_color_command(int argc
, const char *argv
[])
1611 struct line_info
*info
;
1614 config_msg
= "Wrong number of arguments given to color command";
1618 info
= get_line_info(argv
[0]);
1620 static const struct enum_map obsolete
[] = {
1621 ENUM_MAP("main-delim", LINE_DELIMITER
),
1622 ENUM_MAP("main-date", LINE_DATE
),
1623 ENUM_MAP("main-author", LINE_AUTHOR
),
1627 if (!map_enum(&index
, obsolete
, argv
[0])) {
1628 config_msg
= "Unknown color name";
1631 info
= &line_info
[index
];
1634 if (!set_color(&info
->fg
, argv
[1]) ||
1635 !set_color(&info
->bg
, argv
[2])) {
1636 config_msg
= "Unknown color";
1641 while (argc
-- > 3) {
1644 if (!set_attribute(&attr
, argv
[argc
])) {
1645 config_msg
= "Unknown attribute";
1654 static int parse_bool(bool *opt
, const char *arg
)
1656 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1662 parse_string(char *opt
, const char *arg
, size_t optsize
)
1664 int arglen
= strlen(arg
);
1669 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1670 config_msg
= "Unmatched quotation";
1673 arg
+= 1; arglen
-= 2;
1675 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1680 /* Wants: name = value */
1682 option_set_command(int argc
, const char *argv
[])
1685 config_msg
= "Wrong number of arguments given to set command";
1689 if (strcmp(argv
[1], "=")) {
1690 config_msg
= "No value assigned";
1694 if (!strcmp(argv
[0], "show-author"))
1695 return parse_bool(&opt_author
, argv
[2]);
1697 if (!strcmp(argv
[0], "show-date")) {
1700 if (!strcmp(argv
[2], "relative")) {
1701 opt_date
= DATE_RELATIVE
;
1703 } else if (!strcmp(argv
[2], "short")) {
1704 opt_date
= DATE_SHORT
;
1706 } else if (parse_bool(&show_date
, argv
[2])) {
1707 opt_date
= show_date
? DATE_DEFAULT
: DATE_NONE
;
1712 if (!strcmp(argv
[0], "show-rev-graph"))
1713 return parse_bool(&opt_rev_graph
, argv
[2]);
1715 if (!strcmp(argv
[0], "show-refs"))
1716 return parse_bool(&opt_show_refs
, argv
[2]);
1718 if (!strcmp(argv
[0], "show-line-numbers"))
1719 return parse_bool(&opt_line_number
, argv
[2]);
1721 if (!strcmp(argv
[0], "line-graphics"))
1722 return parse_bool(&opt_line_graphics
, argv
[2]);
1724 if (!strcmp(argv
[0], "line-number-interval"))
1725 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
1727 if (!strcmp(argv
[0], "author-width"))
1728 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
1730 if (!strcmp(argv
[0], "horizontal-scroll"))
1731 return parse_step(&opt_hscroll
, argv
[2]);
1733 if (!strcmp(argv
[0], "split-view-height"))
1734 return parse_step(&opt_scale_split_view
, argv
[2]);
1736 if (!strcmp(argv
[0], "tab-size"))
1737 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
1739 if (!strcmp(argv
[0], "commit-encoding"))
1740 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
1742 config_msg
= "Unknown variable name";
1746 /* Wants: mode request key */
1748 option_bind_command(int argc
, const char *argv
[])
1750 enum request request
;
1755 config_msg
= "Wrong number of arguments given to bind command";
1759 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
1760 config_msg
= "Unknown key map";
1764 key
= get_key_value(argv
[1]);
1766 config_msg
= "Unknown key";
1770 request
= get_request(argv
[2]);
1771 if (request
== REQ_NONE
) {
1772 static const struct enum_map obsolete
[] = {
1773 ENUM_MAP("cherry-pick", REQ_NONE
),
1774 ENUM_MAP("screen-resize", REQ_NONE
),
1775 ENUM_MAP("tree-parent", REQ_PARENT
),
1779 if (map_enum(&alias
, obsolete
, argv
[2])) {
1780 if (alias
!= REQ_NONE
)
1781 add_keybinding(keymap
, alias
, key
);
1782 config_msg
= "Obsolete request name";
1786 if (request
== REQ_NONE
&& *argv
[2]++ == '!')
1787 request
= add_run_request(keymap
, key
, argc
- 2, argv
+ 2);
1788 if (request
== REQ_NONE
) {
1789 config_msg
= "Unknown request name";
1793 add_keybinding(keymap
, request
, key
);
1799 set_option(const char *opt
, char *value
)
1801 const char *argv
[SIZEOF_ARG
];
1804 if (!argv_from_string(argv
, &argc
, value
)) {
1805 config_msg
= "Too many option arguments";
1809 if (!strcmp(opt
, "color"))
1810 return option_color_command(argc
, argv
);
1812 if (!strcmp(opt
, "set"))
1813 return option_set_command(argc
, argv
);
1815 if (!strcmp(opt
, "bind"))
1816 return option_bind_command(argc
, argv
);
1818 config_msg
= "Unknown option command";
1823 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
1828 config_msg
= "Internal error";
1830 /* Check for comment markers, since read_properties() will
1831 * only ensure opt and value are split at first " \t". */
1832 optlen
= strcspn(opt
, "#");
1836 if (opt
[optlen
] != 0) {
1837 config_msg
= "No option value";
1841 /* Look for comment endings in the value. */
1842 size_t len
= strcspn(value
, "#");
1844 if (len
< valuelen
) {
1846 value
[valuelen
] = 0;
1849 status
= set_option(opt
, value
);
1852 if (status
== ERR
) {
1853 warn("Error on line %d, near '%.*s': %s",
1854 config_lineno
, (int) optlen
, opt
, config_msg
);
1855 config_errors
= TRUE
;
1858 /* Always keep going if errors are encountered. */
1863 load_option_file(const char *path
)
1867 /* It's OK that the file doesn't exist. */
1868 if (!io_open(&io
, "%s", path
))
1872 config_errors
= FALSE
;
1874 if (io_load(&io
, " \t", read_option
) == ERR
||
1875 config_errors
== TRUE
)
1876 warn("Errors while loading %s.", path
);
1882 const char *home
= getenv("HOME");
1883 const char *tigrc_user
= getenv("TIGRC_USER");
1884 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
1885 char buf
[SIZEOF_STR
];
1887 add_builtin_run_requests();
1890 tigrc_system
= SYSCONFDIR
"/tigrc";
1891 load_option_file(tigrc_system
);
1894 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
1898 load_option_file(tigrc_user
);
1911 /* The display array of active views and the index of the current view. */
1912 static struct view
*display
[2];
1913 static unsigned int current_view
;
1915 #define foreach_displayed_view(view, i) \
1916 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1918 #define displayed_views() (display[1] != NULL ? 2 : 1)
1920 /* Current head and commit ID */
1921 static char ref_blob
[SIZEOF_REF
] = "";
1922 static char ref_commit
[SIZEOF_REF
] = "HEAD";
1923 static char ref_head
[SIZEOF_REF
] = "HEAD";
1926 const char *name
; /* View name */
1927 const char *cmd_env
; /* Command line set via environment */
1928 const char *id
; /* Points to either of ref_{head,commit,blob} */
1930 struct view_ops
*ops
; /* View operations */
1932 enum keymap keymap
; /* What keymap does this view have */
1933 bool git_dir
; /* Whether the view requires a git directory. */
1935 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
1936 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
1938 int height
, width
; /* The width and height of the main window */
1939 WINDOW
*win
; /* The main window */
1940 WINDOW
*title
; /* The title window living below the main window */
1943 unsigned long offset
; /* Offset of the window top */
1944 unsigned long yoffset
; /* Offset from the window side. */
1945 unsigned long lineno
; /* Current line number */
1946 unsigned long p_offset
; /* Previous offset of the window top */
1947 unsigned long p_yoffset
;/* Previous offset from the window side */
1948 unsigned long p_lineno
; /* Previous current line number */
1949 bool p_restore
; /* Should the previous position be restored. */
1952 char grep
[SIZEOF_STR
]; /* Search string */
1953 regex_t
*regex
; /* Pre-compiled regexp */
1955 /* If non-NULL, points to the view that opened this view. If this view
1956 * is closed tig will switch back to the parent view. */
1957 struct view
*parent
;
1960 size_t lines
; /* Total number of lines */
1961 struct line
*line
; /* Line index */
1962 unsigned int digits
; /* Number of digits in the lines member. */
1965 struct line
*curline
; /* Line currently being drawn. */
1966 enum line_type curtype
; /* Attribute currently used for drawing. */
1967 unsigned long col
; /* Column when drawing. */
1968 bool has_scrolled
; /* View was scrolled. */
1978 /* What type of content being displayed. Used in the title bar. */
1980 /* Default command arguments. */
1982 /* Open and reads in all view content. */
1983 bool (*open
)(struct view
*view
);
1984 /* Read one line; updates view->line. */
1985 bool (*read
)(struct view
*view
, char *data
);
1986 /* Draw one line; @lineno must be < view->height. */
1987 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
1988 /* Depending on view handle a special requests. */
1989 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
1990 /* Search for regexp in a line. */
1991 bool (*grep
)(struct view
*view
, struct line
*line
);
1993 void (*select
)(struct view
*view
, struct line
*line
);
1994 /* Prepare view for loading */
1995 bool (*prepare
)(struct view
*view
);
1998 static struct view_ops blame_ops
;
1999 static struct view_ops blob_ops
;
2000 static struct view_ops diff_ops
;
2001 static struct view_ops help_ops
;
2002 static struct view_ops log_ops
;
2003 static struct view_ops main_ops
;
2004 static struct view_ops pager_ops
;
2005 static struct view_ops stage_ops
;
2006 static struct view_ops status_ops
;
2007 static struct view_ops tree_ops
;
2008 static struct view_ops branch_ops
;
2010 #define VIEW_STR(name, env, ref, ops, map, git) \
2011 { name, #env, ref, ops, map, git }
2013 #define VIEW_(id, name, ops, git, ref) \
2014 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2017 static struct view views
[] = {
2018 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
2019 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
2020 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
2021 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
2022 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
2023 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
2024 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
2025 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
2026 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
2027 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
2028 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
2031 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2032 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2034 #define foreach_view(view, i) \
2035 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2037 #define view_is_displayed(view) \
2038 (view == display[0] || view == display[1])
2045 static chtype line_graphics
[] = {
2046 /* LINE_GRAPHIC_VLINE: */ '|'
2050 set_view_attr(struct view
*view
, enum line_type type
)
2052 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
2053 wattrset(view
->win
, get_line_attr(type
));
2054 wchgat(view
->win
, -1, 0, type
, NULL
);
2055 view
->curtype
= type
;
2060 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
2061 int max_len
, bool use_tilde
)
2065 int trimmed
= FALSE
;
2066 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2072 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
);
2074 col
= len
= strlen(string
);
2075 if (len
> max_len
) {
2079 col
= len
= max_len
;
2084 set_view_attr(view
, type
);
2086 waddnstr(view
->win
, string
, len
);
2087 if (trimmed
&& use_tilde
) {
2088 set_view_attr(view
, LINE_DELIMITER
);
2089 waddch(view
->win
, '~');
2097 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
2099 static char space
[] = " ";
2102 spaces
= MIN(max
, spaces
);
2104 while (spaces
> 0) {
2105 int len
= MIN(spaces
, sizeof(space
) - 1);
2107 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
2115 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
2117 view
->col
+= draw_chars(view
, type
, string
, view
->width
+ view
->yoffset
- view
->col
, trim
);
2118 return view
->width
+ view
->yoffset
<= view
->col
;
2122 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
2124 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2125 int max
= view
->width
+ view
->yoffset
- view
->col
;
2131 set_view_attr(view
, type
);
2132 /* Using waddch() instead of waddnstr() ensures that
2133 * they'll be rendered correctly for the cursor line. */
2134 for (i
= skip
; i
< size
; i
++)
2135 waddch(view
->win
, graphic
[i
]);
2138 if (size
< max
&& skip
<= size
)
2139 waddch(view
->win
, ' ');
2142 return view
->width
+ view
->yoffset
<= view
->col
;
2146 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2148 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2152 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2154 col
= draw_space(view
, type
, max
- 1, max
- 1);
2157 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2158 return view
->width
+ view
->yoffset
<= view
->col
;
2162 draw_date(struct view
*view
, time_t *time
)
2164 const char *date
= time
? mkdate(time
) : "";
2165 int cols
= opt_date
== DATE_SHORT
? DATE_SHORT_COLS
: DATE_COLS
;
2167 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2171 draw_author(struct view
*view
, const char *author
)
2173 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5 || !author
;
2176 static char initials
[10];
2179 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2181 memset(initials
, 0, sizeof(initials
));
2182 for (pos
= 0; *author
&& pos
< opt_author_cols
- 1; author
++, pos
++) {
2183 while (is_initial_sep(*author
))
2185 strncpy(&initials
[pos
], author
, sizeof(initials
) - 1 - pos
);
2186 while (*author
&& !is_initial_sep(author
[1]))
2193 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2197 draw_mode(struct view
*view
, mode_t mode
)
2203 else if (S_ISLNK(mode
))
2205 else if (S_ISGITLINK(mode
))
2207 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2209 else if (S_ISREG(mode
))
2214 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2218 draw_lineno(struct view
*view
, unsigned int lineno
)
2221 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2222 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2225 lineno
+= view
->offset
+ 1;
2226 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2227 static char fmt
[] = "%1ld";
2229 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2230 if (string_format(number
, fmt
, lineno
))
2234 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2236 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2237 return draw_graphic(view
, LINE_DEFAULT
, &line_graphics
[LINE_GRAPHIC_VLINE
], 1);
2241 draw_view_line(struct view
*view
, unsigned int lineno
)
2244 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2246 assert(view_is_displayed(view
));
2248 if (view
->offset
+ lineno
>= view
->lines
)
2251 line
= &view
->line
[view
->offset
+ lineno
];
2253 wmove(view
->win
, lineno
, 0);
2255 wclrtoeol(view
->win
);
2257 view
->curline
= line
;
2258 view
->curtype
= LINE_NONE
;
2259 line
->selected
= FALSE
;
2260 line
->dirty
= line
->cleareol
= 0;
2263 set_view_attr(view
, LINE_CURSOR
);
2264 line
->selected
= TRUE
;
2265 view
->ops
->select(view
, line
);
2268 return view
->ops
->draw(view
, line
, lineno
);
2272 redraw_view_dirty(struct view
*view
)
2277 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2278 if (view
->offset
+ lineno
>= view
->lines
)
2280 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2283 if (!draw_view_line(view
, lineno
))
2289 wnoutrefresh(view
->win
);
2293 redraw_view_from(struct view
*view
, int lineno
)
2295 assert(0 <= lineno
&& lineno
< view
->height
);
2297 for (; lineno
< view
->height
; lineno
++) {
2298 if (!draw_view_line(view
, lineno
))
2302 wnoutrefresh(view
->win
);
2306 redraw_view(struct view
*view
)
2309 redraw_view_from(view
, 0);
2314 update_view_title(struct view
*view
)
2316 char buf
[SIZEOF_STR
];
2317 char state
[SIZEOF_STR
];
2318 size_t bufpos
= 0, statelen
= 0;
2320 assert(view_is_displayed(view
));
2322 if (view
!= VIEW(REQ_VIEW_STATUS
) && view
->lines
) {
2323 unsigned int view_lines
= view
->offset
+ view
->height
;
2324 unsigned int lines
= view
->lines
2325 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2328 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2337 time_t secs
= time(NULL
) - view
->start_time
;
2339 /* Three git seconds are a long time ... */
2341 string_format_from(state
, &statelen
, " loading %lds", secs
);
2344 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2345 if (*view
->ref
&& bufpos
< view
->width
) {
2346 size_t refsize
= strlen(view
->ref
);
2347 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2349 if (minsize
< view
->width
)
2350 refsize
= view
->width
- minsize
+ 7;
2351 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2354 if (statelen
&& bufpos
< view
->width
) {
2355 string_format_from(buf
, &bufpos
, "%s", state
);
2358 if (view
== display
[current_view
])
2359 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2361 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2363 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2364 wclrtoeol(view
->title
);
2365 wnoutrefresh(view
->title
);
2369 apply_step(double step
, int value
)
2373 value
*= step
+ 0.01;
2374 return value
? value
: 1;
2378 resize_display(void)
2381 struct view
*base
= display
[0];
2382 struct view
*view
= display
[1] ? display
[1] : display
[0];
2384 /* Setup window dimensions */
2386 getmaxyx(stdscr
, base
->height
, base
->width
);
2388 /* Make room for the status window. */
2392 /* Horizontal split. */
2393 view
->width
= base
->width
;
2394 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2395 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2396 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2397 base
->height
-= view
->height
;
2399 /* Make room for the title bar. */
2403 /* Make room for the title bar. */
2408 foreach_displayed_view (view
, i
) {
2410 view
->win
= newwin(view
->height
, 0, offset
, 0);
2412 die("Failed to create %s view", view
->name
);
2414 scrollok(view
->win
, FALSE
);
2416 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2418 die("Failed to create title window");
2421 wresize(view
->win
, view
->height
, view
->width
);
2422 mvwin(view
->win
, offset
, 0);
2423 mvwin(view
->title
, offset
+ view
->height
, 0);
2426 offset
+= view
->height
+ 1;
2431 redraw_display(bool clear
)
2436 foreach_displayed_view (view
, i
) {
2440 update_view_title(view
);
2445 toggle_date_option(enum date
*date
)
2447 static const char *help
[] = {
2454 opt_date
= (opt_date
+ 1) % ARRAY_SIZE(help
);
2455 redraw_display(FALSE
);
2456 report("Displaying %s dates", help
[opt_date
]);
2460 toggle_view_option(bool *option
, const char *help
)
2463 redraw_display(FALSE
);
2464 report("%sabling %s", *option
? "En" : "Dis", help
);
2468 open_option_menu(void)
2470 const struct menu_item menu
[] = {
2471 { '.', "line numbers", &opt_line_number
},
2472 { 'D', "date display", &opt_date
},
2473 { 'A', "author display", &opt_author
},
2474 { 'g', "revision graph display", &opt_rev_graph
},
2475 { 'F', "reference display", &opt_show_refs
},
2480 if (prompt_menu("Toggle option", menu
, &selected
)) {
2481 if (menu
[selected
].data
== &opt_date
)
2482 toggle_date_option(menu
[selected
].data
);
2484 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2489 maximize_view(struct view
*view
)
2491 memset(display
, 0, sizeof(display
));
2493 display
[current_view
] = view
;
2495 redraw_display(FALSE
);
2505 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2507 if (lineno
>= view
->lines
)
2508 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2510 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2511 unsigned long half
= view
->height
/ 2;
2514 offset
= lineno
- half
;
2519 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2520 view
->offset
= offset
;
2521 view
->lineno
= lineno
;
2528 /* Scrolling backend */
2530 do_scroll_view(struct view
*view
, int lines
)
2532 bool redraw_current_line
= FALSE
;
2534 /* The rendering expects the new offset. */
2535 view
->offset
+= lines
;
2537 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2540 /* Move current line into the view. */
2541 if (view
->lineno
< view
->offset
) {
2542 view
->lineno
= view
->offset
;
2543 redraw_current_line
= TRUE
;
2544 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2545 view
->lineno
= view
->offset
+ view
->height
- 1;
2546 redraw_current_line
= TRUE
;
2549 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2551 /* Redraw the whole screen if scrolling is pointless. */
2552 if (view
->height
< ABS(lines
)) {
2556 int line
= lines
> 0 ? view
->height
- lines
: 0;
2557 int end
= line
+ ABS(lines
);
2559 scrollok(view
->win
, TRUE
);
2560 wscrl(view
->win
, lines
);
2561 scrollok(view
->win
, FALSE
);
2563 while (line
< end
&& draw_view_line(view
, line
))
2566 if (redraw_current_line
)
2567 draw_view_line(view
, view
->lineno
- view
->offset
);
2568 wnoutrefresh(view
->win
);
2571 view
->has_scrolled
= TRUE
;
2575 /* Scroll frontend */
2577 scroll_view(struct view
*view
, enum request request
)
2581 assert(view_is_displayed(view
));
2584 case REQ_SCROLL_LEFT
:
2585 if (view
->yoffset
== 0) {
2586 report("Cannot scroll beyond the first column");
2589 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2592 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2593 redraw_view_from(view
, 0);
2596 case REQ_SCROLL_RIGHT
:
2597 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2601 case REQ_SCROLL_PAGE_DOWN
:
2602 lines
= view
->height
;
2603 case REQ_SCROLL_LINE_DOWN
:
2604 if (view
->offset
+ lines
> view
->lines
)
2605 lines
= view
->lines
- view
->offset
;
2607 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2608 report("Cannot scroll beyond the last line");
2613 case REQ_SCROLL_PAGE_UP
:
2614 lines
= view
->height
;
2615 case REQ_SCROLL_LINE_UP
:
2616 if (lines
> view
->offset
)
2617 lines
= view
->offset
;
2620 report("Cannot scroll beyond the first line");
2628 die("request %d not handled in switch", request
);
2631 do_scroll_view(view
, lines
);
2636 move_view(struct view
*view
, enum request request
)
2638 int scroll_steps
= 0;
2642 case REQ_MOVE_FIRST_LINE
:
2643 steps
= -view
->lineno
;
2646 case REQ_MOVE_LAST_LINE
:
2647 steps
= view
->lines
- view
->lineno
- 1;
2650 case REQ_MOVE_PAGE_UP
:
2651 steps
= view
->height
> view
->lineno
2652 ? -view
->lineno
: -view
->height
;
2655 case REQ_MOVE_PAGE_DOWN
:
2656 steps
= view
->lineno
+ view
->height
>= view
->lines
2657 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2669 die("request %d not handled in switch", request
);
2672 if (steps
<= 0 && view
->lineno
== 0) {
2673 report("Cannot move beyond the first line");
2676 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2677 report("Cannot move beyond the last line");
2681 /* Move the current line */
2682 view
->lineno
+= steps
;
2683 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2685 /* Check whether the view needs to be scrolled */
2686 if (view
->lineno
< view
->offset
||
2687 view
->lineno
>= view
->offset
+ view
->height
) {
2688 scroll_steps
= steps
;
2689 if (steps
< 0 && -steps
> view
->offset
) {
2690 scroll_steps
= -view
->offset
;
2692 } else if (steps
> 0) {
2693 if (view
->lineno
== view
->lines
- 1 &&
2694 view
->lines
> view
->height
) {
2695 scroll_steps
= view
->lines
- view
->offset
- 1;
2696 if (scroll_steps
>= view
->height
)
2697 scroll_steps
-= view
->height
- 1;
2702 if (!view_is_displayed(view
)) {
2703 view
->offset
+= scroll_steps
;
2704 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2705 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2709 /* Repaint the old "current" line if we be scrolling */
2710 if (ABS(steps
) < view
->height
)
2711 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
2714 do_scroll_view(view
, scroll_steps
);
2718 /* Draw the current line */
2719 draw_view_line(view
, view
->lineno
- view
->offset
);
2721 wnoutrefresh(view
->win
);
2730 static void search_view(struct view
*view
, enum request request
);
2733 grep_text(struct view
*view
, const char *text
[])
2738 for (i
= 0; text
[i
]; i
++)
2740 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
2746 select_view_line(struct view
*view
, unsigned long lineno
)
2748 unsigned long old_lineno
= view
->lineno
;
2749 unsigned long old_offset
= view
->offset
;
2751 if (goto_view_line(view
, view
->offset
, lineno
)) {
2752 if (view_is_displayed(view
)) {
2753 if (old_offset
!= view
->offset
) {
2756 draw_view_line(view
, old_lineno
- view
->offset
);
2757 draw_view_line(view
, view
->lineno
- view
->offset
);
2758 wnoutrefresh(view
->win
);
2761 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2767 find_next(struct view
*view
, enum request request
)
2769 unsigned long lineno
= view
->lineno
;
2774 report("No previous search");
2776 search_view(view
, request
);
2786 case REQ_SEARCH_BACK
:
2795 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
2796 lineno
+= direction
;
2798 /* Note, lineno is unsigned long so will wrap around in which case it
2799 * will become bigger than view->lines. */
2800 for (; lineno
< view
->lines
; lineno
+= direction
) {
2801 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
2802 select_view_line(view
, lineno
);
2803 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
2808 report("No match found for '%s'", view
->grep
);
2812 search_view(struct view
*view
, enum request request
)
2817 regfree(view
->regex
);
2820 view
->regex
= calloc(1, sizeof(*view
->regex
));
2825 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
2826 if (regex_err
!= 0) {
2827 char buf
[SIZEOF_STR
] = "unknown error";
2829 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
2830 report("Search failed: %s", buf
);
2834 string_copy(view
->grep
, opt_search
);
2836 find_next(view
, request
);
2840 * Incremental updating
2844 reset_view(struct view
*view
)
2848 for (i
= 0; i
< view
->lines
; i
++)
2849 free(view
->line
[i
].data
);
2852 view
->p_offset
= view
->offset
;
2853 view
->p_yoffset
= view
->yoffset
;
2854 view
->p_lineno
= view
->lineno
;
2862 view
->update_secs
= 0;
2866 free_argv(const char *argv
[])
2870 for (argc
= 0; argv
[argc
]; argc
++)
2871 free((void *) argv
[argc
]);
2875 format_argv(const char *dst_argv
[], const char *src_argv
[], enum format_flags flags
)
2877 char buf
[SIZEOF_STR
];
2879 bool noreplace
= flags
== FORMAT_NONE
;
2881 free_argv(dst_argv
);
2883 for (argc
= 0; src_argv
[argc
]; argc
++) {
2884 const char *arg
= src_argv
[argc
];
2888 char *next
= strstr(arg
, "%(");
2889 int len
= next
- arg
;
2892 if (!next
|| noreplace
) {
2893 if (flags
== FORMAT_DASH
&& !strcmp(arg
, "--"))
2898 } else if (!prefixcmp(next
, "%(directory)")) {
2901 } else if (!prefixcmp(next
, "%(file)")) {
2904 } else if (!prefixcmp(next
, "%(ref)")) {
2905 value
= *opt_ref
? opt_ref
: "HEAD";
2907 } else if (!prefixcmp(next
, "%(head)")) {
2910 } else if (!prefixcmp(next
, "%(commit)")) {
2913 } else if (!prefixcmp(next
, "%(blob)")) {
2917 report("Unknown replacement: `%s`", next
);
2921 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
2924 arg
= next
&& !noreplace
? strchr(next
, ')') + 1 : NULL
;
2927 dst_argv
[argc
] = strdup(buf
);
2928 if (!dst_argv
[argc
])
2932 dst_argv
[argc
] = NULL
;
2934 return src_argv
[argc
] == NULL
;
2938 restore_view_position(struct view
*view
)
2940 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
2943 /* Changing the view position cancels the restoring. */
2944 /* FIXME: Changing back to the first line is not detected. */
2945 if (view
->offset
!= 0 || view
->lineno
!= 0) {
2946 view
->p_restore
= FALSE
;
2950 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
2951 view_is_displayed(view
))
2954 view
->yoffset
= view
->p_yoffset
;
2955 view
->p_restore
= FALSE
;
2961 end_update(struct view
*view
, bool force
)
2965 while (!view
->ops
->read(view
, NULL
))
2968 set_nonblocking_input(FALSE
);
2970 kill_io(view
->pipe
);
2971 done_io(view
->pipe
);
2976 setup_update(struct view
*view
, const char *vid
)
2978 set_nonblocking_input(TRUE
);
2980 string_copy_rev(view
->vid
, vid
);
2981 view
->pipe
= &view
->io
;
2982 view
->start_time
= time(NULL
);
2986 prepare_update(struct view
*view
, const char *argv
[], const char *dir
,
2987 enum format_flags flags
)
2990 end_update(view
, TRUE
);
2991 return init_io_rd(&view
->io
, argv
, dir
, flags
);
2995 prepare_update_file(struct view
*view
, const char *name
)
2998 end_update(view
, TRUE
);
2999 return io_open(&view
->io
, "%s", name
);
3003 begin_update(struct view
*view
, bool refresh
)
3006 end_update(view
, TRUE
);
3009 if (view
->ops
->prepare
) {
3010 if (!view
->ops
->prepare(view
))
3012 } else if (!init_io_rd(&view
->io
, view
->ops
->argv
, NULL
, FORMAT_ALL
)) {
3016 /* Put the current ref_* value to the view title ref
3017 * member. This is needed by the blob view. Most other
3018 * views sets it automatically after loading because the
3019 * first line is a commit line. */
3020 string_copy_rev(view
->ref
, view
->id
);
3023 if (!start_io(&view
->io
))
3026 setup_update(view
, view
->id
);
3032 update_view(struct view
*view
)
3034 char out_buffer
[BUFSIZ
* 2];
3036 /* Clear the view and redraw everything since the tree sorting
3037 * might have rearranged things. */
3038 bool redraw
= view
->lines
== 0;
3039 bool can_read
= TRUE
;
3044 if (!io_can_read(view
->pipe
)) {
3045 if (view
->lines
== 0 && view_is_displayed(view
)) {
3046 time_t secs
= time(NULL
) - view
->start_time
;
3048 if (secs
> 1 && secs
> view
->update_secs
) {
3049 if (view
->update_secs
== 0)
3051 update_view_title(view
);
3052 view
->update_secs
= secs
;
3058 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
3059 if (opt_iconv
!= ICONV_NONE
) {
3060 ICONV_CONST
char *inbuf
= line
;
3061 size_t inlen
= strlen(line
) + 1;
3063 char *outbuf
= out_buffer
;
3064 size_t outlen
= sizeof(out_buffer
);
3068 ret
= iconv(opt_iconv
, &inbuf
, &inlen
, &outbuf
, &outlen
);
3069 if (ret
!= (size_t) -1)
3073 if (!view
->ops
->read(view
, line
)) {
3074 report("Allocation failure");
3075 end_update(view
, TRUE
);
3081 unsigned long lines
= view
->lines
;
3084 for (digits
= 0; lines
; digits
++)
3087 /* Keep the displayed view in sync with line number scaling. */
3088 if (digits
!= view
->digits
) {
3089 view
->digits
= digits
;
3090 if (opt_line_number
|| view
== VIEW(REQ_VIEW_BLAME
))
3095 if (io_error(view
->pipe
)) {
3096 report("Failed to read: %s", io_strerror(view
->pipe
));
3097 end_update(view
, TRUE
);
3099 } else if (io_eof(view
->pipe
)) {
3101 end_update(view
, FALSE
);
3104 if (restore_view_position(view
))
3107 if (!view_is_displayed(view
))
3111 redraw_view_from(view
, 0);
3113 redraw_view_dirty(view
);
3115 /* Update the title _after_ the redraw so that if the redraw picks up a
3116 * commit reference in view->ref it'll be available here. */
3117 update_view_title(view
);
3121 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
3123 static struct line
*
3124 add_line_data(struct view
*view
, void *data
, enum line_type type
)
3128 if (!realloc_lines(&view
->line
, view
->lines
, 1))
3131 line
= &view
->line
[view
->lines
++];
3132 memset(line
, 0, sizeof(*line
));
3140 static struct line
*
3141 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
3143 char *data
= text
? strdup(text
) : NULL
;
3145 return data
? add_line_data(view
, data
, type
) : NULL
;
3148 static struct line
*
3149 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3151 char buf
[SIZEOF_STR
];
3154 va_start(args
, fmt
);
3155 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3159 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3167 OPEN_DEFAULT
= 0, /* Use default view switching. */
3168 OPEN_SPLIT
= 1, /* Split current view. */
3169 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3170 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3171 OPEN_PREPARED
= 32, /* Open already prepared command. */
3175 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3177 bool split
= !!(flags
& OPEN_SPLIT
);
3178 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3179 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3180 struct view
*view
= VIEW(request
);
3181 int nviews
= displayed_views();
3182 struct view
*base_view
= display
[0];
3184 if (view
== prev
&& nviews
== 1 && !reload
) {
3185 report("Already in %s view", view
->name
);
3189 if (view
->git_dir
&& !opt_git_dir
[0]) {
3190 report("The %s view is disabled in pager view", view
->name
);
3197 } else if (!nomaximize
) {
3198 /* Maximize the current view. */
3199 memset(display
, 0, sizeof(display
));
3201 display
[current_view
] = view
;
3204 /* No parent signals that this is the first loaded view. */
3205 if (prev
&& view
!= prev
) {
3206 view
->parent
= prev
;
3209 /* Resize the view when switching between split- and full-screen,
3210 * or when switching between two different full-screen views. */
3211 if (nviews
!= displayed_views() ||
3212 (nviews
== 1 && base_view
!= display
[0]))
3215 if (view
->ops
->open
) {
3217 end_update(view
, TRUE
);
3218 if (!view
->ops
->open(view
)) {
3219 report("Failed to load %s view", view
->name
);
3222 restore_view_position(view
);
3224 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3225 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3226 report("Failed to load %s view", view
->name
);
3230 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3231 /* Take the title line into account. */
3232 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3234 /* Scroll the view that was split if the current line is
3235 * outside the new limited view. */
3236 do_scroll_view(prev
, lines
);
3239 if (prev
&& view
!= prev
) {
3241 /* "Blur" the previous view. */
3242 update_view_title(prev
);
3246 if (view
->pipe
&& view
->lines
== 0) {
3247 /* Clear the old view and let the incremental updating refill
3250 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3252 } else if (view_is_displayed(view
)) {
3259 open_external_viewer(const char *argv
[], const char *dir
)
3261 def_prog_mode(); /* save current tty modes */
3262 endwin(); /* restore original tty modes */
3263 run_io_fg(argv
, dir
);
3264 fprintf(stderr
, "Press Enter to continue");
3267 redraw_display(TRUE
);
3271 open_mergetool(const char *file
)
3273 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3275 open_external_viewer(mergetool_argv
, opt_cdup
);
3279 open_editor(bool from_root
, const char *file
)
3281 const char *editor_argv
[] = { "vi", file
, NULL
};
3284 editor
= getenv("GIT_EDITOR");
3285 if (!editor
&& *opt_editor
)
3286 editor
= opt_editor
;
3288 editor
= getenv("VISUAL");
3290 editor
= getenv("EDITOR");
3294 editor_argv
[0] = editor
;
3295 open_external_viewer(editor_argv
, from_root
? opt_cdup
: NULL
);
3299 open_run_request(enum request request
)
3301 struct run_request
*req
= get_run_request(request
);
3302 const char *argv
[ARRAY_SIZE(req
->argv
)] = { NULL
};
3305 report("Unknown run request");
3309 if (format_argv(argv
, req
->argv
, FORMAT_ALL
))
3310 open_external_viewer(argv
, NULL
);
3315 * User request switch noodle
3319 view_driver(struct view
*view
, enum request request
)
3323 if (request
== REQ_NONE
)
3326 if (request
> REQ_NONE
) {
3327 open_run_request(request
);
3328 /* FIXME: When all views can refresh always do this. */
3329 if (view
== VIEW(REQ_VIEW_STATUS
) ||
3330 view
== VIEW(REQ_VIEW_MAIN
) ||
3331 view
== VIEW(REQ_VIEW_LOG
) ||
3332 view
== VIEW(REQ_VIEW_BRANCH
) ||
3333 view
== VIEW(REQ_VIEW_STAGE
))
3334 request
= REQ_REFRESH
;
3339 if (view
&& view
->lines
) {
3340 request
= view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
3341 if (request
== REQ_NONE
)
3348 case REQ_MOVE_PAGE_UP
:
3349 case REQ_MOVE_PAGE_DOWN
:
3350 case REQ_MOVE_FIRST_LINE
:
3351 case REQ_MOVE_LAST_LINE
:
3352 move_view(view
, request
);
3355 case REQ_SCROLL_LEFT
:
3356 case REQ_SCROLL_RIGHT
:
3357 case REQ_SCROLL_LINE_DOWN
:
3358 case REQ_SCROLL_LINE_UP
:
3359 case REQ_SCROLL_PAGE_DOWN
:
3360 case REQ_SCROLL_PAGE_UP
:
3361 scroll_view(view
, request
);
3364 case REQ_VIEW_BLAME
:
3366 report("No file chosen, press %s to open tree view",
3367 get_key(view
->keymap
, REQ_VIEW_TREE
));
3370 open_view(view
, request
, OPEN_DEFAULT
);
3375 report("No file chosen, press %s to open tree view",
3376 get_key(view
->keymap
, REQ_VIEW_TREE
));
3379 open_view(view
, request
, OPEN_DEFAULT
);
3382 case REQ_VIEW_PAGER
:
3383 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3384 report("No pager content, press %s to run command from prompt",
3385 get_key(view
->keymap
, REQ_PROMPT
));
3388 open_view(view
, request
, OPEN_DEFAULT
);
3391 case REQ_VIEW_STAGE
:
3392 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3393 report("No stage content, press %s to open the status view and choose file",
3394 get_key(view
->keymap
, REQ_VIEW_STATUS
));
3397 open_view(view
, request
, OPEN_DEFAULT
);
3400 case REQ_VIEW_STATUS
:
3401 if (opt_is_inside_work_tree
== FALSE
) {
3402 report("The status view requires a working tree");
3405 open_view(view
, request
, OPEN_DEFAULT
);
3413 case REQ_VIEW_BRANCH
:
3414 open_view(view
, request
, OPEN_DEFAULT
);
3419 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3421 if ((view
== VIEW(REQ_VIEW_DIFF
) &&
3422 view
->parent
== VIEW(REQ_VIEW_MAIN
)) ||
3423 (view
== VIEW(REQ_VIEW_DIFF
) &&
3424 view
->parent
== VIEW(REQ_VIEW_BLAME
)) ||
3425 (view
== VIEW(REQ_VIEW_STAGE
) &&
3426 view
->parent
== VIEW(REQ_VIEW_STATUS
)) ||
3427 (view
== VIEW(REQ_VIEW_BLOB
) &&
3428 view
->parent
== VIEW(REQ_VIEW_TREE
)) ||
3429 (view
== VIEW(REQ_VIEW_MAIN
) &&
3430 view
->parent
== VIEW(REQ_VIEW_BRANCH
))) {
3433 view
= view
->parent
;
3434 line
= view
->lineno
;
3435 move_view(view
, request
);
3436 if (view_is_displayed(view
))
3437 update_view_title(view
);
3438 if (line
!= view
->lineno
)
3439 view
->ops
->request(view
, REQ_ENTER
,
3440 &view
->line
[view
->lineno
]);
3443 move_view(view
, request
);
3449 int nviews
= displayed_views();
3450 int next_view
= (current_view
+ 1) % nviews
;
3452 if (next_view
== current_view
) {
3453 report("Only one view is displayed");
3457 current_view
= next_view
;
3458 /* Blur out the title of the previous view. */
3459 update_view_title(view
);
3464 report("Refreshing is not yet supported for the %s view", view
->name
);
3468 if (displayed_views() == 2)
3469 maximize_view(view
);
3476 case REQ_TOGGLE_LINENO
:
3477 toggle_view_option(&opt_line_number
, "line numbers");
3480 case REQ_TOGGLE_DATE
:
3481 toggle_date_option(&opt_date
);
3484 case REQ_TOGGLE_AUTHOR
:
3485 toggle_view_option(&opt_author
, "author display");
3488 case REQ_TOGGLE_REV_GRAPH
:
3489 toggle_view_option(&opt_rev_graph
, "revision graph display");
3492 case REQ_TOGGLE_REFS
:
3493 toggle_view_option(&opt_show_refs
, "reference display");
3496 case REQ_TOGGLE_SORT_FIELD
:
3497 case REQ_TOGGLE_SORT_ORDER
:
3498 report("Sorting is not yet supported for the %s view", view
->name
);
3502 case REQ_SEARCH_BACK
:
3503 search_view(view
, request
);
3508 find_next(view
, request
);
3511 case REQ_STOP_LOADING
:
3512 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
3515 report("Stopped loading the %s view", view
->name
),
3516 end_update(view
, TRUE
);
3520 case REQ_SHOW_VERSION
:
3521 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3524 case REQ_SCREEN_REDRAW
:
3525 redraw_display(TRUE
);
3529 report("Nothing to edit");
3533 report("Nothing to enter");
3536 case REQ_VIEW_CLOSE
:
3537 /* XXX: Mark closed views by letting view->parent point to the
3538 * view itself. Parents to closed view should never be
3541 view
->parent
->parent
!= view
->parent
) {
3542 maximize_view(view
->parent
);
3543 view
->parent
= view
;
3551 report("Unknown key, press %s for help",
3552 get_key(view
->keymap
, REQ_VIEW_HELP
));
3561 * View backend utilities
3571 const enum sort_field
*fields
;
3572 size_t size
, current
;
3576 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3577 #define get_sort_field(state) ((state).fields[(state).current])
3578 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3581 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3582 int (*compare
)(const void *, const void *))
3585 case REQ_TOGGLE_SORT_FIELD
:
3586 state
->current
= (state
->current
+ 1) % state
->size
;
3589 case REQ_TOGGLE_SORT_ORDER
:
3590 state
->reverse
= !state
->reverse
;
3593 die("Not a sort request");
3596 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3600 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3602 /* Small author cache to reduce memory consumption. It uses binary
3603 * search to lookup or find place to position new entries. No entries
3604 * are ever freed. */
3606 get_author(const char *name
)
3608 static const char **authors
;
3609 static size_t authors_size
;
3610 int from
= 0, to
= authors_size
- 1;
3612 while (from
<= to
) {
3613 size_t pos
= (to
+ from
) / 2;
3614 int cmp
= strcmp(name
, authors
[pos
]);
3617 return authors
[pos
];
3625 if (!realloc_authors(&authors
, authors_size
, 1))
3627 name
= strdup(name
);
3631 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3632 authors
[from
] = name
;
3639 parse_timezone(time_t *time
, const char *zone
)
3643 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3644 tz
+= ('0' - zone
[2]) * 60 * 60;
3645 tz
+= ('0' - zone
[3]) * 60;
3646 tz
+= ('0' - zone
[4]);
3654 /* Parse author lines where the name may be empty:
3655 * author <email@address.tld> 1138474660 +0100
3658 parse_author_line(char *ident
, const char **author
, time_t *time
)
3660 char *nameend
= strchr(ident
, '<');
3661 char *emailend
= strchr(ident
, '>');
3663 if (nameend
&& emailend
)
3664 *nameend
= *emailend
= 0;
3665 ident
= chomp_string(ident
);
3668 ident
= chomp_string(nameend
+ 1);
3673 *author
= get_author(ident
);
3675 /* Parse epoch and timezone */
3676 if (emailend
&& emailend
[1] == ' ') {
3677 char *secs
= emailend
+ 2;
3678 char *zone
= strchr(secs
, ' ');
3680 *time
= (time_t) atol(secs
);
3682 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
3683 parse_timezone(time
, zone
+ 1);
3688 open_commit_parent_menu(char buf
[SIZEOF_STR
], int *parents
)
3690 char rev
[SIZEOF_REV
];
3691 const char *revlist_argv
[] = {
3692 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev
, NULL
3694 struct menu_item
*items
;
3695 char text
[SIZEOF_STR
];
3699 items
= calloc(*parents
+ 1, sizeof(*items
));
3703 for (i
= 0; i
< *parents
; i
++) {
3704 string_copy_rev(rev
, &buf
[SIZEOF_REV
* i
]);
3705 if (!run_io_buf(revlist_argv
, text
, sizeof(text
)) ||
3706 !(items
[i
].text
= strdup(text
))) {
3714 ok
= prompt_menu("Select parent", items
, parents
);
3716 for (i
= 0; items
[i
].text
; i
++)
3717 free((char *) items
[i
].text
);
3723 select_commit_parent(const char *id
, char rev
[SIZEOF_REV
], const char *path
)
3725 char buf
[SIZEOF_STR
* 4];
3726 const char *revlist_argv
[] = {
3727 "git", "log", "--no-color", "-1",
3728 "--pretty=format:%P", id
, "--", path
, NULL
3732 if (!run_io_buf(revlist_argv
, buf
, sizeof(buf
)) ||
3733 (parents
= strlen(buf
) / 40) < 0) {
3734 report("Failed to get parent information");
3737 } else if (parents
== 0) {
3739 report("Path '%s' does not exist in the parent", path
);
3741 report("The selected commit has no parents");
3745 if (parents
> 1 && !open_commit_parent_menu(buf
, &parents
))
3748 string_copy_rev(rev
, &buf
[41 * parents
]);
3757 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
3759 char text
[SIZEOF_STR
];
3761 if (opt_line_number
&& draw_lineno(view
, lineno
))
3764 string_expand(text
, sizeof(text
), line
->data
, opt_tab_size
);
3765 draw_text(view
, line
->type
, text
, TRUE
);
3770 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
3772 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
3773 char ref
[SIZEOF_STR
];
3775 if (!run_io_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
3778 /* This is the only fatal call, since it can "corrupt" the buffer. */
3779 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
3786 add_pager_refs(struct view
*view
, struct line
*line
)
3788 char buf
[SIZEOF_STR
];
3789 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
3790 struct ref_list
*list
;
3791 size_t bufpos
= 0, i
;
3792 const char *sep
= "Refs: ";
3793 bool is_tag
= FALSE
;
3795 assert(line
->type
== LINE_COMMIT
);
3797 list
= get_ref_list(commit_id
);
3799 if (view
== VIEW(REQ_VIEW_DIFF
))
3800 goto try_add_describe_ref
;
3804 for (i
= 0; i
< list
->size
; i
++) {
3805 struct ref
*ref
= list
->refs
[i
];
3806 const char *fmt
= ref
->tag
? "%s[%s]" :
3807 ref
->remote
? "%s<%s>" : "%s%s";
3809 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
3816 if (!is_tag
&& view
== VIEW(REQ_VIEW_DIFF
)) {
3817 try_add_describe_ref
:
3818 /* Add <tag>-g<commit_id> "fake" reference. */
3819 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
3826 add_line_text(view
, buf
, LINE_PP_REFS
);
3830 pager_read(struct view
*view
, char *data
)
3837 line
= add_line_text(view
, data
, get_line_type(data
));
3841 if (line
->type
== LINE_COMMIT
&&
3842 (view
== VIEW(REQ_VIEW_DIFF
) ||
3843 view
== VIEW(REQ_VIEW_LOG
)))
3844 add_pager_refs(view
, line
);
3850 pager_request(struct view
*view
, enum request request
, struct line
*line
)
3854 if (request
!= REQ_ENTER
)
3857 if (line
->type
== LINE_COMMIT
&&
3858 (view
== VIEW(REQ_VIEW_LOG
) ||
3859 view
== VIEW(REQ_VIEW_PAGER
))) {
3860 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
3864 /* Always scroll the view even if it was split. That way
3865 * you can use Enter to scroll through the log view and
3866 * split open each commit diff. */
3867 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
3869 /* FIXME: A minor workaround. Scrolling the view will call report("")
3870 * but if we are scrolling a non-current view this won't properly
3871 * update the view title. */
3873 update_view_title(view
);
3879 pager_grep(struct view
*view
, struct line
*line
)
3881 const char *text
[] = { line
->data
, NULL
};
3883 return grep_text(view
, text
);
3887 pager_select(struct view
*view
, struct line
*line
)
3889 if (line
->type
== LINE_COMMIT
) {
3890 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
3892 if (view
!= VIEW(REQ_VIEW_PAGER
))
3893 string_copy_rev(view
->ref
, text
);
3894 string_copy_rev(ref_commit
, text
);
3898 static struct view_ops pager_ops
= {
3909 static const char *log_argv
[SIZEOF_ARG
] = {
3910 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3914 log_request(struct view
*view
, enum request request
, struct line
*line
)
3919 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
3922 return pager_request(view
, request
, line
);
3926 static struct view_ops log_ops
= {
3937 static const char *diff_argv
[SIZEOF_ARG
] = {
3938 "git", "show", "--pretty=fuller", "--no-color", "--root",
3939 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3942 static struct view_ops diff_ops
= {
3957 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
3960 help_name(char buf
[SIZEOF_STR
], const char *name
, size_t namelen
)
3964 for (bufpos
= 0; bufpos
<= namelen
; bufpos
++) {
3965 buf
[bufpos
] = tolower(name
[bufpos
]);
3966 if (buf
[bufpos
] == '_')
3974 #define help_keymap_name(buf, keymap) \
3975 help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3978 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
3980 char buf
[SIZEOF_STR
];
3983 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
3984 help_keymap_hidden
[keymap
] ? '+' : '-',
3985 help_keymap_name(buf
, keymap
));
3987 line
->other
= keymap
;
3989 return help_keymap_hidden
[keymap
];
3993 help_open_keymap(struct view
*view
, enum keymap keymap
)
3995 const char *group
= NULL
;
3996 char buf
[SIZEOF_STR
];
3998 bool add_title
= TRUE
;
4001 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
4002 const char *key
= NULL
;
4004 if (req_info
[i
].request
== REQ_NONE
)
4007 if (!req_info
[i
].request
) {
4008 group
= req_info
[i
].help
;
4012 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4016 if (add_title
&& help_open_keymap_title(view
, keymap
))
4021 add_line_text(view
, group
, LINE_HELP_GROUP
);
4025 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4026 help_name(buf
, req_info
[i
].name
, req_info
[i
].namelen
),
4030 group
= "External commands:";
4032 for (i
= 0; i
< run_requests
; i
++) {
4033 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4037 if (!req
|| req
->keymap
!= keymap
)
4040 key
= get_key_name(req
->key
);
4042 key
= "(no key defined)";
4044 if (add_title
&& help_open_keymap_title(view
, keymap
))
4047 add_line_text(view
, group
, LINE_HELP_GROUP
);
4051 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4052 if (!string_format_from(buf
, &bufpos
, "%s%s",
4053 argc
? " " : "", req
->argv
[argc
]))
4056 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4061 help_open(struct view
*view
)
4066 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4067 add_line_text(view
, "", LINE_DEFAULT
);
4069 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4070 help_open_keymap(view
, keymap
);
4076 help_request(struct view
*view
, enum request request
, struct line
*line
)
4080 if (line
->type
== LINE_HELP_KEYMAP
) {
4081 help_keymap_hidden
[line
->other
] =
4082 !help_keymap_hidden
[line
->other
];
4083 view
->p_restore
= TRUE
;
4084 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4089 return pager_request(view
, request
, line
);
4093 static struct view_ops help_ops
= {
4109 struct tree_stack_entry
{
4110 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4111 unsigned long lineno
; /* Line number to restore */
4112 char *name
; /* Position of name in opt_path */
4115 /* The top of the path stack. */
4116 static struct tree_stack_entry
*tree_stack
= NULL
;
4117 unsigned long tree_lineno
= 0;
4120 pop_tree_stack_entry(void)
4122 struct tree_stack_entry
*entry
= tree_stack
;
4124 tree_lineno
= entry
->lineno
;
4126 tree_stack
= entry
->prev
;
4131 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4133 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4134 size_t pathlen
= strlen(opt_path
);
4139 entry
->prev
= tree_stack
;
4140 entry
->name
= opt_path
+ pathlen
;
4143 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4144 pop_tree_stack_entry();
4148 /* Move the current line to the first tree entry. */
4150 entry
->lineno
= lineno
;
4153 /* Parse output from git-ls-tree(1):
4155 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4158 #define SIZEOF_TREE_ATTR \
4159 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4161 #define SIZEOF_TREE_MODE \
4162 STRING_SIZE("100644 ")
4164 #define TREE_ID_OFFSET \
4165 STRING_SIZE("100644 blob ")
4168 char id
[SIZEOF_REV
];
4170 time_t time
; /* Date from the author ident. */
4171 const char *author
; /* Author of the commit. */
4176 tree_path(const struct line
*line
)
4178 return ((struct tree_entry
*) line
->data
)->name
;
4182 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4184 if (line1
->type
!= line2
->type
)
4185 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4186 return strcmp(tree_path(line1
), tree_path(line2
));
4189 static const enum sort_field tree_sort_fields
[] = {
4190 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4192 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4195 tree_compare(const void *l1
, const void *l2
)
4197 const struct line
*line1
= (const struct line
*) l1
;
4198 const struct line
*line2
= (const struct line
*) l2
;
4199 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4200 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4202 if (line1
->type
== LINE_TREE_HEAD
)
4204 if (line2
->type
== LINE_TREE_HEAD
)
4207 switch (get_sort_field(tree_sort_state
)) {
4209 return sort_order(tree_sort_state
, entry1
->time
- entry2
->time
);
4211 case ORDERBY_AUTHOR
:
4212 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4216 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4221 static struct line
*
4222 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4223 const char *mode
, const char *id
)
4225 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4226 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4228 if (!entry
|| !line
) {
4233 strncpy(entry
->name
, path
, strlen(path
));
4235 entry
->mode
= strtoul(mode
, NULL
, 8);
4237 string_copy_rev(entry
->id
, id
);
4243 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4245 static const char *author_name
;
4246 static time_t author_time
;
4248 if (!text
&& *read_date
) {
4253 char *path
= *opt_path
? opt_path
: ".";
4254 /* Find next entry to process */
4255 const char *log_file
[] = {
4256 "git", "log", "--no-color", "--pretty=raw",
4257 "--cc", "--raw", view
->id
, "--", path
, NULL
4262 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4263 report("Tree is empty");
4267 if (!run_io_rd(&io
, log_file
, opt_cdup
, FORMAT_NONE
)) {
4268 report("Failed to load tree data");
4272 done_io(view
->pipe
);
4277 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4278 parse_author_line(text
+ STRING_SIZE("author "),
4279 &author_name
, &author_time
);
4281 } else if (*text
== ':') {
4283 size_t annotated
= 1;
4286 pos
= strchr(text
, '\t');
4290 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4291 text
+= strlen(opt_path
);
4292 pos
= strchr(text
, '/');
4296 for (i
= 1; i
< view
->lines
; i
++) {
4297 struct line
*line
= &view
->line
[i
];
4298 struct tree_entry
*entry
= line
->data
;
4300 annotated
+= !!entry
->author
;
4301 if (entry
->author
|| strcmp(entry
->name
, text
))
4304 entry
->author
= author_name
;
4305 entry
->time
= author_time
;
4310 if (annotated
== view
->lines
)
4311 kill_io(view
->pipe
);
4317 tree_read(struct view
*view
, char *text
)
4319 static bool read_date
= FALSE
;
4320 struct tree_entry
*data
;
4321 struct line
*entry
, *line
;
4322 enum line_type type
;
4323 size_t textlen
= text
? strlen(text
) : 0;
4324 char *path
= text
+ SIZEOF_TREE_ATTR
;
4326 if (read_date
|| !text
)
4327 return tree_read_date(view
, text
, &read_date
);
4329 if (textlen
<= SIZEOF_TREE_ATTR
)
4331 if (view
->lines
== 0 &&
4332 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4335 /* Strip the path part ... */
4337 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4338 size_t striplen
= strlen(opt_path
);
4340 if (pathlen
> striplen
)
4341 memmove(path
, path
+ striplen
,
4342 pathlen
- striplen
+ 1);
4344 /* Insert "link" to parent directory. */
4345 if (view
->lines
== 1 &&
4346 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4350 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4351 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4356 /* Skip "Directory ..." and ".." line. */
4357 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4358 if (tree_compare_entry(line
, entry
) <= 0)
4361 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4365 for (; line
<= entry
; line
++)
4366 line
->dirty
= line
->cleareol
= 1;
4370 if (tree_lineno
> view
->lineno
) {
4371 view
->lineno
= tree_lineno
;
4379 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4381 struct tree_entry
*entry
= line
->data
;
4383 if (line
->type
== LINE_TREE_HEAD
) {
4384 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4387 if (draw_mode(view
, entry
->mode
))
4390 if (opt_author
&& draw_author(view
, entry
->author
))
4393 if (opt_date
&& draw_date(view
, entry
->author
? &entry
->time
: NULL
))
4396 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4404 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4405 int fd
= mkstemp(file
);
4408 report("Failed to create temporary file");
4409 else if (!run_io_append(blob_ops
.argv
, FORMAT_ALL
, fd
))
4410 report("Failed to save blob data to file");
4412 open_editor(FALSE
, file
);
4418 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4420 enum open_flags flags
;
4423 case REQ_VIEW_BLAME
:
4424 if (line
->type
!= LINE_TREE_FILE
) {
4425 report("Blame only supported for files");
4429 string_copy(opt_ref
, view
->vid
);
4433 if (line
->type
!= LINE_TREE_FILE
) {
4434 report("Edit only supported for files");
4435 } else if (!is_head_commit(view
->vid
)) {
4438 open_editor(TRUE
, opt_file
);
4442 case REQ_TOGGLE_SORT_FIELD
:
4443 case REQ_TOGGLE_SORT_ORDER
:
4444 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4449 /* quit view if at top of tree */
4450 return REQ_VIEW_CLOSE
;
4453 line
= &view
->line
[1];
4463 /* Cleanup the stack if the tree view is at a different tree. */
4464 while (!*opt_path
&& tree_stack
)
4465 pop_tree_stack_entry();
4467 switch (line
->type
) {
4469 /* Depending on whether it is a subdirectory or parent link
4470 * mangle the path buffer. */
4471 if (line
== &view
->line
[1] && *opt_path
) {
4472 pop_tree_stack_entry();
4475 const char *basename
= tree_path(line
);
4477 push_tree_stack_entry(basename
, view
->lineno
);
4480 /* Trees and subtrees share the same ID, so they are not not
4481 * unique like blobs. */
4482 flags
= OPEN_RELOAD
;
4483 request
= REQ_VIEW_TREE
;
4486 case LINE_TREE_FILE
:
4487 flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4488 request
= REQ_VIEW_BLOB
;
4495 open_view(view
, request
, flags
);
4496 if (request
== REQ_VIEW_TREE
)
4497 view
->lineno
= tree_lineno
;
4503 tree_grep(struct view
*view
, struct line
*line
)
4505 struct tree_entry
*entry
= line
->data
;
4506 const char *text
[] = {
4508 opt_author
? entry
->author
: "",
4509 opt_date
? mkdate(&entry
->time
) : "",
4513 return grep_text(view
, text
);
4517 tree_select(struct view
*view
, struct line
*line
)
4519 struct tree_entry
*entry
= line
->data
;
4521 if (line
->type
== LINE_TREE_FILE
) {
4522 string_copy_rev(ref_blob
, entry
->id
);
4523 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4525 } else if (line
->type
!= LINE_TREE_DIR
) {
4529 string_copy_rev(view
->ref
, entry
->id
);
4533 tree_prepare(struct view
*view
)
4535 if (view
->lines
== 0 && opt_prefix
[0]) {
4536 char *pos
= opt_prefix
;
4538 while (pos
&& *pos
) {
4539 char *end
= strchr(pos
, '/');
4543 push_tree_stack_entry(pos
, 0);
4551 } else if (strcmp(view
->vid
, view
->id
)) {
4555 return init_io_rd(&view
->io
, view
->ops
->argv
, opt_cdup
, FORMAT_ALL
);
4558 static const char *tree_argv
[SIZEOF_ARG
] = {
4559 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4562 static struct view_ops tree_ops
= {
4575 blob_read(struct view
*view
, char *line
)
4579 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4583 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4590 return pager_request(view
, request
, line
);
4594 static const char *blob_argv
[SIZEOF_ARG
] = {
4595 "git", "cat-file", "blob", "%(blob)", NULL
4598 static struct view_ops blob_ops
= {
4612 * Loading the blame view is a two phase job:
4614 * 1. File content is read either using opt_file from the
4615 * filesystem or using git-cat-file.
4616 * 2. Then blame information is incrementally added by
4617 * reading output from git-blame.
4620 static const char *blame_head_argv
[] = {
4621 "git", "blame", "--incremental", "--", "%(file)", NULL
4624 static const char *blame_ref_argv
[] = {
4625 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4628 static const char *blame_cat_file_argv
[] = {
4629 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4632 struct blame_commit
{
4633 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4634 char title
[128]; /* First line of the commit message. */
4635 const char *author
; /* Author of the commit. */
4636 time_t time
; /* Date from the author ident. */
4637 char filename
[128]; /* Name of file. */
4638 bool has_previous
; /* Was a "previous" line detected. */
4642 struct blame_commit
*commit
;
4643 unsigned long lineno
;
4648 blame_open(struct view
*view
)
4650 char path
[SIZEOF_STR
];
4652 if (!view
->parent
&& *opt_prefix
) {
4653 string_copy(path
, opt_file
);
4654 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4658 if (*opt_ref
|| !io_open(&view
->io
, "%s%s", opt_cdup
, opt_file
)) {
4659 if (!run_io_rd(&view
->io
, blame_cat_file_argv
, opt_cdup
, FORMAT_ALL
))
4663 setup_update(view
, opt_file
);
4664 string_format(view
->ref
, "%s ...", opt_file
);
4669 static struct blame_commit
*
4670 get_blame_commit(struct view
*view
, const char *id
)
4674 for (i
= 0; i
< view
->lines
; i
++) {
4675 struct blame
*blame
= view
->line
[i
].data
;
4680 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4681 return blame
->commit
;
4685 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4688 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4694 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4696 const char *pos
= *posref
;
4699 pos
= strchr(pos
+ 1, ' ');
4700 if (!pos
|| !isdigit(pos
[1]))
4702 *number
= atoi(pos
+ 1);
4703 if (*number
< min
|| *number
> max
)
4710 static struct blame_commit
*
4711 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4713 struct blame_commit
*commit
;
4714 struct blame
*blame
;
4715 const char *pos
= text
+ SIZEOF_REV
- 2;
4716 size_t orig_lineno
= 0;
4720 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4723 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4724 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4725 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4728 commit
= get_blame_commit(view
, text
);
4734 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4737 blame
->commit
= commit
;
4738 blame
->lineno
= orig_lineno
+ group
- 1;
4746 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4749 const char **argv
= *opt_ref
? blame_ref_argv
: blame_head_argv
;
4752 if (view
->lines
== 0 && !view
->parent
)
4753 die("No blame exist for %s", view
->vid
);
4755 if (view
->lines
== 0 || !run_io_rd(&io
, argv
, opt_cdup
, FORMAT_ALL
)) {
4756 report("Failed to load blame data");
4760 done_io(view
->pipe
);
4766 size_t linelen
= strlen(line
);
4767 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
4772 blame
->commit
= NULL
;
4773 strncpy(blame
->text
, line
, linelen
);
4774 blame
->text
[linelen
] = 0;
4775 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
4780 match_blame_header(const char *name
, char **line
)
4782 size_t namelen
= strlen(name
);
4783 bool matched
= !strncmp(name
, *line
, namelen
);
4792 blame_read(struct view
*view
, char *line
)
4794 static struct blame_commit
*commit
= NULL
;
4795 static int blamed
= 0;
4796 static bool read_file
= TRUE
;
4799 return blame_read_file(view
, line
, &read_file
);
4806 string_format(view
->ref
, "%s", view
->vid
);
4807 if (view_is_displayed(view
)) {
4808 update_view_title(view
);
4809 redraw_view_from(view
, 0);
4815 commit
= parse_blame_commit(view
, line
, &blamed
);
4816 string_format(view
->ref
, "%s %2d%%", view
->vid
,
4817 view
->lines
? blamed
* 100 / view
->lines
: 0);
4819 } else if (match_blame_header("author ", &line
)) {
4820 commit
->author
= get_author(line
);
4822 } else if (match_blame_header("author-time ", &line
)) {
4823 commit
->time
= (time_t) atol(line
);
4825 } else if (match_blame_header("author-tz ", &line
)) {
4826 parse_timezone(&commit
->time
, line
);
4828 } else if (match_blame_header("summary ", &line
)) {
4829 string_ncopy(commit
->title
, line
, strlen(line
));
4831 } else if (match_blame_header("previous ", &line
)) {
4832 commit
->has_previous
= TRUE
;
4834 } else if (match_blame_header("filename ", &line
)) {
4835 string_ncopy(commit
->filename
, line
, strlen(line
));
4843 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4845 struct blame
*blame
= line
->data
;
4846 time_t *time
= NULL
;
4847 const char *id
= NULL
, *author
= NULL
;
4848 char text
[SIZEOF_STR
];
4850 if (blame
->commit
&& *blame
->commit
->filename
) {
4851 id
= blame
->commit
->id
;
4852 author
= blame
->commit
->author
;
4853 time
= &blame
->commit
->time
;
4856 if (opt_date
&& draw_date(view
, time
))
4859 if (opt_author
&& draw_author(view
, author
))
4862 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
4865 if (draw_lineno(view
, lineno
))
4868 string_expand(text
, sizeof(text
), blame
->text
, opt_tab_size
);
4869 draw_text(view
, LINE_DEFAULT
, text
, TRUE
);
4874 check_blame_commit(struct blame
*blame
, bool check_null_id
)
4877 report("Commit data not loaded yet");
4878 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4879 report("No commit exist for the selected line");
4886 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
4888 const char *diff_tree_argv
[] = {
4889 "git", "diff-tree", "-U0", blame
->commit
->id
,
4890 "--", blame
->commit
->filename
, NULL
4893 int parent_lineno
= -1;
4894 int blamed_lineno
= -1;
4897 if (!run_io(&io
, diff_tree_argv
, NULL
, IO_RD
))
4900 while ((line
= io_get(&io
, '\n', TRUE
))) {
4902 char *pos
= strchr(line
, '+');
4904 parent_lineno
= atoi(line
+ 4);
4906 blamed_lineno
= atoi(pos
+ 1);
4908 } else if (*line
== '+' && parent_lineno
!= -1) {
4909 if (blame
->lineno
== blamed_lineno
- 1 &&
4910 !strcmp(blame
->text
, line
+ 1)) {
4911 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
4922 blame_request(struct view
*view
, enum request request
, struct line
*line
)
4924 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4925 struct blame
*blame
= line
->data
;
4928 case REQ_VIEW_BLAME
:
4929 if (check_blame_commit(blame
, TRUE
)) {
4930 string_copy(opt_ref
, blame
->commit
->id
);
4931 string_copy(opt_file
, blame
->commit
->filename
);
4933 view
->lineno
= blame
->lineno
;
4934 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
4939 if (check_blame_commit(blame
, TRUE
) &&
4940 select_commit_parent(blame
->commit
->id
, opt_ref
,
4941 blame
->commit
->filename
)) {
4942 string_copy(opt_file
, blame
->commit
->filename
);
4943 setup_blame_parent_line(view
, blame
);
4944 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
4949 if (!check_blame_commit(blame
, FALSE
))
4952 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
4953 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
4956 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
4957 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
4958 const char *diff_index_argv
[] = {
4959 "git", "diff-index", "--root", "--patch-with-stat",
4960 "-C", "-M", "HEAD", "--", view
->vid
, NULL
4963 if (!blame
->commit
->has_previous
) {
4964 diff_index_argv
[1] = "diff";
4965 diff_index_argv
[2] = "--no-color";
4966 diff_index_argv
[6] = "--";
4967 diff_index_argv
[7] = "/dev/null";
4970 if (!prepare_update(diff
, diff_index_argv
, NULL
, FORMAT_DASH
)) {
4971 report("Failed to allocate diff command");
4974 flags
|= OPEN_PREPARED
;
4977 open_view(view
, REQ_VIEW_DIFF
, flags
);
4978 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4979 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
4990 blame_grep(struct view
*view
, struct line
*line
)
4992 struct blame
*blame
= line
->data
;
4993 struct blame_commit
*commit
= blame
->commit
;
4994 const char *text
[] = {
4996 commit
? commit
->title
: "",
4997 commit
? commit
->id
: "",
4998 commit
&& opt_author
? commit
->author
: "",
4999 commit
&& opt_date
? mkdate(&commit
->time
) : "",
5003 return grep_text(view
, text
);
5007 blame_select(struct view
*view
, struct line
*line
)
5009 struct blame
*blame
= line
->data
;
5010 struct blame_commit
*commit
= blame
->commit
;
5015 if (!strcmp(commit
->id
, NULL_ID
))
5016 string_ncopy(ref_commit
, "HEAD", 4);
5018 string_copy_rev(ref_commit
, commit
->id
);
5021 static struct view_ops blame_ops
= {
5037 const char *author
; /* Author of the last commit. */
5038 time_t time
; /* Date of the last activity. */
5039 struct ref
*ref
; /* Name and commit ID information. */
5042 static const enum sort_field branch_sort_fields
[] = {
5043 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5045 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5048 branch_compare(const void *l1
, const void *l2
)
5050 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5051 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5053 switch (get_sort_field(branch_sort_state
)) {
5055 return sort_order(branch_sort_state
, branch1
->time
- branch2
->time
);
5057 case ORDERBY_AUTHOR
:
5058 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5062 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5067 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5069 struct branch
*branch
= line
->data
;
5070 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5072 if (opt_date
&& draw_date(view
, branch
->author
? &branch
->time
: NULL
))
5075 if (opt_author
&& draw_author(view
, branch
->author
))
5078 draw_text(view
, type
, branch
->ref
->name
, TRUE
);
5083 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5088 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5091 case REQ_TOGGLE_SORT_FIELD
:
5092 case REQ_TOGGLE_SORT_ORDER
:
5093 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5097 open_view(view
, REQ_VIEW_MAIN
, OPEN_SPLIT
);
5106 branch_read(struct view
*view
, char *line
)
5108 static char id
[SIZEOF_REV
];
5109 struct branch
*reference
;
5115 switch (get_line_type(line
)) {
5117 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5121 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5122 struct branch
*branch
= view
->line
[i
].data
;
5124 if (strcmp(branch
->ref
->id
, id
))
5127 view
->line
[i
].dirty
= TRUE
;
5129 branch
->author
= reference
->author
;
5130 branch
->time
= reference
->time
;
5134 parse_author_line(line
+ STRING_SIZE("author "),
5135 &branch
->author
, &branch
->time
);
5147 branch_open_visitor(void *data
, struct ref
*ref
)
5149 struct view
*view
= data
;
5150 struct branch
*branch
;
5152 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5155 branch
= calloc(1, sizeof(*branch
));
5160 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5164 branch_open(struct view
*view
)
5166 const char *branch_log
[] = {
5167 "git", "log", "--no-color", "--pretty=raw",
5168 "--simplify-by-decoration", "--all", NULL
5171 if (!run_io_rd(&view
->io
, branch_log
, NULL
, FORMAT_NONE
)) {
5172 report("Failed to load branch data");
5176 setup_update(view
, view
->id
);
5177 foreach_ref(branch_open_visitor
, view
);
5178 view
->p_restore
= TRUE
;
5184 branch_grep(struct view
*view
, struct line
*line
)
5186 struct branch
*branch
= line
->data
;
5187 const char *text
[] = {
5193 return grep_text(view
, text
);
5197 branch_select(struct view
*view
, struct line
*line
)
5199 struct branch
*branch
= line
->data
;
5201 string_copy_rev(view
->ref
, branch
->ref
->id
);
5202 string_copy_rev(ref_commit
, branch
->ref
->id
);
5203 string_copy_rev(ref_head
, branch
->ref
->id
);
5206 static struct view_ops branch_ops
= {
5225 char rev
[SIZEOF_REV
];
5226 char name
[SIZEOF_STR
];
5230 char rev
[SIZEOF_REV
];
5231 char name
[SIZEOF_STR
];
5235 static char status_onbranch
[SIZEOF_STR
];
5236 static struct status stage_status
;
5237 static enum line_type stage_line_type
;
5238 static size_t stage_chunks
;
5239 static int *stage_chunk
;
5241 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5243 /* This should work even for the "On branch" line. */
5245 status_has_none(struct view
*view
, struct line
*line
)
5247 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5250 /* Get fields from the diff line:
5251 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5254 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5256 const char *old_mode
= buf
+ 1;
5257 const char *new_mode
= buf
+ 8;
5258 const char *old_rev
= buf
+ 15;
5259 const char *new_rev
= buf
+ 56;
5260 const char *status
= buf
+ 97;
5263 old_mode
[-1] != ':' ||
5264 new_mode
[-1] != ' ' ||
5265 old_rev
[-1] != ' ' ||
5266 new_rev
[-1] != ' ' ||
5270 file
->status
= *status
;
5272 string_copy_rev(file
->old
.rev
, old_rev
);
5273 string_copy_rev(file
->new.rev
, new_rev
);
5275 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5276 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5278 file
->old
.name
[0] = file
->new.name
[0] = 0;
5284 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5286 struct status
*unmerged
= NULL
;
5290 if (!run_io(&io
, argv
, NULL
, IO_RD
))
5293 add_line_data(view
, NULL
, type
);
5295 while ((buf
= io_get(&io
, 0, TRUE
))) {
5296 struct status
*file
= unmerged
;
5299 file
= calloc(1, sizeof(*file
));
5300 if (!file
|| !add_line_data(view
, file
, type
))
5304 /* Parse diff info part. */
5306 file
->status
= status
;
5308 string_copy(file
->old
.rev
, NULL_ID
);
5310 } else if (!file
->status
|| file
== unmerged
) {
5311 if (!status_get_diff(file
, buf
, strlen(buf
)))
5314 buf
= io_get(&io
, 0, TRUE
);
5318 /* Collapse all modified entries that follow an
5319 * associated unmerged entry. */
5320 if (unmerged
== file
) {
5321 unmerged
->status
= 'U';
5323 } else if (file
->status
== 'U') {
5328 /* Grab the old name for rename/copy. */
5329 if (!*file
->old
.name
&&
5330 (file
->status
== 'R' || file
->status
== 'C')) {
5331 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5333 buf
= io_get(&io
, 0, TRUE
);
5338 /* git-ls-files just delivers a NUL separated list of
5339 * file names similar to the second half of the
5340 * git-diff-* output. */
5341 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5342 if (!*file
->old
.name
)
5343 string_copy(file
->old
.name
, file
->new.name
);
5347 if (io_error(&io
)) {
5353 if (!view
->line
[view
->lines
- 1].data
)
5354 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5360 /* Don't show unmerged entries in the staged section. */
5361 static const char *status_diff_index_argv
[] = {
5362 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5363 "--cached", "-M", "HEAD", NULL
5366 static const char *status_diff_files_argv
[] = {
5367 "git", "diff-files", "-z", NULL
5370 static const char *status_list_other_argv
[] = {
5371 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5374 static const char *status_list_no_head_argv
[] = {
5375 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5378 static const char *update_index_argv
[] = {
5379 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5382 /* Restore the previous line number to stay in the context or select a
5383 * line with something that can be updated. */
5385 status_restore(struct view
*view
)
5387 if (view
->p_lineno
>= view
->lines
)
5388 view
->p_lineno
= view
->lines
- 1;
5389 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5391 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5394 /* If the above fails, always skip the "On branch" line. */
5395 if (view
->p_lineno
< view
->lines
)
5396 view
->lineno
= view
->p_lineno
;
5400 if (view
->lineno
< view
->offset
)
5401 view
->offset
= view
->lineno
;
5402 else if (view
->offset
+ view
->height
<= view
->lineno
)
5403 view
->offset
= view
->lineno
- view
->height
+ 1;
5405 view
->p_restore
= FALSE
;
5409 status_update_onbranch(void)
5411 static const char *paths
[][2] = {
5412 { "rebase-apply/rebasing", "Rebasing" },
5413 { "rebase-apply/applying", "Applying mailbox" },
5414 { "rebase-apply/", "Rebasing mailbox" },
5415 { "rebase-merge/interactive", "Interactive rebase" },
5416 { "rebase-merge/", "Rebase merge" },
5417 { "MERGE_HEAD", "Merging" },
5418 { "BISECT_LOG", "Bisecting" },
5419 { "HEAD", "On branch" },
5421 char buf
[SIZEOF_STR
];
5425 if (is_initial_commit()) {
5426 string_copy(status_onbranch
, "Initial commit");
5430 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5431 char *head
= opt_head
;
5433 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5434 lstat(buf
, &stat
) < 0)
5440 if (io_open(&io
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5441 io_read_buf(&io
, buf
, sizeof(buf
))) {
5443 if (!prefixcmp(head
, "refs/heads/"))
5444 head
+= STRING_SIZE("refs/heads/");
5448 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5449 string_copy(status_onbranch
, opt_head
);
5453 string_copy(status_onbranch
, "Not currently on any branch");
5456 /* First parse staged info using git-diff-index(1), then parse unstaged
5457 * info using git-diff-files(1), and finally untracked files using
5458 * git-ls-files(1). */
5460 status_open(struct view
*view
)
5464 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5465 status_update_onbranch();
5467 run_io_bg(update_index_argv
);
5469 if (is_initial_commit()) {
5470 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5472 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5476 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5477 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5480 /* Restore the exact position or use the specialized restore
5482 if (!view
->p_restore
)
5483 status_restore(view
);
5488 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5490 struct status
*status
= line
->data
;
5491 enum line_type type
;
5495 switch (line
->type
) {
5496 case LINE_STAT_STAGED
:
5497 type
= LINE_STAT_SECTION
;
5498 text
= "Changes to be committed:";
5501 case LINE_STAT_UNSTAGED
:
5502 type
= LINE_STAT_SECTION
;
5503 text
= "Changed but not updated:";
5506 case LINE_STAT_UNTRACKED
:
5507 type
= LINE_STAT_SECTION
;
5508 text
= "Untracked files:";
5511 case LINE_STAT_NONE
:
5512 type
= LINE_DEFAULT
;
5513 text
= " (no files)";
5516 case LINE_STAT_HEAD
:
5517 type
= LINE_STAT_HEAD
;
5518 text
= status_onbranch
;
5525 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5527 buf
[0] = status
->status
;
5528 if (draw_text(view
, line
->type
, buf
, TRUE
))
5530 type
= LINE_DEFAULT
;
5531 text
= status
->new.name
;
5534 draw_text(view
, type
, text
, TRUE
);
5539 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5541 if (displayed_views() == 2 || display
[current_view
] != view
)
5542 maximize_view(view
);
5543 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5548 status_enter(struct view
*view
, struct line
*line
)
5550 struct status
*status
= line
->data
;
5551 const char *oldpath
= status
? status
->old
.name
: NULL
;
5552 /* Diffs for unmerged entries are empty when passing the new
5553 * path, so leave it empty. */
5554 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5556 enum open_flags split
;
5557 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5559 if (line
->type
== LINE_STAT_NONE
||
5560 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5561 report("No file to diff");
5565 switch (line
->type
) {
5566 case LINE_STAT_STAGED
:
5567 if (is_initial_commit()) {
5568 const char *no_head_diff_argv
[] = {
5569 "git", "diff", "--no-color", "--patch-with-stat",
5570 "--", "/dev/null", newpath
, NULL
5573 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
, FORMAT_DASH
))
5574 return status_load_error(view
, stage
, newpath
);
5576 const char *index_show_argv
[] = {
5577 "git", "diff-index", "--root", "--patch-with-stat",
5578 "-C", "-M", "--cached", "HEAD", "--",
5579 oldpath
, newpath
, NULL
5582 if (!prepare_update(stage
, index_show_argv
, opt_cdup
, FORMAT_DASH
))
5583 return status_load_error(view
, stage
, newpath
);
5587 info
= "Staged changes to %s";
5589 info
= "Staged changes";
5592 case LINE_STAT_UNSTAGED
:
5594 const char *files_show_argv
[] = {
5595 "git", "diff-files", "--root", "--patch-with-stat",
5596 "-C", "-M", "--", oldpath
, newpath
, NULL
5599 if (!prepare_update(stage
, files_show_argv
, opt_cdup
, FORMAT_DASH
))
5600 return status_load_error(view
, stage
, newpath
);
5602 info
= "Unstaged changes to %s";
5604 info
= "Unstaged changes";
5607 case LINE_STAT_UNTRACKED
:
5609 report("No file to show");
5613 if (!suffixcmp(status
->new.name
, -1, "/")) {
5614 report("Cannot display a directory");
5618 if (!prepare_update_file(stage
, newpath
))
5619 return status_load_error(view
, stage
, newpath
);
5620 info
= "Untracked file %s";
5623 case LINE_STAT_HEAD
:
5627 die("line type %d not handled in switch", line
->type
);
5630 split
= view_is_displayed(view
) ? OPEN_SPLIT
: 0;
5631 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5632 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5634 stage_status
= *status
;
5636 memset(&stage_status
, 0, sizeof(stage_status
));
5639 stage_line_type
= line
->type
;
5641 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5648 status_exists(struct status
*status
, enum line_type type
)
5650 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5651 unsigned long lineno
;
5653 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5654 struct line
*line
= &view
->line
[lineno
];
5655 struct status
*pos
= line
->data
;
5657 if (line
->type
!= type
)
5659 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5660 select_view_line(view
, lineno
);
5663 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5664 select_view_line(view
, lineno
);
5674 status_update_prepare(struct io
*io
, enum line_type type
)
5676 const char *staged_argv
[] = {
5677 "git", "update-index", "-z", "--index-info", NULL
5679 const char *others_argv
[] = {
5680 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5684 case LINE_STAT_STAGED
:
5685 return run_io(io
, staged_argv
, opt_cdup
, IO_WR
);
5687 case LINE_STAT_UNSTAGED
:
5688 return run_io(io
, others_argv
, opt_cdup
, IO_WR
);
5690 case LINE_STAT_UNTRACKED
:
5691 return run_io(io
, others_argv
, NULL
, IO_WR
);
5694 die("line type %d not handled in switch", type
);
5700 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5702 char buf
[SIZEOF_STR
];
5706 case LINE_STAT_STAGED
:
5707 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5710 status
->old
.name
, 0))
5714 case LINE_STAT_UNSTAGED
:
5715 case LINE_STAT_UNTRACKED
:
5716 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
5721 die("line type %d not handled in switch", type
);
5724 return io_write(io
, buf
, bufsize
);
5728 status_update_file(struct status
*status
, enum line_type type
)
5733 if (!status_update_prepare(&io
, type
))
5736 result
= status_update_write(&io
, status
, type
);
5737 return done_io(&io
) && result
;
5741 status_update_files(struct view
*view
, struct line
*line
)
5743 char buf
[sizeof(view
->ref
)];
5746 struct line
*pos
= view
->line
+ view
->lines
;
5749 int cursor_y
= -1, cursor_x
= -1;
5751 if (!status_update_prepare(&io
, line
->type
))
5754 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
5757 string_copy(buf
, view
->ref
);
5758 getsyx(cursor_y
, cursor_x
);
5759 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
5760 int almost_done
= file
* 100 / files
;
5762 if (almost_done
> done
) {
5764 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
5766 update_view_title(view
);
5767 setsyx(cursor_y
, cursor_x
);
5770 result
= status_update_write(&io
, line
->data
, line
->type
);
5772 string_copy(view
->ref
, buf
);
5774 return done_io(&io
) && result
;
5778 status_update(struct view
*view
)
5780 struct line
*line
= &view
->line
[view
->lineno
];
5782 assert(view
->lines
);
5785 /* This should work even for the "On branch" line. */
5786 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
5787 report("Nothing to update");
5791 if (!status_update_files(view
, line
+ 1)) {
5792 report("Failed to update file status");
5796 } else if (!status_update_file(line
->data
, line
->type
)) {
5797 report("Failed to update file status");
5805 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
5807 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
5808 if (type
== LINE_STAT_STAGED
) {
5809 report("Cannot revert changes to staged files");
5810 } else if (type
== LINE_STAT_UNTRACKED
) {
5811 report("Cannot revert changes to untracked files");
5812 } else if (has_none
) {
5813 report("Nothing to revert");
5815 report("Cannot revert changes to multiple files");
5818 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5819 char mode
[10] = "100644";
5820 const char *reset_argv
[] = {
5821 "git", "update-index", "--cacheinfo", mode
,
5822 status
->old
.rev
, status
->old
.name
, NULL
5824 const char *checkout_argv
[] = {
5825 "git", "checkout", "--", status
->old
.name
, NULL
5828 if (status
->status
== 'U') {
5829 string_format(mode
, "%5o", status
->old
.mode
);
5831 if (status
->old
.mode
== 0 && status
->new.mode
== 0) {
5832 reset_argv
[2] = "--force-remove";
5833 reset_argv
[3] = status
->old
.name
;
5834 reset_argv
[4] = NULL
;
5837 if (!run_io_fg(reset_argv
, opt_cdup
))
5839 if (status
->old
.mode
== 0 && status
->new.mode
== 0)
5843 return run_io_fg(checkout_argv
, opt_cdup
);
5850 status_request(struct view
*view
, enum request request
, struct line
*line
)
5852 struct status
*status
= line
->data
;
5855 case REQ_STATUS_UPDATE
:
5856 if (!status_update(view
))
5860 case REQ_STATUS_REVERT
:
5861 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
5865 case REQ_STATUS_MERGE
:
5866 if (!status
|| status
->status
!= 'U') {
5867 report("Merging only possible for files with unmerged status ('U').");
5870 open_mergetool(status
->new.name
);
5876 if (status
->status
== 'D') {
5877 report("File has been deleted.");
5881 open_editor(status
->status
!= '?', status
->new.name
);
5884 case REQ_VIEW_BLAME
:
5886 string_copy(opt_file
, status
->new.name
);
5892 /* After returning the status view has been split to
5893 * show the stage view. No further reloading is
5895 return status_enter(view
, line
);
5898 /* Simply reload the view. */
5905 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
5911 status_select(struct view
*view
, struct line
*line
)
5913 struct status
*status
= line
->data
;
5914 char file
[SIZEOF_STR
] = "all files";
5918 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
5921 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
5924 switch (line
->type
) {
5925 case LINE_STAT_STAGED
:
5926 text
= "Press %s to unstage %s for commit";
5929 case LINE_STAT_UNSTAGED
:
5930 text
= "Press %s to stage %s for commit";
5933 case LINE_STAT_UNTRACKED
:
5934 text
= "Press %s to stage %s for addition";
5937 case LINE_STAT_HEAD
:
5938 case LINE_STAT_NONE
:
5939 text
= "Nothing to update";
5943 die("line type %d not handled in switch", line
->type
);
5946 if (status
&& status
->status
== 'U') {
5947 text
= "Press %s to resolve conflict in %s";
5948 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
5951 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
5954 string_format(view
->ref
, text
, key
, file
);
5958 status_grep(struct view
*view
, struct line
*line
)
5960 struct status
*status
= line
->data
;
5963 const char buf
[2] = { status
->status
, 0 };
5964 const char *text
[] = { status
->new.name
, buf
, NULL
};
5966 return grep_text(view
, text
);
5972 static struct view_ops status_ops
= {
5985 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
5987 while (line
< end
) {
5988 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
5989 !io_write(io
, "\n", 1))
5992 if (line
->type
== LINE_DIFF_CHUNK
||
5993 line
->type
== LINE_DIFF_HEADER
)
6000 static struct line
*
6001 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
6003 for (; view
->line
< line
; line
--)
6004 if (line
->type
== type
)
6011 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
6013 const char *apply_argv
[SIZEOF_ARG
] = {
6014 "git", "apply", "--whitespace=nowarn", NULL
6016 struct line
*diff_hdr
;
6020 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6025 apply_argv
[argc
++] = "--cached";
6026 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6027 apply_argv
[argc
++] = "-R";
6028 apply_argv
[argc
++] = "-";
6029 apply_argv
[argc
++] = NULL
;
6030 if (!run_io(&io
, apply_argv
, opt_cdup
, IO_WR
))
6033 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6034 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6038 run_io_bg(update_index_argv
);
6040 return chunk
? TRUE
: FALSE
;
6044 stage_update(struct view
*view
, struct line
*line
)
6046 struct line
*chunk
= NULL
;
6048 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6049 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6052 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6053 report("Failed to apply chunk");
6057 } else if (!stage_status
.status
) {
6058 view
= VIEW(REQ_VIEW_STATUS
);
6060 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6061 if (line
->type
== stage_line_type
)
6064 if (!status_update_files(view
, line
+ 1)) {
6065 report("Failed to update files");
6069 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6070 report("Failed to update file");
6078 stage_revert(struct view
*view
, struct line
*line
)
6080 struct line
*chunk
= NULL
;
6082 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6083 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6086 if (!prompt_yesno("Are you sure you want to revert changes?"))
6089 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6090 report("Failed to revert chunk");
6096 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6097 stage_line_type
, FALSE
);
6103 stage_next(struct view
*view
, struct line
*line
)
6107 if (!stage_chunks
) {
6108 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6109 if (line
->type
!= LINE_DIFF_CHUNK
)
6112 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6113 report("Allocation failure");
6117 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6121 for (i
= 0; i
< stage_chunks
; i
++) {
6122 if (stage_chunk
[i
] > view
->lineno
) {
6123 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6124 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6129 report("No next chunk found");
6133 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6136 case REQ_STATUS_UPDATE
:
6137 if (!stage_update(view
, line
))
6141 case REQ_STATUS_REVERT
:
6142 if (!stage_revert(view
, line
))
6146 case REQ_STAGE_NEXT
:
6147 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6148 report("File is untracked; press %s to add",
6149 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6152 stage_next(view
, line
);
6156 if (!stage_status
.new.name
[0])
6158 if (stage_status
.status
== 'D') {
6159 report("File has been deleted.");
6163 open_editor(stage_status
.status
!= '?', stage_status
.new.name
);
6167 /* Reload everything ... */
6170 case REQ_VIEW_BLAME
:
6171 if (stage_status
.new.name
[0]) {
6172 string_copy(opt_file
, stage_status
.new.name
);
6178 return pager_request(view
, request
, line
);
6184 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6185 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6187 /* Check whether the staged entry still exists, and close the
6188 * stage view if it doesn't. */
6189 if (!status_exists(&stage_status
, stage_line_type
)) {
6190 status_restore(VIEW(REQ_VIEW_STATUS
));
6191 return REQ_VIEW_CLOSE
;
6194 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6195 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6196 report("Cannot display a directory");
6200 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6201 report("Failed to open file: %s", strerror(errno
));
6205 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6210 static struct view_ops stage_ops
= {
6227 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6228 char title
[128]; /* First line of the commit message. */
6229 const char *author
; /* Author of the commit. */
6230 time_t time
; /* Date from the author ident. */
6231 struct ref_list
*refs
; /* Repository references. */
6232 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6233 size_t graph_size
; /* The width of the graph array. */
6234 bool has_parents
; /* Rewritten --parents seen. */
6237 /* Size of rev graph with no "padding" columns */
6238 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6241 struct rev_graph
*prev
, *next
, *parents
;
6242 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6244 struct commit
*commit
;
6246 unsigned int boundary
:1;
6249 /* Parents of the commit being visualized. */
6250 static struct rev_graph graph_parents
[4];
6252 /* The current stack of revisions on the graph. */
6253 static struct rev_graph graph_stacks
[4] = {
6254 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6255 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6256 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6257 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6261 graph_parent_is_merge(struct rev_graph
*graph
)
6263 return graph
->parents
->size
> 1;
6267 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6269 struct commit
*commit
= graph
->commit
;
6271 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6272 commit
->graph
[commit
->graph_size
++] = symbol
;
6276 clear_rev_graph(struct rev_graph
*graph
)
6278 graph
->boundary
= 0;
6279 graph
->size
= graph
->pos
= 0;
6280 graph
->commit
= NULL
;
6281 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6285 done_rev_graph(struct rev_graph
*graph
)
6287 if (graph_parent_is_merge(graph
) &&
6288 graph
->pos
< graph
->size
- 1 &&
6289 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6290 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6292 graph
->commit
->graph_size
= i
* 2;
6293 while (i
< graph
->next
->size
- 1) {
6294 append_to_rev_graph(graph
, ' ');
6295 append_to_rev_graph(graph
, '\\');
6300 clear_rev_graph(graph
);
6304 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6308 /* "Collapse" duplicate parents lines.
6310 * FIXME: This needs to also update update the drawn graph but
6311 * for now it just serves as a method for pruning graph lines. */
6312 for (i
= 0; i
< graph
->size
; i
++)
6313 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6316 if (graph
->size
< SIZEOF_REVITEMS
) {
6317 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6322 get_rev_graph_symbol(struct rev_graph
*graph
)
6326 if (graph
->boundary
)
6327 symbol
= REVGRAPH_BOUND
;
6328 else if (graph
->parents
->size
== 0)
6329 symbol
= REVGRAPH_INIT
;
6330 else if (graph_parent_is_merge(graph
))
6331 symbol
= REVGRAPH_MERGE
;
6332 else if (graph
->pos
>= graph
->size
)
6333 symbol
= REVGRAPH_BRANCH
;
6335 symbol
= REVGRAPH_COMMIT
;
6341 draw_rev_graph(struct rev_graph
*graph
)
6344 chtype separator
, line
;
6346 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6347 static struct rev_filler fillers
[] = {
6353 chtype symbol
= get_rev_graph_symbol(graph
);
6354 struct rev_filler
*filler
;
6357 if (opt_line_graphics
)
6358 fillers
[DEFAULT
].line
= line_graphics
[LINE_GRAPHIC_VLINE
];
6360 filler
= &fillers
[DEFAULT
];
6362 for (i
= 0; i
< graph
->pos
; i
++) {
6363 append_to_rev_graph(graph
, filler
->line
);
6364 if (graph_parent_is_merge(graph
->prev
) &&
6365 graph
->prev
->pos
== i
)
6366 filler
= &fillers
[RSHARP
];
6368 append_to_rev_graph(graph
, filler
->separator
);
6371 /* Place the symbol for this revision. */
6372 append_to_rev_graph(graph
, symbol
);
6374 if (graph
->prev
->size
> graph
->size
)
6375 filler
= &fillers
[RDIAG
];
6377 filler
= &fillers
[DEFAULT
];
6381 for (; i
< graph
->size
; i
++) {
6382 append_to_rev_graph(graph
, filler
->separator
);
6383 append_to_rev_graph(graph
, filler
->line
);
6384 if (graph_parent_is_merge(graph
->prev
) &&
6385 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6386 filler
= &fillers
[RSHARP
];
6387 if (graph
->prev
->size
> graph
->size
)
6388 filler
= &fillers
[LDIAG
];
6391 if (graph
->prev
->size
> graph
->size
) {
6392 append_to_rev_graph(graph
, filler
->separator
);
6393 if (filler
->line
!= ' ')
6394 append_to_rev_graph(graph
, filler
->line
);
6398 /* Prepare the next rev graph */
6400 prepare_rev_graph(struct rev_graph
*graph
)
6404 /* First, traverse all lines of revisions up to the active one. */
6405 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6406 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6409 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6412 /* Interleave the new revision parent(s). */
6413 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6414 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6416 /* Lastly, put any remaining revisions. */
6417 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6418 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6422 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6424 /* If this is the finalizing update ... */
6426 prepare_rev_graph(graph
);
6428 /* Graph visualization needs a one rev look-ahead,
6429 * so the first update doesn't visualize anything. */
6430 if (!graph
->prev
->commit
)
6433 if (view
->lines
> 2)
6434 view
->line
[view
->lines
- 3].dirty
= 1;
6435 if (view
->lines
> 1)
6436 view
->line
[view
->lines
- 2].dirty
= 1;
6437 draw_rev_graph(graph
->prev
);
6438 done_rev_graph(graph
->prev
->prev
);
6446 static const char *main_argv
[SIZEOF_ARG
] = {
6447 "git", "log", "--no-color", "--pretty=raw", "--parents",
6448 "--topo-order", "%(head)", NULL
6452 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6454 struct commit
*commit
= line
->data
;
6456 if (!commit
->author
)
6459 if (opt_date
&& draw_date(view
, &commit
->time
))
6462 if (opt_author
&& draw_author(view
, commit
->author
))
6465 if (opt_rev_graph
&& commit
->graph_size
&&
6466 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6469 if (opt_show_refs
&& commit
->refs
) {
6472 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6473 struct ref
*ref
= commit
->refs
->refs
[i
];
6474 enum line_type type
;
6477 type
= LINE_MAIN_HEAD
;
6479 type
= LINE_MAIN_LOCAL_TAG
;
6481 type
= LINE_MAIN_TAG
;
6482 else if (ref
->tracked
)
6483 type
= LINE_MAIN_TRACKED
;
6484 else if (ref
->remote
)
6485 type
= LINE_MAIN_REMOTE
;
6487 type
= LINE_MAIN_REF
;
6489 if (draw_text(view
, type
, "[", TRUE
) ||
6490 draw_text(view
, type
, ref
->name
, TRUE
) ||
6491 draw_text(view
, type
, "]", TRUE
))
6494 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6499 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6503 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6505 main_read(struct view
*view
, char *line
)
6507 static struct rev_graph
*graph
= graph_stacks
;
6508 enum line_type type
;
6509 struct commit
*commit
;
6514 if (!view
->lines
&& !view
->parent
)
6515 die("No revisions match the given arguments.");
6516 if (view
->lines
> 0) {
6517 commit
= view
->line
[view
->lines
- 1].data
;
6518 view
->line
[view
->lines
- 1].dirty
= 1;
6519 if (!commit
->author
) {
6522 graph
->commit
= NULL
;
6525 update_rev_graph(view
, graph
);
6527 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6528 clear_rev_graph(&graph_stacks
[i
]);
6532 type
= get_line_type(line
);
6533 if (type
== LINE_COMMIT
) {
6534 commit
= calloc(1, sizeof(struct commit
));
6538 line
+= STRING_SIZE("commit ");
6540 graph
->boundary
= 1;
6544 string_copy_rev(commit
->id
, line
);
6545 commit
->refs
= get_ref_list(commit
->id
);
6546 graph
->commit
= commit
;
6547 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6549 while ((line
= strchr(line
, ' '))) {
6551 push_rev_graph(graph
->parents
, line
);
6552 commit
->has_parents
= TRUE
;
6559 commit
= view
->line
[view
->lines
- 1].data
;
6563 if (commit
->has_parents
)
6565 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6569 parse_author_line(line
+ STRING_SIZE("author "),
6570 &commit
->author
, &commit
->time
);
6571 update_rev_graph(view
, graph
);
6572 graph
= graph
->next
;
6576 /* Fill in the commit title if it has not already been set. */
6577 if (commit
->title
[0])
6580 /* Require titles to start with a non-space character at the
6581 * offset used by git log. */
6582 if (strncmp(line
, " ", 4))
6585 /* Well, if the title starts with a whitespace character,
6586 * try to be forgiving. Otherwise we end up with no title. */
6587 while (isspace(*line
))
6591 /* FIXME: More graceful handling of titles; append "..." to
6592 * shortened titles, etc. */
6594 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6595 view
->line
[view
->lines
- 1].dirty
= 1;
6602 main_request(struct view
*view
, enum request request
, struct line
*line
)
6604 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
6608 open_view(view
, REQ_VIEW_DIFF
, flags
);
6612 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6622 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6627 if (!opt_show_refs
|| !list
)
6630 for (i
= 0; i
< list
->size
; i
++) {
6631 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6639 main_grep(struct view
*view
, struct line
*line
)
6641 struct commit
*commit
= line
->data
;
6642 const char *text
[] = {
6644 opt_author
? commit
->author
: "",
6645 opt_date
? mkdate(&commit
->time
) : "",
6649 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6653 main_select(struct view
*view
, struct line
*line
)
6655 struct commit
*commit
= line
->data
;
6657 string_copy_rev(view
->ref
, commit
->id
);
6658 string_copy_rev(ref_commit
, view
->ref
);
6661 static struct view_ops main_ops
= {
6674 * Unicode / UTF-8 handling
6676 * NOTE: Much of the following code for dealing with Unicode is derived from
6677 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6678 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6682 unicode_width(unsigned long c
)
6685 (c
<= 0x115f /* Hangul Jamo */
6688 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
6690 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
6691 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
6692 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
6693 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
6694 || (c
>= 0xffe0 && c
<= 0xffe6)
6695 || (c
>= 0x20000 && c
<= 0x2fffd)
6696 || (c
>= 0x30000 && c
<= 0x3fffd)))
6700 return opt_tab_size
;
6705 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6706 * Illegal bytes are set one. */
6707 static const unsigned char utf8_bytes
[256] = {
6708 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6709 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6710 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6711 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6712 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6713 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6714 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
6715 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
6718 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6719 static inline unsigned long
6720 utf8_to_unicode(const char *string
, size_t length
)
6722 unsigned long unicode
;
6726 unicode
= string
[0];
6729 unicode
= (string
[0] & 0x1f) << 6;
6730 unicode
+= (string
[1] & 0x3f);
6733 unicode
= (string
[0] & 0x0f) << 12;
6734 unicode
+= ((string
[1] & 0x3f) << 6);
6735 unicode
+= (string
[2] & 0x3f);
6738 unicode
= (string
[0] & 0x0f) << 18;
6739 unicode
+= ((string
[1] & 0x3f) << 12);
6740 unicode
+= ((string
[2] & 0x3f) << 6);
6741 unicode
+= (string
[3] & 0x3f);
6744 unicode
= (string
[0] & 0x0f) << 24;
6745 unicode
+= ((string
[1] & 0x3f) << 18);
6746 unicode
+= ((string
[2] & 0x3f) << 12);
6747 unicode
+= ((string
[3] & 0x3f) << 6);
6748 unicode
+= (string
[4] & 0x3f);
6751 unicode
= (string
[0] & 0x01) << 30;
6752 unicode
+= ((string
[1] & 0x3f) << 24);
6753 unicode
+= ((string
[2] & 0x3f) << 18);
6754 unicode
+= ((string
[3] & 0x3f) << 12);
6755 unicode
+= ((string
[4] & 0x3f) << 6);
6756 unicode
+= (string
[5] & 0x3f);
6759 die("Invalid Unicode length");
6762 /* Invalid characters could return the special 0xfffd value but NUL
6763 * should be just as good. */
6764 return unicode
> 0xffff ? 0 : unicode
;
6767 /* Calculates how much of string can be shown within the given maximum width
6768 * and sets trimmed parameter to non-zero value if all of string could not be
6769 * shown. If the reserve flag is TRUE, it will reserve at least one
6770 * trailing character, which can be useful when drawing a delimiter.
6772 * Returns the number of bytes to output from string to satisfy max_width. */
6774 utf8_length(const char **start
, size_t skip
, int *width
, size_t max_width
, int *trimmed
, bool reserve
)
6776 const char *string
= *start
;
6777 const char *end
= strchr(string
, '\0');
6778 unsigned char last_bytes
= 0;
6779 size_t last_ucwidth
= 0;
6784 while (string
< end
) {
6785 int c
= *(unsigned char *) string
;
6786 unsigned char bytes
= utf8_bytes
[c
];
6788 unsigned long unicode
;
6790 if (string
+ bytes
> end
)
6793 /* Change representation to figure out whether
6794 * it is a single- or double-width character. */
6796 unicode
= utf8_to_unicode(string
, bytes
);
6797 /* FIXME: Graceful handling of invalid Unicode character. */
6801 ucwidth
= unicode_width(unicode
);
6803 skip
-= ucwidth
<= skip
? ucwidth
: skip
;
6807 if (*width
> max_width
) {
6810 if (reserve
&& *width
== max_width
) {
6811 string
-= last_bytes
;
6812 *width
-= last_ucwidth
;
6818 last_bytes
= ucwidth
? bytes
: 0;
6819 last_ucwidth
= ucwidth
;
6822 return string
- *start
;
6830 /* Whether or not the curses interface has been initialized. */
6831 static bool cursed
= FALSE
;
6833 /* Terminal hacks and workarounds. */
6834 static bool use_scroll_redrawwin
;
6835 static bool use_scroll_status_wclear
;
6837 /* The status window is used for polling keystrokes. */
6838 static WINDOW
*status_win
;
6840 /* Reading from the prompt? */
6841 static bool input_mode
= FALSE
;
6843 static bool status_empty
= FALSE
;
6845 /* Update status and title window. */
6847 report(const char *msg
, ...)
6849 struct view
*view
= display
[current_view
];
6855 char buf
[SIZEOF_STR
];
6858 va_start(args
, msg
);
6859 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6860 buf
[sizeof(buf
) - 1] = 0;
6861 buf
[sizeof(buf
) - 2] = '.';
6862 buf
[sizeof(buf
) - 3] = '.';
6863 buf
[sizeof(buf
) - 4] = '.';
6869 if (!status_empty
|| *msg
) {
6872 va_start(args
, msg
);
6874 wmove(status_win
, 0, 0);
6875 if (view
->has_scrolled
&& use_scroll_status_wclear
)
6878 vwprintw(status_win
, msg
, args
);
6879 status_empty
= FALSE
;
6881 status_empty
= TRUE
;
6883 wclrtoeol(status_win
);
6884 wnoutrefresh(status_win
);
6889 update_view_title(view
);
6892 /* Controls when nodelay should be in effect when polling user input. */
6894 set_nonblocking_input(bool loading
)
6896 static unsigned int loading_views
;
6898 if ((loading
== FALSE
&& loading_views
-- == 1) ||
6899 (loading
== TRUE
&& loading_views
++ == 0))
6900 nodelay(status_win
, loading
);
6909 /* Initialize the curses library */
6910 if (isatty(STDIN_FILENO
)) {
6911 cursed
= !!initscr();
6914 /* Leave stdin and stdout alone when acting as a pager. */
6915 opt_tty
= fopen("/dev/tty", "r+");
6917 die("Failed to open /dev/tty");
6918 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
6922 die("Failed to initialize curses");
6924 nonl(); /* Disable conversion and detect newlines from input. */
6925 cbreak(); /* Take input chars one at a time, no wait for \n */
6926 noecho(); /* Don't echo input */
6927 leaveok(stdscr
, FALSE
);
6932 getmaxyx(stdscr
, y
, x
);
6933 status_win
= newwin(1, 0, y
- 1, 0);
6935 die("Failed to create status window");
6937 /* Enable keyboard mapping */
6938 keypad(status_win
, TRUE
);
6939 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
6941 TABSIZE
= opt_tab_size
;
6942 if (opt_line_graphics
) {
6943 line_graphics
[LINE_GRAPHIC_VLINE
] = ACS_VLINE
;
6946 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
6947 if (term
&& !strcmp(term
, "gnome-terminal")) {
6948 /* In the gnome-terminal-emulator, the message from
6949 * scrolling up one line when impossible followed by
6950 * scrolling down one line causes corruption of the
6951 * status line. This is fixed by calling wclear. */
6952 use_scroll_status_wclear
= TRUE
;
6953 use_scroll_redrawwin
= FALSE
;
6955 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
6956 /* No problems with full optimizations in xrvt-(unicode)
6958 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
6961 /* When scrolling in (u)xterm the last line in the
6962 * scrolling direction will update slowly. */
6963 use_scroll_redrawwin
= TRUE
;
6964 use_scroll_status_wclear
= FALSE
;
6969 get_input(int prompt_position
)
6972 int i
, key
, cursor_y
, cursor_x
;
6974 if (prompt_position
)
6978 foreach_view (view
, i
) {
6980 if (view_is_displayed(view
) && view
->has_scrolled
&&
6981 use_scroll_redrawwin
)
6982 redrawwin(view
->win
);
6983 view
->has_scrolled
= FALSE
;
6986 /* Update the cursor position. */
6987 if (prompt_position
) {
6988 getbegyx(status_win
, cursor_y
, cursor_x
);
6989 cursor_x
= prompt_position
;
6991 view
= display
[current_view
];
6992 getbegyx(view
->win
, cursor_y
, cursor_x
);
6993 cursor_x
= view
->width
- 1;
6994 cursor_y
+= view
->lineno
- view
->offset
;
6996 setsyx(cursor_y
, cursor_x
);
6998 /* Refresh, accept single keystroke of input */
7000 key
= wgetch(status_win
);
7002 /* wgetch() with nodelay() enabled returns ERR when
7003 * there's no input. */
7006 } else if (key
== KEY_RESIZE
) {
7009 getmaxyx(stdscr
, height
, width
);
7011 wresize(status_win
, 1, width
);
7012 mvwin(status_win
, height
- 1, 0);
7013 wnoutrefresh(status_win
);
7015 redraw_display(TRUE
);
7025 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7027 enum input_status status
= INPUT_OK
;
7028 static char buf
[SIZEOF_STR
];
7033 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7036 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7037 wclrtoeol(status_win
);
7039 key
= get_input(pos
+ 1);
7044 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7051 status
= INPUT_CANCEL
;
7055 status
= INPUT_CANCEL
;
7059 if (pos
>= sizeof(buf
)) {
7060 report("Input string too long");
7064 status
= handler(data
, buf
, key
);
7065 if (status
== INPUT_OK
)
7066 buf
[pos
++] = (char) key
;
7070 /* Clear the status window */
7071 status_empty
= FALSE
;
7074 if (status
== INPUT_CANCEL
)
7082 static enum input_status
7083 prompt_yesno_handler(void *data
, char *buf
, int c
)
7085 if (c
== 'y' || c
== 'Y')
7087 if (c
== 'n' || c
== 'N')
7088 return INPUT_CANCEL
;
7093 prompt_yesno(const char *prompt
)
7095 char prompt2
[SIZEOF_STR
];
7097 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7100 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7103 static enum input_status
7104 read_prompt_handler(void *data
, char *buf
, int c
)
7106 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7110 read_prompt(const char *prompt
)
7112 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7115 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7117 enum input_status status
= INPUT_OK
;
7120 while (items
[size
].text
)
7123 while (status
== INPUT_OK
) {
7124 const struct menu_item
*item
= &items
[*selected
];
7128 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7129 prompt
, *selected
+ 1, size
);
7131 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7132 wprintw(status_win
, "%s", item
->text
);
7133 wclrtoeol(status_win
);
7135 key
= get_input(COLS
- 1);
7140 status
= INPUT_STOP
;
7145 *selected
= *selected
- 1;
7147 *selected
= size
- 1;
7152 *selected
= (*selected
+ 1) % size
;
7156 status
= INPUT_CANCEL
;
7160 for (i
= 0; items
[i
].text
; i
++)
7161 if (items
[i
].hotkey
== key
) {
7163 status
= INPUT_STOP
;
7169 /* Clear the status window */
7170 status_empty
= FALSE
;
7173 return status
!= INPUT_CANCEL
;
7177 * Repository properties
7180 static struct ref
**refs
= NULL
;
7181 static size_t refs_size
= 0;
7183 static struct ref_list
**ref_lists
= NULL
;
7184 static size_t ref_lists_size
= 0;
7186 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7187 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7188 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7191 compare_refs(const void *ref1_
, const void *ref2_
)
7193 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7194 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7196 if (ref1
->tag
!= ref2
->tag
)
7197 return ref2
->tag
- ref1
->tag
;
7198 if (ref1
->ltag
!= ref2
->ltag
)
7199 return ref2
->ltag
- ref2
->ltag
;
7200 if (ref1
->head
!= ref2
->head
)
7201 return ref2
->head
- ref1
->head
;
7202 if (ref1
->tracked
!= ref2
->tracked
)
7203 return ref2
->tracked
- ref1
->tracked
;
7204 if (ref1
->remote
!= ref2
->remote
)
7205 return ref2
->remote
- ref1
->remote
;
7206 return strcmp(ref1
->name
, ref2
->name
);
7210 foreach_ref(bool (*visitor
)(void *data
, struct ref
*ref
), void *data
)
7214 for (i
= 0; i
< refs_size
; i
++)
7215 if (!visitor(data
, refs
[i
]))
7219 static struct ref_list
*
7220 get_ref_list(const char *id
)
7222 struct ref_list
*list
;
7225 for (i
= 0; i
< ref_lists_size
; i
++)
7226 if (!strcmp(id
, ref_lists
[i
]->id
))
7227 return ref_lists
[i
];
7229 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7231 list
= calloc(1, sizeof(*list
));
7235 for (i
= 0; i
< refs_size
; i
++) {
7236 if (!strcmp(id
, refs
[i
]->id
) &&
7237 realloc_refs_list(&list
->refs
, list
->size
, 1))
7238 list
->refs
[list
->size
++] = refs
[i
];
7246 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7247 ref_lists
[ref_lists_size
++] = list
;
7252 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
7254 struct ref
*ref
= NULL
;
7257 bool remote
= FALSE
;
7258 bool tracked
= FALSE
;
7260 int from
= 0, to
= refs_size
- 1;
7262 if (!prefixcmp(name
, "refs/tags/")) {
7263 if (!suffixcmp(name
, namelen
, "^{}")) {
7271 namelen
-= STRING_SIZE("refs/tags/");
7272 name
+= STRING_SIZE("refs/tags/");
7274 } else if (!prefixcmp(name
, "refs/remotes/")) {
7276 namelen
-= STRING_SIZE("refs/remotes/");
7277 name
+= STRING_SIZE("refs/remotes/");
7278 tracked
= !strcmp(opt_remote
, name
);
7280 } else if (!prefixcmp(name
, "refs/heads/")) {
7281 namelen
-= STRING_SIZE("refs/heads/");
7282 name
+= STRING_SIZE("refs/heads/");
7283 head
= !strncmp(opt_head
, name
, namelen
);
7285 } else if (!strcmp(name
, "HEAD")) {
7286 string_ncopy(opt_head_rev
, id
, idlen
);
7290 /* If we are reloading or it's an annotated tag, replace the
7291 * previous SHA1 with the resolved commit id; relies on the fact
7292 * git-ls-remote lists the commit id of an annotated tag right
7293 * before the commit id it points to. */
7294 while (from
<= to
) {
7295 size_t pos
= (to
+ from
) / 2;
7296 int cmp
= strcmp(name
, refs
[pos
]->name
);
7310 if (!realloc_refs(&refs
, refs_size
, 1))
7312 ref
= calloc(1, sizeof(*ref
) + namelen
);
7315 memmove(refs
+ from
+ 1, refs
+ from
,
7316 (refs_size
- from
) * sizeof(*refs
));
7318 strncpy(ref
->name
, name
, namelen
);
7325 ref
->remote
= remote
;
7326 ref
->tracked
= tracked
;
7327 string_copy_rev(ref
->id
, id
);
7335 const char *head_argv
[] = {
7336 "git", "symbolic-ref", "HEAD", NULL
7338 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7339 "git", "ls-remote", opt_git_dir
, NULL
7341 static bool init
= FALSE
;
7345 argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE");
7352 if (run_io_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7353 !prefixcmp(opt_head
, "refs/heads/")) {
7354 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7356 memmove(opt_head
, offset
, strlen(offset
) + 1);
7359 for (i
= 0; i
< refs_size
; i
++)
7362 if (run_io_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7365 /* Update the ref lists to reflect changes. */
7366 for (i
= 0; i
< ref_lists_size
; i
++) {
7367 struct ref_list
*list
= ref_lists
[i
];
7370 for (old
= new = 0; old
< list
->size
; old
++)
7371 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7372 list
->refs
[new++] = list
->refs
[old
];
7380 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7382 if (!strcmp(name
, ".remote")) {
7383 string_ncopy(opt_remote
, value
, valuelen
);
7385 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7386 size_t from
= strlen(opt_remote
);
7388 if (!prefixcmp(value
, "refs/heads/"))
7389 value
+= STRING_SIZE("refs/heads/");
7391 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7397 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7399 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7400 int argc
= 1 + (cmd
== option_set_command
);
7403 if (!argv_from_string(argv
, &argc
, value
))
7404 config_msg
= "Too many option arguments";
7406 error
= cmd(argc
, argv
);
7409 warn("Option 'tig.%s': %s", name
, config_msg
);
7413 set_environment_variable(const char *name
, const char *value
)
7415 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7416 char *env
= malloc(len
);
7419 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7427 set_work_tree(const char *value
)
7429 char cwd
[SIZEOF_STR
];
7431 if (!getcwd(cwd
, sizeof(cwd
)))
7432 die("Failed to get cwd path: %s", strerror(errno
));
7433 if (chdir(opt_git_dir
) < 0)
7434 die("Failed to chdir(%s): %s", strerror(errno
));
7435 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7436 die("Failed to get git path: %s", strerror(errno
));
7438 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7439 if (chdir(value
) < 0)
7440 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7441 if (!getcwd(cwd
, sizeof(cwd
)))
7442 die("Failed to get cwd path: %s", strerror(errno
));
7443 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7444 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7445 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7446 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7447 opt_is_inside_work_tree
= TRUE
;
7451 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7453 if (!strcmp(name
, "i18n.commitencoding"))
7454 string_ncopy(opt_encoding
, value
, valuelen
);
7456 else if (!strcmp(name
, "core.editor"))
7457 string_ncopy(opt_editor
, value
, valuelen
);
7459 else if (!strcmp(name
, "core.worktree"))
7460 set_work_tree(value
);
7462 else if (!prefixcmp(name
, "tig.color."))
7463 set_repo_config_option(name
+ 10, value
, option_color_command
);
7465 else if (!prefixcmp(name
, "tig.bind."))
7466 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7468 else if (!prefixcmp(name
, "tig."))
7469 set_repo_config_option(name
+ 4, value
, option_set_command
);
7471 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7472 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7473 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7479 load_git_config(void)
7481 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7483 return run_io_load(config_list_argv
, "=", read_repo_config_option
);
7487 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7489 if (!opt_git_dir
[0]) {
7490 string_ncopy(opt_git_dir
, name
, namelen
);
7492 } else if (opt_is_inside_work_tree
== -1) {
7493 /* This can be 3 different values depending on the
7494 * version of git being used. If git-rev-parse does not
7495 * understand --is-inside-work-tree it will simply echo
7496 * the option else either "true" or "false" is printed.
7497 * Default to true for the unknown case. */
7498 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7500 } else if (*name
== '.') {
7501 string_ncopy(opt_cdup
, name
, namelen
);
7504 string_ncopy(opt_prefix
, name
, namelen
);
7511 load_repo_info(void)
7513 const char *rev_parse_argv
[] = {
7514 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7515 "--show-cdup", "--show-prefix", NULL
7518 return run_io_load(rev_parse_argv
, "=", read_repo_info
);
7526 static const char usage
[] =
7527 "tig " TIG_VERSION
" (" __DATE__
")\n"
7529 "Usage: tig [options] [revs] [--] [paths]\n"
7530 " or: tig show [options] [revs] [--] [paths]\n"
7531 " or: tig blame [rev] path\n"
7533 " or: tig < [git command output]\n"
7536 " -v, --version Show version and exit\n"
7537 " -h, --help Show help message and exit";
7539 static void __NORETURN
7542 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7548 static void __NORETURN
7549 die(const char *err
, ...)
7555 va_start(args
, err
);
7556 fputs("tig: ", stderr
);
7557 vfprintf(stderr
, err
, args
);
7558 fputs("\n", stderr
);
7565 warn(const char *msg
, ...)
7569 va_start(args
, msg
);
7570 fputs("tig warning: ", stderr
);
7571 vfprintf(stderr
, msg
, args
);
7572 fputs("\n", stderr
);
7577 parse_options(int argc
, const char *argv
[])
7579 enum request request
= REQ_VIEW_MAIN
;
7580 const char *subcommand
;
7581 bool seen_dashdash
= FALSE
;
7582 /* XXX: This is vulnerable to the user overriding options
7583 * required for the main view parser. */
7584 const char *custom_argv
[SIZEOF_ARG
] = {
7585 "git", "log", "--no-color", "--pretty=raw", "--parents",
7586 "--topo-order", NULL
7590 if (!isatty(STDIN_FILENO
)) {
7591 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7592 return REQ_VIEW_PAGER
;
7598 subcommand
= argv
[1];
7599 if (!strcmp(subcommand
, "status")) {
7601 warn("ignoring arguments after `%s'", subcommand
);
7602 return REQ_VIEW_STATUS
;
7604 } else if (!strcmp(subcommand
, "blame")) {
7605 if (argc
<= 2 || argc
> 4)
7606 die("invalid number of options to blame\n\n%s", usage
);
7610 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7614 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7615 return REQ_VIEW_BLAME
;
7617 } else if (!strcmp(subcommand
, "show")) {
7618 request
= REQ_VIEW_DIFF
;
7625 custom_argv
[1] = subcommand
;
7629 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7630 const char *opt
= argv
[i
];
7632 if (seen_dashdash
|| !strcmp(opt
, "--")) {
7633 seen_dashdash
= TRUE
;
7635 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7636 printf("tig version %s\n", TIG_VERSION
);
7639 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7640 printf("%s\n", usage
);
7644 custom_argv
[j
++] = opt
;
7645 if (j
>= ARRAY_SIZE(custom_argv
))
7646 die("command too long");
7649 if (!prepare_update(VIEW(request
), custom_argv
, NULL
, FORMAT_NONE
))
7650 die("Failed to format arguments");
7656 main(int argc
, const char *argv
[])
7658 enum request request
= parse_options(argc
, argv
);
7662 signal(SIGINT
, quit
);
7663 signal(SIGPIPE
, SIG_IGN
);
7665 if (setlocale(LC_ALL
, "")) {
7666 char *codeset
= nl_langinfo(CODESET
);
7668 string_ncopy(opt_codeset
, codeset
, strlen(codeset
));
7671 if (load_repo_info() == ERR
)
7672 die("Failed to load repo info.");
7674 if (load_options() == ERR
)
7675 die("Failed to load user config.");
7677 if (load_git_config() == ERR
)
7678 die("Failed to load repo config.");
7680 /* Require a git repository unless when running in pager mode. */
7681 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7682 die("Not a git repository");
7684 if (*opt_encoding
&& strcasecmp(opt_encoding
, "UTF-8"))
7687 if (*opt_codeset
&& strcmp(opt_codeset
, opt_encoding
)) {
7688 opt_iconv
= iconv_open(opt_codeset
, opt_encoding
);
7689 if (opt_iconv
== ICONV_NONE
)
7690 die("Failed to initialize character set conversion");
7693 if (load_refs() == ERR
)
7694 die("Failed to load refs.");
7696 foreach_view (view
, i
)
7697 argv_from_env(view
->ops
->argv
, view
->cmd_env
);
7701 if (request
!= REQ_NONE
)
7702 open_view(NULL
, request
, OPEN_PREPARED
);
7703 request
= request
== REQ_NONE
? REQ_VIEW_MAIN
: REQ_NONE
;
7705 while (view_driver(display
[current_view
], request
)) {
7706 int key
= get_input(0);
7708 view
= display
[current_view
];
7709 request
= get_keybinding(view
->keymap
, key
);
7711 /* Some low-level request handling. This keeps access to
7712 * status_win restricted. */
7716 char *cmd
= read_prompt(":");
7718 if (cmd
&& isdigit(*cmd
)) {
7719 int lineno
= view
->lineno
+ 1;
7721 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7722 select_view_line(view
, lineno
- 1);
7725 report("Unable to parse '%s' as a line number", cmd
);
7729 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7730 const char *argv
[SIZEOF_ARG
] = { "git" };
7733 /* When running random commands, initially show the
7734 * command in the title. However, it maybe later be
7735 * overwritten if a commit line is selected. */
7736 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7738 if (!argv_from_string(argv
, &argc
, cmd
)) {
7739 report("Too many arguments");
7740 } else if (!prepare_update(next
, argv
, NULL
, FORMAT_DASH
)) {
7741 report("Failed to format command");
7743 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7751 case REQ_SEARCH_BACK
:
7753 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7754 char *search
= read_prompt(prompt
);
7757 string_ncopy(opt_search
, search
, strlen(search
));
7758 else if (*opt_search
)
7759 request
= request
== REQ_SEARCH
?