1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
37 #include <sys/select.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
63 #define __NORETURN __attribute__((__noreturn__))
68 static void __NORETURN
die(const char *err
, ...);
69 static void warn(const char *msg
, ...);
70 static void report(const char *msg
, ...);
71 static void set_nonblocking_input(bool loading
);
72 static size_t utf8_length(const char **string
, size_t col
, int *width
, size_t max_width
, int *trimmed
, bool reserve
);
73 static inline unsigned char utf8_char_length(const char *string
, const char *end
);
75 #define ABS(x) ((x) >= 0 ? (x) : -(x))
76 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define MAX(x, y) ((x) > (y) ? (x) : (y))
79 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
80 #define STRING_SIZE(x) (sizeof(x) - 1)
82 #define SIZEOF_STR 1024 /* Default string size. */
83 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
84 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
85 #define SIZEOF_ARG 32 /* Default argument array size. */
89 #define REVGRAPH_INIT 'I'
90 #define REVGRAPH_MERGE 'M'
91 #define REVGRAPH_BRANCH '+'
92 #define REVGRAPH_COMMIT '*'
93 #define REVGRAPH_BOUND '^'
95 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
97 /* This color name can be used to refer to the default term colors. */
98 #define COLOR_DEFAULT (-1)
100 #define ICONV_NONE ((iconv_t) -1)
102 #define ICONV_CONST /* nothing */
105 /* The format and size of the date column in the main view. */
106 #define DATE_FORMAT "%Y-%m-%d %H:%M"
107 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
108 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
111 #define AUTHOR_COLS 19
113 #define MIN_VIEW_HEIGHT 4
115 #define NULL_ID "0000000000000000000000000000000000000000"
117 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
119 /* Some ASCII-shorthands fitted into the ncurses namespace. */
121 #define KEY_RETURN '\r'
126 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
127 unsigned int head
:1; /* Is it the current HEAD? */
128 unsigned int tag
:1; /* Is it a tag? */
129 unsigned int ltag
:1; /* If so, is the tag local? */
130 unsigned int remote
:1; /* Is it a remote ref? */
131 unsigned int tracked
:1; /* Is it the remote for the current HEAD? */
132 char name
[1]; /* Ref name; tag or head names are shortened. */
136 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
137 size_t size
; /* Number of refs. */
138 struct ref
**refs
; /* References for this ID. */
141 static struct ref_list
*get_ref_list(const char *id
);
142 static void foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
);
143 static int load_refs(void);
146 FORMAT_ALL
, /* Perform replacement in all arguments. */
147 FORMAT_DASH
, /* Perform replacement up until "--". */
148 FORMAT_NONE
/* No replacement should be performed. */
151 static bool format_argv(const char *dst
[], const char *src
[], enum format_flags flags
);
160 typedef enum input_status (*input_handler
)(void *data
, char *buf
, int c
);
162 static char *prompt_input(const char *prompt
, input_handler handler
, void *data
);
163 static bool prompt_yesno(const char *prompt
);
171 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
);
174 * Allocation helpers ... Entering macro hell to never be seen again.
177 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
179 name(type **mem, size_t size, size_t increase) \
181 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
182 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
185 if (mem == NULL || num_chunks != num_chunks_new) { \
186 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
199 string_ncopy_do(char *dst
, size_t dstlen
, const char *src
, size_t srclen
)
201 if (srclen
> dstlen
- 1)
204 strncpy(dst
, src
, srclen
);
208 /* Shorthands for safely copying into a fixed buffer. */
210 #define string_copy(dst, src) \
211 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
213 #define string_ncopy(dst, src, srclen) \
214 string_ncopy_do(dst, sizeof(dst), src, srclen)
216 #define string_copy_rev(dst, src) \
217 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
219 #define string_add(dst, from, src) \
220 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
223 string_expand(char *dst
, size_t dstlen
, const char *src
, int tabsize
)
227 for (size
= pos
= 0; size
< dstlen
- 1 && src
[pos
]; pos
++) {
228 if (src
[pos
] == '\t') {
229 size_t expanded
= tabsize
- (size
% tabsize
);
231 if (expanded
+ size
>= dstlen
- 1)
232 expanded
= dstlen
- size
- 1;
233 memcpy(dst
+ size
, " ", expanded
);
236 dst
[size
++] = src
[pos
];
244 chomp_string(char *name
)
248 while (isspace(*name
))
251 namelen
= strlen(name
) - 1;
252 while (namelen
> 0 && isspace(name
[namelen
]))
259 string_nformat(char *buf
, size_t bufsize
, size_t *bufpos
, const char *fmt
, ...)
262 size_t pos
= bufpos
? *bufpos
: 0;
265 pos
+= vsnprintf(buf
+ pos
, bufsize
- pos
, fmt
, args
);
271 return pos
>= bufsize
? FALSE
: TRUE
;
274 #define string_format(buf, fmt, args...) \
275 string_nformat(buf, sizeof(buf), NULL, fmt, args)
277 #define string_format_from(buf, from, fmt, args...) \
278 string_nformat(buf, sizeof(buf), from, fmt, args)
281 string_enum_compare(const char *str1
, const char *str2
, int len
)
285 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
287 /* Diff-Header == DIFF_HEADER */
288 for (i
= 0; i
< len
; i
++) {
289 if (toupper(str1
[i
]) == toupper(str2
[i
]))
292 if (string_enum_sep(str1
[i
]) &&
293 string_enum_sep(str2
[i
]))
296 return str1
[i
] - str2
[i
];
302 #define enum_equals(entry, str, len) \
303 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
311 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
314 enum_map_name(const char *name
, size_t namelen
)
316 static char buf
[SIZEOF_STR
];
319 for (bufpos
= 0; bufpos
<= namelen
; bufpos
++) {
320 buf
[bufpos
] = tolower(name
[bufpos
]);
321 if (buf
[bufpos
] == '_')
329 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
332 map_enum_do(const struct enum_map
*map
, size_t map_size
, int *value
, const char *name
)
334 size_t namelen
= strlen(name
);
337 for (i
= 0; i
< map_size
; i
++)
338 if (enum_equals(map
[i
], name
, namelen
)) {
339 *value
= map
[i
].value
;
346 #define map_enum(attr, map, name) \
347 map_enum_do(map, ARRAY_SIZE(map), attr, name)
349 #define prefixcmp(str1, str2) \
350 strncmp(str1, str2, STRING_SIZE(str2))
353 suffixcmp(const char *str
, int slen
, const char *suffix
)
355 size_t len
= slen
>= 0 ? slen
: strlen(str
);
356 size_t suffixlen
= strlen(suffix
);
358 return suffixlen
< len
? strcmp(str
+ len
- suffixlen
, suffix
) : -1;
369 #define DATE_(name) DATE_##name
374 static const struct enum_map date_map
[] = {
375 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
385 static inline int timecmp(const struct time
*t1
, const struct time
*t2
)
387 return t1
->sec
- t2
->sec
;
391 string_date(const struct time
*time
, enum date date
)
393 static char buf
[DATE_COLS
+ 1];
394 static const struct enum_map reldate
[] = {
395 { "second", 1, 60 * 2 },
396 { "minute", 60, 60 * 60 * 2 },
397 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
398 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
399 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
400 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
404 if (date
== DATE_RELATIVE
) {
406 time_t date
= time
->sec
+ time
->tz
;
410 gettimeofday(&now
, NULL
);
411 seconds
= now
.tv_sec
< date
? date
- now
.tv_sec
: now
.tv_sec
- date
;
412 for (i
= 0; i
< ARRAY_SIZE(reldate
); i
++) {
413 if (seconds
>= reldate
[i
].value
)
416 seconds
/= reldate
[i
].namelen
;
417 if (!string_format(buf
, "%ld %s%s %s",
418 seconds
, reldate
[i
].name
,
419 seconds
> 1 ? "s" : "",
420 now
.tv_sec
>= date
? "ago" : "ahead"))
426 gmtime_r(&time
->sec
, &tm
);
427 return strftime(buf
, sizeof(buf
), DATE_FORMAT
, &tm
) ? buf
: NULL
;
431 #define AUTHOR_VALUES \
437 #define AUTHOR_(name) AUTHOR_##name
440 AUTHOR_DEFAULT
= AUTHOR_FULL
443 static const struct enum_map author_map
[] = {
444 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
450 get_author_initials(const char *author
)
452 static char initials
[AUTHOR_COLS
* 6 + 1];
454 const char *end
= strchr(author
, '\0');
456 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
458 memset(initials
, 0, sizeof(initials
));
459 while (author
< end
) {
463 while (is_initial_sep(*author
))
466 bytes
= utf8_char_length(author
, end
);
467 if (bytes
< sizeof(initials
) - 1 - pos
) {
469 initials
[pos
++] = *author
++;
473 for (i
= pos
; author
< end
&& !is_initial_sep(*author
); author
++) {
474 if (i
< sizeof(initials
) - 1)
475 initials
[i
++] = *author
;
486 argv_from_string(const char *argv
[SIZEOF_ARG
], int *argc
, char *cmd
)
490 while (*cmd
&& *argc
< SIZEOF_ARG
&& (valuelen
= strcspn(cmd
, " \t"))) {
491 bool advance
= cmd
[valuelen
] != 0;
494 argv
[(*argc
)++] = chomp_string(cmd
);
495 cmd
= chomp_string(cmd
+ valuelen
+ advance
);
498 if (*argc
< SIZEOF_ARG
)
500 return *argc
< SIZEOF_ARG
;
504 argv_from_env(const char **argv
, const char *name
)
506 char *env
= argv
? getenv(name
) : NULL
;
511 if (env
&& !argv_from_string(argv
, &argc
, env
))
512 die("Too many arguments in the `%s` environment variable", name
);
517 * Executing external commands.
521 IO_FD
, /* File descriptor based IO. */
522 IO_BG
, /* Execute command in the background. */
523 IO_FG
, /* Execute command with same std{in,out,err}. */
524 IO_RD
, /* Read only fork+exec IO. */
525 IO_WR
, /* Write only fork+exec IO. */
526 IO_AP
, /* Append fork+exec output to file. */
530 enum io_type type
; /* The requested type of pipe. */
531 const char *dir
; /* Directory from which to execute. */
532 pid_t pid
; /* Pipe for reading or writing. */
533 int pipe
; /* Pipe end for reading or writing. */
534 int error
; /* Error status. */
535 const char *argv
[SIZEOF_ARG
]; /* Shell command arguments. */
536 char *buf
; /* Read buffer. */
537 size_t bufalloc
; /* Allocated buffer size. */
538 size_t bufsize
; /* Buffer content size. */
539 char *bufpos
; /* Current buffer position. */
540 unsigned int eof
:1; /* Has end of file been reached. */
544 reset_io(struct io
*io
)
548 io
->buf
= io
->bufpos
= NULL
;
549 io
->bufalloc
= io
->bufsize
= 0;
555 init_io(struct io
*io
, const char *dir
, enum io_type type
)
563 init_io_rd(struct io
*io
, const char *argv
[], const char *dir
,
564 enum format_flags flags
)
566 init_io(io
, dir
, IO_RD
);
567 return format_argv(io
->argv
, argv
, flags
);
571 io_open(struct io
*io
, const char *fmt
, ...)
573 char name
[SIZEOF_STR
] = "";
577 init_io(io
, NULL
, IO_FD
);
580 fits
= vsnprintf(name
, sizeof(name
), fmt
, args
) < sizeof(name
);
584 io
->error
= ENAMETOOLONG
;
587 io
->pipe
= *name
? open(name
, O_RDONLY
) : STDIN_FILENO
;
590 return io
->pipe
!= -1;
594 kill_io(struct io
*io
)
596 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
600 done_io(struct io
*io
)
611 pid_t waiting
= waitpid(pid
, &status
, 0);
616 report("waitpid failed (%s)", strerror(errno
));
620 return waiting
== pid
&&
621 !WIFSIGNALED(status
) &&
623 !WEXITSTATUS(status
);
630 start_io(struct io
*io
)
632 int pipefds
[2] = { -1, -1 };
634 if (io
->type
== IO_FD
)
637 if ((io
->type
== IO_RD
|| io
->type
== IO_WR
) &&
640 else if (io
->type
== IO_AP
)
641 pipefds
[1] = io
->pipe
;
643 if ((io
->pid
= fork())) {
644 if (pipefds
[!(io
->type
== IO_WR
)] != -1)
645 close(pipefds
[!(io
->type
== IO_WR
)]);
647 io
->pipe
= pipefds
[!!(io
->type
== IO_WR
)];
652 if (io
->type
!= IO_FG
) {
653 int devnull
= open("/dev/null", O_RDWR
);
654 int readfd
= io
->type
== IO_WR
? pipefds
[0] : devnull
;
655 int writefd
= (io
->type
== IO_RD
|| io
->type
== IO_AP
)
656 ? pipefds
[1] : devnull
;
658 dup2(readfd
, STDIN_FILENO
);
659 dup2(writefd
, STDOUT_FILENO
);
660 dup2(devnull
, STDERR_FILENO
);
663 if (pipefds
[0] != -1)
665 if (pipefds
[1] != -1)
669 if (io
->dir
&& *io
->dir
&& chdir(io
->dir
) == -1)
670 die("Failed to change directory: %s", strerror(errno
));
672 execvp(io
->argv
[0], (char *const*) io
->argv
);
673 die("Failed to execute program: %s", strerror(errno
));
676 if (pipefds
[!!(io
->type
== IO_WR
)] != -1)
677 close(pipefds
[!!(io
->type
== IO_WR
)]);
682 run_io(struct io
*io
, const char **argv
, const char *dir
, enum io_type type
)
684 init_io(io
, dir
, type
);
685 if (!format_argv(io
->argv
, argv
, FORMAT_NONE
))
691 run_io_do(struct io
*io
)
693 return start_io(io
) && done_io(io
);
697 run_io_bg(const char **argv
)
701 init_io(&io
, NULL
, IO_BG
);
702 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
704 return run_io_do(&io
);
708 run_io_fg(const char **argv
, const char *dir
)
712 init_io(&io
, dir
, IO_FG
);
713 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
715 return run_io_do(&io
);
719 run_io_append(const char **argv
, enum format_flags flags
, int fd
)
723 init_io(&io
, NULL
, IO_AP
);
725 if (format_argv(io
.argv
, argv
, flags
))
726 return run_io_do(&io
);
732 run_io_rd(struct io
*io
, const char **argv
, const char *dir
, enum format_flags flags
)
734 return init_io_rd(io
, argv
, dir
, flags
) && start_io(io
);
738 io_eof(struct io
*io
)
744 io_error(struct io
*io
)
750 io_strerror(struct io
*io
)
752 return strerror(io
->error
);
756 io_can_read(struct io
*io
)
758 struct timeval tv
= { 0, 500 };
762 FD_SET(io
->pipe
, &fds
);
764 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
768 io_read(struct io
*io
, void *buf
, size_t bufsize
)
771 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
773 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
775 else if (readsize
== -1)
777 else if (readsize
== 0)
783 DEFINE_ALLOCATOR(realloc_io_buf
, char, BUFSIZ
)
786 io_get(struct io
*io
, int c
, bool can_read
)
792 if (io
->bufsize
> 0) {
793 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
795 char *line
= io
->bufpos
;
798 io
->bufpos
= eol
+ 1;
799 io
->bufsize
-= io
->bufpos
- line
;
806 io
->bufpos
[io
->bufsize
] = 0;
816 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
817 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
819 if (io
->bufalloc
== io
->bufsize
) {
820 if (!realloc_io_buf(&io
->buf
, io
->bufalloc
, BUFSIZ
))
822 io
->bufalloc
+= BUFSIZ
;
825 io
->bufpos
= io
->buf
;
826 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
829 io
->bufsize
+= readsize
;
834 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
838 while (!io_error(io
) && written
< bufsize
) {
841 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
842 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
850 return written
== bufsize
;
854 io_read_buf(struct io
*io
, char buf
[], size_t bufsize
)
856 char *result
= io_get(io
, '\n', TRUE
);
859 result
= chomp_string(result
);
860 string_ncopy_do(buf
, bufsize
, result
, strlen(result
));
863 return done_io(io
) && result
;
867 run_io_buf(const char **argv
, char buf
[], size_t bufsize
)
871 return run_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
872 && io_read_buf(&io
, buf
, bufsize
);
876 io_load(struct io
*io
, const char *separators
,
877 int (*read_property
)(char *, size_t, char *, size_t))
885 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
890 name
= chomp_string(name
);
891 namelen
= strcspn(name
, separators
);
895 value
= chomp_string(name
+ namelen
+ 1);
896 valuelen
= strlen(value
);
903 state
= read_property(name
, namelen
, value
, valuelen
);
906 if (state
!= ERR
&& io_error(io
))
914 run_io_load(const char **argv
, const char *separators
,
915 int (*read_property
)(char *, size_t, char *, size_t))
919 return init_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
920 ? io_load(&io
, separators
, read_property
) : ERR
;
929 /* XXX: Keep the view request first and in sync with views[]. */ \
930 REQ_GROUP("View switching") \
931 REQ_(VIEW_MAIN, "Show main view"), \
932 REQ_(VIEW_DIFF, "Show diff view"), \
933 REQ_(VIEW_LOG, "Show log view"), \
934 REQ_(VIEW_TREE, "Show tree view"), \
935 REQ_(VIEW_BLOB, "Show blob view"), \
936 REQ_(VIEW_BLAME, "Show blame view"), \
937 REQ_(VIEW_BRANCH, "Show branch view"), \
938 REQ_(VIEW_HELP, "Show help page"), \
939 REQ_(VIEW_PAGER, "Show pager view"), \
940 REQ_(VIEW_STATUS, "Show status view"), \
941 REQ_(VIEW_STAGE, "Show stage view"), \
943 REQ_GROUP("View manipulation") \
944 REQ_(ENTER, "Enter current line and scroll"), \
945 REQ_(NEXT, "Move to next"), \
946 REQ_(PREVIOUS, "Move to previous"), \
947 REQ_(PARENT, "Move to parent"), \
948 REQ_(VIEW_NEXT, "Move focus to next view"), \
949 REQ_(REFRESH, "Reload and refresh"), \
950 REQ_(MAXIMIZE, "Maximize the current view"), \
951 REQ_(VIEW_CLOSE, "Close the current view"), \
952 REQ_(QUIT, "Close all views and quit"), \
954 REQ_GROUP("View specific requests") \
955 REQ_(STATUS_UPDATE, "Update file status"), \
956 REQ_(STATUS_REVERT, "Revert file changes"), \
957 REQ_(STATUS_MERGE, "Merge file using external tool"), \
958 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
960 REQ_GROUP("Cursor navigation") \
961 REQ_(MOVE_UP, "Move cursor one line up"), \
962 REQ_(MOVE_DOWN, "Move cursor one line down"), \
963 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
964 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
965 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
966 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
968 REQ_GROUP("Scrolling") \
969 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
970 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
971 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
972 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
973 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
974 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
976 REQ_GROUP("Searching") \
977 REQ_(SEARCH, "Search the view"), \
978 REQ_(SEARCH_BACK, "Search backwards in the view"), \
979 REQ_(FIND_NEXT, "Find next search match"), \
980 REQ_(FIND_PREV, "Find previous search match"), \
982 REQ_GROUP("Option manipulation") \
983 REQ_(OPTIONS, "Open option menu"), \
984 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
985 REQ_(TOGGLE_DATE, "Toggle date display"), \
986 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
987 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
988 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
989 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
990 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
991 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
994 REQ_(PROMPT, "Bring up the prompt"), \
995 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
996 REQ_(SHOW_VERSION, "Show version information"), \
997 REQ_(STOP_LOADING, "Stop all loading views"), \
998 REQ_(EDIT, "Open in editor"), \
999 REQ_(NONE, "Do nothing")
1002 /* User action requests. */
1004 #define REQ_GROUP(help)
1005 #define REQ_(req, help) REQ_##req
1007 /* Offset all requests to avoid conflicts with ncurses getch values. */
1008 REQ_OFFSET
= KEY_MAX
+ 1,
1015 struct request_info
{
1016 enum request request
;
1022 static const struct request_info req_info
[] = {
1023 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1024 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1031 get_request(const char *name
)
1033 int namelen
= strlen(name
);
1036 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
1037 if (enum_equals(req_info
[i
], name
, namelen
))
1038 return req_info
[i
].request
;
1048 /* Option and state variables. */
1049 static enum date opt_date
= DATE_DEFAULT
;
1050 static enum author opt_author
= AUTHOR_DEFAULT
;
1051 static bool opt_line_number
= FALSE
;
1052 static bool opt_line_graphics
= TRUE
;
1053 static bool opt_rev_graph
= FALSE
;
1054 static bool opt_show_refs
= TRUE
;
1055 static int opt_num_interval
= 5;
1056 static double opt_hscroll
= 0.50;
1057 static double opt_scale_split_view
= 2.0 / 3.0;
1058 static int opt_tab_size
= 8;
1059 static int opt_author_cols
= AUTHOR_COLS
;
1060 static char opt_path
[SIZEOF_STR
] = "";
1061 static char opt_file
[SIZEOF_STR
] = "";
1062 static char opt_ref
[SIZEOF_REF
] = "";
1063 static char opt_head
[SIZEOF_REF
] = "";
1064 static char opt_head_rev
[SIZEOF_REV
] = "";
1065 static char opt_remote
[SIZEOF_REF
] = "";
1066 static char opt_encoding
[20] = "UTF-8";
1067 static char opt_codeset
[20] = "UTF-8";
1068 static iconv_t opt_iconv_in
= ICONV_NONE
;
1069 static iconv_t opt_iconv_out
= ICONV_NONE
;
1070 static char opt_search
[SIZEOF_STR
] = "";
1071 static char opt_cdup
[SIZEOF_STR
] = "";
1072 static char opt_prefix
[SIZEOF_STR
] = "";
1073 static char opt_git_dir
[SIZEOF_STR
] = "";
1074 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
1075 static char opt_editor
[SIZEOF_STR
] = "";
1076 static FILE *opt_tty
= NULL
;
1078 #define is_initial_commit() (!*opt_head_rev)
1079 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1080 #define mkdate(time) string_date(time, opt_date)
1084 * Line-oriented content detection.
1088 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1089 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1090 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1091 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1092 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1093 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1094 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1095 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1096 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1097 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1098 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1099 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1100 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1101 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1102 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1103 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1104 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1105 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1106 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1107 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1108 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1109 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1110 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1111 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1112 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1113 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1114 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1115 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1116 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1117 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1118 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1119 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1120 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1121 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1122 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1123 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1124 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1125 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1126 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1127 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1128 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1129 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1130 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1131 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1132 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1133 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1134 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1135 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1136 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1137 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1138 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1139 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1140 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1141 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1142 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1143 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1144 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1147 #define LINE(type, line, fg, bg, attr) \
1155 const char *name
; /* Option name. */
1156 int namelen
; /* Size of option name. */
1157 const char *line
; /* The start of line to match. */
1158 int linelen
; /* Size of string to match. */
1159 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1162 static struct line_info line_info
[] = {
1163 #define LINE(type, line, fg, bg, attr) \
1164 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1169 static enum line_type
1170 get_line_type(const char *line
)
1172 int linelen
= strlen(line
);
1173 enum line_type type
;
1175 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1176 /* Case insensitive search matches Signed-off-by lines better. */
1177 if (linelen
>= line_info
[type
].linelen
&&
1178 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1181 return LINE_DEFAULT
;
1185 get_line_attr(enum line_type type
)
1187 assert(type
< ARRAY_SIZE(line_info
));
1188 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1191 static struct line_info
*
1192 get_line_info(const char *name
)
1194 size_t namelen
= strlen(name
);
1195 enum line_type type
;
1197 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1198 if (enum_equals(line_info
[type
], name
, namelen
))
1199 return &line_info
[type
];
1207 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1208 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1209 enum line_type type
;
1213 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1214 default_bg
= COLOR_BLACK
;
1215 default_fg
= COLOR_WHITE
;
1218 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1219 struct line_info
*info
= &line_info
[type
];
1220 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1221 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1223 init_pair(type
, fg
, bg
);
1228 enum line_type type
;
1231 unsigned int selected
:1;
1232 unsigned int dirty
:1;
1233 unsigned int cleareol
:1;
1234 unsigned int other
:16;
1236 void *data
; /* User data */
1246 enum request request
;
1249 static const struct keybinding default_keybindings
[] = {
1250 /* View switching */
1251 { 'm', REQ_VIEW_MAIN
},
1252 { 'd', REQ_VIEW_DIFF
},
1253 { 'l', REQ_VIEW_LOG
},
1254 { 't', REQ_VIEW_TREE
},
1255 { 'f', REQ_VIEW_BLOB
},
1256 { 'B', REQ_VIEW_BLAME
},
1257 { 'H', REQ_VIEW_BRANCH
},
1258 { 'p', REQ_VIEW_PAGER
},
1259 { 'h', REQ_VIEW_HELP
},
1260 { 'S', REQ_VIEW_STATUS
},
1261 { 'c', REQ_VIEW_STAGE
},
1263 /* View manipulation */
1264 { 'q', REQ_VIEW_CLOSE
},
1265 { KEY_TAB
, REQ_VIEW_NEXT
},
1266 { KEY_RETURN
, REQ_ENTER
},
1267 { KEY_UP
, REQ_PREVIOUS
},
1268 { KEY_DOWN
, REQ_NEXT
},
1269 { 'R', REQ_REFRESH
},
1270 { KEY_F(5), REQ_REFRESH
},
1271 { 'O', REQ_MAXIMIZE
},
1273 /* Cursor navigation */
1274 { 'k', REQ_MOVE_UP
},
1275 { 'j', REQ_MOVE_DOWN
},
1276 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1277 { KEY_END
, REQ_MOVE_LAST_LINE
},
1278 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1279 { ' ', REQ_MOVE_PAGE_DOWN
},
1280 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1281 { 'b', REQ_MOVE_PAGE_UP
},
1282 { '-', REQ_MOVE_PAGE_UP
},
1285 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1286 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1287 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1288 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1289 { 'w', REQ_SCROLL_PAGE_UP
},
1290 { 's', REQ_SCROLL_PAGE_DOWN
},
1293 { '/', REQ_SEARCH
},
1294 { '?', REQ_SEARCH_BACK
},
1295 { 'n', REQ_FIND_NEXT
},
1296 { 'N', REQ_FIND_PREV
},
1300 { 'z', REQ_STOP_LOADING
},
1301 { 'v', REQ_SHOW_VERSION
},
1302 { 'r', REQ_SCREEN_REDRAW
},
1303 { 'o', REQ_OPTIONS
},
1304 { '.', REQ_TOGGLE_LINENO
},
1305 { 'D', REQ_TOGGLE_DATE
},
1306 { 'A', REQ_TOGGLE_AUTHOR
},
1307 { 'g', REQ_TOGGLE_REV_GRAPH
},
1308 { 'F', REQ_TOGGLE_REFS
},
1309 { 'I', REQ_TOGGLE_SORT_ORDER
},
1310 { 'i', REQ_TOGGLE_SORT_FIELD
},
1311 { ':', REQ_PROMPT
},
1312 { 'u', REQ_STATUS_UPDATE
},
1313 { '!', REQ_STATUS_REVERT
},
1314 { 'M', REQ_STATUS_MERGE
},
1315 { '@', REQ_STAGE_NEXT
},
1316 { ',', REQ_PARENT
},
1320 #define KEYMAP_INFO \
1335 #define KEYMAP_(name) KEYMAP_##name
1340 static const struct enum_map keymap_table
[] = {
1341 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1346 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1348 struct keybinding_table
{
1349 struct keybinding
*data
;
1353 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1356 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1358 struct keybinding_table
*table
= &keybindings
[keymap
];
1360 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1362 die("Failed to allocate keybinding");
1363 table
->data
[table
->size
].alias
= key
;
1364 table
->data
[table
->size
++].request
= request
;
1367 /* Looks for a key binding first in the given map, then in the generic map, and
1368 * lastly in the default keybindings. */
1370 get_keybinding(enum keymap keymap
, int key
)
1374 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1375 if (keybindings
[keymap
].data
[i
].alias
== key
)
1376 return keybindings
[keymap
].data
[i
].request
;
1378 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1379 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1380 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1382 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1383 if (default_keybindings
[i
].alias
== key
)
1384 return default_keybindings
[i
].request
;
1386 return (enum request
) key
;
1395 static const struct key key_table
[] = {
1396 { "Enter", KEY_RETURN
},
1398 { "Backspace", KEY_BACKSPACE
},
1400 { "Escape", KEY_ESC
},
1401 { "Left", KEY_LEFT
},
1402 { "Right", KEY_RIGHT
},
1404 { "Down", KEY_DOWN
},
1405 { "Insert", KEY_IC
},
1406 { "Delete", KEY_DC
},
1408 { "Home", KEY_HOME
},
1410 { "PageUp", KEY_PPAGE
},
1411 { "PageDown", KEY_NPAGE
},
1421 { "F10", KEY_F(10) },
1422 { "F11", KEY_F(11) },
1423 { "F12", KEY_F(12) },
1427 get_key_value(const char *name
)
1431 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1432 if (!strcasecmp(key_table
[i
].name
, name
))
1433 return key_table
[i
].value
;
1435 if (strlen(name
) == 1 && isprint(*name
))
1442 get_key_name(int key_value
)
1444 static char key_char
[] = "'X'";
1445 const char *seq
= NULL
;
1448 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1449 if (key_table
[key
].value
== key_value
)
1450 seq
= key_table
[key
].name
;
1454 isprint(key_value
)) {
1455 key_char
[1] = (char) key_value
;
1459 return seq
? seq
: "(no key)";
1463 append_key(char *buf
, size_t *pos
, const struct keybinding
*keybinding
)
1465 const char *sep
= *pos
> 0 ? ", " : "";
1466 const char *keyname
= get_key_name(keybinding
->alias
);
1468 return string_nformat(buf
, BUFSIZ
, pos
, "%s%s", sep
, keyname
);
1472 append_keymap_request_keys(char *buf
, size_t *pos
, enum request request
,
1473 enum keymap keymap
, bool all
)
1477 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1478 if (keybindings
[keymap
].data
[i
].request
== request
) {
1479 if (!append_key(buf
, pos
, &keybindings
[keymap
].data
[i
]))
1489 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1492 get_keys(enum keymap keymap
, enum request request
, bool all
)
1494 static char buf
[BUFSIZ
];
1500 if (!append_keymap_request_keys(buf
, &pos
, request
, keymap
, all
))
1501 return "Too many keybindings!";
1502 if (pos
> 0 && !all
)
1505 if (keymap
!= KEYMAP_GENERIC
) {
1506 /* Only the generic keymap includes the default keybindings when
1507 * listing all keys. */
1511 if (!append_keymap_request_keys(buf
, &pos
, request
, KEYMAP_GENERIC
, all
))
1512 return "Too many keybindings!";
1517 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1518 if (default_keybindings
[i
].request
== request
) {
1519 if (!append_key(buf
, &pos
, &default_keybindings
[i
]))
1520 return "Too many keybindings!";
1529 struct run_request
{
1532 const char *argv
[SIZEOF_ARG
];
1535 static struct run_request
*run_request
;
1536 static size_t run_requests
;
1538 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1541 add_run_request(enum keymap keymap
, int key
, int argc
, const char **argv
)
1543 struct run_request
*req
;
1545 if (argc
>= ARRAY_SIZE(req
->argv
) - 1)
1548 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1551 req
= &run_request
[run_requests
];
1552 req
->keymap
= keymap
;
1554 req
->argv
[0] = NULL
;
1556 if (!format_argv(req
->argv
, argv
, FORMAT_NONE
))
1559 return REQ_NONE
+ ++run_requests
;
1562 static struct run_request
*
1563 get_run_request(enum request request
)
1565 if (request
<= REQ_NONE
)
1567 return &run_request
[request
- REQ_NONE
- 1];
1571 add_builtin_run_requests(void)
1573 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1574 const char *commit
[] = { "git", "commit", NULL
};
1575 const char *gc
[] = { "git", "gc", NULL
};
1582 { KEYMAP_MAIN
, 'C', ARRAY_SIZE(cherry_pick
) - 1, cherry_pick
},
1583 { KEYMAP_STATUS
, 'C', ARRAY_SIZE(commit
) - 1, commit
},
1584 { KEYMAP_GENERIC
, 'G', ARRAY_SIZE(gc
) - 1, gc
},
1588 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1591 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argc
, reqs
[i
].argv
);
1592 if (req
!= REQ_NONE
)
1593 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1598 * User config file handling.
1601 static int config_lineno
;
1602 static bool config_errors
;
1603 static const char *config_msg
;
1605 static const struct enum_map color_map
[] = {
1606 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1618 static const struct enum_map attr_map
[] = {
1619 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1626 ATTR_MAP(UNDERLINE
),
1629 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1631 static int parse_step(double *opt
, const char *arg
)
1634 if (!strchr(arg
, '%'))
1637 /* "Shift down" so 100% and 1 does not conflict. */
1638 *opt
= (*opt
- 1) / 100;
1641 config_msg
= "Step value larger than 100%";
1646 config_msg
= "Invalid step value";
1653 parse_int(int *opt
, const char *arg
, int min
, int max
)
1655 int value
= atoi(arg
);
1657 if (min
<= value
&& value
<= max
) {
1662 config_msg
= "Integer value out of bound";
1667 set_color(int *color
, const char *name
)
1669 if (map_enum(color
, color_map
, name
))
1671 if (!prefixcmp(name
, "color"))
1672 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1676 /* Wants: object fgcolor bgcolor [attribute] */
1678 option_color_command(int argc
, const char *argv
[])
1680 struct line_info
*info
;
1683 config_msg
= "Wrong number of arguments given to color command";
1687 info
= get_line_info(argv
[0]);
1689 static const struct enum_map obsolete
[] = {
1690 ENUM_MAP("main-delim", LINE_DELIMITER
),
1691 ENUM_MAP("main-date", LINE_DATE
),
1692 ENUM_MAP("main-author", LINE_AUTHOR
),
1696 if (!map_enum(&index
, obsolete
, argv
[0])) {
1697 config_msg
= "Unknown color name";
1700 info
= &line_info
[index
];
1703 if (!set_color(&info
->fg
, argv
[1]) ||
1704 !set_color(&info
->bg
, argv
[2])) {
1705 config_msg
= "Unknown color";
1710 while (argc
-- > 3) {
1713 if (!set_attribute(&attr
, argv
[argc
])) {
1714 config_msg
= "Unknown attribute";
1723 static int parse_bool(bool *opt
, const char *arg
)
1725 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1730 static int parse_enum_do(unsigned int *opt
, const char *arg
,
1731 const struct enum_map
*map
, size_t map_size
)
1735 assert(map_size
> 1);
1737 if (map_enum_do(map
, map_size
, (int *) opt
, arg
))
1740 if (parse_bool(&is_true
, arg
) != OK
)
1743 *opt
= is_true
? map
[1].value
: map
[0].value
;
1747 #define parse_enum(opt, arg, map) \
1748 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1751 parse_string(char *opt
, const char *arg
, size_t optsize
)
1753 int arglen
= strlen(arg
);
1758 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1759 config_msg
= "Unmatched quotation";
1762 arg
+= 1; arglen
-= 2;
1764 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1769 /* Wants: name = value */
1771 option_set_command(int argc
, const char *argv
[])
1774 config_msg
= "Wrong number of arguments given to set command";
1778 if (strcmp(argv
[1], "=")) {
1779 config_msg
= "No value assigned";
1783 if (!strcmp(argv
[0], "show-author"))
1784 return parse_enum(&opt_author
, argv
[2], author_map
);
1786 if (!strcmp(argv
[0], "show-date"))
1787 return parse_enum(&opt_date
, argv
[2], date_map
);
1789 if (!strcmp(argv
[0], "show-rev-graph"))
1790 return parse_bool(&opt_rev_graph
, argv
[2]);
1792 if (!strcmp(argv
[0], "show-refs"))
1793 return parse_bool(&opt_show_refs
, argv
[2]);
1795 if (!strcmp(argv
[0], "show-line-numbers"))
1796 return parse_bool(&opt_line_number
, argv
[2]);
1798 if (!strcmp(argv
[0], "line-graphics"))
1799 return parse_bool(&opt_line_graphics
, argv
[2]);
1801 if (!strcmp(argv
[0], "line-number-interval"))
1802 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
1804 if (!strcmp(argv
[0], "author-width"))
1805 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
1807 if (!strcmp(argv
[0], "horizontal-scroll"))
1808 return parse_step(&opt_hscroll
, argv
[2]);
1810 if (!strcmp(argv
[0], "split-view-height"))
1811 return parse_step(&opt_scale_split_view
, argv
[2]);
1813 if (!strcmp(argv
[0], "tab-size"))
1814 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
1816 if (!strcmp(argv
[0], "commit-encoding"))
1817 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
1819 config_msg
= "Unknown variable name";
1823 /* Wants: mode request key */
1825 option_bind_command(int argc
, const char *argv
[])
1827 enum request request
;
1832 config_msg
= "Wrong number of arguments given to bind command";
1836 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
1837 config_msg
= "Unknown key map";
1841 key
= get_key_value(argv
[1]);
1843 config_msg
= "Unknown key";
1847 request
= get_request(argv
[2]);
1848 if (request
== REQ_NONE
) {
1849 static const struct enum_map obsolete
[] = {
1850 ENUM_MAP("cherry-pick", REQ_NONE
),
1851 ENUM_MAP("screen-resize", REQ_NONE
),
1852 ENUM_MAP("tree-parent", REQ_PARENT
),
1856 if (map_enum(&alias
, obsolete
, argv
[2])) {
1857 if (alias
!= REQ_NONE
)
1858 add_keybinding(keymap
, alias
, key
);
1859 config_msg
= "Obsolete request name";
1863 if (request
== REQ_NONE
&& *argv
[2]++ == '!')
1864 request
= add_run_request(keymap
, key
, argc
- 2, argv
+ 2);
1865 if (request
== REQ_NONE
) {
1866 config_msg
= "Unknown request name";
1870 add_keybinding(keymap
, request
, key
);
1876 set_option(const char *opt
, char *value
)
1878 const char *argv
[SIZEOF_ARG
];
1881 if (!argv_from_string(argv
, &argc
, value
)) {
1882 config_msg
= "Too many option arguments";
1886 if (!strcmp(opt
, "color"))
1887 return option_color_command(argc
, argv
);
1889 if (!strcmp(opt
, "set"))
1890 return option_set_command(argc
, argv
);
1892 if (!strcmp(opt
, "bind"))
1893 return option_bind_command(argc
, argv
);
1895 config_msg
= "Unknown option command";
1900 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
1905 config_msg
= "Internal error";
1907 /* Check for comment markers, since read_properties() will
1908 * only ensure opt and value are split at first " \t". */
1909 optlen
= strcspn(opt
, "#");
1913 if (opt
[optlen
] != 0) {
1914 config_msg
= "No option value";
1918 /* Look for comment endings in the value. */
1919 size_t len
= strcspn(value
, "#");
1921 if (len
< valuelen
) {
1923 value
[valuelen
] = 0;
1926 status
= set_option(opt
, value
);
1929 if (status
== ERR
) {
1930 warn("Error on line %d, near '%.*s': %s",
1931 config_lineno
, (int) optlen
, opt
, config_msg
);
1932 config_errors
= TRUE
;
1935 /* Always keep going if errors are encountered. */
1940 load_option_file(const char *path
)
1944 /* It's OK that the file doesn't exist. */
1945 if (!io_open(&io
, "%s", path
))
1949 config_errors
= FALSE
;
1951 if (io_load(&io
, " \t", read_option
) == ERR
||
1952 config_errors
== TRUE
)
1953 warn("Errors while loading %s.", path
);
1959 const char *home
= getenv("HOME");
1960 const char *tigrc_user
= getenv("TIGRC_USER");
1961 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
1962 char buf
[SIZEOF_STR
];
1964 add_builtin_run_requests();
1967 tigrc_system
= SYSCONFDIR
"/tigrc";
1968 load_option_file(tigrc_system
);
1971 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
1975 load_option_file(tigrc_user
);
1988 /* The display array of active views and the index of the current view. */
1989 static struct view
*display
[2];
1990 static unsigned int current_view
;
1992 #define foreach_displayed_view(view, i) \
1993 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1995 #define displayed_views() (display[1] != NULL ? 2 : 1)
1997 /* Current head and commit ID */
1998 static char ref_blob
[SIZEOF_REF
] = "";
1999 static char ref_commit
[SIZEOF_REF
] = "HEAD";
2000 static char ref_head
[SIZEOF_REF
] = "HEAD";
2003 const char *name
; /* View name */
2004 const char *cmd_env
; /* Command line set via environment */
2005 const char *id
; /* Points to either of ref_{head,commit,blob} */
2007 struct view_ops
*ops
; /* View operations */
2009 enum keymap keymap
; /* What keymap does this view have */
2010 bool git_dir
; /* Whether the view requires a git directory. */
2012 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
2013 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
2015 int height
, width
; /* The width and height of the main window */
2016 WINDOW
*win
; /* The main window */
2017 WINDOW
*title
; /* The title window living below the main window */
2020 unsigned long offset
; /* Offset of the window top */
2021 unsigned long yoffset
; /* Offset from the window side. */
2022 unsigned long lineno
; /* Current line number */
2023 unsigned long p_offset
; /* Previous offset of the window top */
2024 unsigned long p_yoffset
;/* Previous offset from the window side */
2025 unsigned long p_lineno
; /* Previous current line number */
2026 bool p_restore
; /* Should the previous position be restored. */
2029 char grep
[SIZEOF_STR
]; /* Search string */
2030 regex_t
*regex
; /* Pre-compiled regexp */
2032 /* If non-NULL, points to the view that opened this view. If this view
2033 * is closed tig will switch back to the parent view. */
2034 struct view
*parent
;
2037 size_t lines
; /* Total number of lines */
2038 struct line
*line
; /* Line index */
2039 unsigned int digits
; /* Number of digits in the lines member. */
2042 struct line
*curline
; /* Line currently being drawn. */
2043 enum line_type curtype
; /* Attribute currently used for drawing. */
2044 unsigned long col
; /* Column when drawing. */
2045 bool has_scrolled
; /* View was scrolled. */
2055 /* What type of content being displayed. Used in the title bar. */
2057 /* Default command arguments. */
2059 /* Open and reads in all view content. */
2060 bool (*open
)(struct view
*view
);
2061 /* Read one line; updates view->line. */
2062 bool (*read
)(struct view
*view
, char *data
);
2063 /* Draw one line; @lineno must be < view->height. */
2064 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
2065 /* Depending on view handle a special requests. */
2066 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
2067 /* Search for regexp in a line. */
2068 bool (*grep
)(struct view
*view
, struct line
*line
);
2070 void (*select
)(struct view
*view
, struct line
*line
);
2071 /* Prepare view for loading */
2072 bool (*prepare
)(struct view
*view
);
2075 static struct view_ops blame_ops
;
2076 static struct view_ops blob_ops
;
2077 static struct view_ops diff_ops
;
2078 static struct view_ops help_ops
;
2079 static struct view_ops log_ops
;
2080 static struct view_ops main_ops
;
2081 static struct view_ops pager_ops
;
2082 static struct view_ops stage_ops
;
2083 static struct view_ops status_ops
;
2084 static struct view_ops tree_ops
;
2085 static struct view_ops branch_ops
;
2087 #define VIEW_STR(name, env, ref, ops, map, git) \
2088 { name, #env, ref, ops, map, git }
2090 #define VIEW_(id, name, ops, git, ref) \
2091 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2094 static struct view views
[] = {
2095 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
2096 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
2097 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
2098 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
2099 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
2100 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
2101 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
2102 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
2103 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
2104 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
2105 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
2108 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2109 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2111 #define foreach_view(view, i) \
2112 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2114 #define view_is_displayed(view) \
2115 (view == display[0] || view == display[1])
2119 set_view_attr(struct view
*view
, enum line_type type
)
2121 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
2122 (void) wattrset(view
->win
, get_line_attr(type
));
2123 wchgat(view
->win
, -1, 0, type
, NULL
);
2124 view
->curtype
= type
;
2129 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
2130 int max_len
, bool use_tilde
)
2132 static char out_buffer
[BUFSIZ
* 2];
2135 int trimmed
= FALSE
;
2136 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2141 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
);
2143 set_view_attr(view
, type
);
2145 if (opt_iconv_out
!= ICONV_NONE
) {
2146 ICONV_CONST
char *inbuf
= (ICONV_CONST
char *) string
;
2147 size_t inlen
= len
+ 1;
2149 char *outbuf
= out_buffer
;
2150 size_t outlen
= sizeof(out_buffer
);
2154 ret
= iconv(opt_iconv_out
, &inbuf
, &inlen
, &outbuf
, &outlen
);
2155 if (ret
!= (size_t) -1) {
2156 string
= out_buffer
;
2157 len
= sizeof(out_buffer
) - outlen
;
2161 waddnstr(view
->win
, string
, len
);
2163 if (trimmed
&& use_tilde
) {
2164 set_view_attr(view
, LINE_DELIMITER
);
2165 waddch(view
->win
, '~');
2173 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
2175 static char space
[] = " ";
2178 spaces
= MIN(max
, spaces
);
2180 while (spaces
> 0) {
2181 int len
= MIN(spaces
, sizeof(space
) - 1);
2183 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
2191 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
2193 view
->col
+= draw_chars(view
, type
, string
, view
->width
+ view
->yoffset
- view
->col
, trim
);
2194 return view
->width
+ view
->yoffset
<= view
->col
;
2198 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
2200 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2201 int max
= view
->width
+ view
->yoffset
- view
->col
;
2207 set_view_attr(view
, type
);
2208 /* Using waddch() instead of waddnstr() ensures that
2209 * they'll be rendered correctly for the cursor line. */
2210 for (i
= skip
; i
< size
; i
++)
2211 waddch(view
->win
, graphic
[i
]);
2214 if (size
< max
&& skip
<= size
)
2215 waddch(view
->win
, ' ');
2218 return view
->width
+ view
->yoffset
<= view
->col
;
2222 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2224 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2228 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2230 col
= draw_space(view
, type
, max
- 1, max
- 1);
2233 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2234 return view
->width
+ view
->yoffset
<= view
->col
;
2238 draw_date(struct view
*view
, struct time
*time
)
2240 const char *date
= time
&& time
->sec
? mkdate(time
) : "";
2241 int cols
= opt_date
== DATE_SHORT
? DATE_SHORT_COLS
: DATE_COLS
;
2243 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2247 draw_author(struct view
*view
, const char *author
)
2249 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5;
2250 bool abbreviate
= opt_author
== AUTHOR_ABBREVIATED
|| !trim
;
2252 if (abbreviate
&& author
)
2253 author
= get_author_initials(author
);
2255 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2259 draw_mode(struct view
*view
, mode_t mode
)
2265 else if (S_ISLNK(mode
))
2267 else if (S_ISGITLINK(mode
))
2269 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2271 else if (S_ISREG(mode
))
2276 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2280 draw_lineno(struct view
*view
, unsigned int lineno
)
2283 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2284 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2286 chtype separator
= opt_line_graphics
? ACS_VLINE
: '|';
2288 lineno
+= view
->offset
+ 1;
2289 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2290 static char fmt
[] = "%1ld";
2292 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2293 if (string_format(number
, fmt
, lineno
))
2297 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2299 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2300 return draw_graphic(view
, LINE_DEFAULT
, &separator
, 1);
2304 draw_view_line(struct view
*view
, unsigned int lineno
)
2307 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2309 assert(view_is_displayed(view
));
2311 if (view
->offset
+ lineno
>= view
->lines
)
2314 line
= &view
->line
[view
->offset
+ lineno
];
2316 wmove(view
->win
, lineno
, 0);
2318 wclrtoeol(view
->win
);
2320 view
->curline
= line
;
2321 view
->curtype
= LINE_NONE
;
2322 line
->selected
= FALSE
;
2323 line
->dirty
= line
->cleareol
= 0;
2326 set_view_attr(view
, LINE_CURSOR
);
2327 line
->selected
= TRUE
;
2328 view
->ops
->select(view
, line
);
2331 return view
->ops
->draw(view
, line
, lineno
);
2335 redraw_view_dirty(struct view
*view
)
2340 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2341 if (view
->offset
+ lineno
>= view
->lines
)
2343 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2346 if (!draw_view_line(view
, lineno
))
2352 wnoutrefresh(view
->win
);
2356 redraw_view_from(struct view
*view
, int lineno
)
2358 assert(0 <= lineno
&& lineno
< view
->height
);
2360 for (; lineno
< view
->height
; lineno
++) {
2361 if (!draw_view_line(view
, lineno
))
2365 wnoutrefresh(view
->win
);
2369 redraw_view(struct view
*view
)
2372 redraw_view_from(view
, 0);
2377 update_view_title(struct view
*view
)
2379 char buf
[SIZEOF_STR
];
2380 char state
[SIZEOF_STR
];
2381 size_t bufpos
= 0, statelen
= 0;
2383 assert(view_is_displayed(view
));
2385 if (view
!= VIEW(REQ_VIEW_STATUS
) && view
->lines
) {
2386 unsigned int view_lines
= view
->offset
+ view
->height
;
2387 unsigned int lines
= view
->lines
2388 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2391 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2400 time_t secs
= time(NULL
) - view
->start_time
;
2402 /* Three git seconds are a long time ... */
2404 string_format_from(state
, &statelen
, " loading %lds", secs
);
2407 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2408 if (*view
->ref
&& bufpos
< view
->width
) {
2409 size_t refsize
= strlen(view
->ref
);
2410 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2412 if (minsize
< view
->width
)
2413 refsize
= view
->width
- minsize
+ 7;
2414 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2417 if (statelen
&& bufpos
< view
->width
) {
2418 string_format_from(buf
, &bufpos
, "%s", state
);
2421 if (view
== display
[current_view
])
2422 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2424 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2426 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2427 wclrtoeol(view
->title
);
2428 wnoutrefresh(view
->title
);
2432 apply_step(double step
, int value
)
2436 value
*= step
+ 0.01;
2437 return value
? value
: 1;
2441 resize_display(void)
2444 struct view
*base
= display
[0];
2445 struct view
*view
= display
[1] ? display
[1] : display
[0];
2447 /* Setup window dimensions */
2449 getmaxyx(stdscr
, base
->height
, base
->width
);
2451 /* Make room for the status window. */
2455 /* Horizontal split. */
2456 view
->width
= base
->width
;
2457 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2458 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2459 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2460 base
->height
-= view
->height
;
2462 /* Make room for the title bar. */
2466 /* Make room for the title bar. */
2471 foreach_displayed_view (view
, i
) {
2473 view
->win
= newwin(view
->height
, 0, offset
, 0);
2475 die("Failed to create %s view", view
->name
);
2477 scrollok(view
->win
, FALSE
);
2479 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2481 die("Failed to create title window");
2484 wresize(view
->win
, view
->height
, view
->width
);
2485 mvwin(view
->win
, offset
, 0);
2486 mvwin(view
->title
, offset
+ view
->height
, 0);
2489 offset
+= view
->height
+ 1;
2494 redraw_display(bool clear
)
2499 foreach_displayed_view (view
, i
) {
2503 update_view_title(view
);
2508 toggle_enum_option_do(unsigned int *opt
, const char *help
,
2509 const struct enum_map
*map
, size_t size
)
2511 *opt
= (*opt
+ 1) % size
;
2512 redraw_display(FALSE
);
2513 report("Displaying %s %s", enum_name(map
[*opt
]), help
);
2516 #define toggle_enum_option(opt, help, map) \
2517 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2519 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2520 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2523 toggle_view_option(bool *option
, const char *help
)
2526 redraw_display(FALSE
);
2527 report("%sabling %s", *option
? "En" : "Dis", help
);
2531 open_option_menu(void)
2533 const struct menu_item menu
[] = {
2534 { '.', "line numbers", &opt_line_number
},
2535 { 'D', "date display", &opt_date
},
2536 { 'A', "author display", &opt_author
},
2537 { 'g', "revision graph display", &opt_rev_graph
},
2538 { 'F', "reference display", &opt_show_refs
},
2543 if (prompt_menu("Toggle option", menu
, &selected
)) {
2544 if (menu
[selected
].data
== &opt_date
)
2546 else if (menu
[selected
].data
== &opt_author
)
2549 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2554 maximize_view(struct view
*view
)
2556 memset(display
, 0, sizeof(display
));
2558 display
[current_view
] = view
;
2560 redraw_display(FALSE
);
2570 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2572 if (lineno
>= view
->lines
)
2573 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2575 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2576 unsigned long half
= view
->height
/ 2;
2579 offset
= lineno
- half
;
2584 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2585 view
->offset
= offset
;
2586 view
->lineno
= lineno
;
2593 /* Scrolling backend */
2595 do_scroll_view(struct view
*view
, int lines
)
2597 bool redraw_current_line
= FALSE
;
2599 /* The rendering expects the new offset. */
2600 view
->offset
+= lines
;
2602 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2605 /* Move current line into the view. */
2606 if (view
->lineno
< view
->offset
) {
2607 view
->lineno
= view
->offset
;
2608 redraw_current_line
= TRUE
;
2609 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2610 view
->lineno
= view
->offset
+ view
->height
- 1;
2611 redraw_current_line
= TRUE
;
2614 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2616 /* Redraw the whole screen if scrolling is pointless. */
2617 if (view
->height
< ABS(lines
)) {
2621 int line
= lines
> 0 ? view
->height
- lines
: 0;
2622 int end
= line
+ ABS(lines
);
2624 scrollok(view
->win
, TRUE
);
2625 wscrl(view
->win
, lines
);
2626 scrollok(view
->win
, FALSE
);
2628 while (line
< end
&& draw_view_line(view
, line
))
2631 if (redraw_current_line
)
2632 draw_view_line(view
, view
->lineno
- view
->offset
);
2633 wnoutrefresh(view
->win
);
2636 view
->has_scrolled
= TRUE
;
2640 /* Scroll frontend */
2642 scroll_view(struct view
*view
, enum request request
)
2646 assert(view_is_displayed(view
));
2649 case REQ_SCROLL_LEFT
:
2650 if (view
->yoffset
== 0) {
2651 report("Cannot scroll beyond the first column");
2654 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2657 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2658 redraw_view_from(view
, 0);
2661 case REQ_SCROLL_RIGHT
:
2662 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2666 case REQ_SCROLL_PAGE_DOWN
:
2667 lines
= view
->height
;
2668 case REQ_SCROLL_LINE_DOWN
:
2669 if (view
->offset
+ lines
> view
->lines
)
2670 lines
= view
->lines
- view
->offset
;
2672 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2673 report("Cannot scroll beyond the last line");
2678 case REQ_SCROLL_PAGE_UP
:
2679 lines
= view
->height
;
2680 case REQ_SCROLL_LINE_UP
:
2681 if (lines
> view
->offset
)
2682 lines
= view
->offset
;
2685 report("Cannot scroll beyond the first line");
2693 die("request %d not handled in switch", request
);
2696 do_scroll_view(view
, lines
);
2701 move_view(struct view
*view
, enum request request
)
2703 int scroll_steps
= 0;
2707 case REQ_MOVE_FIRST_LINE
:
2708 steps
= -view
->lineno
;
2711 case REQ_MOVE_LAST_LINE
:
2712 steps
= view
->lines
- view
->lineno
- 1;
2715 case REQ_MOVE_PAGE_UP
:
2716 steps
= view
->height
> view
->lineno
2717 ? -view
->lineno
: -view
->height
;
2720 case REQ_MOVE_PAGE_DOWN
:
2721 steps
= view
->lineno
+ view
->height
>= view
->lines
2722 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2734 die("request %d not handled in switch", request
);
2737 if (steps
<= 0 && view
->lineno
== 0) {
2738 report("Cannot move beyond the first line");
2741 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2742 report("Cannot move beyond the last line");
2746 /* Move the current line */
2747 view
->lineno
+= steps
;
2748 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2750 /* Check whether the view needs to be scrolled */
2751 if (view
->lineno
< view
->offset
||
2752 view
->lineno
>= view
->offset
+ view
->height
) {
2753 scroll_steps
= steps
;
2754 if (steps
< 0 && -steps
> view
->offset
) {
2755 scroll_steps
= -view
->offset
;
2757 } else if (steps
> 0) {
2758 if (view
->lineno
== view
->lines
- 1 &&
2759 view
->lines
> view
->height
) {
2760 scroll_steps
= view
->lines
- view
->offset
- 1;
2761 if (scroll_steps
>= view
->height
)
2762 scroll_steps
-= view
->height
- 1;
2767 if (!view_is_displayed(view
)) {
2768 view
->offset
+= scroll_steps
;
2769 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2770 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2774 /* Repaint the old "current" line if we be scrolling */
2775 if (ABS(steps
) < view
->height
)
2776 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
2779 do_scroll_view(view
, scroll_steps
);
2783 /* Draw the current line */
2784 draw_view_line(view
, view
->lineno
- view
->offset
);
2786 wnoutrefresh(view
->win
);
2795 static void search_view(struct view
*view
, enum request request
);
2798 grep_text(struct view
*view
, const char *text
[])
2803 for (i
= 0; text
[i
]; i
++)
2805 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
2811 select_view_line(struct view
*view
, unsigned long lineno
)
2813 unsigned long old_lineno
= view
->lineno
;
2814 unsigned long old_offset
= view
->offset
;
2816 if (goto_view_line(view
, view
->offset
, lineno
)) {
2817 if (view_is_displayed(view
)) {
2818 if (old_offset
!= view
->offset
) {
2821 draw_view_line(view
, old_lineno
- view
->offset
);
2822 draw_view_line(view
, view
->lineno
- view
->offset
);
2823 wnoutrefresh(view
->win
);
2826 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2832 find_next(struct view
*view
, enum request request
)
2834 unsigned long lineno
= view
->lineno
;
2839 report("No previous search");
2841 search_view(view
, request
);
2851 case REQ_SEARCH_BACK
:
2860 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
2861 lineno
+= direction
;
2863 /* Note, lineno is unsigned long so will wrap around in which case it
2864 * will become bigger than view->lines. */
2865 for (; lineno
< view
->lines
; lineno
+= direction
) {
2866 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
2867 select_view_line(view
, lineno
);
2868 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
2873 report("No match found for '%s'", view
->grep
);
2877 search_view(struct view
*view
, enum request request
)
2882 regfree(view
->regex
);
2885 view
->regex
= calloc(1, sizeof(*view
->regex
));
2890 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
2891 if (regex_err
!= 0) {
2892 char buf
[SIZEOF_STR
] = "unknown error";
2894 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
2895 report("Search failed: %s", buf
);
2899 string_copy(view
->grep
, opt_search
);
2901 find_next(view
, request
);
2905 * Incremental updating
2909 reset_view(struct view
*view
)
2913 for (i
= 0; i
< view
->lines
; i
++)
2914 free(view
->line
[i
].data
);
2917 view
->p_offset
= view
->offset
;
2918 view
->p_yoffset
= view
->yoffset
;
2919 view
->p_lineno
= view
->lineno
;
2927 view
->update_secs
= 0;
2931 free_argv(const char *argv
[])
2935 for (argc
= 0; argv
[argc
]; argc
++)
2936 free((void *) argv
[argc
]);
2940 format_arg(const char *name
)
2946 const char *value_if_empty
;
2948 #define FORMAT_VAR(name, value, value_if_empty) \
2949 { name, STRING_SIZE(name), value, value_if_empty }
2950 FORMAT_VAR("%(directory)", opt_path
, ""),
2951 FORMAT_VAR("%(file)", opt_file
, ""),
2952 FORMAT_VAR("%(ref)", opt_ref
, "HEAD"),
2953 FORMAT_VAR("%(head)", ref_head
, ""),
2954 FORMAT_VAR("%(commit)", ref_commit
, ""),
2955 FORMAT_VAR("%(blob)", ref_blob
, ""),
2959 for (i
= 0; i
< ARRAY_SIZE(vars
); i
++)
2960 if (!strncmp(name
, vars
[i
].name
, vars
[i
].namelen
))
2961 return *vars
[i
].value
? vars
[i
].value
: vars
[i
].value_if_empty
;
2966 format_argv(const char *dst_argv
[], const char *src_argv
[], enum format_flags flags
)
2968 char buf
[SIZEOF_STR
];
2970 bool noreplace
= flags
== FORMAT_NONE
;
2972 free_argv(dst_argv
);
2974 for (argc
= 0; src_argv
[argc
]; argc
++) {
2975 const char *arg
= src_argv
[argc
];
2979 char *next
= strstr(arg
, "%(");
2980 int len
= next
- arg
;
2983 if (!next
|| noreplace
) {
2984 if (flags
== FORMAT_DASH
&& !strcmp(arg
, "--"))
2990 value
= format_arg(next
);
2993 report("Unknown replacement: `%s`", next
);
2998 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
3001 arg
= next
&& !noreplace
? strchr(next
, ')') + 1 : NULL
;
3004 dst_argv
[argc
] = strdup(buf
);
3005 if (!dst_argv
[argc
])
3009 dst_argv
[argc
] = NULL
;
3011 return src_argv
[argc
] == NULL
;
3015 restore_view_position(struct view
*view
)
3017 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
3020 /* Changing the view position cancels the restoring. */
3021 /* FIXME: Changing back to the first line is not detected. */
3022 if (view
->offset
!= 0 || view
->lineno
!= 0) {
3023 view
->p_restore
= FALSE
;
3027 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
3028 view_is_displayed(view
))
3031 view
->yoffset
= view
->p_yoffset
;
3032 view
->p_restore
= FALSE
;
3038 end_update(struct view
*view
, bool force
)
3042 while (!view
->ops
->read(view
, NULL
))
3045 set_nonblocking_input(FALSE
);
3047 kill_io(view
->pipe
);
3048 done_io(view
->pipe
);
3053 setup_update(struct view
*view
, const char *vid
)
3055 set_nonblocking_input(TRUE
);
3057 string_copy_rev(view
->vid
, vid
);
3058 view
->pipe
= &view
->io
;
3059 view
->start_time
= time(NULL
);
3063 prepare_update(struct view
*view
, const char *argv
[], const char *dir
,
3064 enum format_flags flags
)
3067 end_update(view
, TRUE
);
3068 return init_io_rd(&view
->io
, argv
, dir
, flags
);
3072 prepare_update_file(struct view
*view
, const char *name
)
3075 end_update(view
, TRUE
);
3076 return io_open(&view
->io
, "%s/%s", opt_cdup
[0] ? opt_cdup
: ".", name
);
3080 begin_update(struct view
*view
, bool refresh
)
3083 end_update(view
, TRUE
);
3086 if (view
->ops
->prepare
) {
3087 if (!view
->ops
->prepare(view
))
3089 } else if (!init_io_rd(&view
->io
, view
->ops
->argv
, NULL
, FORMAT_ALL
)) {
3093 /* Put the current ref_* value to the view title ref
3094 * member. This is needed by the blob view. Most other
3095 * views sets it automatically after loading because the
3096 * first line is a commit line. */
3097 string_copy_rev(view
->ref
, view
->id
);
3100 if (!start_io(&view
->io
))
3103 setup_update(view
, view
->id
);
3109 update_view(struct view
*view
)
3111 char out_buffer
[BUFSIZ
* 2];
3113 /* Clear the view and redraw everything since the tree sorting
3114 * might have rearranged things. */
3115 bool redraw
= view
->lines
== 0;
3116 bool can_read
= TRUE
;
3121 if (!io_can_read(view
->pipe
)) {
3122 if (view
->lines
== 0 && view_is_displayed(view
)) {
3123 time_t secs
= time(NULL
) - view
->start_time
;
3125 if (secs
> 1 && secs
> view
->update_secs
) {
3126 if (view
->update_secs
== 0)
3128 update_view_title(view
);
3129 view
->update_secs
= secs
;
3135 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
3136 if (opt_iconv_in
!= ICONV_NONE
) {
3137 ICONV_CONST
char *inbuf
= line
;
3138 size_t inlen
= strlen(line
) + 1;
3140 char *outbuf
= out_buffer
;
3141 size_t outlen
= sizeof(out_buffer
);
3145 ret
= iconv(opt_iconv_in
, &inbuf
, &inlen
, &outbuf
, &outlen
);
3146 if (ret
!= (size_t) -1)
3150 if (!view
->ops
->read(view
, line
)) {
3151 report("Allocation failure");
3152 end_update(view
, TRUE
);
3158 unsigned long lines
= view
->lines
;
3161 for (digits
= 0; lines
; digits
++)
3164 /* Keep the displayed view in sync with line number scaling. */
3165 if (digits
!= view
->digits
) {
3166 view
->digits
= digits
;
3167 if (opt_line_number
|| view
== VIEW(REQ_VIEW_BLAME
))
3172 if (io_error(view
->pipe
)) {
3173 report("Failed to read: %s", io_strerror(view
->pipe
));
3174 end_update(view
, TRUE
);
3176 } else if (io_eof(view
->pipe
)) {
3178 end_update(view
, FALSE
);
3181 if (restore_view_position(view
))
3184 if (!view_is_displayed(view
))
3188 redraw_view_from(view
, 0);
3190 redraw_view_dirty(view
);
3192 /* Update the title _after_ the redraw so that if the redraw picks up a
3193 * commit reference in view->ref it'll be available here. */
3194 update_view_title(view
);
3198 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
3200 static struct line
*
3201 add_line_data(struct view
*view
, void *data
, enum line_type type
)
3205 if (!realloc_lines(&view
->line
, view
->lines
, 1))
3208 line
= &view
->line
[view
->lines
++];
3209 memset(line
, 0, sizeof(*line
));
3217 static struct line
*
3218 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
3220 char *data
= text
? strdup(text
) : NULL
;
3222 return data
? add_line_data(view
, data
, type
) : NULL
;
3225 static struct line
*
3226 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3228 char buf
[SIZEOF_STR
];
3231 va_start(args
, fmt
);
3232 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3236 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3244 OPEN_DEFAULT
= 0, /* Use default view switching. */
3245 OPEN_SPLIT
= 1, /* Split current view. */
3246 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3247 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3248 OPEN_PREPARED
= 32, /* Open already prepared command. */
3252 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3254 bool split
= !!(flags
& OPEN_SPLIT
);
3255 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3256 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3257 struct view
*view
= VIEW(request
);
3258 int nviews
= displayed_views();
3259 struct view
*base_view
= display
[0];
3261 if (view
== prev
&& nviews
== 1 && !reload
) {
3262 report("Already in %s view", view
->name
);
3266 if (view
->git_dir
&& !opt_git_dir
[0]) {
3267 report("The %s view is disabled in pager view", view
->name
);
3274 } else if (!nomaximize
) {
3275 /* Maximize the current view. */
3276 memset(display
, 0, sizeof(display
));
3278 display
[current_view
] = view
;
3281 /* No parent signals that this is the first loaded view. */
3282 if (prev
&& view
!= prev
) {
3283 view
->parent
= prev
;
3286 /* Resize the view when switching between split- and full-screen,
3287 * or when switching between two different full-screen views. */
3288 if (nviews
!= displayed_views() ||
3289 (nviews
== 1 && base_view
!= display
[0]))
3292 if (view
->ops
->open
) {
3294 end_update(view
, TRUE
);
3295 if (!view
->ops
->open(view
)) {
3296 report("Failed to load %s view", view
->name
);
3299 restore_view_position(view
);
3301 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3302 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3303 report("Failed to load %s view", view
->name
);
3307 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3308 /* Take the title line into account. */
3309 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3311 /* Scroll the view that was split if the current line is
3312 * outside the new limited view. */
3313 do_scroll_view(prev
, lines
);
3316 if (prev
&& view
!= prev
&& split
&& view_is_displayed(prev
)) {
3317 /* "Blur" the previous view. */
3318 update_view_title(prev
);
3321 if (view
->pipe
&& view
->lines
== 0) {
3322 /* Clear the old view and let the incremental updating refill
3325 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3327 } else if (view_is_displayed(view
)) {
3334 open_external_viewer(const char *argv
[], const char *dir
)
3336 def_prog_mode(); /* save current tty modes */
3337 endwin(); /* restore original tty modes */
3338 run_io_fg(argv
, dir
);
3339 fprintf(stderr
, "Press Enter to continue");
3342 redraw_display(TRUE
);
3346 open_mergetool(const char *file
)
3348 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3350 open_external_viewer(mergetool_argv
, opt_cdup
);
3354 open_editor(const char *file
)
3356 const char *editor_argv
[] = { "vi", file
, NULL
};
3359 editor
= getenv("GIT_EDITOR");
3360 if (!editor
&& *opt_editor
)
3361 editor
= opt_editor
;
3363 editor
= getenv("VISUAL");
3365 editor
= getenv("EDITOR");
3369 editor_argv
[0] = editor
;
3370 open_external_viewer(editor_argv
, opt_cdup
);
3374 open_run_request(enum request request
)
3376 struct run_request
*req
= get_run_request(request
);
3377 const char *argv
[ARRAY_SIZE(req
->argv
)] = { NULL
};
3380 report("Unknown run request");
3384 if (format_argv(argv
, req
->argv
, FORMAT_ALL
))
3385 open_external_viewer(argv
, NULL
);
3390 * User request switch noodle
3394 view_driver(struct view
*view
, enum request request
)
3398 if (request
== REQ_NONE
)
3401 if (request
> REQ_NONE
) {
3402 open_run_request(request
);
3403 /* FIXME: When all views can refresh always do this. */
3404 if (view
== VIEW(REQ_VIEW_STATUS
) ||
3405 view
== VIEW(REQ_VIEW_MAIN
) ||
3406 view
== VIEW(REQ_VIEW_LOG
) ||
3407 view
== VIEW(REQ_VIEW_BRANCH
) ||
3408 view
== VIEW(REQ_VIEW_STAGE
))
3409 request
= REQ_REFRESH
;
3414 if (view
&& view
->lines
) {
3415 request
= view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
3416 if (request
== REQ_NONE
)
3423 case REQ_MOVE_PAGE_UP
:
3424 case REQ_MOVE_PAGE_DOWN
:
3425 case REQ_MOVE_FIRST_LINE
:
3426 case REQ_MOVE_LAST_LINE
:
3427 move_view(view
, request
);
3430 case REQ_SCROLL_LEFT
:
3431 case REQ_SCROLL_RIGHT
:
3432 case REQ_SCROLL_LINE_DOWN
:
3433 case REQ_SCROLL_LINE_UP
:
3434 case REQ_SCROLL_PAGE_DOWN
:
3435 case REQ_SCROLL_PAGE_UP
:
3436 scroll_view(view
, request
);
3439 case REQ_VIEW_BLAME
:
3441 report("No file chosen, press %s to open tree view",
3442 get_key(view
->keymap
, REQ_VIEW_TREE
));
3445 open_view(view
, request
, OPEN_DEFAULT
);
3450 report("No file chosen, press %s to open tree view",
3451 get_key(view
->keymap
, REQ_VIEW_TREE
));
3454 open_view(view
, request
, OPEN_DEFAULT
);
3457 case REQ_VIEW_PAGER
:
3458 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3459 report("No pager content, press %s to run command from prompt",
3460 get_key(view
->keymap
, REQ_PROMPT
));
3463 open_view(view
, request
, OPEN_DEFAULT
);
3466 case REQ_VIEW_STAGE
:
3467 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3468 report("No stage content, press %s to open the status view and choose file",
3469 get_key(view
->keymap
, REQ_VIEW_STATUS
));
3472 open_view(view
, request
, OPEN_DEFAULT
);
3475 case REQ_VIEW_STATUS
:
3476 if (opt_is_inside_work_tree
== FALSE
) {
3477 report("The status view requires a working tree");
3480 open_view(view
, request
, OPEN_DEFAULT
);
3488 case REQ_VIEW_BRANCH
:
3489 open_view(view
, request
, OPEN_DEFAULT
);
3494 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3496 if ((view
== VIEW(REQ_VIEW_DIFF
) &&
3497 view
->parent
== VIEW(REQ_VIEW_MAIN
)) ||
3498 (view
== VIEW(REQ_VIEW_DIFF
) &&
3499 view
->parent
== VIEW(REQ_VIEW_BLAME
)) ||
3500 (view
== VIEW(REQ_VIEW_STAGE
) &&
3501 view
->parent
== VIEW(REQ_VIEW_STATUS
)) ||
3502 (view
== VIEW(REQ_VIEW_BLOB
) &&
3503 view
->parent
== VIEW(REQ_VIEW_TREE
)) ||
3504 (view
== VIEW(REQ_VIEW_MAIN
) &&
3505 view
->parent
== VIEW(REQ_VIEW_BRANCH
))) {
3508 view
= view
->parent
;
3509 line
= view
->lineno
;
3510 move_view(view
, request
);
3511 if (view_is_displayed(view
))
3512 update_view_title(view
);
3513 if (line
!= view
->lineno
)
3514 view
->ops
->request(view
, REQ_ENTER
,
3515 &view
->line
[view
->lineno
]);
3518 move_view(view
, request
);
3524 int nviews
= displayed_views();
3525 int next_view
= (current_view
+ 1) % nviews
;
3527 if (next_view
== current_view
) {
3528 report("Only one view is displayed");
3532 current_view
= next_view
;
3533 /* Blur out the title of the previous view. */
3534 update_view_title(view
);
3539 report("Refreshing is not yet supported for the %s view", view
->name
);
3543 if (displayed_views() == 2)
3544 maximize_view(view
);
3551 case REQ_TOGGLE_LINENO
:
3552 toggle_view_option(&opt_line_number
, "line numbers");
3555 case REQ_TOGGLE_DATE
:
3559 case REQ_TOGGLE_AUTHOR
:
3563 case REQ_TOGGLE_REV_GRAPH
:
3564 toggle_view_option(&opt_rev_graph
, "revision graph display");
3567 case REQ_TOGGLE_REFS
:
3568 toggle_view_option(&opt_show_refs
, "reference display");
3571 case REQ_TOGGLE_SORT_FIELD
:
3572 case REQ_TOGGLE_SORT_ORDER
:
3573 report("Sorting is not yet supported for the %s view", view
->name
);
3577 case REQ_SEARCH_BACK
:
3578 search_view(view
, request
);
3583 find_next(view
, request
);
3586 case REQ_STOP_LOADING
:
3587 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
3590 report("Stopped loading the %s view", view
->name
),
3591 end_update(view
, TRUE
);
3595 case REQ_SHOW_VERSION
:
3596 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3599 case REQ_SCREEN_REDRAW
:
3600 redraw_display(TRUE
);
3604 report("Nothing to edit");
3608 report("Nothing to enter");
3611 case REQ_VIEW_CLOSE
:
3612 /* XXX: Mark closed views by letting view->parent point to the
3613 * view itself. Parents to closed view should never be
3616 view
->parent
->parent
!= view
->parent
) {
3617 maximize_view(view
->parent
);
3618 view
->parent
= view
;
3626 report("Unknown key, press %s for help",
3627 get_key(view
->keymap
, REQ_VIEW_HELP
));
3636 * View backend utilities
3646 const enum sort_field
*fields
;
3647 size_t size
, current
;
3651 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3652 #define get_sort_field(state) ((state).fields[(state).current])
3653 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3656 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3657 int (*compare
)(const void *, const void *))
3660 case REQ_TOGGLE_SORT_FIELD
:
3661 state
->current
= (state
->current
+ 1) % state
->size
;
3664 case REQ_TOGGLE_SORT_ORDER
:
3665 state
->reverse
= !state
->reverse
;
3668 die("Not a sort request");
3671 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3675 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3677 /* Small author cache to reduce memory consumption. It uses binary
3678 * search to lookup or find place to position new entries. No entries
3679 * are ever freed. */
3681 get_author(const char *name
)
3683 static const char **authors
;
3684 static size_t authors_size
;
3685 int from
= 0, to
= authors_size
- 1;
3687 while (from
<= to
) {
3688 size_t pos
= (to
+ from
) / 2;
3689 int cmp
= strcmp(name
, authors
[pos
]);
3692 return authors
[pos
];
3700 if (!realloc_authors(&authors
, authors_size
, 1))
3702 name
= strdup(name
);
3706 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3707 authors
[from
] = name
;
3714 parse_timesec(struct time
*time
, const char *sec
)
3716 time
->sec
= (time_t) atol(sec
);
3720 parse_timezone(struct time
*time
, const char *zone
)
3724 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3725 tz
+= ('0' - zone
[2]) * 60 * 60;
3726 tz
+= ('0' - zone
[3]) * 60;
3727 tz
+= ('0' - zone
[4]);
3736 /* Parse author lines where the name may be empty:
3737 * author <email@address.tld> 1138474660 +0100
3740 parse_author_line(char *ident
, const char **author
, struct time
*time
)
3742 char *nameend
= strchr(ident
, '<');
3743 char *emailend
= strchr(ident
, '>');
3745 if (nameend
&& emailend
)
3746 *nameend
= *emailend
= 0;
3747 ident
= chomp_string(ident
);
3750 ident
= chomp_string(nameend
+ 1);
3755 *author
= get_author(ident
);
3757 /* Parse epoch and timezone */
3758 if (emailend
&& emailend
[1] == ' ') {
3759 char *secs
= emailend
+ 2;
3760 char *zone
= strchr(secs
, ' ');
3762 parse_timesec(time
, secs
);
3764 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
3765 parse_timezone(time
, zone
+ 1);
3770 open_commit_parent_menu(char buf
[SIZEOF_STR
], int *parents
)
3772 char rev
[SIZEOF_REV
];
3773 const char *revlist_argv
[] = {
3774 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev
, NULL
3776 struct menu_item
*items
;
3777 char text
[SIZEOF_STR
];
3781 items
= calloc(*parents
+ 1, sizeof(*items
));
3785 for (i
= 0; i
< *parents
; i
++) {
3786 string_copy_rev(rev
, &buf
[SIZEOF_REV
* i
]);
3787 if (!run_io_buf(revlist_argv
, text
, sizeof(text
)) ||
3788 !(items
[i
].text
= strdup(text
))) {
3796 ok
= prompt_menu("Select parent", items
, parents
);
3798 for (i
= 0; items
[i
].text
; i
++)
3799 free((char *) items
[i
].text
);
3805 select_commit_parent(const char *id
, char rev
[SIZEOF_REV
], const char *path
)
3807 char buf
[SIZEOF_STR
* 4];
3808 const char *revlist_argv
[] = {
3809 "git", "log", "--no-color", "-1",
3810 "--pretty=format:%P", id
, "--", path
, NULL
3814 if (!run_io_buf(revlist_argv
, buf
, sizeof(buf
)) ||
3815 (parents
= strlen(buf
) / 40) < 0) {
3816 report("Failed to get parent information");
3819 } else if (parents
== 0) {
3821 report("Path '%s' does not exist in the parent", path
);
3823 report("The selected commit has no parents");
3827 if (parents
> 1 && !open_commit_parent_menu(buf
, &parents
))
3830 string_copy_rev(rev
, &buf
[41 * parents
]);
3839 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
3841 char text
[SIZEOF_STR
];
3843 if (opt_line_number
&& draw_lineno(view
, lineno
))
3846 string_expand(text
, sizeof(text
), line
->data
, opt_tab_size
);
3847 draw_text(view
, line
->type
, text
, TRUE
);
3852 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
3854 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
3855 char ref
[SIZEOF_STR
];
3857 if (!run_io_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
3860 /* This is the only fatal call, since it can "corrupt" the buffer. */
3861 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
3868 add_pager_refs(struct view
*view
, struct line
*line
)
3870 char buf
[SIZEOF_STR
];
3871 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
3872 struct ref_list
*list
;
3873 size_t bufpos
= 0, i
;
3874 const char *sep
= "Refs: ";
3875 bool is_tag
= FALSE
;
3877 assert(line
->type
== LINE_COMMIT
);
3879 list
= get_ref_list(commit_id
);
3881 if (view
== VIEW(REQ_VIEW_DIFF
))
3882 goto try_add_describe_ref
;
3886 for (i
= 0; i
< list
->size
; i
++) {
3887 struct ref
*ref
= list
->refs
[i
];
3888 const char *fmt
= ref
->tag
? "%s[%s]" :
3889 ref
->remote
? "%s<%s>" : "%s%s";
3891 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
3898 if (!is_tag
&& view
== VIEW(REQ_VIEW_DIFF
)) {
3899 try_add_describe_ref
:
3900 /* Add <tag>-g<commit_id> "fake" reference. */
3901 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
3908 add_line_text(view
, buf
, LINE_PP_REFS
);
3912 pager_read(struct view
*view
, char *data
)
3919 line
= add_line_text(view
, data
, get_line_type(data
));
3923 if (line
->type
== LINE_COMMIT
&&
3924 (view
== VIEW(REQ_VIEW_DIFF
) ||
3925 view
== VIEW(REQ_VIEW_LOG
)))
3926 add_pager_refs(view
, line
);
3932 pager_request(struct view
*view
, enum request request
, struct line
*line
)
3936 if (request
!= REQ_ENTER
)
3939 if (line
->type
== LINE_COMMIT
&&
3940 (view
== VIEW(REQ_VIEW_LOG
) ||
3941 view
== VIEW(REQ_VIEW_PAGER
))) {
3942 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
3946 /* Always scroll the view even if it was split. That way
3947 * you can use Enter to scroll through the log view and
3948 * split open each commit diff. */
3949 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
3951 /* FIXME: A minor workaround. Scrolling the view will call report("")
3952 * but if we are scrolling a non-current view this won't properly
3953 * update the view title. */
3955 update_view_title(view
);
3961 pager_grep(struct view
*view
, struct line
*line
)
3963 const char *text
[] = { line
->data
, NULL
};
3965 return grep_text(view
, text
);
3969 pager_select(struct view
*view
, struct line
*line
)
3971 if (line
->type
== LINE_COMMIT
) {
3972 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
3974 if (view
!= VIEW(REQ_VIEW_PAGER
))
3975 string_copy_rev(view
->ref
, text
);
3976 string_copy_rev(ref_commit
, text
);
3980 static struct view_ops pager_ops
= {
3991 static const char *log_argv
[SIZEOF_ARG
] = {
3992 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3996 log_request(struct view
*view
, enum request request
, struct line
*line
)
4001 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
4004 return pager_request(view
, request
, line
);
4008 static struct view_ops log_ops
= {
4019 static const char *diff_argv
[SIZEOF_ARG
] = {
4020 "git", "show", "--pretty=fuller", "--no-color", "--root",
4021 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4024 static struct view_ops diff_ops
= {
4039 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
4042 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
4046 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
4047 help_keymap_hidden
[keymap
] ? '+' : '-',
4048 enum_name(keymap_table
[keymap
]));
4050 line
->other
= keymap
;
4052 return help_keymap_hidden
[keymap
];
4056 help_open_keymap(struct view
*view
, enum keymap keymap
)
4058 const char *group
= NULL
;
4059 char buf
[SIZEOF_STR
];
4061 bool add_title
= TRUE
;
4064 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
4065 const char *key
= NULL
;
4067 if (req_info
[i
].request
== REQ_NONE
)
4070 if (!req_info
[i
].request
) {
4071 group
= req_info
[i
].help
;
4075 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4079 if (add_title
&& help_open_keymap_title(view
, keymap
))
4084 add_line_text(view
, group
, LINE_HELP_GROUP
);
4088 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4089 enum_name(req_info
[i
]), req_info
[i
].help
);
4092 group
= "External commands:";
4094 for (i
= 0; i
< run_requests
; i
++) {
4095 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4099 if (!req
|| req
->keymap
!= keymap
)
4102 key
= get_key_name(req
->key
);
4104 key
= "(no key defined)";
4106 if (add_title
&& help_open_keymap_title(view
, keymap
))
4109 add_line_text(view
, group
, LINE_HELP_GROUP
);
4113 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4114 if (!string_format_from(buf
, &bufpos
, "%s%s",
4115 argc
? " " : "", req
->argv
[argc
]))
4118 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4123 help_open(struct view
*view
)
4128 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4129 add_line_text(view
, "", LINE_DEFAULT
);
4131 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4132 help_open_keymap(view
, keymap
);
4138 help_request(struct view
*view
, enum request request
, struct line
*line
)
4142 if (line
->type
== LINE_HELP_KEYMAP
) {
4143 help_keymap_hidden
[line
->other
] =
4144 !help_keymap_hidden
[line
->other
];
4145 view
->p_restore
= TRUE
;
4146 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4151 return pager_request(view
, request
, line
);
4155 static struct view_ops help_ops
= {
4171 struct tree_stack_entry
{
4172 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4173 unsigned long lineno
; /* Line number to restore */
4174 char *name
; /* Position of name in opt_path */
4177 /* The top of the path stack. */
4178 static struct tree_stack_entry
*tree_stack
= NULL
;
4179 unsigned long tree_lineno
= 0;
4182 pop_tree_stack_entry(void)
4184 struct tree_stack_entry
*entry
= tree_stack
;
4186 tree_lineno
= entry
->lineno
;
4188 tree_stack
= entry
->prev
;
4193 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4195 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4196 size_t pathlen
= strlen(opt_path
);
4201 entry
->prev
= tree_stack
;
4202 entry
->name
= opt_path
+ pathlen
;
4205 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4206 pop_tree_stack_entry();
4210 /* Move the current line to the first tree entry. */
4212 entry
->lineno
= lineno
;
4215 /* Parse output from git-ls-tree(1):
4217 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4220 #define SIZEOF_TREE_ATTR \
4221 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4223 #define SIZEOF_TREE_MODE \
4224 STRING_SIZE("100644 ")
4226 #define TREE_ID_OFFSET \
4227 STRING_SIZE("100644 blob ")
4230 char id
[SIZEOF_REV
];
4232 struct time time
; /* Date from the author ident. */
4233 const char *author
; /* Author of the commit. */
4238 tree_path(const struct line
*line
)
4240 return ((struct tree_entry
*) line
->data
)->name
;
4244 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4246 if (line1
->type
!= line2
->type
)
4247 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4248 return strcmp(tree_path(line1
), tree_path(line2
));
4251 static const enum sort_field tree_sort_fields
[] = {
4252 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4254 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4257 tree_compare(const void *l1
, const void *l2
)
4259 const struct line
*line1
= (const struct line
*) l1
;
4260 const struct line
*line2
= (const struct line
*) l2
;
4261 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4262 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4264 if (line1
->type
== LINE_TREE_HEAD
)
4266 if (line2
->type
== LINE_TREE_HEAD
)
4269 switch (get_sort_field(tree_sort_state
)) {
4271 return sort_order(tree_sort_state
, timecmp(&entry1
->time
, &entry2
->time
));
4273 case ORDERBY_AUTHOR
:
4274 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4278 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4283 static struct line
*
4284 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4285 const char *mode
, const char *id
)
4287 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4288 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4290 if (!entry
|| !line
) {
4295 strncpy(entry
->name
, path
, strlen(path
));
4297 entry
->mode
= strtoul(mode
, NULL
, 8);
4299 string_copy_rev(entry
->id
, id
);
4305 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4307 static const char *author_name
;
4308 static struct time author_time
;
4310 if (!text
&& *read_date
) {
4315 char *path
= *opt_path
? opt_path
: ".";
4316 /* Find next entry to process */
4317 const char *log_file
[] = {
4318 "git", "log", "--no-color", "--pretty=raw",
4319 "--cc", "--raw", view
->id
, "--", path
, NULL
4324 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4325 report("Tree is empty");
4329 if (!run_io_rd(&io
, log_file
, opt_cdup
, FORMAT_NONE
)) {
4330 report("Failed to load tree data");
4334 done_io(view
->pipe
);
4339 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4340 parse_author_line(text
+ STRING_SIZE("author "),
4341 &author_name
, &author_time
);
4343 } else if (*text
== ':') {
4345 size_t annotated
= 1;
4348 pos
= strchr(text
, '\t');
4352 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4353 text
+= strlen(opt_path
);
4354 pos
= strchr(text
, '/');
4358 for (i
= 1; i
< view
->lines
; i
++) {
4359 struct line
*line
= &view
->line
[i
];
4360 struct tree_entry
*entry
= line
->data
;
4362 annotated
+= !!entry
->author
;
4363 if (entry
->author
|| strcmp(entry
->name
, text
))
4366 entry
->author
= author_name
;
4367 entry
->time
= author_time
;
4372 if (annotated
== view
->lines
)
4373 kill_io(view
->pipe
);
4379 tree_read(struct view
*view
, char *text
)
4381 static bool read_date
= FALSE
;
4382 struct tree_entry
*data
;
4383 struct line
*entry
, *line
;
4384 enum line_type type
;
4385 size_t textlen
= text
? strlen(text
) : 0;
4386 char *path
= text
+ SIZEOF_TREE_ATTR
;
4388 if (read_date
|| !text
)
4389 return tree_read_date(view
, text
, &read_date
);
4391 if (textlen
<= SIZEOF_TREE_ATTR
)
4393 if (view
->lines
== 0 &&
4394 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4397 /* Strip the path part ... */
4399 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4400 size_t striplen
= strlen(opt_path
);
4402 if (pathlen
> striplen
)
4403 memmove(path
, path
+ striplen
,
4404 pathlen
- striplen
+ 1);
4406 /* Insert "link" to parent directory. */
4407 if (view
->lines
== 1 &&
4408 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4412 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4413 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4418 /* Skip "Directory ..." and ".." line. */
4419 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4420 if (tree_compare_entry(line
, entry
) <= 0)
4423 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4427 for (; line
<= entry
; line
++)
4428 line
->dirty
= line
->cleareol
= 1;
4432 if (tree_lineno
> view
->lineno
) {
4433 view
->lineno
= tree_lineno
;
4441 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4443 struct tree_entry
*entry
= line
->data
;
4445 if (line
->type
== LINE_TREE_HEAD
) {
4446 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4449 if (draw_mode(view
, entry
->mode
))
4452 if (opt_author
&& draw_author(view
, entry
->author
))
4455 if (opt_date
&& draw_date(view
, &entry
->time
))
4458 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4466 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4467 int fd
= mkstemp(file
);
4470 report("Failed to create temporary file");
4471 else if (!run_io_append(blob_ops
.argv
, FORMAT_ALL
, fd
))
4472 report("Failed to save blob data to file");
4480 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4482 enum open_flags flags
;
4485 case REQ_VIEW_BLAME
:
4486 if (line
->type
!= LINE_TREE_FILE
) {
4487 report("Blame only supported for files");
4491 string_copy(opt_ref
, view
->vid
);
4495 if (line
->type
!= LINE_TREE_FILE
) {
4496 report("Edit only supported for files");
4497 } else if (!is_head_commit(view
->vid
)) {
4500 open_editor(opt_file
);
4504 case REQ_TOGGLE_SORT_FIELD
:
4505 case REQ_TOGGLE_SORT_ORDER
:
4506 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4511 /* quit view if at top of tree */
4512 return REQ_VIEW_CLOSE
;
4515 line
= &view
->line
[1];
4525 /* Cleanup the stack if the tree view is at a different tree. */
4526 while (!*opt_path
&& tree_stack
)
4527 pop_tree_stack_entry();
4529 switch (line
->type
) {
4531 /* Depending on whether it is a subdirectory or parent link
4532 * mangle the path buffer. */
4533 if (line
== &view
->line
[1] && *opt_path
) {
4534 pop_tree_stack_entry();
4537 const char *basename
= tree_path(line
);
4539 push_tree_stack_entry(basename
, view
->lineno
);
4542 /* Trees and subtrees share the same ID, so they are not not
4543 * unique like blobs. */
4544 flags
= OPEN_RELOAD
;
4545 request
= REQ_VIEW_TREE
;
4548 case LINE_TREE_FILE
:
4549 flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4550 request
= REQ_VIEW_BLOB
;
4557 open_view(view
, request
, flags
);
4558 if (request
== REQ_VIEW_TREE
)
4559 view
->lineno
= tree_lineno
;
4565 tree_grep(struct view
*view
, struct line
*line
)
4567 struct tree_entry
*entry
= line
->data
;
4568 const char *text
[] = {
4570 opt_author
? entry
->author
: "",
4571 opt_date
? mkdate(&entry
->time
) : "",
4575 return grep_text(view
, text
);
4579 tree_select(struct view
*view
, struct line
*line
)
4581 struct tree_entry
*entry
= line
->data
;
4583 if (line
->type
== LINE_TREE_FILE
) {
4584 string_copy_rev(ref_blob
, entry
->id
);
4585 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4587 } else if (line
->type
!= LINE_TREE_DIR
) {
4591 string_copy_rev(view
->ref
, entry
->id
);
4595 tree_prepare(struct view
*view
)
4597 if (view
->lines
== 0 && opt_prefix
[0]) {
4598 char *pos
= opt_prefix
;
4600 while (pos
&& *pos
) {
4601 char *end
= strchr(pos
, '/');
4605 push_tree_stack_entry(pos
, 0);
4613 } else if (strcmp(view
->vid
, view
->id
)) {
4617 return init_io_rd(&view
->io
, view
->ops
->argv
, opt_cdup
, FORMAT_ALL
);
4620 static const char *tree_argv
[SIZEOF_ARG
] = {
4621 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4624 static struct view_ops tree_ops
= {
4637 blob_read(struct view
*view
, char *line
)
4641 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4645 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4652 return pager_request(view
, request
, line
);
4656 static const char *blob_argv
[SIZEOF_ARG
] = {
4657 "git", "cat-file", "blob", "%(blob)", NULL
4660 static struct view_ops blob_ops
= {
4674 * Loading the blame view is a two phase job:
4676 * 1. File content is read either using opt_file from the
4677 * filesystem or using git-cat-file.
4678 * 2. Then blame information is incrementally added by
4679 * reading output from git-blame.
4682 static const char *blame_head_argv
[] = {
4683 "git", "blame", "--incremental", "--", "%(file)", NULL
4686 static const char *blame_ref_argv
[] = {
4687 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4690 static const char *blame_cat_file_argv
[] = {
4691 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4694 struct blame_commit
{
4695 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4696 char title
[128]; /* First line of the commit message. */
4697 const char *author
; /* Author of the commit. */
4698 struct time time
; /* Date from the author ident. */
4699 char filename
[128]; /* Name of file. */
4700 bool has_previous
; /* Was a "previous" line detected. */
4704 struct blame_commit
*commit
;
4705 unsigned long lineno
;
4710 blame_open(struct view
*view
)
4712 char path
[SIZEOF_STR
];
4714 if (!view
->parent
&& *opt_prefix
) {
4715 string_copy(path
, opt_file
);
4716 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4720 if (*opt_ref
|| !io_open(&view
->io
, "%s%s", opt_cdup
, opt_file
)) {
4721 if (!run_io_rd(&view
->io
, blame_cat_file_argv
, opt_cdup
, FORMAT_ALL
))
4725 setup_update(view
, opt_file
);
4726 string_format(view
->ref
, "%s ...", opt_file
);
4731 static struct blame_commit
*
4732 get_blame_commit(struct view
*view
, const char *id
)
4736 for (i
= 0; i
< view
->lines
; i
++) {
4737 struct blame
*blame
= view
->line
[i
].data
;
4742 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4743 return blame
->commit
;
4747 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4750 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4756 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4758 const char *pos
= *posref
;
4761 pos
= strchr(pos
+ 1, ' ');
4762 if (!pos
|| !isdigit(pos
[1]))
4764 *number
= atoi(pos
+ 1);
4765 if (*number
< min
|| *number
> max
)
4772 static struct blame_commit
*
4773 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4775 struct blame_commit
*commit
;
4776 struct blame
*blame
;
4777 const char *pos
= text
+ SIZEOF_REV
- 2;
4778 size_t orig_lineno
= 0;
4782 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4785 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4786 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4787 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4790 commit
= get_blame_commit(view
, text
);
4796 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4799 blame
->commit
= commit
;
4800 blame
->lineno
= orig_lineno
+ group
- 1;
4808 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4811 const char **argv
= *opt_ref
? blame_ref_argv
: blame_head_argv
;
4814 if (view
->lines
== 0 && !view
->parent
)
4815 die("No blame exist for %s", view
->vid
);
4817 if (view
->lines
== 0 || !run_io_rd(&io
, argv
, opt_cdup
, FORMAT_ALL
)) {
4818 report("Failed to load blame data");
4822 done_io(view
->pipe
);
4828 size_t linelen
= strlen(line
);
4829 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
4834 blame
->commit
= NULL
;
4835 strncpy(blame
->text
, line
, linelen
);
4836 blame
->text
[linelen
] = 0;
4837 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
4842 match_blame_header(const char *name
, char **line
)
4844 size_t namelen
= strlen(name
);
4845 bool matched
= !strncmp(name
, *line
, namelen
);
4854 blame_read(struct view
*view
, char *line
)
4856 static struct blame_commit
*commit
= NULL
;
4857 static int blamed
= 0;
4858 static bool read_file
= TRUE
;
4861 return blame_read_file(view
, line
, &read_file
);
4868 string_format(view
->ref
, "%s", view
->vid
);
4869 if (view_is_displayed(view
)) {
4870 update_view_title(view
);
4871 redraw_view_from(view
, 0);
4877 commit
= parse_blame_commit(view
, line
, &blamed
);
4878 string_format(view
->ref
, "%s %2d%%", view
->vid
,
4879 view
->lines
? blamed
* 100 / view
->lines
: 0);
4881 } else if (match_blame_header("author ", &line
)) {
4882 commit
->author
= get_author(line
);
4884 } else if (match_blame_header("author-time ", &line
)) {
4885 parse_timesec(&commit
->time
, line
);
4887 } else if (match_blame_header("author-tz ", &line
)) {
4888 parse_timezone(&commit
->time
, line
);
4890 } else if (match_blame_header("summary ", &line
)) {
4891 string_ncopy(commit
->title
, line
, strlen(line
));
4893 } else if (match_blame_header("previous ", &line
)) {
4894 commit
->has_previous
= TRUE
;
4896 } else if (match_blame_header("filename ", &line
)) {
4897 string_ncopy(commit
->filename
, line
, strlen(line
));
4905 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4907 struct blame
*blame
= line
->data
;
4908 struct time
*time
= NULL
;
4909 const char *id
= NULL
, *author
= NULL
;
4910 char text
[SIZEOF_STR
];
4912 if (blame
->commit
&& *blame
->commit
->filename
) {
4913 id
= blame
->commit
->id
;
4914 author
= blame
->commit
->author
;
4915 time
= &blame
->commit
->time
;
4918 if (opt_date
&& draw_date(view
, time
))
4921 if (opt_author
&& draw_author(view
, author
))
4924 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
4927 if (draw_lineno(view
, lineno
))
4930 string_expand(text
, sizeof(text
), blame
->text
, opt_tab_size
);
4931 draw_text(view
, LINE_DEFAULT
, text
, TRUE
);
4936 check_blame_commit(struct blame
*blame
, bool check_null_id
)
4939 report("Commit data not loaded yet");
4940 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4941 report("No commit exist for the selected line");
4948 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
4950 const char *diff_tree_argv
[] = {
4951 "git", "diff-tree", "-U0", blame
->commit
->id
,
4952 "--", blame
->commit
->filename
, NULL
4955 int parent_lineno
= -1;
4956 int blamed_lineno
= -1;
4959 if (!run_io(&io
, diff_tree_argv
, NULL
, IO_RD
))
4962 while ((line
= io_get(&io
, '\n', TRUE
))) {
4964 char *pos
= strchr(line
, '+');
4966 parent_lineno
= atoi(line
+ 4);
4968 blamed_lineno
= atoi(pos
+ 1);
4970 } else if (*line
== '+' && parent_lineno
!= -1) {
4971 if (blame
->lineno
== blamed_lineno
- 1 &&
4972 !strcmp(blame
->text
, line
+ 1)) {
4973 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
4984 blame_request(struct view
*view
, enum request request
, struct line
*line
)
4986 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4987 struct blame
*blame
= line
->data
;
4990 case REQ_VIEW_BLAME
:
4991 if (check_blame_commit(blame
, TRUE
)) {
4992 string_copy(opt_ref
, blame
->commit
->id
);
4993 string_copy(opt_file
, blame
->commit
->filename
);
4995 view
->lineno
= blame
->lineno
;
4996 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5001 if (check_blame_commit(blame
, TRUE
) &&
5002 select_commit_parent(blame
->commit
->id
, opt_ref
,
5003 blame
->commit
->filename
)) {
5004 string_copy(opt_file
, blame
->commit
->filename
);
5005 setup_blame_parent_line(view
, blame
);
5006 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5011 if (!check_blame_commit(blame
, FALSE
))
5014 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
5015 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
5018 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
5019 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
5020 const char *diff_index_argv
[] = {
5021 "git", "diff-index", "--root", "--patch-with-stat",
5022 "-C", "-M", "HEAD", "--", view
->vid
, NULL
5025 if (!blame
->commit
->has_previous
) {
5026 diff_index_argv
[1] = "diff";
5027 diff_index_argv
[2] = "--no-color";
5028 diff_index_argv
[6] = "--";
5029 diff_index_argv
[7] = "/dev/null";
5032 if (!prepare_update(diff
, diff_index_argv
, NULL
, FORMAT_DASH
)) {
5033 report("Failed to allocate diff command");
5036 flags
|= OPEN_PREPARED
;
5039 open_view(view
, REQ_VIEW_DIFF
, flags
);
5040 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5041 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
5052 blame_grep(struct view
*view
, struct line
*line
)
5054 struct blame
*blame
= line
->data
;
5055 struct blame_commit
*commit
= blame
->commit
;
5056 const char *text
[] = {
5058 commit
? commit
->title
: "",
5059 commit
? commit
->id
: "",
5060 commit
&& opt_author
? commit
->author
: "",
5061 commit
&& opt_date
? mkdate(&commit
->time
) : "",
5065 return grep_text(view
, text
);
5069 blame_select(struct view
*view
, struct line
*line
)
5071 struct blame
*blame
= line
->data
;
5072 struct blame_commit
*commit
= blame
->commit
;
5077 if (!strcmp(commit
->id
, NULL_ID
))
5078 string_ncopy(ref_commit
, "HEAD", 4);
5080 string_copy_rev(ref_commit
, commit
->id
);
5083 static struct view_ops blame_ops
= {
5099 const char *author
; /* Author of the last commit. */
5100 struct time time
; /* Date of the last activity. */
5101 const struct ref
*ref
; /* Name and commit ID information. */
5104 static const struct ref branch_all
;
5106 static const enum sort_field branch_sort_fields
[] = {
5107 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5109 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5112 branch_compare(const void *l1
, const void *l2
)
5114 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5115 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5117 switch (get_sort_field(branch_sort_state
)) {
5119 return sort_order(branch_sort_state
, timecmp(&branch1
->time
, &branch2
->time
));
5121 case ORDERBY_AUTHOR
:
5122 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5126 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5131 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5133 struct branch
*branch
= line
->data
;
5134 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5136 if (opt_date
&& draw_date(view
, &branch
->time
))
5139 if (opt_author
&& draw_author(view
, branch
->author
))
5142 draw_text(view
, type
, branch
->ref
== &branch_all
? "All branches" : branch
->ref
->name
, TRUE
);
5147 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5149 struct branch
*branch
= line
->data
;
5154 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5157 case REQ_TOGGLE_SORT_FIELD
:
5158 case REQ_TOGGLE_SORT_ORDER
:
5159 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5163 if (branch
->ref
== &branch_all
) {
5164 const char *all_branches_argv
[] = {
5165 "git", "log", "--no-color", "--pretty=raw", "--parents",
5166 "--topo-order", "--all", NULL
5168 struct view
*main_view
= VIEW(REQ_VIEW_MAIN
);
5170 if (!prepare_update(main_view
, all_branches_argv
, NULL
, FORMAT_NONE
)) {
5171 report("Failed to load view of all branches");
5174 open_view(view
, REQ_VIEW_MAIN
, OPEN_PREPARED
| OPEN_SPLIT
);
5176 open_view(view
, REQ_VIEW_MAIN
, OPEN_SPLIT
);
5186 branch_read(struct view
*view
, char *line
)
5188 static char id
[SIZEOF_REV
];
5189 struct branch
*reference
;
5195 switch (get_line_type(line
)) {
5197 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5201 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5202 struct branch
*branch
= view
->line
[i
].data
;
5204 if (strcmp(branch
->ref
->id
, id
))
5207 view
->line
[i
].dirty
= TRUE
;
5209 branch
->author
= reference
->author
;
5210 branch
->time
= reference
->time
;
5214 parse_author_line(line
+ STRING_SIZE("author "),
5215 &branch
->author
, &branch
->time
);
5227 branch_open_visitor(void *data
, const struct ref
*ref
)
5229 struct view
*view
= data
;
5230 struct branch
*branch
;
5232 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5235 branch
= calloc(1, sizeof(*branch
));
5240 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5244 branch_open(struct view
*view
)
5246 const char *branch_log
[] = {
5247 "git", "log", "--no-color", "--pretty=raw",
5248 "--simplify-by-decoration", "--all", NULL
5251 if (!run_io_rd(&view
->io
, branch_log
, NULL
, FORMAT_NONE
)) {
5252 report("Failed to load branch data");
5256 setup_update(view
, view
->id
);
5257 branch_open_visitor(view
, &branch_all
);
5258 foreach_ref(branch_open_visitor
, view
);
5259 view
->p_restore
= TRUE
;
5265 branch_grep(struct view
*view
, struct line
*line
)
5267 struct branch
*branch
= line
->data
;
5268 const char *text
[] = {
5274 return grep_text(view
, text
);
5278 branch_select(struct view
*view
, struct line
*line
)
5280 struct branch
*branch
= line
->data
;
5282 string_copy_rev(view
->ref
, branch
->ref
->id
);
5283 string_copy_rev(ref_commit
, branch
->ref
->id
);
5284 string_copy_rev(ref_head
, branch
->ref
->id
);
5287 static struct view_ops branch_ops
= {
5306 char rev
[SIZEOF_REV
];
5307 char name
[SIZEOF_STR
];
5311 char rev
[SIZEOF_REV
];
5312 char name
[SIZEOF_STR
];
5316 static char status_onbranch
[SIZEOF_STR
];
5317 static struct status stage_status
;
5318 static enum line_type stage_line_type
;
5319 static size_t stage_chunks
;
5320 static int *stage_chunk
;
5322 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5324 /* This should work even for the "On branch" line. */
5326 status_has_none(struct view
*view
, struct line
*line
)
5328 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5331 /* Get fields from the diff line:
5332 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5335 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5337 const char *old_mode
= buf
+ 1;
5338 const char *new_mode
= buf
+ 8;
5339 const char *old_rev
= buf
+ 15;
5340 const char *new_rev
= buf
+ 56;
5341 const char *status
= buf
+ 97;
5344 old_mode
[-1] != ':' ||
5345 new_mode
[-1] != ' ' ||
5346 old_rev
[-1] != ' ' ||
5347 new_rev
[-1] != ' ' ||
5351 file
->status
= *status
;
5353 string_copy_rev(file
->old
.rev
, old_rev
);
5354 string_copy_rev(file
->new.rev
, new_rev
);
5356 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5357 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5359 file
->old
.name
[0] = file
->new.name
[0] = 0;
5365 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5367 struct status
*unmerged
= NULL
;
5371 if (!run_io(&io
, argv
, opt_cdup
, IO_RD
))
5374 add_line_data(view
, NULL
, type
);
5376 while ((buf
= io_get(&io
, 0, TRUE
))) {
5377 struct status
*file
= unmerged
;
5380 file
= calloc(1, sizeof(*file
));
5381 if (!file
|| !add_line_data(view
, file
, type
))
5385 /* Parse diff info part. */
5387 file
->status
= status
;
5389 string_copy(file
->old
.rev
, NULL_ID
);
5391 } else if (!file
->status
|| file
== unmerged
) {
5392 if (!status_get_diff(file
, buf
, strlen(buf
)))
5395 buf
= io_get(&io
, 0, TRUE
);
5399 /* Collapse all modified entries that follow an
5400 * associated unmerged entry. */
5401 if (unmerged
== file
) {
5402 unmerged
->status
= 'U';
5404 } else if (file
->status
== 'U') {
5409 /* Grab the old name for rename/copy. */
5410 if (!*file
->old
.name
&&
5411 (file
->status
== 'R' || file
->status
== 'C')) {
5412 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5414 buf
= io_get(&io
, 0, TRUE
);
5419 /* git-ls-files just delivers a NUL separated list of
5420 * file names similar to the second half of the
5421 * git-diff-* output. */
5422 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5423 if (!*file
->old
.name
)
5424 string_copy(file
->old
.name
, file
->new.name
);
5428 if (io_error(&io
)) {
5434 if (!view
->line
[view
->lines
- 1].data
)
5435 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5441 /* Don't show unmerged entries in the staged section. */
5442 static const char *status_diff_index_argv
[] = {
5443 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5444 "--cached", "-M", "HEAD", NULL
5447 static const char *status_diff_files_argv
[] = {
5448 "git", "diff-files", "-z", NULL
5451 static const char *status_list_other_argv
[] = {
5452 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix
, NULL
5455 static const char *status_list_no_head_argv
[] = {
5456 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5459 static const char *update_index_argv
[] = {
5460 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5463 /* Restore the previous line number to stay in the context or select a
5464 * line with something that can be updated. */
5466 status_restore(struct view
*view
)
5468 if (view
->p_lineno
>= view
->lines
)
5469 view
->p_lineno
= view
->lines
- 1;
5470 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5472 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5475 /* If the above fails, always skip the "On branch" line. */
5476 if (view
->p_lineno
< view
->lines
)
5477 view
->lineno
= view
->p_lineno
;
5481 if (view
->lineno
< view
->offset
)
5482 view
->offset
= view
->lineno
;
5483 else if (view
->offset
+ view
->height
<= view
->lineno
)
5484 view
->offset
= view
->lineno
- view
->height
+ 1;
5486 view
->p_restore
= FALSE
;
5490 status_update_onbranch(void)
5492 static const char *paths
[][2] = {
5493 { "rebase-apply/rebasing", "Rebasing" },
5494 { "rebase-apply/applying", "Applying mailbox" },
5495 { "rebase-apply/", "Rebasing mailbox" },
5496 { "rebase-merge/interactive", "Interactive rebase" },
5497 { "rebase-merge/", "Rebase merge" },
5498 { "MERGE_HEAD", "Merging" },
5499 { "BISECT_LOG", "Bisecting" },
5500 { "HEAD", "On branch" },
5502 char buf
[SIZEOF_STR
];
5506 if (is_initial_commit()) {
5507 string_copy(status_onbranch
, "Initial commit");
5511 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5512 char *head
= opt_head
;
5514 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5515 lstat(buf
, &stat
) < 0)
5521 if (io_open(&io
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5522 io_read_buf(&io
, buf
, sizeof(buf
))) {
5524 if (!prefixcmp(head
, "refs/heads/"))
5525 head
+= STRING_SIZE("refs/heads/");
5529 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5530 string_copy(status_onbranch
, opt_head
);
5534 string_copy(status_onbranch
, "Not currently on any branch");
5537 /* First parse staged info using git-diff-index(1), then parse unstaged
5538 * info using git-diff-files(1), and finally untracked files using
5539 * git-ls-files(1). */
5541 status_open(struct view
*view
)
5545 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5546 status_update_onbranch();
5548 run_io_bg(update_index_argv
);
5550 if (is_initial_commit()) {
5551 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5553 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5557 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5558 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5561 /* Restore the exact position or use the specialized restore
5563 if (!view
->p_restore
)
5564 status_restore(view
);
5569 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5571 struct status
*status
= line
->data
;
5572 enum line_type type
;
5576 switch (line
->type
) {
5577 case LINE_STAT_STAGED
:
5578 type
= LINE_STAT_SECTION
;
5579 text
= "Changes to be committed:";
5582 case LINE_STAT_UNSTAGED
:
5583 type
= LINE_STAT_SECTION
;
5584 text
= "Changed but not updated:";
5587 case LINE_STAT_UNTRACKED
:
5588 type
= LINE_STAT_SECTION
;
5589 text
= "Untracked files:";
5592 case LINE_STAT_NONE
:
5593 type
= LINE_DEFAULT
;
5594 text
= " (no files)";
5597 case LINE_STAT_HEAD
:
5598 type
= LINE_STAT_HEAD
;
5599 text
= status_onbranch
;
5606 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5608 buf
[0] = status
->status
;
5609 if (draw_text(view
, line
->type
, buf
, TRUE
))
5611 type
= LINE_DEFAULT
;
5612 text
= status
->new.name
;
5615 draw_text(view
, type
, text
, TRUE
);
5620 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5622 if (displayed_views() == 2 || display
[current_view
] != view
)
5623 maximize_view(view
);
5624 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5629 status_enter(struct view
*view
, struct line
*line
)
5631 struct status
*status
= line
->data
;
5632 const char *oldpath
= status
? status
->old
.name
: NULL
;
5633 /* Diffs for unmerged entries are empty when passing the new
5634 * path, so leave it empty. */
5635 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5637 enum open_flags split
;
5638 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5640 if (line
->type
== LINE_STAT_NONE
||
5641 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5642 report("No file to diff");
5646 switch (line
->type
) {
5647 case LINE_STAT_STAGED
:
5648 if (is_initial_commit()) {
5649 const char *no_head_diff_argv
[] = {
5650 "git", "diff", "--no-color", "--patch-with-stat",
5651 "--", "/dev/null", newpath
, NULL
5654 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
, FORMAT_DASH
))
5655 return status_load_error(view
, stage
, newpath
);
5657 const char *index_show_argv
[] = {
5658 "git", "diff-index", "--root", "--patch-with-stat",
5659 "-C", "-M", "--cached", "HEAD", "--",
5660 oldpath
, newpath
, NULL
5663 if (!prepare_update(stage
, index_show_argv
, opt_cdup
, FORMAT_DASH
))
5664 return status_load_error(view
, stage
, newpath
);
5668 info
= "Staged changes to %s";
5670 info
= "Staged changes";
5673 case LINE_STAT_UNSTAGED
:
5675 const char *files_show_argv
[] = {
5676 "git", "diff-files", "--root", "--patch-with-stat",
5677 "-C", "-M", "--", oldpath
, newpath
, NULL
5680 if (!prepare_update(stage
, files_show_argv
, opt_cdup
, FORMAT_DASH
))
5681 return status_load_error(view
, stage
, newpath
);
5683 info
= "Unstaged changes to %s";
5685 info
= "Unstaged changes";
5688 case LINE_STAT_UNTRACKED
:
5690 report("No file to show");
5694 if (!suffixcmp(status
->new.name
, -1, "/")) {
5695 report("Cannot display a directory");
5699 if (!prepare_update_file(stage
, newpath
))
5700 return status_load_error(view
, stage
, newpath
);
5701 info
= "Untracked file %s";
5704 case LINE_STAT_HEAD
:
5708 die("line type %d not handled in switch", line
->type
);
5711 split
= view_is_displayed(view
) ? OPEN_SPLIT
: 0;
5712 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5713 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5715 stage_status
= *status
;
5717 memset(&stage_status
, 0, sizeof(stage_status
));
5720 stage_line_type
= line
->type
;
5722 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5729 status_exists(struct status
*status
, enum line_type type
)
5731 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5732 unsigned long lineno
;
5734 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5735 struct line
*line
= &view
->line
[lineno
];
5736 struct status
*pos
= line
->data
;
5738 if (line
->type
!= type
)
5740 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5741 select_view_line(view
, lineno
);
5744 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5745 select_view_line(view
, lineno
);
5755 status_update_prepare(struct io
*io
, enum line_type type
)
5757 const char *staged_argv
[] = {
5758 "git", "update-index", "-z", "--index-info", NULL
5760 const char *others_argv
[] = {
5761 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5765 case LINE_STAT_STAGED
:
5766 return run_io(io
, staged_argv
, opt_cdup
, IO_WR
);
5768 case LINE_STAT_UNSTAGED
:
5769 case LINE_STAT_UNTRACKED
:
5770 return run_io(io
, others_argv
, opt_cdup
, IO_WR
);
5773 die("line type %d not handled in switch", type
);
5779 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5781 char buf
[SIZEOF_STR
];
5785 case LINE_STAT_STAGED
:
5786 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5789 status
->old
.name
, 0))
5793 case LINE_STAT_UNSTAGED
:
5794 case LINE_STAT_UNTRACKED
:
5795 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
5800 die("line type %d not handled in switch", type
);
5803 return io_write(io
, buf
, bufsize
);
5807 status_update_file(struct status
*status
, enum line_type type
)
5812 if (!status_update_prepare(&io
, type
))
5815 result
= status_update_write(&io
, status
, type
);
5816 return done_io(&io
) && result
;
5820 status_update_files(struct view
*view
, struct line
*line
)
5822 char buf
[sizeof(view
->ref
)];
5825 struct line
*pos
= view
->line
+ view
->lines
;
5828 int cursor_y
= -1, cursor_x
= -1;
5830 if (!status_update_prepare(&io
, line
->type
))
5833 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
5836 string_copy(buf
, view
->ref
);
5837 getsyx(cursor_y
, cursor_x
);
5838 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
5839 int almost_done
= file
* 100 / files
;
5841 if (almost_done
> done
) {
5843 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
5845 update_view_title(view
);
5846 setsyx(cursor_y
, cursor_x
);
5849 result
= status_update_write(&io
, line
->data
, line
->type
);
5851 string_copy(view
->ref
, buf
);
5853 return done_io(&io
) && result
;
5857 status_update(struct view
*view
)
5859 struct line
*line
= &view
->line
[view
->lineno
];
5861 assert(view
->lines
);
5864 /* This should work even for the "On branch" line. */
5865 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
5866 report("Nothing to update");
5870 if (!status_update_files(view
, line
+ 1)) {
5871 report("Failed to update file status");
5875 } else if (!status_update_file(line
->data
, line
->type
)) {
5876 report("Failed to update file status");
5884 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
5886 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
5887 if (type
== LINE_STAT_STAGED
) {
5888 report("Cannot revert changes to staged files");
5889 } else if (type
== LINE_STAT_UNTRACKED
) {
5890 report("Cannot revert changes to untracked files");
5891 } else if (has_none
) {
5892 report("Nothing to revert");
5894 report("Cannot revert changes to multiple files");
5897 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5898 char mode
[10] = "100644";
5899 const char *reset_argv
[] = {
5900 "git", "update-index", "--cacheinfo", mode
,
5901 status
->old
.rev
, status
->old
.name
, NULL
5903 const char *checkout_argv
[] = {
5904 "git", "checkout", "--", status
->old
.name
, NULL
5907 if (status
->status
== 'U') {
5908 string_format(mode
, "%5o", status
->old
.mode
);
5910 if (status
->old
.mode
== 0 && status
->new.mode
== 0) {
5911 reset_argv
[2] = "--force-remove";
5912 reset_argv
[3] = status
->old
.name
;
5913 reset_argv
[4] = NULL
;
5916 if (!run_io_fg(reset_argv
, opt_cdup
))
5918 if (status
->old
.mode
== 0 && status
->new.mode
== 0)
5922 return run_io_fg(checkout_argv
, opt_cdup
);
5929 status_request(struct view
*view
, enum request request
, struct line
*line
)
5931 struct status
*status
= line
->data
;
5934 case REQ_STATUS_UPDATE
:
5935 if (!status_update(view
))
5939 case REQ_STATUS_REVERT
:
5940 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
5944 case REQ_STATUS_MERGE
:
5945 if (!status
|| status
->status
!= 'U') {
5946 report("Merging only possible for files with unmerged status ('U').");
5949 open_mergetool(status
->new.name
);
5955 if (status
->status
== 'D') {
5956 report("File has been deleted.");
5960 open_editor(status
->new.name
);
5963 case REQ_VIEW_BLAME
:
5969 /* After returning the status view has been split to
5970 * show the stage view. No further reloading is
5972 return status_enter(view
, line
);
5975 /* Simply reload the view. */
5982 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
5988 status_select(struct view
*view
, struct line
*line
)
5990 struct status
*status
= line
->data
;
5991 char file
[SIZEOF_STR
] = "all files";
5995 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
5998 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
6001 switch (line
->type
) {
6002 case LINE_STAT_STAGED
:
6003 text
= "Press %s to unstage %s for commit";
6006 case LINE_STAT_UNSTAGED
:
6007 text
= "Press %s to stage %s for commit";
6010 case LINE_STAT_UNTRACKED
:
6011 text
= "Press %s to stage %s for addition";
6014 case LINE_STAT_HEAD
:
6015 case LINE_STAT_NONE
:
6016 text
= "Nothing to update";
6020 die("line type %d not handled in switch", line
->type
);
6023 if (status
&& status
->status
== 'U') {
6024 text
= "Press %s to resolve conflict in %s";
6025 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
6028 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
6031 string_format(view
->ref
, text
, key
, file
);
6033 string_copy(opt_file
, status
->new.name
);
6037 status_grep(struct view
*view
, struct line
*line
)
6039 struct status
*status
= line
->data
;
6042 const char buf
[2] = { status
->status
, 0 };
6043 const char *text
[] = { status
->new.name
, buf
, NULL
};
6045 return grep_text(view
, text
);
6051 static struct view_ops status_ops
= {
6064 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
6066 while (line
< end
) {
6067 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
6068 !io_write(io
, "\n", 1))
6071 if (line
->type
== LINE_DIFF_CHUNK
||
6072 line
->type
== LINE_DIFF_HEADER
)
6079 static struct line
*
6080 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
6082 for (; view
->line
< line
; line
--)
6083 if (line
->type
== type
)
6090 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
6092 const char *apply_argv
[SIZEOF_ARG
] = {
6093 "git", "apply", "--whitespace=nowarn", NULL
6095 struct line
*diff_hdr
;
6099 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6104 apply_argv
[argc
++] = "--cached";
6105 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6106 apply_argv
[argc
++] = "-R";
6107 apply_argv
[argc
++] = "-";
6108 apply_argv
[argc
++] = NULL
;
6109 if (!run_io(&io
, apply_argv
, opt_cdup
, IO_WR
))
6112 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6113 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6117 run_io_bg(update_index_argv
);
6119 return chunk
? TRUE
: FALSE
;
6123 stage_update(struct view
*view
, struct line
*line
)
6125 struct line
*chunk
= NULL
;
6127 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6128 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6131 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6132 report("Failed to apply chunk");
6136 } else if (!stage_status
.status
) {
6137 view
= VIEW(REQ_VIEW_STATUS
);
6139 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6140 if (line
->type
== stage_line_type
)
6143 if (!status_update_files(view
, line
+ 1)) {
6144 report("Failed to update files");
6148 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6149 report("Failed to update file");
6157 stage_revert(struct view
*view
, struct line
*line
)
6159 struct line
*chunk
= NULL
;
6161 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6162 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6165 if (!prompt_yesno("Are you sure you want to revert changes?"))
6168 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6169 report("Failed to revert chunk");
6175 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6176 stage_line_type
, FALSE
);
6182 stage_next(struct view
*view
, struct line
*line
)
6186 if (!stage_chunks
) {
6187 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6188 if (line
->type
!= LINE_DIFF_CHUNK
)
6191 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6192 report("Allocation failure");
6196 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6200 for (i
= 0; i
< stage_chunks
; i
++) {
6201 if (stage_chunk
[i
] > view
->lineno
) {
6202 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6203 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6208 report("No next chunk found");
6212 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6215 case REQ_STATUS_UPDATE
:
6216 if (!stage_update(view
, line
))
6220 case REQ_STATUS_REVERT
:
6221 if (!stage_revert(view
, line
))
6225 case REQ_STAGE_NEXT
:
6226 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6227 report("File is untracked; press %s to add",
6228 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6231 stage_next(view
, line
);
6235 if (!stage_status
.new.name
[0])
6237 if (stage_status
.status
== 'D') {
6238 report("File has been deleted.");
6242 open_editor(stage_status
.new.name
);
6246 /* Reload everything ... */
6249 case REQ_VIEW_BLAME
:
6250 if (stage_status
.new.name
[0]) {
6251 string_copy(opt_file
, stage_status
.new.name
);
6257 return pager_request(view
, request
, line
);
6263 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6264 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6266 /* Check whether the staged entry still exists, and close the
6267 * stage view if it doesn't. */
6268 if (!status_exists(&stage_status
, stage_line_type
)) {
6269 status_restore(VIEW(REQ_VIEW_STATUS
));
6270 return REQ_VIEW_CLOSE
;
6273 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6274 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6275 report("Cannot display a directory");
6279 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6280 report("Failed to open file: %s", strerror(errno
));
6284 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6289 static struct view_ops stage_ops
= {
6306 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6307 char title
[128]; /* First line of the commit message. */
6308 const char *author
; /* Author of the commit. */
6309 struct time time
; /* Date from the author ident. */
6310 struct ref_list
*refs
; /* Repository references. */
6311 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6312 size_t graph_size
; /* The width of the graph array. */
6313 bool has_parents
; /* Rewritten --parents seen. */
6316 /* Size of rev graph with no "padding" columns */
6317 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6320 struct rev_graph
*prev
, *next
, *parents
;
6321 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6323 struct commit
*commit
;
6325 unsigned int boundary
:1;
6328 /* Parents of the commit being visualized. */
6329 static struct rev_graph graph_parents
[4];
6331 /* The current stack of revisions on the graph. */
6332 static struct rev_graph graph_stacks
[4] = {
6333 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6334 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6335 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6336 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6340 graph_parent_is_merge(struct rev_graph
*graph
)
6342 return graph
->parents
->size
> 1;
6346 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6348 struct commit
*commit
= graph
->commit
;
6350 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6351 commit
->graph
[commit
->graph_size
++] = symbol
;
6355 clear_rev_graph(struct rev_graph
*graph
)
6357 graph
->boundary
= 0;
6358 graph
->size
= graph
->pos
= 0;
6359 graph
->commit
= NULL
;
6360 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6364 done_rev_graph(struct rev_graph
*graph
)
6366 if (graph_parent_is_merge(graph
) &&
6367 graph
->pos
< graph
->size
- 1 &&
6368 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6369 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6371 graph
->commit
->graph_size
= i
* 2;
6372 while (i
< graph
->next
->size
- 1) {
6373 append_to_rev_graph(graph
, ' ');
6374 append_to_rev_graph(graph
, '\\');
6379 clear_rev_graph(graph
);
6383 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6387 /* "Collapse" duplicate parents lines.
6389 * FIXME: This needs to also update update the drawn graph but
6390 * for now it just serves as a method for pruning graph lines. */
6391 for (i
= 0; i
< graph
->size
; i
++)
6392 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6395 if (graph
->size
< SIZEOF_REVITEMS
) {
6396 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6401 get_rev_graph_symbol(struct rev_graph
*graph
)
6405 if (graph
->boundary
)
6406 symbol
= REVGRAPH_BOUND
;
6407 else if (graph
->parents
->size
== 0)
6408 symbol
= REVGRAPH_INIT
;
6409 else if (graph_parent_is_merge(graph
))
6410 symbol
= REVGRAPH_MERGE
;
6411 else if (graph
->pos
>= graph
->size
)
6412 symbol
= REVGRAPH_BRANCH
;
6414 symbol
= REVGRAPH_COMMIT
;
6420 draw_rev_graph(struct rev_graph
*graph
)
6423 chtype separator
, line
;
6425 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6426 static struct rev_filler fillers
[] = {
6432 chtype symbol
= get_rev_graph_symbol(graph
);
6433 struct rev_filler
*filler
;
6436 fillers
[DEFAULT
].line
= opt_line_graphics
? ACS_VLINE
: '|';
6437 filler
= &fillers
[DEFAULT
];
6439 for (i
= 0; i
< graph
->pos
; i
++) {
6440 append_to_rev_graph(graph
, filler
->line
);
6441 if (graph_parent_is_merge(graph
->prev
) &&
6442 graph
->prev
->pos
== i
)
6443 filler
= &fillers
[RSHARP
];
6445 append_to_rev_graph(graph
, filler
->separator
);
6448 /* Place the symbol for this revision. */
6449 append_to_rev_graph(graph
, symbol
);
6451 if (graph
->prev
->size
> graph
->size
)
6452 filler
= &fillers
[RDIAG
];
6454 filler
= &fillers
[DEFAULT
];
6458 for (; i
< graph
->size
; i
++) {
6459 append_to_rev_graph(graph
, filler
->separator
);
6460 append_to_rev_graph(graph
, filler
->line
);
6461 if (graph_parent_is_merge(graph
->prev
) &&
6462 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6463 filler
= &fillers
[RSHARP
];
6464 if (graph
->prev
->size
> graph
->size
)
6465 filler
= &fillers
[LDIAG
];
6468 if (graph
->prev
->size
> graph
->size
) {
6469 append_to_rev_graph(graph
, filler
->separator
);
6470 if (filler
->line
!= ' ')
6471 append_to_rev_graph(graph
, filler
->line
);
6475 /* Prepare the next rev graph */
6477 prepare_rev_graph(struct rev_graph
*graph
)
6481 /* First, traverse all lines of revisions up to the active one. */
6482 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6483 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6486 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6489 /* Interleave the new revision parent(s). */
6490 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6491 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6493 /* Lastly, put any remaining revisions. */
6494 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6495 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6499 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6501 /* If this is the finalizing update ... */
6503 prepare_rev_graph(graph
);
6505 /* Graph visualization needs a one rev look-ahead,
6506 * so the first update doesn't visualize anything. */
6507 if (!graph
->prev
->commit
)
6510 if (view
->lines
> 2)
6511 view
->line
[view
->lines
- 3].dirty
= 1;
6512 if (view
->lines
> 1)
6513 view
->line
[view
->lines
- 2].dirty
= 1;
6514 draw_rev_graph(graph
->prev
);
6515 done_rev_graph(graph
->prev
->prev
);
6523 static const char *main_argv
[SIZEOF_ARG
] = {
6524 "git", "log", "--no-color", "--pretty=raw", "--parents",
6525 "--topo-order", "%(head)", NULL
6529 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6531 struct commit
*commit
= line
->data
;
6533 if (!commit
->author
)
6536 if (opt_date
&& draw_date(view
, &commit
->time
))
6539 if (opt_author
&& draw_author(view
, commit
->author
))
6542 if (opt_rev_graph
&& commit
->graph_size
&&
6543 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6546 if (opt_show_refs
&& commit
->refs
) {
6549 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6550 struct ref
*ref
= commit
->refs
->refs
[i
];
6551 enum line_type type
;
6554 type
= LINE_MAIN_HEAD
;
6556 type
= LINE_MAIN_LOCAL_TAG
;
6558 type
= LINE_MAIN_TAG
;
6559 else if (ref
->tracked
)
6560 type
= LINE_MAIN_TRACKED
;
6561 else if (ref
->remote
)
6562 type
= LINE_MAIN_REMOTE
;
6564 type
= LINE_MAIN_REF
;
6566 if (draw_text(view
, type
, "[", TRUE
) ||
6567 draw_text(view
, type
, ref
->name
, TRUE
) ||
6568 draw_text(view
, type
, "]", TRUE
))
6571 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6576 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6580 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6582 main_read(struct view
*view
, char *line
)
6584 static struct rev_graph
*graph
= graph_stacks
;
6585 enum line_type type
;
6586 struct commit
*commit
;
6591 if (!view
->lines
&& !view
->parent
)
6592 die("No revisions match the given arguments.");
6593 if (view
->lines
> 0) {
6594 commit
= view
->line
[view
->lines
- 1].data
;
6595 view
->line
[view
->lines
- 1].dirty
= 1;
6596 if (!commit
->author
) {
6599 graph
->commit
= NULL
;
6602 update_rev_graph(view
, graph
);
6604 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6605 clear_rev_graph(&graph_stacks
[i
]);
6609 type
= get_line_type(line
);
6610 if (type
== LINE_COMMIT
) {
6611 commit
= calloc(1, sizeof(struct commit
));
6615 line
+= STRING_SIZE("commit ");
6617 graph
->boundary
= 1;
6621 string_copy_rev(commit
->id
, line
);
6622 commit
->refs
= get_ref_list(commit
->id
);
6623 graph
->commit
= commit
;
6624 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6626 while ((line
= strchr(line
, ' '))) {
6628 push_rev_graph(graph
->parents
, line
);
6629 commit
->has_parents
= TRUE
;
6636 commit
= view
->line
[view
->lines
- 1].data
;
6640 if (commit
->has_parents
)
6642 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6646 parse_author_line(line
+ STRING_SIZE("author "),
6647 &commit
->author
, &commit
->time
);
6648 update_rev_graph(view
, graph
);
6649 graph
= graph
->next
;
6653 /* Fill in the commit title if it has not already been set. */
6654 if (commit
->title
[0])
6657 /* Require titles to start with a non-space character at the
6658 * offset used by git log. */
6659 if (strncmp(line
, " ", 4))
6662 /* Well, if the title starts with a whitespace character,
6663 * try to be forgiving. Otherwise we end up with no title. */
6664 while (isspace(*line
))
6668 /* FIXME: More graceful handling of titles; append "..." to
6669 * shortened titles, etc. */
6671 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6672 view
->line
[view
->lines
- 1].dirty
= 1;
6679 main_request(struct view
*view
, enum request request
, struct line
*line
)
6681 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
6685 open_view(view
, REQ_VIEW_DIFF
, flags
);
6689 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6699 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6704 if (!opt_show_refs
|| !list
)
6707 for (i
= 0; i
< list
->size
; i
++) {
6708 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6716 main_grep(struct view
*view
, struct line
*line
)
6718 struct commit
*commit
= line
->data
;
6719 const char *text
[] = {
6721 opt_author
? commit
->author
: "",
6722 opt_date
? mkdate(&commit
->time
) : "",
6726 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6730 main_select(struct view
*view
, struct line
*line
)
6732 struct commit
*commit
= line
->data
;
6734 string_copy_rev(view
->ref
, commit
->id
);
6735 string_copy_rev(ref_commit
, view
->ref
);
6738 static struct view_ops main_ops
= {
6751 * Unicode / UTF-8 handling
6753 * NOTE: Much of the following code for dealing with Unicode is derived from
6754 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6755 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6759 unicode_width(unsigned long c
)
6762 (c
<= 0x115f /* Hangul Jamo */
6765 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
6767 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
6768 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
6769 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
6770 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
6771 || (c
>= 0xffe0 && c
<= 0xffe6)
6772 || (c
>= 0x20000 && c
<= 0x2fffd)
6773 || (c
>= 0x30000 && c
<= 0x3fffd)))
6777 return opt_tab_size
;
6782 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6783 * Illegal bytes are set one. */
6784 static const unsigned char utf8_bytes
[256] = {
6785 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,
6786 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,
6787 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,
6788 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6789 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6790 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6791 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,
6792 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,
6795 static inline unsigned char
6796 utf8_char_length(const char *string
, const char *end
)
6798 int c
= *(unsigned char *) string
;
6800 return utf8_bytes
[c
];
6803 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6804 static inline unsigned long
6805 utf8_to_unicode(const char *string
, size_t length
)
6807 unsigned long unicode
;
6811 unicode
= string
[0];
6814 unicode
= (string
[0] & 0x1f) << 6;
6815 unicode
+= (string
[1] & 0x3f);
6818 unicode
= (string
[0] & 0x0f) << 12;
6819 unicode
+= ((string
[1] & 0x3f) << 6);
6820 unicode
+= (string
[2] & 0x3f);
6823 unicode
= (string
[0] & 0x0f) << 18;
6824 unicode
+= ((string
[1] & 0x3f) << 12);
6825 unicode
+= ((string
[2] & 0x3f) << 6);
6826 unicode
+= (string
[3] & 0x3f);
6829 unicode
= (string
[0] & 0x0f) << 24;
6830 unicode
+= ((string
[1] & 0x3f) << 18);
6831 unicode
+= ((string
[2] & 0x3f) << 12);
6832 unicode
+= ((string
[3] & 0x3f) << 6);
6833 unicode
+= (string
[4] & 0x3f);
6836 unicode
= (string
[0] & 0x01) << 30;
6837 unicode
+= ((string
[1] & 0x3f) << 24);
6838 unicode
+= ((string
[2] & 0x3f) << 18);
6839 unicode
+= ((string
[3] & 0x3f) << 12);
6840 unicode
+= ((string
[4] & 0x3f) << 6);
6841 unicode
+= (string
[5] & 0x3f);
6844 die("Invalid Unicode length");
6847 /* Invalid characters could return the special 0xfffd value but NUL
6848 * should be just as good. */
6849 return unicode
> 0xffff ? 0 : unicode
;
6852 /* Calculates how much of string can be shown within the given maximum width
6853 * and sets trimmed parameter to non-zero value if all of string could not be
6854 * shown. If the reserve flag is TRUE, it will reserve at least one
6855 * trailing character, which can be useful when drawing a delimiter.
6857 * Returns the number of bytes to output from string to satisfy max_width. */
6859 utf8_length(const char **start
, size_t skip
, int *width
, size_t max_width
, int *trimmed
, bool reserve
)
6861 const char *string
= *start
;
6862 const char *end
= strchr(string
, '\0');
6863 unsigned char last_bytes
= 0;
6864 size_t last_ucwidth
= 0;
6869 while (string
< end
) {
6870 unsigned char bytes
= utf8_char_length(string
, end
);
6872 unsigned long unicode
;
6874 if (string
+ bytes
> end
)
6877 /* Change representation to figure out whether
6878 * it is a single- or double-width character. */
6880 unicode
= utf8_to_unicode(string
, bytes
);
6881 /* FIXME: Graceful handling of invalid Unicode character. */
6885 ucwidth
= unicode_width(unicode
);
6887 skip
-= ucwidth
<= skip
? ucwidth
: skip
;
6891 if (*width
> max_width
) {
6894 if (reserve
&& *width
== max_width
) {
6895 string
-= last_bytes
;
6896 *width
-= last_ucwidth
;
6902 last_bytes
= ucwidth
? bytes
: 0;
6903 last_ucwidth
= ucwidth
;
6906 return string
- *start
;
6914 /* Whether or not the curses interface has been initialized. */
6915 static bool cursed
= FALSE
;
6917 /* Terminal hacks and workarounds. */
6918 static bool use_scroll_redrawwin
;
6919 static bool use_scroll_status_wclear
;
6921 /* The status window is used for polling keystrokes. */
6922 static WINDOW
*status_win
;
6924 /* Reading from the prompt? */
6925 static bool input_mode
= FALSE
;
6927 static bool status_empty
= FALSE
;
6929 /* Update status and title window. */
6931 report(const char *msg
, ...)
6933 struct view
*view
= display
[current_view
];
6939 char buf
[SIZEOF_STR
];
6942 va_start(args
, msg
);
6943 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6944 buf
[sizeof(buf
) - 1] = 0;
6945 buf
[sizeof(buf
) - 2] = '.';
6946 buf
[sizeof(buf
) - 3] = '.';
6947 buf
[sizeof(buf
) - 4] = '.';
6953 if (!status_empty
|| *msg
) {
6956 va_start(args
, msg
);
6958 wmove(status_win
, 0, 0);
6959 if (view
->has_scrolled
&& use_scroll_status_wclear
)
6962 vwprintw(status_win
, msg
, args
);
6963 status_empty
= FALSE
;
6965 status_empty
= TRUE
;
6967 wclrtoeol(status_win
);
6968 wnoutrefresh(status_win
);
6973 update_view_title(view
);
6976 /* Controls when nodelay should be in effect when polling user input. */
6978 set_nonblocking_input(bool loading
)
6980 static unsigned int loading_views
;
6982 if ((loading
== FALSE
&& loading_views
-- == 1) ||
6983 (loading
== TRUE
&& loading_views
++ == 0))
6984 nodelay(status_win
, loading
);
6993 /* Initialize the curses library */
6994 if (isatty(STDIN_FILENO
)) {
6995 cursed
= !!initscr();
6998 /* Leave stdin and stdout alone when acting as a pager. */
6999 opt_tty
= fopen("/dev/tty", "r+");
7001 die("Failed to open /dev/tty");
7002 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
7006 die("Failed to initialize curses");
7008 nonl(); /* Disable conversion and detect newlines from input. */
7009 cbreak(); /* Take input chars one at a time, no wait for \n */
7010 noecho(); /* Don't echo input */
7011 leaveok(stdscr
, FALSE
);
7016 getmaxyx(stdscr
, y
, x
);
7017 status_win
= newwin(1, 0, y
- 1, 0);
7019 die("Failed to create status window");
7021 /* Enable keyboard mapping */
7022 keypad(status_win
, TRUE
);
7023 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
7025 TABSIZE
= opt_tab_size
;
7027 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
7028 if (term
&& !strcmp(term
, "gnome-terminal")) {
7029 /* In the gnome-terminal-emulator, the message from
7030 * scrolling up one line when impossible followed by
7031 * scrolling down one line causes corruption of the
7032 * status line. This is fixed by calling wclear. */
7033 use_scroll_status_wclear
= TRUE
;
7034 use_scroll_redrawwin
= FALSE
;
7036 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
7037 /* No problems with full optimizations in xrvt-(unicode)
7039 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
7042 /* When scrolling in (u)xterm the last line in the
7043 * scrolling direction will update slowly. */
7044 use_scroll_redrawwin
= TRUE
;
7045 use_scroll_status_wclear
= FALSE
;
7050 get_input(int prompt_position
)
7053 int i
, key
, cursor_y
, cursor_x
;
7055 if (prompt_position
)
7059 foreach_view (view
, i
) {
7061 if (view_is_displayed(view
) && view
->has_scrolled
&&
7062 use_scroll_redrawwin
)
7063 redrawwin(view
->win
);
7064 view
->has_scrolled
= FALSE
;
7067 /* Update the cursor position. */
7068 if (prompt_position
) {
7069 getbegyx(status_win
, cursor_y
, cursor_x
);
7070 cursor_x
= prompt_position
;
7072 view
= display
[current_view
];
7073 getbegyx(view
->win
, cursor_y
, cursor_x
);
7074 cursor_x
= view
->width
- 1;
7075 cursor_y
+= view
->lineno
- view
->offset
;
7077 setsyx(cursor_y
, cursor_x
);
7079 /* Refresh, accept single keystroke of input */
7081 key
= wgetch(status_win
);
7083 /* wgetch() with nodelay() enabled returns ERR when
7084 * there's no input. */
7087 } else if (key
== KEY_RESIZE
) {
7090 getmaxyx(stdscr
, height
, width
);
7092 wresize(status_win
, 1, width
);
7093 mvwin(status_win
, height
- 1, 0);
7094 wnoutrefresh(status_win
);
7096 redraw_display(TRUE
);
7106 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7108 enum input_status status
= INPUT_OK
;
7109 static char buf
[SIZEOF_STR
];
7114 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7117 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7118 wclrtoeol(status_win
);
7120 key
= get_input(pos
+ 1);
7125 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7132 status
= INPUT_CANCEL
;
7136 status
= INPUT_CANCEL
;
7140 if (pos
>= sizeof(buf
)) {
7141 report("Input string too long");
7145 status
= handler(data
, buf
, key
);
7146 if (status
== INPUT_OK
)
7147 buf
[pos
++] = (char) key
;
7151 /* Clear the status window */
7152 status_empty
= FALSE
;
7155 if (status
== INPUT_CANCEL
)
7163 static enum input_status
7164 prompt_yesno_handler(void *data
, char *buf
, int c
)
7166 if (c
== 'y' || c
== 'Y')
7168 if (c
== 'n' || c
== 'N')
7169 return INPUT_CANCEL
;
7174 prompt_yesno(const char *prompt
)
7176 char prompt2
[SIZEOF_STR
];
7178 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7181 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7184 static enum input_status
7185 read_prompt_handler(void *data
, char *buf
, int c
)
7187 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7191 read_prompt(const char *prompt
)
7193 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7196 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7198 enum input_status status
= INPUT_OK
;
7201 while (items
[size
].text
)
7204 while (status
== INPUT_OK
) {
7205 const struct menu_item
*item
= &items
[*selected
];
7209 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7210 prompt
, *selected
+ 1, size
);
7212 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7213 wprintw(status_win
, "%s", item
->text
);
7214 wclrtoeol(status_win
);
7216 key
= get_input(COLS
- 1);
7221 status
= INPUT_STOP
;
7226 *selected
= *selected
- 1;
7228 *selected
= size
- 1;
7233 *selected
= (*selected
+ 1) % size
;
7237 status
= INPUT_CANCEL
;
7241 for (i
= 0; items
[i
].text
; i
++)
7242 if (items
[i
].hotkey
== key
) {
7244 status
= INPUT_STOP
;
7250 /* Clear the status window */
7251 status_empty
= FALSE
;
7254 return status
!= INPUT_CANCEL
;
7258 * Repository properties
7261 static struct ref
**refs
= NULL
;
7262 static size_t refs_size
= 0;
7264 static struct ref_list
**ref_lists
= NULL
;
7265 static size_t ref_lists_size
= 0;
7267 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7268 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7269 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7272 compare_refs(const void *ref1_
, const void *ref2_
)
7274 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7275 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7277 if (ref1
->tag
!= ref2
->tag
)
7278 return ref2
->tag
- ref1
->tag
;
7279 if (ref1
->ltag
!= ref2
->ltag
)
7280 return ref2
->ltag
- ref2
->ltag
;
7281 if (ref1
->head
!= ref2
->head
)
7282 return ref2
->head
- ref1
->head
;
7283 if (ref1
->tracked
!= ref2
->tracked
)
7284 return ref2
->tracked
- ref1
->tracked
;
7285 if (ref1
->remote
!= ref2
->remote
)
7286 return ref2
->remote
- ref1
->remote
;
7287 return strcmp(ref1
->name
, ref2
->name
);
7291 foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
)
7295 for (i
= 0; i
< refs_size
; i
++)
7296 if (!visitor(data
, refs
[i
]))
7300 static struct ref_list
*
7301 get_ref_list(const char *id
)
7303 struct ref_list
*list
;
7306 for (i
= 0; i
< ref_lists_size
; i
++)
7307 if (!strcmp(id
, ref_lists
[i
]->id
))
7308 return ref_lists
[i
];
7310 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7312 list
= calloc(1, sizeof(*list
));
7316 for (i
= 0; i
< refs_size
; i
++) {
7317 if (!strcmp(id
, refs
[i
]->id
) &&
7318 realloc_refs_list(&list
->refs
, list
->size
, 1))
7319 list
->refs
[list
->size
++] = refs
[i
];
7327 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7328 ref_lists
[ref_lists_size
++] = list
;
7333 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
7335 struct ref
*ref
= NULL
;
7338 bool remote
= FALSE
;
7339 bool tracked
= FALSE
;
7341 int from
= 0, to
= refs_size
- 1;
7343 if (!prefixcmp(name
, "refs/tags/")) {
7344 if (!suffixcmp(name
, namelen
, "^{}")) {
7352 namelen
-= STRING_SIZE("refs/tags/");
7353 name
+= STRING_SIZE("refs/tags/");
7355 } else if (!prefixcmp(name
, "refs/remotes/")) {
7357 namelen
-= STRING_SIZE("refs/remotes/");
7358 name
+= STRING_SIZE("refs/remotes/");
7359 tracked
= !strcmp(opt_remote
, name
);
7361 } else if (!prefixcmp(name
, "refs/heads/")) {
7362 namelen
-= STRING_SIZE("refs/heads/");
7363 name
+= STRING_SIZE("refs/heads/");
7364 head
= !strncmp(opt_head
, name
, namelen
);
7366 } else if (!strcmp(name
, "HEAD")) {
7367 string_ncopy(opt_head_rev
, id
, idlen
);
7371 /* If we are reloading or it's an annotated tag, replace the
7372 * previous SHA1 with the resolved commit id; relies on the fact
7373 * git-ls-remote lists the commit id of an annotated tag right
7374 * before the commit id it points to. */
7375 while (from
<= to
) {
7376 size_t pos
= (to
+ from
) / 2;
7377 int cmp
= strcmp(name
, refs
[pos
]->name
);
7391 if (!realloc_refs(&refs
, refs_size
, 1))
7393 ref
= calloc(1, sizeof(*ref
) + namelen
);
7396 memmove(refs
+ from
+ 1, refs
+ from
,
7397 (refs_size
- from
) * sizeof(*refs
));
7399 strncpy(ref
->name
, name
, namelen
);
7406 ref
->remote
= remote
;
7407 ref
->tracked
= tracked
;
7408 string_copy_rev(ref
->id
, id
);
7416 const char *head_argv
[] = {
7417 "git", "symbolic-ref", "HEAD", NULL
7419 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7420 "git", "ls-remote", opt_git_dir
, NULL
7422 static bool init
= FALSE
;
7426 argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE");
7433 if (run_io_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7434 !prefixcmp(opt_head
, "refs/heads/")) {
7435 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7437 memmove(opt_head
, offset
, strlen(offset
) + 1);
7440 for (i
= 0; i
< refs_size
; i
++)
7443 if (run_io_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7446 /* Update the ref lists to reflect changes. */
7447 for (i
= 0; i
< ref_lists_size
; i
++) {
7448 struct ref_list
*list
= ref_lists
[i
];
7451 for (old
= new = 0; old
< list
->size
; old
++)
7452 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7453 list
->refs
[new++] = list
->refs
[old
];
7461 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7463 if (!strcmp(name
, ".remote")) {
7464 string_ncopy(opt_remote
, value
, valuelen
);
7466 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7467 size_t from
= strlen(opt_remote
);
7469 if (!prefixcmp(value
, "refs/heads/"))
7470 value
+= STRING_SIZE("refs/heads/");
7472 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7478 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7480 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7481 int argc
= 1 + (cmd
== option_set_command
);
7484 if (!argv_from_string(argv
, &argc
, value
))
7485 config_msg
= "Too many option arguments";
7487 error
= cmd(argc
, argv
);
7490 warn("Option 'tig.%s': %s", name
, config_msg
);
7494 set_environment_variable(const char *name
, const char *value
)
7496 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7497 char *env
= malloc(len
);
7500 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7508 set_work_tree(const char *value
)
7510 char cwd
[SIZEOF_STR
];
7512 if (!getcwd(cwd
, sizeof(cwd
)))
7513 die("Failed to get cwd path: %s", strerror(errno
));
7514 if (chdir(opt_git_dir
) < 0)
7515 die("Failed to chdir(%s): %s", strerror(errno
));
7516 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7517 die("Failed to get git path: %s", strerror(errno
));
7519 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7520 if (chdir(value
) < 0)
7521 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7522 if (!getcwd(cwd
, sizeof(cwd
)))
7523 die("Failed to get cwd path: %s", strerror(errno
));
7524 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7525 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7526 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7527 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7528 opt_is_inside_work_tree
= TRUE
;
7532 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7534 if (!strcmp(name
, "i18n.commitencoding"))
7535 string_ncopy(opt_encoding
, value
, valuelen
);
7537 else if (!strcmp(name
, "core.editor"))
7538 string_ncopy(opt_editor
, value
, valuelen
);
7540 else if (!strcmp(name
, "core.worktree"))
7541 set_work_tree(value
);
7543 else if (!prefixcmp(name
, "tig.color."))
7544 set_repo_config_option(name
+ 10, value
, option_color_command
);
7546 else if (!prefixcmp(name
, "tig.bind."))
7547 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7549 else if (!prefixcmp(name
, "tig."))
7550 set_repo_config_option(name
+ 4, value
, option_set_command
);
7552 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7553 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7554 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7560 load_git_config(void)
7562 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7564 return run_io_load(config_list_argv
, "=", read_repo_config_option
);
7568 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7570 if (!opt_git_dir
[0]) {
7571 string_ncopy(opt_git_dir
, name
, namelen
);
7573 } else if (opt_is_inside_work_tree
== -1) {
7574 /* This can be 3 different values depending on the
7575 * version of git being used. If git-rev-parse does not
7576 * understand --is-inside-work-tree it will simply echo
7577 * the option else either "true" or "false" is printed.
7578 * Default to true for the unknown case. */
7579 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7581 } else if (*name
== '.') {
7582 string_ncopy(opt_cdup
, name
, namelen
);
7585 string_ncopy(opt_prefix
, name
, namelen
);
7592 load_repo_info(void)
7594 const char *rev_parse_argv
[] = {
7595 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7596 "--show-cdup", "--show-prefix", NULL
7599 return run_io_load(rev_parse_argv
, "=", read_repo_info
);
7607 static const char usage
[] =
7608 "tig " TIG_VERSION
" (" __DATE__
")\n"
7610 "Usage: tig [options] [revs] [--] [paths]\n"
7611 " or: tig show [options] [revs] [--] [paths]\n"
7612 " or: tig blame [rev] path\n"
7614 " or: tig < [git command output]\n"
7617 " -v, --version Show version and exit\n"
7618 " -h, --help Show help message and exit";
7620 static void __NORETURN
7623 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7629 static void __NORETURN
7630 die(const char *err
, ...)
7636 va_start(args
, err
);
7637 fputs("tig: ", stderr
);
7638 vfprintf(stderr
, err
, args
);
7639 fputs("\n", stderr
);
7646 warn(const char *msg
, ...)
7650 va_start(args
, msg
);
7651 fputs("tig warning: ", stderr
);
7652 vfprintf(stderr
, msg
, args
);
7653 fputs("\n", stderr
);
7658 parse_options(int argc
, const char *argv
[])
7660 enum request request
= REQ_VIEW_MAIN
;
7661 const char *subcommand
;
7662 bool seen_dashdash
= FALSE
;
7663 /* XXX: This is vulnerable to the user overriding options
7664 * required for the main view parser. */
7665 const char *custom_argv
[SIZEOF_ARG
] = {
7666 "git", "log", "--no-color", "--pretty=raw", "--parents",
7667 "--topo-order", NULL
7671 if (!isatty(STDIN_FILENO
)) {
7672 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7673 return REQ_VIEW_PAGER
;
7679 subcommand
= argv
[1];
7680 if (!strcmp(subcommand
, "status")) {
7682 warn("ignoring arguments after `%s'", subcommand
);
7683 return REQ_VIEW_STATUS
;
7685 } else if (!strcmp(subcommand
, "blame")) {
7686 if (argc
<= 2 || argc
> 4)
7687 die("invalid number of options to blame\n\n%s", usage
);
7691 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7695 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7696 return REQ_VIEW_BLAME
;
7698 } else if (!strcmp(subcommand
, "show")) {
7699 request
= REQ_VIEW_DIFF
;
7706 custom_argv
[1] = subcommand
;
7710 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7711 const char *opt
= argv
[i
];
7713 if (seen_dashdash
|| !strcmp(opt
, "--")) {
7714 seen_dashdash
= TRUE
;
7716 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7717 printf("tig version %s\n", TIG_VERSION
);
7720 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7721 printf("%s\n", usage
);
7725 custom_argv
[j
++] = opt
;
7726 if (j
>= ARRAY_SIZE(custom_argv
))
7727 die("command too long");
7730 if (!prepare_update(VIEW(request
), custom_argv
, NULL
, FORMAT_NONE
))
7731 die("Failed to format arguments");
7737 main(int argc
, const char *argv
[])
7739 enum request request
= parse_options(argc
, argv
);
7743 signal(SIGINT
, quit
);
7744 signal(SIGPIPE
, SIG_IGN
);
7746 if (setlocale(LC_ALL
, "")) {
7747 char *codeset
= nl_langinfo(CODESET
);
7749 string_ncopy(opt_codeset
, codeset
, strlen(codeset
));
7752 if (load_repo_info() == ERR
)
7753 die("Failed to load repo info.");
7755 if (load_options() == ERR
)
7756 die("Failed to load user config.");
7758 if (load_git_config() == ERR
)
7759 die("Failed to load repo config.");
7761 /* Require a git repository unless when running in pager mode. */
7762 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7763 die("Not a git repository");
7765 if (*opt_encoding
&& strcmp(opt_codeset
, "UTF-8")) {
7766 opt_iconv_in
= iconv_open("UTF-8", opt_encoding
);
7767 if (opt_iconv_in
== ICONV_NONE
)
7768 die("Failed to initialize character set conversion");
7771 if (*opt_codeset
&& strcmp(opt_codeset
, "UTF-8")) {
7772 opt_iconv_out
= iconv_open(opt_codeset
, "UTF-8");
7773 if (opt_iconv_out
== ICONV_NONE
)
7774 die("Failed to initialize character set conversion");
7777 if (load_refs() == ERR
)
7778 die("Failed to load refs.");
7780 foreach_view (view
, i
)
7781 argv_from_env(view
->ops
->argv
, view
->cmd_env
);
7785 if (request
!= REQ_NONE
)
7786 open_view(NULL
, request
, OPEN_PREPARED
);
7787 request
= request
== REQ_NONE
? REQ_VIEW_MAIN
: REQ_NONE
;
7789 while (view_driver(display
[current_view
], request
)) {
7790 int key
= get_input(0);
7792 view
= display
[current_view
];
7793 request
= get_keybinding(view
->keymap
, key
);
7795 /* Some low-level request handling. This keeps access to
7796 * status_win restricted. */
7800 char *cmd
= read_prompt(":");
7802 if (cmd
&& isdigit(*cmd
)) {
7803 int lineno
= view
->lineno
+ 1;
7805 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7806 select_view_line(view
, lineno
- 1);
7809 report("Unable to parse '%s' as a line number", cmd
);
7813 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7814 const char *argv
[SIZEOF_ARG
] = { "git" };
7817 /* When running random commands, initially show the
7818 * command in the title. However, it maybe later be
7819 * overwritten if a commit line is selected. */
7820 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7822 if (!argv_from_string(argv
, &argc
, cmd
)) {
7823 report("Too many arguments");
7824 } else if (!prepare_update(next
, argv
, NULL
, FORMAT_DASH
)) {
7825 report("Failed to format command");
7827 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7835 case REQ_SEARCH_BACK
:
7837 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7838 char *search
= read_prompt(prompt
);
7841 string_ncopy(opt_search
, search
, strlen(search
));
7842 else if (*opt_search
)
7843 request
= request
== REQ_SEARCH
?