1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
37 #include <sys/select.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
63 #define __NORETURN __attribute__((__noreturn__))
68 static void __NORETURN
die(const char *err
, ...);
69 static void warn(const char *msg
, ...);
70 static void report(const char *msg
, ...);
71 static void set_nonblocking_input(bool loading
);
72 static size_t utf8_length(const char **string
, size_t col
, int *width
, size_t max_width
, int *trimmed
, bool reserve
);
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;
363 * What value of "tz" was in effect back then at "time" in the
366 static int local_tzoffset(time_t time
)
370 int offset
, eastwest
;
373 localtime_r(&t
, &tm
);
374 t_local
= mktime(&tm
);
378 offset
= t
- t_local
;
381 offset
= t_local
- t
;
383 offset
/= 60; /* in minutes */
384 offset
= (offset
% 60) + ((offset
/ 60) * 100);
385 return offset
* eastwest
;
395 #define DATE_(name) DATE_##name
400 static const struct enum_map date_map
[] = {
401 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
407 string_date(const time_t *time
, enum date date
)
409 static char buf
[DATE_COLS
+ 1];
410 static const struct enum_map reldate
[] = {
411 { "second", 1, 60 * 2 },
412 { "minute", 60, 60 * 60 * 2 },
413 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
414 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
415 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
416 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
420 if (date
== DATE_RELATIVE
) {
422 time_t date
= *time
+ local_tzoffset(*time
);
426 gettimeofday(&now
, NULL
);
427 seconds
= now
.tv_sec
< date
? date
- now
.tv_sec
: now
.tv_sec
- date
;
428 for (i
= 0; i
< ARRAY_SIZE(reldate
); i
++) {
429 if (seconds
>= reldate
[i
].value
)
432 seconds
/= reldate
[i
].namelen
;
433 if (!string_format(buf
, "%ld %s%s %s",
434 seconds
, reldate
[i
].name
,
435 seconds
> 1 ? "s" : "",
436 now
.tv_sec
>= date
? "ago" : "ahead"))
443 return strftime(buf
, sizeof(buf
), DATE_FORMAT
, &tm
) ? buf
: NULL
;
447 #define AUTHOR_VALUES \
453 #define AUTHOR_(name) AUTHOR_##name
456 AUTHOR_DEFAULT
= AUTHOR_FULL
459 static const struct enum_map author_map
[] = {
460 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
466 get_author_initials(const char *author
)
468 static char initials
[AUTHOR_COLS
* 6 + 1];
470 const char *end
= strchr(author
, '\0');
472 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
474 memset(initials
, 0, sizeof(initials
));
475 while (author
< end
) {
479 while (is_initial_sep(*author
))
482 bytes
= utf8_char_length(author
, end
);
483 if (bytes
< sizeof(initials
) - 1 - pos
) {
485 initials
[pos
++] = *author
++;
489 for (i
= pos
; author
< end
&& !is_initial_sep(*author
); author
++) {
490 if (i
< sizeof(initials
) - 1)
491 initials
[i
++] = *author
;
502 argv_from_string(const char *argv
[SIZEOF_ARG
], int *argc
, char *cmd
)
506 while (*cmd
&& *argc
< SIZEOF_ARG
&& (valuelen
= strcspn(cmd
, " \t"))) {
507 bool advance
= cmd
[valuelen
] != 0;
510 argv
[(*argc
)++] = chomp_string(cmd
);
511 cmd
= chomp_string(cmd
+ valuelen
+ advance
);
514 if (*argc
< SIZEOF_ARG
)
516 return *argc
< SIZEOF_ARG
;
520 argv_from_env(const char **argv
, const char *name
)
522 char *env
= argv
? getenv(name
) : NULL
;
527 if (env
&& !argv_from_string(argv
, &argc
, env
))
528 die("Too many arguments in the `%s` environment variable", name
);
533 * Executing external commands.
537 IO_FD
, /* File descriptor based IO. */
538 IO_BG
, /* Execute command in the background. */
539 IO_FG
, /* Execute command with same std{in,out,err}. */
540 IO_RD
, /* Read only fork+exec IO. */
541 IO_WR
, /* Write only fork+exec IO. */
542 IO_AP
, /* Append fork+exec output to file. */
546 enum io_type type
; /* The requested type of pipe. */
547 const char *dir
; /* Directory from which to execute. */
548 pid_t pid
; /* Pipe for reading or writing. */
549 int pipe
; /* Pipe end for reading or writing. */
550 int error
; /* Error status. */
551 const char *argv
[SIZEOF_ARG
]; /* Shell command arguments. */
552 char *buf
; /* Read buffer. */
553 size_t bufalloc
; /* Allocated buffer size. */
554 size_t bufsize
; /* Buffer content size. */
555 char *bufpos
; /* Current buffer position. */
556 unsigned int eof
:1; /* Has end of file been reached. */
560 reset_io(struct io
*io
)
564 io
->buf
= io
->bufpos
= NULL
;
565 io
->bufalloc
= io
->bufsize
= 0;
571 init_io(struct io
*io
, const char *dir
, enum io_type type
)
579 init_io_rd(struct io
*io
, const char *argv
[], const char *dir
,
580 enum format_flags flags
)
582 init_io(io
, dir
, IO_RD
);
583 return format_argv(io
->argv
, argv
, flags
);
587 io_open(struct io
*io
, const char *fmt
, ...)
589 char name
[SIZEOF_STR
] = "";
593 init_io(io
, NULL
, IO_FD
);
596 fits
= vsnprintf(name
, sizeof(name
), fmt
, args
) < sizeof(name
);
600 io
->error
= ENAMETOOLONG
;
603 io
->pipe
= *name
? open(name
, O_RDONLY
) : STDIN_FILENO
;
606 return io
->pipe
!= -1;
610 kill_io(struct io
*io
)
612 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
616 done_io(struct io
*io
)
627 pid_t waiting
= waitpid(pid
, &status
, 0);
632 report("waitpid failed (%s)", strerror(errno
));
636 return waiting
== pid
&&
637 !WIFSIGNALED(status
) &&
639 !WEXITSTATUS(status
);
646 start_io(struct io
*io
)
648 int pipefds
[2] = { -1, -1 };
650 if (io
->type
== IO_FD
)
653 if ((io
->type
== IO_RD
|| io
->type
== IO_WR
) &&
656 else if (io
->type
== IO_AP
)
657 pipefds
[1] = io
->pipe
;
659 if ((io
->pid
= fork())) {
660 if (pipefds
[!(io
->type
== IO_WR
)] != -1)
661 close(pipefds
[!(io
->type
== IO_WR
)]);
663 io
->pipe
= pipefds
[!!(io
->type
== IO_WR
)];
668 if (io
->type
!= IO_FG
) {
669 int devnull
= open("/dev/null", O_RDWR
);
670 int readfd
= io
->type
== IO_WR
? pipefds
[0] : devnull
;
671 int writefd
= (io
->type
== IO_RD
|| io
->type
== IO_AP
)
672 ? pipefds
[1] : devnull
;
674 dup2(readfd
, STDIN_FILENO
);
675 dup2(writefd
, STDOUT_FILENO
);
676 dup2(devnull
, STDERR_FILENO
);
679 if (pipefds
[0] != -1)
681 if (pipefds
[1] != -1)
685 if (io
->dir
&& *io
->dir
&& chdir(io
->dir
) == -1)
686 die("Failed to change directory: %s", strerror(errno
));
688 execvp(io
->argv
[0], (char *const*) io
->argv
);
689 die("Failed to execute program: %s", strerror(errno
));
692 if (pipefds
[!!(io
->type
== IO_WR
)] != -1)
693 close(pipefds
[!!(io
->type
== IO_WR
)]);
698 run_io(struct io
*io
, const char **argv
, const char *dir
, enum io_type type
)
700 init_io(io
, dir
, type
);
701 if (!format_argv(io
->argv
, argv
, FORMAT_NONE
))
707 run_io_do(struct io
*io
)
709 return start_io(io
) && done_io(io
);
713 run_io_bg(const char **argv
)
717 init_io(&io
, NULL
, IO_BG
);
718 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
720 return run_io_do(&io
);
724 run_io_fg(const char **argv
, const char *dir
)
728 init_io(&io
, dir
, IO_FG
);
729 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
731 return run_io_do(&io
);
735 run_io_append(const char **argv
, enum format_flags flags
, int fd
)
739 init_io(&io
, NULL
, IO_AP
);
741 if (format_argv(io
.argv
, argv
, flags
))
742 return run_io_do(&io
);
748 run_io_rd(struct io
*io
, const char **argv
, const char *dir
, enum format_flags flags
)
750 return init_io_rd(io
, argv
, dir
, flags
) && start_io(io
);
754 io_eof(struct io
*io
)
760 io_error(struct io
*io
)
766 io_strerror(struct io
*io
)
768 return strerror(io
->error
);
772 io_can_read(struct io
*io
)
774 struct timeval tv
= { 0, 500 };
778 FD_SET(io
->pipe
, &fds
);
780 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
784 io_read(struct io
*io
, void *buf
, size_t bufsize
)
787 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
789 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
791 else if (readsize
== -1)
793 else if (readsize
== 0)
799 DEFINE_ALLOCATOR(realloc_io_buf
, char, BUFSIZ
)
802 io_get(struct io
*io
, int c
, bool can_read
)
808 if (io
->bufsize
> 0) {
809 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
811 char *line
= io
->bufpos
;
814 io
->bufpos
= eol
+ 1;
815 io
->bufsize
-= io
->bufpos
- line
;
822 io
->bufpos
[io
->bufsize
] = 0;
832 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
833 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
835 if (io
->bufalloc
== io
->bufsize
) {
836 if (!realloc_io_buf(&io
->buf
, io
->bufalloc
, BUFSIZ
))
838 io
->bufalloc
+= BUFSIZ
;
841 io
->bufpos
= io
->buf
;
842 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
845 io
->bufsize
+= readsize
;
850 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
854 while (!io_error(io
) && written
< bufsize
) {
857 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
858 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
866 return written
== bufsize
;
870 io_read_buf(struct io
*io
, char buf
[], size_t bufsize
)
872 char *result
= io_get(io
, '\n', TRUE
);
875 result
= chomp_string(result
);
876 string_ncopy_do(buf
, bufsize
, result
, strlen(result
));
879 return done_io(io
) && result
;
883 run_io_buf(const char **argv
, char buf
[], size_t bufsize
)
887 return run_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
888 && io_read_buf(&io
, buf
, bufsize
);
892 io_load(struct io
*io
, const char *separators
,
893 int (*read_property
)(char *, size_t, char *, size_t))
901 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
906 name
= chomp_string(name
);
907 namelen
= strcspn(name
, separators
);
911 value
= chomp_string(name
+ namelen
+ 1);
912 valuelen
= strlen(value
);
919 state
= read_property(name
, namelen
, value
, valuelen
);
922 if (state
!= ERR
&& io_error(io
))
930 run_io_load(const char **argv
, const char *separators
,
931 int (*read_property
)(char *, size_t, char *, size_t))
935 return init_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
936 ? io_load(&io
, separators
, read_property
) : ERR
;
945 /* XXX: Keep the view request first and in sync with views[]. */ \
946 REQ_GROUP("View switching") \
947 REQ_(VIEW_MAIN, "Show main view"), \
948 REQ_(VIEW_DIFF, "Show diff view"), \
949 REQ_(VIEW_LOG, "Show log view"), \
950 REQ_(VIEW_TREE, "Show tree view"), \
951 REQ_(VIEW_BLOB, "Show blob view"), \
952 REQ_(VIEW_BLAME, "Show blame view"), \
953 REQ_(VIEW_BRANCH, "Show branch view"), \
954 REQ_(VIEW_HELP, "Show help page"), \
955 REQ_(VIEW_PAGER, "Show pager view"), \
956 REQ_(VIEW_STATUS, "Show status view"), \
957 REQ_(VIEW_STAGE, "Show stage view"), \
959 REQ_GROUP("View manipulation") \
960 REQ_(ENTER, "Enter current line and scroll"), \
961 REQ_(NEXT, "Move to next"), \
962 REQ_(PREVIOUS, "Move to previous"), \
963 REQ_(PARENT, "Move to parent"), \
964 REQ_(VIEW_NEXT, "Move focus to next view"), \
965 REQ_(REFRESH, "Reload and refresh"), \
966 REQ_(MAXIMIZE, "Maximize the current view"), \
967 REQ_(VIEW_CLOSE, "Close the current view"), \
968 REQ_(QUIT, "Close all views and quit"), \
970 REQ_GROUP("View specific requests") \
971 REQ_(STATUS_UPDATE, "Update file status"), \
972 REQ_(STATUS_REVERT, "Revert file changes"), \
973 REQ_(STATUS_MERGE, "Merge file using external tool"), \
974 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
976 REQ_GROUP("Cursor navigation") \
977 REQ_(MOVE_UP, "Move cursor one line up"), \
978 REQ_(MOVE_DOWN, "Move cursor one line down"), \
979 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
980 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
981 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
982 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
984 REQ_GROUP("Scrolling") \
985 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
986 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
987 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
988 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
989 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
990 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
992 REQ_GROUP("Searching") \
993 REQ_(SEARCH, "Search the view"), \
994 REQ_(SEARCH_BACK, "Search backwards in the view"), \
995 REQ_(FIND_NEXT, "Find next search match"), \
996 REQ_(FIND_PREV, "Find previous search match"), \
998 REQ_GROUP("Option manipulation") \
999 REQ_(OPTIONS, "Open option menu"), \
1000 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1001 REQ_(TOGGLE_DATE, "Toggle date display"), \
1002 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1003 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1004 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1005 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1006 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1007 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1010 REQ_(PROMPT, "Bring up the prompt"), \
1011 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1012 REQ_(SHOW_VERSION, "Show version information"), \
1013 REQ_(STOP_LOADING, "Stop all loading views"), \
1014 REQ_(EDIT, "Open in editor"), \
1015 REQ_(NONE, "Do nothing")
1018 /* User action requests. */
1020 #define REQ_GROUP(help)
1021 #define REQ_(req, help) REQ_##req
1023 /* Offset all requests to avoid conflicts with ncurses getch values. */
1024 REQ_OFFSET
= KEY_MAX
+ 1,
1031 struct request_info
{
1032 enum request request
;
1038 static const struct request_info req_info
[] = {
1039 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1040 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1047 get_request(const char *name
)
1049 int namelen
= strlen(name
);
1052 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
1053 if (enum_equals(req_info
[i
], name
, namelen
))
1054 return req_info
[i
].request
;
1064 /* Option and state variables. */
1065 static enum date opt_date
= DATE_DEFAULT
;
1066 static enum author opt_author
= AUTHOR_DEFAULT
;
1067 static bool opt_line_number
= FALSE
;
1068 static bool opt_line_graphics
= TRUE
;
1069 static bool opt_rev_graph
= FALSE
;
1070 static bool opt_show_refs
= TRUE
;
1071 static int opt_num_interval
= 5;
1072 static double opt_hscroll
= 0.50;
1073 static double opt_scale_split_view
= 2.0 / 3.0;
1074 static int opt_tab_size
= 8;
1075 static int opt_author_cols
= AUTHOR_COLS
;
1076 static char opt_path
[SIZEOF_STR
] = "";
1077 static char opt_file
[SIZEOF_STR
] = "";
1078 static char opt_ref
[SIZEOF_REF
] = "";
1079 static char opt_head
[SIZEOF_REF
] = "";
1080 static char opt_head_rev
[SIZEOF_REV
] = "";
1081 static char opt_remote
[SIZEOF_REF
] = "";
1082 static char opt_encoding
[20] = "UTF-8";
1083 static char opt_codeset
[20] = "UTF-8";
1084 static iconv_t opt_iconv_in
= ICONV_NONE
;
1085 static iconv_t opt_iconv_out
= ICONV_NONE
;
1086 static char opt_search
[SIZEOF_STR
] = "";
1087 static char opt_cdup
[SIZEOF_STR
] = "";
1088 static char opt_prefix
[SIZEOF_STR
] = "";
1089 static char opt_git_dir
[SIZEOF_STR
] = "";
1090 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
1091 static char opt_editor
[SIZEOF_STR
] = "";
1092 static FILE *opt_tty
= NULL
;
1094 #define is_initial_commit() (!*opt_head_rev)
1095 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1096 #define mkdate(time) string_date(time, opt_date)
1100 * Line-oriented content detection.
1104 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1105 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1106 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1107 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1108 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1109 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1110 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1111 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1112 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1113 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1114 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1115 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1116 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1117 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1118 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1119 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1120 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1121 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1122 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1123 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1124 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1125 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1126 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1127 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1128 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1129 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1130 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1131 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1132 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1133 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1134 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1135 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1136 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1137 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1138 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1139 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1140 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1141 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1142 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1143 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1144 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1145 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1146 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1147 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1148 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1149 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1150 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1151 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1152 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1153 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1154 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1155 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1156 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1157 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1158 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1159 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1160 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1163 #define LINE(type, line, fg, bg, attr) \
1171 const char *name
; /* Option name. */
1172 int namelen
; /* Size of option name. */
1173 const char *line
; /* The start of line to match. */
1174 int linelen
; /* Size of string to match. */
1175 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1178 static struct line_info line_info
[] = {
1179 #define LINE(type, line, fg, bg, attr) \
1180 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1185 static enum line_type
1186 get_line_type(const char *line
)
1188 int linelen
= strlen(line
);
1189 enum line_type type
;
1191 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1192 /* Case insensitive search matches Signed-off-by lines better. */
1193 if (linelen
>= line_info
[type
].linelen
&&
1194 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1197 return LINE_DEFAULT
;
1201 get_line_attr(enum line_type type
)
1203 assert(type
< ARRAY_SIZE(line_info
));
1204 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1207 static struct line_info
*
1208 get_line_info(const char *name
)
1210 size_t namelen
= strlen(name
);
1211 enum line_type type
;
1213 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1214 if (enum_equals(line_info
[type
], name
, namelen
))
1215 return &line_info
[type
];
1223 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1224 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1225 enum line_type type
;
1229 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1230 default_bg
= COLOR_BLACK
;
1231 default_fg
= COLOR_WHITE
;
1234 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1235 struct line_info
*info
= &line_info
[type
];
1236 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1237 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1239 init_pair(type
, fg
, bg
);
1244 enum line_type type
;
1247 unsigned int selected
:1;
1248 unsigned int dirty
:1;
1249 unsigned int cleareol
:1;
1250 unsigned int other
:16;
1252 void *data
; /* User data */
1262 enum request request
;
1265 static const struct keybinding default_keybindings
[] = {
1266 /* View switching */
1267 { 'm', REQ_VIEW_MAIN
},
1268 { 'd', REQ_VIEW_DIFF
},
1269 { 'l', REQ_VIEW_LOG
},
1270 { 't', REQ_VIEW_TREE
},
1271 { 'f', REQ_VIEW_BLOB
},
1272 { 'B', REQ_VIEW_BLAME
},
1273 { 'H', REQ_VIEW_BRANCH
},
1274 { 'p', REQ_VIEW_PAGER
},
1275 { 'h', REQ_VIEW_HELP
},
1276 { 'S', REQ_VIEW_STATUS
},
1277 { 'c', REQ_VIEW_STAGE
},
1279 /* View manipulation */
1280 { 'q', REQ_VIEW_CLOSE
},
1281 { KEY_TAB
, REQ_VIEW_NEXT
},
1282 { KEY_RETURN
, REQ_ENTER
},
1283 { KEY_UP
, REQ_PREVIOUS
},
1284 { KEY_DOWN
, REQ_NEXT
},
1285 { 'R', REQ_REFRESH
},
1286 { KEY_F(5), REQ_REFRESH
},
1287 { 'O', REQ_MAXIMIZE
},
1289 /* Cursor navigation */
1290 { 'k', REQ_MOVE_UP
},
1291 { 'j', REQ_MOVE_DOWN
},
1292 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1293 { KEY_END
, REQ_MOVE_LAST_LINE
},
1294 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1295 { ' ', REQ_MOVE_PAGE_DOWN
},
1296 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1297 { 'b', REQ_MOVE_PAGE_UP
},
1298 { '-', REQ_MOVE_PAGE_UP
},
1301 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1302 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1303 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1304 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1305 { 'w', REQ_SCROLL_PAGE_UP
},
1306 { 's', REQ_SCROLL_PAGE_DOWN
},
1309 { '/', REQ_SEARCH
},
1310 { '?', REQ_SEARCH_BACK
},
1311 { 'n', REQ_FIND_NEXT
},
1312 { 'N', REQ_FIND_PREV
},
1316 { 'z', REQ_STOP_LOADING
},
1317 { 'v', REQ_SHOW_VERSION
},
1318 { 'r', REQ_SCREEN_REDRAW
},
1319 { 'o', REQ_OPTIONS
},
1320 { '.', REQ_TOGGLE_LINENO
},
1321 { 'D', REQ_TOGGLE_DATE
},
1322 { 'A', REQ_TOGGLE_AUTHOR
},
1323 { 'g', REQ_TOGGLE_REV_GRAPH
},
1324 { 'F', REQ_TOGGLE_REFS
},
1325 { 'I', REQ_TOGGLE_SORT_ORDER
},
1326 { 'i', REQ_TOGGLE_SORT_FIELD
},
1327 { ':', REQ_PROMPT
},
1328 { 'u', REQ_STATUS_UPDATE
},
1329 { '!', REQ_STATUS_REVERT
},
1330 { 'M', REQ_STATUS_MERGE
},
1331 { '@', REQ_STAGE_NEXT
},
1332 { ',', REQ_PARENT
},
1336 #define KEYMAP_INFO \
1351 #define KEYMAP_(name) KEYMAP_##name
1356 static const struct enum_map keymap_table
[] = {
1357 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1362 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1364 struct keybinding_table
{
1365 struct keybinding
*data
;
1369 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1372 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1374 struct keybinding_table
*table
= &keybindings
[keymap
];
1376 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1378 die("Failed to allocate keybinding");
1379 table
->data
[table
->size
].alias
= key
;
1380 table
->data
[table
->size
++].request
= request
;
1383 /* Looks for a key binding first in the given map, then in the generic map, and
1384 * lastly in the default keybindings. */
1386 get_keybinding(enum keymap keymap
, int key
)
1390 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1391 if (keybindings
[keymap
].data
[i
].alias
== key
)
1392 return keybindings
[keymap
].data
[i
].request
;
1394 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1395 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1396 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1398 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1399 if (default_keybindings
[i
].alias
== key
)
1400 return default_keybindings
[i
].request
;
1402 return (enum request
) key
;
1411 static const struct key key_table
[] = {
1412 { "Enter", KEY_RETURN
},
1414 { "Backspace", KEY_BACKSPACE
},
1416 { "Escape", KEY_ESC
},
1417 { "Left", KEY_LEFT
},
1418 { "Right", KEY_RIGHT
},
1420 { "Down", KEY_DOWN
},
1421 { "Insert", KEY_IC
},
1422 { "Delete", KEY_DC
},
1424 { "Home", KEY_HOME
},
1426 { "PageUp", KEY_PPAGE
},
1427 { "PageDown", KEY_NPAGE
},
1437 { "F10", KEY_F(10) },
1438 { "F11", KEY_F(11) },
1439 { "F12", KEY_F(12) },
1443 get_key_value(const char *name
)
1447 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1448 if (!strcasecmp(key_table
[i
].name
, name
))
1449 return key_table
[i
].value
;
1451 if (strlen(name
) == 1 && isprint(*name
))
1458 get_key_name(int key_value
)
1460 static char key_char
[] = "'X'";
1461 const char *seq
= NULL
;
1464 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1465 if (key_table
[key
].value
== key_value
)
1466 seq
= key_table
[key
].name
;
1470 isprint(key_value
)) {
1471 key_char
[1] = (char) key_value
;
1475 return seq
? seq
: "(no key)";
1479 append_key(char *buf
, size_t *pos
, const struct keybinding
*keybinding
)
1481 const char *sep
= *pos
> 0 ? ", " : "";
1482 const char *keyname
= get_key_name(keybinding
->alias
);
1484 return string_nformat(buf
, BUFSIZ
, pos
, "%s%s", sep
, keyname
);
1488 append_keymap_request_keys(char *buf
, size_t *pos
, enum request request
,
1489 enum keymap keymap
, bool all
)
1493 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1494 if (keybindings
[keymap
].data
[i
].request
== request
) {
1495 if (!append_key(buf
, pos
, &keybindings
[keymap
].data
[i
]))
1505 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1508 get_keys(enum keymap keymap
, enum request request
, bool all
)
1510 static char buf
[BUFSIZ
];
1516 if (!append_keymap_request_keys(buf
, &pos
, request
, keymap
, all
))
1517 return "Too many keybindings!";
1518 if (pos
> 0 && !all
)
1521 if (keymap
!= KEYMAP_GENERIC
) {
1522 /* Only the generic keymap includes the default keybindings when
1523 * listing all keys. */
1527 if (!append_keymap_request_keys(buf
, &pos
, request
, KEYMAP_GENERIC
, all
))
1528 return "Too many keybindings!";
1533 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1534 if (default_keybindings
[i
].request
== request
) {
1535 if (!append_key(buf
, &pos
, &default_keybindings
[i
]))
1536 return "Too many keybindings!";
1545 struct run_request
{
1548 const char *argv
[SIZEOF_ARG
];
1551 static struct run_request
*run_request
;
1552 static size_t run_requests
;
1554 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1557 add_run_request(enum keymap keymap
, int key
, int argc
, const char **argv
)
1559 struct run_request
*req
;
1561 if (argc
>= ARRAY_SIZE(req
->argv
) - 1)
1564 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1567 req
= &run_request
[run_requests
];
1568 req
->keymap
= keymap
;
1570 req
->argv
[0] = NULL
;
1572 if (!format_argv(req
->argv
, argv
, FORMAT_NONE
))
1575 return REQ_NONE
+ ++run_requests
;
1578 static struct run_request
*
1579 get_run_request(enum request request
)
1581 if (request
<= REQ_NONE
)
1583 return &run_request
[request
- REQ_NONE
- 1];
1587 add_builtin_run_requests(void)
1589 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1590 const char *commit
[] = { "git", "commit", NULL
};
1591 const char *gc
[] = { "git", "gc", NULL
};
1598 { KEYMAP_MAIN
, 'C', ARRAY_SIZE(cherry_pick
) - 1, cherry_pick
},
1599 { KEYMAP_STATUS
, 'C', ARRAY_SIZE(commit
) - 1, commit
},
1600 { KEYMAP_GENERIC
, 'G', ARRAY_SIZE(gc
) - 1, gc
},
1604 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1607 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argc
, reqs
[i
].argv
);
1608 if (req
!= REQ_NONE
)
1609 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1614 * User config file handling.
1617 static int config_lineno
;
1618 static bool config_errors
;
1619 static const char *config_msg
;
1621 static const struct enum_map color_map
[] = {
1622 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1634 static const struct enum_map attr_map
[] = {
1635 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1642 ATTR_MAP(UNDERLINE
),
1645 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1647 static int parse_step(double *opt
, const char *arg
)
1650 if (!strchr(arg
, '%'))
1653 /* "Shift down" so 100% and 1 does not conflict. */
1654 *opt
= (*opt
- 1) / 100;
1657 config_msg
= "Step value larger than 100%";
1662 config_msg
= "Invalid step value";
1669 parse_int(int *opt
, const char *arg
, int min
, int max
)
1671 int value
= atoi(arg
);
1673 if (min
<= value
&& value
<= max
) {
1678 config_msg
= "Integer value out of bound";
1683 set_color(int *color
, const char *name
)
1685 if (map_enum(color
, color_map
, name
))
1687 if (!prefixcmp(name
, "color"))
1688 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1692 /* Wants: object fgcolor bgcolor [attribute] */
1694 option_color_command(int argc
, const char *argv
[])
1696 struct line_info
*info
;
1699 config_msg
= "Wrong number of arguments given to color command";
1703 info
= get_line_info(argv
[0]);
1705 static const struct enum_map obsolete
[] = {
1706 ENUM_MAP("main-delim", LINE_DELIMITER
),
1707 ENUM_MAP("main-date", LINE_DATE
),
1708 ENUM_MAP("main-author", LINE_AUTHOR
),
1712 if (!map_enum(&index
, obsolete
, argv
[0])) {
1713 config_msg
= "Unknown color name";
1716 info
= &line_info
[index
];
1719 if (!set_color(&info
->fg
, argv
[1]) ||
1720 !set_color(&info
->bg
, argv
[2])) {
1721 config_msg
= "Unknown color";
1726 while (argc
-- > 3) {
1729 if (!set_attribute(&attr
, argv
[argc
])) {
1730 config_msg
= "Unknown attribute";
1739 static int parse_bool(bool *opt
, const char *arg
)
1741 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1746 static int parse_enum_do(unsigned int *opt
, const char *arg
,
1747 const struct enum_map
*map
, size_t map_size
)
1751 assert(map_size
> 1);
1753 if (map_enum_do(map
, map_size
, (int *) opt
, arg
))
1756 if (parse_bool(&is_true
, arg
) != OK
)
1759 *opt
= is_true
? map
[1].value
: map
[0].value
;
1763 #define parse_enum(opt, arg, map) \
1764 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1767 parse_string(char *opt
, const char *arg
, size_t optsize
)
1769 int arglen
= strlen(arg
);
1774 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1775 config_msg
= "Unmatched quotation";
1778 arg
+= 1; arglen
-= 2;
1780 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1785 /* Wants: name = value */
1787 option_set_command(int argc
, const char *argv
[])
1790 config_msg
= "Wrong number of arguments given to set command";
1794 if (strcmp(argv
[1], "=")) {
1795 config_msg
= "No value assigned";
1799 if (!strcmp(argv
[0], "show-author"))
1800 return parse_enum(&opt_author
, argv
[2], author_map
);
1802 if (!strcmp(argv
[0], "show-date"))
1803 return parse_enum(&opt_date
, argv
[2], date_map
);
1805 if (!strcmp(argv
[0], "show-rev-graph"))
1806 return parse_bool(&opt_rev_graph
, argv
[2]);
1808 if (!strcmp(argv
[0], "show-refs"))
1809 return parse_bool(&opt_show_refs
, argv
[2]);
1811 if (!strcmp(argv
[0], "show-line-numbers"))
1812 return parse_bool(&opt_line_number
, argv
[2]);
1814 if (!strcmp(argv
[0], "line-graphics"))
1815 return parse_bool(&opt_line_graphics
, argv
[2]);
1817 if (!strcmp(argv
[0], "line-number-interval"))
1818 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
1820 if (!strcmp(argv
[0], "author-width"))
1821 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
1823 if (!strcmp(argv
[0], "horizontal-scroll"))
1824 return parse_step(&opt_hscroll
, argv
[2]);
1826 if (!strcmp(argv
[0], "split-view-height"))
1827 return parse_step(&opt_scale_split_view
, argv
[2]);
1829 if (!strcmp(argv
[0], "tab-size"))
1830 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
1832 if (!strcmp(argv
[0], "commit-encoding"))
1833 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
1835 config_msg
= "Unknown variable name";
1839 /* Wants: mode request key */
1841 option_bind_command(int argc
, const char *argv
[])
1843 enum request request
;
1848 config_msg
= "Wrong number of arguments given to bind command";
1852 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
1853 config_msg
= "Unknown key map";
1857 key
= get_key_value(argv
[1]);
1859 config_msg
= "Unknown key";
1863 request
= get_request(argv
[2]);
1864 if (request
== REQ_NONE
) {
1865 static const struct enum_map obsolete
[] = {
1866 ENUM_MAP("cherry-pick", REQ_NONE
),
1867 ENUM_MAP("screen-resize", REQ_NONE
),
1868 ENUM_MAP("tree-parent", REQ_PARENT
),
1872 if (map_enum(&alias
, obsolete
, argv
[2])) {
1873 if (alias
!= REQ_NONE
)
1874 add_keybinding(keymap
, alias
, key
);
1875 config_msg
= "Obsolete request name";
1879 if (request
== REQ_NONE
&& *argv
[2]++ == '!')
1880 request
= add_run_request(keymap
, key
, argc
- 2, argv
+ 2);
1881 if (request
== REQ_NONE
) {
1882 config_msg
= "Unknown request name";
1886 add_keybinding(keymap
, request
, key
);
1892 set_option(const char *opt
, char *value
)
1894 const char *argv
[SIZEOF_ARG
];
1897 if (!argv_from_string(argv
, &argc
, value
)) {
1898 config_msg
= "Too many option arguments";
1902 if (!strcmp(opt
, "color"))
1903 return option_color_command(argc
, argv
);
1905 if (!strcmp(opt
, "set"))
1906 return option_set_command(argc
, argv
);
1908 if (!strcmp(opt
, "bind"))
1909 return option_bind_command(argc
, argv
);
1911 config_msg
= "Unknown option command";
1916 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
1921 config_msg
= "Internal error";
1923 /* Check for comment markers, since read_properties() will
1924 * only ensure opt and value are split at first " \t". */
1925 optlen
= strcspn(opt
, "#");
1929 if (opt
[optlen
] != 0) {
1930 config_msg
= "No option value";
1934 /* Look for comment endings in the value. */
1935 size_t len
= strcspn(value
, "#");
1937 if (len
< valuelen
) {
1939 value
[valuelen
] = 0;
1942 status
= set_option(opt
, value
);
1945 if (status
== ERR
) {
1946 warn("Error on line %d, near '%.*s': %s",
1947 config_lineno
, (int) optlen
, opt
, config_msg
);
1948 config_errors
= TRUE
;
1951 /* Always keep going if errors are encountered. */
1956 load_option_file(const char *path
)
1960 /* It's OK that the file doesn't exist. */
1961 if (!io_open(&io
, "%s", path
))
1965 config_errors
= FALSE
;
1967 if (io_load(&io
, " \t", read_option
) == ERR
||
1968 config_errors
== TRUE
)
1969 warn("Errors while loading %s.", path
);
1975 const char *home
= getenv("HOME");
1976 const char *tigrc_user
= getenv("TIGRC_USER");
1977 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
1978 char buf
[SIZEOF_STR
];
1980 add_builtin_run_requests();
1983 tigrc_system
= SYSCONFDIR
"/tigrc";
1984 load_option_file(tigrc_system
);
1987 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
1991 load_option_file(tigrc_user
);
2004 /* The display array of active views and the index of the current view. */
2005 static struct view
*display
[2];
2006 static unsigned int current_view
;
2008 #define foreach_displayed_view(view, i) \
2009 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2011 #define displayed_views() (display[1] != NULL ? 2 : 1)
2013 /* Current head and commit ID */
2014 static char ref_blob
[SIZEOF_REF
] = "";
2015 static char ref_commit
[SIZEOF_REF
] = "HEAD";
2016 static char ref_head
[SIZEOF_REF
] = "HEAD";
2019 const char *name
; /* View name */
2020 const char *cmd_env
; /* Command line set via environment */
2021 const char *id
; /* Points to either of ref_{head,commit,blob} */
2023 struct view_ops
*ops
; /* View operations */
2025 enum keymap keymap
; /* What keymap does this view have */
2026 bool git_dir
; /* Whether the view requires a git directory. */
2028 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
2029 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
2031 int height
, width
; /* The width and height of the main window */
2032 WINDOW
*win
; /* The main window */
2033 WINDOW
*title
; /* The title window living below the main window */
2036 unsigned long offset
; /* Offset of the window top */
2037 unsigned long yoffset
; /* Offset from the window side. */
2038 unsigned long lineno
; /* Current line number */
2039 unsigned long p_offset
; /* Previous offset of the window top */
2040 unsigned long p_yoffset
;/* Previous offset from the window side */
2041 unsigned long p_lineno
; /* Previous current line number */
2042 bool p_restore
; /* Should the previous position be restored. */
2045 char grep
[SIZEOF_STR
]; /* Search string */
2046 regex_t
*regex
; /* Pre-compiled regexp */
2048 /* If non-NULL, points to the view that opened this view. If this view
2049 * is closed tig will switch back to the parent view. */
2050 struct view
*parent
;
2053 size_t lines
; /* Total number of lines */
2054 struct line
*line
; /* Line index */
2055 unsigned int digits
; /* Number of digits in the lines member. */
2058 struct line
*curline
; /* Line currently being drawn. */
2059 enum line_type curtype
; /* Attribute currently used for drawing. */
2060 unsigned long col
; /* Column when drawing. */
2061 bool has_scrolled
; /* View was scrolled. */
2071 /* What type of content being displayed. Used in the title bar. */
2073 /* Default command arguments. */
2075 /* Open and reads in all view content. */
2076 bool (*open
)(struct view
*view
);
2077 /* Read one line; updates view->line. */
2078 bool (*read
)(struct view
*view
, char *data
);
2079 /* Draw one line; @lineno must be < view->height. */
2080 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
2081 /* Depending on view handle a special requests. */
2082 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
2083 /* Search for regexp in a line. */
2084 bool (*grep
)(struct view
*view
, struct line
*line
);
2086 void (*select
)(struct view
*view
, struct line
*line
);
2087 /* Prepare view for loading */
2088 bool (*prepare
)(struct view
*view
);
2091 static struct view_ops blame_ops
;
2092 static struct view_ops blob_ops
;
2093 static struct view_ops diff_ops
;
2094 static struct view_ops help_ops
;
2095 static struct view_ops log_ops
;
2096 static struct view_ops main_ops
;
2097 static struct view_ops pager_ops
;
2098 static struct view_ops stage_ops
;
2099 static struct view_ops status_ops
;
2100 static struct view_ops tree_ops
;
2101 static struct view_ops branch_ops
;
2103 #define VIEW_STR(name, env, ref, ops, map, git) \
2104 { name, #env, ref, ops, map, git }
2106 #define VIEW_(id, name, ops, git, ref) \
2107 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2110 static struct view views
[] = {
2111 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
2112 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
2113 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
2114 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
2115 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
2116 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
2117 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
2118 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
2119 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
2120 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
2121 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
2124 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2125 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2127 #define foreach_view(view, i) \
2128 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2130 #define view_is_displayed(view) \
2131 (view == display[0] || view == display[1])
2138 static chtype line_graphics
[] = {
2139 /* LINE_GRAPHIC_VLINE: */ '|'
2143 set_view_attr(struct view
*view
, enum line_type type
)
2145 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
2146 wattrset(view
->win
, get_line_attr(type
));
2147 wchgat(view
->win
, -1, 0, type
, NULL
);
2148 view
->curtype
= type
;
2153 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
2154 int max_len
, bool use_tilde
)
2156 static char out_buffer
[BUFSIZ
* 2];
2159 int trimmed
= FALSE
;
2160 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2165 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
);
2167 set_view_attr(view
, type
);
2169 if (opt_iconv_out
!= ICONV_NONE
) {
2170 ICONV_CONST
char *inbuf
= (ICONV_CONST
char *) string
;
2171 size_t inlen
= len
+ 1;
2173 char *outbuf
= out_buffer
;
2174 size_t outlen
= sizeof(out_buffer
);
2178 ret
= iconv(opt_iconv_out
, &inbuf
, &inlen
, &outbuf
, &outlen
);
2179 if (ret
!= (size_t) -1) {
2180 string
= out_buffer
;
2181 len
= sizeof(out_buffer
) - outlen
;
2185 waddnstr(view
->win
, string
, len
);
2187 if (trimmed
&& use_tilde
) {
2188 set_view_attr(view
, LINE_DELIMITER
);
2189 waddch(view
->win
, '~');
2197 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
2199 static char space
[] = " ";
2202 spaces
= MIN(max
, spaces
);
2204 while (spaces
> 0) {
2205 int len
= MIN(spaces
, sizeof(space
) - 1);
2207 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
2215 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
2217 view
->col
+= draw_chars(view
, type
, string
, view
->width
+ view
->yoffset
- view
->col
, trim
);
2218 return view
->width
+ view
->yoffset
<= view
->col
;
2222 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
2224 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2225 int max
= view
->width
+ view
->yoffset
- view
->col
;
2231 set_view_attr(view
, type
);
2232 /* Using waddch() instead of waddnstr() ensures that
2233 * they'll be rendered correctly for the cursor line. */
2234 for (i
= skip
; i
< size
; i
++)
2235 waddch(view
->win
, graphic
[i
]);
2238 if (size
< max
&& skip
<= size
)
2239 waddch(view
->win
, ' ');
2242 return view
->width
+ view
->yoffset
<= view
->col
;
2246 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2248 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2252 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2254 col
= draw_space(view
, type
, max
- 1, max
- 1);
2257 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2258 return view
->width
+ view
->yoffset
<= view
->col
;
2262 draw_date(struct view
*view
, time_t *time
)
2264 const char *date
= time
? mkdate(time
) : "";
2265 int cols
= opt_date
== DATE_SHORT
? DATE_SHORT_COLS
: DATE_COLS
;
2267 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2271 draw_author(struct view
*view
, const char *author
)
2273 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5;
2274 bool abbreviate
= opt_author
== AUTHOR_ABBREVIATED
|| !trim
;
2276 if (abbreviate
&& author
)
2277 author
= get_author_initials(author
);
2279 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2283 draw_mode(struct view
*view
, mode_t mode
)
2289 else if (S_ISLNK(mode
))
2291 else if (S_ISGITLINK(mode
))
2293 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2295 else if (S_ISREG(mode
))
2300 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2304 draw_lineno(struct view
*view
, unsigned int lineno
)
2307 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2308 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2311 lineno
+= view
->offset
+ 1;
2312 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2313 static char fmt
[] = "%1ld";
2315 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2316 if (string_format(number
, fmt
, lineno
))
2320 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2322 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2323 return draw_graphic(view
, LINE_DEFAULT
, &line_graphics
[LINE_GRAPHIC_VLINE
], 1);
2327 draw_view_line(struct view
*view
, unsigned int lineno
)
2330 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2332 assert(view_is_displayed(view
));
2334 if (view
->offset
+ lineno
>= view
->lines
)
2337 line
= &view
->line
[view
->offset
+ lineno
];
2339 wmove(view
->win
, lineno
, 0);
2341 wclrtoeol(view
->win
);
2343 view
->curline
= line
;
2344 view
->curtype
= LINE_NONE
;
2345 line
->selected
= FALSE
;
2346 line
->dirty
= line
->cleareol
= 0;
2349 set_view_attr(view
, LINE_CURSOR
);
2350 line
->selected
= TRUE
;
2351 view
->ops
->select(view
, line
);
2354 return view
->ops
->draw(view
, line
, lineno
);
2358 redraw_view_dirty(struct view
*view
)
2363 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2364 if (view
->offset
+ lineno
>= view
->lines
)
2366 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2369 if (!draw_view_line(view
, lineno
))
2375 wnoutrefresh(view
->win
);
2379 redraw_view_from(struct view
*view
, int lineno
)
2381 assert(0 <= lineno
&& lineno
< view
->height
);
2383 for (; lineno
< view
->height
; lineno
++) {
2384 if (!draw_view_line(view
, lineno
))
2388 wnoutrefresh(view
->win
);
2392 redraw_view(struct view
*view
)
2395 redraw_view_from(view
, 0);
2400 update_view_title(struct view
*view
)
2402 char buf
[SIZEOF_STR
];
2403 char state
[SIZEOF_STR
];
2404 size_t bufpos
= 0, statelen
= 0;
2406 assert(view_is_displayed(view
));
2408 if (view
!= VIEW(REQ_VIEW_STATUS
) && view
->lines
) {
2409 unsigned int view_lines
= view
->offset
+ view
->height
;
2410 unsigned int lines
= view
->lines
2411 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2414 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2423 time_t secs
= time(NULL
) - view
->start_time
;
2425 /* Three git seconds are a long time ... */
2427 string_format_from(state
, &statelen
, " loading %lds", secs
);
2430 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2431 if (*view
->ref
&& bufpos
< view
->width
) {
2432 size_t refsize
= strlen(view
->ref
);
2433 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2435 if (minsize
< view
->width
)
2436 refsize
= view
->width
- minsize
+ 7;
2437 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2440 if (statelen
&& bufpos
< view
->width
) {
2441 string_format_from(buf
, &bufpos
, "%s", state
);
2444 if (view
== display
[current_view
])
2445 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2447 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2449 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2450 wclrtoeol(view
->title
);
2451 wnoutrefresh(view
->title
);
2455 apply_step(double step
, int value
)
2459 value
*= step
+ 0.01;
2460 return value
? value
: 1;
2464 resize_display(void)
2467 struct view
*base
= display
[0];
2468 struct view
*view
= display
[1] ? display
[1] : display
[0];
2470 /* Setup window dimensions */
2472 getmaxyx(stdscr
, base
->height
, base
->width
);
2474 /* Make room for the status window. */
2478 /* Horizontal split. */
2479 view
->width
= base
->width
;
2480 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2481 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2482 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2483 base
->height
-= view
->height
;
2485 /* Make room for the title bar. */
2489 /* Make room for the title bar. */
2494 foreach_displayed_view (view
, i
) {
2496 view
->win
= newwin(view
->height
, 0, offset
, 0);
2498 die("Failed to create %s view", view
->name
);
2500 scrollok(view
->win
, FALSE
);
2502 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2504 die("Failed to create title window");
2507 wresize(view
->win
, view
->height
, view
->width
);
2508 mvwin(view
->win
, offset
, 0);
2509 mvwin(view
->title
, offset
+ view
->height
, 0);
2512 offset
+= view
->height
+ 1;
2517 redraw_display(bool clear
)
2522 foreach_displayed_view (view
, i
) {
2526 update_view_title(view
);
2531 toggle_enum_option_do(unsigned int *opt
, const char *help
,
2532 const struct enum_map
*map
, size_t size
)
2534 *opt
= (*opt
+ 1) % size
;
2535 redraw_display(FALSE
);
2536 report("Displaying %s %s", enum_name(map
[*opt
]), help
);
2539 #define toggle_enum_option(opt, help, map) \
2540 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2542 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2543 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2546 toggle_view_option(bool *option
, const char *help
)
2549 redraw_display(FALSE
);
2550 report("%sabling %s", *option
? "En" : "Dis", help
);
2554 open_option_menu(void)
2556 const struct menu_item menu
[] = {
2557 { '.', "line numbers", &opt_line_number
},
2558 { 'D', "date display", &opt_date
},
2559 { 'A', "author display", &opt_author
},
2560 { 'g', "revision graph display", &opt_rev_graph
},
2561 { 'F', "reference display", &opt_show_refs
},
2566 if (prompt_menu("Toggle option", menu
, &selected
)) {
2567 if (menu
[selected
].data
== &opt_date
)
2569 else if (menu
[selected
].data
== &opt_author
)
2572 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2577 maximize_view(struct view
*view
)
2579 memset(display
, 0, sizeof(display
));
2581 display
[current_view
] = view
;
2583 redraw_display(FALSE
);
2593 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2595 if (lineno
>= view
->lines
)
2596 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2598 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2599 unsigned long half
= view
->height
/ 2;
2602 offset
= lineno
- half
;
2607 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2608 view
->offset
= offset
;
2609 view
->lineno
= lineno
;
2616 /* Scrolling backend */
2618 do_scroll_view(struct view
*view
, int lines
)
2620 bool redraw_current_line
= FALSE
;
2622 /* The rendering expects the new offset. */
2623 view
->offset
+= lines
;
2625 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2628 /* Move current line into the view. */
2629 if (view
->lineno
< view
->offset
) {
2630 view
->lineno
= view
->offset
;
2631 redraw_current_line
= TRUE
;
2632 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2633 view
->lineno
= view
->offset
+ view
->height
- 1;
2634 redraw_current_line
= TRUE
;
2637 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2639 /* Redraw the whole screen if scrolling is pointless. */
2640 if (view
->height
< ABS(lines
)) {
2644 int line
= lines
> 0 ? view
->height
- lines
: 0;
2645 int end
= line
+ ABS(lines
);
2647 scrollok(view
->win
, TRUE
);
2648 wscrl(view
->win
, lines
);
2649 scrollok(view
->win
, FALSE
);
2651 while (line
< end
&& draw_view_line(view
, line
))
2654 if (redraw_current_line
)
2655 draw_view_line(view
, view
->lineno
- view
->offset
);
2656 wnoutrefresh(view
->win
);
2659 view
->has_scrolled
= TRUE
;
2663 /* Scroll frontend */
2665 scroll_view(struct view
*view
, enum request request
)
2669 assert(view_is_displayed(view
));
2672 case REQ_SCROLL_LEFT
:
2673 if (view
->yoffset
== 0) {
2674 report("Cannot scroll beyond the first column");
2677 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2680 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2681 redraw_view_from(view
, 0);
2684 case REQ_SCROLL_RIGHT
:
2685 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2689 case REQ_SCROLL_PAGE_DOWN
:
2690 lines
= view
->height
;
2691 case REQ_SCROLL_LINE_DOWN
:
2692 if (view
->offset
+ lines
> view
->lines
)
2693 lines
= view
->lines
- view
->offset
;
2695 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2696 report("Cannot scroll beyond the last line");
2701 case REQ_SCROLL_PAGE_UP
:
2702 lines
= view
->height
;
2703 case REQ_SCROLL_LINE_UP
:
2704 if (lines
> view
->offset
)
2705 lines
= view
->offset
;
2708 report("Cannot scroll beyond the first line");
2716 die("request %d not handled in switch", request
);
2719 do_scroll_view(view
, lines
);
2724 move_view(struct view
*view
, enum request request
)
2726 int scroll_steps
= 0;
2730 case REQ_MOVE_FIRST_LINE
:
2731 steps
= -view
->lineno
;
2734 case REQ_MOVE_LAST_LINE
:
2735 steps
= view
->lines
- view
->lineno
- 1;
2738 case REQ_MOVE_PAGE_UP
:
2739 steps
= view
->height
> view
->lineno
2740 ? -view
->lineno
: -view
->height
;
2743 case REQ_MOVE_PAGE_DOWN
:
2744 steps
= view
->lineno
+ view
->height
>= view
->lines
2745 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2757 die("request %d not handled in switch", request
);
2760 if (steps
<= 0 && view
->lineno
== 0) {
2761 report("Cannot move beyond the first line");
2764 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2765 report("Cannot move beyond the last line");
2769 /* Move the current line */
2770 view
->lineno
+= steps
;
2771 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2773 /* Check whether the view needs to be scrolled */
2774 if (view
->lineno
< view
->offset
||
2775 view
->lineno
>= view
->offset
+ view
->height
) {
2776 scroll_steps
= steps
;
2777 if (steps
< 0 && -steps
> view
->offset
) {
2778 scroll_steps
= -view
->offset
;
2780 } else if (steps
> 0) {
2781 if (view
->lineno
== view
->lines
- 1 &&
2782 view
->lines
> view
->height
) {
2783 scroll_steps
= view
->lines
- view
->offset
- 1;
2784 if (scroll_steps
>= view
->height
)
2785 scroll_steps
-= view
->height
- 1;
2790 if (!view_is_displayed(view
)) {
2791 view
->offset
+= scroll_steps
;
2792 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2793 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2797 /* Repaint the old "current" line if we be scrolling */
2798 if (ABS(steps
) < view
->height
)
2799 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
2802 do_scroll_view(view
, scroll_steps
);
2806 /* Draw the current line */
2807 draw_view_line(view
, view
->lineno
- view
->offset
);
2809 wnoutrefresh(view
->win
);
2818 static void search_view(struct view
*view
, enum request request
);
2821 grep_text(struct view
*view
, const char *text
[])
2826 for (i
= 0; text
[i
]; i
++)
2828 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
2834 select_view_line(struct view
*view
, unsigned long lineno
)
2836 unsigned long old_lineno
= view
->lineno
;
2837 unsigned long old_offset
= view
->offset
;
2839 if (goto_view_line(view
, view
->offset
, lineno
)) {
2840 if (view_is_displayed(view
)) {
2841 if (old_offset
!= view
->offset
) {
2844 draw_view_line(view
, old_lineno
- view
->offset
);
2845 draw_view_line(view
, view
->lineno
- view
->offset
);
2846 wnoutrefresh(view
->win
);
2849 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2855 find_next(struct view
*view
, enum request request
)
2857 unsigned long lineno
= view
->lineno
;
2862 report("No previous search");
2864 search_view(view
, request
);
2874 case REQ_SEARCH_BACK
:
2883 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
2884 lineno
+= direction
;
2886 /* Note, lineno is unsigned long so will wrap around in which case it
2887 * will become bigger than view->lines. */
2888 for (; lineno
< view
->lines
; lineno
+= direction
) {
2889 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
2890 select_view_line(view
, lineno
);
2891 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
2896 report("No match found for '%s'", view
->grep
);
2900 search_view(struct view
*view
, enum request request
)
2905 regfree(view
->regex
);
2908 view
->regex
= calloc(1, sizeof(*view
->regex
));
2913 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
2914 if (regex_err
!= 0) {
2915 char buf
[SIZEOF_STR
] = "unknown error";
2917 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
2918 report("Search failed: %s", buf
);
2922 string_copy(view
->grep
, opt_search
);
2924 find_next(view
, request
);
2928 * Incremental updating
2932 reset_view(struct view
*view
)
2936 for (i
= 0; i
< view
->lines
; i
++)
2937 free(view
->line
[i
].data
);
2940 view
->p_offset
= view
->offset
;
2941 view
->p_yoffset
= view
->yoffset
;
2942 view
->p_lineno
= view
->lineno
;
2950 view
->update_secs
= 0;
2954 free_argv(const char *argv
[])
2958 for (argc
= 0; argv
[argc
]; argc
++)
2959 free((void *) argv
[argc
]);
2963 format_arg(const char *name
)
2969 const char *value_if_empty
;
2971 #define FORMAT_VAR(name, value, value_if_empty) \
2972 { name, STRING_SIZE(name), value, value_if_empty }
2973 FORMAT_VAR("%(directory)", opt_path
, ""),
2974 FORMAT_VAR("%(file)", opt_file
, ""),
2975 FORMAT_VAR("%(ref)", opt_ref
, "HEAD"),
2976 FORMAT_VAR("%(head)", ref_head
, ""),
2977 FORMAT_VAR("%(commit)", ref_commit
, ""),
2978 FORMAT_VAR("%(blob)", ref_blob
, ""),
2982 for (i
= 0; i
< ARRAY_SIZE(vars
); i
++)
2983 if (!strncmp(name
, vars
[i
].name
, vars
[i
].namelen
))
2984 return *vars
[i
].value
? vars
[i
].value
: vars
[i
].value_if_empty
;
2989 format_argv(const char *dst_argv
[], const char *src_argv
[], enum format_flags flags
)
2991 char buf
[SIZEOF_STR
];
2993 bool noreplace
= flags
== FORMAT_NONE
;
2995 free_argv(dst_argv
);
2997 for (argc
= 0; src_argv
[argc
]; argc
++) {
2998 const char *arg
= src_argv
[argc
];
3002 char *next
= strstr(arg
, "%(");
3003 int len
= next
- arg
;
3006 if (!next
|| noreplace
) {
3007 if (flags
== FORMAT_DASH
&& !strcmp(arg
, "--"))
3013 value
= format_arg(next
);
3016 report("Unknown replacement: `%s`", next
);
3021 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
3024 arg
= next
&& !noreplace
? strchr(next
, ')') + 1 : NULL
;
3027 dst_argv
[argc
] = strdup(buf
);
3028 if (!dst_argv
[argc
])
3032 dst_argv
[argc
] = NULL
;
3034 return src_argv
[argc
] == NULL
;
3038 restore_view_position(struct view
*view
)
3040 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
3043 /* Changing the view position cancels the restoring. */
3044 /* FIXME: Changing back to the first line is not detected. */
3045 if (view
->offset
!= 0 || view
->lineno
!= 0) {
3046 view
->p_restore
= FALSE
;
3050 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
3051 view_is_displayed(view
))
3054 view
->yoffset
= view
->p_yoffset
;
3055 view
->p_restore
= FALSE
;
3061 end_update(struct view
*view
, bool force
)
3065 while (!view
->ops
->read(view
, NULL
))
3068 set_nonblocking_input(FALSE
);
3070 kill_io(view
->pipe
);
3071 done_io(view
->pipe
);
3076 setup_update(struct view
*view
, const char *vid
)
3078 set_nonblocking_input(TRUE
);
3080 string_copy_rev(view
->vid
, vid
);
3081 view
->pipe
= &view
->io
;
3082 view
->start_time
= time(NULL
);
3086 prepare_update(struct view
*view
, const char *argv
[], const char *dir
,
3087 enum format_flags flags
)
3090 end_update(view
, TRUE
);
3091 return init_io_rd(&view
->io
, argv
, dir
, flags
);
3095 prepare_update_file(struct view
*view
, const char *name
)
3098 end_update(view
, TRUE
);
3099 return io_open(&view
->io
, "%s/%s", opt_cdup
[0] ? opt_cdup
: ".", name
);
3103 begin_update(struct view
*view
, bool refresh
)
3106 end_update(view
, TRUE
);
3109 if (view
->ops
->prepare
) {
3110 if (!view
->ops
->prepare(view
))
3112 } else if (!init_io_rd(&view
->io
, view
->ops
->argv
, NULL
, FORMAT_ALL
)) {
3116 /* Put the current ref_* value to the view title ref
3117 * member. This is needed by the blob view. Most other
3118 * views sets it automatically after loading because the
3119 * first line is a commit line. */
3120 string_copy_rev(view
->ref
, view
->id
);
3123 if (!start_io(&view
->io
))
3126 setup_update(view
, view
->id
);
3132 update_view(struct view
*view
)
3134 char out_buffer
[BUFSIZ
* 2];
3136 /* Clear the view and redraw everything since the tree sorting
3137 * might have rearranged things. */
3138 bool redraw
= view
->lines
== 0;
3139 bool can_read
= TRUE
;
3144 if (!io_can_read(view
->pipe
)) {
3145 if (view
->lines
== 0 && view_is_displayed(view
)) {
3146 time_t secs
= time(NULL
) - view
->start_time
;
3148 if (secs
> 1 && secs
> view
->update_secs
) {
3149 if (view
->update_secs
== 0)
3151 update_view_title(view
);
3152 view
->update_secs
= secs
;
3158 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
3159 if (opt_iconv_in
!= ICONV_NONE
) {
3160 ICONV_CONST
char *inbuf
= line
;
3161 size_t inlen
= strlen(line
) + 1;
3163 char *outbuf
= out_buffer
;
3164 size_t outlen
= sizeof(out_buffer
);
3168 ret
= iconv(opt_iconv_in
, &inbuf
, &inlen
, &outbuf
, &outlen
);
3169 if (ret
!= (size_t) -1)
3173 if (!view
->ops
->read(view
, line
)) {
3174 report("Allocation failure");
3175 end_update(view
, TRUE
);
3181 unsigned long lines
= view
->lines
;
3184 for (digits
= 0; lines
; digits
++)
3187 /* Keep the displayed view in sync with line number scaling. */
3188 if (digits
!= view
->digits
) {
3189 view
->digits
= digits
;
3190 if (opt_line_number
|| view
== VIEW(REQ_VIEW_BLAME
))
3195 if (io_error(view
->pipe
)) {
3196 report("Failed to read: %s", io_strerror(view
->pipe
));
3197 end_update(view
, TRUE
);
3199 } else if (io_eof(view
->pipe
)) {
3201 end_update(view
, FALSE
);
3204 if (restore_view_position(view
))
3207 if (!view_is_displayed(view
))
3211 redraw_view_from(view
, 0);
3213 redraw_view_dirty(view
);
3215 /* Update the title _after_ the redraw so that if the redraw picks up a
3216 * commit reference in view->ref it'll be available here. */
3217 update_view_title(view
);
3221 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
3223 static struct line
*
3224 add_line_data(struct view
*view
, void *data
, enum line_type type
)
3228 if (!realloc_lines(&view
->line
, view
->lines
, 1))
3231 line
= &view
->line
[view
->lines
++];
3232 memset(line
, 0, sizeof(*line
));
3240 static struct line
*
3241 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
3243 char *data
= text
? strdup(text
) : NULL
;
3245 return data
? add_line_data(view
, data
, type
) : NULL
;
3248 static struct line
*
3249 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3251 char buf
[SIZEOF_STR
];
3254 va_start(args
, fmt
);
3255 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3259 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3267 OPEN_DEFAULT
= 0, /* Use default view switching. */
3268 OPEN_SPLIT
= 1, /* Split current view. */
3269 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3270 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3271 OPEN_PREPARED
= 32, /* Open already prepared command. */
3275 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3277 bool split
= !!(flags
& OPEN_SPLIT
);
3278 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3279 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3280 struct view
*view
= VIEW(request
);
3281 int nviews
= displayed_views();
3282 struct view
*base_view
= display
[0];
3284 if (view
== prev
&& nviews
== 1 && !reload
) {
3285 report("Already in %s view", view
->name
);
3289 if (view
->git_dir
&& !opt_git_dir
[0]) {
3290 report("The %s view is disabled in pager view", view
->name
);
3297 } else if (!nomaximize
) {
3298 /* Maximize the current view. */
3299 memset(display
, 0, sizeof(display
));
3301 display
[current_view
] = view
;
3304 /* No parent signals that this is the first loaded view. */
3305 if (prev
&& view
!= prev
) {
3306 view
->parent
= prev
;
3309 /* Resize the view when switching between split- and full-screen,
3310 * or when switching between two different full-screen views. */
3311 if (nviews
!= displayed_views() ||
3312 (nviews
== 1 && base_view
!= display
[0]))
3315 if (view
->ops
->open
) {
3317 end_update(view
, TRUE
);
3318 if (!view
->ops
->open(view
)) {
3319 report("Failed to load %s view", view
->name
);
3322 restore_view_position(view
);
3324 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3325 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3326 report("Failed to load %s view", view
->name
);
3330 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3331 /* Take the title line into account. */
3332 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3334 /* Scroll the view that was split if the current line is
3335 * outside the new limited view. */
3336 do_scroll_view(prev
, lines
);
3339 if (prev
&& view
!= prev
&& split
&& view_is_displayed(prev
)) {
3340 /* "Blur" the previous view. */
3341 update_view_title(prev
);
3344 if (view
->pipe
&& view
->lines
== 0) {
3345 /* Clear the old view and let the incremental updating refill
3348 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3350 } else if (view_is_displayed(view
)) {
3357 open_external_viewer(const char *argv
[], const char *dir
)
3359 def_prog_mode(); /* save current tty modes */
3360 endwin(); /* restore original tty modes */
3361 run_io_fg(argv
, dir
);
3362 fprintf(stderr
, "Press Enter to continue");
3365 redraw_display(TRUE
);
3369 open_mergetool(const char *file
)
3371 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3373 open_external_viewer(mergetool_argv
, opt_cdup
);
3377 open_editor(const char *file
)
3379 const char *editor_argv
[] = { "vi", file
, NULL
};
3382 editor
= getenv("GIT_EDITOR");
3383 if (!editor
&& *opt_editor
)
3384 editor
= opt_editor
;
3386 editor
= getenv("VISUAL");
3388 editor
= getenv("EDITOR");
3392 editor_argv
[0] = editor
;
3393 open_external_viewer(editor_argv
, opt_cdup
);
3397 open_run_request(enum request request
)
3399 struct run_request
*req
= get_run_request(request
);
3400 const char *argv
[ARRAY_SIZE(req
->argv
)] = { NULL
};
3403 report("Unknown run request");
3407 if (format_argv(argv
, req
->argv
, FORMAT_ALL
))
3408 open_external_viewer(argv
, NULL
);
3413 * User request switch noodle
3417 view_driver(struct view
*view
, enum request request
)
3421 if (request
== REQ_NONE
)
3424 if (request
> REQ_NONE
) {
3425 open_run_request(request
);
3426 /* FIXME: When all views can refresh always do this. */
3427 if (view
== VIEW(REQ_VIEW_STATUS
) ||
3428 view
== VIEW(REQ_VIEW_MAIN
) ||
3429 view
== VIEW(REQ_VIEW_LOG
) ||
3430 view
== VIEW(REQ_VIEW_BRANCH
) ||
3431 view
== VIEW(REQ_VIEW_STAGE
))
3432 request
= REQ_REFRESH
;
3437 if (view
&& view
->lines
) {
3438 request
= view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
3439 if (request
== REQ_NONE
)
3446 case REQ_MOVE_PAGE_UP
:
3447 case REQ_MOVE_PAGE_DOWN
:
3448 case REQ_MOVE_FIRST_LINE
:
3449 case REQ_MOVE_LAST_LINE
:
3450 move_view(view
, request
);
3453 case REQ_SCROLL_LEFT
:
3454 case REQ_SCROLL_RIGHT
:
3455 case REQ_SCROLL_LINE_DOWN
:
3456 case REQ_SCROLL_LINE_UP
:
3457 case REQ_SCROLL_PAGE_DOWN
:
3458 case REQ_SCROLL_PAGE_UP
:
3459 scroll_view(view
, request
);
3462 case REQ_VIEW_BLAME
:
3464 report("No file chosen, press %s to open tree view",
3465 get_key(view
->keymap
, REQ_VIEW_TREE
));
3468 open_view(view
, request
, OPEN_DEFAULT
);
3473 report("No file chosen, press %s to open tree view",
3474 get_key(view
->keymap
, REQ_VIEW_TREE
));
3477 open_view(view
, request
, OPEN_DEFAULT
);
3480 case REQ_VIEW_PAGER
:
3481 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3482 report("No pager content, press %s to run command from prompt",
3483 get_key(view
->keymap
, REQ_PROMPT
));
3486 open_view(view
, request
, OPEN_DEFAULT
);
3489 case REQ_VIEW_STAGE
:
3490 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3491 report("No stage content, press %s to open the status view and choose file",
3492 get_key(view
->keymap
, REQ_VIEW_STATUS
));
3495 open_view(view
, request
, OPEN_DEFAULT
);
3498 case REQ_VIEW_STATUS
:
3499 if (opt_is_inside_work_tree
== FALSE
) {
3500 report("The status view requires a working tree");
3503 open_view(view
, request
, OPEN_DEFAULT
);
3511 case REQ_VIEW_BRANCH
:
3512 open_view(view
, request
, OPEN_DEFAULT
);
3517 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3519 if ((view
== VIEW(REQ_VIEW_DIFF
) &&
3520 view
->parent
== VIEW(REQ_VIEW_MAIN
)) ||
3521 (view
== VIEW(REQ_VIEW_DIFF
) &&
3522 view
->parent
== VIEW(REQ_VIEW_BLAME
)) ||
3523 (view
== VIEW(REQ_VIEW_STAGE
) &&
3524 view
->parent
== VIEW(REQ_VIEW_STATUS
)) ||
3525 (view
== VIEW(REQ_VIEW_BLOB
) &&
3526 view
->parent
== VIEW(REQ_VIEW_TREE
)) ||
3527 (view
== VIEW(REQ_VIEW_MAIN
) &&
3528 view
->parent
== VIEW(REQ_VIEW_BRANCH
))) {
3531 view
= view
->parent
;
3532 line
= view
->lineno
;
3533 move_view(view
, request
);
3534 if (view_is_displayed(view
))
3535 update_view_title(view
);
3536 if (line
!= view
->lineno
)
3537 view
->ops
->request(view
, REQ_ENTER
,
3538 &view
->line
[view
->lineno
]);
3541 move_view(view
, request
);
3547 int nviews
= displayed_views();
3548 int next_view
= (current_view
+ 1) % nviews
;
3550 if (next_view
== current_view
) {
3551 report("Only one view is displayed");
3555 current_view
= next_view
;
3556 /* Blur out the title of the previous view. */
3557 update_view_title(view
);
3562 report("Refreshing is not yet supported for the %s view", view
->name
);
3566 if (displayed_views() == 2)
3567 maximize_view(view
);
3574 case REQ_TOGGLE_LINENO
:
3575 toggle_view_option(&opt_line_number
, "line numbers");
3578 case REQ_TOGGLE_DATE
:
3582 case REQ_TOGGLE_AUTHOR
:
3586 case REQ_TOGGLE_REV_GRAPH
:
3587 toggle_view_option(&opt_rev_graph
, "revision graph display");
3590 case REQ_TOGGLE_REFS
:
3591 toggle_view_option(&opt_show_refs
, "reference display");
3594 case REQ_TOGGLE_SORT_FIELD
:
3595 case REQ_TOGGLE_SORT_ORDER
:
3596 report("Sorting is not yet supported for the %s view", view
->name
);
3600 case REQ_SEARCH_BACK
:
3601 search_view(view
, request
);
3606 find_next(view
, request
);
3609 case REQ_STOP_LOADING
:
3610 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
3613 report("Stopped loading the %s view", view
->name
),
3614 end_update(view
, TRUE
);
3618 case REQ_SHOW_VERSION
:
3619 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3622 case REQ_SCREEN_REDRAW
:
3623 redraw_display(TRUE
);
3627 report("Nothing to edit");
3631 report("Nothing to enter");
3634 case REQ_VIEW_CLOSE
:
3635 /* XXX: Mark closed views by letting view->parent point to the
3636 * view itself. Parents to closed view should never be
3639 view
->parent
->parent
!= view
->parent
) {
3640 maximize_view(view
->parent
);
3641 view
->parent
= view
;
3649 report("Unknown key, press %s for help",
3650 get_key(view
->keymap
, REQ_VIEW_HELP
));
3659 * View backend utilities
3669 const enum sort_field
*fields
;
3670 size_t size
, current
;
3674 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3675 #define get_sort_field(state) ((state).fields[(state).current])
3676 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3679 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3680 int (*compare
)(const void *, const void *))
3683 case REQ_TOGGLE_SORT_FIELD
:
3684 state
->current
= (state
->current
+ 1) % state
->size
;
3687 case REQ_TOGGLE_SORT_ORDER
:
3688 state
->reverse
= !state
->reverse
;
3691 die("Not a sort request");
3694 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3698 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3700 /* Small author cache to reduce memory consumption. It uses binary
3701 * search to lookup or find place to position new entries. No entries
3702 * are ever freed. */
3704 get_author(const char *name
)
3706 static const char **authors
;
3707 static size_t authors_size
;
3708 int from
= 0, to
= authors_size
- 1;
3710 while (from
<= to
) {
3711 size_t pos
= (to
+ from
) / 2;
3712 int cmp
= strcmp(name
, authors
[pos
]);
3715 return authors
[pos
];
3723 if (!realloc_authors(&authors
, authors_size
, 1))
3725 name
= strdup(name
);
3729 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3730 authors
[from
] = name
;
3737 parse_timezone(time_t *time
, const char *zone
)
3741 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3742 tz
+= ('0' - zone
[2]) * 60 * 60;
3743 tz
+= ('0' - zone
[3]) * 60;
3744 tz
+= ('0' - zone
[4]);
3752 /* Parse author lines where the name may be empty:
3753 * author <email@address.tld> 1138474660 +0100
3756 parse_author_line(char *ident
, const char **author
, time_t *time
)
3758 char *nameend
= strchr(ident
, '<');
3759 char *emailend
= strchr(ident
, '>');
3761 if (nameend
&& emailend
)
3762 *nameend
= *emailend
= 0;
3763 ident
= chomp_string(ident
);
3766 ident
= chomp_string(nameend
+ 1);
3771 *author
= get_author(ident
);
3773 /* Parse epoch and timezone */
3774 if (emailend
&& emailend
[1] == ' ') {
3775 char *secs
= emailend
+ 2;
3776 char *zone
= strchr(secs
, ' ');
3778 *time
= (time_t) atol(secs
);
3780 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
3781 parse_timezone(time
, zone
+ 1);
3786 open_commit_parent_menu(char buf
[SIZEOF_STR
], int *parents
)
3788 char rev
[SIZEOF_REV
];
3789 const char *revlist_argv
[] = {
3790 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev
, NULL
3792 struct menu_item
*items
;
3793 char text
[SIZEOF_STR
];
3797 items
= calloc(*parents
+ 1, sizeof(*items
));
3801 for (i
= 0; i
< *parents
; i
++) {
3802 string_copy_rev(rev
, &buf
[SIZEOF_REV
* i
]);
3803 if (!run_io_buf(revlist_argv
, text
, sizeof(text
)) ||
3804 !(items
[i
].text
= strdup(text
))) {
3812 ok
= prompt_menu("Select parent", items
, parents
);
3814 for (i
= 0; items
[i
].text
; i
++)
3815 free((char *) items
[i
].text
);
3821 select_commit_parent(const char *id
, char rev
[SIZEOF_REV
], const char *path
)
3823 char buf
[SIZEOF_STR
* 4];
3824 const char *revlist_argv
[] = {
3825 "git", "log", "--no-color", "-1",
3826 "--pretty=format:%P", id
, "--", path
, NULL
3830 if (!run_io_buf(revlist_argv
, buf
, sizeof(buf
)) ||
3831 (parents
= strlen(buf
) / 40) < 0) {
3832 report("Failed to get parent information");
3835 } else if (parents
== 0) {
3837 report("Path '%s' does not exist in the parent", path
);
3839 report("The selected commit has no parents");
3843 if (parents
> 1 && !open_commit_parent_menu(buf
, &parents
))
3846 string_copy_rev(rev
, &buf
[41 * parents
]);
3855 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
3857 char text
[SIZEOF_STR
];
3859 if (opt_line_number
&& draw_lineno(view
, lineno
))
3862 string_expand(text
, sizeof(text
), line
->data
, opt_tab_size
);
3863 draw_text(view
, line
->type
, text
, TRUE
);
3868 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
3870 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
3871 char ref
[SIZEOF_STR
];
3873 if (!run_io_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
3876 /* This is the only fatal call, since it can "corrupt" the buffer. */
3877 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
3884 add_pager_refs(struct view
*view
, struct line
*line
)
3886 char buf
[SIZEOF_STR
];
3887 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
3888 struct ref_list
*list
;
3889 size_t bufpos
= 0, i
;
3890 const char *sep
= "Refs: ";
3891 bool is_tag
= FALSE
;
3893 assert(line
->type
== LINE_COMMIT
);
3895 list
= get_ref_list(commit_id
);
3897 if (view
== VIEW(REQ_VIEW_DIFF
))
3898 goto try_add_describe_ref
;
3902 for (i
= 0; i
< list
->size
; i
++) {
3903 struct ref
*ref
= list
->refs
[i
];
3904 const char *fmt
= ref
->tag
? "%s[%s]" :
3905 ref
->remote
? "%s<%s>" : "%s%s";
3907 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
3914 if (!is_tag
&& view
== VIEW(REQ_VIEW_DIFF
)) {
3915 try_add_describe_ref
:
3916 /* Add <tag>-g<commit_id> "fake" reference. */
3917 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
3924 add_line_text(view
, buf
, LINE_PP_REFS
);
3928 pager_read(struct view
*view
, char *data
)
3935 line
= add_line_text(view
, data
, get_line_type(data
));
3939 if (line
->type
== LINE_COMMIT
&&
3940 (view
== VIEW(REQ_VIEW_DIFF
) ||
3941 view
== VIEW(REQ_VIEW_LOG
)))
3942 add_pager_refs(view
, line
);
3948 pager_request(struct view
*view
, enum request request
, struct line
*line
)
3952 if (request
!= REQ_ENTER
)
3955 if (line
->type
== LINE_COMMIT
&&
3956 (view
== VIEW(REQ_VIEW_LOG
) ||
3957 view
== VIEW(REQ_VIEW_PAGER
))) {
3958 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
3962 /* Always scroll the view even if it was split. That way
3963 * you can use Enter to scroll through the log view and
3964 * split open each commit diff. */
3965 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
3967 /* FIXME: A minor workaround. Scrolling the view will call report("")
3968 * but if we are scrolling a non-current view this won't properly
3969 * update the view title. */
3971 update_view_title(view
);
3977 pager_grep(struct view
*view
, struct line
*line
)
3979 const char *text
[] = { line
->data
, NULL
};
3981 return grep_text(view
, text
);
3985 pager_select(struct view
*view
, struct line
*line
)
3987 if (line
->type
== LINE_COMMIT
) {
3988 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
3990 if (view
!= VIEW(REQ_VIEW_PAGER
))
3991 string_copy_rev(view
->ref
, text
);
3992 string_copy_rev(ref_commit
, text
);
3996 static struct view_ops pager_ops
= {
4007 static const char *log_argv
[SIZEOF_ARG
] = {
4008 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4012 log_request(struct view
*view
, enum request request
, struct line
*line
)
4017 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
4020 return pager_request(view
, request
, line
);
4024 static struct view_ops log_ops
= {
4035 static const char *diff_argv
[SIZEOF_ARG
] = {
4036 "git", "show", "--pretty=fuller", "--no-color", "--root",
4037 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4040 static struct view_ops diff_ops
= {
4055 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
4058 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
4062 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
4063 help_keymap_hidden
[keymap
] ? '+' : '-',
4064 enum_name(keymap_table
[keymap
]));
4066 line
->other
= keymap
;
4068 return help_keymap_hidden
[keymap
];
4072 help_open_keymap(struct view
*view
, enum keymap keymap
)
4074 const char *group
= NULL
;
4075 char buf
[SIZEOF_STR
];
4077 bool add_title
= TRUE
;
4080 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
4081 const char *key
= NULL
;
4083 if (req_info
[i
].request
== REQ_NONE
)
4086 if (!req_info
[i
].request
) {
4087 group
= req_info
[i
].help
;
4091 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4095 if (add_title
&& help_open_keymap_title(view
, keymap
))
4100 add_line_text(view
, group
, LINE_HELP_GROUP
);
4104 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4105 enum_name(req_info
[i
]), req_info
[i
].help
);
4108 group
= "External commands:";
4110 for (i
= 0; i
< run_requests
; i
++) {
4111 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4115 if (!req
|| req
->keymap
!= keymap
)
4118 key
= get_key_name(req
->key
);
4120 key
= "(no key defined)";
4122 if (add_title
&& help_open_keymap_title(view
, keymap
))
4125 add_line_text(view
, group
, LINE_HELP_GROUP
);
4129 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4130 if (!string_format_from(buf
, &bufpos
, "%s%s",
4131 argc
? " " : "", req
->argv
[argc
]))
4134 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4139 help_open(struct view
*view
)
4144 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4145 add_line_text(view
, "", LINE_DEFAULT
);
4147 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4148 help_open_keymap(view
, keymap
);
4154 help_request(struct view
*view
, enum request request
, struct line
*line
)
4158 if (line
->type
== LINE_HELP_KEYMAP
) {
4159 help_keymap_hidden
[line
->other
] =
4160 !help_keymap_hidden
[line
->other
];
4161 view
->p_restore
= TRUE
;
4162 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4167 return pager_request(view
, request
, line
);
4171 static struct view_ops help_ops
= {
4187 struct tree_stack_entry
{
4188 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4189 unsigned long lineno
; /* Line number to restore */
4190 char *name
; /* Position of name in opt_path */
4193 /* The top of the path stack. */
4194 static struct tree_stack_entry
*tree_stack
= NULL
;
4195 unsigned long tree_lineno
= 0;
4198 pop_tree_stack_entry(void)
4200 struct tree_stack_entry
*entry
= tree_stack
;
4202 tree_lineno
= entry
->lineno
;
4204 tree_stack
= entry
->prev
;
4209 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4211 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4212 size_t pathlen
= strlen(opt_path
);
4217 entry
->prev
= tree_stack
;
4218 entry
->name
= opt_path
+ pathlen
;
4221 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4222 pop_tree_stack_entry();
4226 /* Move the current line to the first tree entry. */
4228 entry
->lineno
= lineno
;
4231 /* Parse output from git-ls-tree(1):
4233 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4236 #define SIZEOF_TREE_ATTR \
4237 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4239 #define SIZEOF_TREE_MODE \
4240 STRING_SIZE("100644 ")
4242 #define TREE_ID_OFFSET \
4243 STRING_SIZE("100644 blob ")
4246 char id
[SIZEOF_REV
];
4248 time_t time
; /* Date from the author ident. */
4249 const char *author
; /* Author of the commit. */
4254 tree_path(const struct line
*line
)
4256 return ((struct tree_entry
*) line
->data
)->name
;
4260 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4262 if (line1
->type
!= line2
->type
)
4263 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4264 return strcmp(tree_path(line1
), tree_path(line2
));
4267 static const enum sort_field tree_sort_fields
[] = {
4268 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4270 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4273 tree_compare(const void *l1
, const void *l2
)
4275 const struct line
*line1
= (const struct line
*) l1
;
4276 const struct line
*line2
= (const struct line
*) l2
;
4277 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4278 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4280 if (line1
->type
== LINE_TREE_HEAD
)
4282 if (line2
->type
== LINE_TREE_HEAD
)
4285 switch (get_sort_field(tree_sort_state
)) {
4287 return sort_order(tree_sort_state
, entry1
->time
- entry2
->time
);
4289 case ORDERBY_AUTHOR
:
4290 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4294 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4299 static struct line
*
4300 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4301 const char *mode
, const char *id
)
4303 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4304 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4306 if (!entry
|| !line
) {
4311 strncpy(entry
->name
, path
, strlen(path
));
4313 entry
->mode
= strtoul(mode
, NULL
, 8);
4315 string_copy_rev(entry
->id
, id
);
4321 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4323 static const char *author_name
;
4324 static time_t author_time
;
4326 if (!text
&& *read_date
) {
4331 char *path
= *opt_path
? opt_path
: ".";
4332 /* Find next entry to process */
4333 const char *log_file
[] = {
4334 "git", "log", "--no-color", "--pretty=raw",
4335 "--cc", "--raw", view
->id
, "--", path
, NULL
4340 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4341 report("Tree is empty");
4345 if (!run_io_rd(&io
, log_file
, opt_cdup
, FORMAT_NONE
)) {
4346 report("Failed to load tree data");
4350 done_io(view
->pipe
);
4355 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4356 parse_author_line(text
+ STRING_SIZE("author "),
4357 &author_name
, &author_time
);
4359 } else if (*text
== ':') {
4361 size_t annotated
= 1;
4364 pos
= strchr(text
, '\t');
4368 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4369 text
+= strlen(opt_path
);
4370 pos
= strchr(text
, '/');
4374 for (i
= 1; i
< view
->lines
; i
++) {
4375 struct line
*line
= &view
->line
[i
];
4376 struct tree_entry
*entry
= line
->data
;
4378 annotated
+= !!entry
->author
;
4379 if (entry
->author
|| strcmp(entry
->name
, text
))
4382 entry
->author
= author_name
;
4383 entry
->time
= author_time
;
4388 if (annotated
== view
->lines
)
4389 kill_io(view
->pipe
);
4395 tree_read(struct view
*view
, char *text
)
4397 static bool read_date
= FALSE
;
4398 struct tree_entry
*data
;
4399 struct line
*entry
, *line
;
4400 enum line_type type
;
4401 size_t textlen
= text
? strlen(text
) : 0;
4402 char *path
= text
+ SIZEOF_TREE_ATTR
;
4404 if (read_date
|| !text
)
4405 return tree_read_date(view
, text
, &read_date
);
4407 if (textlen
<= SIZEOF_TREE_ATTR
)
4409 if (view
->lines
== 0 &&
4410 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4413 /* Strip the path part ... */
4415 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4416 size_t striplen
= strlen(opt_path
);
4418 if (pathlen
> striplen
)
4419 memmove(path
, path
+ striplen
,
4420 pathlen
- striplen
+ 1);
4422 /* Insert "link" to parent directory. */
4423 if (view
->lines
== 1 &&
4424 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4428 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4429 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4434 /* Skip "Directory ..." and ".." line. */
4435 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4436 if (tree_compare_entry(line
, entry
) <= 0)
4439 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4443 for (; line
<= entry
; line
++)
4444 line
->dirty
= line
->cleareol
= 1;
4448 if (tree_lineno
> view
->lineno
) {
4449 view
->lineno
= tree_lineno
;
4457 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4459 struct tree_entry
*entry
= line
->data
;
4461 if (line
->type
== LINE_TREE_HEAD
) {
4462 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4465 if (draw_mode(view
, entry
->mode
))
4468 if (opt_author
&& draw_author(view
, entry
->author
))
4471 if (opt_date
&& draw_date(view
, entry
->author
? &entry
->time
: NULL
))
4474 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4482 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4483 int fd
= mkstemp(file
);
4486 report("Failed to create temporary file");
4487 else if (!run_io_append(blob_ops
.argv
, FORMAT_ALL
, fd
))
4488 report("Failed to save blob data to file");
4496 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4498 enum open_flags flags
;
4501 case REQ_VIEW_BLAME
:
4502 if (line
->type
!= LINE_TREE_FILE
) {
4503 report("Blame only supported for files");
4507 string_copy(opt_ref
, view
->vid
);
4511 if (line
->type
!= LINE_TREE_FILE
) {
4512 report("Edit only supported for files");
4513 } else if (!is_head_commit(view
->vid
)) {
4516 open_editor(opt_file
);
4520 case REQ_TOGGLE_SORT_FIELD
:
4521 case REQ_TOGGLE_SORT_ORDER
:
4522 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4527 /* quit view if at top of tree */
4528 return REQ_VIEW_CLOSE
;
4531 line
= &view
->line
[1];
4541 /* Cleanup the stack if the tree view is at a different tree. */
4542 while (!*opt_path
&& tree_stack
)
4543 pop_tree_stack_entry();
4545 switch (line
->type
) {
4547 /* Depending on whether it is a subdirectory or parent link
4548 * mangle the path buffer. */
4549 if (line
== &view
->line
[1] && *opt_path
) {
4550 pop_tree_stack_entry();
4553 const char *basename
= tree_path(line
);
4555 push_tree_stack_entry(basename
, view
->lineno
);
4558 /* Trees and subtrees share the same ID, so they are not not
4559 * unique like blobs. */
4560 flags
= OPEN_RELOAD
;
4561 request
= REQ_VIEW_TREE
;
4564 case LINE_TREE_FILE
:
4565 flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4566 request
= REQ_VIEW_BLOB
;
4573 open_view(view
, request
, flags
);
4574 if (request
== REQ_VIEW_TREE
)
4575 view
->lineno
= tree_lineno
;
4581 tree_grep(struct view
*view
, struct line
*line
)
4583 struct tree_entry
*entry
= line
->data
;
4584 const char *text
[] = {
4586 opt_author
? entry
->author
: "",
4587 opt_date
? mkdate(&entry
->time
) : "",
4591 return grep_text(view
, text
);
4595 tree_select(struct view
*view
, struct line
*line
)
4597 struct tree_entry
*entry
= line
->data
;
4599 if (line
->type
== LINE_TREE_FILE
) {
4600 string_copy_rev(ref_blob
, entry
->id
);
4601 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4603 } else if (line
->type
!= LINE_TREE_DIR
) {
4607 string_copy_rev(view
->ref
, entry
->id
);
4611 tree_prepare(struct view
*view
)
4613 if (view
->lines
== 0 && opt_prefix
[0]) {
4614 char *pos
= opt_prefix
;
4616 while (pos
&& *pos
) {
4617 char *end
= strchr(pos
, '/');
4621 push_tree_stack_entry(pos
, 0);
4629 } else if (strcmp(view
->vid
, view
->id
)) {
4633 return init_io_rd(&view
->io
, view
->ops
->argv
, opt_cdup
, FORMAT_ALL
);
4636 static const char *tree_argv
[SIZEOF_ARG
] = {
4637 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4640 static struct view_ops tree_ops
= {
4653 blob_read(struct view
*view
, char *line
)
4657 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4661 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4668 return pager_request(view
, request
, line
);
4672 static const char *blob_argv
[SIZEOF_ARG
] = {
4673 "git", "cat-file", "blob", "%(blob)", NULL
4676 static struct view_ops blob_ops
= {
4690 * Loading the blame view is a two phase job:
4692 * 1. File content is read either using opt_file from the
4693 * filesystem or using git-cat-file.
4694 * 2. Then blame information is incrementally added by
4695 * reading output from git-blame.
4698 static const char *blame_head_argv
[] = {
4699 "git", "blame", "--incremental", "--", "%(file)", NULL
4702 static const char *blame_ref_argv
[] = {
4703 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4706 static const char *blame_cat_file_argv
[] = {
4707 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4710 struct blame_commit
{
4711 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4712 char title
[128]; /* First line of the commit message. */
4713 const char *author
; /* Author of the commit. */
4714 time_t time
; /* Date from the author ident. */
4715 char filename
[128]; /* Name of file. */
4716 bool has_previous
; /* Was a "previous" line detected. */
4720 struct blame_commit
*commit
;
4721 unsigned long lineno
;
4726 blame_open(struct view
*view
)
4728 char path
[SIZEOF_STR
];
4730 if (!view
->parent
&& *opt_prefix
) {
4731 string_copy(path
, opt_file
);
4732 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4736 if (*opt_ref
|| !io_open(&view
->io
, "%s%s", opt_cdup
, opt_file
)) {
4737 if (!run_io_rd(&view
->io
, blame_cat_file_argv
, opt_cdup
, FORMAT_ALL
))
4741 setup_update(view
, opt_file
);
4742 string_format(view
->ref
, "%s ...", opt_file
);
4747 static struct blame_commit
*
4748 get_blame_commit(struct view
*view
, const char *id
)
4752 for (i
= 0; i
< view
->lines
; i
++) {
4753 struct blame
*blame
= view
->line
[i
].data
;
4758 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4759 return blame
->commit
;
4763 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4766 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4772 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4774 const char *pos
= *posref
;
4777 pos
= strchr(pos
+ 1, ' ');
4778 if (!pos
|| !isdigit(pos
[1]))
4780 *number
= atoi(pos
+ 1);
4781 if (*number
< min
|| *number
> max
)
4788 static struct blame_commit
*
4789 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4791 struct blame_commit
*commit
;
4792 struct blame
*blame
;
4793 const char *pos
= text
+ SIZEOF_REV
- 2;
4794 size_t orig_lineno
= 0;
4798 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4801 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4802 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4803 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4806 commit
= get_blame_commit(view
, text
);
4812 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4815 blame
->commit
= commit
;
4816 blame
->lineno
= orig_lineno
+ group
- 1;
4824 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4827 const char **argv
= *opt_ref
? blame_ref_argv
: blame_head_argv
;
4830 if (view
->lines
== 0 && !view
->parent
)
4831 die("No blame exist for %s", view
->vid
);
4833 if (view
->lines
== 0 || !run_io_rd(&io
, argv
, opt_cdup
, FORMAT_ALL
)) {
4834 report("Failed to load blame data");
4838 done_io(view
->pipe
);
4844 size_t linelen
= strlen(line
);
4845 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
4850 blame
->commit
= NULL
;
4851 strncpy(blame
->text
, line
, linelen
);
4852 blame
->text
[linelen
] = 0;
4853 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
4858 match_blame_header(const char *name
, char **line
)
4860 size_t namelen
= strlen(name
);
4861 bool matched
= !strncmp(name
, *line
, namelen
);
4870 blame_read(struct view
*view
, char *line
)
4872 static struct blame_commit
*commit
= NULL
;
4873 static int blamed
= 0;
4874 static bool read_file
= TRUE
;
4877 return blame_read_file(view
, line
, &read_file
);
4884 string_format(view
->ref
, "%s", view
->vid
);
4885 if (view_is_displayed(view
)) {
4886 update_view_title(view
);
4887 redraw_view_from(view
, 0);
4893 commit
= parse_blame_commit(view
, line
, &blamed
);
4894 string_format(view
->ref
, "%s %2d%%", view
->vid
,
4895 view
->lines
? blamed
* 100 / view
->lines
: 0);
4897 } else if (match_blame_header("author ", &line
)) {
4898 commit
->author
= get_author(line
);
4900 } else if (match_blame_header("author-time ", &line
)) {
4901 commit
->time
= (time_t) atol(line
);
4903 } else if (match_blame_header("author-tz ", &line
)) {
4904 parse_timezone(&commit
->time
, line
);
4906 } else if (match_blame_header("summary ", &line
)) {
4907 string_ncopy(commit
->title
, line
, strlen(line
));
4909 } else if (match_blame_header("previous ", &line
)) {
4910 commit
->has_previous
= TRUE
;
4912 } else if (match_blame_header("filename ", &line
)) {
4913 string_ncopy(commit
->filename
, line
, strlen(line
));
4921 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4923 struct blame
*blame
= line
->data
;
4924 time_t *time
= NULL
;
4925 const char *id
= NULL
, *author
= NULL
;
4926 char text
[SIZEOF_STR
];
4928 if (blame
->commit
&& *blame
->commit
->filename
) {
4929 id
= blame
->commit
->id
;
4930 author
= blame
->commit
->author
;
4931 time
= &blame
->commit
->time
;
4934 if (opt_date
&& draw_date(view
, time
))
4937 if (opt_author
&& draw_author(view
, author
))
4940 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
4943 if (draw_lineno(view
, lineno
))
4946 string_expand(text
, sizeof(text
), blame
->text
, opt_tab_size
);
4947 draw_text(view
, LINE_DEFAULT
, text
, TRUE
);
4952 check_blame_commit(struct blame
*blame
, bool check_null_id
)
4955 report("Commit data not loaded yet");
4956 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4957 report("No commit exist for the selected line");
4964 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
4966 const char *diff_tree_argv
[] = {
4967 "git", "diff-tree", "-U0", blame
->commit
->id
,
4968 "--", blame
->commit
->filename
, NULL
4971 int parent_lineno
= -1;
4972 int blamed_lineno
= -1;
4975 if (!run_io(&io
, diff_tree_argv
, NULL
, IO_RD
))
4978 while ((line
= io_get(&io
, '\n', TRUE
))) {
4980 char *pos
= strchr(line
, '+');
4982 parent_lineno
= atoi(line
+ 4);
4984 blamed_lineno
= atoi(pos
+ 1);
4986 } else if (*line
== '+' && parent_lineno
!= -1) {
4987 if (blame
->lineno
== blamed_lineno
- 1 &&
4988 !strcmp(blame
->text
, line
+ 1)) {
4989 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
5000 blame_request(struct view
*view
, enum request request
, struct line
*line
)
5002 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
5003 struct blame
*blame
= line
->data
;
5006 case REQ_VIEW_BLAME
:
5007 if (check_blame_commit(blame
, TRUE
)) {
5008 string_copy(opt_ref
, blame
->commit
->id
);
5009 string_copy(opt_file
, blame
->commit
->filename
);
5011 view
->lineno
= blame
->lineno
;
5012 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5017 if (check_blame_commit(blame
, TRUE
) &&
5018 select_commit_parent(blame
->commit
->id
, opt_ref
,
5019 blame
->commit
->filename
)) {
5020 string_copy(opt_file
, blame
->commit
->filename
);
5021 setup_blame_parent_line(view
, blame
);
5022 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5027 if (!check_blame_commit(blame
, FALSE
))
5030 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
5031 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
5034 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
5035 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
5036 const char *diff_index_argv
[] = {
5037 "git", "diff-index", "--root", "--patch-with-stat",
5038 "-C", "-M", "HEAD", "--", view
->vid
, NULL
5041 if (!blame
->commit
->has_previous
) {
5042 diff_index_argv
[1] = "diff";
5043 diff_index_argv
[2] = "--no-color";
5044 diff_index_argv
[6] = "--";
5045 diff_index_argv
[7] = "/dev/null";
5048 if (!prepare_update(diff
, diff_index_argv
, NULL
, FORMAT_DASH
)) {
5049 report("Failed to allocate diff command");
5052 flags
|= OPEN_PREPARED
;
5055 open_view(view
, REQ_VIEW_DIFF
, flags
);
5056 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5057 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
5068 blame_grep(struct view
*view
, struct line
*line
)
5070 struct blame
*blame
= line
->data
;
5071 struct blame_commit
*commit
= blame
->commit
;
5072 const char *text
[] = {
5074 commit
? commit
->title
: "",
5075 commit
? commit
->id
: "",
5076 commit
&& opt_author
? commit
->author
: "",
5077 commit
&& opt_date
? mkdate(&commit
->time
) : "",
5081 return grep_text(view
, text
);
5085 blame_select(struct view
*view
, struct line
*line
)
5087 struct blame
*blame
= line
->data
;
5088 struct blame_commit
*commit
= blame
->commit
;
5093 if (!strcmp(commit
->id
, NULL_ID
))
5094 string_ncopy(ref_commit
, "HEAD", 4);
5096 string_copy_rev(ref_commit
, commit
->id
);
5099 static struct view_ops blame_ops
= {
5115 const char *author
; /* Author of the last commit. */
5116 time_t time
; /* Date of the last activity. */
5117 const struct ref
*ref
; /* Name and commit ID information. */
5120 static const struct ref branch_all
;
5122 static const enum sort_field branch_sort_fields
[] = {
5123 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5125 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5128 branch_compare(const void *l1
, const void *l2
)
5130 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5131 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5133 switch (get_sort_field(branch_sort_state
)) {
5135 return sort_order(branch_sort_state
, branch1
->time
- branch2
->time
);
5137 case ORDERBY_AUTHOR
:
5138 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5142 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5147 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5149 struct branch
*branch
= line
->data
;
5150 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5152 if (opt_date
&& draw_date(view
, branch
->author
? &branch
->time
: NULL
))
5155 if (opt_author
&& draw_author(view
, branch
->author
))
5158 draw_text(view
, type
, branch
->ref
== &branch_all
? "All branches" : branch
->ref
->name
, TRUE
);
5163 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5165 struct branch
*branch
= line
->data
;
5170 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5173 case REQ_TOGGLE_SORT_FIELD
:
5174 case REQ_TOGGLE_SORT_ORDER
:
5175 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5179 if (branch
->ref
== &branch_all
) {
5180 const char *all_branches_argv
[] = {
5181 "git", "log", "--no-color", "--pretty=raw", "--parents",
5182 "--topo-order", "--all", NULL
5184 struct view
*main_view
= VIEW(REQ_VIEW_MAIN
);
5186 if (!prepare_update(main_view
, all_branches_argv
, NULL
, FORMAT_NONE
)) {
5187 report("Failed to load view of all branches");
5190 open_view(view
, REQ_VIEW_MAIN
, OPEN_PREPARED
| OPEN_SPLIT
);
5192 open_view(view
, REQ_VIEW_MAIN
, OPEN_SPLIT
);
5202 branch_read(struct view
*view
, char *line
)
5204 static char id
[SIZEOF_REV
];
5205 struct branch
*reference
;
5211 switch (get_line_type(line
)) {
5213 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5217 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5218 struct branch
*branch
= view
->line
[i
].data
;
5220 if (strcmp(branch
->ref
->id
, id
))
5223 view
->line
[i
].dirty
= TRUE
;
5225 branch
->author
= reference
->author
;
5226 branch
->time
= reference
->time
;
5230 parse_author_line(line
+ STRING_SIZE("author "),
5231 &branch
->author
, &branch
->time
);
5243 branch_open_visitor(void *data
, const struct ref
*ref
)
5245 struct view
*view
= data
;
5246 struct branch
*branch
;
5248 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5251 branch
= calloc(1, sizeof(*branch
));
5256 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5260 branch_open(struct view
*view
)
5262 const char *branch_log
[] = {
5263 "git", "log", "--no-color", "--pretty=raw",
5264 "--simplify-by-decoration", "--all", NULL
5267 if (!run_io_rd(&view
->io
, branch_log
, NULL
, FORMAT_NONE
)) {
5268 report("Failed to load branch data");
5272 setup_update(view
, view
->id
);
5273 branch_open_visitor(view
, &branch_all
);
5274 foreach_ref(branch_open_visitor
, view
);
5275 view
->p_restore
= TRUE
;
5281 branch_grep(struct view
*view
, struct line
*line
)
5283 struct branch
*branch
= line
->data
;
5284 const char *text
[] = {
5290 return grep_text(view
, text
);
5294 branch_select(struct view
*view
, struct line
*line
)
5296 struct branch
*branch
= line
->data
;
5298 string_copy_rev(view
->ref
, branch
->ref
->id
);
5299 string_copy_rev(ref_commit
, branch
->ref
->id
);
5300 string_copy_rev(ref_head
, branch
->ref
->id
);
5303 static struct view_ops branch_ops
= {
5322 char rev
[SIZEOF_REV
];
5323 char name
[SIZEOF_STR
];
5327 char rev
[SIZEOF_REV
];
5328 char name
[SIZEOF_STR
];
5332 static char status_onbranch
[SIZEOF_STR
];
5333 static struct status stage_status
;
5334 static enum line_type stage_line_type
;
5335 static size_t stage_chunks
;
5336 static int *stage_chunk
;
5338 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5340 /* This should work even for the "On branch" line. */
5342 status_has_none(struct view
*view
, struct line
*line
)
5344 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5347 /* Get fields from the diff line:
5348 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5351 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5353 const char *old_mode
= buf
+ 1;
5354 const char *new_mode
= buf
+ 8;
5355 const char *old_rev
= buf
+ 15;
5356 const char *new_rev
= buf
+ 56;
5357 const char *status
= buf
+ 97;
5360 old_mode
[-1] != ':' ||
5361 new_mode
[-1] != ' ' ||
5362 old_rev
[-1] != ' ' ||
5363 new_rev
[-1] != ' ' ||
5367 file
->status
= *status
;
5369 string_copy_rev(file
->old
.rev
, old_rev
);
5370 string_copy_rev(file
->new.rev
, new_rev
);
5372 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5373 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5375 file
->old
.name
[0] = file
->new.name
[0] = 0;
5381 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5383 struct status
*unmerged
= NULL
;
5387 if (!run_io(&io
, argv
, opt_cdup
, IO_RD
))
5390 add_line_data(view
, NULL
, type
);
5392 while ((buf
= io_get(&io
, 0, TRUE
))) {
5393 struct status
*file
= unmerged
;
5396 file
= calloc(1, sizeof(*file
));
5397 if (!file
|| !add_line_data(view
, file
, type
))
5401 /* Parse diff info part. */
5403 file
->status
= status
;
5405 string_copy(file
->old
.rev
, NULL_ID
);
5407 } else if (!file
->status
|| file
== unmerged
) {
5408 if (!status_get_diff(file
, buf
, strlen(buf
)))
5411 buf
= io_get(&io
, 0, TRUE
);
5415 /* Collapse all modified entries that follow an
5416 * associated unmerged entry. */
5417 if (unmerged
== file
) {
5418 unmerged
->status
= 'U';
5420 } else if (file
->status
== 'U') {
5425 /* Grab the old name for rename/copy. */
5426 if (!*file
->old
.name
&&
5427 (file
->status
== 'R' || file
->status
== 'C')) {
5428 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5430 buf
= io_get(&io
, 0, TRUE
);
5435 /* git-ls-files just delivers a NUL separated list of
5436 * file names similar to the second half of the
5437 * git-diff-* output. */
5438 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5439 if (!*file
->old
.name
)
5440 string_copy(file
->old
.name
, file
->new.name
);
5444 if (io_error(&io
)) {
5450 if (!view
->line
[view
->lines
- 1].data
)
5451 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5457 /* Don't show unmerged entries in the staged section. */
5458 static const char *status_diff_index_argv
[] = {
5459 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5460 "--cached", "-M", "HEAD", NULL
5463 static const char *status_diff_files_argv
[] = {
5464 "git", "diff-files", "-z", NULL
5467 static const char *status_list_other_argv
[] = {
5468 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix
, NULL
5471 static const char *status_list_no_head_argv
[] = {
5472 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5475 static const char *update_index_argv
[] = {
5476 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5479 /* Restore the previous line number to stay in the context or select a
5480 * line with something that can be updated. */
5482 status_restore(struct view
*view
)
5484 if (view
->p_lineno
>= view
->lines
)
5485 view
->p_lineno
= view
->lines
- 1;
5486 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5488 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5491 /* If the above fails, always skip the "On branch" line. */
5492 if (view
->p_lineno
< view
->lines
)
5493 view
->lineno
= view
->p_lineno
;
5497 if (view
->lineno
< view
->offset
)
5498 view
->offset
= view
->lineno
;
5499 else if (view
->offset
+ view
->height
<= view
->lineno
)
5500 view
->offset
= view
->lineno
- view
->height
+ 1;
5502 view
->p_restore
= FALSE
;
5506 status_update_onbranch(void)
5508 static const char *paths
[][2] = {
5509 { "rebase-apply/rebasing", "Rebasing" },
5510 { "rebase-apply/applying", "Applying mailbox" },
5511 { "rebase-apply/", "Rebasing mailbox" },
5512 { "rebase-merge/interactive", "Interactive rebase" },
5513 { "rebase-merge/", "Rebase merge" },
5514 { "MERGE_HEAD", "Merging" },
5515 { "BISECT_LOG", "Bisecting" },
5516 { "HEAD", "On branch" },
5518 char buf
[SIZEOF_STR
];
5522 if (is_initial_commit()) {
5523 string_copy(status_onbranch
, "Initial commit");
5527 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5528 char *head
= opt_head
;
5530 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5531 lstat(buf
, &stat
) < 0)
5537 if (io_open(&io
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5538 io_read_buf(&io
, buf
, sizeof(buf
))) {
5540 if (!prefixcmp(head
, "refs/heads/"))
5541 head
+= STRING_SIZE("refs/heads/");
5545 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5546 string_copy(status_onbranch
, opt_head
);
5550 string_copy(status_onbranch
, "Not currently on any branch");
5553 /* First parse staged info using git-diff-index(1), then parse unstaged
5554 * info using git-diff-files(1), and finally untracked files using
5555 * git-ls-files(1). */
5557 status_open(struct view
*view
)
5561 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5562 status_update_onbranch();
5564 run_io_bg(update_index_argv
);
5566 if (is_initial_commit()) {
5567 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5569 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5573 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5574 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5577 /* Restore the exact position or use the specialized restore
5579 if (!view
->p_restore
)
5580 status_restore(view
);
5585 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5587 struct status
*status
= line
->data
;
5588 enum line_type type
;
5592 switch (line
->type
) {
5593 case LINE_STAT_STAGED
:
5594 type
= LINE_STAT_SECTION
;
5595 text
= "Changes to be committed:";
5598 case LINE_STAT_UNSTAGED
:
5599 type
= LINE_STAT_SECTION
;
5600 text
= "Changed but not updated:";
5603 case LINE_STAT_UNTRACKED
:
5604 type
= LINE_STAT_SECTION
;
5605 text
= "Untracked files:";
5608 case LINE_STAT_NONE
:
5609 type
= LINE_DEFAULT
;
5610 text
= " (no files)";
5613 case LINE_STAT_HEAD
:
5614 type
= LINE_STAT_HEAD
;
5615 text
= status_onbranch
;
5622 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5624 buf
[0] = status
->status
;
5625 if (draw_text(view
, line
->type
, buf
, TRUE
))
5627 type
= LINE_DEFAULT
;
5628 text
= status
->new.name
;
5631 draw_text(view
, type
, text
, TRUE
);
5636 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5638 if (displayed_views() == 2 || display
[current_view
] != view
)
5639 maximize_view(view
);
5640 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5645 status_enter(struct view
*view
, struct line
*line
)
5647 struct status
*status
= line
->data
;
5648 const char *oldpath
= status
? status
->old
.name
: NULL
;
5649 /* Diffs for unmerged entries are empty when passing the new
5650 * path, so leave it empty. */
5651 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5653 enum open_flags split
;
5654 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5656 if (line
->type
== LINE_STAT_NONE
||
5657 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5658 report("No file to diff");
5662 switch (line
->type
) {
5663 case LINE_STAT_STAGED
:
5664 if (is_initial_commit()) {
5665 const char *no_head_diff_argv
[] = {
5666 "git", "diff", "--no-color", "--patch-with-stat",
5667 "--", "/dev/null", newpath
, NULL
5670 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
, FORMAT_DASH
))
5671 return status_load_error(view
, stage
, newpath
);
5673 const char *index_show_argv
[] = {
5674 "git", "diff-index", "--root", "--patch-with-stat",
5675 "-C", "-M", "--cached", "HEAD", "--",
5676 oldpath
, newpath
, NULL
5679 if (!prepare_update(stage
, index_show_argv
, opt_cdup
, FORMAT_DASH
))
5680 return status_load_error(view
, stage
, newpath
);
5684 info
= "Staged changes to %s";
5686 info
= "Staged changes";
5689 case LINE_STAT_UNSTAGED
:
5691 const char *files_show_argv
[] = {
5692 "git", "diff-files", "--root", "--patch-with-stat",
5693 "-C", "-M", "--", oldpath
, newpath
, NULL
5696 if (!prepare_update(stage
, files_show_argv
, opt_cdup
, FORMAT_DASH
))
5697 return status_load_error(view
, stage
, newpath
);
5699 info
= "Unstaged changes to %s";
5701 info
= "Unstaged changes";
5704 case LINE_STAT_UNTRACKED
:
5706 report("No file to show");
5710 if (!suffixcmp(status
->new.name
, -1, "/")) {
5711 report("Cannot display a directory");
5715 if (!prepare_update_file(stage
, newpath
))
5716 return status_load_error(view
, stage
, newpath
);
5717 info
= "Untracked file %s";
5720 case LINE_STAT_HEAD
:
5724 die("line type %d not handled in switch", line
->type
);
5727 split
= view_is_displayed(view
) ? OPEN_SPLIT
: 0;
5728 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5729 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5731 stage_status
= *status
;
5733 memset(&stage_status
, 0, sizeof(stage_status
));
5736 stage_line_type
= line
->type
;
5738 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5745 status_exists(struct status
*status
, enum line_type type
)
5747 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5748 unsigned long lineno
;
5750 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5751 struct line
*line
= &view
->line
[lineno
];
5752 struct status
*pos
= line
->data
;
5754 if (line
->type
!= type
)
5756 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5757 select_view_line(view
, lineno
);
5760 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5761 select_view_line(view
, lineno
);
5771 status_update_prepare(struct io
*io
, enum line_type type
)
5773 const char *staged_argv
[] = {
5774 "git", "update-index", "-z", "--index-info", NULL
5776 const char *others_argv
[] = {
5777 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5781 case LINE_STAT_STAGED
:
5782 return run_io(io
, staged_argv
, opt_cdup
, IO_WR
);
5784 case LINE_STAT_UNSTAGED
:
5785 case LINE_STAT_UNTRACKED
:
5786 return run_io(io
, others_argv
, opt_cdup
, IO_WR
);
5789 die("line type %d not handled in switch", type
);
5795 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5797 char buf
[SIZEOF_STR
];
5801 case LINE_STAT_STAGED
:
5802 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5805 status
->old
.name
, 0))
5809 case LINE_STAT_UNSTAGED
:
5810 case LINE_STAT_UNTRACKED
:
5811 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
5816 die("line type %d not handled in switch", type
);
5819 return io_write(io
, buf
, bufsize
);
5823 status_update_file(struct status
*status
, enum line_type type
)
5828 if (!status_update_prepare(&io
, type
))
5831 result
= status_update_write(&io
, status
, type
);
5832 return done_io(&io
) && result
;
5836 status_update_files(struct view
*view
, struct line
*line
)
5838 char buf
[sizeof(view
->ref
)];
5841 struct line
*pos
= view
->line
+ view
->lines
;
5844 int cursor_y
= -1, cursor_x
= -1;
5846 if (!status_update_prepare(&io
, line
->type
))
5849 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
5852 string_copy(buf
, view
->ref
);
5853 getsyx(cursor_y
, cursor_x
);
5854 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
5855 int almost_done
= file
* 100 / files
;
5857 if (almost_done
> done
) {
5859 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
5861 update_view_title(view
);
5862 setsyx(cursor_y
, cursor_x
);
5865 result
= status_update_write(&io
, line
->data
, line
->type
);
5867 string_copy(view
->ref
, buf
);
5869 return done_io(&io
) && result
;
5873 status_update(struct view
*view
)
5875 struct line
*line
= &view
->line
[view
->lineno
];
5877 assert(view
->lines
);
5880 /* This should work even for the "On branch" line. */
5881 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
5882 report("Nothing to update");
5886 if (!status_update_files(view
, line
+ 1)) {
5887 report("Failed to update file status");
5891 } else if (!status_update_file(line
->data
, line
->type
)) {
5892 report("Failed to update file status");
5900 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
5902 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
5903 if (type
== LINE_STAT_STAGED
) {
5904 report("Cannot revert changes to staged files");
5905 } else if (type
== LINE_STAT_UNTRACKED
) {
5906 report("Cannot revert changes to untracked files");
5907 } else if (has_none
) {
5908 report("Nothing to revert");
5910 report("Cannot revert changes to multiple files");
5913 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5914 char mode
[10] = "100644";
5915 const char *reset_argv
[] = {
5916 "git", "update-index", "--cacheinfo", mode
,
5917 status
->old
.rev
, status
->old
.name
, NULL
5919 const char *checkout_argv
[] = {
5920 "git", "checkout", "--", status
->old
.name
, NULL
5923 if (status
->status
== 'U') {
5924 string_format(mode
, "%5o", status
->old
.mode
);
5926 if (status
->old
.mode
== 0 && status
->new.mode
== 0) {
5927 reset_argv
[2] = "--force-remove";
5928 reset_argv
[3] = status
->old
.name
;
5929 reset_argv
[4] = NULL
;
5932 if (!run_io_fg(reset_argv
, opt_cdup
))
5934 if (status
->old
.mode
== 0 && status
->new.mode
== 0)
5938 return run_io_fg(checkout_argv
, opt_cdup
);
5945 status_request(struct view
*view
, enum request request
, struct line
*line
)
5947 struct status
*status
= line
->data
;
5950 case REQ_STATUS_UPDATE
:
5951 if (!status_update(view
))
5955 case REQ_STATUS_REVERT
:
5956 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
5960 case REQ_STATUS_MERGE
:
5961 if (!status
|| status
->status
!= 'U') {
5962 report("Merging only possible for files with unmerged status ('U').");
5965 open_mergetool(status
->new.name
);
5971 if (status
->status
== 'D') {
5972 report("File has been deleted.");
5976 open_editor(status
->new.name
);
5979 case REQ_VIEW_BLAME
:
5985 /* After returning the status view has been split to
5986 * show the stage view. No further reloading is
5988 return status_enter(view
, line
);
5991 /* Simply reload the view. */
5998 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
6004 status_select(struct view
*view
, struct line
*line
)
6006 struct status
*status
= line
->data
;
6007 char file
[SIZEOF_STR
] = "all files";
6011 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
6014 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
6017 switch (line
->type
) {
6018 case LINE_STAT_STAGED
:
6019 text
= "Press %s to unstage %s for commit";
6022 case LINE_STAT_UNSTAGED
:
6023 text
= "Press %s to stage %s for commit";
6026 case LINE_STAT_UNTRACKED
:
6027 text
= "Press %s to stage %s for addition";
6030 case LINE_STAT_HEAD
:
6031 case LINE_STAT_NONE
:
6032 text
= "Nothing to update";
6036 die("line type %d not handled in switch", line
->type
);
6039 if (status
&& status
->status
== 'U') {
6040 text
= "Press %s to resolve conflict in %s";
6041 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
6044 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
6047 string_format(view
->ref
, text
, key
, file
);
6049 string_copy(opt_file
, status
->new.name
);
6053 status_grep(struct view
*view
, struct line
*line
)
6055 struct status
*status
= line
->data
;
6058 const char buf
[2] = { status
->status
, 0 };
6059 const char *text
[] = { status
->new.name
, buf
, NULL
};
6061 return grep_text(view
, text
);
6067 static struct view_ops status_ops
= {
6080 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
6082 while (line
< end
) {
6083 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
6084 !io_write(io
, "\n", 1))
6087 if (line
->type
== LINE_DIFF_CHUNK
||
6088 line
->type
== LINE_DIFF_HEADER
)
6095 static struct line
*
6096 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
6098 for (; view
->line
< line
; line
--)
6099 if (line
->type
== type
)
6106 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
6108 const char *apply_argv
[SIZEOF_ARG
] = {
6109 "git", "apply", "--whitespace=nowarn", NULL
6111 struct line
*diff_hdr
;
6115 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6120 apply_argv
[argc
++] = "--cached";
6121 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6122 apply_argv
[argc
++] = "-R";
6123 apply_argv
[argc
++] = "-";
6124 apply_argv
[argc
++] = NULL
;
6125 if (!run_io(&io
, apply_argv
, opt_cdup
, IO_WR
))
6128 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6129 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6133 run_io_bg(update_index_argv
);
6135 return chunk
? TRUE
: FALSE
;
6139 stage_update(struct view
*view
, struct line
*line
)
6141 struct line
*chunk
= NULL
;
6143 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6144 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6147 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6148 report("Failed to apply chunk");
6152 } else if (!stage_status
.status
) {
6153 view
= VIEW(REQ_VIEW_STATUS
);
6155 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6156 if (line
->type
== stage_line_type
)
6159 if (!status_update_files(view
, line
+ 1)) {
6160 report("Failed to update files");
6164 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6165 report("Failed to update file");
6173 stage_revert(struct view
*view
, struct line
*line
)
6175 struct line
*chunk
= NULL
;
6177 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6178 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6181 if (!prompt_yesno("Are you sure you want to revert changes?"))
6184 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6185 report("Failed to revert chunk");
6191 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6192 stage_line_type
, FALSE
);
6198 stage_next(struct view
*view
, struct line
*line
)
6202 if (!stage_chunks
) {
6203 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6204 if (line
->type
!= LINE_DIFF_CHUNK
)
6207 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6208 report("Allocation failure");
6212 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6216 for (i
= 0; i
< stage_chunks
; i
++) {
6217 if (stage_chunk
[i
] > view
->lineno
) {
6218 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6219 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6224 report("No next chunk found");
6228 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6231 case REQ_STATUS_UPDATE
:
6232 if (!stage_update(view
, line
))
6236 case REQ_STATUS_REVERT
:
6237 if (!stage_revert(view
, line
))
6241 case REQ_STAGE_NEXT
:
6242 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6243 report("File is untracked; press %s to add",
6244 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6247 stage_next(view
, line
);
6251 if (!stage_status
.new.name
[0])
6253 if (stage_status
.status
== 'D') {
6254 report("File has been deleted.");
6258 open_editor(stage_status
.new.name
);
6262 /* Reload everything ... */
6265 case REQ_VIEW_BLAME
:
6266 if (stage_status
.new.name
[0]) {
6267 string_copy(opt_file
, stage_status
.new.name
);
6273 return pager_request(view
, request
, line
);
6279 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6280 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6282 /* Check whether the staged entry still exists, and close the
6283 * stage view if it doesn't. */
6284 if (!status_exists(&stage_status
, stage_line_type
)) {
6285 status_restore(VIEW(REQ_VIEW_STATUS
));
6286 return REQ_VIEW_CLOSE
;
6289 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6290 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6291 report("Cannot display a directory");
6295 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6296 report("Failed to open file: %s", strerror(errno
));
6300 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6305 static struct view_ops stage_ops
= {
6322 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6323 char title
[128]; /* First line of the commit message. */
6324 const char *author
; /* Author of the commit. */
6325 time_t time
; /* Date from the author ident. */
6326 struct ref_list
*refs
; /* Repository references. */
6327 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6328 size_t graph_size
; /* The width of the graph array. */
6329 bool has_parents
; /* Rewritten --parents seen. */
6332 /* Size of rev graph with no "padding" columns */
6333 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6336 struct rev_graph
*prev
, *next
, *parents
;
6337 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6339 struct commit
*commit
;
6341 unsigned int boundary
:1;
6344 /* Parents of the commit being visualized. */
6345 static struct rev_graph graph_parents
[4];
6347 /* The current stack of revisions on the graph. */
6348 static struct rev_graph graph_stacks
[4] = {
6349 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6350 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6351 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6352 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6356 graph_parent_is_merge(struct rev_graph
*graph
)
6358 return graph
->parents
->size
> 1;
6362 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6364 struct commit
*commit
= graph
->commit
;
6366 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6367 commit
->graph
[commit
->graph_size
++] = symbol
;
6371 clear_rev_graph(struct rev_graph
*graph
)
6373 graph
->boundary
= 0;
6374 graph
->size
= graph
->pos
= 0;
6375 graph
->commit
= NULL
;
6376 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6380 done_rev_graph(struct rev_graph
*graph
)
6382 if (graph_parent_is_merge(graph
) &&
6383 graph
->pos
< graph
->size
- 1 &&
6384 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6385 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6387 graph
->commit
->graph_size
= i
* 2;
6388 while (i
< graph
->next
->size
- 1) {
6389 append_to_rev_graph(graph
, ' ');
6390 append_to_rev_graph(graph
, '\\');
6395 clear_rev_graph(graph
);
6399 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6403 /* "Collapse" duplicate parents lines.
6405 * FIXME: This needs to also update update the drawn graph but
6406 * for now it just serves as a method for pruning graph lines. */
6407 for (i
= 0; i
< graph
->size
; i
++)
6408 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6411 if (graph
->size
< SIZEOF_REVITEMS
) {
6412 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6417 get_rev_graph_symbol(struct rev_graph
*graph
)
6421 if (graph
->boundary
)
6422 symbol
= REVGRAPH_BOUND
;
6423 else if (graph
->parents
->size
== 0)
6424 symbol
= REVGRAPH_INIT
;
6425 else if (graph_parent_is_merge(graph
))
6426 symbol
= REVGRAPH_MERGE
;
6427 else if (graph
->pos
>= graph
->size
)
6428 symbol
= REVGRAPH_BRANCH
;
6430 symbol
= REVGRAPH_COMMIT
;
6436 draw_rev_graph(struct rev_graph
*graph
)
6439 chtype separator
, line
;
6441 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6442 static struct rev_filler fillers
[] = {
6448 chtype symbol
= get_rev_graph_symbol(graph
);
6449 struct rev_filler
*filler
;
6452 if (opt_line_graphics
)
6453 fillers
[DEFAULT
].line
= line_graphics
[LINE_GRAPHIC_VLINE
];
6455 filler
= &fillers
[DEFAULT
];
6457 for (i
= 0; i
< graph
->pos
; i
++) {
6458 append_to_rev_graph(graph
, filler
->line
);
6459 if (graph_parent_is_merge(graph
->prev
) &&
6460 graph
->prev
->pos
== i
)
6461 filler
= &fillers
[RSHARP
];
6463 append_to_rev_graph(graph
, filler
->separator
);
6466 /* Place the symbol for this revision. */
6467 append_to_rev_graph(graph
, symbol
);
6469 if (graph
->prev
->size
> graph
->size
)
6470 filler
= &fillers
[RDIAG
];
6472 filler
= &fillers
[DEFAULT
];
6476 for (; i
< graph
->size
; i
++) {
6477 append_to_rev_graph(graph
, filler
->separator
);
6478 append_to_rev_graph(graph
, filler
->line
);
6479 if (graph_parent_is_merge(graph
->prev
) &&
6480 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6481 filler
= &fillers
[RSHARP
];
6482 if (graph
->prev
->size
> graph
->size
)
6483 filler
= &fillers
[LDIAG
];
6486 if (graph
->prev
->size
> graph
->size
) {
6487 append_to_rev_graph(graph
, filler
->separator
);
6488 if (filler
->line
!= ' ')
6489 append_to_rev_graph(graph
, filler
->line
);
6493 /* Prepare the next rev graph */
6495 prepare_rev_graph(struct rev_graph
*graph
)
6499 /* First, traverse all lines of revisions up to the active one. */
6500 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6501 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6504 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6507 /* Interleave the new revision parent(s). */
6508 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6509 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6511 /* Lastly, put any remaining revisions. */
6512 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6513 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6517 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6519 /* If this is the finalizing update ... */
6521 prepare_rev_graph(graph
);
6523 /* Graph visualization needs a one rev look-ahead,
6524 * so the first update doesn't visualize anything. */
6525 if (!graph
->prev
->commit
)
6528 if (view
->lines
> 2)
6529 view
->line
[view
->lines
- 3].dirty
= 1;
6530 if (view
->lines
> 1)
6531 view
->line
[view
->lines
- 2].dirty
= 1;
6532 draw_rev_graph(graph
->prev
);
6533 done_rev_graph(graph
->prev
->prev
);
6541 static const char *main_argv
[SIZEOF_ARG
] = {
6542 "git", "log", "--no-color", "--pretty=raw", "--parents",
6543 "--topo-order", "%(head)", NULL
6547 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6549 struct commit
*commit
= line
->data
;
6551 if (!commit
->author
)
6554 if (opt_date
&& draw_date(view
, &commit
->time
))
6557 if (opt_author
&& draw_author(view
, commit
->author
))
6560 if (opt_rev_graph
&& commit
->graph_size
&&
6561 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6564 if (opt_show_refs
&& commit
->refs
) {
6567 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6568 struct ref
*ref
= commit
->refs
->refs
[i
];
6569 enum line_type type
;
6572 type
= LINE_MAIN_HEAD
;
6574 type
= LINE_MAIN_LOCAL_TAG
;
6576 type
= LINE_MAIN_TAG
;
6577 else if (ref
->tracked
)
6578 type
= LINE_MAIN_TRACKED
;
6579 else if (ref
->remote
)
6580 type
= LINE_MAIN_REMOTE
;
6582 type
= LINE_MAIN_REF
;
6584 if (draw_text(view
, type
, "[", TRUE
) ||
6585 draw_text(view
, type
, ref
->name
, TRUE
) ||
6586 draw_text(view
, type
, "]", TRUE
))
6589 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6594 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6598 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6600 main_read(struct view
*view
, char *line
)
6602 static struct rev_graph
*graph
= graph_stacks
;
6603 enum line_type type
;
6604 struct commit
*commit
;
6609 if (!view
->lines
&& !view
->parent
)
6610 die("No revisions match the given arguments.");
6611 if (view
->lines
> 0) {
6612 commit
= view
->line
[view
->lines
- 1].data
;
6613 view
->line
[view
->lines
- 1].dirty
= 1;
6614 if (!commit
->author
) {
6617 graph
->commit
= NULL
;
6620 update_rev_graph(view
, graph
);
6622 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6623 clear_rev_graph(&graph_stacks
[i
]);
6627 type
= get_line_type(line
);
6628 if (type
== LINE_COMMIT
) {
6629 commit
= calloc(1, sizeof(struct commit
));
6633 line
+= STRING_SIZE("commit ");
6635 graph
->boundary
= 1;
6639 string_copy_rev(commit
->id
, line
);
6640 commit
->refs
= get_ref_list(commit
->id
);
6641 graph
->commit
= commit
;
6642 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6644 while ((line
= strchr(line
, ' '))) {
6646 push_rev_graph(graph
->parents
, line
);
6647 commit
->has_parents
= TRUE
;
6654 commit
= view
->line
[view
->lines
- 1].data
;
6658 if (commit
->has_parents
)
6660 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6664 parse_author_line(line
+ STRING_SIZE("author "),
6665 &commit
->author
, &commit
->time
);
6666 update_rev_graph(view
, graph
);
6667 graph
= graph
->next
;
6671 /* Fill in the commit title if it has not already been set. */
6672 if (commit
->title
[0])
6675 /* Require titles to start with a non-space character at the
6676 * offset used by git log. */
6677 if (strncmp(line
, " ", 4))
6680 /* Well, if the title starts with a whitespace character,
6681 * try to be forgiving. Otherwise we end up with no title. */
6682 while (isspace(*line
))
6686 /* FIXME: More graceful handling of titles; append "..." to
6687 * shortened titles, etc. */
6689 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6690 view
->line
[view
->lines
- 1].dirty
= 1;
6697 main_request(struct view
*view
, enum request request
, struct line
*line
)
6699 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
6703 open_view(view
, REQ_VIEW_DIFF
, flags
);
6707 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6717 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6722 if (!opt_show_refs
|| !list
)
6725 for (i
= 0; i
< list
->size
; i
++) {
6726 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6734 main_grep(struct view
*view
, struct line
*line
)
6736 struct commit
*commit
= line
->data
;
6737 const char *text
[] = {
6739 opt_author
? commit
->author
: "",
6740 opt_date
? mkdate(&commit
->time
) : "",
6744 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6748 main_select(struct view
*view
, struct line
*line
)
6750 struct commit
*commit
= line
->data
;
6752 string_copy_rev(view
->ref
, commit
->id
);
6753 string_copy_rev(ref_commit
, view
->ref
);
6756 static struct view_ops main_ops
= {
6769 * Unicode / UTF-8 handling
6771 * NOTE: Much of the following code for dealing with Unicode is derived from
6772 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6773 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6777 unicode_width(unsigned long c
)
6780 (c
<= 0x115f /* Hangul Jamo */
6783 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
6785 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
6786 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
6787 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
6788 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
6789 || (c
>= 0xffe0 && c
<= 0xffe6)
6790 || (c
>= 0x20000 && c
<= 0x2fffd)
6791 || (c
>= 0x30000 && c
<= 0x3fffd)))
6795 return opt_tab_size
;
6800 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6801 * Illegal bytes are set one. */
6802 static const unsigned char utf8_bytes
[256] = {
6803 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,
6804 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,
6805 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,
6806 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,
6807 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,
6808 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,
6809 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,
6810 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,
6813 static inline unsigned char
6814 utf8_char_length(const char *string
, const char *end
)
6816 int c
= *(unsigned char *) string
;
6818 return utf8_bytes
[c
];
6821 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6822 static inline unsigned long
6823 utf8_to_unicode(const char *string
, size_t length
)
6825 unsigned long unicode
;
6829 unicode
= string
[0];
6832 unicode
= (string
[0] & 0x1f) << 6;
6833 unicode
+= (string
[1] & 0x3f);
6836 unicode
= (string
[0] & 0x0f) << 12;
6837 unicode
+= ((string
[1] & 0x3f) << 6);
6838 unicode
+= (string
[2] & 0x3f);
6841 unicode
= (string
[0] & 0x0f) << 18;
6842 unicode
+= ((string
[1] & 0x3f) << 12);
6843 unicode
+= ((string
[2] & 0x3f) << 6);
6844 unicode
+= (string
[3] & 0x3f);
6847 unicode
= (string
[0] & 0x0f) << 24;
6848 unicode
+= ((string
[1] & 0x3f) << 18);
6849 unicode
+= ((string
[2] & 0x3f) << 12);
6850 unicode
+= ((string
[3] & 0x3f) << 6);
6851 unicode
+= (string
[4] & 0x3f);
6854 unicode
= (string
[0] & 0x01) << 30;
6855 unicode
+= ((string
[1] & 0x3f) << 24);
6856 unicode
+= ((string
[2] & 0x3f) << 18);
6857 unicode
+= ((string
[3] & 0x3f) << 12);
6858 unicode
+= ((string
[4] & 0x3f) << 6);
6859 unicode
+= (string
[5] & 0x3f);
6862 die("Invalid Unicode length");
6865 /* Invalid characters could return the special 0xfffd value but NUL
6866 * should be just as good. */
6867 return unicode
> 0xffff ? 0 : unicode
;
6870 /* Calculates how much of string can be shown within the given maximum width
6871 * and sets trimmed parameter to non-zero value if all of string could not be
6872 * shown. If the reserve flag is TRUE, it will reserve at least one
6873 * trailing character, which can be useful when drawing a delimiter.
6875 * Returns the number of bytes to output from string to satisfy max_width. */
6877 utf8_length(const char **start
, size_t skip
, int *width
, size_t max_width
, int *trimmed
, bool reserve
)
6879 const char *string
= *start
;
6880 const char *end
= strchr(string
, '\0');
6881 unsigned char last_bytes
= 0;
6882 size_t last_ucwidth
= 0;
6887 while (string
< end
) {
6888 unsigned char bytes
= utf8_char_length(string
, end
);
6890 unsigned long unicode
;
6892 if (string
+ bytes
> end
)
6895 /* Change representation to figure out whether
6896 * it is a single- or double-width character. */
6898 unicode
= utf8_to_unicode(string
, bytes
);
6899 /* FIXME: Graceful handling of invalid Unicode character. */
6903 ucwidth
= unicode_width(unicode
);
6905 skip
-= ucwidth
<= skip
? ucwidth
: skip
;
6909 if (*width
> max_width
) {
6912 if (reserve
&& *width
== max_width
) {
6913 string
-= last_bytes
;
6914 *width
-= last_ucwidth
;
6920 last_bytes
= ucwidth
? bytes
: 0;
6921 last_ucwidth
= ucwidth
;
6924 return string
- *start
;
6932 /* Whether or not the curses interface has been initialized. */
6933 static bool cursed
= FALSE
;
6935 /* Terminal hacks and workarounds. */
6936 static bool use_scroll_redrawwin
;
6937 static bool use_scroll_status_wclear
;
6939 /* The status window is used for polling keystrokes. */
6940 static WINDOW
*status_win
;
6942 /* Reading from the prompt? */
6943 static bool input_mode
= FALSE
;
6945 static bool status_empty
= FALSE
;
6947 /* Update status and title window. */
6949 report(const char *msg
, ...)
6951 struct view
*view
= display
[current_view
];
6957 char buf
[SIZEOF_STR
];
6960 va_start(args
, msg
);
6961 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6962 buf
[sizeof(buf
) - 1] = 0;
6963 buf
[sizeof(buf
) - 2] = '.';
6964 buf
[sizeof(buf
) - 3] = '.';
6965 buf
[sizeof(buf
) - 4] = '.';
6971 if (!status_empty
|| *msg
) {
6974 va_start(args
, msg
);
6976 wmove(status_win
, 0, 0);
6977 if (view
->has_scrolled
&& use_scroll_status_wclear
)
6980 vwprintw(status_win
, msg
, args
);
6981 status_empty
= FALSE
;
6983 status_empty
= TRUE
;
6985 wclrtoeol(status_win
);
6986 wnoutrefresh(status_win
);
6991 update_view_title(view
);
6994 /* Controls when nodelay should be in effect when polling user input. */
6996 set_nonblocking_input(bool loading
)
6998 static unsigned int loading_views
;
7000 if ((loading
== FALSE
&& loading_views
-- == 1) ||
7001 (loading
== TRUE
&& loading_views
++ == 0))
7002 nodelay(status_win
, loading
);
7011 /* Initialize the curses library */
7012 if (isatty(STDIN_FILENO
)) {
7013 cursed
= !!initscr();
7016 /* Leave stdin and stdout alone when acting as a pager. */
7017 opt_tty
= fopen("/dev/tty", "r+");
7019 die("Failed to open /dev/tty");
7020 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
7024 die("Failed to initialize curses");
7026 nonl(); /* Disable conversion and detect newlines from input. */
7027 cbreak(); /* Take input chars one at a time, no wait for \n */
7028 noecho(); /* Don't echo input */
7029 leaveok(stdscr
, FALSE
);
7034 getmaxyx(stdscr
, y
, x
);
7035 status_win
= newwin(1, 0, y
- 1, 0);
7037 die("Failed to create status window");
7039 /* Enable keyboard mapping */
7040 keypad(status_win
, TRUE
);
7041 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
7043 TABSIZE
= opt_tab_size
;
7044 if (opt_line_graphics
) {
7045 line_graphics
[LINE_GRAPHIC_VLINE
] = ACS_VLINE
;
7048 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
7049 if (term
&& !strcmp(term
, "gnome-terminal")) {
7050 /* In the gnome-terminal-emulator, the message from
7051 * scrolling up one line when impossible followed by
7052 * scrolling down one line causes corruption of the
7053 * status line. This is fixed by calling wclear. */
7054 use_scroll_status_wclear
= TRUE
;
7055 use_scroll_redrawwin
= FALSE
;
7057 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
7058 /* No problems with full optimizations in xrvt-(unicode)
7060 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
7063 /* When scrolling in (u)xterm the last line in the
7064 * scrolling direction will update slowly. */
7065 use_scroll_redrawwin
= TRUE
;
7066 use_scroll_status_wclear
= FALSE
;
7071 get_input(int prompt_position
)
7074 int i
, key
, cursor_y
, cursor_x
;
7076 if (prompt_position
)
7080 foreach_view (view
, i
) {
7082 if (view_is_displayed(view
) && view
->has_scrolled
&&
7083 use_scroll_redrawwin
)
7084 redrawwin(view
->win
);
7085 view
->has_scrolled
= FALSE
;
7088 /* Update the cursor position. */
7089 if (prompt_position
) {
7090 getbegyx(status_win
, cursor_y
, cursor_x
);
7091 cursor_x
= prompt_position
;
7093 view
= display
[current_view
];
7094 getbegyx(view
->win
, cursor_y
, cursor_x
);
7095 cursor_x
= view
->width
- 1;
7096 cursor_y
+= view
->lineno
- view
->offset
;
7098 setsyx(cursor_y
, cursor_x
);
7100 /* Refresh, accept single keystroke of input */
7102 key
= wgetch(status_win
);
7104 /* wgetch() with nodelay() enabled returns ERR when
7105 * there's no input. */
7108 } else if (key
== KEY_RESIZE
) {
7111 getmaxyx(stdscr
, height
, width
);
7113 wresize(status_win
, 1, width
);
7114 mvwin(status_win
, height
- 1, 0);
7115 wnoutrefresh(status_win
);
7117 redraw_display(TRUE
);
7127 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7129 enum input_status status
= INPUT_OK
;
7130 static char buf
[SIZEOF_STR
];
7135 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7138 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7139 wclrtoeol(status_win
);
7141 key
= get_input(pos
+ 1);
7146 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7153 status
= INPUT_CANCEL
;
7157 status
= INPUT_CANCEL
;
7161 if (pos
>= sizeof(buf
)) {
7162 report("Input string too long");
7166 status
= handler(data
, buf
, key
);
7167 if (status
== INPUT_OK
)
7168 buf
[pos
++] = (char) key
;
7172 /* Clear the status window */
7173 status_empty
= FALSE
;
7176 if (status
== INPUT_CANCEL
)
7184 static enum input_status
7185 prompt_yesno_handler(void *data
, char *buf
, int c
)
7187 if (c
== 'y' || c
== 'Y')
7189 if (c
== 'n' || c
== 'N')
7190 return INPUT_CANCEL
;
7195 prompt_yesno(const char *prompt
)
7197 char prompt2
[SIZEOF_STR
];
7199 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7202 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7205 static enum input_status
7206 read_prompt_handler(void *data
, char *buf
, int c
)
7208 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7212 read_prompt(const char *prompt
)
7214 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7217 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7219 enum input_status status
= INPUT_OK
;
7222 while (items
[size
].text
)
7225 while (status
== INPUT_OK
) {
7226 const struct menu_item
*item
= &items
[*selected
];
7230 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7231 prompt
, *selected
+ 1, size
);
7233 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7234 wprintw(status_win
, "%s", item
->text
);
7235 wclrtoeol(status_win
);
7237 key
= get_input(COLS
- 1);
7242 status
= INPUT_STOP
;
7247 *selected
= *selected
- 1;
7249 *selected
= size
- 1;
7254 *selected
= (*selected
+ 1) % size
;
7258 status
= INPUT_CANCEL
;
7262 for (i
= 0; items
[i
].text
; i
++)
7263 if (items
[i
].hotkey
== key
) {
7265 status
= INPUT_STOP
;
7271 /* Clear the status window */
7272 status_empty
= FALSE
;
7275 return status
!= INPUT_CANCEL
;
7279 * Repository properties
7282 static struct ref
**refs
= NULL
;
7283 static size_t refs_size
= 0;
7285 static struct ref_list
**ref_lists
= NULL
;
7286 static size_t ref_lists_size
= 0;
7288 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7289 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7290 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7293 compare_refs(const void *ref1_
, const void *ref2_
)
7295 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7296 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7298 if (ref1
->tag
!= ref2
->tag
)
7299 return ref2
->tag
- ref1
->tag
;
7300 if (ref1
->ltag
!= ref2
->ltag
)
7301 return ref2
->ltag
- ref2
->ltag
;
7302 if (ref1
->head
!= ref2
->head
)
7303 return ref2
->head
- ref1
->head
;
7304 if (ref1
->tracked
!= ref2
->tracked
)
7305 return ref2
->tracked
- ref1
->tracked
;
7306 if (ref1
->remote
!= ref2
->remote
)
7307 return ref2
->remote
- ref1
->remote
;
7308 return strcmp(ref1
->name
, ref2
->name
);
7312 foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
)
7316 for (i
= 0; i
< refs_size
; i
++)
7317 if (!visitor(data
, refs
[i
]))
7321 static struct ref_list
*
7322 get_ref_list(const char *id
)
7324 struct ref_list
*list
;
7327 for (i
= 0; i
< ref_lists_size
; i
++)
7328 if (!strcmp(id
, ref_lists
[i
]->id
))
7329 return ref_lists
[i
];
7331 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7333 list
= calloc(1, sizeof(*list
));
7337 for (i
= 0; i
< refs_size
; i
++) {
7338 if (!strcmp(id
, refs
[i
]->id
) &&
7339 realloc_refs_list(&list
->refs
, list
->size
, 1))
7340 list
->refs
[list
->size
++] = refs
[i
];
7348 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7349 ref_lists
[ref_lists_size
++] = list
;
7354 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
7356 struct ref
*ref
= NULL
;
7359 bool remote
= FALSE
;
7360 bool tracked
= FALSE
;
7362 int from
= 0, to
= refs_size
- 1;
7364 if (!prefixcmp(name
, "refs/tags/")) {
7365 if (!suffixcmp(name
, namelen
, "^{}")) {
7373 namelen
-= STRING_SIZE("refs/tags/");
7374 name
+= STRING_SIZE("refs/tags/");
7376 } else if (!prefixcmp(name
, "refs/remotes/")) {
7378 namelen
-= STRING_SIZE("refs/remotes/");
7379 name
+= STRING_SIZE("refs/remotes/");
7380 tracked
= !strcmp(opt_remote
, name
);
7382 } else if (!prefixcmp(name
, "refs/heads/")) {
7383 namelen
-= STRING_SIZE("refs/heads/");
7384 name
+= STRING_SIZE("refs/heads/");
7385 head
= !strncmp(opt_head
, name
, namelen
);
7387 } else if (!strcmp(name
, "HEAD")) {
7388 string_ncopy(opt_head_rev
, id
, idlen
);
7392 /* If we are reloading or it's an annotated tag, replace the
7393 * previous SHA1 with the resolved commit id; relies on the fact
7394 * git-ls-remote lists the commit id of an annotated tag right
7395 * before the commit id it points to. */
7396 while (from
<= to
) {
7397 size_t pos
= (to
+ from
) / 2;
7398 int cmp
= strcmp(name
, refs
[pos
]->name
);
7412 if (!realloc_refs(&refs
, refs_size
, 1))
7414 ref
= calloc(1, sizeof(*ref
) + namelen
);
7417 memmove(refs
+ from
+ 1, refs
+ from
,
7418 (refs_size
- from
) * sizeof(*refs
));
7420 strncpy(ref
->name
, name
, namelen
);
7427 ref
->remote
= remote
;
7428 ref
->tracked
= tracked
;
7429 string_copy_rev(ref
->id
, id
);
7437 const char *head_argv
[] = {
7438 "git", "symbolic-ref", "HEAD", NULL
7440 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7441 "git", "ls-remote", opt_git_dir
, NULL
7443 static bool init
= FALSE
;
7447 argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE");
7454 if (run_io_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7455 !prefixcmp(opt_head
, "refs/heads/")) {
7456 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7458 memmove(opt_head
, offset
, strlen(offset
) + 1);
7461 for (i
= 0; i
< refs_size
; i
++)
7464 if (run_io_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7467 /* Update the ref lists to reflect changes. */
7468 for (i
= 0; i
< ref_lists_size
; i
++) {
7469 struct ref_list
*list
= ref_lists
[i
];
7472 for (old
= new = 0; old
< list
->size
; old
++)
7473 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7474 list
->refs
[new++] = list
->refs
[old
];
7482 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7484 if (!strcmp(name
, ".remote")) {
7485 string_ncopy(opt_remote
, value
, valuelen
);
7487 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7488 size_t from
= strlen(opt_remote
);
7490 if (!prefixcmp(value
, "refs/heads/"))
7491 value
+= STRING_SIZE("refs/heads/");
7493 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7499 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7501 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7502 int argc
= 1 + (cmd
== option_set_command
);
7505 if (!argv_from_string(argv
, &argc
, value
))
7506 config_msg
= "Too many option arguments";
7508 error
= cmd(argc
, argv
);
7511 warn("Option 'tig.%s': %s", name
, config_msg
);
7515 set_environment_variable(const char *name
, const char *value
)
7517 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7518 char *env
= malloc(len
);
7521 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7529 set_work_tree(const char *value
)
7531 char cwd
[SIZEOF_STR
];
7533 if (!getcwd(cwd
, sizeof(cwd
)))
7534 die("Failed to get cwd path: %s", strerror(errno
));
7535 if (chdir(opt_git_dir
) < 0)
7536 die("Failed to chdir(%s): %s", strerror(errno
));
7537 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7538 die("Failed to get git path: %s", strerror(errno
));
7540 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7541 if (chdir(value
) < 0)
7542 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7543 if (!getcwd(cwd
, sizeof(cwd
)))
7544 die("Failed to get cwd path: %s", strerror(errno
));
7545 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7546 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7547 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7548 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7549 opt_is_inside_work_tree
= TRUE
;
7553 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7555 if (!strcmp(name
, "i18n.commitencoding"))
7556 string_ncopy(opt_encoding
, value
, valuelen
);
7558 else if (!strcmp(name
, "core.editor"))
7559 string_ncopy(opt_editor
, value
, valuelen
);
7561 else if (!strcmp(name
, "core.worktree"))
7562 set_work_tree(value
);
7564 else if (!prefixcmp(name
, "tig.color."))
7565 set_repo_config_option(name
+ 10, value
, option_color_command
);
7567 else if (!prefixcmp(name
, "tig.bind."))
7568 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7570 else if (!prefixcmp(name
, "tig."))
7571 set_repo_config_option(name
+ 4, value
, option_set_command
);
7573 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7574 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7575 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7581 load_git_config(void)
7583 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7585 return run_io_load(config_list_argv
, "=", read_repo_config_option
);
7589 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7591 if (!opt_git_dir
[0]) {
7592 string_ncopy(opt_git_dir
, name
, namelen
);
7594 } else if (opt_is_inside_work_tree
== -1) {
7595 /* This can be 3 different values depending on the
7596 * version of git being used. If git-rev-parse does not
7597 * understand --is-inside-work-tree it will simply echo
7598 * the option else either "true" or "false" is printed.
7599 * Default to true for the unknown case. */
7600 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7602 } else if (*name
== '.') {
7603 string_ncopy(opt_cdup
, name
, namelen
);
7606 string_ncopy(opt_prefix
, name
, namelen
);
7613 load_repo_info(void)
7615 const char *rev_parse_argv
[] = {
7616 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7617 "--show-cdup", "--show-prefix", NULL
7620 return run_io_load(rev_parse_argv
, "=", read_repo_info
);
7628 static const char usage
[] =
7629 "tig " TIG_VERSION
" (" __DATE__
")\n"
7631 "Usage: tig [options] [revs] [--] [paths]\n"
7632 " or: tig show [options] [revs] [--] [paths]\n"
7633 " or: tig blame [rev] path\n"
7635 " or: tig < [git command output]\n"
7638 " -v, --version Show version and exit\n"
7639 " -h, --help Show help message and exit";
7641 static void __NORETURN
7644 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7650 static void __NORETURN
7651 die(const char *err
, ...)
7657 va_start(args
, err
);
7658 fputs("tig: ", stderr
);
7659 vfprintf(stderr
, err
, args
);
7660 fputs("\n", stderr
);
7667 warn(const char *msg
, ...)
7671 va_start(args
, msg
);
7672 fputs("tig warning: ", stderr
);
7673 vfprintf(stderr
, msg
, args
);
7674 fputs("\n", stderr
);
7679 parse_options(int argc
, const char *argv
[])
7681 enum request request
= REQ_VIEW_MAIN
;
7682 const char *subcommand
;
7683 bool seen_dashdash
= FALSE
;
7684 /* XXX: This is vulnerable to the user overriding options
7685 * required for the main view parser. */
7686 const char *custom_argv
[SIZEOF_ARG
] = {
7687 "git", "log", "--no-color", "--pretty=raw", "--parents",
7688 "--topo-order", NULL
7692 if (!isatty(STDIN_FILENO
)) {
7693 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7694 return REQ_VIEW_PAGER
;
7700 subcommand
= argv
[1];
7701 if (!strcmp(subcommand
, "status")) {
7703 warn("ignoring arguments after `%s'", subcommand
);
7704 return REQ_VIEW_STATUS
;
7706 } else if (!strcmp(subcommand
, "blame")) {
7707 if (argc
<= 2 || argc
> 4)
7708 die("invalid number of options to blame\n\n%s", usage
);
7712 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7716 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7717 return REQ_VIEW_BLAME
;
7719 } else if (!strcmp(subcommand
, "show")) {
7720 request
= REQ_VIEW_DIFF
;
7727 custom_argv
[1] = subcommand
;
7731 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7732 const char *opt
= argv
[i
];
7734 if (seen_dashdash
|| !strcmp(opt
, "--")) {
7735 seen_dashdash
= TRUE
;
7737 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7738 printf("tig version %s\n", TIG_VERSION
);
7741 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7742 printf("%s\n", usage
);
7746 custom_argv
[j
++] = opt
;
7747 if (j
>= ARRAY_SIZE(custom_argv
))
7748 die("command too long");
7751 if (!prepare_update(VIEW(request
), custom_argv
, NULL
, FORMAT_NONE
))
7752 die("Failed to format arguments");
7758 main(int argc
, const char *argv
[])
7760 enum request request
= parse_options(argc
, argv
);
7764 signal(SIGINT
, quit
);
7765 signal(SIGPIPE
, SIG_IGN
);
7767 if (setlocale(LC_ALL
, "")) {
7768 char *codeset
= nl_langinfo(CODESET
);
7770 string_ncopy(opt_codeset
, codeset
, strlen(codeset
));
7773 if (load_repo_info() == ERR
)
7774 die("Failed to load repo info.");
7776 if (load_options() == ERR
)
7777 die("Failed to load user config.");
7779 if (load_git_config() == ERR
)
7780 die("Failed to load repo config.");
7782 /* Require a git repository unless when running in pager mode. */
7783 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7784 die("Not a git repository");
7786 if (*opt_encoding
&& strcmp(opt_codeset
, "UTF-8")) {
7787 opt_iconv_in
= iconv_open("UTF-8", opt_encoding
);
7788 if (opt_iconv_in
== ICONV_NONE
)
7789 die("Failed to initialize character set conversion");
7792 if (*opt_codeset
&& strcmp(opt_codeset
, "UTF-8")) {
7793 opt_iconv_out
= iconv_open(opt_codeset
, "UTF-8");
7794 if (opt_iconv_out
== ICONV_NONE
)
7795 die("Failed to initialize character set conversion");
7798 if (load_refs() == ERR
)
7799 die("Failed to load refs.");
7801 foreach_view (view
, i
)
7802 argv_from_env(view
->ops
->argv
, view
->cmd_env
);
7806 if (request
!= REQ_NONE
)
7807 open_view(NULL
, request
, OPEN_PREPARED
);
7808 request
= request
== REQ_NONE
? REQ_VIEW_MAIN
: REQ_NONE
;
7810 while (view_driver(display
[current_view
], request
)) {
7811 int key
= get_input(0);
7813 view
= display
[current_view
];
7814 request
= get_keybinding(view
->keymap
, key
);
7816 /* Some low-level request handling. This keeps access to
7817 * status_win restricted. */
7821 char *cmd
= read_prompt(":");
7823 if (cmd
&& isdigit(*cmd
)) {
7824 int lineno
= view
->lineno
+ 1;
7826 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7827 select_view_line(view
, lineno
- 1);
7830 report("Unable to parse '%s' as a line number", cmd
);
7834 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7835 const char *argv
[SIZEOF_ARG
] = { "git" };
7838 /* When running random commands, initially show the
7839 * command in the title. However, it maybe later be
7840 * overwritten if a commit line is selected. */
7841 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7843 if (!argv_from_string(argv
, &argc
, cmd
)) {
7844 report("Too many arguments");
7845 } else if (!prepare_update(next
, argv
, NULL
, FORMAT_DASH
)) {
7846 report("Failed to format command");
7848 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7856 case REQ_SEARCH_BACK
:
7858 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7859 char *search
= read_prompt(prompt
);
7862 string_ncopy(opt_search
, search
, strlen(search
));
7863 else if (*opt_search
)
7864 request
= request
== REQ_SEARCH
?