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 ")
110 #define AUTHOR_COLS 19
112 #define MIN_VIEW_HEIGHT 4
114 #define NULL_ID "0000000000000000000000000000000000000000"
116 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
118 /* Some ASCII-shorthands fitted into the ncurses namespace. */
120 #define KEY_RETURN '\r'
125 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
126 unsigned int head
:1; /* Is it the current HEAD? */
127 unsigned int tag
:1; /* Is it a tag? */
128 unsigned int ltag
:1; /* If so, is the tag local? */
129 unsigned int remote
:1; /* Is it a remote ref? */
130 unsigned int tracked
:1; /* Is it the remote for the current HEAD? */
131 char name
[1]; /* Ref name; tag or head names are shortened. */
135 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
136 size_t size
; /* Number of refs. */
137 struct ref
**refs
; /* References for this ID. */
140 static struct ref_list
*get_ref_list(const char *id
);
141 static void foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
);
142 static int load_refs(void);
145 FORMAT_ALL
, /* Perform replacement in all arguments. */
146 FORMAT_DASH
, /* Perform replacement up until "--". */
147 FORMAT_NONE
/* No replacement should be performed. */
150 static bool format_argv(const char *dst
[], const char *src
[], enum format_flags flags
);
159 typedef enum input_status (*input_handler
)(void *data
, char *buf
, int c
);
161 static char *prompt_input(const char *prompt
, input_handler handler
, void *data
);
162 static bool prompt_yesno(const char *prompt
);
170 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
);
173 * Allocation helpers ... Entering macro hell to never be seen again.
176 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
178 name(type **mem, size_t size, size_t increase) \
180 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
181 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
184 if (mem == NULL || num_chunks != num_chunks_new) { \
185 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
198 string_ncopy_do(char *dst
, size_t dstlen
, const char *src
, size_t srclen
)
200 if (srclen
> dstlen
- 1)
203 strncpy(dst
, src
, srclen
);
207 /* Shorthands for safely copying into a fixed buffer. */
209 #define string_copy(dst, src) \
210 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
212 #define string_ncopy(dst, src, srclen) \
213 string_ncopy_do(dst, sizeof(dst), src, srclen)
215 #define string_copy_rev(dst, src) \
216 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
218 #define string_add(dst, from, src) \
219 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
222 string_expand(char *dst
, size_t dstlen
, const char *src
, int tabsize
)
226 for (size
= pos
= 0; size
< dstlen
- 1 && src
[pos
]; pos
++) {
227 if (src
[pos
] == '\t') {
228 size_t expanded
= tabsize
- (size
% tabsize
);
230 if (expanded
+ size
>= dstlen
- 1)
231 expanded
= dstlen
- size
- 1;
232 memcpy(dst
+ size
, " ", expanded
);
235 dst
[size
++] = src
[pos
];
243 chomp_string(char *name
)
247 while (isspace(*name
))
250 namelen
= strlen(name
) - 1;
251 while (namelen
> 0 && isspace(name
[namelen
]))
258 string_nformat(char *buf
, size_t bufsize
, size_t *bufpos
, const char *fmt
, ...)
261 size_t pos
= bufpos
? *bufpos
: 0;
264 pos
+= vsnprintf(buf
+ pos
, bufsize
- pos
, fmt
, args
);
270 return pos
>= bufsize
? FALSE
: TRUE
;
273 #define string_format(buf, fmt, args...) \
274 string_nformat(buf, sizeof(buf), NULL, fmt, args)
276 #define string_format_from(buf, from, fmt, args...) \
277 string_nformat(buf, sizeof(buf), from, fmt, args)
280 string_enum_compare(const char *str1
, const char *str2
, int len
)
284 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
286 /* Diff-Header == DIFF_HEADER */
287 for (i
= 0; i
< len
; i
++) {
288 if (toupper(str1
[i
]) == toupper(str2
[i
]))
291 if (string_enum_sep(str1
[i
]) &&
292 string_enum_sep(str2
[i
]))
295 return str1
[i
] - str2
[i
];
301 #define enum_equals(entry, str, len) \
302 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
310 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
313 enum_map_name(const char *name
, size_t namelen
)
315 static char buf
[SIZEOF_STR
];
318 for (bufpos
= 0; bufpos
<= namelen
; bufpos
++) {
319 buf
[bufpos
] = tolower(name
[bufpos
]);
320 if (buf
[bufpos
] == '_')
328 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
331 map_enum_do(const struct enum_map
*map
, size_t map_size
, int *value
, const char *name
)
333 size_t namelen
= strlen(name
);
336 for (i
= 0; i
< map_size
; i
++)
337 if (enum_equals(map
[i
], name
, namelen
)) {
338 *value
= map
[i
].value
;
345 #define map_enum(attr, map, name) \
346 map_enum_do(map, ARRAY_SIZE(map), attr, name)
348 #define prefixcmp(str1, str2) \
349 strncmp(str1, str2, STRING_SIZE(str2))
352 suffixcmp(const char *str
, int slen
, const char *suffix
)
354 size_t len
= slen
>= 0 ? slen
: strlen(str
);
355 size_t suffixlen
= strlen(suffix
);
357 return suffixlen
< len
? strcmp(str
+ len
- suffixlen
, suffix
) : -1;
362 * What value of "tz" was in effect back then at "time" in the
365 static int local_tzoffset(time_t time
)
369 int offset
, eastwest
;
372 localtime_r(&t
, &tm
);
373 t_local
= mktime(&tm
);
377 offset
= t
- t_local
;
380 offset
= t_local
- t
;
382 offset
/= 60; /* in minutes */
383 offset
= (offset
% 60) + ((offset
/ 60) * 100);
384 return offset
* eastwest
;
394 #define DATE_(name) DATE_##name
399 static const struct enum_map date_map
[] = {
400 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
406 string_date(const time_t *time
, enum date date
)
408 static char buf
[DATE_COLS
+ 1];
409 static const struct enum_map reldate
[] = {
410 { "second", 1, 60 * 2 },
411 { "minute", 60, 60 * 60 * 2 },
412 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
413 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
414 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
415 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
419 if (date
== DATE_RELATIVE
) {
421 time_t date
= *time
+ local_tzoffset(*time
);
425 gettimeofday(&now
, NULL
);
426 seconds
= now
.tv_sec
< date
? date
- now
.tv_sec
: now
.tv_sec
- date
;
427 for (i
= 0; i
< ARRAY_SIZE(reldate
); i
++) {
428 if (seconds
>= reldate
[i
].value
)
431 seconds
/= reldate
[i
].namelen
;
432 if (!string_format(buf
, "%ld %s%s %s",
433 seconds
, reldate
[i
].name
,
434 seconds
> 1 ? "s" : "",
435 now
.tv_sec
>= date
? "ago" : "ahead"))
442 return strftime(buf
, sizeof(buf
), DATE_FORMAT
, &tm
) ? buf
: NULL
;
446 #define AUTHOR_VALUES \
452 #define AUTHOR_(name) AUTHOR_##name
455 AUTHOR_DEFAULT
= AUTHOR_FULL
458 static const struct enum_map author_map
[] = {
459 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
464 /* FIXME: Handle multi-byte and multi-column characters. */
466 get_author_initials(const char *author
, size_t max_columns
)
468 static char initials
[AUTHOR_COLS
];
471 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
473 memset(initials
, 0, sizeof(initials
));
474 for (pos
= 0; *author
&& pos
< sizeof(initials
) - 1; author
++, pos
++) {
475 while (is_initial_sep(*author
))
477 strncpy(&initials
[pos
], author
, sizeof(initials
) - 1 - pos
);
478 while (*author
&& author
[1] && !is_initial_sep(author
[1]))
487 argv_from_string(const char *argv
[SIZEOF_ARG
], int *argc
, char *cmd
)
491 while (*cmd
&& *argc
< SIZEOF_ARG
&& (valuelen
= strcspn(cmd
, " \t"))) {
492 bool advance
= cmd
[valuelen
] != 0;
495 argv
[(*argc
)++] = chomp_string(cmd
);
496 cmd
= chomp_string(cmd
+ valuelen
+ advance
);
499 if (*argc
< SIZEOF_ARG
)
501 return *argc
< SIZEOF_ARG
;
505 argv_from_env(const char **argv
, const char *name
)
507 char *env
= argv
? getenv(name
) : NULL
;
512 if (env
&& !argv_from_string(argv
, &argc
, env
))
513 die("Too many arguments in the `%s` environment variable", name
);
518 * Executing external commands.
522 IO_FD
, /* File descriptor based IO. */
523 IO_BG
, /* Execute command in the background. */
524 IO_FG
, /* Execute command with same std{in,out,err}. */
525 IO_RD
, /* Read only fork+exec IO. */
526 IO_WR
, /* Write only fork+exec IO. */
527 IO_AP
, /* Append fork+exec output to file. */
531 enum io_type type
; /* The requested type of pipe. */
532 const char *dir
; /* Directory from which to execute. */
533 pid_t pid
; /* Pipe for reading or writing. */
534 int pipe
; /* Pipe end for reading or writing. */
535 int error
; /* Error status. */
536 const char *argv
[SIZEOF_ARG
]; /* Shell command arguments. */
537 char *buf
; /* Read buffer. */
538 size_t bufalloc
; /* Allocated buffer size. */
539 size_t bufsize
; /* Buffer content size. */
540 char *bufpos
; /* Current buffer position. */
541 unsigned int eof
:1; /* Has end of file been reached. */
545 reset_io(struct io
*io
)
549 io
->buf
= io
->bufpos
= NULL
;
550 io
->bufalloc
= io
->bufsize
= 0;
556 init_io(struct io
*io
, const char *dir
, enum io_type type
)
564 init_io_rd(struct io
*io
, const char *argv
[], const char *dir
,
565 enum format_flags flags
)
567 init_io(io
, dir
, IO_RD
);
568 return format_argv(io
->argv
, argv
, flags
);
572 io_open(struct io
*io
, const char *fmt
, ...)
574 char name
[SIZEOF_STR
] = "";
578 init_io(io
, NULL
, IO_FD
);
581 fits
= vsnprintf(name
, sizeof(name
), fmt
, args
) < sizeof(name
);
585 io
->error
= ENAMETOOLONG
;
588 io
->pipe
= *name
? open(name
, O_RDONLY
) : STDIN_FILENO
;
591 return io
->pipe
!= -1;
595 kill_io(struct io
*io
)
597 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
601 done_io(struct io
*io
)
612 pid_t waiting
= waitpid(pid
, &status
, 0);
617 report("waitpid failed (%s)", strerror(errno
));
621 return waiting
== pid
&&
622 !WIFSIGNALED(status
) &&
624 !WEXITSTATUS(status
);
631 start_io(struct io
*io
)
633 int pipefds
[2] = { -1, -1 };
635 if (io
->type
== IO_FD
)
638 if ((io
->type
== IO_RD
|| io
->type
== IO_WR
) &&
641 else if (io
->type
== IO_AP
)
642 pipefds
[1] = io
->pipe
;
644 if ((io
->pid
= fork())) {
645 if (pipefds
[!(io
->type
== IO_WR
)] != -1)
646 close(pipefds
[!(io
->type
== IO_WR
)]);
648 io
->pipe
= pipefds
[!!(io
->type
== IO_WR
)];
653 if (io
->type
!= IO_FG
) {
654 int devnull
= open("/dev/null", O_RDWR
);
655 int readfd
= io
->type
== IO_WR
? pipefds
[0] : devnull
;
656 int writefd
= (io
->type
== IO_RD
|| io
->type
== IO_AP
)
657 ? pipefds
[1] : devnull
;
659 dup2(readfd
, STDIN_FILENO
);
660 dup2(writefd
, STDOUT_FILENO
);
661 dup2(devnull
, STDERR_FILENO
);
664 if (pipefds
[0] != -1)
666 if (pipefds
[1] != -1)
670 if (io
->dir
&& *io
->dir
&& chdir(io
->dir
) == -1)
671 die("Failed to change directory: %s", strerror(errno
));
673 execvp(io
->argv
[0], (char *const*) io
->argv
);
674 die("Failed to execute program: %s", strerror(errno
));
677 if (pipefds
[!!(io
->type
== IO_WR
)] != -1)
678 close(pipefds
[!!(io
->type
== IO_WR
)]);
683 run_io(struct io
*io
, const char **argv
, const char *dir
, enum io_type type
)
685 init_io(io
, dir
, type
);
686 if (!format_argv(io
->argv
, argv
, FORMAT_NONE
))
692 run_io_do(struct io
*io
)
694 return start_io(io
) && done_io(io
);
698 run_io_bg(const char **argv
)
702 init_io(&io
, NULL
, IO_BG
);
703 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
705 return run_io_do(&io
);
709 run_io_fg(const char **argv
, const char *dir
)
713 init_io(&io
, dir
, IO_FG
);
714 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
716 return run_io_do(&io
);
720 run_io_append(const char **argv
, enum format_flags flags
, int fd
)
724 init_io(&io
, NULL
, IO_AP
);
726 if (format_argv(io
.argv
, argv
, flags
))
727 return run_io_do(&io
);
733 run_io_rd(struct io
*io
, const char **argv
, const char *dir
, enum format_flags flags
)
735 return init_io_rd(io
, argv
, dir
, flags
) && start_io(io
);
739 io_eof(struct io
*io
)
745 io_error(struct io
*io
)
751 io_strerror(struct io
*io
)
753 return strerror(io
->error
);
757 io_can_read(struct io
*io
)
759 struct timeval tv
= { 0, 500 };
763 FD_SET(io
->pipe
, &fds
);
765 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
769 io_read(struct io
*io
, void *buf
, size_t bufsize
)
772 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
774 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
776 else if (readsize
== -1)
778 else if (readsize
== 0)
784 DEFINE_ALLOCATOR(realloc_io_buf
, char, BUFSIZ
)
787 io_get(struct io
*io
, int c
, bool can_read
)
793 if (io
->bufsize
> 0) {
794 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
796 char *line
= io
->bufpos
;
799 io
->bufpos
= eol
+ 1;
800 io
->bufsize
-= io
->bufpos
- line
;
807 io
->bufpos
[io
->bufsize
] = 0;
817 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
818 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
820 if (io
->bufalloc
== io
->bufsize
) {
821 if (!realloc_io_buf(&io
->buf
, io
->bufalloc
, BUFSIZ
))
823 io
->bufalloc
+= BUFSIZ
;
826 io
->bufpos
= io
->buf
;
827 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
830 io
->bufsize
+= readsize
;
835 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
839 while (!io_error(io
) && written
< bufsize
) {
842 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
843 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
851 return written
== bufsize
;
855 io_read_buf(struct io
*io
, char buf
[], size_t bufsize
)
857 char *result
= io_get(io
, '\n', TRUE
);
860 result
= chomp_string(result
);
861 string_ncopy_do(buf
, bufsize
, result
, strlen(result
));
864 return done_io(io
) && result
;
868 run_io_buf(const char **argv
, char buf
[], size_t bufsize
)
872 return run_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
873 && io_read_buf(&io
, buf
, bufsize
);
877 io_load(struct io
*io
, const char *separators
,
878 int (*read_property
)(char *, size_t, char *, size_t))
886 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
891 name
= chomp_string(name
);
892 namelen
= strcspn(name
, separators
);
896 value
= chomp_string(name
+ namelen
+ 1);
897 valuelen
= strlen(value
);
904 state
= read_property(name
, namelen
, value
, valuelen
);
907 if (state
!= ERR
&& io_error(io
))
915 run_io_load(const char **argv
, const char *separators
,
916 int (*read_property
)(char *, size_t, char *, size_t))
920 return init_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
921 ? io_load(&io
, separators
, read_property
) : ERR
;
930 /* XXX: Keep the view request first and in sync with views[]. */ \
931 REQ_GROUP("View switching") \
932 REQ_(VIEW_MAIN, "Show main view"), \
933 REQ_(VIEW_DIFF, "Show diff view"), \
934 REQ_(VIEW_LOG, "Show log view"), \
935 REQ_(VIEW_TREE, "Show tree view"), \
936 REQ_(VIEW_BLOB, "Show blob view"), \
937 REQ_(VIEW_BLAME, "Show blame view"), \
938 REQ_(VIEW_BRANCH, "Show branch view"), \
939 REQ_(VIEW_HELP, "Show help page"), \
940 REQ_(VIEW_PAGER, "Show pager view"), \
941 REQ_(VIEW_STATUS, "Show status view"), \
942 REQ_(VIEW_STAGE, "Show stage view"), \
944 REQ_GROUP("View manipulation") \
945 REQ_(ENTER, "Enter current line and scroll"), \
946 REQ_(NEXT, "Move to next"), \
947 REQ_(PREVIOUS, "Move to previous"), \
948 REQ_(PARENT, "Move to parent"), \
949 REQ_(VIEW_NEXT, "Move focus to next view"), \
950 REQ_(REFRESH, "Reload and refresh"), \
951 REQ_(MAXIMIZE, "Maximize the current view"), \
952 REQ_(VIEW_CLOSE, "Close the current view"), \
953 REQ_(QUIT, "Close all views and quit"), \
955 REQ_GROUP("View specific requests") \
956 REQ_(STATUS_UPDATE, "Update file status"), \
957 REQ_(STATUS_REVERT, "Revert file changes"), \
958 REQ_(STATUS_MERGE, "Merge file using external tool"), \
959 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
961 REQ_GROUP("Cursor navigation") \
962 REQ_(MOVE_UP, "Move cursor one line up"), \
963 REQ_(MOVE_DOWN, "Move cursor one line down"), \
964 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
965 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
966 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
967 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
969 REQ_GROUP("Scrolling") \
970 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
971 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
972 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
973 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
974 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
975 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
977 REQ_GROUP("Searching") \
978 REQ_(SEARCH, "Search the view"), \
979 REQ_(SEARCH_BACK, "Search backwards in the view"), \
980 REQ_(FIND_NEXT, "Find next search match"), \
981 REQ_(FIND_PREV, "Find previous search match"), \
983 REQ_GROUP("Option manipulation") \
984 REQ_(OPTIONS, "Open option menu"), \
985 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
986 REQ_(TOGGLE_DATE, "Toggle date display"), \
987 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
988 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
989 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
990 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
991 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
992 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
995 REQ_(PROMPT, "Bring up the prompt"), \
996 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
997 REQ_(SHOW_VERSION, "Show version information"), \
998 REQ_(STOP_LOADING, "Stop all loading views"), \
999 REQ_(EDIT, "Open in editor"), \
1000 REQ_(NONE, "Do nothing")
1003 /* User action requests. */
1005 #define REQ_GROUP(help)
1006 #define REQ_(req, help) REQ_##req
1008 /* Offset all requests to avoid conflicts with ncurses getch values. */
1009 REQ_OFFSET
= KEY_MAX
+ 1,
1016 struct request_info
{
1017 enum request request
;
1023 static const struct request_info req_info
[] = {
1024 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1025 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1032 get_request(const char *name
)
1034 int namelen
= strlen(name
);
1037 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
1038 if (enum_equals(req_info
[i
], name
, namelen
))
1039 return req_info
[i
].request
;
1049 /* Option and state variables. */
1050 static enum date opt_date
= DATE_DEFAULT
;
1051 static enum author opt_author
= AUTHOR_DEFAULT
;
1052 static bool opt_line_number
= FALSE
;
1053 static bool opt_line_graphics
= TRUE
;
1054 static bool opt_rev_graph
= FALSE
;
1055 static bool opt_show_refs
= TRUE
;
1056 static int opt_num_interval
= 5;
1057 static double opt_hscroll
= 0.50;
1058 static double opt_scale_split_view
= 2.0 / 3.0;
1059 static int opt_tab_size
= 8;
1060 static int opt_author_cols
= AUTHOR_COLS
;
1061 static char opt_path
[SIZEOF_STR
] = "";
1062 static char opt_file
[SIZEOF_STR
] = "";
1063 static char opt_ref
[SIZEOF_REF
] = "";
1064 static char opt_head
[SIZEOF_REF
] = "";
1065 static char opt_head_rev
[SIZEOF_REV
] = "";
1066 static char opt_remote
[SIZEOF_REF
] = "";
1067 static char opt_encoding
[20] = "UTF-8";
1068 static char opt_codeset
[20] = "UTF-8";
1069 static iconv_t opt_iconv_in
= ICONV_NONE
;
1070 static iconv_t opt_iconv_out
= ICONV_NONE
;
1071 static char opt_search
[SIZEOF_STR
] = "";
1072 static char opt_cdup
[SIZEOF_STR
] = "";
1073 static char opt_prefix
[SIZEOF_STR
] = "";
1074 static char opt_git_dir
[SIZEOF_STR
] = "";
1075 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
1076 static char opt_editor
[SIZEOF_STR
] = "";
1077 static FILE *opt_tty
= NULL
;
1079 #define is_initial_commit() (!*opt_head_rev)
1080 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1081 #define mkdate(time) string_date(time, opt_date)
1085 * Line-oriented content detection.
1089 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1090 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1091 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1092 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1093 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1094 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1095 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1096 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1097 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1098 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1099 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1100 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1101 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1102 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1103 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1104 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1105 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1106 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1107 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1108 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1109 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1110 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1111 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1112 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1113 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1114 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1115 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1116 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1117 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1118 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1119 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1120 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1121 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1122 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1123 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1124 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1125 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1126 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1127 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1128 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1129 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1130 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1131 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1132 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1133 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1134 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1135 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1136 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1137 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1138 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1139 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1140 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1141 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1142 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1143 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1144 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1145 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1148 #define LINE(type, line, fg, bg, attr) \
1156 const char *name
; /* Option name. */
1157 int namelen
; /* Size of option name. */
1158 const char *line
; /* The start of line to match. */
1159 int linelen
; /* Size of string to match. */
1160 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1163 static struct line_info line_info
[] = {
1164 #define LINE(type, line, fg, bg, attr) \
1165 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1170 static enum line_type
1171 get_line_type(const char *line
)
1173 int linelen
= strlen(line
);
1174 enum line_type type
;
1176 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1177 /* Case insensitive search matches Signed-off-by lines better. */
1178 if (linelen
>= line_info
[type
].linelen
&&
1179 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1182 return LINE_DEFAULT
;
1186 get_line_attr(enum line_type type
)
1188 assert(type
< ARRAY_SIZE(line_info
));
1189 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1192 static struct line_info
*
1193 get_line_info(const char *name
)
1195 size_t namelen
= strlen(name
);
1196 enum line_type type
;
1198 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1199 if (enum_equals(line_info
[type
], name
, namelen
))
1200 return &line_info
[type
];
1208 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1209 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1210 enum line_type type
;
1214 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1215 default_bg
= COLOR_BLACK
;
1216 default_fg
= COLOR_WHITE
;
1219 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1220 struct line_info
*info
= &line_info
[type
];
1221 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1222 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1224 init_pair(type
, fg
, bg
);
1229 enum line_type type
;
1232 unsigned int selected
:1;
1233 unsigned int dirty
:1;
1234 unsigned int cleareol
:1;
1235 unsigned int other
:16;
1237 void *data
; /* User data */
1247 enum request request
;
1250 static const struct keybinding default_keybindings
[] = {
1251 /* View switching */
1252 { 'm', REQ_VIEW_MAIN
},
1253 { 'd', REQ_VIEW_DIFF
},
1254 { 'l', REQ_VIEW_LOG
},
1255 { 't', REQ_VIEW_TREE
},
1256 { 'f', REQ_VIEW_BLOB
},
1257 { 'B', REQ_VIEW_BLAME
},
1258 { 'H', REQ_VIEW_BRANCH
},
1259 { 'p', REQ_VIEW_PAGER
},
1260 { 'h', REQ_VIEW_HELP
},
1261 { 'S', REQ_VIEW_STATUS
},
1262 { 'c', REQ_VIEW_STAGE
},
1264 /* View manipulation */
1265 { 'q', REQ_VIEW_CLOSE
},
1266 { KEY_TAB
, REQ_VIEW_NEXT
},
1267 { KEY_RETURN
, REQ_ENTER
},
1268 { KEY_UP
, REQ_PREVIOUS
},
1269 { KEY_DOWN
, REQ_NEXT
},
1270 { 'R', REQ_REFRESH
},
1271 { KEY_F(5), REQ_REFRESH
},
1272 { 'O', REQ_MAXIMIZE
},
1274 /* Cursor navigation */
1275 { 'k', REQ_MOVE_UP
},
1276 { 'j', REQ_MOVE_DOWN
},
1277 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1278 { KEY_END
, REQ_MOVE_LAST_LINE
},
1279 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1280 { ' ', REQ_MOVE_PAGE_DOWN
},
1281 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1282 { 'b', REQ_MOVE_PAGE_UP
},
1283 { '-', REQ_MOVE_PAGE_UP
},
1286 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1287 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1288 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1289 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1290 { 'w', REQ_SCROLL_PAGE_UP
},
1291 { 's', REQ_SCROLL_PAGE_DOWN
},
1294 { '/', REQ_SEARCH
},
1295 { '?', REQ_SEARCH_BACK
},
1296 { 'n', REQ_FIND_NEXT
},
1297 { 'N', REQ_FIND_PREV
},
1301 { 'z', REQ_STOP_LOADING
},
1302 { 'v', REQ_SHOW_VERSION
},
1303 { 'r', REQ_SCREEN_REDRAW
},
1304 { 'o', REQ_OPTIONS
},
1305 { '.', REQ_TOGGLE_LINENO
},
1306 { 'D', REQ_TOGGLE_DATE
},
1307 { 'A', REQ_TOGGLE_AUTHOR
},
1308 { 'g', REQ_TOGGLE_REV_GRAPH
},
1309 { 'F', REQ_TOGGLE_REFS
},
1310 { 'I', REQ_TOGGLE_SORT_ORDER
},
1311 { 'i', REQ_TOGGLE_SORT_FIELD
},
1312 { ':', REQ_PROMPT
},
1313 { 'u', REQ_STATUS_UPDATE
},
1314 { '!', REQ_STATUS_REVERT
},
1315 { 'M', REQ_STATUS_MERGE
},
1316 { '@', REQ_STAGE_NEXT
},
1317 { ',', REQ_PARENT
},
1321 #define KEYMAP_INFO \
1336 #define KEYMAP_(name) KEYMAP_##name
1341 static const struct enum_map keymap_table
[] = {
1342 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1347 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1349 struct keybinding_table
{
1350 struct keybinding
*data
;
1354 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1357 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1359 struct keybinding_table
*table
= &keybindings
[keymap
];
1361 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1363 die("Failed to allocate keybinding");
1364 table
->data
[table
->size
].alias
= key
;
1365 table
->data
[table
->size
++].request
= request
;
1368 /* Looks for a key binding first in the given map, then in the generic map, and
1369 * lastly in the default keybindings. */
1371 get_keybinding(enum keymap keymap
, int key
)
1375 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1376 if (keybindings
[keymap
].data
[i
].alias
== key
)
1377 return keybindings
[keymap
].data
[i
].request
;
1379 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1380 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1381 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1383 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1384 if (default_keybindings
[i
].alias
== key
)
1385 return default_keybindings
[i
].request
;
1387 return (enum request
) key
;
1396 static const struct key key_table
[] = {
1397 { "Enter", KEY_RETURN
},
1399 { "Backspace", KEY_BACKSPACE
},
1401 { "Escape", KEY_ESC
},
1402 { "Left", KEY_LEFT
},
1403 { "Right", KEY_RIGHT
},
1405 { "Down", KEY_DOWN
},
1406 { "Insert", KEY_IC
},
1407 { "Delete", KEY_DC
},
1409 { "Home", KEY_HOME
},
1411 { "PageUp", KEY_PPAGE
},
1412 { "PageDown", KEY_NPAGE
},
1422 { "F10", KEY_F(10) },
1423 { "F11", KEY_F(11) },
1424 { "F12", KEY_F(12) },
1428 get_key_value(const char *name
)
1432 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1433 if (!strcasecmp(key_table
[i
].name
, name
))
1434 return key_table
[i
].value
;
1436 if (strlen(name
) == 1 && isprint(*name
))
1443 get_key_name(int key_value
)
1445 static char key_char
[] = "'X'";
1446 const char *seq
= NULL
;
1449 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1450 if (key_table
[key
].value
== key_value
)
1451 seq
= key_table
[key
].name
;
1455 isprint(key_value
)) {
1456 key_char
[1] = (char) key_value
;
1460 return seq
? seq
: "(no key)";
1464 append_key(char *buf
, size_t *pos
, const struct keybinding
*keybinding
)
1466 const char *sep
= *pos
> 0 ? ", " : "";
1467 const char *keyname
= get_key_name(keybinding
->alias
);
1469 return string_nformat(buf
, BUFSIZ
, pos
, "%s%s", sep
, keyname
);
1473 append_keymap_request_keys(char *buf
, size_t *pos
, enum request request
,
1474 enum keymap keymap
, bool all
)
1478 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1479 if (keybindings
[keymap
].data
[i
].request
== request
) {
1480 if (!append_key(buf
, pos
, &keybindings
[keymap
].data
[i
]))
1490 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1493 get_keys(enum keymap keymap
, enum request request
, bool all
)
1495 static char buf
[BUFSIZ
];
1501 if (!append_keymap_request_keys(buf
, &pos
, request
, keymap
, all
))
1502 return "Too many keybindings!";
1503 if (pos
> 0 && !all
)
1506 if (keymap
!= KEYMAP_GENERIC
) {
1507 /* Only the generic keymap includes the default keybindings when
1508 * listing all keys. */
1512 if (!append_keymap_request_keys(buf
, &pos
, request
, KEYMAP_GENERIC
, all
))
1513 return "Too many keybindings!";
1518 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1519 if (default_keybindings
[i
].request
== request
) {
1520 if (!append_key(buf
, &pos
, &default_keybindings
[i
]))
1521 return "Too many keybindings!";
1530 struct run_request
{
1533 const char *argv
[SIZEOF_ARG
];
1536 static struct run_request
*run_request
;
1537 static size_t run_requests
;
1539 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1542 add_run_request(enum keymap keymap
, int key
, int argc
, const char **argv
)
1544 struct run_request
*req
;
1546 if (argc
>= ARRAY_SIZE(req
->argv
) - 1)
1549 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1552 req
= &run_request
[run_requests
];
1553 req
->keymap
= keymap
;
1555 req
->argv
[0] = NULL
;
1557 if (!format_argv(req
->argv
, argv
, FORMAT_NONE
))
1560 return REQ_NONE
+ ++run_requests
;
1563 static struct run_request
*
1564 get_run_request(enum request request
)
1566 if (request
<= REQ_NONE
)
1568 return &run_request
[request
- REQ_NONE
- 1];
1572 add_builtin_run_requests(void)
1574 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1575 const char *commit
[] = { "git", "commit", NULL
};
1576 const char *gc
[] = { "git", "gc", NULL
};
1583 { KEYMAP_MAIN
, 'C', ARRAY_SIZE(cherry_pick
) - 1, cherry_pick
},
1584 { KEYMAP_STATUS
, 'C', ARRAY_SIZE(commit
) - 1, commit
},
1585 { KEYMAP_GENERIC
, 'G', ARRAY_SIZE(gc
) - 1, gc
},
1589 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1592 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argc
, reqs
[i
].argv
);
1593 if (req
!= REQ_NONE
)
1594 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1599 * User config file handling.
1602 static int config_lineno
;
1603 static bool config_errors
;
1604 static const char *config_msg
;
1606 static const struct enum_map color_map
[] = {
1607 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1619 static const struct enum_map attr_map
[] = {
1620 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1627 ATTR_MAP(UNDERLINE
),
1630 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1632 static int parse_step(double *opt
, const char *arg
)
1635 if (!strchr(arg
, '%'))
1638 /* "Shift down" so 100% and 1 does not conflict. */
1639 *opt
= (*opt
- 1) / 100;
1642 config_msg
= "Step value larger than 100%";
1647 config_msg
= "Invalid step value";
1654 parse_int(int *opt
, const char *arg
, int min
, int max
)
1656 int value
= atoi(arg
);
1658 if (min
<= value
&& value
<= max
) {
1663 config_msg
= "Integer value out of bound";
1668 set_color(int *color
, const char *name
)
1670 if (map_enum(color
, color_map
, name
))
1672 if (!prefixcmp(name
, "color"))
1673 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1677 /* Wants: object fgcolor bgcolor [attribute] */
1679 option_color_command(int argc
, const char *argv
[])
1681 struct line_info
*info
;
1684 config_msg
= "Wrong number of arguments given to color command";
1688 info
= get_line_info(argv
[0]);
1690 static const struct enum_map obsolete
[] = {
1691 ENUM_MAP("main-delim", LINE_DELIMITER
),
1692 ENUM_MAP("main-date", LINE_DATE
),
1693 ENUM_MAP("main-author", LINE_AUTHOR
),
1697 if (!map_enum(&index
, obsolete
, argv
[0])) {
1698 config_msg
= "Unknown color name";
1701 info
= &line_info
[index
];
1704 if (!set_color(&info
->fg
, argv
[1]) ||
1705 !set_color(&info
->bg
, argv
[2])) {
1706 config_msg
= "Unknown color";
1711 while (argc
-- > 3) {
1714 if (!set_attribute(&attr
, argv
[argc
])) {
1715 config_msg
= "Unknown attribute";
1724 static int parse_bool(bool *opt
, const char *arg
)
1726 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1731 static int parse_enum_do(unsigned int *opt
, const char *arg
,
1732 const struct enum_map
*map
, size_t map_size
)
1736 assert(map_size
> 1);
1738 if (map_enum_do(map
, map_size
, (int *) opt
, arg
))
1741 if (parse_bool(&is_true
, arg
) != OK
)
1744 *opt
= is_true
? map
[1].value
: map
[0].value
;
1748 #define parse_enum(opt, arg, map) \
1749 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1752 parse_string(char *opt
, const char *arg
, size_t optsize
)
1754 int arglen
= strlen(arg
);
1759 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1760 config_msg
= "Unmatched quotation";
1763 arg
+= 1; arglen
-= 2;
1765 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1770 /* Wants: name = value */
1772 option_set_command(int argc
, const char *argv
[])
1775 config_msg
= "Wrong number of arguments given to set command";
1779 if (strcmp(argv
[1], "=")) {
1780 config_msg
= "No value assigned";
1784 if (!strcmp(argv
[0], "show-author"))
1785 return parse_enum(&opt_author
, argv
[2], author_map
);
1787 if (!strcmp(argv
[0], "show-date"))
1788 return parse_enum(&opt_date
, argv
[2], date_map
);
1790 if (!strcmp(argv
[0], "show-rev-graph"))
1791 return parse_bool(&opt_rev_graph
, argv
[2]);
1793 if (!strcmp(argv
[0], "show-refs"))
1794 return parse_bool(&opt_show_refs
, argv
[2]);
1796 if (!strcmp(argv
[0], "show-line-numbers"))
1797 return parse_bool(&opt_line_number
, argv
[2]);
1799 if (!strcmp(argv
[0], "line-graphics"))
1800 return parse_bool(&opt_line_graphics
, argv
[2]);
1802 if (!strcmp(argv
[0], "line-number-interval"))
1803 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
1805 if (!strcmp(argv
[0], "author-width"))
1806 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
1808 if (!strcmp(argv
[0], "horizontal-scroll"))
1809 return parse_step(&opt_hscroll
, argv
[2]);
1811 if (!strcmp(argv
[0], "split-view-height"))
1812 return parse_step(&opt_scale_split_view
, argv
[2]);
1814 if (!strcmp(argv
[0], "tab-size"))
1815 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
1817 if (!strcmp(argv
[0], "commit-encoding"))
1818 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
1820 config_msg
= "Unknown variable name";
1824 /* Wants: mode request key */
1826 option_bind_command(int argc
, const char *argv
[])
1828 enum request request
;
1833 config_msg
= "Wrong number of arguments given to bind command";
1837 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
1838 config_msg
= "Unknown key map";
1842 key
= get_key_value(argv
[1]);
1844 config_msg
= "Unknown key";
1848 request
= get_request(argv
[2]);
1849 if (request
== REQ_NONE
) {
1850 static const struct enum_map obsolete
[] = {
1851 ENUM_MAP("cherry-pick", REQ_NONE
),
1852 ENUM_MAP("screen-resize", REQ_NONE
),
1853 ENUM_MAP("tree-parent", REQ_PARENT
),
1857 if (map_enum(&alias
, obsolete
, argv
[2])) {
1858 if (alias
!= REQ_NONE
)
1859 add_keybinding(keymap
, alias
, key
);
1860 config_msg
= "Obsolete request name";
1864 if (request
== REQ_NONE
&& *argv
[2]++ == '!')
1865 request
= add_run_request(keymap
, key
, argc
- 2, argv
+ 2);
1866 if (request
== REQ_NONE
) {
1867 config_msg
= "Unknown request name";
1871 add_keybinding(keymap
, request
, key
);
1877 set_option(const char *opt
, char *value
)
1879 const char *argv
[SIZEOF_ARG
];
1882 if (!argv_from_string(argv
, &argc
, value
)) {
1883 config_msg
= "Too many option arguments";
1887 if (!strcmp(opt
, "color"))
1888 return option_color_command(argc
, argv
);
1890 if (!strcmp(opt
, "set"))
1891 return option_set_command(argc
, argv
);
1893 if (!strcmp(opt
, "bind"))
1894 return option_bind_command(argc
, argv
);
1896 config_msg
= "Unknown option command";
1901 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
1906 config_msg
= "Internal error";
1908 /* Check for comment markers, since read_properties() will
1909 * only ensure opt and value are split at first " \t". */
1910 optlen
= strcspn(opt
, "#");
1914 if (opt
[optlen
] != 0) {
1915 config_msg
= "No option value";
1919 /* Look for comment endings in the value. */
1920 size_t len
= strcspn(value
, "#");
1922 if (len
< valuelen
) {
1924 value
[valuelen
] = 0;
1927 status
= set_option(opt
, value
);
1930 if (status
== ERR
) {
1931 warn("Error on line %d, near '%.*s': %s",
1932 config_lineno
, (int) optlen
, opt
, config_msg
);
1933 config_errors
= TRUE
;
1936 /* Always keep going if errors are encountered. */
1941 load_option_file(const char *path
)
1945 /* It's OK that the file doesn't exist. */
1946 if (!io_open(&io
, "%s", path
))
1950 config_errors
= FALSE
;
1952 if (io_load(&io
, " \t", read_option
) == ERR
||
1953 config_errors
== TRUE
)
1954 warn("Errors while loading %s.", path
);
1960 const char *home
= getenv("HOME");
1961 const char *tigrc_user
= getenv("TIGRC_USER");
1962 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
1963 char buf
[SIZEOF_STR
];
1965 add_builtin_run_requests();
1968 tigrc_system
= SYSCONFDIR
"/tigrc";
1969 load_option_file(tigrc_system
);
1972 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
1976 load_option_file(tigrc_user
);
1989 /* The display array of active views and the index of the current view. */
1990 static struct view
*display
[2];
1991 static unsigned int current_view
;
1993 #define foreach_displayed_view(view, i) \
1994 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1996 #define displayed_views() (display[1] != NULL ? 2 : 1)
1998 /* Current head and commit ID */
1999 static char ref_blob
[SIZEOF_REF
] = "";
2000 static char ref_commit
[SIZEOF_REF
] = "HEAD";
2001 static char ref_head
[SIZEOF_REF
] = "HEAD";
2004 const char *name
; /* View name */
2005 const char *cmd_env
; /* Command line set via environment */
2006 const char *id
; /* Points to either of ref_{head,commit,blob} */
2008 struct view_ops
*ops
; /* View operations */
2010 enum keymap keymap
; /* What keymap does this view have */
2011 bool git_dir
; /* Whether the view requires a git directory. */
2013 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
2014 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
2016 int height
, width
; /* The width and height of the main window */
2017 WINDOW
*win
; /* The main window */
2018 WINDOW
*title
; /* The title window living below the main window */
2021 unsigned long offset
; /* Offset of the window top */
2022 unsigned long yoffset
; /* Offset from the window side. */
2023 unsigned long lineno
; /* Current line number */
2024 unsigned long p_offset
; /* Previous offset of the window top */
2025 unsigned long p_yoffset
;/* Previous offset from the window side */
2026 unsigned long p_lineno
; /* Previous current line number */
2027 bool p_restore
; /* Should the previous position be restored. */
2030 char grep
[SIZEOF_STR
]; /* Search string */
2031 regex_t
*regex
; /* Pre-compiled regexp */
2033 /* If non-NULL, points to the view that opened this view. If this view
2034 * is closed tig will switch back to the parent view. */
2035 struct view
*parent
;
2038 size_t lines
; /* Total number of lines */
2039 struct line
*line
; /* Line index */
2040 unsigned int digits
; /* Number of digits in the lines member. */
2043 struct line
*curline
; /* Line currently being drawn. */
2044 enum line_type curtype
; /* Attribute currently used for drawing. */
2045 unsigned long col
; /* Column when drawing. */
2046 bool has_scrolled
; /* View was scrolled. */
2056 /* What type of content being displayed. Used in the title bar. */
2058 /* Default command arguments. */
2060 /* Open and reads in all view content. */
2061 bool (*open
)(struct view
*view
);
2062 /* Read one line; updates view->line. */
2063 bool (*read
)(struct view
*view
, char *data
);
2064 /* Draw one line; @lineno must be < view->height. */
2065 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
2066 /* Depending on view handle a special requests. */
2067 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
2068 /* Search for regexp in a line. */
2069 bool (*grep
)(struct view
*view
, struct line
*line
);
2071 void (*select
)(struct view
*view
, struct line
*line
);
2072 /* Prepare view for loading */
2073 bool (*prepare
)(struct view
*view
);
2076 static struct view_ops blame_ops
;
2077 static struct view_ops blob_ops
;
2078 static struct view_ops diff_ops
;
2079 static struct view_ops help_ops
;
2080 static struct view_ops log_ops
;
2081 static struct view_ops main_ops
;
2082 static struct view_ops pager_ops
;
2083 static struct view_ops stage_ops
;
2084 static struct view_ops status_ops
;
2085 static struct view_ops tree_ops
;
2086 static struct view_ops branch_ops
;
2088 #define VIEW_STR(name, env, ref, ops, map, git) \
2089 { name, #env, ref, ops, map, git }
2091 #define VIEW_(id, name, ops, git, ref) \
2092 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2095 static struct view views
[] = {
2096 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
2097 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
2098 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
2099 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
2100 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
2101 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
2102 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
2103 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
2104 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
2105 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
2106 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
2109 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2110 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2112 #define foreach_view(view, i) \
2113 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2115 #define view_is_displayed(view) \
2116 (view == display[0] || view == display[1])
2123 static chtype line_graphics
[] = {
2124 /* LINE_GRAPHIC_VLINE: */ '|'
2128 set_view_attr(struct view
*view
, enum line_type type
)
2130 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
2131 wattrset(view
->win
, get_line_attr(type
));
2132 wchgat(view
->win
, -1, 0, type
, NULL
);
2133 view
->curtype
= type
;
2138 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
2139 int max_len
, bool use_tilde
)
2141 static char out_buffer
[BUFSIZ
* 2];
2144 int trimmed
= FALSE
;
2145 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2150 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
);
2152 set_view_attr(view
, type
);
2154 if (opt_iconv_out
!= ICONV_NONE
) {
2155 ICONV_CONST
char *inbuf
= (ICONV_CONST
char *) string
;
2156 size_t inlen
= len
+ 1;
2158 char *outbuf
= out_buffer
;
2159 size_t outlen
= sizeof(out_buffer
);
2163 ret
= iconv(opt_iconv_out
, &inbuf
, &inlen
, &outbuf
, &outlen
);
2164 if (ret
!= (size_t) -1) {
2165 string
= out_buffer
;
2166 len
= sizeof(out_buffer
) - outlen
;
2170 waddnstr(view
->win
, string
, len
);
2172 if (trimmed
&& use_tilde
) {
2173 set_view_attr(view
, LINE_DELIMITER
);
2174 waddch(view
->win
, '~');
2182 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
2184 static char space
[] = " ";
2187 spaces
= MIN(max
, spaces
);
2189 while (spaces
> 0) {
2190 int len
= MIN(spaces
, sizeof(space
) - 1);
2192 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
2200 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
2202 view
->col
+= draw_chars(view
, type
, string
, view
->width
+ view
->yoffset
- view
->col
, trim
);
2203 return view
->width
+ view
->yoffset
<= view
->col
;
2207 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
2209 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2210 int max
= view
->width
+ view
->yoffset
- view
->col
;
2216 set_view_attr(view
, type
);
2217 /* Using waddch() instead of waddnstr() ensures that
2218 * they'll be rendered correctly for the cursor line. */
2219 for (i
= skip
; i
< size
; i
++)
2220 waddch(view
->win
, graphic
[i
]);
2223 if (size
< max
&& skip
<= size
)
2224 waddch(view
->win
, ' ');
2227 return view
->width
+ view
->yoffset
<= view
->col
;
2231 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2233 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2237 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2239 col
= draw_space(view
, type
, max
- 1, max
- 1);
2242 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2243 return view
->width
+ view
->yoffset
<= view
->col
;
2247 draw_date(struct view
*view
, time_t *time
)
2249 const char *date
= time
? mkdate(time
) : "";
2250 int cols
= opt_date
== DATE_SHORT
? DATE_SHORT_COLS
: DATE_COLS
;
2252 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2256 draw_author(struct view
*view
, const char *author
)
2258 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5;
2259 bool abbreviate
= opt_author
== AUTHOR_ABBREVIATED
|| !trim
;
2261 if (abbreviate
&& author
)
2262 author
= get_author_initials(author
, opt_author_cols
);
2264 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2268 draw_mode(struct view
*view
, mode_t mode
)
2274 else if (S_ISLNK(mode
))
2276 else if (S_ISGITLINK(mode
))
2278 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2280 else if (S_ISREG(mode
))
2285 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2289 draw_lineno(struct view
*view
, unsigned int lineno
)
2292 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2293 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2296 lineno
+= view
->offset
+ 1;
2297 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2298 static char fmt
[] = "%1ld";
2300 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2301 if (string_format(number
, fmt
, lineno
))
2305 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2307 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2308 return draw_graphic(view
, LINE_DEFAULT
, &line_graphics
[LINE_GRAPHIC_VLINE
], 1);
2312 draw_view_line(struct view
*view
, unsigned int lineno
)
2315 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2317 assert(view_is_displayed(view
));
2319 if (view
->offset
+ lineno
>= view
->lines
)
2322 line
= &view
->line
[view
->offset
+ lineno
];
2324 wmove(view
->win
, lineno
, 0);
2326 wclrtoeol(view
->win
);
2328 view
->curline
= line
;
2329 view
->curtype
= LINE_NONE
;
2330 line
->selected
= FALSE
;
2331 line
->dirty
= line
->cleareol
= 0;
2334 set_view_attr(view
, LINE_CURSOR
);
2335 line
->selected
= TRUE
;
2336 view
->ops
->select(view
, line
);
2339 return view
->ops
->draw(view
, line
, lineno
);
2343 redraw_view_dirty(struct view
*view
)
2348 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2349 if (view
->offset
+ lineno
>= view
->lines
)
2351 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2354 if (!draw_view_line(view
, lineno
))
2360 wnoutrefresh(view
->win
);
2364 redraw_view_from(struct view
*view
, int lineno
)
2366 assert(0 <= lineno
&& lineno
< view
->height
);
2368 for (; lineno
< view
->height
; lineno
++) {
2369 if (!draw_view_line(view
, lineno
))
2373 wnoutrefresh(view
->win
);
2377 redraw_view(struct view
*view
)
2380 redraw_view_from(view
, 0);
2385 update_view_title(struct view
*view
)
2387 char buf
[SIZEOF_STR
];
2388 char state
[SIZEOF_STR
];
2389 size_t bufpos
= 0, statelen
= 0;
2391 assert(view_is_displayed(view
));
2393 if (view
!= VIEW(REQ_VIEW_STATUS
) && view
->lines
) {
2394 unsigned int view_lines
= view
->offset
+ view
->height
;
2395 unsigned int lines
= view
->lines
2396 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2399 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2408 time_t secs
= time(NULL
) - view
->start_time
;
2410 /* Three git seconds are a long time ... */
2412 string_format_from(state
, &statelen
, " loading %lds", secs
);
2415 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2416 if (*view
->ref
&& bufpos
< view
->width
) {
2417 size_t refsize
= strlen(view
->ref
);
2418 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2420 if (minsize
< view
->width
)
2421 refsize
= view
->width
- minsize
+ 7;
2422 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2425 if (statelen
&& bufpos
< view
->width
) {
2426 string_format_from(buf
, &bufpos
, "%s", state
);
2429 if (view
== display
[current_view
])
2430 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2432 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2434 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2435 wclrtoeol(view
->title
);
2436 wnoutrefresh(view
->title
);
2440 apply_step(double step
, int value
)
2444 value
*= step
+ 0.01;
2445 return value
? value
: 1;
2449 resize_display(void)
2452 struct view
*base
= display
[0];
2453 struct view
*view
= display
[1] ? display
[1] : display
[0];
2455 /* Setup window dimensions */
2457 getmaxyx(stdscr
, base
->height
, base
->width
);
2459 /* Make room for the status window. */
2463 /* Horizontal split. */
2464 view
->width
= base
->width
;
2465 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2466 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2467 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2468 base
->height
-= view
->height
;
2470 /* Make room for the title bar. */
2474 /* Make room for the title bar. */
2479 foreach_displayed_view (view
, i
) {
2481 view
->win
= newwin(view
->height
, 0, offset
, 0);
2483 die("Failed to create %s view", view
->name
);
2485 scrollok(view
->win
, FALSE
);
2487 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2489 die("Failed to create title window");
2492 wresize(view
->win
, view
->height
, view
->width
);
2493 mvwin(view
->win
, offset
, 0);
2494 mvwin(view
->title
, offset
+ view
->height
, 0);
2497 offset
+= view
->height
+ 1;
2502 redraw_display(bool clear
)
2507 foreach_displayed_view (view
, i
) {
2511 update_view_title(view
);
2516 toggle_enum_option_do(unsigned int *opt
, const char *help
,
2517 const struct enum_map
*map
, size_t size
)
2519 *opt
= (*opt
+ 1) % size
;
2520 redraw_display(FALSE
);
2521 report("Displaying %s %s", enum_name(map
[*opt
]), help
);
2524 #define toggle_enum_option(opt, help, map) \
2525 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2527 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2528 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2531 toggle_view_option(bool *option
, const char *help
)
2534 redraw_display(FALSE
);
2535 report("%sabling %s", *option
? "En" : "Dis", help
);
2539 open_option_menu(void)
2541 const struct menu_item menu
[] = {
2542 { '.', "line numbers", &opt_line_number
},
2543 { 'D', "date display", &opt_date
},
2544 { 'A', "author display", &opt_author
},
2545 { 'g', "revision graph display", &opt_rev_graph
},
2546 { 'F', "reference display", &opt_show_refs
},
2551 if (prompt_menu("Toggle option", menu
, &selected
)) {
2552 if (menu
[selected
].data
== &opt_date
)
2554 else if (menu
[selected
].data
== &opt_author
)
2557 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2562 maximize_view(struct view
*view
)
2564 memset(display
, 0, sizeof(display
));
2566 display
[current_view
] = view
;
2568 redraw_display(FALSE
);
2578 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2580 if (lineno
>= view
->lines
)
2581 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2583 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2584 unsigned long half
= view
->height
/ 2;
2587 offset
= lineno
- half
;
2592 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2593 view
->offset
= offset
;
2594 view
->lineno
= lineno
;
2601 /* Scrolling backend */
2603 do_scroll_view(struct view
*view
, int lines
)
2605 bool redraw_current_line
= FALSE
;
2607 /* The rendering expects the new offset. */
2608 view
->offset
+= lines
;
2610 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2613 /* Move current line into the view. */
2614 if (view
->lineno
< view
->offset
) {
2615 view
->lineno
= view
->offset
;
2616 redraw_current_line
= TRUE
;
2617 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2618 view
->lineno
= view
->offset
+ view
->height
- 1;
2619 redraw_current_line
= TRUE
;
2622 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2624 /* Redraw the whole screen if scrolling is pointless. */
2625 if (view
->height
< ABS(lines
)) {
2629 int line
= lines
> 0 ? view
->height
- lines
: 0;
2630 int end
= line
+ ABS(lines
);
2632 scrollok(view
->win
, TRUE
);
2633 wscrl(view
->win
, lines
);
2634 scrollok(view
->win
, FALSE
);
2636 while (line
< end
&& draw_view_line(view
, line
))
2639 if (redraw_current_line
)
2640 draw_view_line(view
, view
->lineno
- view
->offset
);
2641 wnoutrefresh(view
->win
);
2644 view
->has_scrolled
= TRUE
;
2648 /* Scroll frontend */
2650 scroll_view(struct view
*view
, enum request request
)
2654 assert(view_is_displayed(view
));
2657 case REQ_SCROLL_LEFT
:
2658 if (view
->yoffset
== 0) {
2659 report("Cannot scroll beyond the first column");
2662 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2665 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2666 redraw_view_from(view
, 0);
2669 case REQ_SCROLL_RIGHT
:
2670 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2674 case REQ_SCROLL_PAGE_DOWN
:
2675 lines
= view
->height
;
2676 case REQ_SCROLL_LINE_DOWN
:
2677 if (view
->offset
+ lines
> view
->lines
)
2678 lines
= view
->lines
- view
->offset
;
2680 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2681 report("Cannot scroll beyond the last line");
2686 case REQ_SCROLL_PAGE_UP
:
2687 lines
= view
->height
;
2688 case REQ_SCROLL_LINE_UP
:
2689 if (lines
> view
->offset
)
2690 lines
= view
->offset
;
2693 report("Cannot scroll beyond the first line");
2701 die("request %d not handled in switch", request
);
2704 do_scroll_view(view
, lines
);
2709 move_view(struct view
*view
, enum request request
)
2711 int scroll_steps
= 0;
2715 case REQ_MOVE_FIRST_LINE
:
2716 steps
= -view
->lineno
;
2719 case REQ_MOVE_LAST_LINE
:
2720 steps
= view
->lines
- view
->lineno
- 1;
2723 case REQ_MOVE_PAGE_UP
:
2724 steps
= view
->height
> view
->lineno
2725 ? -view
->lineno
: -view
->height
;
2728 case REQ_MOVE_PAGE_DOWN
:
2729 steps
= view
->lineno
+ view
->height
>= view
->lines
2730 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2742 die("request %d not handled in switch", request
);
2745 if (steps
<= 0 && view
->lineno
== 0) {
2746 report("Cannot move beyond the first line");
2749 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2750 report("Cannot move beyond the last line");
2754 /* Move the current line */
2755 view
->lineno
+= steps
;
2756 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2758 /* Check whether the view needs to be scrolled */
2759 if (view
->lineno
< view
->offset
||
2760 view
->lineno
>= view
->offset
+ view
->height
) {
2761 scroll_steps
= steps
;
2762 if (steps
< 0 && -steps
> view
->offset
) {
2763 scroll_steps
= -view
->offset
;
2765 } else if (steps
> 0) {
2766 if (view
->lineno
== view
->lines
- 1 &&
2767 view
->lines
> view
->height
) {
2768 scroll_steps
= view
->lines
- view
->offset
- 1;
2769 if (scroll_steps
>= view
->height
)
2770 scroll_steps
-= view
->height
- 1;
2775 if (!view_is_displayed(view
)) {
2776 view
->offset
+= scroll_steps
;
2777 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2778 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2782 /* Repaint the old "current" line if we be scrolling */
2783 if (ABS(steps
) < view
->height
)
2784 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
2787 do_scroll_view(view
, scroll_steps
);
2791 /* Draw the current line */
2792 draw_view_line(view
, view
->lineno
- view
->offset
);
2794 wnoutrefresh(view
->win
);
2803 static void search_view(struct view
*view
, enum request request
);
2806 grep_text(struct view
*view
, const char *text
[])
2811 for (i
= 0; text
[i
]; i
++)
2813 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
2819 select_view_line(struct view
*view
, unsigned long lineno
)
2821 unsigned long old_lineno
= view
->lineno
;
2822 unsigned long old_offset
= view
->offset
;
2824 if (goto_view_line(view
, view
->offset
, lineno
)) {
2825 if (view_is_displayed(view
)) {
2826 if (old_offset
!= view
->offset
) {
2829 draw_view_line(view
, old_lineno
- view
->offset
);
2830 draw_view_line(view
, view
->lineno
- view
->offset
);
2831 wnoutrefresh(view
->win
);
2834 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2840 find_next(struct view
*view
, enum request request
)
2842 unsigned long lineno
= view
->lineno
;
2847 report("No previous search");
2849 search_view(view
, request
);
2859 case REQ_SEARCH_BACK
:
2868 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
2869 lineno
+= direction
;
2871 /* Note, lineno is unsigned long so will wrap around in which case it
2872 * will become bigger than view->lines. */
2873 for (; lineno
< view
->lines
; lineno
+= direction
) {
2874 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
2875 select_view_line(view
, lineno
);
2876 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
2881 report("No match found for '%s'", view
->grep
);
2885 search_view(struct view
*view
, enum request request
)
2890 regfree(view
->regex
);
2893 view
->regex
= calloc(1, sizeof(*view
->regex
));
2898 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
2899 if (regex_err
!= 0) {
2900 char buf
[SIZEOF_STR
] = "unknown error";
2902 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
2903 report("Search failed: %s", buf
);
2907 string_copy(view
->grep
, opt_search
);
2909 find_next(view
, request
);
2913 * Incremental updating
2917 reset_view(struct view
*view
)
2921 for (i
= 0; i
< view
->lines
; i
++)
2922 free(view
->line
[i
].data
);
2925 view
->p_offset
= view
->offset
;
2926 view
->p_yoffset
= view
->yoffset
;
2927 view
->p_lineno
= view
->lineno
;
2935 view
->update_secs
= 0;
2939 free_argv(const char *argv
[])
2943 for (argc
= 0; argv
[argc
]; argc
++)
2944 free((void *) argv
[argc
]);
2948 format_arg(const char *name
)
2954 const char *value_if_empty
;
2956 #define FORMAT_VAR(name, value, value_if_empty) \
2957 { name, STRING_SIZE(name), value, value_if_empty }
2958 FORMAT_VAR("%(directory)", opt_path
, ""),
2959 FORMAT_VAR("%(file)", opt_file
, ""),
2960 FORMAT_VAR("%(ref)", opt_ref
, "HEAD"),
2961 FORMAT_VAR("%(head)", ref_head
, ""),
2962 FORMAT_VAR("%(commit)", ref_commit
, ""),
2963 FORMAT_VAR("%(blob)", ref_blob
, ""),
2967 for (i
= 0; i
< ARRAY_SIZE(vars
); i
++)
2968 if (!strncmp(name
, vars
[i
].name
, vars
[i
].namelen
))
2969 return *vars
[i
].value
? vars
[i
].value
: vars
[i
].value_if_empty
;
2974 format_argv(const char *dst_argv
[], const char *src_argv
[], enum format_flags flags
)
2976 char buf
[SIZEOF_STR
];
2978 bool noreplace
= flags
== FORMAT_NONE
;
2980 free_argv(dst_argv
);
2982 for (argc
= 0; src_argv
[argc
]; argc
++) {
2983 const char *arg
= src_argv
[argc
];
2987 char *next
= strstr(arg
, "%(");
2988 int len
= next
- arg
;
2991 if (!next
|| noreplace
) {
2992 if (flags
== FORMAT_DASH
&& !strcmp(arg
, "--"))
2998 value
= format_arg(next
);
3001 report("Unknown replacement: `%s`", next
);
3006 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
3009 arg
= next
&& !noreplace
? strchr(next
, ')') + 1 : NULL
;
3012 dst_argv
[argc
] = strdup(buf
);
3013 if (!dst_argv
[argc
])
3017 dst_argv
[argc
] = NULL
;
3019 return src_argv
[argc
] == NULL
;
3023 restore_view_position(struct view
*view
)
3025 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
3028 /* Changing the view position cancels the restoring. */
3029 /* FIXME: Changing back to the first line is not detected. */
3030 if (view
->offset
!= 0 || view
->lineno
!= 0) {
3031 view
->p_restore
= FALSE
;
3035 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
3036 view_is_displayed(view
))
3039 view
->yoffset
= view
->p_yoffset
;
3040 view
->p_restore
= FALSE
;
3046 end_update(struct view
*view
, bool force
)
3050 while (!view
->ops
->read(view
, NULL
))
3053 set_nonblocking_input(FALSE
);
3055 kill_io(view
->pipe
);
3056 done_io(view
->pipe
);
3061 setup_update(struct view
*view
, const char *vid
)
3063 set_nonblocking_input(TRUE
);
3065 string_copy_rev(view
->vid
, vid
);
3066 view
->pipe
= &view
->io
;
3067 view
->start_time
= time(NULL
);
3071 prepare_update(struct view
*view
, const char *argv
[], const char *dir
,
3072 enum format_flags flags
)
3075 end_update(view
, TRUE
);
3076 return init_io_rd(&view
->io
, argv
, dir
, flags
);
3080 prepare_update_file(struct view
*view
, const char *name
)
3083 end_update(view
, TRUE
);
3084 return io_open(&view
->io
, "%s", name
);
3088 begin_update(struct view
*view
, bool refresh
)
3091 end_update(view
, TRUE
);
3094 if (view
->ops
->prepare
) {
3095 if (!view
->ops
->prepare(view
))
3097 } else if (!init_io_rd(&view
->io
, view
->ops
->argv
, NULL
, FORMAT_ALL
)) {
3101 /* Put the current ref_* value to the view title ref
3102 * member. This is needed by the blob view. Most other
3103 * views sets it automatically after loading because the
3104 * first line is a commit line. */
3105 string_copy_rev(view
->ref
, view
->id
);
3108 if (!start_io(&view
->io
))
3111 setup_update(view
, view
->id
);
3117 update_view(struct view
*view
)
3119 char out_buffer
[BUFSIZ
* 2];
3121 /* Clear the view and redraw everything since the tree sorting
3122 * might have rearranged things. */
3123 bool redraw
= view
->lines
== 0;
3124 bool can_read
= TRUE
;
3129 if (!io_can_read(view
->pipe
)) {
3130 if (view
->lines
== 0 && view_is_displayed(view
)) {
3131 time_t secs
= time(NULL
) - view
->start_time
;
3133 if (secs
> 1 && secs
> view
->update_secs
) {
3134 if (view
->update_secs
== 0)
3136 update_view_title(view
);
3137 view
->update_secs
= secs
;
3143 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
3144 if (opt_iconv_in
!= ICONV_NONE
) {
3145 ICONV_CONST
char *inbuf
= line
;
3146 size_t inlen
= strlen(line
) + 1;
3148 char *outbuf
= out_buffer
;
3149 size_t outlen
= sizeof(out_buffer
);
3153 ret
= iconv(opt_iconv_in
, &inbuf
, &inlen
, &outbuf
, &outlen
);
3154 if (ret
!= (size_t) -1)
3158 if (!view
->ops
->read(view
, line
)) {
3159 report("Allocation failure");
3160 end_update(view
, TRUE
);
3166 unsigned long lines
= view
->lines
;
3169 for (digits
= 0; lines
; digits
++)
3172 /* Keep the displayed view in sync with line number scaling. */
3173 if (digits
!= view
->digits
) {
3174 view
->digits
= digits
;
3175 if (opt_line_number
|| view
== VIEW(REQ_VIEW_BLAME
))
3180 if (io_error(view
->pipe
)) {
3181 report("Failed to read: %s", io_strerror(view
->pipe
));
3182 end_update(view
, TRUE
);
3184 } else if (io_eof(view
->pipe
)) {
3186 end_update(view
, FALSE
);
3189 if (restore_view_position(view
))
3192 if (!view_is_displayed(view
))
3196 redraw_view_from(view
, 0);
3198 redraw_view_dirty(view
);
3200 /* Update the title _after_ the redraw so that if the redraw picks up a
3201 * commit reference in view->ref it'll be available here. */
3202 update_view_title(view
);
3206 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
3208 static struct line
*
3209 add_line_data(struct view
*view
, void *data
, enum line_type type
)
3213 if (!realloc_lines(&view
->line
, view
->lines
, 1))
3216 line
= &view
->line
[view
->lines
++];
3217 memset(line
, 0, sizeof(*line
));
3225 static struct line
*
3226 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
3228 char *data
= text
? strdup(text
) : NULL
;
3230 return data
? add_line_data(view
, data
, type
) : NULL
;
3233 static struct line
*
3234 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3236 char buf
[SIZEOF_STR
];
3239 va_start(args
, fmt
);
3240 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3244 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3252 OPEN_DEFAULT
= 0, /* Use default view switching. */
3253 OPEN_SPLIT
= 1, /* Split current view. */
3254 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3255 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3256 OPEN_PREPARED
= 32, /* Open already prepared command. */
3260 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3262 bool split
= !!(flags
& OPEN_SPLIT
);
3263 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3264 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3265 struct view
*view
= VIEW(request
);
3266 int nviews
= displayed_views();
3267 struct view
*base_view
= display
[0];
3269 if (view
== prev
&& nviews
== 1 && !reload
) {
3270 report("Already in %s view", view
->name
);
3274 if (view
->git_dir
&& !opt_git_dir
[0]) {
3275 report("The %s view is disabled in pager view", view
->name
);
3282 } else if (!nomaximize
) {
3283 /* Maximize the current view. */
3284 memset(display
, 0, sizeof(display
));
3286 display
[current_view
] = view
;
3289 /* No parent signals that this is the first loaded view. */
3290 if (prev
&& view
!= prev
) {
3291 view
->parent
= prev
;
3294 /* Resize the view when switching between split- and full-screen,
3295 * or when switching between two different full-screen views. */
3296 if (nviews
!= displayed_views() ||
3297 (nviews
== 1 && base_view
!= display
[0]))
3300 if (view
->ops
->open
) {
3302 end_update(view
, TRUE
);
3303 if (!view
->ops
->open(view
)) {
3304 report("Failed to load %s view", view
->name
);
3307 restore_view_position(view
);
3309 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3310 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3311 report("Failed to load %s view", view
->name
);
3315 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3316 /* Take the title line into account. */
3317 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3319 /* Scroll the view that was split if the current line is
3320 * outside the new limited view. */
3321 do_scroll_view(prev
, lines
);
3324 if (prev
&& view
!= prev
&& split
&& view_is_displayed(prev
)) {
3325 /* "Blur" the previous view. */
3326 update_view_title(prev
);
3329 if (view
->pipe
&& view
->lines
== 0) {
3330 /* Clear the old view and let the incremental updating refill
3333 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3335 } else if (view_is_displayed(view
)) {
3342 open_external_viewer(const char *argv
[], const char *dir
)
3344 def_prog_mode(); /* save current tty modes */
3345 endwin(); /* restore original tty modes */
3346 run_io_fg(argv
, dir
);
3347 fprintf(stderr
, "Press Enter to continue");
3350 redraw_display(TRUE
);
3354 open_mergetool(const char *file
)
3356 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3358 open_external_viewer(mergetool_argv
, opt_cdup
);
3362 open_editor(const char *file
)
3364 const char *editor_argv
[] = { "vi", file
, NULL
};
3367 editor
= getenv("GIT_EDITOR");
3368 if (!editor
&& *opt_editor
)
3369 editor
= opt_editor
;
3371 editor
= getenv("VISUAL");
3373 editor
= getenv("EDITOR");
3377 editor_argv
[0] = editor
;
3378 open_external_viewer(editor_argv
, opt_cdup
);
3382 open_run_request(enum request request
)
3384 struct run_request
*req
= get_run_request(request
);
3385 const char *argv
[ARRAY_SIZE(req
->argv
)] = { NULL
};
3388 report("Unknown run request");
3392 if (format_argv(argv
, req
->argv
, FORMAT_ALL
))
3393 open_external_viewer(argv
, NULL
);
3398 * User request switch noodle
3402 view_driver(struct view
*view
, enum request request
)
3406 if (request
== REQ_NONE
)
3409 if (request
> REQ_NONE
) {
3410 open_run_request(request
);
3411 /* FIXME: When all views can refresh always do this. */
3412 if (view
== VIEW(REQ_VIEW_STATUS
) ||
3413 view
== VIEW(REQ_VIEW_MAIN
) ||
3414 view
== VIEW(REQ_VIEW_LOG
) ||
3415 view
== VIEW(REQ_VIEW_BRANCH
) ||
3416 view
== VIEW(REQ_VIEW_STAGE
))
3417 request
= REQ_REFRESH
;
3422 if (view
&& view
->lines
) {
3423 request
= view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
3424 if (request
== REQ_NONE
)
3431 case REQ_MOVE_PAGE_UP
:
3432 case REQ_MOVE_PAGE_DOWN
:
3433 case REQ_MOVE_FIRST_LINE
:
3434 case REQ_MOVE_LAST_LINE
:
3435 move_view(view
, request
);
3438 case REQ_SCROLL_LEFT
:
3439 case REQ_SCROLL_RIGHT
:
3440 case REQ_SCROLL_LINE_DOWN
:
3441 case REQ_SCROLL_LINE_UP
:
3442 case REQ_SCROLL_PAGE_DOWN
:
3443 case REQ_SCROLL_PAGE_UP
:
3444 scroll_view(view
, request
);
3447 case REQ_VIEW_BLAME
:
3449 report("No file chosen, press %s to open tree view",
3450 get_key(view
->keymap
, REQ_VIEW_TREE
));
3453 open_view(view
, request
, OPEN_DEFAULT
);
3458 report("No file chosen, press %s to open tree view",
3459 get_key(view
->keymap
, REQ_VIEW_TREE
));
3462 open_view(view
, request
, OPEN_DEFAULT
);
3465 case REQ_VIEW_PAGER
:
3466 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3467 report("No pager content, press %s to run command from prompt",
3468 get_key(view
->keymap
, REQ_PROMPT
));
3471 open_view(view
, request
, OPEN_DEFAULT
);
3474 case REQ_VIEW_STAGE
:
3475 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3476 report("No stage content, press %s to open the status view and choose file",
3477 get_key(view
->keymap
, REQ_VIEW_STATUS
));
3480 open_view(view
, request
, OPEN_DEFAULT
);
3483 case REQ_VIEW_STATUS
:
3484 if (opt_is_inside_work_tree
== FALSE
) {
3485 report("The status view requires a working tree");
3488 open_view(view
, request
, OPEN_DEFAULT
);
3496 case REQ_VIEW_BRANCH
:
3497 open_view(view
, request
, OPEN_DEFAULT
);
3502 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3504 if ((view
== VIEW(REQ_VIEW_DIFF
) &&
3505 view
->parent
== VIEW(REQ_VIEW_MAIN
)) ||
3506 (view
== VIEW(REQ_VIEW_DIFF
) &&
3507 view
->parent
== VIEW(REQ_VIEW_BLAME
)) ||
3508 (view
== VIEW(REQ_VIEW_STAGE
) &&
3509 view
->parent
== VIEW(REQ_VIEW_STATUS
)) ||
3510 (view
== VIEW(REQ_VIEW_BLOB
) &&
3511 view
->parent
== VIEW(REQ_VIEW_TREE
)) ||
3512 (view
== VIEW(REQ_VIEW_MAIN
) &&
3513 view
->parent
== VIEW(REQ_VIEW_BRANCH
))) {
3516 view
= view
->parent
;
3517 line
= view
->lineno
;
3518 move_view(view
, request
);
3519 if (view_is_displayed(view
))
3520 update_view_title(view
);
3521 if (line
!= view
->lineno
)
3522 view
->ops
->request(view
, REQ_ENTER
,
3523 &view
->line
[view
->lineno
]);
3526 move_view(view
, request
);
3532 int nviews
= displayed_views();
3533 int next_view
= (current_view
+ 1) % nviews
;
3535 if (next_view
== current_view
) {
3536 report("Only one view is displayed");
3540 current_view
= next_view
;
3541 /* Blur out the title of the previous view. */
3542 update_view_title(view
);
3547 report("Refreshing is not yet supported for the %s view", view
->name
);
3551 if (displayed_views() == 2)
3552 maximize_view(view
);
3559 case REQ_TOGGLE_LINENO
:
3560 toggle_view_option(&opt_line_number
, "line numbers");
3563 case REQ_TOGGLE_DATE
:
3567 case REQ_TOGGLE_AUTHOR
:
3571 case REQ_TOGGLE_REV_GRAPH
:
3572 toggle_view_option(&opt_rev_graph
, "revision graph display");
3575 case REQ_TOGGLE_REFS
:
3576 toggle_view_option(&opt_show_refs
, "reference display");
3579 case REQ_TOGGLE_SORT_FIELD
:
3580 case REQ_TOGGLE_SORT_ORDER
:
3581 report("Sorting is not yet supported for the %s view", view
->name
);
3585 case REQ_SEARCH_BACK
:
3586 search_view(view
, request
);
3591 find_next(view
, request
);
3594 case REQ_STOP_LOADING
:
3595 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
3598 report("Stopped loading the %s view", view
->name
),
3599 end_update(view
, TRUE
);
3603 case REQ_SHOW_VERSION
:
3604 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3607 case REQ_SCREEN_REDRAW
:
3608 redraw_display(TRUE
);
3612 report("Nothing to edit");
3616 report("Nothing to enter");
3619 case REQ_VIEW_CLOSE
:
3620 /* XXX: Mark closed views by letting view->parent point to the
3621 * view itself. Parents to closed view should never be
3624 view
->parent
->parent
!= view
->parent
) {
3625 maximize_view(view
->parent
);
3626 view
->parent
= view
;
3634 report("Unknown key, press %s for help",
3635 get_key(view
->keymap
, REQ_VIEW_HELP
));
3644 * View backend utilities
3654 const enum sort_field
*fields
;
3655 size_t size
, current
;
3659 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3660 #define get_sort_field(state) ((state).fields[(state).current])
3661 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3664 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3665 int (*compare
)(const void *, const void *))
3668 case REQ_TOGGLE_SORT_FIELD
:
3669 state
->current
= (state
->current
+ 1) % state
->size
;
3672 case REQ_TOGGLE_SORT_ORDER
:
3673 state
->reverse
= !state
->reverse
;
3676 die("Not a sort request");
3679 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3683 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3685 /* Small author cache to reduce memory consumption. It uses binary
3686 * search to lookup or find place to position new entries. No entries
3687 * are ever freed. */
3689 get_author(const char *name
)
3691 static const char **authors
;
3692 static size_t authors_size
;
3693 int from
= 0, to
= authors_size
- 1;
3695 while (from
<= to
) {
3696 size_t pos
= (to
+ from
) / 2;
3697 int cmp
= strcmp(name
, authors
[pos
]);
3700 return authors
[pos
];
3708 if (!realloc_authors(&authors
, authors_size
, 1))
3710 name
= strdup(name
);
3714 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3715 authors
[from
] = name
;
3722 parse_timezone(time_t *time
, const char *zone
)
3726 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3727 tz
+= ('0' - zone
[2]) * 60 * 60;
3728 tz
+= ('0' - zone
[3]) * 60;
3729 tz
+= ('0' - zone
[4]);
3737 /* Parse author lines where the name may be empty:
3738 * author <email@address.tld> 1138474660 +0100
3741 parse_author_line(char *ident
, const char **author
, time_t *time
)
3743 char *nameend
= strchr(ident
, '<');
3744 char *emailend
= strchr(ident
, '>');
3746 if (nameend
&& emailend
)
3747 *nameend
= *emailend
= 0;
3748 ident
= chomp_string(ident
);
3751 ident
= chomp_string(nameend
+ 1);
3756 *author
= get_author(ident
);
3758 /* Parse epoch and timezone */
3759 if (emailend
&& emailend
[1] == ' ') {
3760 char *secs
= emailend
+ 2;
3761 char *zone
= strchr(secs
, ' ');
3763 *time
= (time_t) atol(secs
);
3765 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
3766 parse_timezone(time
, zone
+ 1);
3771 open_commit_parent_menu(char buf
[SIZEOF_STR
], int *parents
)
3773 char rev
[SIZEOF_REV
];
3774 const char *revlist_argv
[] = {
3775 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev
, NULL
3777 struct menu_item
*items
;
3778 char text
[SIZEOF_STR
];
3782 items
= calloc(*parents
+ 1, sizeof(*items
));
3786 for (i
= 0; i
< *parents
; i
++) {
3787 string_copy_rev(rev
, &buf
[SIZEOF_REV
* i
]);
3788 if (!run_io_buf(revlist_argv
, text
, sizeof(text
)) ||
3789 !(items
[i
].text
= strdup(text
))) {
3797 ok
= prompt_menu("Select parent", items
, parents
);
3799 for (i
= 0; items
[i
].text
; i
++)
3800 free((char *) items
[i
].text
);
3806 select_commit_parent(const char *id
, char rev
[SIZEOF_REV
], const char *path
)
3808 char buf
[SIZEOF_STR
* 4];
3809 const char *revlist_argv
[] = {
3810 "git", "log", "--no-color", "-1",
3811 "--pretty=format:%P", id
, "--", path
, NULL
3815 if (!run_io_buf(revlist_argv
, buf
, sizeof(buf
)) ||
3816 (parents
= strlen(buf
) / 40) < 0) {
3817 report("Failed to get parent information");
3820 } else if (parents
== 0) {
3822 report("Path '%s' does not exist in the parent", path
);
3824 report("The selected commit has no parents");
3828 if (parents
> 1 && !open_commit_parent_menu(buf
, &parents
))
3831 string_copy_rev(rev
, &buf
[41 * parents
]);
3840 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
3842 char text
[SIZEOF_STR
];
3844 if (opt_line_number
&& draw_lineno(view
, lineno
))
3847 string_expand(text
, sizeof(text
), line
->data
, opt_tab_size
);
3848 draw_text(view
, line
->type
, text
, TRUE
);
3853 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
3855 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
3856 char ref
[SIZEOF_STR
];
3858 if (!run_io_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
3861 /* This is the only fatal call, since it can "corrupt" the buffer. */
3862 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
3869 add_pager_refs(struct view
*view
, struct line
*line
)
3871 char buf
[SIZEOF_STR
];
3872 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
3873 struct ref_list
*list
;
3874 size_t bufpos
= 0, i
;
3875 const char *sep
= "Refs: ";
3876 bool is_tag
= FALSE
;
3878 assert(line
->type
== LINE_COMMIT
);
3880 list
= get_ref_list(commit_id
);
3882 if (view
== VIEW(REQ_VIEW_DIFF
))
3883 goto try_add_describe_ref
;
3887 for (i
= 0; i
< list
->size
; i
++) {
3888 struct ref
*ref
= list
->refs
[i
];
3889 const char *fmt
= ref
->tag
? "%s[%s]" :
3890 ref
->remote
? "%s<%s>" : "%s%s";
3892 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
3899 if (!is_tag
&& view
== VIEW(REQ_VIEW_DIFF
)) {
3900 try_add_describe_ref
:
3901 /* Add <tag>-g<commit_id> "fake" reference. */
3902 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
3909 add_line_text(view
, buf
, LINE_PP_REFS
);
3913 pager_read(struct view
*view
, char *data
)
3920 line
= add_line_text(view
, data
, get_line_type(data
));
3924 if (line
->type
== LINE_COMMIT
&&
3925 (view
== VIEW(REQ_VIEW_DIFF
) ||
3926 view
== VIEW(REQ_VIEW_LOG
)))
3927 add_pager_refs(view
, line
);
3933 pager_request(struct view
*view
, enum request request
, struct line
*line
)
3937 if (request
!= REQ_ENTER
)
3940 if (line
->type
== LINE_COMMIT
&&
3941 (view
== VIEW(REQ_VIEW_LOG
) ||
3942 view
== VIEW(REQ_VIEW_PAGER
))) {
3943 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
3947 /* Always scroll the view even if it was split. That way
3948 * you can use Enter to scroll through the log view and
3949 * split open each commit diff. */
3950 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
3952 /* FIXME: A minor workaround. Scrolling the view will call report("")
3953 * but if we are scrolling a non-current view this won't properly
3954 * update the view title. */
3956 update_view_title(view
);
3962 pager_grep(struct view
*view
, struct line
*line
)
3964 const char *text
[] = { line
->data
, NULL
};
3966 return grep_text(view
, text
);
3970 pager_select(struct view
*view
, struct line
*line
)
3972 if (line
->type
== LINE_COMMIT
) {
3973 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
3975 if (view
!= VIEW(REQ_VIEW_PAGER
))
3976 string_copy_rev(view
->ref
, text
);
3977 string_copy_rev(ref_commit
, text
);
3981 static struct view_ops pager_ops
= {
3992 static const char *log_argv
[SIZEOF_ARG
] = {
3993 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3997 log_request(struct view
*view
, enum request request
, struct line
*line
)
4002 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
4005 return pager_request(view
, request
, line
);
4009 static struct view_ops log_ops
= {
4020 static const char *diff_argv
[SIZEOF_ARG
] = {
4021 "git", "show", "--pretty=fuller", "--no-color", "--root",
4022 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4025 static struct view_ops diff_ops
= {
4040 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
4043 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
4047 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
4048 help_keymap_hidden
[keymap
] ? '+' : '-',
4049 enum_name(keymap_table
[keymap
]));
4051 line
->other
= keymap
;
4053 return help_keymap_hidden
[keymap
];
4057 help_open_keymap(struct view
*view
, enum keymap keymap
)
4059 const char *group
= NULL
;
4060 char buf
[SIZEOF_STR
];
4062 bool add_title
= TRUE
;
4065 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
4066 const char *key
= NULL
;
4068 if (req_info
[i
].request
== REQ_NONE
)
4071 if (!req_info
[i
].request
) {
4072 group
= req_info
[i
].help
;
4076 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4080 if (add_title
&& help_open_keymap_title(view
, keymap
))
4085 add_line_text(view
, group
, LINE_HELP_GROUP
);
4089 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4090 enum_name(req_info
[i
]), req_info
[i
].help
);
4093 group
= "External commands:";
4095 for (i
= 0; i
< run_requests
; i
++) {
4096 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4100 if (!req
|| req
->keymap
!= keymap
)
4103 key
= get_key_name(req
->key
);
4105 key
= "(no key defined)";
4107 if (add_title
&& help_open_keymap_title(view
, keymap
))
4110 add_line_text(view
, group
, LINE_HELP_GROUP
);
4114 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4115 if (!string_format_from(buf
, &bufpos
, "%s%s",
4116 argc
? " " : "", req
->argv
[argc
]))
4119 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4124 help_open(struct view
*view
)
4129 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4130 add_line_text(view
, "", LINE_DEFAULT
);
4132 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4133 help_open_keymap(view
, keymap
);
4139 help_request(struct view
*view
, enum request request
, struct line
*line
)
4143 if (line
->type
== LINE_HELP_KEYMAP
) {
4144 help_keymap_hidden
[line
->other
] =
4145 !help_keymap_hidden
[line
->other
];
4146 view
->p_restore
= TRUE
;
4147 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4152 return pager_request(view
, request
, line
);
4156 static struct view_ops help_ops
= {
4172 struct tree_stack_entry
{
4173 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4174 unsigned long lineno
; /* Line number to restore */
4175 char *name
; /* Position of name in opt_path */
4178 /* The top of the path stack. */
4179 static struct tree_stack_entry
*tree_stack
= NULL
;
4180 unsigned long tree_lineno
= 0;
4183 pop_tree_stack_entry(void)
4185 struct tree_stack_entry
*entry
= tree_stack
;
4187 tree_lineno
= entry
->lineno
;
4189 tree_stack
= entry
->prev
;
4194 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4196 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4197 size_t pathlen
= strlen(opt_path
);
4202 entry
->prev
= tree_stack
;
4203 entry
->name
= opt_path
+ pathlen
;
4206 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4207 pop_tree_stack_entry();
4211 /* Move the current line to the first tree entry. */
4213 entry
->lineno
= lineno
;
4216 /* Parse output from git-ls-tree(1):
4218 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4221 #define SIZEOF_TREE_ATTR \
4222 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4224 #define SIZEOF_TREE_MODE \
4225 STRING_SIZE("100644 ")
4227 #define TREE_ID_OFFSET \
4228 STRING_SIZE("100644 blob ")
4231 char id
[SIZEOF_REV
];
4233 time_t time
; /* Date from the author ident. */
4234 const char *author
; /* Author of the commit. */
4239 tree_path(const struct line
*line
)
4241 return ((struct tree_entry
*) line
->data
)->name
;
4245 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4247 if (line1
->type
!= line2
->type
)
4248 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4249 return strcmp(tree_path(line1
), tree_path(line2
));
4252 static const enum sort_field tree_sort_fields
[] = {
4253 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4255 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4258 tree_compare(const void *l1
, const void *l2
)
4260 const struct line
*line1
= (const struct line
*) l1
;
4261 const struct line
*line2
= (const struct line
*) l2
;
4262 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4263 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4265 if (line1
->type
== LINE_TREE_HEAD
)
4267 if (line2
->type
== LINE_TREE_HEAD
)
4270 switch (get_sort_field(tree_sort_state
)) {
4272 return sort_order(tree_sort_state
, entry1
->time
- entry2
->time
);
4274 case ORDERBY_AUTHOR
:
4275 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4279 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4284 static struct line
*
4285 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4286 const char *mode
, const char *id
)
4288 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4289 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4291 if (!entry
|| !line
) {
4296 strncpy(entry
->name
, path
, strlen(path
));
4298 entry
->mode
= strtoul(mode
, NULL
, 8);
4300 string_copy_rev(entry
->id
, id
);
4306 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4308 static const char *author_name
;
4309 static time_t author_time
;
4311 if (!text
&& *read_date
) {
4316 char *path
= *opt_path
? opt_path
: ".";
4317 /* Find next entry to process */
4318 const char *log_file
[] = {
4319 "git", "log", "--no-color", "--pretty=raw",
4320 "--cc", "--raw", view
->id
, "--", path
, NULL
4325 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4326 report("Tree is empty");
4330 if (!run_io_rd(&io
, log_file
, opt_cdup
, FORMAT_NONE
)) {
4331 report("Failed to load tree data");
4335 done_io(view
->pipe
);
4340 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4341 parse_author_line(text
+ STRING_SIZE("author "),
4342 &author_name
, &author_time
);
4344 } else if (*text
== ':') {
4346 size_t annotated
= 1;
4349 pos
= strchr(text
, '\t');
4353 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4354 text
+= strlen(opt_path
);
4355 pos
= strchr(text
, '/');
4359 for (i
= 1; i
< view
->lines
; i
++) {
4360 struct line
*line
= &view
->line
[i
];
4361 struct tree_entry
*entry
= line
->data
;
4363 annotated
+= !!entry
->author
;
4364 if (entry
->author
|| strcmp(entry
->name
, text
))
4367 entry
->author
= author_name
;
4368 entry
->time
= author_time
;
4373 if (annotated
== view
->lines
)
4374 kill_io(view
->pipe
);
4380 tree_read(struct view
*view
, char *text
)
4382 static bool read_date
= FALSE
;
4383 struct tree_entry
*data
;
4384 struct line
*entry
, *line
;
4385 enum line_type type
;
4386 size_t textlen
= text
? strlen(text
) : 0;
4387 char *path
= text
+ SIZEOF_TREE_ATTR
;
4389 if (read_date
|| !text
)
4390 return tree_read_date(view
, text
, &read_date
);
4392 if (textlen
<= SIZEOF_TREE_ATTR
)
4394 if (view
->lines
== 0 &&
4395 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4398 /* Strip the path part ... */
4400 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4401 size_t striplen
= strlen(opt_path
);
4403 if (pathlen
> striplen
)
4404 memmove(path
, path
+ striplen
,
4405 pathlen
- striplen
+ 1);
4407 /* Insert "link" to parent directory. */
4408 if (view
->lines
== 1 &&
4409 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4413 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4414 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4419 /* Skip "Directory ..." and ".." line. */
4420 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4421 if (tree_compare_entry(line
, entry
) <= 0)
4424 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4428 for (; line
<= entry
; line
++)
4429 line
->dirty
= line
->cleareol
= 1;
4433 if (tree_lineno
> view
->lineno
) {
4434 view
->lineno
= tree_lineno
;
4442 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4444 struct tree_entry
*entry
= line
->data
;
4446 if (line
->type
== LINE_TREE_HEAD
) {
4447 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4450 if (draw_mode(view
, entry
->mode
))
4453 if (opt_author
&& draw_author(view
, entry
->author
))
4456 if (opt_date
&& draw_date(view
, entry
->author
? &entry
->time
: NULL
))
4459 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4467 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4468 int fd
= mkstemp(file
);
4471 report("Failed to create temporary file");
4472 else if (!run_io_append(blob_ops
.argv
, FORMAT_ALL
, fd
))
4473 report("Failed to save blob data to file");
4481 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4483 enum open_flags flags
;
4486 case REQ_VIEW_BLAME
:
4487 if (line
->type
!= LINE_TREE_FILE
) {
4488 report("Blame only supported for files");
4492 string_copy(opt_ref
, view
->vid
);
4496 if (line
->type
!= LINE_TREE_FILE
) {
4497 report("Edit only supported for files");
4498 } else if (!is_head_commit(view
->vid
)) {
4501 open_editor(opt_file
);
4505 case REQ_TOGGLE_SORT_FIELD
:
4506 case REQ_TOGGLE_SORT_ORDER
:
4507 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4512 /* quit view if at top of tree */
4513 return REQ_VIEW_CLOSE
;
4516 line
= &view
->line
[1];
4526 /* Cleanup the stack if the tree view is at a different tree. */
4527 while (!*opt_path
&& tree_stack
)
4528 pop_tree_stack_entry();
4530 switch (line
->type
) {
4532 /* Depending on whether it is a subdirectory or parent link
4533 * mangle the path buffer. */
4534 if (line
== &view
->line
[1] && *opt_path
) {
4535 pop_tree_stack_entry();
4538 const char *basename
= tree_path(line
);
4540 push_tree_stack_entry(basename
, view
->lineno
);
4543 /* Trees and subtrees share the same ID, so they are not not
4544 * unique like blobs. */
4545 flags
= OPEN_RELOAD
;
4546 request
= REQ_VIEW_TREE
;
4549 case LINE_TREE_FILE
:
4550 flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4551 request
= REQ_VIEW_BLOB
;
4558 open_view(view
, request
, flags
);
4559 if (request
== REQ_VIEW_TREE
)
4560 view
->lineno
= tree_lineno
;
4566 tree_grep(struct view
*view
, struct line
*line
)
4568 struct tree_entry
*entry
= line
->data
;
4569 const char *text
[] = {
4571 opt_author
? entry
->author
: "",
4572 opt_date
? mkdate(&entry
->time
) : "",
4576 return grep_text(view
, text
);
4580 tree_select(struct view
*view
, struct line
*line
)
4582 struct tree_entry
*entry
= line
->data
;
4584 if (line
->type
== LINE_TREE_FILE
) {
4585 string_copy_rev(ref_blob
, entry
->id
);
4586 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4588 } else if (line
->type
!= LINE_TREE_DIR
) {
4592 string_copy_rev(view
->ref
, entry
->id
);
4596 tree_prepare(struct view
*view
)
4598 if (view
->lines
== 0 && opt_prefix
[0]) {
4599 char *pos
= opt_prefix
;
4601 while (pos
&& *pos
) {
4602 char *end
= strchr(pos
, '/');
4606 push_tree_stack_entry(pos
, 0);
4614 } else if (strcmp(view
->vid
, view
->id
)) {
4618 return init_io_rd(&view
->io
, view
->ops
->argv
, opt_cdup
, FORMAT_ALL
);
4621 static const char *tree_argv
[SIZEOF_ARG
] = {
4622 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4625 static struct view_ops tree_ops
= {
4638 blob_read(struct view
*view
, char *line
)
4642 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4646 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4653 return pager_request(view
, request
, line
);
4657 static const char *blob_argv
[SIZEOF_ARG
] = {
4658 "git", "cat-file", "blob", "%(blob)", NULL
4661 static struct view_ops blob_ops
= {
4675 * Loading the blame view is a two phase job:
4677 * 1. File content is read either using opt_file from the
4678 * filesystem or using git-cat-file.
4679 * 2. Then blame information is incrementally added by
4680 * reading output from git-blame.
4683 static const char *blame_head_argv
[] = {
4684 "git", "blame", "--incremental", "--", "%(file)", NULL
4687 static const char *blame_ref_argv
[] = {
4688 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4691 static const char *blame_cat_file_argv
[] = {
4692 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4695 struct blame_commit
{
4696 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4697 char title
[128]; /* First line of the commit message. */
4698 const char *author
; /* Author of the commit. */
4699 time_t time
; /* Date from the author ident. */
4700 char filename
[128]; /* Name of file. */
4701 bool has_previous
; /* Was a "previous" line detected. */
4705 struct blame_commit
*commit
;
4706 unsigned long lineno
;
4711 blame_open(struct view
*view
)
4713 char path
[SIZEOF_STR
];
4715 if (!view
->parent
&& *opt_prefix
) {
4716 string_copy(path
, opt_file
);
4717 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4721 if (*opt_ref
|| !io_open(&view
->io
, "%s%s", opt_cdup
, opt_file
)) {
4722 if (!run_io_rd(&view
->io
, blame_cat_file_argv
, opt_cdup
, FORMAT_ALL
))
4726 setup_update(view
, opt_file
);
4727 string_format(view
->ref
, "%s ...", opt_file
);
4732 static struct blame_commit
*
4733 get_blame_commit(struct view
*view
, const char *id
)
4737 for (i
= 0; i
< view
->lines
; i
++) {
4738 struct blame
*blame
= view
->line
[i
].data
;
4743 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4744 return blame
->commit
;
4748 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4751 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4757 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4759 const char *pos
= *posref
;
4762 pos
= strchr(pos
+ 1, ' ');
4763 if (!pos
|| !isdigit(pos
[1]))
4765 *number
= atoi(pos
+ 1);
4766 if (*number
< min
|| *number
> max
)
4773 static struct blame_commit
*
4774 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4776 struct blame_commit
*commit
;
4777 struct blame
*blame
;
4778 const char *pos
= text
+ SIZEOF_REV
- 2;
4779 size_t orig_lineno
= 0;
4783 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4786 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4787 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4788 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4791 commit
= get_blame_commit(view
, text
);
4797 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4800 blame
->commit
= commit
;
4801 blame
->lineno
= orig_lineno
+ group
- 1;
4809 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4812 const char **argv
= *opt_ref
? blame_ref_argv
: blame_head_argv
;
4815 if (view
->lines
== 0 && !view
->parent
)
4816 die("No blame exist for %s", view
->vid
);
4818 if (view
->lines
== 0 || !run_io_rd(&io
, argv
, opt_cdup
, FORMAT_ALL
)) {
4819 report("Failed to load blame data");
4823 done_io(view
->pipe
);
4829 size_t linelen
= strlen(line
);
4830 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
4835 blame
->commit
= NULL
;
4836 strncpy(blame
->text
, line
, linelen
);
4837 blame
->text
[linelen
] = 0;
4838 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
4843 match_blame_header(const char *name
, char **line
)
4845 size_t namelen
= strlen(name
);
4846 bool matched
= !strncmp(name
, *line
, namelen
);
4855 blame_read(struct view
*view
, char *line
)
4857 static struct blame_commit
*commit
= NULL
;
4858 static int blamed
= 0;
4859 static bool read_file
= TRUE
;
4862 return blame_read_file(view
, line
, &read_file
);
4869 string_format(view
->ref
, "%s", view
->vid
);
4870 if (view_is_displayed(view
)) {
4871 update_view_title(view
);
4872 redraw_view_from(view
, 0);
4878 commit
= parse_blame_commit(view
, line
, &blamed
);
4879 string_format(view
->ref
, "%s %2d%%", view
->vid
,
4880 view
->lines
? blamed
* 100 / view
->lines
: 0);
4882 } else if (match_blame_header("author ", &line
)) {
4883 commit
->author
= get_author(line
);
4885 } else if (match_blame_header("author-time ", &line
)) {
4886 commit
->time
= (time_t) atol(line
);
4888 } else if (match_blame_header("author-tz ", &line
)) {
4889 parse_timezone(&commit
->time
, line
);
4891 } else if (match_blame_header("summary ", &line
)) {
4892 string_ncopy(commit
->title
, line
, strlen(line
));
4894 } else if (match_blame_header("previous ", &line
)) {
4895 commit
->has_previous
= TRUE
;
4897 } else if (match_blame_header("filename ", &line
)) {
4898 string_ncopy(commit
->filename
, line
, strlen(line
));
4906 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4908 struct blame
*blame
= line
->data
;
4909 time_t *time
= NULL
;
4910 const char *id
= NULL
, *author
= NULL
;
4911 char text
[SIZEOF_STR
];
4913 if (blame
->commit
&& *blame
->commit
->filename
) {
4914 id
= blame
->commit
->id
;
4915 author
= blame
->commit
->author
;
4916 time
= &blame
->commit
->time
;
4919 if (opt_date
&& draw_date(view
, time
))
4922 if (opt_author
&& draw_author(view
, author
))
4925 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
4928 if (draw_lineno(view
, lineno
))
4931 string_expand(text
, sizeof(text
), blame
->text
, opt_tab_size
);
4932 draw_text(view
, LINE_DEFAULT
, text
, TRUE
);
4937 check_blame_commit(struct blame
*blame
, bool check_null_id
)
4940 report("Commit data not loaded yet");
4941 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4942 report("No commit exist for the selected line");
4949 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
4951 const char *diff_tree_argv
[] = {
4952 "git", "diff-tree", "-U0", blame
->commit
->id
,
4953 "--", blame
->commit
->filename
, NULL
4956 int parent_lineno
= -1;
4957 int blamed_lineno
= -1;
4960 if (!run_io(&io
, diff_tree_argv
, NULL
, IO_RD
))
4963 while ((line
= io_get(&io
, '\n', TRUE
))) {
4965 char *pos
= strchr(line
, '+');
4967 parent_lineno
= atoi(line
+ 4);
4969 blamed_lineno
= atoi(pos
+ 1);
4971 } else if (*line
== '+' && parent_lineno
!= -1) {
4972 if (blame
->lineno
== blamed_lineno
- 1 &&
4973 !strcmp(blame
->text
, line
+ 1)) {
4974 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
4985 blame_request(struct view
*view
, enum request request
, struct line
*line
)
4987 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4988 struct blame
*blame
= line
->data
;
4991 case REQ_VIEW_BLAME
:
4992 if (check_blame_commit(blame
, TRUE
)) {
4993 string_copy(opt_ref
, blame
->commit
->id
);
4994 string_copy(opt_file
, blame
->commit
->filename
);
4996 view
->lineno
= blame
->lineno
;
4997 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5002 if (check_blame_commit(blame
, TRUE
) &&
5003 select_commit_parent(blame
->commit
->id
, opt_ref
,
5004 blame
->commit
->filename
)) {
5005 string_copy(opt_file
, blame
->commit
->filename
);
5006 setup_blame_parent_line(view
, blame
);
5007 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5012 if (!check_blame_commit(blame
, FALSE
))
5015 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
5016 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
5019 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
5020 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
5021 const char *diff_index_argv
[] = {
5022 "git", "diff-index", "--root", "--patch-with-stat",
5023 "-C", "-M", "HEAD", "--", view
->vid
, NULL
5026 if (!blame
->commit
->has_previous
) {
5027 diff_index_argv
[1] = "diff";
5028 diff_index_argv
[2] = "--no-color";
5029 diff_index_argv
[6] = "--";
5030 diff_index_argv
[7] = "/dev/null";
5033 if (!prepare_update(diff
, diff_index_argv
, NULL
, FORMAT_DASH
)) {
5034 report("Failed to allocate diff command");
5037 flags
|= OPEN_PREPARED
;
5040 open_view(view
, REQ_VIEW_DIFF
, flags
);
5041 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5042 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
5053 blame_grep(struct view
*view
, struct line
*line
)
5055 struct blame
*blame
= line
->data
;
5056 struct blame_commit
*commit
= blame
->commit
;
5057 const char *text
[] = {
5059 commit
? commit
->title
: "",
5060 commit
? commit
->id
: "",
5061 commit
&& opt_author
? commit
->author
: "",
5062 commit
&& opt_date
? mkdate(&commit
->time
) : "",
5066 return grep_text(view
, text
);
5070 blame_select(struct view
*view
, struct line
*line
)
5072 struct blame
*blame
= line
->data
;
5073 struct blame_commit
*commit
= blame
->commit
;
5078 if (!strcmp(commit
->id
, NULL_ID
))
5079 string_ncopy(ref_commit
, "HEAD", 4);
5081 string_copy_rev(ref_commit
, commit
->id
);
5084 static struct view_ops blame_ops
= {
5100 const char *author
; /* Author of the last commit. */
5101 time_t time
; /* Date of the last activity. */
5102 const struct ref
*ref
; /* Name and commit ID information. */
5105 static const struct ref branch_all
;
5107 static const enum sort_field branch_sort_fields
[] = {
5108 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5110 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5113 branch_compare(const void *l1
, const void *l2
)
5115 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5116 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5118 switch (get_sort_field(branch_sort_state
)) {
5120 return sort_order(branch_sort_state
, branch1
->time
- branch2
->time
);
5122 case ORDERBY_AUTHOR
:
5123 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5127 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5132 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5134 struct branch
*branch
= line
->data
;
5135 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5137 if (opt_date
&& draw_date(view
, branch
->author
? &branch
->time
: NULL
))
5140 if (opt_author
&& draw_author(view
, branch
->author
))
5143 draw_text(view
, type
, branch
->ref
== &branch_all
? "All branches" : branch
->ref
->name
, TRUE
);
5148 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5150 struct branch
*branch
= line
->data
;
5155 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5158 case REQ_TOGGLE_SORT_FIELD
:
5159 case REQ_TOGGLE_SORT_ORDER
:
5160 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5164 if (branch
->ref
== &branch_all
) {
5165 const char *all_branches_argv
[] = {
5166 "git", "log", "--no-color", "--pretty=raw", "--parents",
5167 "--topo-order", "--all", NULL
5169 struct view
*main_view
= VIEW(REQ_VIEW_MAIN
);
5171 if (!prepare_update(main_view
, all_branches_argv
, NULL
, FORMAT_NONE
)) {
5172 report("Failed to load view of all branches");
5175 open_view(view
, REQ_VIEW_MAIN
, OPEN_PREPARED
| OPEN_SPLIT
);
5177 open_view(view
, REQ_VIEW_MAIN
, OPEN_SPLIT
);
5187 branch_read(struct view
*view
, char *line
)
5189 static char id
[SIZEOF_REV
];
5190 struct branch
*reference
;
5196 switch (get_line_type(line
)) {
5198 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5202 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5203 struct branch
*branch
= view
->line
[i
].data
;
5205 if (strcmp(branch
->ref
->id
, id
))
5208 view
->line
[i
].dirty
= TRUE
;
5210 branch
->author
= reference
->author
;
5211 branch
->time
= reference
->time
;
5215 parse_author_line(line
+ STRING_SIZE("author "),
5216 &branch
->author
, &branch
->time
);
5228 branch_open_visitor(void *data
, const struct ref
*ref
)
5230 struct view
*view
= data
;
5231 struct branch
*branch
;
5233 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5236 branch
= calloc(1, sizeof(*branch
));
5241 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5245 branch_open(struct view
*view
)
5247 const char *branch_log
[] = {
5248 "git", "log", "--no-color", "--pretty=raw",
5249 "--simplify-by-decoration", "--all", NULL
5252 if (!run_io_rd(&view
->io
, branch_log
, NULL
, FORMAT_NONE
)) {
5253 report("Failed to load branch data");
5257 setup_update(view
, view
->id
);
5258 branch_open_visitor(view
, &branch_all
);
5259 foreach_ref(branch_open_visitor
, view
);
5260 view
->p_restore
= TRUE
;
5266 branch_grep(struct view
*view
, struct line
*line
)
5268 struct branch
*branch
= line
->data
;
5269 const char *text
[] = {
5275 return grep_text(view
, text
);
5279 branch_select(struct view
*view
, struct line
*line
)
5281 struct branch
*branch
= line
->data
;
5283 string_copy_rev(view
->ref
, branch
->ref
->id
);
5284 string_copy_rev(ref_commit
, branch
->ref
->id
);
5285 string_copy_rev(ref_head
, branch
->ref
->id
);
5288 static struct view_ops branch_ops
= {
5307 char rev
[SIZEOF_REV
];
5308 char name
[SIZEOF_STR
];
5312 char rev
[SIZEOF_REV
];
5313 char name
[SIZEOF_STR
];
5317 static char status_onbranch
[SIZEOF_STR
];
5318 static struct status stage_status
;
5319 static enum line_type stage_line_type
;
5320 static size_t stage_chunks
;
5321 static int *stage_chunk
;
5323 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5325 /* This should work even for the "On branch" line. */
5327 status_has_none(struct view
*view
, struct line
*line
)
5329 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5332 /* Get fields from the diff line:
5333 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5336 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5338 const char *old_mode
= buf
+ 1;
5339 const char *new_mode
= buf
+ 8;
5340 const char *old_rev
= buf
+ 15;
5341 const char *new_rev
= buf
+ 56;
5342 const char *status
= buf
+ 97;
5345 old_mode
[-1] != ':' ||
5346 new_mode
[-1] != ' ' ||
5347 old_rev
[-1] != ' ' ||
5348 new_rev
[-1] != ' ' ||
5352 file
->status
= *status
;
5354 string_copy_rev(file
->old
.rev
, old_rev
);
5355 string_copy_rev(file
->new.rev
, new_rev
);
5357 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5358 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5360 file
->old
.name
[0] = file
->new.name
[0] = 0;
5366 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5368 struct status
*unmerged
= NULL
;
5372 if (!run_io(&io
, argv
, opt_cdup
, IO_RD
))
5375 add_line_data(view
, NULL
, type
);
5377 while ((buf
= io_get(&io
, 0, TRUE
))) {
5378 struct status
*file
= unmerged
;
5381 file
= calloc(1, sizeof(*file
));
5382 if (!file
|| !add_line_data(view
, file
, type
))
5386 /* Parse diff info part. */
5388 file
->status
= status
;
5390 string_copy(file
->old
.rev
, NULL_ID
);
5392 } else if (!file
->status
|| file
== unmerged
) {
5393 if (!status_get_diff(file
, buf
, strlen(buf
)))
5396 buf
= io_get(&io
, 0, TRUE
);
5400 /* Collapse all modified entries that follow an
5401 * associated unmerged entry. */
5402 if (unmerged
== file
) {
5403 unmerged
->status
= 'U';
5405 } else if (file
->status
== 'U') {
5410 /* Grab the old name for rename/copy. */
5411 if (!*file
->old
.name
&&
5412 (file
->status
== 'R' || file
->status
== 'C')) {
5413 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5415 buf
= io_get(&io
, 0, TRUE
);
5420 /* git-ls-files just delivers a NUL separated list of
5421 * file names similar to the second half of the
5422 * git-diff-* output. */
5423 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5424 if (!*file
->old
.name
)
5425 string_copy(file
->old
.name
, file
->new.name
);
5429 if (io_error(&io
)) {
5435 if (!view
->line
[view
->lines
- 1].data
)
5436 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5442 /* Don't show unmerged entries in the staged section. */
5443 static const char *status_diff_index_argv
[] = {
5444 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5445 "--cached", "-M", "HEAD", NULL
5448 static const char *status_diff_files_argv
[] = {
5449 "git", "diff-files", "-z", NULL
5452 static const char *status_list_other_argv
[] = {
5453 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix
, NULL
5456 static const char *status_list_no_head_argv
[] = {
5457 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5460 static const char *update_index_argv
[] = {
5461 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5464 /* Restore the previous line number to stay in the context or select a
5465 * line with something that can be updated. */
5467 status_restore(struct view
*view
)
5469 if (view
->p_lineno
>= view
->lines
)
5470 view
->p_lineno
= view
->lines
- 1;
5471 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5473 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5476 /* If the above fails, always skip the "On branch" line. */
5477 if (view
->p_lineno
< view
->lines
)
5478 view
->lineno
= view
->p_lineno
;
5482 if (view
->lineno
< view
->offset
)
5483 view
->offset
= view
->lineno
;
5484 else if (view
->offset
+ view
->height
<= view
->lineno
)
5485 view
->offset
= view
->lineno
- view
->height
+ 1;
5487 view
->p_restore
= FALSE
;
5491 status_update_onbranch(void)
5493 static const char *paths
[][2] = {
5494 { "rebase-apply/rebasing", "Rebasing" },
5495 { "rebase-apply/applying", "Applying mailbox" },
5496 { "rebase-apply/", "Rebasing mailbox" },
5497 { "rebase-merge/interactive", "Interactive rebase" },
5498 { "rebase-merge/", "Rebase merge" },
5499 { "MERGE_HEAD", "Merging" },
5500 { "BISECT_LOG", "Bisecting" },
5501 { "HEAD", "On branch" },
5503 char buf
[SIZEOF_STR
];
5507 if (is_initial_commit()) {
5508 string_copy(status_onbranch
, "Initial commit");
5512 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5513 char *head
= opt_head
;
5515 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5516 lstat(buf
, &stat
) < 0)
5522 if (io_open(&io
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5523 io_read_buf(&io
, buf
, sizeof(buf
))) {
5525 if (!prefixcmp(head
, "refs/heads/"))
5526 head
+= STRING_SIZE("refs/heads/");
5530 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5531 string_copy(status_onbranch
, opt_head
);
5535 string_copy(status_onbranch
, "Not currently on any branch");
5538 /* First parse staged info using git-diff-index(1), then parse unstaged
5539 * info using git-diff-files(1), and finally untracked files using
5540 * git-ls-files(1). */
5542 status_open(struct view
*view
)
5546 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5547 status_update_onbranch();
5549 run_io_bg(update_index_argv
);
5551 if (is_initial_commit()) {
5552 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5554 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5558 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5559 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5562 /* Restore the exact position or use the specialized restore
5564 if (!view
->p_restore
)
5565 status_restore(view
);
5570 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5572 struct status
*status
= line
->data
;
5573 enum line_type type
;
5577 switch (line
->type
) {
5578 case LINE_STAT_STAGED
:
5579 type
= LINE_STAT_SECTION
;
5580 text
= "Changes to be committed:";
5583 case LINE_STAT_UNSTAGED
:
5584 type
= LINE_STAT_SECTION
;
5585 text
= "Changed but not updated:";
5588 case LINE_STAT_UNTRACKED
:
5589 type
= LINE_STAT_SECTION
;
5590 text
= "Untracked files:";
5593 case LINE_STAT_NONE
:
5594 type
= LINE_DEFAULT
;
5595 text
= " (no files)";
5598 case LINE_STAT_HEAD
:
5599 type
= LINE_STAT_HEAD
;
5600 text
= status_onbranch
;
5607 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5609 buf
[0] = status
->status
;
5610 if (draw_text(view
, line
->type
, buf
, TRUE
))
5612 type
= LINE_DEFAULT
;
5613 text
= status
->new.name
;
5616 draw_text(view
, type
, text
, TRUE
);
5621 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5623 if (displayed_views() == 2 || display
[current_view
] != view
)
5624 maximize_view(view
);
5625 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5630 status_enter(struct view
*view
, struct line
*line
)
5632 struct status
*status
= line
->data
;
5633 const char *oldpath
= status
? status
->old
.name
: NULL
;
5634 /* Diffs for unmerged entries are empty when passing the new
5635 * path, so leave it empty. */
5636 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5638 enum open_flags split
;
5639 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5641 if (line
->type
== LINE_STAT_NONE
||
5642 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5643 report("No file to diff");
5647 switch (line
->type
) {
5648 case LINE_STAT_STAGED
:
5649 if (is_initial_commit()) {
5650 const char *no_head_diff_argv
[] = {
5651 "git", "diff", "--no-color", "--patch-with-stat",
5652 "--", "/dev/null", newpath
, NULL
5655 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
, FORMAT_DASH
))
5656 return status_load_error(view
, stage
, newpath
);
5658 const char *index_show_argv
[] = {
5659 "git", "diff-index", "--root", "--patch-with-stat",
5660 "-C", "-M", "--cached", "HEAD", "--",
5661 oldpath
, newpath
, NULL
5664 if (!prepare_update(stage
, index_show_argv
, opt_cdup
, FORMAT_DASH
))
5665 return status_load_error(view
, stage
, newpath
);
5669 info
= "Staged changes to %s";
5671 info
= "Staged changes";
5674 case LINE_STAT_UNSTAGED
:
5676 const char *files_show_argv
[] = {
5677 "git", "diff-files", "--root", "--patch-with-stat",
5678 "-C", "-M", "--", oldpath
, newpath
, NULL
5681 if (!prepare_update(stage
, files_show_argv
, opt_cdup
, FORMAT_DASH
))
5682 return status_load_error(view
, stage
, newpath
);
5684 info
= "Unstaged changes to %s";
5686 info
= "Unstaged changes";
5689 case LINE_STAT_UNTRACKED
:
5691 report("No file to show");
5695 if (!suffixcmp(status
->new.name
, -1, "/")) {
5696 report("Cannot display a directory");
5700 if (!prepare_update_file(stage
, newpath
))
5701 return status_load_error(view
, stage
, newpath
);
5702 info
= "Untracked file %s";
5705 case LINE_STAT_HEAD
:
5709 die("line type %d not handled in switch", line
->type
);
5712 split
= view_is_displayed(view
) ? OPEN_SPLIT
: 0;
5713 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5714 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5716 stage_status
= *status
;
5718 memset(&stage_status
, 0, sizeof(stage_status
));
5721 stage_line_type
= line
->type
;
5723 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5730 status_exists(struct status
*status
, enum line_type type
)
5732 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5733 unsigned long lineno
;
5735 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5736 struct line
*line
= &view
->line
[lineno
];
5737 struct status
*pos
= line
->data
;
5739 if (line
->type
!= type
)
5741 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5742 select_view_line(view
, lineno
);
5745 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5746 select_view_line(view
, lineno
);
5756 status_update_prepare(struct io
*io
, enum line_type type
)
5758 const char *staged_argv
[] = {
5759 "git", "update-index", "-z", "--index-info", NULL
5761 const char *others_argv
[] = {
5762 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5766 case LINE_STAT_STAGED
:
5767 return run_io(io
, staged_argv
, opt_cdup
, IO_WR
);
5769 case LINE_STAT_UNSTAGED
:
5770 case LINE_STAT_UNTRACKED
:
5771 return run_io(io
, others_argv
, opt_cdup
, IO_WR
);
5774 die("line type %d not handled in switch", type
);
5780 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5782 char buf
[SIZEOF_STR
];
5786 case LINE_STAT_STAGED
:
5787 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5790 status
->old
.name
, 0))
5794 case LINE_STAT_UNSTAGED
:
5795 case LINE_STAT_UNTRACKED
:
5796 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
5801 die("line type %d not handled in switch", type
);
5804 return io_write(io
, buf
, bufsize
);
5808 status_update_file(struct status
*status
, enum line_type type
)
5813 if (!status_update_prepare(&io
, type
))
5816 result
= status_update_write(&io
, status
, type
);
5817 return done_io(&io
) && result
;
5821 status_update_files(struct view
*view
, struct line
*line
)
5823 char buf
[sizeof(view
->ref
)];
5826 struct line
*pos
= view
->line
+ view
->lines
;
5829 int cursor_y
= -1, cursor_x
= -1;
5831 if (!status_update_prepare(&io
, line
->type
))
5834 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
5837 string_copy(buf
, view
->ref
);
5838 getsyx(cursor_y
, cursor_x
);
5839 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
5840 int almost_done
= file
* 100 / files
;
5842 if (almost_done
> done
) {
5844 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
5846 update_view_title(view
);
5847 setsyx(cursor_y
, cursor_x
);
5850 result
= status_update_write(&io
, line
->data
, line
->type
);
5852 string_copy(view
->ref
, buf
);
5854 return done_io(&io
) && result
;
5858 status_update(struct view
*view
)
5860 struct line
*line
= &view
->line
[view
->lineno
];
5862 assert(view
->lines
);
5865 /* This should work even for the "On branch" line. */
5866 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
5867 report("Nothing to update");
5871 if (!status_update_files(view
, line
+ 1)) {
5872 report("Failed to update file status");
5876 } else if (!status_update_file(line
->data
, line
->type
)) {
5877 report("Failed to update file status");
5885 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
5887 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
5888 if (type
== LINE_STAT_STAGED
) {
5889 report("Cannot revert changes to staged files");
5890 } else if (type
== LINE_STAT_UNTRACKED
) {
5891 report("Cannot revert changes to untracked files");
5892 } else if (has_none
) {
5893 report("Nothing to revert");
5895 report("Cannot revert changes to multiple files");
5898 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5899 char mode
[10] = "100644";
5900 const char *reset_argv
[] = {
5901 "git", "update-index", "--cacheinfo", mode
,
5902 status
->old
.rev
, status
->old
.name
, NULL
5904 const char *checkout_argv
[] = {
5905 "git", "checkout", "--", status
->old
.name
, NULL
5908 if (status
->status
== 'U') {
5909 string_format(mode
, "%5o", status
->old
.mode
);
5911 if (status
->old
.mode
== 0 && status
->new.mode
== 0) {
5912 reset_argv
[2] = "--force-remove";
5913 reset_argv
[3] = status
->old
.name
;
5914 reset_argv
[4] = NULL
;
5917 if (!run_io_fg(reset_argv
, opt_cdup
))
5919 if (status
->old
.mode
== 0 && status
->new.mode
== 0)
5923 return run_io_fg(checkout_argv
, opt_cdup
);
5930 status_request(struct view
*view
, enum request request
, struct line
*line
)
5932 struct status
*status
= line
->data
;
5935 case REQ_STATUS_UPDATE
:
5936 if (!status_update(view
))
5940 case REQ_STATUS_REVERT
:
5941 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
5945 case REQ_STATUS_MERGE
:
5946 if (!status
|| status
->status
!= 'U') {
5947 report("Merging only possible for files with unmerged status ('U').");
5950 open_mergetool(status
->new.name
);
5956 if (status
->status
== 'D') {
5957 report("File has been deleted.");
5961 open_editor(status
->new.name
);
5964 case REQ_VIEW_BLAME
:
5970 /* After returning the status view has been split to
5971 * show the stage view. No further reloading is
5973 return status_enter(view
, line
);
5976 /* Simply reload the view. */
5983 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
5989 status_select(struct view
*view
, struct line
*line
)
5991 struct status
*status
= line
->data
;
5992 char file
[SIZEOF_STR
] = "all files";
5996 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
5999 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
6002 switch (line
->type
) {
6003 case LINE_STAT_STAGED
:
6004 text
= "Press %s to unstage %s for commit";
6007 case LINE_STAT_UNSTAGED
:
6008 text
= "Press %s to stage %s for commit";
6011 case LINE_STAT_UNTRACKED
:
6012 text
= "Press %s to stage %s for addition";
6015 case LINE_STAT_HEAD
:
6016 case LINE_STAT_NONE
:
6017 text
= "Nothing to update";
6021 die("line type %d not handled in switch", line
->type
);
6024 if (status
&& status
->status
== 'U') {
6025 text
= "Press %s to resolve conflict in %s";
6026 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
6029 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
6032 string_format(view
->ref
, text
, key
, file
);
6034 string_copy(opt_file
, status
->new.name
);
6038 status_grep(struct view
*view
, struct line
*line
)
6040 struct status
*status
= line
->data
;
6043 const char buf
[2] = { status
->status
, 0 };
6044 const char *text
[] = { status
->new.name
, buf
, NULL
};
6046 return grep_text(view
, text
);
6052 static struct view_ops status_ops
= {
6065 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
6067 while (line
< end
) {
6068 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
6069 !io_write(io
, "\n", 1))
6072 if (line
->type
== LINE_DIFF_CHUNK
||
6073 line
->type
== LINE_DIFF_HEADER
)
6080 static struct line
*
6081 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
6083 for (; view
->line
< line
; line
--)
6084 if (line
->type
== type
)
6091 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
6093 const char *apply_argv
[SIZEOF_ARG
] = {
6094 "git", "apply", "--whitespace=nowarn", NULL
6096 struct line
*diff_hdr
;
6100 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6105 apply_argv
[argc
++] = "--cached";
6106 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6107 apply_argv
[argc
++] = "-R";
6108 apply_argv
[argc
++] = "-";
6109 apply_argv
[argc
++] = NULL
;
6110 if (!run_io(&io
, apply_argv
, opt_cdup
, IO_WR
))
6113 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6114 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6118 run_io_bg(update_index_argv
);
6120 return chunk
? TRUE
: FALSE
;
6124 stage_update(struct view
*view
, struct line
*line
)
6126 struct line
*chunk
= NULL
;
6128 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6129 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6132 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6133 report("Failed to apply chunk");
6137 } else if (!stage_status
.status
) {
6138 view
= VIEW(REQ_VIEW_STATUS
);
6140 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6141 if (line
->type
== stage_line_type
)
6144 if (!status_update_files(view
, line
+ 1)) {
6145 report("Failed to update files");
6149 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6150 report("Failed to update file");
6158 stage_revert(struct view
*view
, struct line
*line
)
6160 struct line
*chunk
= NULL
;
6162 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6163 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6166 if (!prompt_yesno("Are you sure you want to revert changes?"))
6169 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6170 report("Failed to revert chunk");
6176 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6177 stage_line_type
, FALSE
);
6183 stage_next(struct view
*view
, struct line
*line
)
6187 if (!stage_chunks
) {
6188 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6189 if (line
->type
!= LINE_DIFF_CHUNK
)
6192 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6193 report("Allocation failure");
6197 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6201 for (i
= 0; i
< stage_chunks
; i
++) {
6202 if (stage_chunk
[i
] > view
->lineno
) {
6203 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6204 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6209 report("No next chunk found");
6213 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6216 case REQ_STATUS_UPDATE
:
6217 if (!stage_update(view
, line
))
6221 case REQ_STATUS_REVERT
:
6222 if (!stage_revert(view
, line
))
6226 case REQ_STAGE_NEXT
:
6227 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6228 report("File is untracked; press %s to add",
6229 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6232 stage_next(view
, line
);
6236 if (!stage_status
.new.name
[0])
6238 if (stage_status
.status
== 'D') {
6239 report("File has been deleted.");
6243 open_editor(stage_status
.new.name
);
6247 /* Reload everything ... */
6250 case REQ_VIEW_BLAME
:
6251 if (stage_status
.new.name
[0]) {
6252 string_copy(opt_file
, stage_status
.new.name
);
6258 return pager_request(view
, request
, line
);
6264 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6265 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6267 /* Check whether the staged entry still exists, and close the
6268 * stage view if it doesn't. */
6269 if (!status_exists(&stage_status
, stage_line_type
)) {
6270 status_restore(VIEW(REQ_VIEW_STATUS
));
6271 return REQ_VIEW_CLOSE
;
6274 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6275 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6276 report("Cannot display a directory");
6280 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6281 report("Failed to open file: %s", strerror(errno
));
6285 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6290 static struct view_ops stage_ops
= {
6307 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6308 char title
[128]; /* First line of the commit message. */
6309 const char *author
; /* Author of the commit. */
6310 time_t time
; /* Date from the author ident. */
6311 struct ref_list
*refs
; /* Repository references. */
6312 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6313 size_t graph_size
; /* The width of the graph array. */
6314 bool has_parents
; /* Rewritten --parents seen. */
6317 /* Size of rev graph with no "padding" columns */
6318 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6321 struct rev_graph
*prev
, *next
, *parents
;
6322 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6324 struct commit
*commit
;
6326 unsigned int boundary
:1;
6329 /* Parents of the commit being visualized. */
6330 static struct rev_graph graph_parents
[4];
6332 /* The current stack of revisions on the graph. */
6333 static struct rev_graph graph_stacks
[4] = {
6334 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6335 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6336 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6337 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6341 graph_parent_is_merge(struct rev_graph
*graph
)
6343 return graph
->parents
->size
> 1;
6347 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6349 struct commit
*commit
= graph
->commit
;
6351 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6352 commit
->graph
[commit
->graph_size
++] = symbol
;
6356 clear_rev_graph(struct rev_graph
*graph
)
6358 graph
->boundary
= 0;
6359 graph
->size
= graph
->pos
= 0;
6360 graph
->commit
= NULL
;
6361 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6365 done_rev_graph(struct rev_graph
*graph
)
6367 if (graph_parent_is_merge(graph
) &&
6368 graph
->pos
< graph
->size
- 1 &&
6369 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6370 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6372 graph
->commit
->graph_size
= i
* 2;
6373 while (i
< graph
->next
->size
- 1) {
6374 append_to_rev_graph(graph
, ' ');
6375 append_to_rev_graph(graph
, '\\');
6380 clear_rev_graph(graph
);
6384 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6388 /* "Collapse" duplicate parents lines.
6390 * FIXME: This needs to also update update the drawn graph but
6391 * for now it just serves as a method for pruning graph lines. */
6392 for (i
= 0; i
< graph
->size
; i
++)
6393 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6396 if (graph
->size
< SIZEOF_REVITEMS
) {
6397 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6402 get_rev_graph_symbol(struct rev_graph
*graph
)
6406 if (graph
->boundary
)
6407 symbol
= REVGRAPH_BOUND
;
6408 else if (graph
->parents
->size
== 0)
6409 symbol
= REVGRAPH_INIT
;
6410 else if (graph_parent_is_merge(graph
))
6411 symbol
= REVGRAPH_MERGE
;
6412 else if (graph
->pos
>= graph
->size
)
6413 symbol
= REVGRAPH_BRANCH
;
6415 symbol
= REVGRAPH_COMMIT
;
6421 draw_rev_graph(struct rev_graph
*graph
)
6424 chtype separator
, line
;
6426 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6427 static struct rev_filler fillers
[] = {
6433 chtype symbol
= get_rev_graph_symbol(graph
);
6434 struct rev_filler
*filler
;
6437 if (opt_line_graphics
)
6438 fillers
[DEFAULT
].line
= line_graphics
[LINE_GRAPHIC_VLINE
];
6440 filler
= &fillers
[DEFAULT
];
6442 for (i
= 0; i
< graph
->pos
; i
++) {
6443 append_to_rev_graph(graph
, filler
->line
);
6444 if (graph_parent_is_merge(graph
->prev
) &&
6445 graph
->prev
->pos
== i
)
6446 filler
= &fillers
[RSHARP
];
6448 append_to_rev_graph(graph
, filler
->separator
);
6451 /* Place the symbol for this revision. */
6452 append_to_rev_graph(graph
, symbol
);
6454 if (graph
->prev
->size
> graph
->size
)
6455 filler
= &fillers
[RDIAG
];
6457 filler
= &fillers
[DEFAULT
];
6461 for (; i
< graph
->size
; i
++) {
6462 append_to_rev_graph(graph
, filler
->separator
);
6463 append_to_rev_graph(graph
, filler
->line
);
6464 if (graph_parent_is_merge(graph
->prev
) &&
6465 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6466 filler
= &fillers
[RSHARP
];
6467 if (graph
->prev
->size
> graph
->size
)
6468 filler
= &fillers
[LDIAG
];
6471 if (graph
->prev
->size
> graph
->size
) {
6472 append_to_rev_graph(graph
, filler
->separator
);
6473 if (filler
->line
!= ' ')
6474 append_to_rev_graph(graph
, filler
->line
);
6478 /* Prepare the next rev graph */
6480 prepare_rev_graph(struct rev_graph
*graph
)
6484 /* First, traverse all lines of revisions up to the active one. */
6485 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6486 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6489 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6492 /* Interleave the new revision parent(s). */
6493 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6494 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6496 /* Lastly, put any remaining revisions. */
6497 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6498 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6502 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6504 /* If this is the finalizing update ... */
6506 prepare_rev_graph(graph
);
6508 /* Graph visualization needs a one rev look-ahead,
6509 * so the first update doesn't visualize anything. */
6510 if (!graph
->prev
->commit
)
6513 if (view
->lines
> 2)
6514 view
->line
[view
->lines
- 3].dirty
= 1;
6515 if (view
->lines
> 1)
6516 view
->line
[view
->lines
- 2].dirty
= 1;
6517 draw_rev_graph(graph
->prev
);
6518 done_rev_graph(graph
->prev
->prev
);
6526 static const char *main_argv
[SIZEOF_ARG
] = {
6527 "git", "log", "--no-color", "--pretty=raw", "--parents",
6528 "--topo-order", "%(head)", NULL
6532 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6534 struct commit
*commit
= line
->data
;
6536 if (!commit
->author
)
6539 if (opt_date
&& draw_date(view
, &commit
->time
))
6542 if (opt_author
&& draw_author(view
, commit
->author
))
6545 if (opt_rev_graph
&& commit
->graph_size
&&
6546 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6549 if (opt_show_refs
&& commit
->refs
) {
6552 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6553 struct ref
*ref
= commit
->refs
->refs
[i
];
6554 enum line_type type
;
6557 type
= LINE_MAIN_HEAD
;
6559 type
= LINE_MAIN_LOCAL_TAG
;
6561 type
= LINE_MAIN_TAG
;
6562 else if (ref
->tracked
)
6563 type
= LINE_MAIN_TRACKED
;
6564 else if (ref
->remote
)
6565 type
= LINE_MAIN_REMOTE
;
6567 type
= LINE_MAIN_REF
;
6569 if (draw_text(view
, type
, "[", TRUE
) ||
6570 draw_text(view
, type
, ref
->name
, TRUE
) ||
6571 draw_text(view
, type
, "]", TRUE
))
6574 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6579 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6583 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6585 main_read(struct view
*view
, char *line
)
6587 static struct rev_graph
*graph
= graph_stacks
;
6588 enum line_type type
;
6589 struct commit
*commit
;
6594 if (!view
->lines
&& !view
->parent
)
6595 die("No revisions match the given arguments.");
6596 if (view
->lines
> 0) {
6597 commit
= view
->line
[view
->lines
- 1].data
;
6598 view
->line
[view
->lines
- 1].dirty
= 1;
6599 if (!commit
->author
) {
6602 graph
->commit
= NULL
;
6605 update_rev_graph(view
, graph
);
6607 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6608 clear_rev_graph(&graph_stacks
[i
]);
6612 type
= get_line_type(line
);
6613 if (type
== LINE_COMMIT
) {
6614 commit
= calloc(1, sizeof(struct commit
));
6618 line
+= STRING_SIZE("commit ");
6620 graph
->boundary
= 1;
6624 string_copy_rev(commit
->id
, line
);
6625 commit
->refs
= get_ref_list(commit
->id
);
6626 graph
->commit
= commit
;
6627 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6629 while ((line
= strchr(line
, ' '))) {
6631 push_rev_graph(graph
->parents
, line
);
6632 commit
->has_parents
= TRUE
;
6639 commit
= view
->line
[view
->lines
- 1].data
;
6643 if (commit
->has_parents
)
6645 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6649 parse_author_line(line
+ STRING_SIZE("author "),
6650 &commit
->author
, &commit
->time
);
6651 update_rev_graph(view
, graph
);
6652 graph
= graph
->next
;
6656 /* Fill in the commit title if it has not already been set. */
6657 if (commit
->title
[0])
6660 /* Require titles to start with a non-space character at the
6661 * offset used by git log. */
6662 if (strncmp(line
, " ", 4))
6665 /* Well, if the title starts with a whitespace character,
6666 * try to be forgiving. Otherwise we end up with no title. */
6667 while (isspace(*line
))
6671 /* FIXME: More graceful handling of titles; append "..." to
6672 * shortened titles, etc. */
6674 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6675 view
->line
[view
->lines
- 1].dirty
= 1;
6682 main_request(struct view
*view
, enum request request
, struct line
*line
)
6684 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
6688 open_view(view
, REQ_VIEW_DIFF
, flags
);
6692 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6702 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6707 if (!opt_show_refs
|| !list
)
6710 for (i
= 0; i
< list
->size
; i
++) {
6711 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6719 main_grep(struct view
*view
, struct line
*line
)
6721 struct commit
*commit
= line
->data
;
6722 const char *text
[] = {
6724 opt_author
? commit
->author
: "",
6725 opt_date
? mkdate(&commit
->time
) : "",
6729 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6733 main_select(struct view
*view
, struct line
*line
)
6735 struct commit
*commit
= line
->data
;
6737 string_copy_rev(view
->ref
, commit
->id
);
6738 string_copy_rev(ref_commit
, view
->ref
);
6741 static struct view_ops main_ops
= {
6754 * Unicode / UTF-8 handling
6756 * NOTE: Much of the following code for dealing with Unicode is derived from
6757 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6758 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6762 unicode_width(unsigned long c
)
6765 (c
<= 0x115f /* Hangul Jamo */
6768 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
6770 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
6771 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
6772 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
6773 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
6774 || (c
>= 0xffe0 && c
<= 0xffe6)
6775 || (c
>= 0x20000 && c
<= 0x2fffd)
6776 || (c
>= 0x30000 && c
<= 0x3fffd)))
6780 return opt_tab_size
;
6785 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6786 * Illegal bytes are set one. */
6787 static const unsigned char utf8_bytes
[256] = {
6788 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,
6789 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,
6790 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,
6791 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,
6792 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,
6793 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,
6794 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,
6795 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,
6798 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6799 static inline unsigned long
6800 utf8_to_unicode(const char *string
, size_t length
)
6802 unsigned long unicode
;
6806 unicode
= string
[0];
6809 unicode
= (string
[0] & 0x1f) << 6;
6810 unicode
+= (string
[1] & 0x3f);
6813 unicode
= (string
[0] & 0x0f) << 12;
6814 unicode
+= ((string
[1] & 0x3f) << 6);
6815 unicode
+= (string
[2] & 0x3f);
6818 unicode
= (string
[0] & 0x0f) << 18;
6819 unicode
+= ((string
[1] & 0x3f) << 12);
6820 unicode
+= ((string
[2] & 0x3f) << 6);
6821 unicode
+= (string
[3] & 0x3f);
6824 unicode
= (string
[0] & 0x0f) << 24;
6825 unicode
+= ((string
[1] & 0x3f) << 18);
6826 unicode
+= ((string
[2] & 0x3f) << 12);
6827 unicode
+= ((string
[3] & 0x3f) << 6);
6828 unicode
+= (string
[4] & 0x3f);
6831 unicode
= (string
[0] & 0x01) << 30;
6832 unicode
+= ((string
[1] & 0x3f) << 24);
6833 unicode
+= ((string
[2] & 0x3f) << 18);
6834 unicode
+= ((string
[3] & 0x3f) << 12);
6835 unicode
+= ((string
[4] & 0x3f) << 6);
6836 unicode
+= (string
[5] & 0x3f);
6839 die("Invalid Unicode length");
6842 /* Invalid characters could return the special 0xfffd value but NUL
6843 * should be just as good. */
6844 return unicode
> 0xffff ? 0 : unicode
;
6847 /* Calculates how much of string can be shown within the given maximum width
6848 * and sets trimmed parameter to non-zero value if all of string could not be
6849 * shown. If the reserve flag is TRUE, it will reserve at least one
6850 * trailing character, which can be useful when drawing a delimiter.
6852 * Returns the number of bytes to output from string to satisfy max_width. */
6854 utf8_length(const char **start
, size_t skip
, int *width
, size_t max_width
, int *trimmed
, bool reserve
)
6856 const char *string
= *start
;
6857 const char *end
= strchr(string
, '\0');
6858 unsigned char last_bytes
= 0;
6859 size_t last_ucwidth
= 0;
6864 while (string
< end
) {
6865 int c
= *(unsigned char *) string
;
6866 unsigned char bytes
= utf8_bytes
[c
];
6868 unsigned long unicode
;
6870 if (string
+ bytes
> end
)
6873 /* Change representation to figure out whether
6874 * it is a single- or double-width character. */
6876 unicode
= utf8_to_unicode(string
, bytes
);
6877 /* FIXME: Graceful handling of invalid Unicode character. */
6881 ucwidth
= unicode_width(unicode
);
6883 skip
-= ucwidth
<= skip
? ucwidth
: skip
;
6887 if (*width
> max_width
) {
6890 if (reserve
&& *width
== max_width
) {
6891 string
-= last_bytes
;
6892 *width
-= last_ucwidth
;
6898 last_bytes
= ucwidth
? bytes
: 0;
6899 last_ucwidth
= ucwidth
;
6902 return string
- *start
;
6910 /* Whether or not the curses interface has been initialized. */
6911 static bool cursed
= FALSE
;
6913 /* Terminal hacks and workarounds. */
6914 static bool use_scroll_redrawwin
;
6915 static bool use_scroll_status_wclear
;
6917 /* The status window is used for polling keystrokes. */
6918 static WINDOW
*status_win
;
6920 /* Reading from the prompt? */
6921 static bool input_mode
= FALSE
;
6923 static bool status_empty
= FALSE
;
6925 /* Update status and title window. */
6927 report(const char *msg
, ...)
6929 struct view
*view
= display
[current_view
];
6935 char buf
[SIZEOF_STR
];
6938 va_start(args
, msg
);
6939 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6940 buf
[sizeof(buf
) - 1] = 0;
6941 buf
[sizeof(buf
) - 2] = '.';
6942 buf
[sizeof(buf
) - 3] = '.';
6943 buf
[sizeof(buf
) - 4] = '.';
6949 if (!status_empty
|| *msg
) {
6952 va_start(args
, msg
);
6954 wmove(status_win
, 0, 0);
6955 if (view
->has_scrolled
&& use_scroll_status_wclear
)
6958 vwprintw(status_win
, msg
, args
);
6959 status_empty
= FALSE
;
6961 status_empty
= TRUE
;
6963 wclrtoeol(status_win
);
6964 wnoutrefresh(status_win
);
6969 update_view_title(view
);
6972 /* Controls when nodelay should be in effect when polling user input. */
6974 set_nonblocking_input(bool loading
)
6976 static unsigned int loading_views
;
6978 if ((loading
== FALSE
&& loading_views
-- == 1) ||
6979 (loading
== TRUE
&& loading_views
++ == 0))
6980 nodelay(status_win
, loading
);
6989 /* Initialize the curses library */
6990 if (isatty(STDIN_FILENO
)) {
6991 cursed
= !!initscr();
6994 /* Leave stdin and stdout alone when acting as a pager. */
6995 opt_tty
= fopen("/dev/tty", "r+");
6997 die("Failed to open /dev/tty");
6998 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
7002 die("Failed to initialize curses");
7004 nonl(); /* Disable conversion and detect newlines from input. */
7005 cbreak(); /* Take input chars one at a time, no wait for \n */
7006 noecho(); /* Don't echo input */
7007 leaveok(stdscr
, FALSE
);
7012 getmaxyx(stdscr
, y
, x
);
7013 status_win
= newwin(1, 0, y
- 1, 0);
7015 die("Failed to create status window");
7017 /* Enable keyboard mapping */
7018 keypad(status_win
, TRUE
);
7019 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
7021 TABSIZE
= opt_tab_size
;
7022 if (opt_line_graphics
) {
7023 line_graphics
[LINE_GRAPHIC_VLINE
] = ACS_VLINE
;
7026 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
7027 if (term
&& !strcmp(term
, "gnome-terminal")) {
7028 /* In the gnome-terminal-emulator, the message from
7029 * scrolling up one line when impossible followed by
7030 * scrolling down one line causes corruption of the
7031 * status line. This is fixed by calling wclear. */
7032 use_scroll_status_wclear
= TRUE
;
7033 use_scroll_redrawwin
= FALSE
;
7035 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
7036 /* No problems with full optimizations in xrvt-(unicode)
7038 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
7041 /* When scrolling in (u)xterm the last line in the
7042 * scrolling direction will update slowly. */
7043 use_scroll_redrawwin
= TRUE
;
7044 use_scroll_status_wclear
= FALSE
;
7049 get_input(int prompt_position
)
7052 int i
, key
, cursor_y
, cursor_x
;
7054 if (prompt_position
)
7058 foreach_view (view
, i
) {
7060 if (view_is_displayed(view
) && view
->has_scrolled
&&
7061 use_scroll_redrawwin
)
7062 redrawwin(view
->win
);
7063 view
->has_scrolled
= FALSE
;
7066 /* Update the cursor position. */
7067 if (prompt_position
) {
7068 getbegyx(status_win
, cursor_y
, cursor_x
);
7069 cursor_x
= prompt_position
;
7071 view
= display
[current_view
];
7072 getbegyx(view
->win
, cursor_y
, cursor_x
);
7073 cursor_x
= view
->width
- 1;
7074 cursor_y
+= view
->lineno
- view
->offset
;
7076 setsyx(cursor_y
, cursor_x
);
7078 /* Refresh, accept single keystroke of input */
7080 key
= wgetch(status_win
);
7082 /* wgetch() with nodelay() enabled returns ERR when
7083 * there's no input. */
7086 } else if (key
== KEY_RESIZE
) {
7089 getmaxyx(stdscr
, height
, width
);
7091 wresize(status_win
, 1, width
);
7092 mvwin(status_win
, height
- 1, 0);
7093 wnoutrefresh(status_win
);
7095 redraw_display(TRUE
);
7105 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7107 enum input_status status
= INPUT_OK
;
7108 static char buf
[SIZEOF_STR
];
7113 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7116 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7117 wclrtoeol(status_win
);
7119 key
= get_input(pos
+ 1);
7124 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7131 status
= INPUT_CANCEL
;
7135 status
= INPUT_CANCEL
;
7139 if (pos
>= sizeof(buf
)) {
7140 report("Input string too long");
7144 status
= handler(data
, buf
, key
);
7145 if (status
== INPUT_OK
)
7146 buf
[pos
++] = (char) key
;
7150 /* Clear the status window */
7151 status_empty
= FALSE
;
7154 if (status
== INPUT_CANCEL
)
7162 static enum input_status
7163 prompt_yesno_handler(void *data
, char *buf
, int c
)
7165 if (c
== 'y' || c
== 'Y')
7167 if (c
== 'n' || c
== 'N')
7168 return INPUT_CANCEL
;
7173 prompt_yesno(const char *prompt
)
7175 char prompt2
[SIZEOF_STR
];
7177 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7180 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7183 static enum input_status
7184 read_prompt_handler(void *data
, char *buf
, int c
)
7186 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7190 read_prompt(const char *prompt
)
7192 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7195 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7197 enum input_status status
= INPUT_OK
;
7200 while (items
[size
].text
)
7203 while (status
== INPUT_OK
) {
7204 const struct menu_item
*item
= &items
[*selected
];
7208 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7209 prompt
, *selected
+ 1, size
);
7211 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7212 wprintw(status_win
, "%s", item
->text
);
7213 wclrtoeol(status_win
);
7215 key
= get_input(COLS
- 1);
7220 status
= INPUT_STOP
;
7225 *selected
= *selected
- 1;
7227 *selected
= size
- 1;
7232 *selected
= (*selected
+ 1) % size
;
7236 status
= INPUT_CANCEL
;
7240 for (i
= 0; items
[i
].text
; i
++)
7241 if (items
[i
].hotkey
== key
) {
7243 status
= INPUT_STOP
;
7249 /* Clear the status window */
7250 status_empty
= FALSE
;
7253 return status
!= INPUT_CANCEL
;
7257 * Repository properties
7260 static struct ref
**refs
= NULL
;
7261 static size_t refs_size
= 0;
7263 static struct ref_list
**ref_lists
= NULL
;
7264 static size_t ref_lists_size
= 0;
7266 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7267 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7268 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7271 compare_refs(const void *ref1_
, const void *ref2_
)
7273 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7274 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7276 if (ref1
->tag
!= ref2
->tag
)
7277 return ref2
->tag
- ref1
->tag
;
7278 if (ref1
->ltag
!= ref2
->ltag
)
7279 return ref2
->ltag
- ref2
->ltag
;
7280 if (ref1
->head
!= ref2
->head
)
7281 return ref2
->head
- ref1
->head
;
7282 if (ref1
->tracked
!= ref2
->tracked
)
7283 return ref2
->tracked
- ref1
->tracked
;
7284 if (ref1
->remote
!= ref2
->remote
)
7285 return ref2
->remote
- ref1
->remote
;
7286 return strcmp(ref1
->name
, ref2
->name
);
7290 foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
)
7294 for (i
= 0; i
< refs_size
; i
++)
7295 if (!visitor(data
, refs
[i
]))
7299 static struct ref_list
*
7300 get_ref_list(const char *id
)
7302 struct ref_list
*list
;
7305 for (i
= 0; i
< ref_lists_size
; i
++)
7306 if (!strcmp(id
, ref_lists
[i
]->id
))
7307 return ref_lists
[i
];
7309 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7311 list
= calloc(1, sizeof(*list
));
7315 for (i
= 0; i
< refs_size
; i
++) {
7316 if (!strcmp(id
, refs
[i
]->id
) &&
7317 realloc_refs_list(&list
->refs
, list
->size
, 1))
7318 list
->refs
[list
->size
++] = refs
[i
];
7326 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7327 ref_lists
[ref_lists_size
++] = list
;
7332 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
7334 struct ref
*ref
= NULL
;
7337 bool remote
= FALSE
;
7338 bool tracked
= FALSE
;
7340 int from
= 0, to
= refs_size
- 1;
7342 if (!prefixcmp(name
, "refs/tags/")) {
7343 if (!suffixcmp(name
, namelen
, "^{}")) {
7351 namelen
-= STRING_SIZE("refs/tags/");
7352 name
+= STRING_SIZE("refs/tags/");
7354 } else if (!prefixcmp(name
, "refs/remotes/")) {
7356 namelen
-= STRING_SIZE("refs/remotes/");
7357 name
+= STRING_SIZE("refs/remotes/");
7358 tracked
= !strcmp(opt_remote
, name
);
7360 } else if (!prefixcmp(name
, "refs/heads/")) {
7361 namelen
-= STRING_SIZE("refs/heads/");
7362 name
+= STRING_SIZE("refs/heads/");
7363 head
= !strncmp(opt_head
, name
, namelen
);
7365 } else if (!strcmp(name
, "HEAD")) {
7366 string_ncopy(opt_head_rev
, id
, idlen
);
7370 /* If we are reloading or it's an annotated tag, replace the
7371 * previous SHA1 with the resolved commit id; relies on the fact
7372 * git-ls-remote lists the commit id of an annotated tag right
7373 * before the commit id it points to. */
7374 while (from
<= to
) {
7375 size_t pos
= (to
+ from
) / 2;
7376 int cmp
= strcmp(name
, refs
[pos
]->name
);
7390 if (!realloc_refs(&refs
, refs_size
, 1))
7392 ref
= calloc(1, sizeof(*ref
) + namelen
);
7395 memmove(refs
+ from
+ 1, refs
+ from
,
7396 (refs_size
- from
) * sizeof(*refs
));
7398 strncpy(ref
->name
, name
, namelen
);
7405 ref
->remote
= remote
;
7406 ref
->tracked
= tracked
;
7407 string_copy_rev(ref
->id
, id
);
7415 const char *head_argv
[] = {
7416 "git", "symbolic-ref", "HEAD", NULL
7418 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7419 "git", "ls-remote", opt_git_dir
, NULL
7421 static bool init
= FALSE
;
7425 argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE");
7432 if (run_io_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7433 !prefixcmp(opt_head
, "refs/heads/")) {
7434 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7436 memmove(opt_head
, offset
, strlen(offset
) + 1);
7439 for (i
= 0; i
< refs_size
; i
++)
7442 if (run_io_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7445 /* Update the ref lists to reflect changes. */
7446 for (i
= 0; i
< ref_lists_size
; i
++) {
7447 struct ref_list
*list
= ref_lists
[i
];
7450 for (old
= new = 0; old
< list
->size
; old
++)
7451 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7452 list
->refs
[new++] = list
->refs
[old
];
7460 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7462 if (!strcmp(name
, ".remote")) {
7463 string_ncopy(opt_remote
, value
, valuelen
);
7465 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7466 size_t from
= strlen(opt_remote
);
7468 if (!prefixcmp(value
, "refs/heads/"))
7469 value
+= STRING_SIZE("refs/heads/");
7471 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7477 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7479 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7480 int argc
= 1 + (cmd
== option_set_command
);
7483 if (!argv_from_string(argv
, &argc
, value
))
7484 config_msg
= "Too many option arguments";
7486 error
= cmd(argc
, argv
);
7489 warn("Option 'tig.%s': %s", name
, config_msg
);
7493 set_environment_variable(const char *name
, const char *value
)
7495 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7496 char *env
= malloc(len
);
7499 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7507 set_work_tree(const char *value
)
7509 char cwd
[SIZEOF_STR
];
7511 if (!getcwd(cwd
, sizeof(cwd
)))
7512 die("Failed to get cwd path: %s", strerror(errno
));
7513 if (chdir(opt_git_dir
) < 0)
7514 die("Failed to chdir(%s): %s", strerror(errno
));
7515 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7516 die("Failed to get git path: %s", strerror(errno
));
7518 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7519 if (chdir(value
) < 0)
7520 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7521 if (!getcwd(cwd
, sizeof(cwd
)))
7522 die("Failed to get cwd path: %s", strerror(errno
));
7523 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7524 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7525 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7526 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7527 opt_is_inside_work_tree
= TRUE
;
7531 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7533 if (!strcmp(name
, "i18n.commitencoding"))
7534 string_ncopy(opt_encoding
, value
, valuelen
);
7536 else if (!strcmp(name
, "core.editor"))
7537 string_ncopy(opt_editor
, value
, valuelen
);
7539 else if (!strcmp(name
, "core.worktree"))
7540 set_work_tree(value
);
7542 else if (!prefixcmp(name
, "tig.color."))
7543 set_repo_config_option(name
+ 10, value
, option_color_command
);
7545 else if (!prefixcmp(name
, "tig.bind."))
7546 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7548 else if (!prefixcmp(name
, "tig."))
7549 set_repo_config_option(name
+ 4, value
, option_set_command
);
7551 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7552 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7553 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7559 load_git_config(void)
7561 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7563 return run_io_load(config_list_argv
, "=", read_repo_config_option
);
7567 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7569 if (!opt_git_dir
[0]) {
7570 string_ncopy(opt_git_dir
, name
, namelen
);
7572 } else if (opt_is_inside_work_tree
== -1) {
7573 /* This can be 3 different values depending on the
7574 * version of git being used. If git-rev-parse does not
7575 * understand --is-inside-work-tree it will simply echo
7576 * the option else either "true" or "false" is printed.
7577 * Default to true for the unknown case. */
7578 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7580 } else if (*name
== '.') {
7581 string_ncopy(opt_cdup
, name
, namelen
);
7584 string_ncopy(opt_prefix
, name
, namelen
);
7591 load_repo_info(void)
7593 const char *rev_parse_argv
[] = {
7594 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7595 "--show-cdup", "--show-prefix", NULL
7598 return run_io_load(rev_parse_argv
, "=", read_repo_info
);
7606 static const char usage
[] =
7607 "tig " TIG_VERSION
" (" __DATE__
")\n"
7609 "Usage: tig [options] [revs] [--] [paths]\n"
7610 " or: tig show [options] [revs] [--] [paths]\n"
7611 " or: tig blame [rev] path\n"
7613 " or: tig < [git command output]\n"
7616 " -v, --version Show version and exit\n"
7617 " -h, --help Show help message and exit";
7619 static void __NORETURN
7622 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7628 static void __NORETURN
7629 die(const char *err
, ...)
7635 va_start(args
, err
);
7636 fputs("tig: ", stderr
);
7637 vfprintf(stderr
, err
, args
);
7638 fputs("\n", stderr
);
7645 warn(const char *msg
, ...)
7649 va_start(args
, msg
);
7650 fputs("tig warning: ", stderr
);
7651 vfprintf(stderr
, msg
, args
);
7652 fputs("\n", stderr
);
7657 parse_options(int argc
, const char *argv
[])
7659 enum request request
= REQ_VIEW_MAIN
;
7660 const char *subcommand
;
7661 bool seen_dashdash
= FALSE
;
7662 /* XXX: This is vulnerable to the user overriding options
7663 * required for the main view parser. */
7664 const char *custom_argv
[SIZEOF_ARG
] = {
7665 "git", "log", "--no-color", "--pretty=raw", "--parents",
7666 "--topo-order", NULL
7670 if (!isatty(STDIN_FILENO
)) {
7671 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7672 return REQ_VIEW_PAGER
;
7678 subcommand
= argv
[1];
7679 if (!strcmp(subcommand
, "status")) {
7681 warn("ignoring arguments after `%s'", subcommand
);
7682 return REQ_VIEW_STATUS
;
7684 } else if (!strcmp(subcommand
, "blame")) {
7685 if (argc
<= 2 || argc
> 4)
7686 die("invalid number of options to blame\n\n%s", usage
);
7690 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7694 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7695 return REQ_VIEW_BLAME
;
7697 } else if (!strcmp(subcommand
, "show")) {
7698 request
= REQ_VIEW_DIFF
;
7705 custom_argv
[1] = subcommand
;
7709 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7710 const char *opt
= argv
[i
];
7712 if (seen_dashdash
|| !strcmp(opt
, "--")) {
7713 seen_dashdash
= TRUE
;
7715 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7716 printf("tig version %s\n", TIG_VERSION
);
7719 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7720 printf("%s\n", usage
);
7724 custom_argv
[j
++] = opt
;
7725 if (j
>= ARRAY_SIZE(custom_argv
))
7726 die("command too long");
7729 if (!prepare_update(VIEW(request
), custom_argv
, NULL
, FORMAT_NONE
))
7730 die("Failed to format arguments");
7736 main(int argc
, const char *argv
[])
7738 enum request request
= parse_options(argc
, argv
);
7742 signal(SIGINT
, quit
);
7743 signal(SIGPIPE
, SIG_IGN
);
7745 if (setlocale(LC_ALL
, "")) {
7746 char *codeset
= nl_langinfo(CODESET
);
7748 string_ncopy(opt_codeset
, codeset
, strlen(codeset
));
7751 if (load_repo_info() == ERR
)
7752 die("Failed to load repo info.");
7754 if (load_options() == ERR
)
7755 die("Failed to load user config.");
7757 if (load_git_config() == ERR
)
7758 die("Failed to load repo config.");
7760 /* Require a git repository unless when running in pager mode. */
7761 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7762 die("Not a git repository");
7764 if (*opt_encoding
&& strcmp(opt_codeset
, "UTF-8")) {
7765 opt_iconv_in
= iconv_open("UTF-8", opt_encoding
);
7766 if (opt_iconv_in
== ICONV_NONE
)
7767 die("Failed to initialize character set conversion");
7770 if (*opt_codeset
&& strcmp(opt_codeset
, "UTF-8")) {
7771 opt_iconv_out
= iconv_open(opt_codeset
, "UTF-8");
7772 if (opt_iconv_out
== ICONV_NONE
)
7773 die("Failed to initialize character set conversion");
7776 if (load_refs() == ERR
)
7777 die("Failed to load refs.");
7779 foreach_view (view
, i
)
7780 argv_from_env(view
->ops
->argv
, view
->cmd_env
);
7784 if (request
!= REQ_NONE
)
7785 open_view(NULL
, request
, OPEN_PREPARED
);
7786 request
= request
== REQ_NONE
? REQ_VIEW_MAIN
: REQ_NONE
;
7788 while (view_driver(display
[current_view
], request
)) {
7789 int key
= get_input(0);
7791 view
= display
[current_view
];
7792 request
= get_keybinding(view
->keymap
, key
);
7794 /* Some low-level request handling. This keeps access to
7795 * status_win restricted. */
7799 char *cmd
= read_prompt(":");
7801 if (cmd
&& isdigit(*cmd
)) {
7802 int lineno
= view
->lineno
+ 1;
7804 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7805 select_view_line(view
, lineno
- 1);
7808 report("Unable to parse '%s' as a line number", cmd
);
7812 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7813 const char *argv
[SIZEOF_ARG
] = { "git" };
7816 /* When running random commands, initially show the
7817 * command in the title. However, it maybe later be
7818 * overwritten if a commit line is selected. */
7819 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7821 if (!argv_from_string(argv
, &argc
, cmd
)) {
7822 report("Too many arguments");
7823 } else if (!prepare_update(next
, argv
, NULL
, FORMAT_DASH
)) {
7824 report("Failed to format command");
7826 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7834 case REQ_SEARCH_BACK
:
7836 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7837 char *search
= read_prompt(prompt
);
7840 string_ncopy(opt_search
, search
, strlen(search
));
7841 else if (*opt_search
)
7842 request
= request
== REQ_SEARCH
?