1 /* Copyright (c) 2006-2010 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
);
73 static inline unsigned char utf8_char_length(const char *string
, const char *end
);
75 #define ABS(x) ((x) >= 0 ? (x) : -(x))
76 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define MAX(x, y) ((x) > (y) ? (x) : (y))
79 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
80 #define STRING_SIZE(x) (sizeof(x) - 1)
82 #define SIZEOF_STR 1024 /* Default string size. */
83 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
84 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
85 #define SIZEOF_ARG 32 /* Default argument array size. */
89 #define REVGRAPH_INIT 'I'
90 #define REVGRAPH_MERGE 'M'
91 #define REVGRAPH_BRANCH '+'
92 #define REVGRAPH_COMMIT '*'
93 #define REVGRAPH_BOUND '^'
95 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
97 /* This color name can be used to refer to the default term colors. */
98 #define COLOR_DEFAULT (-1)
100 #define ICONV_NONE ((iconv_t) -1)
102 #define ICONV_CONST /* nothing */
105 /* The format and size of the date column in the main view. */
106 #define DATE_FORMAT "%Y-%m-%d %H:%M"
107 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
108 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
111 #define AUTHOR_COLS 19
113 #define MIN_VIEW_HEIGHT 4
115 #define NULL_ID "0000000000000000000000000000000000000000"
117 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
119 /* Some ASCII-shorthands fitted into the ncurses namespace. */
121 #define KEY_RETURN '\r'
126 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
127 unsigned int head
:1; /* Is it the current HEAD? */
128 unsigned int tag
:1; /* Is it a tag? */
129 unsigned int ltag
:1; /* If so, is the tag local? */
130 unsigned int remote
:1; /* Is it a remote ref? */
131 unsigned int tracked
:1; /* Is it the remote for the current HEAD? */
132 char name
[1]; /* Ref name; tag or head names are shortened. */
136 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
137 size_t size
; /* Number of refs. */
138 struct ref
**refs
; /* References for this ID. */
141 static struct ref_list
*get_ref_list(const char *id
);
142 static void foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
);
143 static int load_refs(void);
146 FORMAT_ALL
, /* Perform replacement in all arguments. */
147 FORMAT_DASH
, /* Perform replacement up until "--". */
148 FORMAT_NONE
/* No replacement should be performed. */
151 static bool format_argv(const char *dst
[], const char *src
[], enum format_flags flags
);
160 typedef enum input_status (*input_handler
)(void *data
, char *buf
, int c
);
162 static char *prompt_input(const char *prompt
, input_handler handler
, void *data
);
163 static bool prompt_yesno(const char *prompt
);
171 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
);
174 * Allocation helpers ... Entering macro hell to never be seen again.
177 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
179 name(type **mem, size_t size, size_t increase) \
181 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
182 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
185 if (mem == NULL || num_chunks != num_chunks_new) { \
186 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
199 string_ncopy_do(char *dst
, size_t dstlen
, const char *src
, size_t srclen
)
201 if (srclen
> dstlen
- 1)
204 strncpy(dst
, src
, srclen
);
208 /* Shorthands for safely copying into a fixed buffer. */
210 #define string_copy(dst, src) \
211 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
213 #define string_ncopy(dst, src, srclen) \
214 string_ncopy_do(dst, sizeof(dst), src, srclen)
216 #define string_copy_rev(dst, src) \
217 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
219 #define string_add(dst, from, src) \
220 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
223 string_expand(char *dst
, size_t dstlen
, const char *src
, int tabsize
)
227 for (size
= pos
= 0; size
< dstlen
- 1 && src
[pos
]; pos
++) {
228 if (src
[pos
] == '\t') {
229 size_t expanded
= tabsize
- (size
% tabsize
);
231 if (expanded
+ size
>= dstlen
- 1)
232 expanded
= dstlen
- size
- 1;
233 memcpy(dst
+ size
, " ", expanded
);
236 dst
[size
++] = src
[pos
];
244 chomp_string(char *name
)
248 while (isspace(*name
))
251 namelen
= strlen(name
) - 1;
252 while (namelen
> 0 && isspace(name
[namelen
]))
259 string_nformat(char *buf
, size_t bufsize
, size_t *bufpos
, const char *fmt
, ...)
262 size_t pos
= bufpos
? *bufpos
: 0;
265 pos
+= vsnprintf(buf
+ pos
, bufsize
- pos
, fmt
, args
);
271 return pos
>= bufsize
? FALSE
: TRUE
;
274 #define string_format(buf, fmt, args...) \
275 string_nformat(buf, sizeof(buf), NULL, fmt, args)
277 #define string_format_from(buf, from, fmt, args...) \
278 string_nformat(buf, sizeof(buf), from, fmt, args)
281 string_enum_compare(const char *str1
, const char *str2
, int len
)
285 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
287 /* Diff-Header == DIFF_HEADER */
288 for (i
= 0; i
< len
; i
++) {
289 if (toupper(str1
[i
]) == toupper(str2
[i
]))
292 if (string_enum_sep(str1
[i
]) &&
293 string_enum_sep(str2
[i
]))
296 return str1
[i
] - str2
[i
];
302 #define enum_equals(entry, str, len) \
303 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
311 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
314 enum_map_name(const char *name
, size_t namelen
)
316 static char buf
[SIZEOF_STR
];
319 for (bufpos
= 0; bufpos
<= namelen
; bufpos
++) {
320 buf
[bufpos
] = tolower(name
[bufpos
]);
321 if (buf
[bufpos
] == '_')
329 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
332 map_enum_do(const struct enum_map
*map
, size_t map_size
, int *value
, const char *name
)
334 size_t namelen
= strlen(name
);
337 for (i
= 0; i
< map_size
; i
++)
338 if (enum_equals(map
[i
], name
, namelen
)) {
339 *value
= map
[i
].value
;
346 #define map_enum(attr, map, name) \
347 map_enum_do(map, ARRAY_SIZE(map), attr, name)
349 #define prefixcmp(str1, str2) \
350 strncmp(str1, str2, STRING_SIZE(str2))
353 suffixcmp(const char *str
, int slen
, const char *suffix
)
355 size_t len
= slen
>= 0 ? slen
: strlen(str
);
356 size_t suffixlen
= strlen(suffix
);
358 return suffixlen
< len
? strcmp(str
+ len
- suffixlen
, suffix
) : -1;
369 #define DATE_(name) DATE_##name
374 static const struct enum_map date_map
[] = {
375 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
385 static inline int timecmp(const struct time
*t1
, const struct time
*t2
)
387 return t1
->sec
- t2
->sec
;
391 string_date(const struct time
*time
, enum date date
)
393 static char buf
[DATE_COLS
+ 1];
394 static const struct enum_map reldate
[] = {
395 { "second", 1, 60 * 2 },
396 { "minute", 60, 60 * 60 * 2 },
397 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
398 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
399 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
400 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
404 if (date
== DATE_RELATIVE
) {
406 time_t date
= time
->sec
+ time
->tz
;
410 gettimeofday(&now
, NULL
);
411 seconds
= now
.tv_sec
< date
? date
- now
.tv_sec
: now
.tv_sec
- date
;
412 for (i
= 0; i
< ARRAY_SIZE(reldate
); i
++) {
413 if (seconds
>= reldate
[i
].value
)
416 seconds
/= reldate
[i
].namelen
;
417 if (!string_format(buf
, "%ld %s%s %s",
418 seconds
, reldate
[i
].name
,
419 seconds
> 1 ? "s" : "",
420 now
.tv_sec
>= date
? "ago" : "ahead"))
426 gmtime_r(&time
->sec
, &tm
);
427 return strftime(buf
, sizeof(buf
), DATE_FORMAT
, &tm
) ? buf
: NULL
;
431 #define AUTHOR_VALUES \
437 #define AUTHOR_(name) AUTHOR_##name
440 AUTHOR_DEFAULT
= AUTHOR_FULL
443 static const struct enum_map author_map
[] = {
444 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
450 get_author_initials(const char *author
)
452 static char initials
[AUTHOR_COLS
* 6 + 1];
454 const char *end
= strchr(author
, '\0');
456 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
458 memset(initials
, 0, sizeof(initials
));
459 while (author
< end
) {
463 while (is_initial_sep(*author
))
466 bytes
= utf8_char_length(author
, end
);
467 if (bytes
< sizeof(initials
) - 1 - pos
) {
469 initials
[pos
++] = *author
++;
473 for (i
= pos
; author
< end
&& !is_initial_sep(*author
); author
++) {
474 if (i
< sizeof(initials
) - 1)
475 initials
[i
++] = *author
;
486 argv_from_string(const char *argv
[SIZEOF_ARG
], int *argc
, char *cmd
)
490 while (*cmd
&& *argc
< SIZEOF_ARG
&& (valuelen
= strcspn(cmd
, " \t"))) {
491 bool advance
= cmd
[valuelen
] != 0;
494 argv
[(*argc
)++] = chomp_string(cmd
);
495 cmd
= chomp_string(cmd
+ valuelen
+ advance
);
498 if (*argc
< SIZEOF_ARG
)
500 return *argc
< SIZEOF_ARG
;
504 argv_from_env(const char **argv
, const char *name
)
506 char *env
= argv
? getenv(name
) : NULL
;
511 if (env
&& !argv_from_string(argv
, &argc
, env
))
512 die("Too many arguments in the `%s` environment variable", name
);
517 * Executing external commands.
521 IO_FD
, /* File descriptor based IO. */
522 IO_BG
, /* Execute command in the background. */
523 IO_FG
, /* Execute command with same std{in,out,err}. */
524 IO_RD
, /* Read only fork+exec IO. */
525 IO_WR
, /* Write only fork+exec IO. */
526 IO_AP
, /* Append fork+exec output to file. */
530 enum io_type type
; /* The requested type of pipe. */
531 const char *dir
; /* Directory from which to execute. */
532 pid_t pid
; /* Pipe for reading or writing. */
533 int pipe
; /* Pipe end for reading or writing. */
534 int error
; /* Error status. */
535 const char *argv
[SIZEOF_ARG
]; /* Shell command arguments. */
536 char *buf
; /* Read buffer. */
537 size_t bufalloc
; /* Allocated buffer size. */
538 size_t bufsize
; /* Buffer content size. */
539 char *bufpos
; /* Current buffer position. */
540 unsigned int eof
:1; /* Has end of file been reached. */
544 reset_io(struct io
*io
)
548 io
->buf
= io
->bufpos
= NULL
;
549 io
->bufalloc
= io
->bufsize
= 0;
555 init_io(struct io
*io
, const char *dir
, enum io_type type
)
563 init_io_rd(struct io
*io
, const char *argv
[], const char *dir
,
564 enum format_flags flags
)
566 init_io(io
, dir
, IO_RD
);
567 return format_argv(io
->argv
, argv
, flags
);
571 io_open(struct io
*io
, const char *fmt
, ...)
573 char name
[SIZEOF_STR
] = "";
577 init_io(io
, NULL
, IO_FD
);
580 fits
= vsnprintf(name
, sizeof(name
), fmt
, args
) < sizeof(name
);
584 io
->error
= ENAMETOOLONG
;
587 io
->pipe
= *name
? open(name
, O_RDONLY
) : STDIN_FILENO
;
590 return io
->pipe
!= -1;
594 kill_io(struct io
*io
)
596 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
600 done_io(struct io
*io
)
611 pid_t waiting
= waitpid(pid
, &status
, 0);
616 report("waitpid failed (%s)", strerror(errno
));
620 return waiting
== pid
&&
621 !WIFSIGNALED(status
) &&
623 !WEXITSTATUS(status
);
630 start_io(struct io
*io
)
632 int pipefds
[2] = { -1, -1 };
634 if (io
->type
== IO_FD
)
637 if ((io
->type
== IO_RD
|| io
->type
== IO_WR
) &&
640 else if (io
->type
== IO_AP
)
641 pipefds
[1] = io
->pipe
;
643 if ((io
->pid
= fork())) {
644 if (pipefds
[!(io
->type
== IO_WR
)] != -1)
645 close(pipefds
[!(io
->type
== IO_WR
)]);
647 io
->pipe
= pipefds
[!!(io
->type
== IO_WR
)];
652 if (io
->type
!= IO_FG
) {
653 int devnull
= open("/dev/null", O_RDWR
);
654 int readfd
= io
->type
== IO_WR
? pipefds
[0] : devnull
;
655 int writefd
= (io
->type
== IO_RD
|| io
->type
== IO_AP
)
656 ? pipefds
[1] : devnull
;
658 dup2(readfd
, STDIN_FILENO
);
659 dup2(writefd
, STDOUT_FILENO
);
660 dup2(devnull
, STDERR_FILENO
);
663 if (pipefds
[0] != -1)
665 if (pipefds
[1] != -1)
669 if (io
->dir
&& *io
->dir
&& chdir(io
->dir
) == -1)
670 die("Failed to change directory: %s", strerror(errno
));
672 execvp(io
->argv
[0], (char *const*) io
->argv
);
673 die("Failed to execute program: %s", strerror(errno
));
676 if (pipefds
[!!(io
->type
== IO_WR
)] != -1)
677 close(pipefds
[!!(io
->type
== IO_WR
)]);
682 run_io(struct io
*io
, const char **argv
, const char *dir
, enum io_type type
)
684 init_io(io
, dir
, type
);
685 if (!format_argv(io
->argv
, argv
, FORMAT_NONE
))
691 run_io_do(struct io
*io
)
693 return start_io(io
) && done_io(io
);
697 run_io_bg(const char **argv
)
701 init_io(&io
, NULL
, IO_BG
);
702 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
704 return run_io_do(&io
);
708 run_io_fg(const char **argv
, const char *dir
)
712 init_io(&io
, dir
, IO_FG
);
713 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
715 return run_io_do(&io
);
719 run_io_append(const char **argv
, enum format_flags flags
, int fd
)
723 init_io(&io
, NULL
, IO_AP
);
725 if (format_argv(io
.argv
, argv
, flags
))
726 return run_io_do(&io
);
732 run_io_rd(struct io
*io
, const char **argv
, const char *dir
, enum format_flags flags
)
734 return init_io_rd(io
, argv
, dir
, flags
) && start_io(io
);
738 io_eof(struct io
*io
)
744 io_error(struct io
*io
)
750 io_strerror(struct io
*io
)
752 return strerror(io
->error
);
756 io_can_read(struct io
*io
)
758 struct timeval tv
= { 0, 500 };
762 FD_SET(io
->pipe
, &fds
);
764 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
768 io_read(struct io
*io
, void *buf
, size_t bufsize
)
771 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
773 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
775 else if (readsize
== -1)
777 else if (readsize
== 0)
783 DEFINE_ALLOCATOR(realloc_io_buf
, char, BUFSIZ
)
786 io_get(struct io
*io
, int c
, bool can_read
)
792 if (io
->bufsize
> 0) {
793 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
795 char *line
= io
->bufpos
;
798 io
->bufpos
= eol
+ 1;
799 io
->bufsize
-= io
->bufpos
- line
;
806 io
->bufpos
[io
->bufsize
] = 0;
816 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
817 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
819 if (io
->bufalloc
== io
->bufsize
) {
820 if (!realloc_io_buf(&io
->buf
, io
->bufalloc
, BUFSIZ
))
822 io
->bufalloc
+= BUFSIZ
;
825 io
->bufpos
= io
->buf
;
826 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
829 io
->bufsize
+= readsize
;
834 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
838 while (!io_error(io
) && written
< bufsize
) {
841 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
842 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
850 return written
== bufsize
;
854 io_read_buf(struct io
*io
, char buf
[], size_t bufsize
)
856 char *result
= io_get(io
, '\n', TRUE
);
859 result
= chomp_string(result
);
860 string_ncopy_do(buf
, bufsize
, result
, strlen(result
));
863 return done_io(io
) && result
;
867 run_io_buf(const char **argv
, char buf
[], size_t bufsize
)
871 return run_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
872 && io_read_buf(&io
, buf
, bufsize
);
876 io_load(struct io
*io
, const char *separators
,
877 int (*read_property
)(char *, size_t, char *, size_t))
885 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
890 name
= chomp_string(name
);
891 namelen
= strcspn(name
, separators
);
895 value
= chomp_string(name
+ namelen
+ 1);
896 valuelen
= strlen(value
);
903 state
= read_property(name
, namelen
, value
, valuelen
);
906 if (state
!= ERR
&& io_error(io
))
914 run_io_load(const char **argv
, const char *separators
,
915 int (*read_property
)(char *, size_t, char *, size_t))
919 return init_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
920 ? io_load(&io
, separators
, read_property
) : ERR
;
929 /* XXX: Keep the view request first and in sync with views[]. */ \
930 REQ_GROUP("View switching") \
931 REQ_(VIEW_MAIN, "Show main view"), \
932 REQ_(VIEW_DIFF, "Show diff view"), \
933 REQ_(VIEW_LOG, "Show log view"), \
934 REQ_(VIEW_TREE, "Show tree view"), \
935 REQ_(VIEW_BLOB, "Show blob view"), \
936 REQ_(VIEW_BLAME, "Show blame view"), \
937 REQ_(VIEW_BRANCH, "Show branch view"), \
938 REQ_(VIEW_HELP, "Show help page"), \
939 REQ_(VIEW_PAGER, "Show pager view"), \
940 REQ_(VIEW_STATUS, "Show status view"), \
941 REQ_(VIEW_STAGE, "Show stage view"), \
943 REQ_GROUP("View manipulation") \
944 REQ_(ENTER, "Enter current line and scroll"), \
945 REQ_(NEXT, "Move to next"), \
946 REQ_(PREVIOUS, "Move to previous"), \
947 REQ_(PARENT, "Move to parent"), \
948 REQ_(VIEW_NEXT, "Move focus to next view"), \
949 REQ_(REFRESH, "Reload and refresh"), \
950 REQ_(MAXIMIZE, "Maximize the current view"), \
951 REQ_(VIEW_CLOSE, "Close the current view"), \
952 REQ_(QUIT, "Close all views and quit"), \
954 REQ_GROUP("View specific requests") \
955 REQ_(STATUS_UPDATE, "Update file status"), \
956 REQ_(STATUS_REVERT, "Revert file changes"), \
957 REQ_(STATUS_MERGE, "Merge file using external tool"), \
958 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
960 REQ_GROUP("Cursor navigation") \
961 REQ_(MOVE_UP, "Move cursor one line up"), \
962 REQ_(MOVE_DOWN, "Move cursor one line down"), \
963 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
964 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
965 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
966 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
968 REQ_GROUP("Scrolling") \
969 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
970 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
971 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
972 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
973 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
974 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
976 REQ_GROUP("Searching") \
977 REQ_(SEARCH, "Search the view"), \
978 REQ_(SEARCH_BACK, "Search backwards in the view"), \
979 REQ_(FIND_NEXT, "Find next search match"), \
980 REQ_(FIND_PREV, "Find previous search match"), \
982 REQ_GROUP("Option manipulation") \
983 REQ_(OPTIONS, "Open option menu"), \
984 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
985 REQ_(TOGGLE_DATE, "Toggle date display"), \
986 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
987 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
988 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
989 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
990 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
991 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
994 REQ_(PROMPT, "Bring up the prompt"), \
995 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
996 REQ_(SHOW_VERSION, "Show version information"), \
997 REQ_(STOP_LOADING, "Stop all loading views"), \
998 REQ_(EDIT, "Open in editor"), \
999 REQ_(NONE, "Do nothing")
1002 /* User action requests. */
1004 #define REQ_GROUP(help)
1005 #define REQ_(req, help) REQ_##req
1007 /* Offset all requests to avoid conflicts with ncurses getch values. */
1008 REQ_OFFSET
= KEY_MAX
+ 1,
1015 struct request_info
{
1016 enum request request
;
1022 static const struct request_info req_info
[] = {
1023 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1024 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1031 get_request(const char *name
)
1033 int namelen
= strlen(name
);
1036 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
1037 if (enum_equals(req_info
[i
], name
, namelen
))
1038 return req_info
[i
].request
;
1048 /* Option and state variables. */
1049 static enum date opt_date
= DATE_DEFAULT
;
1050 static enum author opt_author
= AUTHOR_DEFAULT
;
1051 static bool opt_line_number
= FALSE
;
1052 static bool opt_line_graphics
= TRUE
;
1053 static bool opt_rev_graph
= FALSE
;
1054 static bool opt_show_refs
= TRUE
;
1055 static int opt_num_interval
= 5;
1056 static double opt_hscroll
= 0.50;
1057 static double opt_scale_split_view
= 2.0 / 3.0;
1058 static int opt_tab_size
= 8;
1059 static int opt_author_cols
= AUTHOR_COLS
;
1060 static char opt_path
[SIZEOF_STR
] = "";
1061 static char opt_file
[SIZEOF_STR
] = "";
1062 static char opt_ref
[SIZEOF_REF
] = "";
1063 static char opt_head
[SIZEOF_REF
] = "";
1064 static char opt_head_rev
[SIZEOF_REV
] = "";
1065 static char opt_remote
[SIZEOF_REF
] = "";
1066 static char opt_encoding
[20] = "UTF-8";
1067 static char opt_codeset
[20] = "UTF-8";
1068 static iconv_t opt_iconv_in
= ICONV_NONE
;
1069 static iconv_t opt_iconv_out
= ICONV_NONE
;
1070 static char opt_search
[SIZEOF_STR
] = "";
1071 static char opt_cdup
[SIZEOF_STR
] = "";
1072 static char opt_prefix
[SIZEOF_STR
] = "";
1073 static char opt_git_dir
[SIZEOF_STR
] = "";
1074 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
1075 static char opt_editor
[SIZEOF_STR
] = "";
1076 static FILE *opt_tty
= NULL
;
1078 #define is_initial_commit() (!*opt_head_rev)
1079 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1080 #define mkdate(time) string_date(time, opt_date)
1084 * Line-oriented content detection.
1088 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1089 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1090 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1091 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1092 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1093 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1094 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1095 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1096 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1097 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1098 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1099 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1100 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1101 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1102 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1103 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1104 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1105 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1106 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1107 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1108 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1109 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1110 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1111 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1112 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1113 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1114 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1115 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1116 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1117 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1118 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1119 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1120 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1121 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1122 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1123 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1124 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1125 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1126 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1127 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1128 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1129 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1130 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1131 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1132 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1133 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1134 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1135 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1136 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1137 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1138 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1139 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1140 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1141 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1142 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1143 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1144 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1147 #define LINE(type, line, fg, bg, attr) \
1155 const char *name
; /* Option name. */
1156 int namelen
; /* Size of option name. */
1157 const char *line
; /* The start of line to match. */
1158 int linelen
; /* Size of string to match. */
1159 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1162 static struct line_info line_info
[] = {
1163 #define LINE(type, line, fg, bg, attr) \
1164 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1169 static enum line_type
1170 get_line_type(const char *line
)
1172 int linelen
= strlen(line
);
1173 enum line_type type
;
1175 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1176 /* Case insensitive search matches Signed-off-by lines better. */
1177 if (linelen
>= line_info
[type
].linelen
&&
1178 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1181 return LINE_DEFAULT
;
1185 get_line_attr(enum line_type type
)
1187 assert(type
< ARRAY_SIZE(line_info
));
1188 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1191 static struct line_info
*
1192 get_line_info(const char *name
)
1194 size_t namelen
= strlen(name
);
1195 enum line_type type
;
1197 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1198 if (enum_equals(line_info
[type
], name
, namelen
))
1199 return &line_info
[type
];
1207 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1208 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1209 enum line_type type
;
1213 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1214 default_bg
= COLOR_BLACK
;
1215 default_fg
= COLOR_WHITE
;
1218 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1219 struct line_info
*info
= &line_info
[type
];
1220 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1221 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1223 init_pair(type
, fg
, bg
);
1228 enum line_type type
;
1231 unsigned int selected
:1;
1232 unsigned int dirty
:1;
1233 unsigned int cleareol
:1;
1234 unsigned int other
:16;
1236 void *data
; /* User data */
1246 enum request request
;
1249 static const struct keybinding default_keybindings
[] = {
1250 /* View switching */
1251 { 'm', REQ_VIEW_MAIN
},
1252 { 'd', REQ_VIEW_DIFF
},
1253 { 'l', REQ_VIEW_LOG
},
1254 { 't', REQ_VIEW_TREE
},
1255 { 'f', REQ_VIEW_BLOB
},
1256 { 'B', REQ_VIEW_BLAME
},
1257 { 'H', REQ_VIEW_BRANCH
},
1258 { 'p', REQ_VIEW_PAGER
},
1259 { 'h', REQ_VIEW_HELP
},
1260 { 'S', REQ_VIEW_STATUS
},
1261 { 'c', REQ_VIEW_STAGE
},
1263 /* View manipulation */
1264 { 'q', REQ_VIEW_CLOSE
},
1265 { KEY_TAB
, REQ_VIEW_NEXT
},
1266 { KEY_RETURN
, REQ_ENTER
},
1267 { KEY_UP
, REQ_PREVIOUS
},
1268 { KEY_DOWN
, REQ_NEXT
},
1269 { 'R', REQ_REFRESH
},
1270 { KEY_F(5), REQ_REFRESH
},
1271 { 'O', REQ_MAXIMIZE
},
1273 /* Cursor navigation */
1274 { 'k', REQ_MOVE_UP
},
1275 { 'j', REQ_MOVE_DOWN
},
1276 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1277 { KEY_END
, REQ_MOVE_LAST_LINE
},
1278 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1279 { ' ', REQ_MOVE_PAGE_DOWN
},
1280 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1281 { 'b', REQ_MOVE_PAGE_UP
},
1282 { '-', REQ_MOVE_PAGE_UP
},
1285 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1286 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1287 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1288 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1289 { 'w', REQ_SCROLL_PAGE_UP
},
1290 { 's', REQ_SCROLL_PAGE_DOWN
},
1293 { '/', REQ_SEARCH
},
1294 { '?', REQ_SEARCH_BACK
},
1295 { 'n', REQ_FIND_NEXT
},
1296 { 'N', REQ_FIND_PREV
},
1300 { 'z', REQ_STOP_LOADING
},
1301 { 'v', REQ_SHOW_VERSION
},
1302 { 'r', REQ_SCREEN_REDRAW
},
1303 { 'o', REQ_OPTIONS
},
1304 { '.', REQ_TOGGLE_LINENO
},
1305 { 'D', REQ_TOGGLE_DATE
},
1306 { 'A', REQ_TOGGLE_AUTHOR
},
1307 { 'g', REQ_TOGGLE_REV_GRAPH
},
1308 { 'F', REQ_TOGGLE_REFS
},
1309 { 'I', REQ_TOGGLE_SORT_ORDER
},
1310 { 'i', REQ_TOGGLE_SORT_FIELD
},
1311 { ':', REQ_PROMPT
},
1312 { 'u', REQ_STATUS_UPDATE
},
1313 { '!', REQ_STATUS_REVERT
},
1314 { 'M', REQ_STATUS_MERGE
},
1315 { '@', REQ_STAGE_NEXT
},
1316 { ',', REQ_PARENT
},
1320 #define KEYMAP_INFO \
1335 #define KEYMAP_(name) KEYMAP_##name
1340 static const struct enum_map keymap_table
[] = {
1341 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1346 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1348 struct keybinding_table
{
1349 struct keybinding
*data
;
1353 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1356 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1358 struct keybinding_table
*table
= &keybindings
[keymap
];
1360 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1362 die("Failed to allocate keybinding");
1363 table
->data
[table
->size
].alias
= key
;
1364 table
->data
[table
->size
++].request
= request
;
1367 /* Looks for a key binding first in the given map, then in the generic map, and
1368 * lastly in the default keybindings. */
1370 get_keybinding(enum keymap keymap
, int key
)
1374 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1375 if (keybindings
[keymap
].data
[i
].alias
== key
)
1376 return keybindings
[keymap
].data
[i
].request
;
1378 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1379 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1380 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1382 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1383 if (default_keybindings
[i
].alias
== key
)
1384 return default_keybindings
[i
].request
;
1386 return (enum request
) key
;
1395 static const struct key key_table
[] = {
1396 { "Enter", KEY_RETURN
},
1398 { "Backspace", KEY_BACKSPACE
},
1400 { "Escape", KEY_ESC
},
1401 { "Left", KEY_LEFT
},
1402 { "Right", KEY_RIGHT
},
1404 { "Down", KEY_DOWN
},
1405 { "Insert", KEY_IC
},
1406 { "Delete", KEY_DC
},
1408 { "Home", KEY_HOME
},
1410 { "PageUp", KEY_PPAGE
},
1411 { "PageDown", KEY_NPAGE
},
1421 { "F10", KEY_F(10) },
1422 { "F11", KEY_F(11) },
1423 { "F12", KEY_F(12) },
1427 get_key_value(const char *name
)
1431 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1432 if (!strcasecmp(key_table
[i
].name
, name
))
1433 return key_table
[i
].value
;
1435 if (strlen(name
) == 1 && isprint(*name
))
1442 get_key_name(int key_value
)
1444 static char key_char
[] = "'X'";
1445 const char *seq
= NULL
;
1448 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1449 if (key_table
[key
].value
== key_value
)
1450 seq
= key_table
[key
].name
;
1454 isprint(key_value
)) {
1455 key_char
[1] = (char) key_value
;
1459 return seq
? seq
: "(no key)";
1463 append_key(char *buf
, size_t *pos
, const struct keybinding
*keybinding
)
1465 const char *sep
= *pos
> 0 ? ", " : "";
1466 const char *keyname
= get_key_name(keybinding
->alias
);
1468 return string_nformat(buf
, BUFSIZ
, pos
, "%s%s", sep
, keyname
);
1472 append_keymap_request_keys(char *buf
, size_t *pos
, enum request request
,
1473 enum keymap keymap
, bool all
)
1477 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1478 if (keybindings
[keymap
].data
[i
].request
== request
) {
1479 if (!append_key(buf
, pos
, &keybindings
[keymap
].data
[i
]))
1489 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1492 get_keys(enum keymap keymap
, enum request request
, bool all
)
1494 static char buf
[BUFSIZ
];
1500 if (!append_keymap_request_keys(buf
, &pos
, request
, keymap
, all
))
1501 return "Too many keybindings!";
1502 if (pos
> 0 && !all
)
1505 if (keymap
!= KEYMAP_GENERIC
) {
1506 /* Only the generic keymap includes the default keybindings when
1507 * listing all keys. */
1511 if (!append_keymap_request_keys(buf
, &pos
, request
, KEYMAP_GENERIC
, all
))
1512 return "Too many keybindings!";
1517 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1518 if (default_keybindings
[i
].request
== request
) {
1519 if (!append_key(buf
, &pos
, &default_keybindings
[i
]))
1520 return "Too many keybindings!";
1529 struct run_request
{
1532 const char *argv
[SIZEOF_ARG
];
1535 static struct run_request
*run_request
;
1536 static size_t run_requests
;
1538 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1541 add_run_request(enum keymap keymap
, int key
, int argc
, const char **argv
)
1543 struct run_request
*req
;
1545 if (argc
>= ARRAY_SIZE(req
->argv
) - 1)
1548 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1551 req
= &run_request
[run_requests
];
1552 req
->keymap
= keymap
;
1554 req
->argv
[0] = NULL
;
1556 if (!format_argv(req
->argv
, argv
, FORMAT_NONE
))
1559 return REQ_NONE
+ ++run_requests
;
1562 static struct run_request
*
1563 get_run_request(enum request request
)
1565 if (request
<= REQ_NONE
)
1567 return &run_request
[request
- REQ_NONE
- 1];
1571 add_builtin_run_requests(void)
1573 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1574 const char *commit
[] = { "git", "commit", NULL
};
1575 const char *gc
[] = { "git", "gc", NULL
};
1582 { KEYMAP_MAIN
, 'C', ARRAY_SIZE(cherry_pick
) - 1, cherry_pick
},
1583 { KEYMAP_STATUS
, 'C', ARRAY_SIZE(commit
) - 1, commit
},
1584 { KEYMAP_GENERIC
, 'G', ARRAY_SIZE(gc
) - 1, gc
},
1588 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1591 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argc
, reqs
[i
].argv
);
1592 if (req
!= REQ_NONE
)
1593 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1598 * User config file handling.
1601 static int config_lineno
;
1602 static bool config_errors
;
1603 static const char *config_msg
;
1605 static const struct enum_map color_map
[] = {
1606 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1618 static const struct enum_map attr_map
[] = {
1619 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1626 ATTR_MAP(UNDERLINE
),
1629 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1631 static int parse_step(double *opt
, const char *arg
)
1634 if (!strchr(arg
, '%'))
1637 /* "Shift down" so 100% and 1 does not conflict. */
1638 *opt
= (*opt
- 1) / 100;
1641 config_msg
= "Step value larger than 100%";
1646 config_msg
= "Invalid step value";
1653 parse_int(int *opt
, const char *arg
, int min
, int max
)
1655 int value
= atoi(arg
);
1657 if (min
<= value
&& value
<= max
) {
1662 config_msg
= "Integer value out of bound";
1667 set_color(int *color
, const char *name
)
1669 if (map_enum(color
, color_map
, name
))
1671 if (!prefixcmp(name
, "color"))
1672 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1676 /* Wants: object fgcolor bgcolor [attribute] */
1678 option_color_command(int argc
, const char *argv
[])
1680 struct line_info
*info
;
1683 config_msg
= "Wrong number of arguments given to color command";
1687 info
= get_line_info(argv
[0]);
1689 static const struct enum_map obsolete
[] = {
1690 ENUM_MAP("main-delim", LINE_DELIMITER
),
1691 ENUM_MAP("main-date", LINE_DATE
),
1692 ENUM_MAP("main-author", LINE_AUTHOR
),
1696 if (!map_enum(&index
, obsolete
, argv
[0])) {
1697 config_msg
= "Unknown color name";
1700 info
= &line_info
[index
];
1703 if (!set_color(&info
->fg
, argv
[1]) ||
1704 !set_color(&info
->bg
, argv
[2])) {
1705 config_msg
= "Unknown color";
1710 while (argc
-- > 3) {
1713 if (!set_attribute(&attr
, argv
[argc
])) {
1714 config_msg
= "Unknown attribute";
1723 static int parse_bool(bool *opt
, const char *arg
)
1725 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1730 static int parse_enum_do(unsigned int *opt
, const char *arg
,
1731 const struct enum_map
*map
, size_t map_size
)
1735 assert(map_size
> 1);
1737 if (map_enum_do(map
, map_size
, (int *) opt
, arg
))
1740 if (parse_bool(&is_true
, arg
) != OK
)
1743 *opt
= is_true
? map
[1].value
: map
[0].value
;
1747 #define parse_enum(opt, arg, map) \
1748 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1751 parse_string(char *opt
, const char *arg
, size_t optsize
)
1753 int arglen
= strlen(arg
);
1758 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1759 config_msg
= "Unmatched quotation";
1762 arg
+= 1; arglen
-= 2;
1764 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1769 /* Wants: name = value */
1771 option_set_command(int argc
, const char *argv
[])
1774 config_msg
= "Wrong number of arguments given to set command";
1778 if (strcmp(argv
[1], "=")) {
1779 config_msg
= "No value assigned";
1783 if (!strcmp(argv
[0], "show-author"))
1784 return parse_enum(&opt_author
, argv
[2], author_map
);
1786 if (!strcmp(argv
[0], "show-date"))
1787 return parse_enum(&opt_date
, argv
[2], date_map
);
1789 if (!strcmp(argv
[0], "show-rev-graph"))
1790 return parse_bool(&opt_rev_graph
, argv
[2]);
1792 if (!strcmp(argv
[0], "show-refs"))
1793 return parse_bool(&opt_show_refs
, argv
[2]);
1795 if (!strcmp(argv
[0], "show-line-numbers"))
1796 return parse_bool(&opt_line_number
, argv
[2]);
1798 if (!strcmp(argv
[0], "line-graphics"))
1799 return parse_bool(&opt_line_graphics
, argv
[2]);
1801 if (!strcmp(argv
[0], "line-number-interval"))
1802 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
1804 if (!strcmp(argv
[0], "author-width"))
1805 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
1807 if (!strcmp(argv
[0], "horizontal-scroll"))
1808 return parse_step(&opt_hscroll
, argv
[2]);
1810 if (!strcmp(argv
[0], "split-view-height"))
1811 return parse_step(&opt_scale_split_view
, argv
[2]);
1813 if (!strcmp(argv
[0], "tab-size"))
1814 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
1816 if (!strcmp(argv
[0], "commit-encoding"))
1817 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
1819 config_msg
= "Unknown variable name";
1823 /* Wants: mode request key */
1825 option_bind_command(int argc
, const char *argv
[])
1827 enum request request
;
1832 config_msg
= "Wrong number of arguments given to bind command";
1836 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
1837 config_msg
= "Unknown key map";
1841 key
= get_key_value(argv
[1]);
1843 config_msg
= "Unknown key";
1847 request
= get_request(argv
[2]);
1848 if (request
== REQ_NONE
) {
1849 static const struct enum_map obsolete
[] = {
1850 ENUM_MAP("cherry-pick", REQ_NONE
),
1851 ENUM_MAP("screen-resize", REQ_NONE
),
1852 ENUM_MAP("tree-parent", REQ_PARENT
),
1856 if (map_enum(&alias
, obsolete
, argv
[2])) {
1857 if (alias
!= REQ_NONE
)
1858 add_keybinding(keymap
, alias
, key
);
1859 config_msg
= "Obsolete request name";
1863 if (request
== REQ_NONE
&& *argv
[2]++ == '!')
1864 request
= add_run_request(keymap
, key
, argc
- 2, argv
+ 2);
1865 if (request
== REQ_NONE
) {
1866 config_msg
= "Unknown request name";
1870 add_keybinding(keymap
, request
, key
);
1876 set_option(const char *opt
, char *value
)
1878 const char *argv
[SIZEOF_ARG
];
1881 if (!argv_from_string(argv
, &argc
, value
)) {
1882 config_msg
= "Too many option arguments";
1886 if (!strcmp(opt
, "color"))
1887 return option_color_command(argc
, argv
);
1889 if (!strcmp(opt
, "set"))
1890 return option_set_command(argc
, argv
);
1892 if (!strcmp(opt
, "bind"))
1893 return option_bind_command(argc
, argv
);
1895 config_msg
= "Unknown option command";
1900 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
1905 config_msg
= "Internal error";
1907 /* Check for comment markers, since read_properties() will
1908 * only ensure opt and value are split at first " \t". */
1909 optlen
= strcspn(opt
, "#");
1913 if (opt
[optlen
] != 0) {
1914 config_msg
= "No option value";
1918 /* Look for comment endings in the value. */
1919 size_t len
= strcspn(value
, "#");
1921 if (len
< valuelen
) {
1923 value
[valuelen
] = 0;
1926 status
= set_option(opt
, value
);
1929 if (status
== ERR
) {
1930 warn("Error on line %d, near '%.*s': %s",
1931 config_lineno
, (int) optlen
, opt
, config_msg
);
1932 config_errors
= TRUE
;
1935 /* Always keep going if errors are encountered. */
1940 load_option_file(const char *path
)
1944 /* It's OK that the file doesn't exist. */
1945 if (!io_open(&io
, "%s", path
))
1949 config_errors
= FALSE
;
1951 if (io_load(&io
, " \t", read_option
) == ERR
||
1952 config_errors
== TRUE
)
1953 warn("Errors while loading %s.", path
);
1959 const char *home
= getenv("HOME");
1960 const char *tigrc_user
= getenv("TIGRC_USER");
1961 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
1962 char buf
[SIZEOF_STR
];
1964 add_builtin_run_requests();
1967 tigrc_system
= SYSCONFDIR
"/tigrc";
1968 load_option_file(tigrc_system
);
1971 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
1975 load_option_file(tigrc_user
);
1988 /* The display array of active views and the index of the current view. */
1989 static struct view
*display
[2];
1990 static unsigned int current_view
;
1992 #define foreach_displayed_view(view, i) \
1993 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1995 #define displayed_views() (display[1] != NULL ? 2 : 1)
1997 /* Current head and commit ID */
1998 static char ref_blob
[SIZEOF_REF
] = "";
1999 static char ref_commit
[SIZEOF_REF
] = "HEAD";
2000 static char ref_head
[SIZEOF_REF
] = "HEAD";
2003 const char *name
; /* View name */
2004 const char *cmd_env
; /* Command line set via environment */
2005 const char *id
; /* Points to either of ref_{head,commit,blob} */
2007 struct view_ops
*ops
; /* View operations */
2009 enum keymap keymap
; /* What keymap does this view have */
2010 bool git_dir
; /* Whether the view requires a git directory. */
2012 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
2013 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
2015 int height
, width
; /* The width and height of the main window */
2016 WINDOW
*win
; /* The main window */
2017 WINDOW
*title
; /* The title window living below the main window */
2020 unsigned long offset
; /* Offset of the window top */
2021 unsigned long yoffset
; /* Offset from the window side. */
2022 unsigned long lineno
; /* Current line number */
2023 unsigned long p_offset
; /* Previous offset of the window top */
2024 unsigned long p_yoffset
;/* Previous offset from the window side */
2025 unsigned long p_lineno
; /* Previous current line number */
2026 bool p_restore
; /* Should the previous position be restored. */
2029 char grep
[SIZEOF_STR
]; /* Search string */
2030 regex_t
*regex
; /* Pre-compiled regexp */
2032 /* If non-NULL, points to the view that opened this view. If this view
2033 * is closed tig will switch back to the parent view. */
2034 struct view
*parent
;
2037 size_t lines
; /* Total number of lines */
2038 struct line
*line
; /* Line index */
2039 unsigned int digits
; /* Number of digits in the lines member. */
2042 struct line
*curline
; /* Line currently being drawn. */
2043 enum line_type curtype
; /* Attribute currently used for drawing. */
2044 unsigned long col
; /* Column when drawing. */
2045 bool has_scrolled
; /* View was scrolled. */
2055 /* What type of content being displayed. Used in the title bar. */
2057 /* Default command arguments. */
2059 /* Open and reads in all view content. */
2060 bool (*open
)(struct view
*view
);
2061 /* Read one line; updates view->line. */
2062 bool (*read
)(struct view
*view
, char *data
);
2063 /* Draw one line; @lineno must be < view->height. */
2064 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
2065 /* Depending on view handle a special requests. */
2066 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
2067 /* Search for regexp in a line. */
2068 bool (*grep
)(struct view
*view
, struct line
*line
);
2070 void (*select
)(struct view
*view
, struct line
*line
);
2071 /* Prepare view for loading */
2072 bool (*prepare
)(struct view
*view
);
2075 static struct view_ops blame_ops
;
2076 static struct view_ops blob_ops
;
2077 static struct view_ops diff_ops
;
2078 static struct view_ops help_ops
;
2079 static struct view_ops log_ops
;
2080 static struct view_ops main_ops
;
2081 static struct view_ops pager_ops
;
2082 static struct view_ops stage_ops
;
2083 static struct view_ops status_ops
;
2084 static struct view_ops tree_ops
;
2085 static struct view_ops branch_ops
;
2087 #define VIEW_STR(name, env, ref, ops, map, git) \
2088 { name, #env, ref, ops, map, git }
2090 #define VIEW_(id, name, ops, git, ref) \
2091 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2094 static struct view views
[] = {
2095 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
2096 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
2097 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
2098 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
2099 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
2100 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
2101 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
2102 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
2103 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
2104 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
2105 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
2108 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2109 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2111 #define foreach_view(view, i) \
2112 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2114 #define view_is_displayed(view) \
2115 (view == display[0] || view == display[1])
2122 static chtype line_graphics
[] = {
2123 /* LINE_GRAPHIC_VLINE: */ '|'
2127 set_view_attr(struct view
*view
, enum line_type type
)
2129 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
2130 wattrset(view
->win
, get_line_attr(type
));
2131 wchgat(view
->win
, -1, 0, type
, NULL
);
2132 view
->curtype
= type
;
2137 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
2138 int max_len
, bool use_tilde
)
2140 static char out_buffer
[BUFSIZ
* 2];
2143 int trimmed
= FALSE
;
2144 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2149 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
);
2151 set_view_attr(view
, type
);
2153 if (opt_iconv_out
!= ICONV_NONE
) {
2154 ICONV_CONST
char *inbuf
= (ICONV_CONST
char *) string
;
2155 size_t inlen
= len
+ 1;
2157 char *outbuf
= out_buffer
;
2158 size_t outlen
= sizeof(out_buffer
);
2162 ret
= iconv(opt_iconv_out
, &inbuf
, &inlen
, &outbuf
, &outlen
);
2163 if (ret
!= (size_t) -1) {
2164 string
= out_buffer
;
2165 len
= sizeof(out_buffer
) - outlen
;
2169 waddnstr(view
->win
, string
, len
);
2171 if (trimmed
&& use_tilde
) {
2172 set_view_attr(view
, LINE_DELIMITER
);
2173 waddch(view
->win
, '~');
2181 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
2183 static char space
[] = " ";
2186 spaces
= MIN(max
, spaces
);
2188 while (spaces
> 0) {
2189 int len
= MIN(spaces
, sizeof(space
) - 1);
2191 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
2199 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
2201 view
->col
+= draw_chars(view
, type
, string
, view
->width
+ view
->yoffset
- view
->col
, trim
);
2202 return view
->width
+ view
->yoffset
<= view
->col
;
2206 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
2208 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2209 int max
= view
->width
+ view
->yoffset
- view
->col
;
2215 set_view_attr(view
, type
);
2216 /* Using waddch() instead of waddnstr() ensures that
2217 * they'll be rendered correctly for the cursor line. */
2218 for (i
= skip
; i
< size
; i
++)
2219 waddch(view
->win
, graphic
[i
]);
2222 if (size
< max
&& skip
<= size
)
2223 waddch(view
->win
, ' ');
2226 return view
->width
+ view
->yoffset
<= view
->col
;
2230 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2232 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2236 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2238 col
= draw_space(view
, type
, max
- 1, max
- 1);
2241 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2242 return view
->width
+ view
->yoffset
<= view
->col
;
2246 draw_date(struct view
*view
, struct time
*time
)
2248 const char *date
= time
&& time
->sec
? mkdate(time
) : "";
2249 int cols
= opt_date
== DATE_SHORT
? DATE_SHORT_COLS
: DATE_COLS
;
2251 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2255 draw_author(struct view
*view
, const char *author
)
2257 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5;
2258 bool abbreviate
= opt_author
== AUTHOR_ABBREVIATED
|| !trim
;
2260 if (abbreviate
&& author
)
2261 author
= get_author_initials(author
);
2263 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2267 draw_mode(struct view
*view
, mode_t mode
)
2273 else if (S_ISLNK(mode
))
2275 else if (S_ISGITLINK(mode
))
2277 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2279 else if (S_ISREG(mode
))
2284 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2288 draw_lineno(struct view
*view
, unsigned int lineno
)
2291 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2292 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2295 lineno
+= view
->offset
+ 1;
2296 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2297 static char fmt
[] = "%1ld";
2299 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2300 if (string_format(number
, fmt
, lineno
))
2304 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2306 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2307 return draw_graphic(view
, LINE_DEFAULT
, &line_graphics
[LINE_GRAPHIC_VLINE
], 1);
2311 draw_view_line(struct view
*view
, unsigned int lineno
)
2314 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2316 assert(view_is_displayed(view
));
2318 if (view
->offset
+ lineno
>= view
->lines
)
2321 line
= &view
->line
[view
->offset
+ lineno
];
2323 wmove(view
->win
, lineno
, 0);
2325 wclrtoeol(view
->win
);
2327 view
->curline
= line
;
2328 view
->curtype
= LINE_NONE
;
2329 line
->selected
= FALSE
;
2330 line
->dirty
= line
->cleareol
= 0;
2333 set_view_attr(view
, LINE_CURSOR
);
2334 line
->selected
= TRUE
;
2335 view
->ops
->select(view
, line
);
2338 return view
->ops
->draw(view
, line
, lineno
);
2342 redraw_view_dirty(struct view
*view
)
2347 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2348 if (view
->offset
+ lineno
>= view
->lines
)
2350 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2353 if (!draw_view_line(view
, lineno
))
2359 wnoutrefresh(view
->win
);
2363 redraw_view_from(struct view
*view
, int lineno
)
2365 assert(0 <= lineno
&& lineno
< view
->height
);
2367 for (; lineno
< view
->height
; lineno
++) {
2368 if (!draw_view_line(view
, lineno
))
2372 wnoutrefresh(view
->win
);
2376 redraw_view(struct view
*view
)
2379 redraw_view_from(view
, 0);
2384 update_view_title(struct view
*view
)
2386 char buf
[SIZEOF_STR
];
2387 char state
[SIZEOF_STR
];
2388 size_t bufpos
= 0, statelen
= 0;
2390 assert(view_is_displayed(view
));
2392 if (view
!= VIEW(REQ_VIEW_STATUS
) && view
->lines
) {
2393 unsigned int view_lines
= view
->offset
+ view
->height
;
2394 unsigned int lines
= view
->lines
2395 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2398 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2407 time_t secs
= time(NULL
) - view
->start_time
;
2409 /* Three git seconds are a long time ... */
2411 string_format_from(state
, &statelen
, " loading %lds", secs
);
2414 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2415 if (*view
->ref
&& bufpos
< view
->width
) {
2416 size_t refsize
= strlen(view
->ref
);
2417 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2419 if (minsize
< view
->width
)
2420 refsize
= view
->width
- minsize
+ 7;
2421 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2424 if (statelen
&& bufpos
< view
->width
) {
2425 string_format_from(buf
, &bufpos
, "%s", state
);
2428 if (view
== display
[current_view
])
2429 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2431 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2433 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2434 wclrtoeol(view
->title
);
2435 wnoutrefresh(view
->title
);
2439 apply_step(double step
, int value
)
2443 value
*= step
+ 0.01;
2444 return value
? value
: 1;
2448 resize_display(void)
2451 struct view
*base
= display
[0];
2452 struct view
*view
= display
[1] ? display
[1] : display
[0];
2454 /* Setup window dimensions */
2456 getmaxyx(stdscr
, base
->height
, base
->width
);
2458 /* Make room for the status window. */
2462 /* Horizontal split. */
2463 view
->width
= base
->width
;
2464 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2465 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2466 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2467 base
->height
-= view
->height
;
2469 /* Make room for the title bar. */
2473 /* Make room for the title bar. */
2478 foreach_displayed_view (view
, i
) {
2480 view
->win
= newwin(view
->height
, 0, offset
, 0);
2482 die("Failed to create %s view", view
->name
);
2484 scrollok(view
->win
, FALSE
);
2486 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2488 die("Failed to create title window");
2491 wresize(view
->win
, view
->height
, view
->width
);
2492 mvwin(view
->win
, offset
, 0);
2493 mvwin(view
->title
, offset
+ view
->height
, 0);
2496 offset
+= view
->height
+ 1;
2501 redraw_display(bool clear
)
2506 foreach_displayed_view (view
, i
) {
2510 update_view_title(view
);
2515 toggle_enum_option_do(unsigned int *opt
, const char *help
,
2516 const struct enum_map
*map
, size_t size
)
2518 *opt
= (*opt
+ 1) % size
;
2519 redraw_display(FALSE
);
2520 report("Displaying %s %s", enum_name(map
[*opt
]), help
);
2523 #define toggle_enum_option(opt, help, map) \
2524 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2526 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2527 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2530 toggle_view_option(bool *option
, const char *help
)
2533 redraw_display(FALSE
);
2534 report("%sabling %s", *option
? "En" : "Dis", help
);
2538 open_option_menu(void)
2540 const struct menu_item menu
[] = {
2541 { '.', "line numbers", &opt_line_number
},
2542 { 'D', "date display", &opt_date
},
2543 { 'A', "author display", &opt_author
},
2544 { 'g', "revision graph display", &opt_rev_graph
},
2545 { 'F', "reference display", &opt_show_refs
},
2550 if (prompt_menu("Toggle option", menu
, &selected
)) {
2551 if (menu
[selected
].data
== &opt_date
)
2553 else if (menu
[selected
].data
== &opt_author
)
2556 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2561 maximize_view(struct view
*view
)
2563 memset(display
, 0, sizeof(display
));
2565 display
[current_view
] = view
;
2567 redraw_display(FALSE
);
2577 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2579 if (lineno
>= view
->lines
)
2580 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2582 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2583 unsigned long half
= view
->height
/ 2;
2586 offset
= lineno
- half
;
2591 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2592 view
->offset
= offset
;
2593 view
->lineno
= lineno
;
2600 /* Scrolling backend */
2602 do_scroll_view(struct view
*view
, int lines
)
2604 bool redraw_current_line
= FALSE
;
2606 /* The rendering expects the new offset. */
2607 view
->offset
+= lines
;
2609 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2612 /* Move current line into the view. */
2613 if (view
->lineno
< view
->offset
) {
2614 view
->lineno
= view
->offset
;
2615 redraw_current_line
= TRUE
;
2616 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2617 view
->lineno
= view
->offset
+ view
->height
- 1;
2618 redraw_current_line
= TRUE
;
2621 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2623 /* Redraw the whole screen if scrolling is pointless. */
2624 if (view
->height
< ABS(lines
)) {
2628 int line
= lines
> 0 ? view
->height
- lines
: 0;
2629 int end
= line
+ ABS(lines
);
2631 scrollok(view
->win
, TRUE
);
2632 wscrl(view
->win
, lines
);
2633 scrollok(view
->win
, FALSE
);
2635 while (line
< end
&& draw_view_line(view
, line
))
2638 if (redraw_current_line
)
2639 draw_view_line(view
, view
->lineno
- view
->offset
);
2640 wnoutrefresh(view
->win
);
2643 view
->has_scrolled
= TRUE
;
2647 /* Scroll frontend */
2649 scroll_view(struct view
*view
, enum request request
)
2653 assert(view_is_displayed(view
));
2656 case REQ_SCROLL_LEFT
:
2657 if (view
->yoffset
== 0) {
2658 report("Cannot scroll beyond the first column");
2661 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2664 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2665 redraw_view_from(view
, 0);
2668 case REQ_SCROLL_RIGHT
:
2669 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2673 case REQ_SCROLL_PAGE_DOWN
:
2674 lines
= view
->height
;
2675 case REQ_SCROLL_LINE_DOWN
:
2676 if (view
->offset
+ lines
> view
->lines
)
2677 lines
= view
->lines
- view
->offset
;
2679 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2680 report("Cannot scroll beyond the last line");
2685 case REQ_SCROLL_PAGE_UP
:
2686 lines
= view
->height
;
2687 case REQ_SCROLL_LINE_UP
:
2688 if (lines
> view
->offset
)
2689 lines
= view
->offset
;
2692 report("Cannot scroll beyond the first line");
2700 die("request %d not handled in switch", request
);
2703 do_scroll_view(view
, lines
);
2708 move_view(struct view
*view
, enum request request
)
2710 int scroll_steps
= 0;
2714 case REQ_MOVE_FIRST_LINE
:
2715 steps
= -view
->lineno
;
2718 case REQ_MOVE_LAST_LINE
:
2719 steps
= view
->lines
- view
->lineno
- 1;
2722 case REQ_MOVE_PAGE_UP
:
2723 steps
= view
->height
> view
->lineno
2724 ? -view
->lineno
: -view
->height
;
2727 case REQ_MOVE_PAGE_DOWN
:
2728 steps
= view
->lineno
+ view
->height
>= view
->lines
2729 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2741 die("request %d not handled in switch", request
);
2744 if (steps
<= 0 && view
->lineno
== 0) {
2745 report("Cannot move beyond the first line");
2748 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2749 report("Cannot move beyond the last line");
2753 /* Move the current line */
2754 view
->lineno
+= steps
;
2755 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2757 /* Check whether the view needs to be scrolled */
2758 if (view
->lineno
< view
->offset
||
2759 view
->lineno
>= view
->offset
+ view
->height
) {
2760 scroll_steps
= steps
;
2761 if (steps
< 0 && -steps
> view
->offset
) {
2762 scroll_steps
= -view
->offset
;
2764 } else if (steps
> 0) {
2765 if (view
->lineno
== view
->lines
- 1 &&
2766 view
->lines
> view
->height
) {
2767 scroll_steps
= view
->lines
- view
->offset
- 1;
2768 if (scroll_steps
>= view
->height
)
2769 scroll_steps
-= view
->height
- 1;
2774 if (!view_is_displayed(view
)) {
2775 view
->offset
+= scroll_steps
;
2776 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2777 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2781 /* Repaint the old "current" line if we be scrolling */
2782 if (ABS(steps
) < view
->height
)
2783 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
2786 do_scroll_view(view
, scroll_steps
);
2790 /* Draw the current line */
2791 draw_view_line(view
, view
->lineno
- view
->offset
);
2793 wnoutrefresh(view
->win
);
2802 static void search_view(struct view
*view
, enum request request
);
2805 grep_text(struct view
*view
, const char *text
[])
2810 for (i
= 0; text
[i
]; i
++)
2812 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
2818 select_view_line(struct view
*view
, unsigned long lineno
)
2820 unsigned long old_lineno
= view
->lineno
;
2821 unsigned long old_offset
= view
->offset
;
2823 if (goto_view_line(view
, view
->offset
, lineno
)) {
2824 if (view_is_displayed(view
)) {
2825 if (old_offset
!= view
->offset
) {
2828 draw_view_line(view
, old_lineno
- view
->offset
);
2829 draw_view_line(view
, view
->lineno
- view
->offset
);
2830 wnoutrefresh(view
->win
);
2833 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2839 find_next(struct view
*view
, enum request request
)
2841 unsigned long lineno
= view
->lineno
;
2846 report("No previous search");
2848 search_view(view
, request
);
2858 case REQ_SEARCH_BACK
:
2867 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
2868 lineno
+= direction
;
2870 /* Note, lineno is unsigned long so will wrap around in which case it
2871 * will become bigger than view->lines. */
2872 for (; lineno
< view
->lines
; lineno
+= direction
) {
2873 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
2874 select_view_line(view
, lineno
);
2875 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
2880 report("No match found for '%s'", view
->grep
);
2884 search_view(struct view
*view
, enum request request
)
2889 regfree(view
->regex
);
2892 view
->regex
= calloc(1, sizeof(*view
->regex
));
2897 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
2898 if (regex_err
!= 0) {
2899 char buf
[SIZEOF_STR
] = "unknown error";
2901 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
2902 report("Search failed: %s", buf
);
2906 string_copy(view
->grep
, opt_search
);
2908 find_next(view
, request
);
2912 * Incremental updating
2916 reset_view(struct view
*view
)
2920 for (i
= 0; i
< view
->lines
; i
++)
2921 free(view
->line
[i
].data
);
2924 view
->p_offset
= view
->offset
;
2925 view
->p_yoffset
= view
->yoffset
;
2926 view
->p_lineno
= view
->lineno
;
2934 view
->update_secs
= 0;
2938 free_argv(const char *argv
[])
2942 for (argc
= 0; argv
[argc
]; argc
++)
2943 free((void *) argv
[argc
]);
2947 format_arg(const char *name
)
2953 const char *value_if_empty
;
2955 #define FORMAT_VAR(name, value, value_if_empty) \
2956 { name, STRING_SIZE(name), value, value_if_empty }
2957 FORMAT_VAR("%(directory)", opt_path
, ""),
2958 FORMAT_VAR("%(file)", opt_file
, ""),
2959 FORMAT_VAR("%(ref)", opt_ref
, "HEAD"),
2960 FORMAT_VAR("%(head)", ref_head
, ""),
2961 FORMAT_VAR("%(commit)", ref_commit
, ""),
2962 FORMAT_VAR("%(blob)", ref_blob
, ""),
2966 for (i
= 0; i
< ARRAY_SIZE(vars
); i
++)
2967 if (!strncmp(name
, vars
[i
].name
, vars
[i
].namelen
))
2968 return *vars
[i
].value
? vars
[i
].value
: vars
[i
].value_if_empty
;
2973 format_argv(const char *dst_argv
[], const char *src_argv
[], enum format_flags flags
)
2975 char buf
[SIZEOF_STR
];
2977 bool noreplace
= flags
== FORMAT_NONE
;
2979 free_argv(dst_argv
);
2981 for (argc
= 0; src_argv
[argc
]; argc
++) {
2982 const char *arg
= src_argv
[argc
];
2986 char *next
= strstr(arg
, "%(");
2987 int len
= next
- arg
;
2990 if (!next
|| noreplace
) {
2991 if (flags
== FORMAT_DASH
&& !strcmp(arg
, "--"))
2997 value
= format_arg(next
);
3000 report("Unknown replacement: `%s`", next
);
3005 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
3008 arg
= next
&& !noreplace
? strchr(next
, ')') + 1 : NULL
;
3011 dst_argv
[argc
] = strdup(buf
);
3012 if (!dst_argv
[argc
])
3016 dst_argv
[argc
] = NULL
;
3018 return src_argv
[argc
] == NULL
;
3022 restore_view_position(struct view
*view
)
3024 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
3027 /* Changing the view position cancels the restoring. */
3028 /* FIXME: Changing back to the first line is not detected. */
3029 if (view
->offset
!= 0 || view
->lineno
!= 0) {
3030 view
->p_restore
= FALSE
;
3034 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
3035 view_is_displayed(view
))
3038 view
->yoffset
= view
->p_yoffset
;
3039 view
->p_restore
= FALSE
;
3045 end_update(struct view
*view
, bool force
)
3049 while (!view
->ops
->read(view
, NULL
))
3052 set_nonblocking_input(FALSE
);
3054 kill_io(view
->pipe
);
3055 done_io(view
->pipe
);
3060 setup_update(struct view
*view
, const char *vid
)
3062 set_nonblocking_input(TRUE
);
3064 string_copy_rev(view
->vid
, vid
);
3065 view
->pipe
= &view
->io
;
3066 view
->start_time
= time(NULL
);
3070 prepare_update(struct view
*view
, const char *argv
[], const char *dir
,
3071 enum format_flags flags
)
3074 end_update(view
, TRUE
);
3075 return init_io_rd(&view
->io
, argv
, dir
, flags
);
3079 prepare_update_file(struct view
*view
, const char *name
)
3082 end_update(view
, TRUE
);
3083 return io_open(&view
->io
, "%s/%s", opt_cdup
[0] ? opt_cdup
: ".", name
);
3087 begin_update(struct view
*view
, bool refresh
)
3090 end_update(view
, TRUE
);
3093 if (view
->ops
->prepare
) {
3094 if (!view
->ops
->prepare(view
))
3096 } else if (!init_io_rd(&view
->io
, view
->ops
->argv
, NULL
, FORMAT_ALL
)) {
3100 /* Put the current ref_* value to the view title ref
3101 * member. This is needed by the blob view. Most other
3102 * views sets it automatically after loading because the
3103 * first line is a commit line. */
3104 string_copy_rev(view
->ref
, view
->id
);
3107 if (!start_io(&view
->io
))
3110 setup_update(view
, view
->id
);
3116 update_view(struct view
*view
)
3118 char out_buffer
[BUFSIZ
* 2];
3120 /* Clear the view and redraw everything since the tree sorting
3121 * might have rearranged things. */
3122 bool redraw
= view
->lines
== 0;
3123 bool can_read
= TRUE
;
3128 if (!io_can_read(view
->pipe
)) {
3129 if (view
->lines
== 0 && view_is_displayed(view
)) {
3130 time_t secs
= time(NULL
) - view
->start_time
;
3132 if (secs
> 1 && secs
> view
->update_secs
) {
3133 if (view
->update_secs
== 0)
3135 update_view_title(view
);
3136 view
->update_secs
= secs
;
3142 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
3143 if (opt_iconv_in
!= ICONV_NONE
) {
3144 ICONV_CONST
char *inbuf
= line
;
3145 size_t inlen
= strlen(line
) + 1;
3147 char *outbuf
= out_buffer
;
3148 size_t outlen
= sizeof(out_buffer
);
3152 ret
= iconv(opt_iconv_in
, &inbuf
, &inlen
, &outbuf
, &outlen
);
3153 if (ret
!= (size_t) -1)
3157 if (!view
->ops
->read(view
, line
)) {
3158 report("Allocation failure");
3159 end_update(view
, TRUE
);
3165 unsigned long lines
= view
->lines
;
3168 for (digits
= 0; lines
; digits
++)
3171 /* Keep the displayed view in sync with line number scaling. */
3172 if (digits
!= view
->digits
) {
3173 view
->digits
= digits
;
3174 if (opt_line_number
|| view
== VIEW(REQ_VIEW_BLAME
))
3179 if (io_error(view
->pipe
)) {
3180 report("Failed to read: %s", io_strerror(view
->pipe
));
3181 end_update(view
, TRUE
);
3183 } else if (io_eof(view
->pipe
)) {
3185 end_update(view
, FALSE
);
3188 if (restore_view_position(view
))
3191 if (!view_is_displayed(view
))
3195 redraw_view_from(view
, 0);
3197 redraw_view_dirty(view
);
3199 /* Update the title _after_ the redraw so that if the redraw picks up a
3200 * commit reference in view->ref it'll be available here. */
3201 update_view_title(view
);
3205 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
3207 static struct line
*
3208 add_line_data(struct view
*view
, void *data
, enum line_type type
)
3212 if (!realloc_lines(&view
->line
, view
->lines
, 1))
3215 line
= &view
->line
[view
->lines
++];
3216 memset(line
, 0, sizeof(*line
));
3224 static struct line
*
3225 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
3227 char *data
= text
? strdup(text
) : NULL
;
3229 return data
? add_line_data(view
, data
, type
) : NULL
;
3232 static struct line
*
3233 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3235 char buf
[SIZEOF_STR
];
3238 va_start(args
, fmt
);
3239 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3243 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3251 OPEN_DEFAULT
= 0, /* Use default view switching. */
3252 OPEN_SPLIT
= 1, /* Split current view. */
3253 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3254 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3255 OPEN_PREPARED
= 32, /* Open already prepared command. */
3259 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3261 bool split
= !!(flags
& OPEN_SPLIT
);
3262 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3263 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3264 struct view
*view
= VIEW(request
);
3265 int nviews
= displayed_views();
3266 struct view
*base_view
= display
[0];
3268 if (view
== prev
&& nviews
== 1 && !reload
) {
3269 report("Already in %s view", view
->name
);
3273 if (view
->git_dir
&& !opt_git_dir
[0]) {
3274 report("The %s view is disabled in pager view", view
->name
);
3281 } else if (!nomaximize
) {
3282 /* Maximize the current view. */
3283 memset(display
, 0, sizeof(display
));
3285 display
[current_view
] = view
;
3288 /* No parent signals that this is the first loaded view. */
3289 if (prev
&& view
!= prev
) {
3290 view
->parent
= prev
;
3293 /* Resize the view when switching between split- and full-screen,
3294 * or when switching between two different full-screen views. */
3295 if (nviews
!= displayed_views() ||
3296 (nviews
== 1 && base_view
!= display
[0]))
3299 if (view
->ops
->open
) {
3301 end_update(view
, TRUE
);
3302 if (!view
->ops
->open(view
)) {
3303 report("Failed to load %s view", view
->name
);
3306 restore_view_position(view
);
3308 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3309 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3310 report("Failed to load %s view", view
->name
);
3314 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3315 /* Take the title line into account. */
3316 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3318 /* Scroll the view that was split if the current line is
3319 * outside the new limited view. */
3320 do_scroll_view(prev
, lines
);
3323 if (prev
&& view
!= prev
&& split
&& view_is_displayed(prev
)) {
3324 /* "Blur" the previous view. */
3325 update_view_title(prev
);
3328 if (view
->pipe
&& view
->lines
== 0) {
3329 /* Clear the old view and let the incremental updating refill
3332 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3334 } else if (view_is_displayed(view
)) {
3341 open_external_viewer(const char *argv
[], const char *dir
)
3343 def_prog_mode(); /* save current tty modes */
3344 endwin(); /* restore original tty modes */
3345 run_io_fg(argv
, dir
);
3346 fprintf(stderr
, "Press Enter to continue");
3349 redraw_display(TRUE
);
3353 open_mergetool(const char *file
)
3355 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3357 open_external_viewer(mergetool_argv
, opt_cdup
);
3361 open_editor(const char *file
)
3363 const char *editor_argv
[] = { "vi", file
, NULL
};
3366 editor
= getenv("GIT_EDITOR");
3367 if (!editor
&& *opt_editor
)
3368 editor
= opt_editor
;
3370 editor
= getenv("VISUAL");
3372 editor
= getenv("EDITOR");
3376 editor_argv
[0] = editor
;
3377 open_external_viewer(editor_argv
, opt_cdup
);
3381 open_run_request(enum request request
)
3383 struct run_request
*req
= get_run_request(request
);
3384 const char *argv
[ARRAY_SIZE(req
->argv
)] = { NULL
};
3387 report("Unknown run request");
3391 if (format_argv(argv
, req
->argv
, FORMAT_ALL
))
3392 open_external_viewer(argv
, NULL
);
3397 * User request switch noodle
3401 view_driver(struct view
*view
, enum request request
)
3405 if (request
== REQ_NONE
)
3408 if (request
> REQ_NONE
) {
3409 open_run_request(request
);
3410 /* FIXME: When all views can refresh always do this. */
3411 if (view
== VIEW(REQ_VIEW_STATUS
) ||
3412 view
== VIEW(REQ_VIEW_MAIN
) ||
3413 view
== VIEW(REQ_VIEW_LOG
) ||
3414 view
== VIEW(REQ_VIEW_BRANCH
) ||
3415 view
== VIEW(REQ_VIEW_STAGE
))
3416 request
= REQ_REFRESH
;
3421 if (view
&& view
->lines
) {
3422 request
= view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
3423 if (request
== REQ_NONE
)
3430 case REQ_MOVE_PAGE_UP
:
3431 case REQ_MOVE_PAGE_DOWN
:
3432 case REQ_MOVE_FIRST_LINE
:
3433 case REQ_MOVE_LAST_LINE
:
3434 move_view(view
, request
);
3437 case REQ_SCROLL_LEFT
:
3438 case REQ_SCROLL_RIGHT
:
3439 case REQ_SCROLL_LINE_DOWN
:
3440 case REQ_SCROLL_LINE_UP
:
3441 case REQ_SCROLL_PAGE_DOWN
:
3442 case REQ_SCROLL_PAGE_UP
:
3443 scroll_view(view
, request
);
3446 case REQ_VIEW_BLAME
:
3448 report("No file chosen, press %s to open tree view",
3449 get_key(view
->keymap
, REQ_VIEW_TREE
));
3452 open_view(view
, request
, OPEN_DEFAULT
);
3457 report("No file chosen, press %s to open tree view",
3458 get_key(view
->keymap
, REQ_VIEW_TREE
));
3461 open_view(view
, request
, OPEN_DEFAULT
);
3464 case REQ_VIEW_PAGER
:
3465 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3466 report("No pager content, press %s to run command from prompt",
3467 get_key(view
->keymap
, REQ_PROMPT
));
3470 open_view(view
, request
, OPEN_DEFAULT
);
3473 case REQ_VIEW_STAGE
:
3474 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3475 report("No stage content, press %s to open the status view and choose file",
3476 get_key(view
->keymap
, REQ_VIEW_STATUS
));
3479 open_view(view
, request
, OPEN_DEFAULT
);
3482 case REQ_VIEW_STATUS
:
3483 if (opt_is_inside_work_tree
== FALSE
) {
3484 report("The status view requires a working tree");
3487 open_view(view
, request
, OPEN_DEFAULT
);
3495 case REQ_VIEW_BRANCH
:
3496 open_view(view
, request
, OPEN_DEFAULT
);
3501 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3503 if ((view
== VIEW(REQ_VIEW_DIFF
) &&
3504 view
->parent
== VIEW(REQ_VIEW_MAIN
)) ||
3505 (view
== VIEW(REQ_VIEW_DIFF
) &&
3506 view
->parent
== VIEW(REQ_VIEW_BLAME
)) ||
3507 (view
== VIEW(REQ_VIEW_STAGE
) &&
3508 view
->parent
== VIEW(REQ_VIEW_STATUS
)) ||
3509 (view
== VIEW(REQ_VIEW_BLOB
) &&
3510 view
->parent
== VIEW(REQ_VIEW_TREE
)) ||
3511 (view
== VIEW(REQ_VIEW_MAIN
) &&
3512 view
->parent
== VIEW(REQ_VIEW_BRANCH
))) {
3515 view
= view
->parent
;
3516 line
= view
->lineno
;
3517 move_view(view
, request
);
3518 if (view_is_displayed(view
))
3519 update_view_title(view
);
3520 if (line
!= view
->lineno
)
3521 view
->ops
->request(view
, REQ_ENTER
,
3522 &view
->line
[view
->lineno
]);
3525 move_view(view
, request
);
3531 int nviews
= displayed_views();
3532 int next_view
= (current_view
+ 1) % nviews
;
3534 if (next_view
== current_view
) {
3535 report("Only one view is displayed");
3539 current_view
= next_view
;
3540 /* Blur out the title of the previous view. */
3541 update_view_title(view
);
3546 report("Refreshing is not yet supported for the %s view", view
->name
);
3550 if (displayed_views() == 2)
3551 maximize_view(view
);
3558 case REQ_TOGGLE_LINENO
:
3559 toggle_view_option(&opt_line_number
, "line numbers");
3562 case REQ_TOGGLE_DATE
:
3566 case REQ_TOGGLE_AUTHOR
:
3570 case REQ_TOGGLE_REV_GRAPH
:
3571 toggle_view_option(&opt_rev_graph
, "revision graph display");
3574 case REQ_TOGGLE_REFS
:
3575 toggle_view_option(&opt_show_refs
, "reference display");
3578 case REQ_TOGGLE_SORT_FIELD
:
3579 case REQ_TOGGLE_SORT_ORDER
:
3580 report("Sorting is not yet supported for the %s view", view
->name
);
3584 case REQ_SEARCH_BACK
:
3585 search_view(view
, request
);
3590 find_next(view
, request
);
3593 case REQ_STOP_LOADING
:
3594 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
3597 report("Stopped loading the %s view", view
->name
),
3598 end_update(view
, TRUE
);
3602 case REQ_SHOW_VERSION
:
3603 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3606 case REQ_SCREEN_REDRAW
:
3607 redraw_display(TRUE
);
3611 report("Nothing to edit");
3615 report("Nothing to enter");
3618 case REQ_VIEW_CLOSE
:
3619 /* XXX: Mark closed views by letting view->parent point to the
3620 * view itself. Parents to closed view should never be
3623 view
->parent
->parent
!= view
->parent
) {
3624 maximize_view(view
->parent
);
3625 view
->parent
= view
;
3633 report("Unknown key, press %s for help",
3634 get_key(view
->keymap
, REQ_VIEW_HELP
));
3643 * View backend utilities
3653 const enum sort_field
*fields
;
3654 size_t size
, current
;
3658 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3659 #define get_sort_field(state) ((state).fields[(state).current])
3660 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3663 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3664 int (*compare
)(const void *, const void *))
3667 case REQ_TOGGLE_SORT_FIELD
:
3668 state
->current
= (state
->current
+ 1) % state
->size
;
3671 case REQ_TOGGLE_SORT_ORDER
:
3672 state
->reverse
= !state
->reverse
;
3675 die("Not a sort request");
3678 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3682 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3684 /* Small author cache to reduce memory consumption. It uses binary
3685 * search to lookup or find place to position new entries. No entries
3686 * are ever freed. */
3688 get_author(const char *name
)
3690 static const char **authors
;
3691 static size_t authors_size
;
3692 int from
= 0, to
= authors_size
- 1;
3694 while (from
<= to
) {
3695 size_t pos
= (to
+ from
) / 2;
3696 int cmp
= strcmp(name
, authors
[pos
]);
3699 return authors
[pos
];
3707 if (!realloc_authors(&authors
, authors_size
, 1))
3709 name
= strdup(name
);
3713 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3714 authors
[from
] = name
;
3721 parse_timesec(struct time
*time
, const char *sec
)
3723 time
->sec
= (time_t) atol(sec
);
3727 parse_timezone(struct time
*time
, const char *zone
)
3731 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3732 tz
+= ('0' - zone
[2]) * 60 * 60;
3733 tz
+= ('0' - zone
[3]) * 60;
3734 tz
+= ('0' - zone
[4]);
3743 /* Parse author lines where the name may be empty:
3744 * author <email@address.tld> 1138474660 +0100
3747 parse_author_line(char *ident
, const char **author
, struct time
*time
)
3749 char *nameend
= strchr(ident
, '<');
3750 char *emailend
= strchr(ident
, '>');
3752 if (nameend
&& emailend
)
3753 *nameend
= *emailend
= 0;
3754 ident
= chomp_string(ident
);
3757 ident
= chomp_string(nameend
+ 1);
3762 *author
= get_author(ident
);
3764 /* Parse epoch and timezone */
3765 if (emailend
&& emailend
[1] == ' ') {
3766 char *secs
= emailend
+ 2;
3767 char *zone
= strchr(secs
, ' ');
3769 parse_timesec(time
, secs
);
3771 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
3772 parse_timezone(time
, zone
+ 1);
3777 open_commit_parent_menu(char buf
[SIZEOF_STR
], int *parents
)
3779 char rev
[SIZEOF_REV
];
3780 const char *revlist_argv
[] = {
3781 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev
, NULL
3783 struct menu_item
*items
;
3784 char text
[SIZEOF_STR
];
3788 items
= calloc(*parents
+ 1, sizeof(*items
));
3792 for (i
= 0; i
< *parents
; i
++) {
3793 string_copy_rev(rev
, &buf
[SIZEOF_REV
* i
]);
3794 if (!run_io_buf(revlist_argv
, text
, sizeof(text
)) ||
3795 !(items
[i
].text
= strdup(text
))) {
3803 ok
= prompt_menu("Select parent", items
, parents
);
3805 for (i
= 0; items
[i
].text
; i
++)
3806 free((char *) items
[i
].text
);
3812 select_commit_parent(const char *id
, char rev
[SIZEOF_REV
], const char *path
)
3814 char buf
[SIZEOF_STR
* 4];
3815 const char *revlist_argv
[] = {
3816 "git", "log", "--no-color", "-1",
3817 "--pretty=format:%P", id
, "--", path
, NULL
3821 if (!run_io_buf(revlist_argv
, buf
, sizeof(buf
)) ||
3822 (parents
= strlen(buf
) / 40) < 0) {
3823 report("Failed to get parent information");
3826 } else if (parents
== 0) {
3828 report("Path '%s' does not exist in the parent", path
);
3830 report("The selected commit has no parents");
3834 if (parents
> 1 && !open_commit_parent_menu(buf
, &parents
))
3837 string_copy_rev(rev
, &buf
[41 * parents
]);
3846 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
3848 char text
[SIZEOF_STR
];
3850 if (opt_line_number
&& draw_lineno(view
, lineno
))
3853 string_expand(text
, sizeof(text
), line
->data
, opt_tab_size
);
3854 draw_text(view
, line
->type
, text
, TRUE
);
3859 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
3861 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
3862 char ref
[SIZEOF_STR
];
3864 if (!run_io_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
3867 /* This is the only fatal call, since it can "corrupt" the buffer. */
3868 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
3875 add_pager_refs(struct view
*view
, struct line
*line
)
3877 char buf
[SIZEOF_STR
];
3878 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
3879 struct ref_list
*list
;
3880 size_t bufpos
= 0, i
;
3881 const char *sep
= "Refs: ";
3882 bool is_tag
= FALSE
;
3884 assert(line
->type
== LINE_COMMIT
);
3886 list
= get_ref_list(commit_id
);
3888 if (view
== VIEW(REQ_VIEW_DIFF
))
3889 goto try_add_describe_ref
;
3893 for (i
= 0; i
< list
->size
; i
++) {
3894 struct ref
*ref
= list
->refs
[i
];
3895 const char *fmt
= ref
->tag
? "%s[%s]" :
3896 ref
->remote
? "%s<%s>" : "%s%s";
3898 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
3905 if (!is_tag
&& view
== VIEW(REQ_VIEW_DIFF
)) {
3906 try_add_describe_ref
:
3907 /* Add <tag>-g<commit_id> "fake" reference. */
3908 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
3915 add_line_text(view
, buf
, LINE_PP_REFS
);
3919 pager_read(struct view
*view
, char *data
)
3926 line
= add_line_text(view
, data
, get_line_type(data
));
3930 if (line
->type
== LINE_COMMIT
&&
3931 (view
== VIEW(REQ_VIEW_DIFF
) ||
3932 view
== VIEW(REQ_VIEW_LOG
)))
3933 add_pager_refs(view
, line
);
3939 pager_request(struct view
*view
, enum request request
, struct line
*line
)
3943 if (request
!= REQ_ENTER
)
3946 if (line
->type
== LINE_COMMIT
&&
3947 (view
== VIEW(REQ_VIEW_LOG
) ||
3948 view
== VIEW(REQ_VIEW_PAGER
))) {
3949 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
3953 /* Always scroll the view even if it was split. That way
3954 * you can use Enter to scroll through the log view and
3955 * split open each commit diff. */
3956 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
3958 /* FIXME: A minor workaround. Scrolling the view will call report("")
3959 * but if we are scrolling a non-current view this won't properly
3960 * update the view title. */
3962 update_view_title(view
);
3968 pager_grep(struct view
*view
, struct line
*line
)
3970 const char *text
[] = { line
->data
, NULL
};
3972 return grep_text(view
, text
);
3976 pager_select(struct view
*view
, struct line
*line
)
3978 if (line
->type
== LINE_COMMIT
) {
3979 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
3981 if (view
!= VIEW(REQ_VIEW_PAGER
))
3982 string_copy_rev(view
->ref
, text
);
3983 string_copy_rev(ref_commit
, text
);
3987 static struct view_ops pager_ops
= {
3998 static const char *log_argv
[SIZEOF_ARG
] = {
3999 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4003 log_request(struct view
*view
, enum request request
, struct line
*line
)
4008 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
4011 return pager_request(view
, request
, line
);
4015 static struct view_ops log_ops
= {
4026 static const char *diff_argv
[SIZEOF_ARG
] = {
4027 "git", "show", "--pretty=fuller", "--no-color", "--root",
4028 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4031 static struct view_ops diff_ops
= {
4046 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
4049 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
4053 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
4054 help_keymap_hidden
[keymap
] ? '+' : '-',
4055 enum_name(keymap_table
[keymap
]));
4057 line
->other
= keymap
;
4059 return help_keymap_hidden
[keymap
];
4063 help_open_keymap(struct view
*view
, enum keymap keymap
)
4065 const char *group
= NULL
;
4066 char buf
[SIZEOF_STR
];
4068 bool add_title
= TRUE
;
4071 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
4072 const char *key
= NULL
;
4074 if (req_info
[i
].request
== REQ_NONE
)
4077 if (!req_info
[i
].request
) {
4078 group
= req_info
[i
].help
;
4082 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4086 if (add_title
&& help_open_keymap_title(view
, keymap
))
4091 add_line_text(view
, group
, LINE_HELP_GROUP
);
4095 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4096 enum_name(req_info
[i
]), req_info
[i
].help
);
4099 group
= "External commands:";
4101 for (i
= 0; i
< run_requests
; i
++) {
4102 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4106 if (!req
|| req
->keymap
!= keymap
)
4109 key
= get_key_name(req
->key
);
4111 key
= "(no key defined)";
4113 if (add_title
&& help_open_keymap_title(view
, keymap
))
4116 add_line_text(view
, group
, LINE_HELP_GROUP
);
4120 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4121 if (!string_format_from(buf
, &bufpos
, "%s%s",
4122 argc
? " " : "", req
->argv
[argc
]))
4125 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4130 help_open(struct view
*view
)
4135 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4136 add_line_text(view
, "", LINE_DEFAULT
);
4138 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4139 help_open_keymap(view
, keymap
);
4145 help_request(struct view
*view
, enum request request
, struct line
*line
)
4149 if (line
->type
== LINE_HELP_KEYMAP
) {
4150 help_keymap_hidden
[line
->other
] =
4151 !help_keymap_hidden
[line
->other
];
4152 view
->p_restore
= TRUE
;
4153 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4158 return pager_request(view
, request
, line
);
4162 static struct view_ops help_ops
= {
4178 struct tree_stack_entry
{
4179 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4180 unsigned long lineno
; /* Line number to restore */
4181 char *name
; /* Position of name in opt_path */
4184 /* The top of the path stack. */
4185 static struct tree_stack_entry
*tree_stack
= NULL
;
4186 unsigned long tree_lineno
= 0;
4189 pop_tree_stack_entry(void)
4191 struct tree_stack_entry
*entry
= tree_stack
;
4193 tree_lineno
= entry
->lineno
;
4195 tree_stack
= entry
->prev
;
4200 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4202 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4203 size_t pathlen
= strlen(opt_path
);
4208 entry
->prev
= tree_stack
;
4209 entry
->name
= opt_path
+ pathlen
;
4212 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4213 pop_tree_stack_entry();
4217 /* Move the current line to the first tree entry. */
4219 entry
->lineno
= lineno
;
4222 /* Parse output from git-ls-tree(1):
4224 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4227 #define SIZEOF_TREE_ATTR \
4228 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4230 #define SIZEOF_TREE_MODE \
4231 STRING_SIZE("100644 ")
4233 #define TREE_ID_OFFSET \
4234 STRING_SIZE("100644 blob ")
4237 char id
[SIZEOF_REV
];
4239 struct time time
; /* Date from the author ident. */
4240 const char *author
; /* Author of the commit. */
4245 tree_path(const struct line
*line
)
4247 return ((struct tree_entry
*) line
->data
)->name
;
4251 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4253 if (line1
->type
!= line2
->type
)
4254 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4255 return strcmp(tree_path(line1
), tree_path(line2
));
4258 static const enum sort_field tree_sort_fields
[] = {
4259 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4261 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4264 tree_compare(const void *l1
, const void *l2
)
4266 const struct line
*line1
= (const struct line
*) l1
;
4267 const struct line
*line2
= (const struct line
*) l2
;
4268 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4269 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4271 if (line1
->type
== LINE_TREE_HEAD
)
4273 if (line2
->type
== LINE_TREE_HEAD
)
4276 switch (get_sort_field(tree_sort_state
)) {
4278 return sort_order(tree_sort_state
, timecmp(&entry1
->time
, &entry2
->time
));
4280 case ORDERBY_AUTHOR
:
4281 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4285 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4290 static struct line
*
4291 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4292 const char *mode
, const char *id
)
4294 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4295 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4297 if (!entry
|| !line
) {
4302 strncpy(entry
->name
, path
, strlen(path
));
4304 entry
->mode
= strtoul(mode
, NULL
, 8);
4306 string_copy_rev(entry
->id
, id
);
4312 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4314 static const char *author_name
;
4315 static struct time author_time
;
4317 if (!text
&& *read_date
) {
4322 char *path
= *opt_path
? opt_path
: ".";
4323 /* Find next entry to process */
4324 const char *log_file
[] = {
4325 "git", "log", "--no-color", "--pretty=raw",
4326 "--cc", "--raw", view
->id
, "--", path
, NULL
4331 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4332 report("Tree is empty");
4336 if (!run_io_rd(&io
, log_file
, opt_cdup
, FORMAT_NONE
)) {
4337 report("Failed to load tree data");
4341 done_io(view
->pipe
);
4346 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4347 parse_author_line(text
+ STRING_SIZE("author "),
4348 &author_name
, &author_time
);
4350 } else if (*text
== ':') {
4352 size_t annotated
= 1;
4355 pos
= strchr(text
, '\t');
4359 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4360 text
+= strlen(opt_path
);
4361 pos
= strchr(text
, '/');
4365 for (i
= 1; i
< view
->lines
; i
++) {
4366 struct line
*line
= &view
->line
[i
];
4367 struct tree_entry
*entry
= line
->data
;
4369 annotated
+= !!entry
->author
;
4370 if (entry
->author
|| strcmp(entry
->name
, text
))
4373 entry
->author
= author_name
;
4374 entry
->time
= author_time
;
4379 if (annotated
== view
->lines
)
4380 kill_io(view
->pipe
);
4386 tree_read(struct view
*view
, char *text
)
4388 static bool read_date
= FALSE
;
4389 struct tree_entry
*data
;
4390 struct line
*entry
, *line
;
4391 enum line_type type
;
4392 size_t textlen
= text
? strlen(text
) : 0;
4393 char *path
= text
+ SIZEOF_TREE_ATTR
;
4395 if (read_date
|| !text
)
4396 return tree_read_date(view
, text
, &read_date
);
4398 if (textlen
<= SIZEOF_TREE_ATTR
)
4400 if (view
->lines
== 0 &&
4401 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4404 /* Strip the path part ... */
4406 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4407 size_t striplen
= strlen(opt_path
);
4409 if (pathlen
> striplen
)
4410 memmove(path
, path
+ striplen
,
4411 pathlen
- striplen
+ 1);
4413 /* Insert "link" to parent directory. */
4414 if (view
->lines
== 1 &&
4415 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4419 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4420 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4425 /* Skip "Directory ..." and ".." line. */
4426 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4427 if (tree_compare_entry(line
, entry
) <= 0)
4430 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4434 for (; line
<= entry
; line
++)
4435 line
->dirty
= line
->cleareol
= 1;
4439 if (tree_lineno
> view
->lineno
) {
4440 view
->lineno
= tree_lineno
;
4448 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4450 struct tree_entry
*entry
= line
->data
;
4452 if (line
->type
== LINE_TREE_HEAD
) {
4453 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4456 if (draw_mode(view
, entry
->mode
))
4459 if (opt_author
&& draw_author(view
, entry
->author
))
4462 if (opt_date
&& draw_date(view
, &entry
->time
))
4465 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4473 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4474 int fd
= mkstemp(file
);
4477 report("Failed to create temporary file");
4478 else if (!run_io_append(blob_ops
.argv
, FORMAT_ALL
, fd
))
4479 report("Failed to save blob data to file");
4487 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4489 enum open_flags flags
;
4492 case REQ_VIEW_BLAME
:
4493 if (line
->type
!= LINE_TREE_FILE
) {
4494 report("Blame only supported for files");
4498 string_copy(opt_ref
, view
->vid
);
4502 if (line
->type
!= LINE_TREE_FILE
) {
4503 report("Edit only supported for files");
4504 } else if (!is_head_commit(view
->vid
)) {
4507 open_editor(opt_file
);
4511 case REQ_TOGGLE_SORT_FIELD
:
4512 case REQ_TOGGLE_SORT_ORDER
:
4513 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4518 /* quit view if at top of tree */
4519 return REQ_VIEW_CLOSE
;
4522 line
= &view
->line
[1];
4532 /* Cleanup the stack if the tree view is at a different tree. */
4533 while (!*opt_path
&& tree_stack
)
4534 pop_tree_stack_entry();
4536 switch (line
->type
) {
4538 /* Depending on whether it is a subdirectory or parent link
4539 * mangle the path buffer. */
4540 if (line
== &view
->line
[1] && *opt_path
) {
4541 pop_tree_stack_entry();
4544 const char *basename
= tree_path(line
);
4546 push_tree_stack_entry(basename
, view
->lineno
);
4549 /* Trees and subtrees share the same ID, so they are not not
4550 * unique like blobs. */
4551 flags
= OPEN_RELOAD
;
4552 request
= REQ_VIEW_TREE
;
4555 case LINE_TREE_FILE
:
4556 flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4557 request
= REQ_VIEW_BLOB
;
4564 open_view(view
, request
, flags
);
4565 if (request
== REQ_VIEW_TREE
)
4566 view
->lineno
= tree_lineno
;
4572 tree_grep(struct view
*view
, struct line
*line
)
4574 struct tree_entry
*entry
= line
->data
;
4575 const char *text
[] = {
4577 opt_author
? entry
->author
: "",
4578 opt_date
? mkdate(&entry
->time
) : "",
4582 return grep_text(view
, text
);
4586 tree_select(struct view
*view
, struct line
*line
)
4588 struct tree_entry
*entry
= line
->data
;
4590 if (line
->type
== LINE_TREE_FILE
) {
4591 string_copy_rev(ref_blob
, entry
->id
);
4592 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4594 } else if (line
->type
!= LINE_TREE_DIR
) {
4598 string_copy_rev(view
->ref
, entry
->id
);
4602 tree_prepare(struct view
*view
)
4604 if (view
->lines
== 0 && opt_prefix
[0]) {
4605 char *pos
= opt_prefix
;
4607 while (pos
&& *pos
) {
4608 char *end
= strchr(pos
, '/');
4612 push_tree_stack_entry(pos
, 0);
4620 } else if (strcmp(view
->vid
, view
->id
)) {
4624 return init_io_rd(&view
->io
, view
->ops
->argv
, opt_cdup
, FORMAT_ALL
);
4627 static const char *tree_argv
[SIZEOF_ARG
] = {
4628 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4631 static struct view_ops tree_ops
= {
4644 blob_read(struct view
*view
, char *line
)
4648 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4652 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4659 return pager_request(view
, request
, line
);
4663 static const char *blob_argv
[SIZEOF_ARG
] = {
4664 "git", "cat-file", "blob", "%(blob)", NULL
4667 static struct view_ops blob_ops
= {
4681 * Loading the blame view is a two phase job:
4683 * 1. File content is read either using opt_file from the
4684 * filesystem or using git-cat-file.
4685 * 2. Then blame information is incrementally added by
4686 * reading output from git-blame.
4689 static const char *blame_head_argv
[] = {
4690 "git", "blame", "--incremental", "--", "%(file)", NULL
4693 static const char *blame_ref_argv
[] = {
4694 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4697 static const char *blame_cat_file_argv
[] = {
4698 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4701 struct blame_commit
{
4702 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4703 char title
[128]; /* First line of the commit message. */
4704 const char *author
; /* Author of the commit. */
4705 struct time time
; /* Date from the author ident. */
4706 char filename
[128]; /* Name of file. */
4707 bool has_previous
; /* Was a "previous" line detected. */
4711 struct blame_commit
*commit
;
4712 unsigned long lineno
;
4717 blame_open(struct view
*view
)
4719 char path
[SIZEOF_STR
];
4721 if (!view
->parent
&& *opt_prefix
) {
4722 string_copy(path
, opt_file
);
4723 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4727 if (*opt_ref
|| !io_open(&view
->io
, "%s%s", opt_cdup
, opt_file
)) {
4728 if (!run_io_rd(&view
->io
, blame_cat_file_argv
, opt_cdup
, FORMAT_ALL
))
4732 setup_update(view
, opt_file
);
4733 string_format(view
->ref
, "%s ...", opt_file
);
4738 static struct blame_commit
*
4739 get_blame_commit(struct view
*view
, const char *id
)
4743 for (i
= 0; i
< view
->lines
; i
++) {
4744 struct blame
*blame
= view
->line
[i
].data
;
4749 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4750 return blame
->commit
;
4754 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4757 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4763 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4765 const char *pos
= *posref
;
4768 pos
= strchr(pos
+ 1, ' ');
4769 if (!pos
|| !isdigit(pos
[1]))
4771 *number
= atoi(pos
+ 1);
4772 if (*number
< min
|| *number
> max
)
4779 static struct blame_commit
*
4780 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4782 struct blame_commit
*commit
;
4783 struct blame
*blame
;
4784 const char *pos
= text
+ SIZEOF_REV
- 2;
4785 size_t orig_lineno
= 0;
4789 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4792 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4793 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4794 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4797 commit
= get_blame_commit(view
, text
);
4803 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4806 blame
->commit
= commit
;
4807 blame
->lineno
= orig_lineno
+ group
- 1;
4815 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4818 const char **argv
= *opt_ref
? blame_ref_argv
: blame_head_argv
;
4821 if (view
->lines
== 0 && !view
->parent
)
4822 die("No blame exist for %s", view
->vid
);
4824 if (view
->lines
== 0 || !run_io_rd(&io
, argv
, opt_cdup
, FORMAT_ALL
)) {
4825 report("Failed to load blame data");
4829 done_io(view
->pipe
);
4835 size_t linelen
= strlen(line
);
4836 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
4841 blame
->commit
= NULL
;
4842 strncpy(blame
->text
, line
, linelen
);
4843 blame
->text
[linelen
] = 0;
4844 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
4849 match_blame_header(const char *name
, char **line
)
4851 size_t namelen
= strlen(name
);
4852 bool matched
= !strncmp(name
, *line
, namelen
);
4861 blame_read(struct view
*view
, char *line
)
4863 static struct blame_commit
*commit
= NULL
;
4864 static int blamed
= 0;
4865 static bool read_file
= TRUE
;
4868 return blame_read_file(view
, line
, &read_file
);
4875 string_format(view
->ref
, "%s", view
->vid
);
4876 if (view_is_displayed(view
)) {
4877 update_view_title(view
);
4878 redraw_view_from(view
, 0);
4884 commit
= parse_blame_commit(view
, line
, &blamed
);
4885 string_format(view
->ref
, "%s %2d%%", view
->vid
,
4886 view
->lines
? blamed
* 100 / view
->lines
: 0);
4888 } else if (match_blame_header("author ", &line
)) {
4889 commit
->author
= get_author(line
);
4891 } else if (match_blame_header("author-time ", &line
)) {
4892 parse_timesec(&commit
->time
, line
);
4894 } else if (match_blame_header("author-tz ", &line
)) {
4895 parse_timezone(&commit
->time
, line
);
4897 } else if (match_blame_header("summary ", &line
)) {
4898 string_ncopy(commit
->title
, line
, strlen(line
));
4900 } else if (match_blame_header("previous ", &line
)) {
4901 commit
->has_previous
= TRUE
;
4903 } else if (match_blame_header("filename ", &line
)) {
4904 string_ncopy(commit
->filename
, line
, strlen(line
));
4912 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4914 struct blame
*blame
= line
->data
;
4915 struct time
*time
= NULL
;
4916 const char *id
= NULL
, *author
= NULL
;
4917 char text
[SIZEOF_STR
];
4919 if (blame
->commit
&& *blame
->commit
->filename
) {
4920 id
= blame
->commit
->id
;
4921 author
= blame
->commit
->author
;
4922 time
= &blame
->commit
->time
;
4925 if (opt_date
&& draw_date(view
, time
))
4928 if (opt_author
&& draw_author(view
, author
))
4931 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
4934 if (draw_lineno(view
, lineno
))
4937 string_expand(text
, sizeof(text
), blame
->text
, opt_tab_size
);
4938 draw_text(view
, LINE_DEFAULT
, text
, TRUE
);
4943 check_blame_commit(struct blame
*blame
, bool check_null_id
)
4946 report("Commit data not loaded yet");
4947 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4948 report("No commit exist for the selected line");
4955 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
4957 const char *diff_tree_argv
[] = {
4958 "git", "diff-tree", "-U0", blame
->commit
->id
,
4959 "--", blame
->commit
->filename
, NULL
4962 int parent_lineno
= -1;
4963 int blamed_lineno
= -1;
4966 if (!run_io(&io
, diff_tree_argv
, NULL
, IO_RD
))
4969 while ((line
= io_get(&io
, '\n', TRUE
))) {
4971 char *pos
= strchr(line
, '+');
4973 parent_lineno
= atoi(line
+ 4);
4975 blamed_lineno
= atoi(pos
+ 1);
4977 } else if (*line
== '+' && parent_lineno
!= -1) {
4978 if (blame
->lineno
== blamed_lineno
- 1 &&
4979 !strcmp(blame
->text
, line
+ 1)) {
4980 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
4991 blame_request(struct view
*view
, enum request request
, struct line
*line
)
4993 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4994 struct blame
*blame
= line
->data
;
4997 case REQ_VIEW_BLAME
:
4998 if (check_blame_commit(blame
, TRUE
)) {
4999 string_copy(opt_ref
, blame
->commit
->id
);
5000 string_copy(opt_file
, blame
->commit
->filename
);
5002 view
->lineno
= blame
->lineno
;
5003 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5008 if (check_blame_commit(blame
, TRUE
) &&
5009 select_commit_parent(blame
->commit
->id
, opt_ref
,
5010 blame
->commit
->filename
)) {
5011 string_copy(opt_file
, blame
->commit
->filename
);
5012 setup_blame_parent_line(view
, blame
);
5013 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5018 if (!check_blame_commit(blame
, FALSE
))
5021 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
5022 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
5025 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
5026 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
5027 const char *diff_index_argv
[] = {
5028 "git", "diff-index", "--root", "--patch-with-stat",
5029 "-C", "-M", "HEAD", "--", view
->vid
, NULL
5032 if (!blame
->commit
->has_previous
) {
5033 diff_index_argv
[1] = "diff";
5034 diff_index_argv
[2] = "--no-color";
5035 diff_index_argv
[6] = "--";
5036 diff_index_argv
[7] = "/dev/null";
5039 if (!prepare_update(diff
, diff_index_argv
, NULL
, FORMAT_DASH
)) {
5040 report("Failed to allocate diff command");
5043 flags
|= OPEN_PREPARED
;
5046 open_view(view
, REQ_VIEW_DIFF
, flags
);
5047 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5048 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
5059 blame_grep(struct view
*view
, struct line
*line
)
5061 struct blame
*blame
= line
->data
;
5062 struct blame_commit
*commit
= blame
->commit
;
5063 const char *text
[] = {
5065 commit
? commit
->title
: "",
5066 commit
? commit
->id
: "",
5067 commit
&& opt_author
? commit
->author
: "",
5068 commit
&& opt_date
? mkdate(&commit
->time
) : "",
5072 return grep_text(view
, text
);
5076 blame_select(struct view
*view
, struct line
*line
)
5078 struct blame
*blame
= line
->data
;
5079 struct blame_commit
*commit
= blame
->commit
;
5084 if (!strcmp(commit
->id
, NULL_ID
))
5085 string_ncopy(ref_commit
, "HEAD", 4);
5087 string_copy_rev(ref_commit
, commit
->id
);
5090 static struct view_ops blame_ops
= {
5106 const char *author
; /* Author of the last commit. */
5107 struct time time
; /* Date of the last activity. */
5108 const struct ref
*ref
; /* Name and commit ID information. */
5111 static const struct ref branch_all
;
5113 static const enum sort_field branch_sort_fields
[] = {
5114 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5116 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5119 branch_compare(const void *l1
, const void *l2
)
5121 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5122 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5124 switch (get_sort_field(branch_sort_state
)) {
5126 return sort_order(branch_sort_state
, timecmp(&branch1
->time
, &branch2
->time
));
5128 case ORDERBY_AUTHOR
:
5129 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5133 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5138 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5140 struct branch
*branch
= line
->data
;
5141 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5143 if (opt_date
&& draw_date(view
, &branch
->time
))
5146 if (opt_author
&& draw_author(view
, branch
->author
))
5149 draw_text(view
, type
, branch
->ref
== &branch_all
? "All branches" : branch
->ref
->name
, TRUE
);
5154 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5156 struct branch
*branch
= line
->data
;
5161 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5164 case REQ_TOGGLE_SORT_FIELD
:
5165 case REQ_TOGGLE_SORT_ORDER
:
5166 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5170 if (branch
->ref
== &branch_all
) {
5171 const char *all_branches_argv
[] = {
5172 "git", "log", "--no-color", "--pretty=raw", "--parents",
5173 "--topo-order", "--all", NULL
5175 struct view
*main_view
= VIEW(REQ_VIEW_MAIN
);
5177 if (!prepare_update(main_view
, all_branches_argv
, NULL
, FORMAT_NONE
)) {
5178 report("Failed to load view of all branches");
5181 open_view(view
, REQ_VIEW_MAIN
, OPEN_PREPARED
| OPEN_SPLIT
);
5183 open_view(view
, REQ_VIEW_MAIN
, OPEN_SPLIT
);
5193 branch_read(struct view
*view
, char *line
)
5195 static char id
[SIZEOF_REV
];
5196 struct branch
*reference
;
5202 switch (get_line_type(line
)) {
5204 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5208 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5209 struct branch
*branch
= view
->line
[i
].data
;
5211 if (strcmp(branch
->ref
->id
, id
))
5214 view
->line
[i
].dirty
= TRUE
;
5216 branch
->author
= reference
->author
;
5217 branch
->time
= reference
->time
;
5221 parse_author_line(line
+ STRING_SIZE("author "),
5222 &branch
->author
, &branch
->time
);
5234 branch_open_visitor(void *data
, const struct ref
*ref
)
5236 struct view
*view
= data
;
5237 struct branch
*branch
;
5239 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5242 branch
= calloc(1, sizeof(*branch
));
5247 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5251 branch_open(struct view
*view
)
5253 const char *branch_log
[] = {
5254 "git", "log", "--no-color", "--pretty=raw",
5255 "--simplify-by-decoration", "--all", NULL
5258 if (!run_io_rd(&view
->io
, branch_log
, NULL
, FORMAT_NONE
)) {
5259 report("Failed to load branch data");
5263 setup_update(view
, view
->id
);
5264 branch_open_visitor(view
, &branch_all
);
5265 foreach_ref(branch_open_visitor
, view
);
5266 view
->p_restore
= TRUE
;
5272 branch_grep(struct view
*view
, struct line
*line
)
5274 struct branch
*branch
= line
->data
;
5275 const char *text
[] = {
5281 return grep_text(view
, text
);
5285 branch_select(struct view
*view
, struct line
*line
)
5287 struct branch
*branch
= line
->data
;
5289 string_copy_rev(view
->ref
, branch
->ref
->id
);
5290 string_copy_rev(ref_commit
, branch
->ref
->id
);
5291 string_copy_rev(ref_head
, branch
->ref
->id
);
5294 static struct view_ops branch_ops
= {
5313 char rev
[SIZEOF_REV
];
5314 char name
[SIZEOF_STR
];
5318 char rev
[SIZEOF_REV
];
5319 char name
[SIZEOF_STR
];
5323 static char status_onbranch
[SIZEOF_STR
];
5324 static struct status stage_status
;
5325 static enum line_type stage_line_type
;
5326 static size_t stage_chunks
;
5327 static int *stage_chunk
;
5329 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5331 /* This should work even for the "On branch" line. */
5333 status_has_none(struct view
*view
, struct line
*line
)
5335 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5338 /* Get fields from the diff line:
5339 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5342 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5344 const char *old_mode
= buf
+ 1;
5345 const char *new_mode
= buf
+ 8;
5346 const char *old_rev
= buf
+ 15;
5347 const char *new_rev
= buf
+ 56;
5348 const char *status
= buf
+ 97;
5351 old_mode
[-1] != ':' ||
5352 new_mode
[-1] != ' ' ||
5353 old_rev
[-1] != ' ' ||
5354 new_rev
[-1] != ' ' ||
5358 file
->status
= *status
;
5360 string_copy_rev(file
->old
.rev
, old_rev
);
5361 string_copy_rev(file
->new.rev
, new_rev
);
5363 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5364 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5366 file
->old
.name
[0] = file
->new.name
[0] = 0;
5372 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5374 struct status
*unmerged
= NULL
;
5378 if (!run_io(&io
, argv
, opt_cdup
, IO_RD
))
5381 add_line_data(view
, NULL
, type
);
5383 while ((buf
= io_get(&io
, 0, TRUE
))) {
5384 struct status
*file
= unmerged
;
5387 file
= calloc(1, sizeof(*file
));
5388 if (!file
|| !add_line_data(view
, file
, type
))
5392 /* Parse diff info part. */
5394 file
->status
= status
;
5396 string_copy(file
->old
.rev
, NULL_ID
);
5398 } else if (!file
->status
|| file
== unmerged
) {
5399 if (!status_get_diff(file
, buf
, strlen(buf
)))
5402 buf
= io_get(&io
, 0, TRUE
);
5406 /* Collapse all modified entries that follow an
5407 * associated unmerged entry. */
5408 if (unmerged
== file
) {
5409 unmerged
->status
= 'U';
5411 } else if (file
->status
== 'U') {
5416 /* Grab the old name for rename/copy. */
5417 if (!*file
->old
.name
&&
5418 (file
->status
== 'R' || file
->status
== 'C')) {
5419 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5421 buf
= io_get(&io
, 0, TRUE
);
5426 /* git-ls-files just delivers a NUL separated list of
5427 * file names similar to the second half of the
5428 * git-diff-* output. */
5429 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5430 if (!*file
->old
.name
)
5431 string_copy(file
->old
.name
, file
->new.name
);
5435 if (io_error(&io
)) {
5441 if (!view
->line
[view
->lines
- 1].data
)
5442 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5448 /* Don't show unmerged entries in the staged section. */
5449 static const char *status_diff_index_argv
[] = {
5450 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5451 "--cached", "-M", "HEAD", NULL
5454 static const char *status_diff_files_argv
[] = {
5455 "git", "diff-files", "-z", NULL
5458 static const char *status_list_other_argv
[] = {
5459 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix
, NULL
5462 static const char *status_list_no_head_argv
[] = {
5463 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5466 static const char *update_index_argv
[] = {
5467 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5470 /* Restore the previous line number to stay in the context or select a
5471 * line with something that can be updated. */
5473 status_restore(struct view
*view
)
5475 if (view
->p_lineno
>= view
->lines
)
5476 view
->p_lineno
= view
->lines
- 1;
5477 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5479 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5482 /* If the above fails, always skip the "On branch" line. */
5483 if (view
->p_lineno
< view
->lines
)
5484 view
->lineno
= view
->p_lineno
;
5488 if (view
->lineno
< view
->offset
)
5489 view
->offset
= view
->lineno
;
5490 else if (view
->offset
+ view
->height
<= view
->lineno
)
5491 view
->offset
= view
->lineno
- view
->height
+ 1;
5493 view
->p_restore
= FALSE
;
5497 status_update_onbranch(void)
5499 static const char *paths
[][2] = {
5500 { "rebase-apply/rebasing", "Rebasing" },
5501 { "rebase-apply/applying", "Applying mailbox" },
5502 { "rebase-apply/", "Rebasing mailbox" },
5503 { "rebase-merge/interactive", "Interactive rebase" },
5504 { "rebase-merge/", "Rebase merge" },
5505 { "MERGE_HEAD", "Merging" },
5506 { "BISECT_LOG", "Bisecting" },
5507 { "HEAD", "On branch" },
5509 char buf
[SIZEOF_STR
];
5513 if (is_initial_commit()) {
5514 string_copy(status_onbranch
, "Initial commit");
5518 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5519 char *head
= opt_head
;
5521 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5522 lstat(buf
, &stat
) < 0)
5528 if (io_open(&io
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5529 io_read_buf(&io
, buf
, sizeof(buf
))) {
5531 if (!prefixcmp(head
, "refs/heads/"))
5532 head
+= STRING_SIZE("refs/heads/");
5536 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5537 string_copy(status_onbranch
, opt_head
);
5541 string_copy(status_onbranch
, "Not currently on any branch");
5544 /* First parse staged info using git-diff-index(1), then parse unstaged
5545 * info using git-diff-files(1), and finally untracked files using
5546 * git-ls-files(1). */
5548 status_open(struct view
*view
)
5552 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5553 status_update_onbranch();
5555 run_io_bg(update_index_argv
);
5557 if (is_initial_commit()) {
5558 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5560 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5564 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5565 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5568 /* Restore the exact position or use the specialized restore
5570 if (!view
->p_restore
)
5571 status_restore(view
);
5576 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5578 struct status
*status
= line
->data
;
5579 enum line_type type
;
5583 switch (line
->type
) {
5584 case LINE_STAT_STAGED
:
5585 type
= LINE_STAT_SECTION
;
5586 text
= "Changes to be committed:";
5589 case LINE_STAT_UNSTAGED
:
5590 type
= LINE_STAT_SECTION
;
5591 text
= "Changed but not updated:";
5594 case LINE_STAT_UNTRACKED
:
5595 type
= LINE_STAT_SECTION
;
5596 text
= "Untracked files:";
5599 case LINE_STAT_NONE
:
5600 type
= LINE_DEFAULT
;
5601 text
= " (no files)";
5604 case LINE_STAT_HEAD
:
5605 type
= LINE_STAT_HEAD
;
5606 text
= status_onbranch
;
5613 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5615 buf
[0] = status
->status
;
5616 if (draw_text(view
, line
->type
, buf
, TRUE
))
5618 type
= LINE_DEFAULT
;
5619 text
= status
->new.name
;
5622 draw_text(view
, type
, text
, TRUE
);
5627 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5629 if (displayed_views() == 2 || display
[current_view
] != view
)
5630 maximize_view(view
);
5631 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5636 status_enter(struct view
*view
, struct line
*line
)
5638 struct status
*status
= line
->data
;
5639 const char *oldpath
= status
? status
->old
.name
: NULL
;
5640 /* Diffs for unmerged entries are empty when passing the new
5641 * path, so leave it empty. */
5642 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5644 enum open_flags split
;
5645 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5647 if (line
->type
== LINE_STAT_NONE
||
5648 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5649 report("No file to diff");
5653 switch (line
->type
) {
5654 case LINE_STAT_STAGED
:
5655 if (is_initial_commit()) {
5656 const char *no_head_diff_argv
[] = {
5657 "git", "diff", "--no-color", "--patch-with-stat",
5658 "--", "/dev/null", newpath
, NULL
5661 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
, FORMAT_DASH
))
5662 return status_load_error(view
, stage
, newpath
);
5664 const char *index_show_argv
[] = {
5665 "git", "diff-index", "--root", "--patch-with-stat",
5666 "-C", "-M", "--cached", "HEAD", "--",
5667 oldpath
, newpath
, NULL
5670 if (!prepare_update(stage
, index_show_argv
, opt_cdup
, FORMAT_DASH
))
5671 return status_load_error(view
, stage
, newpath
);
5675 info
= "Staged changes to %s";
5677 info
= "Staged changes";
5680 case LINE_STAT_UNSTAGED
:
5682 const char *files_show_argv
[] = {
5683 "git", "diff-files", "--root", "--patch-with-stat",
5684 "-C", "-M", "--", oldpath
, newpath
, NULL
5687 if (!prepare_update(stage
, files_show_argv
, opt_cdup
, FORMAT_DASH
))
5688 return status_load_error(view
, stage
, newpath
);
5690 info
= "Unstaged changes to %s";
5692 info
= "Unstaged changes";
5695 case LINE_STAT_UNTRACKED
:
5697 report("No file to show");
5701 if (!suffixcmp(status
->new.name
, -1, "/")) {
5702 report("Cannot display a directory");
5706 if (!prepare_update_file(stage
, newpath
))
5707 return status_load_error(view
, stage
, newpath
);
5708 info
= "Untracked file %s";
5711 case LINE_STAT_HEAD
:
5715 die("line type %d not handled in switch", line
->type
);
5718 split
= view_is_displayed(view
) ? OPEN_SPLIT
: 0;
5719 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5720 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5722 stage_status
= *status
;
5724 memset(&stage_status
, 0, sizeof(stage_status
));
5727 stage_line_type
= line
->type
;
5729 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5736 status_exists(struct status
*status
, enum line_type type
)
5738 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5739 unsigned long lineno
;
5741 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5742 struct line
*line
= &view
->line
[lineno
];
5743 struct status
*pos
= line
->data
;
5745 if (line
->type
!= type
)
5747 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5748 select_view_line(view
, lineno
);
5751 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5752 select_view_line(view
, lineno
);
5762 status_update_prepare(struct io
*io
, enum line_type type
)
5764 const char *staged_argv
[] = {
5765 "git", "update-index", "-z", "--index-info", NULL
5767 const char *others_argv
[] = {
5768 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5772 case LINE_STAT_STAGED
:
5773 return run_io(io
, staged_argv
, opt_cdup
, IO_WR
);
5775 case LINE_STAT_UNSTAGED
:
5776 case LINE_STAT_UNTRACKED
:
5777 return run_io(io
, others_argv
, opt_cdup
, IO_WR
);
5780 die("line type %d not handled in switch", type
);
5786 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5788 char buf
[SIZEOF_STR
];
5792 case LINE_STAT_STAGED
:
5793 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5796 status
->old
.name
, 0))
5800 case LINE_STAT_UNSTAGED
:
5801 case LINE_STAT_UNTRACKED
:
5802 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
5807 die("line type %d not handled in switch", type
);
5810 return io_write(io
, buf
, bufsize
);
5814 status_update_file(struct status
*status
, enum line_type type
)
5819 if (!status_update_prepare(&io
, type
))
5822 result
= status_update_write(&io
, status
, type
);
5823 return done_io(&io
) && result
;
5827 status_update_files(struct view
*view
, struct line
*line
)
5829 char buf
[sizeof(view
->ref
)];
5832 struct line
*pos
= view
->line
+ view
->lines
;
5835 int cursor_y
= -1, cursor_x
= -1;
5837 if (!status_update_prepare(&io
, line
->type
))
5840 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
5843 string_copy(buf
, view
->ref
);
5844 getsyx(cursor_y
, cursor_x
);
5845 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
5846 int almost_done
= file
* 100 / files
;
5848 if (almost_done
> done
) {
5850 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
5852 update_view_title(view
);
5853 setsyx(cursor_y
, cursor_x
);
5856 result
= status_update_write(&io
, line
->data
, line
->type
);
5858 string_copy(view
->ref
, buf
);
5860 return done_io(&io
) && result
;
5864 status_update(struct view
*view
)
5866 struct line
*line
= &view
->line
[view
->lineno
];
5868 assert(view
->lines
);
5871 /* This should work even for the "On branch" line. */
5872 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
5873 report("Nothing to update");
5877 if (!status_update_files(view
, line
+ 1)) {
5878 report("Failed to update file status");
5882 } else if (!status_update_file(line
->data
, line
->type
)) {
5883 report("Failed to update file status");
5891 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
5893 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
5894 if (type
== LINE_STAT_STAGED
) {
5895 report("Cannot revert changes to staged files");
5896 } else if (type
== LINE_STAT_UNTRACKED
) {
5897 report("Cannot revert changes to untracked files");
5898 } else if (has_none
) {
5899 report("Nothing to revert");
5901 report("Cannot revert changes to multiple files");
5904 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5905 char mode
[10] = "100644";
5906 const char *reset_argv
[] = {
5907 "git", "update-index", "--cacheinfo", mode
,
5908 status
->old
.rev
, status
->old
.name
, NULL
5910 const char *checkout_argv
[] = {
5911 "git", "checkout", "--", status
->old
.name
, NULL
5914 if (status
->status
== 'U') {
5915 string_format(mode
, "%5o", status
->old
.mode
);
5917 if (status
->old
.mode
== 0 && status
->new.mode
== 0) {
5918 reset_argv
[2] = "--force-remove";
5919 reset_argv
[3] = status
->old
.name
;
5920 reset_argv
[4] = NULL
;
5923 if (!run_io_fg(reset_argv
, opt_cdup
))
5925 if (status
->old
.mode
== 0 && status
->new.mode
== 0)
5929 return run_io_fg(checkout_argv
, opt_cdup
);
5936 status_request(struct view
*view
, enum request request
, struct line
*line
)
5938 struct status
*status
= line
->data
;
5941 case REQ_STATUS_UPDATE
:
5942 if (!status_update(view
))
5946 case REQ_STATUS_REVERT
:
5947 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
5951 case REQ_STATUS_MERGE
:
5952 if (!status
|| status
->status
!= 'U') {
5953 report("Merging only possible for files with unmerged status ('U').");
5956 open_mergetool(status
->new.name
);
5962 if (status
->status
== 'D') {
5963 report("File has been deleted.");
5967 open_editor(status
->new.name
);
5970 case REQ_VIEW_BLAME
:
5976 /* After returning the status view has been split to
5977 * show the stage view. No further reloading is
5979 return status_enter(view
, line
);
5982 /* Simply reload the view. */
5989 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
5995 status_select(struct view
*view
, struct line
*line
)
5997 struct status
*status
= line
->data
;
5998 char file
[SIZEOF_STR
] = "all files";
6002 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
6005 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
6008 switch (line
->type
) {
6009 case LINE_STAT_STAGED
:
6010 text
= "Press %s to unstage %s for commit";
6013 case LINE_STAT_UNSTAGED
:
6014 text
= "Press %s to stage %s for commit";
6017 case LINE_STAT_UNTRACKED
:
6018 text
= "Press %s to stage %s for addition";
6021 case LINE_STAT_HEAD
:
6022 case LINE_STAT_NONE
:
6023 text
= "Nothing to update";
6027 die("line type %d not handled in switch", line
->type
);
6030 if (status
&& status
->status
== 'U') {
6031 text
= "Press %s to resolve conflict in %s";
6032 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
6035 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
6038 string_format(view
->ref
, text
, key
, file
);
6040 string_copy(opt_file
, status
->new.name
);
6044 status_grep(struct view
*view
, struct line
*line
)
6046 struct status
*status
= line
->data
;
6049 const char buf
[2] = { status
->status
, 0 };
6050 const char *text
[] = { status
->new.name
, buf
, NULL
};
6052 return grep_text(view
, text
);
6058 static struct view_ops status_ops
= {
6071 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
6073 while (line
< end
) {
6074 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
6075 !io_write(io
, "\n", 1))
6078 if (line
->type
== LINE_DIFF_CHUNK
||
6079 line
->type
== LINE_DIFF_HEADER
)
6086 static struct line
*
6087 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
6089 for (; view
->line
< line
; line
--)
6090 if (line
->type
== type
)
6097 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
6099 const char *apply_argv
[SIZEOF_ARG
] = {
6100 "git", "apply", "--whitespace=nowarn", NULL
6102 struct line
*diff_hdr
;
6106 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6111 apply_argv
[argc
++] = "--cached";
6112 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6113 apply_argv
[argc
++] = "-R";
6114 apply_argv
[argc
++] = "-";
6115 apply_argv
[argc
++] = NULL
;
6116 if (!run_io(&io
, apply_argv
, opt_cdup
, IO_WR
))
6119 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6120 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6124 run_io_bg(update_index_argv
);
6126 return chunk
? TRUE
: FALSE
;
6130 stage_update(struct view
*view
, struct line
*line
)
6132 struct line
*chunk
= NULL
;
6134 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6135 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6138 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6139 report("Failed to apply chunk");
6143 } else if (!stage_status
.status
) {
6144 view
= VIEW(REQ_VIEW_STATUS
);
6146 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6147 if (line
->type
== stage_line_type
)
6150 if (!status_update_files(view
, line
+ 1)) {
6151 report("Failed to update files");
6155 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6156 report("Failed to update file");
6164 stage_revert(struct view
*view
, struct line
*line
)
6166 struct line
*chunk
= NULL
;
6168 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6169 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6172 if (!prompt_yesno("Are you sure you want to revert changes?"))
6175 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6176 report("Failed to revert chunk");
6182 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6183 stage_line_type
, FALSE
);
6189 stage_next(struct view
*view
, struct line
*line
)
6193 if (!stage_chunks
) {
6194 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6195 if (line
->type
!= LINE_DIFF_CHUNK
)
6198 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6199 report("Allocation failure");
6203 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6207 for (i
= 0; i
< stage_chunks
; i
++) {
6208 if (stage_chunk
[i
] > view
->lineno
) {
6209 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6210 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6215 report("No next chunk found");
6219 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6222 case REQ_STATUS_UPDATE
:
6223 if (!stage_update(view
, line
))
6227 case REQ_STATUS_REVERT
:
6228 if (!stage_revert(view
, line
))
6232 case REQ_STAGE_NEXT
:
6233 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6234 report("File is untracked; press %s to add",
6235 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6238 stage_next(view
, line
);
6242 if (!stage_status
.new.name
[0])
6244 if (stage_status
.status
== 'D') {
6245 report("File has been deleted.");
6249 open_editor(stage_status
.new.name
);
6253 /* Reload everything ... */
6256 case REQ_VIEW_BLAME
:
6257 if (stage_status
.new.name
[0]) {
6258 string_copy(opt_file
, stage_status
.new.name
);
6264 return pager_request(view
, request
, line
);
6270 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6271 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6273 /* Check whether the staged entry still exists, and close the
6274 * stage view if it doesn't. */
6275 if (!status_exists(&stage_status
, stage_line_type
)) {
6276 status_restore(VIEW(REQ_VIEW_STATUS
));
6277 return REQ_VIEW_CLOSE
;
6280 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6281 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6282 report("Cannot display a directory");
6286 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6287 report("Failed to open file: %s", strerror(errno
));
6291 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6296 static struct view_ops stage_ops
= {
6313 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6314 char title
[128]; /* First line of the commit message. */
6315 const char *author
; /* Author of the commit. */
6316 struct time time
; /* Date from the author ident. */
6317 struct ref_list
*refs
; /* Repository references. */
6318 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6319 size_t graph_size
; /* The width of the graph array. */
6320 bool has_parents
; /* Rewritten --parents seen. */
6323 /* Size of rev graph with no "padding" columns */
6324 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6327 struct rev_graph
*prev
, *next
, *parents
;
6328 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6330 struct commit
*commit
;
6332 unsigned int boundary
:1;
6335 /* Parents of the commit being visualized. */
6336 static struct rev_graph graph_parents
[4];
6338 /* The current stack of revisions on the graph. */
6339 static struct rev_graph graph_stacks
[4] = {
6340 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6341 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6342 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6343 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6347 graph_parent_is_merge(struct rev_graph
*graph
)
6349 return graph
->parents
->size
> 1;
6353 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6355 struct commit
*commit
= graph
->commit
;
6357 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6358 commit
->graph
[commit
->graph_size
++] = symbol
;
6362 clear_rev_graph(struct rev_graph
*graph
)
6364 graph
->boundary
= 0;
6365 graph
->size
= graph
->pos
= 0;
6366 graph
->commit
= NULL
;
6367 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6371 done_rev_graph(struct rev_graph
*graph
)
6373 if (graph_parent_is_merge(graph
) &&
6374 graph
->pos
< graph
->size
- 1 &&
6375 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6376 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6378 graph
->commit
->graph_size
= i
* 2;
6379 while (i
< graph
->next
->size
- 1) {
6380 append_to_rev_graph(graph
, ' ');
6381 append_to_rev_graph(graph
, '\\');
6386 clear_rev_graph(graph
);
6390 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6394 /* "Collapse" duplicate parents lines.
6396 * FIXME: This needs to also update update the drawn graph but
6397 * for now it just serves as a method for pruning graph lines. */
6398 for (i
= 0; i
< graph
->size
; i
++)
6399 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6402 if (graph
->size
< SIZEOF_REVITEMS
) {
6403 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6408 get_rev_graph_symbol(struct rev_graph
*graph
)
6412 if (graph
->boundary
)
6413 symbol
= REVGRAPH_BOUND
;
6414 else if (graph
->parents
->size
== 0)
6415 symbol
= REVGRAPH_INIT
;
6416 else if (graph_parent_is_merge(graph
))
6417 symbol
= REVGRAPH_MERGE
;
6418 else if (graph
->pos
>= graph
->size
)
6419 symbol
= REVGRAPH_BRANCH
;
6421 symbol
= REVGRAPH_COMMIT
;
6427 draw_rev_graph(struct rev_graph
*graph
)
6430 chtype separator
, line
;
6432 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6433 static struct rev_filler fillers
[] = {
6439 chtype symbol
= get_rev_graph_symbol(graph
);
6440 struct rev_filler
*filler
;
6443 if (opt_line_graphics
)
6444 fillers
[DEFAULT
].line
= line_graphics
[LINE_GRAPHIC_VLINE
];
6446 filler
= &fillers
[DEFAULT
];
6448 for (i
= 0; i
< graph
->pos
; i
++) {
6449 append_to_rev_graph(graph
, filler
->line
);
6450 if (graph_parent_is_merge(graph
->prev
) &&
6451 graph
->prev
->pos
== i
)
6452 filler
= &fillers
[RSHARP
];
6454 append_to_rev_graph(graph
, filler
->separator
);
6457 /* Place the symbol for this revision. */
6458 append_to_rev_graph(graph
, symbol
);
6460 if (graph
->prev
->size
> graph
->size
)
6461 filler
= &fillers
[RDIAG
];
6463 filler
= &fillers
[DEFAULT
];
6467 for (; i
< graph
->size
; i
++) {
6468 append_to_rev_graph(graph
, filler
->separator
);
6469 append_to_rev_graph(graph
, filler
->line
);
6470 if (graph_parent_is_merge(graph
->prev
) &&
6471 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6472 filler
= &fillers
[RSHARP
];
6473 if (graph
->prev
->size
> graph
->size
)
6474 filler
= &fillers
[LDIAG
];
6477 if (graph
->prev
->size
> graph
->size
) {
6478 append_to_rev_graph(graph
, filler
->separator
);
6479 if (filler
->line
!= ' ')
6480 append_to_rev_graph(graph
, filler
->line
);
6484 /* Prepare the next rev graph */
6486 prepare_rev_graph(struct rev_graph
*graph
)
6490 /* First, traverse all lines of revisions up to the active one. */
6491 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6492 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6495 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6498 /* Interleave the new revision parent(s). */
6499 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6500 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6502 /* Lastly, put any remaining revisions. */
6503 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6504 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6508 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6510 /* If this is the finalizing update ... */
6512 prepare_rev_graph(graph
);
6514 /* Graph visualization needs a one rev look-ahead,
6515 * so the first update doesn't visualize anything. */
6516 if (!graph
->prev
->commit
)
6519 if (view
->lines
> 2)
6520 view
->line
[view
->lines
- 3].dirty
= 1;
6521 if (view
->lines
> 1)
6522 view
->line
[view
->lines
- 2].dirty
= 1;
6523 draw_rev_graph(graph
->prev
);
6524 done_rev_graph(graph
->prev
->prev
);
6532 static const char *main_argv
[SIZEOF_ARG
] = {
6533 "git", "log", "--no-color", "--pretty=raw", "--parents",
6534 "--topo-order", "%(head)", NULL
6538 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6540 struct commit
*commit
= line
->data
;
6542 if (!commit
->author
)
6545 if (opt_date
&& draw_date(view
, &commit
->time
))
6548 if (opt_author
&& draw_author(view
, commit
->author
))
6551 if (opt_rev_graph
&& commit
->graph_size
&&
6552 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6555 if (opt_show_refs
&& commit
->refs
) {
6558 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6559 struct ref
*ref
= commit
->refs
->refs
[i
];
6560 enum line_type type
;
6563 type
= LINE_MAIN_HEAD
;
6565 type
= LINE_MAIN_LOCAL_TAG
;
6567 type
= LINE_MAIN_TAG
;
6568 else if (ref
->tracked
)
6569 type
= LINE_MAIN_TRACKED
;
6570 else if (ref
->remote
)
6571 type
= LINE_MAIN_REMOTE
;
6573 type
= LINE_MAIN_REF
;
6575 if (draw_text(view
, type
, "[", TRUE
) ||
6576 draw_text(view
, type
, ref
->name
, TRUE
) ||
6577 draw_text(view
, type
, "]", TRUE
))
6580 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6585 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6589 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6591 main_read(struct view
*view
, char *line
)
6593 static struct rev_graph
*graph
= graph_stacks
;
6594 enum line_type type
;
6595 struct commit
*commit
;
6600 if (!view
->lines
&& !view
->parent
)
6601 die("No revisions match the given arguments.");
6602 if (view
->lines
> 0) {
6603 commit
= view
->line
[view
->lines
- 1].data
;
6604 view
->line
[view
->lines
- 1].dirty
= 1;
6605 if (!commit
->author
) {
6608 graph
->commit
= NULL
;
6611 update_rev_graph(view
, graph
);
6613 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6614 clear_rev_graph(&graph_stacks
[i
]);
6618 type
= get_line_type(line
);
6619 if (type
== LINE_COMMIT
) {
6620 commit
= calloc(1, sizeof(struct commit
));
6624 line
+= STRING_SIZE("commit ");
6626 graph
->boundary
= 1;
6630 string_copy_rev(commit
->id
, line
);
6631 commit
->refs
= get_ref_list(commit
->id
);
6632 graph
->commit
= commit
;
6633 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6635 while ((line
= strchr(line
, ' '))) {
6637 push_rev_graph(graph
->parents
, line
);
6638 commit
->has_parents
= TRUE
;
6645 commit
= view
->line
[view
->lines
- 1].data
;
6649 if (commit
->has_parents
)
6651 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6655 parse_author_line(line
+ STRING_SIZE("author "),
6656 &commit
->author
, &commit
->time
);
6657 update_rev_graph(view
, graph
);
6658 graph
= graph
->next
;
6662 /* Fill in the commit title if it has not already been set. */
6663 if (commit
->title
[0])
6666 /* Require titles to start with a non-space character at the
6667 * offset used by git log. */
6668 if (strncmp(line
, " ", 4))
6671 /* Well, if the title starts with a whitespace character,
6672 * try to be forgiving. Otherwise we end up with no title. */
6673 while (isspace(*line
))
6677 /* FIXME: More graceful handling of titles; append "..." to
6678 * shortened titles, etc. */
6680 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6681 view
->line
[view
->lines
- 1].dirty
= 1;
6688 main_request(struct view
*view
, enum request request
, struct line
*line
)
6690 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
6694 open_view(view
, REQ_VIEW_DIFF
, flags
);
6698 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6708 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6713 if (!opt_show_refs
|| !list
)
6716 for (i
= 0; i
< list
->size
; i
++) {
6717 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6725 main_grep(struct view
*view
, struct line
*line
)
6727 struct commit
*commit
= line
->data
;
6728 const char *text
[] = {
6730 opt_author
? commit
->author
: "",
6731 opt_date
? mkdate(&commit
->time
) : "",
6735 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6739 main_select(struct view
*view
, struct line
*line
)
6741 struct commit
*commit
= line
->data
;
6743 string_copy_rev(view
->ref
, commit
->id
);
6744 string_copy_rev(ref_commit
, view
->ref
);
6747 static struct view_ops main_ops
= {
6760 * Unicode / UTF-8 handling
6762 * NOTE: Much of the following code for dealing with Unicode is derived from
6763 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6764 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6768 unicode_width(unsigned long c
)
6771 (c
<= 0x115f /* Hangul Jamo */
6774 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
6776 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
6777 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
6778 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
6779 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
6780 || (c
>= 0xffe0 && c
<= 0xffe6)
6781 || (c
>= 0x20000 && c
<= 0x2fffd)
6782 || (c
>= 0x30000 && c
<= 0x3fffd)))
6786 return opt_tab_size
;
6791 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6792 * Illegal bytes are set one. */
6793 static const unsigned char utf8_bytes
[256] = {
6794 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,
6795 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,
6796 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,
6797 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,
6798 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,
6799 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,
6800 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,
6801 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,
6804 static inline unsigned char
6805 utf8_char_length(const char *string
, const char *end
)
6807 int c
= *(unsigned char *) string
;
6809 return utf8_bytes
[c
];
6812 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6813 static inline unsigned long
6814 utf8_to_unicode(const char *string
, size_t length
)
6816 unsigned long unicode
;
6820 unicode
= string
[0];
6823 unicode
= (string
[0] & 0x1f) << 6;
6824 unicode
+= (string
[1] & 0x3f);
6827 unicode
= (string
[0] & 0x0f) << 12;
6828 unicode
+= ((string
[1] & 0x3f) << 6);
6829 unicode
+= (string
[2] & 0x3f);
6832 unicode
= (string
[0] & 0x0f) << 18;
6833 unicode
+= ((string
[1] & 0x3f) << 12);
6834 unicode
+= ((string
[2] & 0x3f) << 6);
6835 unicode
+= (string
[3] & 0x3f);
6838 unicode
= (string
[0] & 0x0f) << 24;
6839 unicode
+= ((string
[1] & 0x3f) << 18);
6840 unicode
+= ((string
[2] & 0x3f) << 12);
6841 unicode
+= ((string
[3] & 0x3f) << 6);
6842 unicode
+= (string
[4] & 0x3f);
6845 unicode
= (string
[0] & 0x01) << 30;
6846 unicode
+= ((string
[1] & 0x3f) << 24);
6847 unicode
+= ((string
[2] & 0x3f) << 18);
6848 unicode
+= ((string
[3] & 0x3f) << 12);
6849 unicode
+= ((string
[4] & 0x3f) << 6);
6850 unicode
+= (string
[5] & 0x3f);
6853 die("Invalid Unicode length");
6856 /* Invalid characters could return the special 0xfffd value but NUL
6857 * should be just as good. */
6858 return unicode
> 0xffff ? 0 : unicode
;
6861 /* Calculates how much of string can be shown within the given maximum width
6862 * and sets trimmed parameter to non-zero value if all of string could not be
6863 * shown. If the reserve flag is TRUE, it will reserve at least one
6864 * trailing character, which can be useful when drawing a delimiter.
6866 * Returns the number of bytes to output from string to satisfy max_width. */
6868 utf8_length(const char **start
, size_t skip
, int *width
, size_t max_width
, int *trimmed
, bool reserve
)
6870 const char *string
= *start
;
6871 const char *end
= strchr(string
, '\0');
6872 unsigned char last_bytes
= 0;
6873 size_t last_ucwidth
= 0;
6878 while (string
< end
) {
6879 unsigned char bytes
= utf8_char_length(string
, end
);
6881 unsigned long unicode
;
6883 if (string
+ bytes
> end
)
6886 /* Change representation to figure out whether
6887 * it is a single- or double-width character. */
6889 unicode
= utf8_to_unicode(string
, bytes
);
6890 /* FIXME: Graceful handling of invalid Unicode character. */
6894 ucwidth
= unicode_width(unicode
);
6896 skip
-= ucwidth
<= skip
? ucwidth
: skip
;
6900 if (*width
> max_width
) {
6903 if (reserve
&& *width
== max_width
) {
6904 string
-= last_bytes
;
6905 *width
-= last_ucwidth
;
6911 last_bytes
= ucwidth
? bytes
: 0;
6912 last_ucwidth
= ucwidth
;
6915 return string
- *start
;
6923 /* Whether or not the curses interface has been initialized. */
6924 static bool cursed
= FALSE
;
6926 /* Terminal hacks and workarounds. */
6927 static bool use_scroll_redrawwin
;
6928 static bool use_scroll_status_wclear
;
6930 /* The status window is used for polling keystrokes. */
6931 static WINDOW
*status_win
;
6933 /* Reading from the prompt? */
6934 static bool input_mode
= FALSE
;
6936 static bool status_empty
= FALSE
;
6938 /* Update status and title window. */
6940 report(const char *msg
, ...)
6942 struct view
*view
= display
[current_view
];
6948 char buf
[SIZEOF_STR
];
6951 va_start(args
, msg
);
6952 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6953 buf
[sizeof(buf
) - 1] = 0;
6954 buf
[sizeof(buf
) - 2] = '.';
6955 buf
[sizeof(buf
) - 3] = '.';
6956 buf
[sizeof(buf
) - 4] = '.';
6962 if (!status_empty
|| *msg
) {
6965 va_start(args
, msg
);
6967 wmove(status_win
, 0, 0);
6968 if (view
->has_scrolled
&& use_scroll_status_wclear
)
6971 vwprintw(status_win
, msg
, args
);
6972 status_empty
= FALSE
;
6974 status_empty
= TRUE
;
6976 wclrtoeol(status_win
);
6977 wnoutrefresh(status_win
);
6982 update_view_title(view
);
6985 /* Controls when nodelay should be in effect when polling user input. */
6987 set_nonblocking_input(bool loading
)
6989 static unsigned int loading_views
;
6991 if ((loading
== FALSE
&& loading_views
-- == 1) ||
6992 (loading
== TRUE
&& loading_views
++ == 0))
6993 nodelay(status_win
, loading
);
7002 /* Initialize the curses library */
7003 if (isatty(STDIN_FILENO
)) {
7004 cursed
= !!initscr();
7007 /* Leave stdin and stdout alone when acting as a pager. */
7008 opt_tty
= fopen("/dev/tty", "r+");
7010 die("Failed to open /dev/tty");
7011 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
7015 die("Failed to initialize curses");
7017 nonl(); /* Disable conversion and detect newlines from input. */
7018 cbreak(); /* Take input chars one at a time, no wait for \n */
7019 noecho(); /* Don't echo input */
7020 leaveok(stdscr
, FALSE
);
7025 getmaxyx(stdscr
, y
, x
);
7026 status_win
= newwin(1, 0, y
- 1, 0);
7028 die("Failed to create status window");
7030 /* Enable keyboard mapping */
7031 keypad(status_win
, TRUE
);
7032 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
7034 TABSIZE
= opt_tab_size
;
7035 if (opt_line_graphics
) {
7036 line_graphics
[LINE_GRAPHIC_VLINE
] = ACS_VLINE
;
7039 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
7040 if (term
&& !strcmp(term
, "gnome-terminal")) {
7041 /* In the gnome-terminal-emulator, the message from
7042 * scrolling up one line when impossible followed by
7043 * scrolling down one line causes corruption of the
7044 * status line. This is fixed by calling wclear. */
7045 use_scroll_status_wclear
= TRUE
;
7046 use_scroll_redrawwin
= FALSE
;
7048 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
7049 /* No problems with full optimizations in xrvt-(unicode)
7051 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
7054 /* When scrolling in (u)xterm the last line in the
7055 * scrolling direction will update slowly. */
7056 use_scroll_redrawwin
= TRUE
;
7057 use_scroll_status_wclear
= FALSE
;
7062 get_input(int prompt_position
)
7065 int i
, key
, cursor_y
, cursor_x
;
7067 if (prompt_position
)
7071 foreach_view (view
, i
) {
7073 if (view_is_displayed(view
) && view
->has_scrolled
&&
7074 use_scroll_redrawwin
)
7075 redrawwin(view
->win
);
7076 view
->has_scrolled
= FALSE
;
7079 /* Update the cursor position. */
7080 if (prompt_position
) {
7081 getbegyx(status_win
, cursor_y
, cursor_x
);
7082 cursor_x
= prompt_position
;
7084 view
= display
[current_view
];
7085 getbegyx(view
->win
, cursor_y
, cursor_x
);
7086 cursor_x
= view
->width
- 1;
7087 cursor_y
+= view
->lineno
- view
->offset
;
7089 setsyx(cursor_y
, cursor_x
);
7091 /* Refresh, accept single keystroke of input */
7093 key
= wgetch(status_win
);
7095 /* wgetch() with nodelay() enabled returns ERR when
7096 * there's no input. */
7099 } else if (key
== KEY_RESIZE
) {
7102 getmaxyx(stdscr
, height
, width
);
7104 wresize(status_win
, 1, width
);
7105 mvwin(status_win
, height
- 1, 0);
7106 wnoutrefresh(status_win
);
7108 redraw_display(TRUE
);
7118 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7120 enum input_status status
= INPUT_OK
;
7121 static char buf
[SIZEOF_STR
];
7126 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7129 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7130 wclrtoeol(status_win
);
7132 key
= get_input(pos
+ 1);
7137 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7144 status
= INPUT_CANCEL
;
7148 status
= INPUT_CANCEL
;
7152 if (pos
>= sizeof(buf
)) {
7153 report("Input string too long");
7157 status
= handler(data
, buf
, key
);
7158 if (status
== INPUT_OK
)
7159 buf
[pos
++] = (char) key
;
7163 /* Clear the status window */
7164 status_empty
= FALSE
;
7167 if (status
== INPUT_CANCEL
)
7175 static enum input_status
7176 prompt_yesno_handler(void *data
, char *buf
, int c
)
7178 if (c
== 'y' || c
== 'Y')
7180 if (c
== 'n' || c
== 'N')
7181 return INPUT_CANCEL
;
7186 prompt_yesno(const char *prompt
)
7188 char prompt2
[SIZEOF_STR
];
7190 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7193 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7196 static enum input_status
7197 read_prompt_handler(void *data
, char *buf
, int c
)
7199 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7203 read_prompt(const char *prompt
)
7205 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7208 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7210 enum input_status status
= INPUT_OK
;
7213 while (items
[size
].text
)
7216 while (status
== INPUT_OK
) {
7217 const struct menu_item
*item
= &items
[*selected
];
7221 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7222 prompt
, *selected
+ 1, size
);
7224 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7225 wprintw(status_win
, "%s", item
->text
);
7226 wclrtoeol(status_win
);
7228 key
= get_input(COLS
- 1);
7233 status
= INPUT_STOP
;
7238 *selected
= *selected
- 1;
7240 *selected
= size
- 1;
7245 *selected
= (*selected
+ 1) % size
;
7249 status
= INPUT_CANCEL
;
7253 for (i
= 0; items
[i
].text
; i
++)
7254 if (items
[i
].hotkey
== key
) {
7256 status
= INPUT_STOP
;
7262 /* Clear the status window */
7263 status_empty
= FALSE
;
7266 return status
!= INPUT_CANCEL
;
7270 * Repository properties
7273 static struct ref
**refs
= NULL
;
7274 static size_t refs_size
= 0;
7276 static struct ref_list
**ref_lists
= NULL
;
7277 static size_t ref_lists_size
= 0;
7279 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7280 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7281 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7284 compare_refs(const void *ref1_
, const void *ref2_
)
7286 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7287 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7289 if (ref1
->tag
!= ref2
->tag
)
7290 return ref2
->tag
- ref1
->tag
;
7291 if (ref1
->ltag
!= ref2
->ltag
)
7292 return ref2
->ltag
- ref2
->ltag
;
7293 if (ref1
->head
!= ref2
->head
)
7294 return ref2
->head
- ref1
->head
;
7295 if (ref1
->tracked
!= ref2
->tracked
)
7296 return ref2
->tracked
- ref1
->tracked
;
7297 if (ref1
->remote
!= ref2
->remote
)
7298 return ref2
->remote
- ref1
->remote
;
7299 return strcmp(ref1
->name
, ref2
->name
);
7303 foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
)
7307 for (i
= 0; i
< refs_size
; i
++)
7308 if (!visitor(data
, refs
[i
]))
7312 static struct ref_list
*
7313 get_ref_list(const char *id
)
7315 struct ref_list
*list
;
7318 for (i
= 0; i
< ref_lists_size
; i
++)
7319 if (!strcmp(id
, ref_lists
[i
]->id
))
7320 return ref_lists
[i
];
7322 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7324 list
= calloc(1, sizeof(*list
));
7328 for (i
= 0; i
< refs_size
; i
++) {
7329 if (!strcmp(id
, refs
[i
]->id
) &&
7330 realloc_refs_list(&list
->refs
, list
->size
, 1))
7331 list
->refs
[list
->size
++] = refs
[i
];
7339 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7340 ref_lists
[ref_lists_size
++] = list
;
7345 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
7347 struct ref
*ref
= NULL
;
7350 bool remote
= FALSE
;
7351 bool tracked
= FALSE
;
7353 int from
= 0, to
= refs_size
- 1;
7355 if (!prefixcmp(name
, "refs/tags/")) {
7356 if (!suffixcmp(name
, namelen
, "^{}")) {
7364 namelen
-= STRING_SIZE("refs/tags/");
7365 name
+= STRING_SIZE("refs/tags/");
7367 } else if (!prefixcmp(name
, "refs/remotes/")) {
7369 namelen
-= STRING_SIZE("refs/remotes/");
7370 name
+= STRING_SIZE("refs/remotes/");
7371 tracked
= !strcmp(opt_remote
, name
);
7373 } else if (!prefixcmp(name
, "refs/heads/")) {
7374 namelen
-= STRING_SIZE("refs/heads/");
7375 name
+= STRING_SIZE("refs/heads/");
7376 head
= !strncmp(opt_head
, name
, namelen
);
7378 } else if (!strcmp(name
, "HEAD")) {
7379 string_ncopy(opt_head_rev
, id
, idlen
);
7383 /* If we are reloading or it's an annotated tag, replace the
7384 * previous SHA1 with the resolved commit id; relies on the fact
7385 * git-ls-remote lists the commit id of an annotated tag right
7386 * before the commit id it points to. */
7387 while (from
<= to
) {
7388 size_t pos
= (to
+ from
) / 2;
7389 int cmp
= strcmp(name
, refs
[pos
]->name
);
7403 if (!realloc_refs(&refs
, refs_size
, 1))
7405 ref
= calloc(1, sizeof(*ref
) + namelen
);
7408 memmove(refs
+ from
+ 1, refs
+ from
,
7409 (refs_size
- from
) * sizeof(*refs
));
7411 strncpy(ref
->name
, name
, namelen
);
7418 ref
->remote
= remote
;
7419 ref
->tracked
= tracked
;
7420 string_copy_rev(ref
->id
, id
);
7428 const char *head_argv
[] = {
7429 "git", "symbolic-ref", "HEAD", NULL
7431 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7432 "git", "ls-remote", opt_git_dir
, NULL
7434 static bool init
= FALSE
;
7438 argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE");
7445 if (run_io_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7446 !prefixcmp(opt_head
, "refs/heads/")) {
7447 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7449 memmove(opt_head
, offset
, strlen(offset
) + 1);
7452 for (i
= 0; i
< refs_size
; i
++)
7455 if (run_io_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7458 /* Update the ref lists to reflect changes. */
7459 for (i
= 0; i
< ref_lists_size
; i
++) {
7460 struct ref_list
*list
= ref_lists
[i
];
7463 for (old
= new = 0; old
< list
->size
; old
++)
7464 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7465 list
->refs
[new++] = list
->refs
[old
];
7473 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7475 if (!strcmp(name
, ".remote")) {
7476 string_ncopy(opt_remote
, value
, valuelen
);
7478 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7479 size_t from
= strlen(opt_remote
);
7481 if (!prefixcmp(value
, "refs/heads/"))
7482 value
+= STRING_SIZE("refs/heads/");
7484 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7490 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7492 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7493 int argc
= 1 + (cmd
== option_set_command
);
7496 if (!argv_from_string(argv
, &argc
, value
))
7497 config_msg
= "Too many option arguments";
7499 error
= cmd(argc
, argv
);
7502 warn("Option 'tig.%s': %s", name
, config_msg
);
7506 set_environment_variable(const char *name
, const char *value
)
7508 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7509 char *env
= malloc(len
);
7512 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7520 set_work_tree(const char *value
)
7522 char cwd
[SIZEOF_STR
];
7524 if (!getcwd(cwd
, sizeof(cwd
)))
7525 die("Failed to get cwd path: %s", strerror(errno
));
7526 if (chdir(opt_git_dir
) < 0)
7527 die("Failed to chdir(%s): %s", strerror(errno
));
7528 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7529 die("Failed to get git path: %s", strerror(errno
));
7531 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7532 if (chdir(value
) < 0)
7533 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7534 if (!getcwd(cwd
, sizeof(cwd
)))
7535 die("Failed to get cwd path: %s", strerror(errno
));
7536 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7537 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7538 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7539 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7540 opt_is_inside_work_tree
= TRUE
;
7544 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7546 if (!strcmp(name
, "i18n.commitencoding"))
7547 string_ncopy(opt_encoding
, value
, valuelen
);
7549 else if (!strcmp(name
, "core.editor"))
7550 string_ncopy(opt_editor
, value
, valuelen
);
7552 else if (!strcmp(name
, "core.worktree"))
7553 set_work_tree(value
);
7555 else if (!prefixcmp(name
, "tig.color."))
7556 set_repo_config_option(name
+ 10, value
, option_color_command
);
7558 else if (!prefixcmp(name
, "tig.bind."))
7559 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7561 else if (!prefixcmp(name
, "tig."))
7562 set_repo_config_option(name
+ 4, value
, option_set_command
);
7564 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7565 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7566 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7572 load_git_config(void)
7574 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7576 return run_io_load(config_list_argv
, "=", read_repo_config_option
);
7580 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7582 if (!opt_git_dir
[0]) {
7583 string_ncopy(opt_git_dir
, name
, namelen
);
7585 } else if (opt_is_inside_work_tree
== -1) {
7586 /* This can be 3 different values depending on the
7587 * version of git being used. If git-rev-parse does not
7588 * understand --is-inside-work-tree it will simply echo
7589 * the option else either "true" or "false" is printed.
7590 * Default to true for the unknown case. */
7591 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7593 } else if (*name
== '.') {
7594 string_ncopy(opt_cdup
, name
, namelen
);
7597 string_ncopy(opt_prefix
, name
, namelen
);
7604 load_repo_info(void)
7606 const char *rev_parse_argv
[] = {
7607 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7608 "--show-cdup", "--show-prefix", NULL
7611 return run_io_load(rev_parse_argv
, "=", read_repo_info
);
7619 static const char usage
[] =
7620 "tig " TIG_VERSION
" (" __DATE__
")\n"
7622 "Usage: tig [options] [revs] [--] [paths]\n"
7623 " or: tig show [options] [revs] [--] [paths]\n"
7624 " or: tig blame [rev] path\n"
7626 " or: tig < [git command output]\n"
7629 " -v, --version Show version and exit\n"
7630 " -h, --help Show help message and exit";
7632 static void __NORETURN
7635 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7641 static void __NORETURN
7642 die(const char *err
, ...)
7648 va_start(args
, err
);
7649 fputs("tig: ", stderr
);
7650 vfprintf(stderr
, err
, args
);
7651 fputs("\n", stderr
);
7658 warn(const char *msg
, ...)
7662 va_start(args
, msg
);
7663 fputs("tig warning: ", stderr
);
7664 vfprintf(stderr
, msg
, args
);
7665 fputs("\n", stderr
);
7670 parse_options(int argc
, const char *argv
[])
7672 enum request request
= REQ_VIEW_MAIN
;
7673 const char *subcommand
;
7674 bool seen_dashdash
= FALSE
;
7675 /* XXX: This is vulnerable to the user overriding options
7676 * required for the main view parser. */
7677 const char *custom_argv
[SIZEOF_ARG
] = {
7678 "git", "log", "--no-color", "--pretty=raw", "--parents",
7679 "--topo-order", NULL
7683 if (!isatty(STDIN_FILENO
)) {
7684 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7685 return REQ_VIEW_PAGER
;
7691 subcommand
= argv
[1];
7692 if (!strcmp(subcommand
, "status")) {
7694 warn("ignoring arguments after `%s'", subcommand
);
7695 return REQ_VIEW_STATUS
;
7697 } else if (!strcmp(subcommand
, "blame")) {
7698 if (argc
<= 2 || argc
> 4)
7699 die("invalid number of options to blame\n\n%s", usage
);
7703 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7707 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7708 return REQ_VIEW_BLAME
;
7710 } else if (!strcmp(subcommand
, "show")) {
7711 request
= REQ_VIEW_DIFF
;
7718 custom_argv
[1] = subcommand
;
7722 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7723 const char *opt
= argv
[i
];
7725 if (seen_dashdash
|| !strcmp(opt
, "--")) {
7726 seen_dashdash
= TRUE
;
7728 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7729 printf("tig version %s\n", TIG_VERSION
);
7732 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7733 printf("%s\n", usage
);
7737 custom_argv
[j
++] = opt
;
7738 if (j
>= ARRAY_SIZE(custom_argv
))
7739 die("command too long");
7742 if (!prepare_update(VIEW(request
), custom_argv
, NULL
, FORMAT_NONE
))
7743 die("Failed to format arguments");
7749 main(int argc
, const char *argv
[])
7751 enum request request
= parse_options(argc
, argv
);
7755 signal(SIGINT
, quit
);
7756 signal(SIGPIPE
, SIG_IGN
);
7758 if (setlocale(LC_ALL
, "")) {
7759 char *codeset
= nl_langinfo(CODESET
);
7761 string_ncopy(opt_codeset
, codeset
, strlen(codeset
));
7764 if (load_repo_info() == ERR
)
7765 die("Failed to load repo info.");
7767 if (load_options() == ERR
)
7768 die("Failed to load user config.");
7770 if (load_git_config() == ERR
)
7771 die("Failed to load repo config.");
7773 /* Require a git repository unless when running in pager mode. */
7774 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7775 die("Not a git repository");
7777 if (*opt_encoding
&& strcmp(opt_codeset
, "UTF-8")) {
7778 opt_iconv_in
= iconv_open("UTF-8", opt_encoding
);
7779 if (opt_iconv_in
== ICONV_NONE
)
7780 die("Failed to initialize character set conversion");
7783 if (*opt_codeset
&& strcmp(opt_codeset
, "UTF-8")) {
7784 opt_iconv_out
= iconv_open(opt_codeset
, "UTF-8");
7785 if (opt_iconv_out
== ICONV_NONE
)
7786 die("Failed to initialize character set conversion");
7789 if (load_refs() == ERR
)
7790 die("Failed to load refs.");
7792 foreach_view (view
, i
)
7793 argv_from_env(view
->ops
->argv
, view
->cmd_env
);
7797 if (request
!= REQ_NONE
)
7798 open_view(NULL
, request
, OPEN_PREPARED
);
7799 request
= request
== REQ_NONE
? REQ_VIEW_MAIN
: REQ_NONE
;
7801 while (view_driver(display
[current_view
], request
)) {
7802 int key
= get_input(0);
7804 view
= display
[current_view
];
7805 request
= get_keybinding(view
->keymap
, key
);
7807 /* Some low-level request handling. This keeps access to
7808 * status_win restricted. */
7812 char *cmd
= read_prompt(":");
7814 if (cmd
&& isdigit(*cmd
)) {
7815 int lineno
= view
->lineno
+ 1;
7817 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7818 select_view_line(view
, lineno
- 1);
7821 report("Unable to parse '%s' as a line number", cmd
);
7825 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7826 const char *argv
[SIZEOF_ARG
] = { "git" };
7829 /* When running random commands, initially show the
7830 * command in the title. However, it maybe later be
7831 * overwritten if a commit line is selected. */
7832 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7834 if (!argv_from_string(argv
, &argc
, cmd
)) {
7835 report("Too many arguments");
7836 } else if (!prepare_update(next
, argv
, NULL
, FORMAT_DASH
)) {
7837 report("Failed to format command");
7839 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7847 case REQ_SEARCH_BACK
:
7849 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7850 char *search
= read_prompt(prompt
);
7853 string_ncopy(opt_search
, search
, strlen(search
));
7854 else if (*opt_search
)
7855 request
= request
== REQ_SEARCH
?