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
, ...);
72 #define ABS(x) ((x) >= 0 ? (x) : -(x))
73 #define MIN(x, y) ((x) < (y) ? (x) : (y))
74 #define MAX(x, y) ((x) > (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
99 #define ICONV_CONST /* nothing */
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
108 #define AUTHOR_COLS 19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_CTL(x) ((x) & 0x1f) /* KEY_CTL(A) == ^A == \1 */
119 #define KEY_RETURN '\r'
124 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
125 unsigned int head
:1; /* Is it the current HEAD? */
126 unsigned int tag
:1; /* Is it a tag? */
127 unsigned int ltag
:1; /* If so, is the tag local? */
128 unsigned int remote
:1; /* Is it a remote ref? */
129 unsigned int tracked
:1; /* Is it the remote for the current HEAD? */
130 char name
[1]; /* Ref name; tag or head names are shortened. */
134 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
135 size_t size
; /* Number of refs. */
136 struct ref
**refs
; /* References for this ID. */
139 static struct ref
*get_ref_head();
140 static struct ref_list
*get_ref_list(const char *id
);
141 static void foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
);
142 static int load_refs(void);
151 typedef enum input_status (*input_handler
)(void *data
, char *buf
, int c
);
153 static char *prompt_input(const char *prompt
, input_handler handler
, void *data
);
154 static bool prompt_yesno(const char *prompt
);
162 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
);
165 * Allocation helpers ... Entering macro hell to never be seen again.
168 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
170 name(type **mem, size_t size, size_t increase) \
172 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
173 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
176 if (mem == NULL || num_chunks != num_chunks_new) { \
177 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
190 string_ncopy_do(char *dst
, size_t dstlen
, const char *src
, size_t srclen
)
192 if (srclen
> dstlen
- 1)
195 strncpy(dst
, src
, srclen
);
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205 string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
214 string_expand(char *dst
, size_t dstlen
, const char *src
, int tabsize
)
218 for (size
= pos
= 0; size
< dstlen
- 1 && src
[pos
]; pos
++) {
219 if (src
[pos
] == '\t') {
220 size_t expanded
= tabsize
- (size
% tabsize
);
222 if (expanded
+ size
>= dstlen
- 1)
223 expanded
= dstlen
- size
- 1;
224 memcpy(dst
+ size
, " ", expanded
);
227 dst
[size
++] = src
[pos
];
236 chomp_string(char *name
)
240 while (isspace(*name
))
243 namelen
= strlen(name
) - 1;
244 while (namelen
> 0 && isspace(name
[namelen
]))
251 string_nformat(char *buf
, size_t bufsize
, size_t *bufpos
, const char *fmt
, ...)
254 size_t pos
= bufpos
? *bufpos
: 0;
257 pos
+= vsnprintf(buf
+ pos
, bufsize
- pos
, fmt
, args
);
263 return pos
>= bufsize
? FALSE
: TRUE
;
266 #define string_format(buf, fmt, args...) \
267 string_nformat(buf, sizeof(buf), NULL, fmt, args)
269 #define string_format_from(buf, from, fmt, args...) \
270 string_nformat(buf, sizeof(buf), from, fmt, args)
273 string_enum_compare(const char *str1
, const char *str2
, int len
)
277 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
279 /* Diff-Header == DIFF_HEADER */
280 for (i
= 0; i
< len
; i
++) {
281 if (toupper(str1
[i
]) == toupper(str2
[i
]))
284 if (string_enum_sep(str1
[i
]) &&
285 string_enum_sep(str2
[i
]))
288 return str1
[i
] - str2
[i
];
294 #define enum_equals(entry, str, len) \
295 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
303 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
306 enum_map_name(const char *name
, size_t namelen
)
308 static char buf
[SIZEOF_STR
];
311 for (bufpos
= 0; bufpos
<= namelen
; bufpos
++) {
312 buf
[bufpos
] = tolower(name
[bufpos
]);
313 if (buf
[bufpos
] == '_')
321 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
324 map_enum_do(const struct enum_map
*map
, size_t map_size
, int *value
, const char *name
)
326 size_t namelen
= strlen(name
);
329 for (i
= 0; i
< map_size
; i
++)
330 if (enum_equals(map
[i
], name
, namelen
)) {
331 *value
= map
[i
].value
;
338 #define map_enum(attr, map, name) \
339 map_enum_do(map, ARRAY_SIZE(map), attr, name)
341 #define prefixcmp(str1, str2) \
342 strncmp(str1, str2, STRING_SIZE(str2))
345 suffixcmp(const char *str
, int slen
, const char *suffix
)
347 size_t len
= slen
>= 0 ? slen
: strlen(str
);
348 size_t suffixlen
= strlen(suffix
);
350 return suffixlen
< len
? strcmp(str
+ len
- suffixlen
, suffix
) : -1;
355 * Unicode / UTF-8 handling
357 * NOTE: Much of the following code for dealing with Unicode is derived from
358 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
359 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
363 unicode_width(unsigned long c
, int tab_size
)
366 (c
<= 0x115f /* Hangul Jamo */
369 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
371 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
372 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
373 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
374 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
375 || (c
>= 0xffe0 && c
<= 0xffe6)
376 || (c
>= 0x20000 && c
<= 0x2fffd)
377 || (c
>= 0x30000 && c
<= 0x3fffd)))
386 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
387 * Illegal bytes are set one. */
388 static const unsigned char utf8_bytes
[256] = {
389 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,
390 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,
391 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,
392 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,
393 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,
394 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,
395 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,
396 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,
399 static inline unsigned char
400 utf8_char_length(const char *string
, const char *end
)
402 int c
= *(unsigned char *) string
;
404 return utf8_bytes
[c
];
407 /* Decode UTF-8 multi-byte representation into a Unicode character. */
408 static inline unsigned long
409 utf8_to_unicode(const char *string
, size_t length
)
411 unsigned long unicode
;
418 unicode
= (string
[0] & 0x1f) << 6;
419 unicode
+= (string
[1] & 0x3f);
422 unicode
= (string
[0] & 0x0f) << 12;
423 unicode
+= ((string
[1] & 0x3f) << 6);
424 unicode
+= (string
[2] & 0x3f);
427 unicode
= (string
[0] & 0x0f) << 18;
428 unicode
+= ((string
[1] & 0x3f) << 12);
429 unicode
+= ((string
[2] & 0x3f) << 6);
430 unicode
+= (string
[3] & 0x3f);
433 unicode
= (string
[0] & 0x0f) << 24;
434 unicode
+= ((string
[1] & 0x3f) << 18);
435 unicode
+= ((string
[2] & 0x3f) << 12);
436 unicode
+= ((string
[3] & 0x3f) << 6);
437 unicode
+= (string
[4] & 0x3f);
440 unicode
= (string
[0] & 0x01) << 30;
441 unicode
+= ((string
[1] & 0x3f) << 24);
442 unicode
+= ((string
[2] & 0x3f) << 18);
443 unicode
+= ((string
[3] & 0x3f) << 12);
444 unicode
+= ((string
[4] & 0x3f) << 6);
445 unicode
+= (string
[5] & 0x3f);
451 /* Invalid characters could return the special 0xfffd value but NUL
452 * should be just as good. */
453 return unicode
> 0xffff ? 0 : unicode
;
456 /* Calculates how much of string can be shown within the given maximum width
457 * and sets trimmed parameter to non-zero value if all of string could not be
458 * shown. If the reserve flag is TRUE, it will reserve at least one
459 * trailing character, which can be useful when drawing a delimiter.
461 * Returns the number of bytes to output from string to satisfy max_width. */
463 utf8_length(const char **start
, size_t skip
, int *width
, size_t max_width
, int *trimmed
, bool reserve
, int tab_size
)
465 const char *string
= *start
;
466 const char *end
= strchr(string
, '\0');
467 unsigned char last_bytes
= 0;
468 size_t last_ucwidth
= 0;
473 while (string
< end
) {
474 unsigned char bytes
= utf8_char_length(string
, end
);
476 unsigned long unicode
;
478 if (string
+ bytes
> end
)
481 /* Change representation to figure out whether
482 * it is a single- or double-width character. */
484 unicode
= utf8_to_unicode(string
, bytes
);
485 /* FIXME: Graceful handling of invalid Unicode character. */
489 ucwidth
= unicode_width(unicode
, tab_size
);
491 skip
-= ucwidth
<= skip
? ucwidth
: skip
;
495 if (*width
> max_width
) {
498 if (reserve
&& *width
== max_width
) {
499 string
-= last_bytes
;
500 *width
-= last_ucwidth
;
506 last_bytes
= ucwidth
? bytes
: 0;
507 last_ucwidth
= ucwidth
;
510 return string
- *start
;
522 #define DATE_(name) DATE_##name
527 static const struct enum_map date_map
[] = {
528 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
538 static inline int timecmp(const struct time
*t1
, const struct time
*t2
)
540 return t1
->sec
- t2
->sec
;
544 mkdate(const struct time
*time
, enum date date
)
546 static char buf
[DATE_COLS
+ 1];
547 static const struct enum_map reldate
[] = {
548 { "second", 1, 60 * 2 },
549 { "minute", 60, 60 * 60 * 2 },
550 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
551 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
552 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
553 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
557 if (!date
|| !time
|| !time
->sec
)
560 if (date
== DATE_RELATIVE
) {
562 time_t date
= time
->sec
+ time
->tz
;
566 gettimeofday(&now
, NULL
);
567 seconds
= now
.tv_sec
< date
? date
- now
.tv_sec
: now
.tv_sec
- date
;
568 for (i
= 0; i
< ARRAY_SIZE(reldate
); i
++) {
569 if (seconds
>= reldate
[i
].value
)
572 seconds
/= reldate
[i
].namelen
;
573 if (!string_format(buf
, "%ld %s%s %s",
574 seconds
, reldate
[i
].name
,
575 seconds
> 1 ? "s" : "",
576 now
.tv_sec
>= date
? "ago" : "ahead"))
582 if (date
== DATE_LOCAL
) {
583 time_t date
= time
->sec
+ time
->tz
;
584 localtime_r(&date
, &tm
);
587 gmtime_r(&time
->sec
, &tm
);
589 return strftime(buf
, sizeof(buf
), DATE_FORMAT
, &tm
) ? buf
: NULL
;
593 #define AUTHOR_VALUES \
599 #define AUTHOR_(name) AUTHOR_##name
602 AUTHOR_DEFAULT
= AUTHOR_FULL
605 static const struct enum_map author_map
[] = {
606 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
612 get_author_initials(const char *author
)
614 static char initials
[AUTHOR_COLS
* 6 + 1];
616 const char *end
= strchr(author
, '\0');
618 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
620 memset(initials
, 0, sizeof(initials
));
621 while (author
< end
) {
625 while (is_initial_sep(*author
))
628 bytes
= utf8_char_length(author
, end
);
629 if (bytes
< sizeof(initials
) - 1 - pos
) {
631 initials
[pos
++] = *author
++;
635 for (i
= pos
; author
< end
&& !is_initial_sep(*author
); author
++) {
636 if (i
< sizeof(initials
) - 1)
637 initials
[i
++] = *author
;
648 argv_from_string(const char *argv
[SIZEOF_ARG
], int *argc
, char *cmd
)
652 while (*cmd
&& *argc
< SIZEOF_ARG
&& (valuelen
= strcspn(cmd
, " \t"))) {
653 bool advance
= cmd
[valuelen
] != 0;
656 argv
[(*argc
)++] = chomp_string(cmd
);
657 cmd
= chomp_string(cmd
+ valuelen
+ advance
);
660 if (*argc
< SIZEOF_ARG
)
662 return *argc
< SIZEOF_ARG
;
666 argv_from_env(const char **argv
, const char *name
)
668 char *env
= argv
? getenv(name
) : NULL
;
673 return !env
|| argv_from_string(argv
, &argc
, env
);
677 argv_free(const char *argv
[])
683 for (argc
= 0; argv
[argc
]; argc
++)
684 free((void *) argv
[argc
]);
689 argv_size(const char **argv
)
693 while (argv
&& argv
[argc
])
699 DEFINE_ALLOCATOR(argv_realloc
, const char *, SIZEOF_ARG
)
702 argv_append(const char ***argv
, const char *arg
)
704 size_t argc
= argv_size(*argv
);
706 if (!argv_realloc(argv
, argc
, 2))
709 (*argv
)[argc
++] = strdup(arg
);
710 (*argv
)[argc
] = NULL
;
715 argv_append_array(const char ***dst_argv
, const char *src_argv
[])
719 for (i
= 0; src_argv
&& src_argv
[i
]; i
++)
720 if (!argv_append(dst_argv
, src_argv
[i
]))
726 argv_copy(const char ***dst
, const char *src
[])
730 for (argc
= 0; src
[argc
]; argc
++)
731 if (!argv_append(dst
, src
[argc
]))
738 * Executing external commands.
742 IO_FD
, /* File descriptor based IO. */
743 IO_BG
, /* Execute command in the background. */
744 IO_FG
, /* Execute command with same std{in,out,err}. */
745 IO_RD
, /* Read only fork+exec IO. */
746 IO_WR
, /* Write only fork+exec IO. */
747 IO_AP
, /* Append fork+exec output to file. */
751 int pipe
; /* Pipe end for reading or writing. */
752 pid_t pid
; /* PID of spawned process. */
753 int error
; /* Error status. */
754 char *buf
; /* Read buffer. */
755 size_t bufalloc
; /* Allocated buffer size. */
756 size_t bufsize
; /* Buffer content size. */
757 char *bufpos
; /* Current buffer position. */
758 unsigned int eof
:1; /* Has end of file been reached. */
762 io_init(struct io
*io
)
764 memset(io
, 0, sizeof(*io
));
769 io_open(struct io
*io
, const char *fmt
, ...)
771 char name
[SIZEOF_STR
] = "";
778 fits
= vsnprintf(name
, sizeof(name
), fmt
, args
) < sizeof(name
);
782 io
->error
= ENAMETOOLONG
;
785 io
->pipe
= *name
? open(name
, O_RDONLY
) : STDIN_FILENO
;
788 return io
->pipe
!= -1;
792 io_kill(struct io
*io
)
794 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
798 io_done(struct io
*io
)
809 pid_t waiting
= waitpid(pid
, &status
, 0);
818 return waiting
== pid
&&
819 !WIFSIGNALED(status
) &&
821 !WEXITSTATUS(status
);
828 io_run(struct io
*io
, enum io_type type
, const char *dir
, const char *argv
[], ...)
830 int pipefds
[2] = { -1, -1 };
835 if ((type
== IO_RD
|| type
== IO_WR
) && pipe(pipefds
) < 0) {
838 } else if (type
== IO_AP
) {
839 va_start(args
, argv
);
840 pipefds
[1] = va_arg(args
, int);
844 if ((io
->pid
= fork())) {
847 if (pipefds
[!(type
== IO_WR
)] != -1)
848 close(pipefds
[!(type
== IO_WR
)]);
850 io
->pipe
= pipefds
[!!(type
== IO_WR
)];
856 int devnull
= open("/dev/null", O_RDWR
);
857 int readfd
= type
== IO_WR
? pipefds
[0] : devnull
;
858 int writefd
= (type
== IO_RD
|| type
== IO_AP
)
859 ? pipefds
[1] : devnull
;
861 dup2(readfd
, STDIN_FILENO
);
862 dup2(writefd
, STDOUT_FILENO
);
863 dup2(devnull
, STDERR_FILENO
);
866 if (pipefds
[0] != -1)
868 if (pipefds
[1] != -1)
872 if (dir
&& *dir
&& chdir(dir
) == -1)
875 execvp(argv
[0], (char *const*) argv
);
879 if (pipefds
[!!(type
== IO_WR
)] != -1)
880 close(pipefds
[!!(type
== IO_WR
)]);
885 io_complete(enum io_type type
, const char **argv
, const char *dir
, int fd
)
889 return io_run(&io
, type
, dir
, argv
, fd
) && io_done(&io
);
893 io_run_bg(const char **argv
)
895 return io_complete(IO_BG
, argv
, NULL
, -1);
899 io_run_fg(const char **argv
, const char *dir
)
901 return io_complete(IO_FG
, argv
, dir
, -1);
905 io_run_append(const char **argv
, int fd
)
907 return io_complete(IO_AP
, argv
, NULL
, fd
);
911 io_eof(struct io
*io
)
917 io_error(struct io
*io
)
923 io_strerror(struct io
*io
)
925 return strerror(io
->error
);
929 io_can_read(struct io
*io
)
931 struct timeval tv
= { 0, 500 };
935 FD_SET(io
->pipe
, &fds
);
937 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
941 io_read(struct io
*io
, void *buf
, size_t bufsize
)
944 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
946 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
948 else if (readsize
== -1)
950 else if (readsize
== 0)
956 DEFINE_ALLOCATOR(io_realloc_buf
, char, BUFSIZ
)
959 io_get(struct io
*io
, int c
, bool can_read
)
965 if (io
->bufsize
> 0) {
966 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
968 char *line
= io
->bufpos
;
971 io
->bufpos
= eol
+ 1;
972 io
->bufsize
-= io
->bufpos
- line
;
979 io
->bufpos
[io
->bufsize
] = 0;
989 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
990 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
992 if (io
->bufalloc
== io
->bufsize
) {
993 if (!io_realloc_buf(&io
->buf
, io
->bufalloc
, BUFSIZ
))
995 io
->bufalloc
+= BUFSIZ
;
998 io
->bufpos
= io
->buf
;
999 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
1002 io
->bufsize
+= readsize
;
1007 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
1011 while (!io_error(io
) && written
< bufsize
) {
1014 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
1015 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
1017 else if (size
== -1)
1023 return written
== bufsize
;
1027 io_read_buf(struct io
*io
, char buf
[], size_t bufsize
)
1029 char *result
= io_get(io
, '\n', TRUE
);
1032 result
= chomp_string(result
);
1033 string_ncopy_do(buf
, bufsize
, result
, strlen(result
));
1036 return io_done(io
) && result
;
1040 io_run_buf(const char **argv
, char buf
[], size_t bufsize
)
1044 return io_run(&io
, IO_RD
, NULL
, argv
) && io_read_buf(&io
, buf
, bufsize
);
1048 io_load(struct io
*io
, const char *separators
,
1049 int (*read_property
)(char *, size_t, char *, size_t))
1054 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
1059 name
= chomp_string(name
);
1060 namelen
= strcspn(name
, separators
);
1062 if (name
[namelen
]) {
1064 value
= chomp_string(name
+ namelen
+ 1);
1065 valuelen
= strlen(value
);
1072 state
= read_property(name
, namelen
, value
, valuelen
);
1075 if (state
!= ERR
&& io_error(io
))
1083 io_run_load(const char **argv
, const char *separators
,
1084 int (*read_property
)(char *, size_t, char *, size_t))
1088 if (!io_run(&io
, IO_RD
, NULL
, argv
))
1090 return io_load(&io
, separators
, read_property
);
1099 /* XXX: Keep the view request first and in sync with views[]. */ \
1100 REQ_GROUP("View switching") \
1101 REQ_(VIEW_MAIN, "Show main view"), \
1102 REQ_(VIEW_DIFF, "Show diff view"), \
1103 REQ_(VIEW_LOG, "Show log view"), \
1104 REQ_(VIEW_TREE, "Show tree view"), \
1105 REQ_(VIEW_BLOB, "Show blob view"), \
1106 REQ_(VIEW_BLAME, "Show blame view"), \
1107 REQ_(VIEW_BRANCH, "Show branch view"), \
1108 REQ_(VIEW_HELP, "Show help page"), \
1109 REQ_(VIEW_PAGER, "Show pager view"), \
1110 REQ_(VIEW_STATUS, "Show status view"), \
1111 REQ_(VIEW_STAGE, "Show stage view"), \
1113 REQ_GROUP("View manipulation") \
1114 REQ_(ENTER, "Enter current line and scroll"), \
1115 REQ_(NEXT, "Move to next"), \
1116 REQ_(PREVIOUS, "Move to previous"), \
1117 REQ_(PARENT, "Move to parent"), \
1118 REQ_(VIEW_NEXT, "Move focus to next view"), \
1119 REQ_(REFRESH, "Reload and refresh"), \
1120 REQ_(MAXIMIZE, "Maximize the current view"), \
1121 REQ_(VIEW_CLOSE, "Close the current view"), \
1122 REQ_(QUIT, "Close all views and quit"), \
1124 REQ_GROUP("View specific requests") \
1125 REQ_(STATUS_UPDATE, "Update file status"), \
1126 REQ_(STATUS_REVERT, "Revert file changes"), \
1127 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1128 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1130 REQ_GROUP("Cursor navigation") \
1131 REQ_(MOVE_UP, "Move cursor one line up"), \
1132 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1133 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1134 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1135 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1136 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1138 REQ_GROUP("Scrolling") \
1139 REQ_(SCROLL_FIRST_COL, "Scroll to the first line columns"), \
1140 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1141 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1142 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1143 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1144 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1145 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1147 REQ_GROUP("Searching") \
1148 REQ_(SEARCH, "Search the view"), \
1149 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1150 REQ_(FIND_NEXT, "Find next search match"), \
1151 REQ_(FIND_PREV, "Find previous search match"), \
1153 REQ_GROUP("Option manipulation") \
1154 REQ_(OPTIONS, "Open option menu"), \
1155 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1156 REQ_(TOGGLE_DATE, "Toggle date display"), \
1157 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1158 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1159 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1160 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1161 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1164 REQ_(PROMPT, "Bring up the prompt"), \
1165 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1166 REQ_(SHOW_VERSION, "Show version information"), \
1167 REQ_(STOP_LOADING, "Stop all loading views"), \
1168 REQ_(EDIT, "Open in editor"), \
1169 REQ_(NONE, "Do nothing")
1172 /* User action requests. */
1174 #define REQ_GROUP(help)
1175 #define REQ_(req, help) REQ_##req
1177 /* Offset all requests to avoid conflicts with ncurses getch values. */
1178 REQ_UNKNOWN
= KEY_MAX
+ 1,
1186 struct request_info
{
1187 enum request request
;
1193 static const struct request_info req_info
[] = {
1194 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1195 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1202 get_request(const char *name
)
1204 int namelen
= strlen(name
);
1207 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
1208 if (enum_equals(req_info
[i
], name
, namelen
))
1209 return req_info
[i
].request
;
1219 /* Option and state variables. */
1220 static enum date opt_date
= DATE_DEFAULT
;
1221 static enum author opt_author
= AUTHOR_DEFAULT
;
1222 static bool opt_line_number
= FALSE
;
1223 static bool opt_line_graphics
= TRUE
;
1224 static bool opt_rev_graph
= FALSE
;
1225 static bool opt_show_refs
= TRUE
;
1226 static bool opt_untracked_dirs_content
= TRUE
;
1227 static int opt_num_interval
= 5;
1228 static double opt_hscroll
= 0.50;
1229 static double opt_scale_split_view
= 2.0 / 3.0;
1230 static int opt_tab_size
= 8;
1231 static int opt_author_cols
= AUTHOR_COLS
;
1232 static char opt_path
[SIZEOF_STR
] = "";
1233 static char opt_file
[SIZEOF_STR
] = "";
1234 static char opt_ref
[SIZEOF_REF
] = "";
1235 static char opt_head
[SIZEOF_REF
] = "";
1236 static char opt_remote
[SIZEOF_REF
] = "";
1237 static char opt_encoding
[20] = "UTF-8";
1238 static iconv_t opt_iconv_in
= ICONV_NONE
;
1239 static iconv_t opt_iconv_out
= ICONV_NONE
;
1240 static char opt_search
[SIZEOF_STR
] = "";
1241 static char opt_cdup
[SIZEOF_STR
] = "";
1242 static char opt_prefix
[SIZEOF_STR
] = "";
1243 static char opt_git_dir
[SIZEOF_STR
] = "";
1244 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
1245 static char opt_editor
[SIZEOF_STR
] = "";
1246 static FILE *opt_tty
= NULL
;
1247 static const char **opt_diff_args
= NULL
;
1248 static const char **opt_rev_args
= NULL
;
1249 static const char **opt_file_args
= NULL
;
1251 #define is_initial_commit() (!get_ref_head())
1252 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1256 * Line-oriented content detection.
1260 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1271 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1272 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1273 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1274 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1275 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1276 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1277 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1279 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1280 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1281 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1282 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1283 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1284 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1285 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1286 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1287 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1288 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1289 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1290 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1291 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1292 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1293 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1294 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1295 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1296 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1297 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1298 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1299 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1300 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1301 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1302 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1303 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1304 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1305 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1306 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1307 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1308 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1309 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1310 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1311 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1312 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1313 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1314 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1315 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1316 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1317 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1318 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1321 #define LINE(type, line, fg, bg, attr) \
1329 const char *name
; /* Option name. */
1330 int namelen
; /* Size of option name. */
1331 const char *line
; /* The start of line to match. */
1332 int linelen
; /* Size of string to match. */
1333 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1336 static struct line_info line_info
[] = {
1337 #define LINE(type, line, fg, bg, attr) \
1338 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1343 static enum line_type
1344 get_line_type(const char *line
)
1346 int linelen
= strlen(line
);
1347 enum line_type type
;
1349 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1350 /* Case insensitive search matches Signed-off-by lines better. */
1351 if (linelen
>= line_info
[type
].linelen
&&
1352 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1355 return LINE_DEFAULT
;
1359 get_line_attr(enum line_type type
)
1361 assert(type
< ARRAY_SIZE(line_info
));
1362 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1365 static struct line_info
*
1366 get_line_info(const char *name
)
1368 size_t namelen
= strlen(name
);
1369 enum line_type type
;
1371 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1372 if (enum_equals(line_info
[type
], name
, namelen
))
1373 return &line_info
[type
];
1381 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1382 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1383 enum line_type type
;
1387 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1388 default_bg
= COLOR_BLACK
;
1389 default_fg
= COLOR_WHITE
;
1392 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1393 struct line_info
*info
= &line_info
[type
];
1394 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1395 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1397 init_pair(type
, fg
, bg
);
1402 enum line_type type
;
1405 unsigned int selected
:1;
1406 unsigned int dirty
:1;
1407 unsigned int cleareol
:1;
1408 unsigned int other
:16;
1410 void *data
; /* User data */
1420 enum request request
;
1423 static struct keybinding default_keybindings
[] = {
1424 /* View switching */
1425 { 'm', REQ_VIEW_MAIN
},
1426 { 'd', REQ_VIEW_DIFF
},
1427 { 'l', REQ_VIEW_LOG
},
1428 { 't', REQ_VIEW_TREE
},
1429 { 'f', REQ_VIEW_BLOB
},
1430 { 'B', REQ_VIEW_BLAME
},
1431 { 'H', REQ_VIEW_BRANCH
},
1432 { 'p', REQ_VIEW_PAGER
},
1433 { 'h', REQ_VIEW_HELP
},
1434 { 'S', REQ_VIEW_STATUS
},
1435 { 'c', REQ_VIEW_STAGE
},
1437 /* View manipulation */
1438 { 'q', REQ_VIEW_CLOSE
},
1439 { KEY_TAB
, REQ_VIEW_NEXT
},
1440 { KEY_RETURN
, REQ_ENTER
},
1441 { KEY_UP
, REQ_PREVIOUS
},
1442 { KEY_CTL('P'), REQ_PREVIOUS
},
1443 { KEY_DOWN
, REQ_NEXT
},
1444 { KEY_CTL('N'), REQ_NEXT
},
1445 { 'R', REQ_REFRESH
},
1446 { KEY_F(5), REQ_REFRESH
},
1447 { 'O', REQ_MAXIMIZE
},
1449 /* Cursor navigation */
1450 { 'k', REQ_MOVE_UP
},
1451 { 'j', REQ_MOVE_DOWN
},
1452 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1453 { KEY_END
, REQ_MOVE_LAST_LINE
},
1454 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1455 { KEY_CTL('D'), REQ_MOVE_PAGE_DOWN
},
1456 { ' ', REQ_MOVE_PAGE_DOWN
},
1457 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1458 { KEY_CTL('U'), REQ_MOVE_PAGE_UP
},
1459 { 'b', REQ_MOVE_PAGE_UP
},
1460 { '-', REQ_MOVE_PAGE_UP
},
1463 { '|', REQ_SCROLL_FIRST_COL
},
1464 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1465 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1466 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1467 { KEY_CTL('Y'), REQ_SCROLL_LINE_UP
},
1468 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1469 { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN
},
1470 { 'w', REQ_SCROLL_PAGE_UP
},
1471 { 's', REQ_SCROLL_PAGE_DOWN
},
1474 { '/', REQ_SEARCH
},
1475 { '?', REQ_SEARCH_BACK
},
1476 { 'n', REQ_FIND_NEXT
},
1477 { 'N', REQ_FIND_PREV
},
1481 { 'z', REQ_STOP_LOADING
},
1482 { 'v', REQ_SHOW_VERSION
},
1483 { 'r', REQ_SCREEN_REDRAW
},
1484 { KEY_CTL('L'), REQ_SCREEN_REDRAW
},
1485 { 'o', REQ_OPTIONS
},
1486 { '.', REQ_TOGGLE_LINENO
},
1487 { 'D', REQ_TOGGLE_DATE
},
1488 { 'A', REQ_TOGGLE_AUTHOR
},
1489 { 'g', REQ_TOGGLE_REV_GRAPH
},
1490 { 'F', REQ_TOGGLE_REFS
},
1491 { 'I', REQ_TOGGLE_SORT_ORDER
},
1492 { 'i', REQ_TOGGLE_SORT_FIELD
},
1493 { ':', REQ_PROMPT
},
1494 { 'u', REQ_STATUS_UPDATE
},
1495 { '!', REQ_STATUS_REVERT
},
1496 { 'M', REQ_STATUS_MERGE
},
1497 { '@', REQ_STAGE_NEXT
},
1498 { ',', REQ_PARENT
},
1502 #define KEYMAP_INFO \
1517 #define KEYMAP_(name) KEYMAP_##name
1522 static const struct enum_map keymap_table
[] = {
1523 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1528 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1530 struct keybinding_table
{
1531 struct keybinding
*data
;
1535 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1538 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1540 struct keybinding_table
*table
= &keybindings
[keymap
];
1543 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1544 if (keybindings
[keymap
].data
[i
].alias
== key
) {
1545 keybindings
[keymap
].data
[i
].request
= request
;
1550 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1552 die("Failed to allocate keybinding");
1553 table
->data
[table
->size
].alias
= key
;
1554 table
->data
[table
->size
++].request
= request
;
1556 if (request
== REQ_NONE
&& keymap
== KEYMAP_GENERIC
) {
1559 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1560 if (default_keybindings
[i
].alias
== key
)
1561 default_keybindings
[i
].request
= REQ_NONE
;
1565 /* Looks for a key binding first in the given map, then in the generic map, and
1566 * lastly in the default keybindings. */
1568 get_keybinding(enum keymap keymap
, int key
)
1572 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1573 if (keybindings
[keymap
].data
[i
].alias
== key
)
1574 return keybindings
[keymap
].data
[i
].request
;
1576 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1577 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1578 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1580 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1581 if (default_keybindings
[i
].alias
== key
)
1582 return default_keybindings
[i
].request
;
1584 return (enum request
) key
;
1593 static const struct key key_table
[] = {
1594 { "Enter", KEY_RETURN
},
1596 { "Backspace", KEY_BACKSPACE
},
1598 { "Escape", KEY_ESC
},
1599 { "Left", KEY_LEFT
},
1600 { "Right", KEY_RIGHT
},
1602 { "Down", KEY_DOWN
},
1603 { "Insert", KEY_IC
},
1604 { "Delete", KEY_DC
},
1606 { "Home", KEY_HOME
},
1608 { "PageUp", KEY_PPAGE
},
1609 { "PageDown", KEY_NPAGE
},
1619 { "F10", KEY_F(10) },
1620 { "F11", KEY_F(11) },
1621 { "F12", KEY_F(12) },
1625 get_key_value(const char *name
)
1629 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1630 if (!strcasecmp(key_table
[i
].name
, name
))
1631 return key_table
[i
].value
;
1633 if (strlen(name
) == 2 && name
[0] == '^' && isprint(*name
))
1634 return (int)name
[1] & 0x1f;
1635 if (strlen(name
) == 1 && isprint(*name
))
1641 get_key_name(int key_value
)
1643 static char key_char
[] = "'X'\0";
1644 const char *seq
= NULL
;
1647 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1648 if (key_table
[key
].value
== key_value
)
1649 seq
= key_table
[key
].name
;
1651 if (seq
== NULL
&& key_value
< 0x7f) {
1652 char *s
= key_char
+ 1;
1654 if (key_value
>= 0x20) {
1658 *s
++ = 0x40 | (key_value
& 0x1f);
1665 return seq
? seq
: "(no key)";
1669 append_key(char *buf
, size_t *pos
, const struct keybinding
*keybinding
)
1671 const char *sep
= *pos
> 0 ? ", " : "";
1672 const char *keyname
= get_key_name(keybinding
->alias
);
1674 return string_nformat(buf
, BUFSIZ
, pos
, "%s%s", sep
, keyname
);
1678 append_keymap_request_keys(char *buf
, size_t *pos
, enum request request
,
1679 enum keymap keymap
, bool all
)
1683 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1684 if (keybindings
[keymap
].data
[i
].request
== request
) {
1685 if (!append_key(buf
, pos
, &keybindings
[keymap
].data
[i
]))
1695 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1698 get_keys(enum keymap keymap
, enum request request
, bool all
)
1700 static char buf
[BUFSIZ
];
1706 if (!append_keymap_request_keys(buf
, &pos
, request
, keymap
, all
))
1707 return "Too many keybindings!";
1708 if (pos
> 0 && !all
)
1711 if (keymap
!= KEYMAP_GENERIC
) {
1712 /* Only the generic keymap includes the default keybindings when
1713 * listing all keys. */
1717 if (!append_keymap_request_keys(buf
, &pos
, request
, KEYMAP_GENERIC
, all
))
1718 return "Too many keybindings!";
1723 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1724 if (default_keybindings
[i
].request
== request
) {
1725 if (!append_key(buf
, &pos
, &default_keybindings
[i
]))
1726 return "Too many keybindings!";
1735 struct run_request
{
1741 static struct run_request
*run_request
;
1742 static size_t run_requests
;
1744 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1747 add_run_request(enum keymap keymap
, int key
, const char **argv
)
1749 struct run_request
*req
;
1751 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1754 req
= &run_request
[run_requests
];
1755 req
->keymap
= keymap
;
1759 if (!argv_copy(&req
->argv
, argv
))
1762 return REQ_NONE
+ ++run_requests
;
1765 static struct run_request
*
1766 get_run_request(enum request request
)
1768 if (request
<= REQ_NONE
)
1770 return &run_request
[request
- REQ_NONE
- 1];
1774 add_builtin_run_requests(void)
1776 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1777 const char *checkout
[] = { "git", "checkout", "%(branch)", NULL
};
1778 const char *commit
[] = { "git", "commit", NULL
};
1779 const char *gc
[] = { "git", "gc", NULL
};
1780 struct run_request reqs
[] = {
1781 { KEYMAP_MAIN
, 'C', cherry_pick
},
1782 { KEYMAP_STATUS
, 'C', commit
},
1783 { KEYMAP_BRANCH
, 'C', checkout
},
1784 { KEYMAP_GENERIC
, 'G', gc
},
1788 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1789 enum request req
= get_keybinding(reqs
[i
].keymap
, reqs
[i
].key
);
1791 if (req
!= reqs
[i
].key
)
1793 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argv
);
1794 if (req
!= REQ_NONE
)
1795 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1800 * User config file handling.
1803 static int config_lineno
;
1804 static bool config_errors
;
1805 static const char *config_msg
;
1807 static const struct enum_map color_map
[] = {
1808 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1820 static const struct enum_map attr_map
[] = {
1821 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1828 ATTR_MAP(UNDERLINE
),
1831 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1833 static int parse_step(double *opt
, const char *arg
)
1836 if (!strchr(arg
, '%'))
1839 /* "Shift down" so 100% and 1 does not conflict. */
1840 *opt
= (*opt
- 1) / 100;
1843 config_msg
= "Step value larger than 100%";
1848 config_msg
= "Invalid step value";
1855 parse_int(int *opt
, const char *arg
, int min
, int max
)
1857 int value
= atoi(arg
);
1859 if (min
<= value
&& value
<= max
) {
1864 config_msg
= "Integer value out of bound";
1869 set_color(int *color
, const char *name
)
1871 if (map_enum(color
, color_map
, name
))
1873 if (!prefixcmp(name
, "color"))
1874 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1878 /* Wants: object fgcolor bgcolor [attribute] */
1880 option_color_command(int argc
, const char *argv
[])
1882 struct line_info
*info
;
1885 config_msg
= "Wrong number of arguments given to color command";
1889 info
= get_line_info(argv
[0]);
1891 static const struct enum_map obsolete
[] = {
1892 ENUM_MAP("main-delim", LINE_DELIMITER
),
1893 ENUM_MAP("main-date", LINE_DATE
),
1894 ENUM_MAP("main-author", LINE_AUTHOR
),
1898 if (!map_enum(&index
, obsolete
, argv
[0])) {
1899 config_msg
= "Unknown color name";
1902 info
= &line_info
[index
];
1905 if (!set_color(&info
->fg
, argv
[1]) ||
1906 !set_color(&info
->bg
, argv
[2])) {
1907 config_msg
= "Unknown color";
1912 while (argc
-- > 3) {
1915 if (!set_attribute(&attr
, argv
[argc
])) {
1916 config_msg
= "Unknown attribute";
1925 static int parse_bool(bool *opt
, const char *arg
)
1927 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1932 static int parse_enum_do(unsigned int *opt
, const char *arg
,
1933 const struct enum_map
*map
, size_t map_size
)
1937 assert(map_size
> 1);
1939 if (map_enum_do(map
, map_size
, (int *) opt
, arg
))
1942 if (parse_bool(&is_true
, arg
) != OK
)
1945 *opt
= is_true
? map
[1].value
: map
[0].value
;
1949 #define parse_enum(opt, arg, map) \
1950 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1953 parse_string(char *opt
, const char *arg
, size_t optsize
)
1955 int arglen
= strlen(arg
);
1960 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1961 config_msg
= "Unmatched quotation";
1964 arg
+= 1; arglen
-= 2;
1966 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1971 /* Wants: name = value */
1973 option_set_command(int argc
, const char *argv
[])
1976 config_msg
= "Wrong number of arguments given to set command";
1980 if (strcmp(argv
[1], "=")) {
1981 config_msg
= "No value assigned";
1985 if (!strcmp(argv
[0], "show-author"))
1986 return parse_enum(&opt_author
, argv
[2], author_map
);
1988 if (!strcmp(argv
[0], "show-date"))
1989 return parse_enum(&opt_date
, argv
[2], date_map
);
1991 if (!strcmp(argv
[0], "show-rev-graph"))
1992 return parse_bool(&opt_rev_graph
, argv
[2]);
1994 if (!strcmp(argv
[0], "show-refs"))
1995 return parse_bool(&opt_show_refs
, argv
[2]);
1997 if (!strcmp(argv
[0], "show-line-numbers"))
1998 return parse_bool(&opt_line_number
, argv
[2]);
2000 if (!strcmp(argv
[0], "line-graphics"))
2001 return parse_bool(&opt_line_graphics
, argv
[2]);
2003 if (!strcmp(argv
[0], "line-number-interval"))
2004 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
2006 if (!strcmp(argv
[0], "author-width"))
2007 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
2009 if (!strcmp(argv
[0], "horizontal-scroll"))
2010 return parse_step(&opt_hscroll
, argv
[2]);
2012 if (!strcmp(argv
[0], "split-view-height"))
2013 return parse_step(&opt_scale_split_view
, argv
[2]);
2015 if (!strcmp(argv
[0], "tab-size"))
2016 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
2018 if (!strcmp(argv
[0], "commit-encoding"))
2019 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
2021 if (!strcmp(argv
[0], "status-untracked-dirs"))
2022 return parse_bool(&opt_untracked_dirs_content
, argv
[2]);
2024 config_msg
= "Unknown variable name";
2028 /* Wants: mode request key */
2030 option_bind_command(int argc
, const char *argv
[])
2032 enum request request
;
2037 config_msg
= "Wrong number of arguments given to bind command";
2041 if (!set_keymap(&keymap
, argv
[0])) {
2042 config_msg
= "Unknown key map";
2046 key
= get_key_value(argv
[1]);
2048 config_msg
= "Unknown key";
2052 request
= get_request(argv
[2]);
2053 if (request
== REQ_UNKNOWN
) {
2054 static const struct enum_map obsolete
[] = {
2055 ENUM_MAP("cherry-pick", REQ_NONE
),
2056 ENUM_MAP("screen-resize", REQ_NONE
),
2057 ENUM_MAP("tree-parent", REQ_PARENT
),
2061 if (map_enum(&alias
, obsolete
, argv
[2])) {
2062 if (alias
!= REQ_NONE
)
2063 add_keybinding(keymap
, alias
, key
);
2064 config_msg
= "Obsolete request name";
2068 if (request
== REQ_UNKNOWN
&& *argv
[2]++ == '!')
2069 request
= add_run_request(keymap
, key
, argv
+ 2);
2070 if (request
== REQ_UNKNOWN
) {
2071 config_msg
= "Unknown request name";
2075 add_keybinding(keymap
, request
, key
);
2081 set_option(const char *opt
, char *value
)
2083 const char *argv
[SIZEOF_ARG
];
2086 if (!argv_from_string(argv
, &argc
, value
)) {
2087 config_msg
= "Too many option arguments";
2091 if (!strcmp(opt
, "color"))
2092 return option_color_command(argc
, argv
);
2094 if (!strcmp(opt
, "set"))
2095 return option_set_command(argc
, argv
);
2097 if (!strcmp(opt
, "bind"))
2098 return option_bind_command(argc
, argv
);
2100 config_msg
= "Unknown option command";
2105 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
2110 config_msg
= "Internal error";
2112 /* Check for comment markers, since read_properties() will
2113 * only ensure opt and value are split at first " \t". */
2114 optlen
= strcspn(opt
, "#");
2118 if (opt
[optlen
] != 0) {
2119 config_msg
= "No option value";
2123 /* Look for comment endings in the value. */
2124 size_t len
= strcspn(value
, "#");
2126 if (len
< valuelen
) {
2128 value
[valuelen
] = 0;
2131 status
= set_option(opt
, value
);
2134 if (status
== ERR
) {
2135 warn("Error on line %d, near '%.*s': %s",
2136 config_lineno
, (int) optlen
, opt
, config_msg
);
2137 config_errors
= TRUE
;
2140 /* Always keep going if errors are encountered. */
2145 load_option_file(const char *path
)
2149 /* It's OK that the file doesn't exist. */
2150 if (!io_open(&io
, "%s", path
))
2154 config_errors
= FALSE
;
2156 if (io_load(&io
, " \t", read_option
) == ERR
||
2157 config_errors
== TRUE
)
2158 warn("Errors while loading %s.", path
);
2164 const char *home
= getenv("HOME");
2165 const char *tigrc_user
= getenv("TIGRC_USER");
2166 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
2167 const char *tig_diff_opts
= getenv("TIG_DIFF_OPTS");
2168 char buf
[SIZEOF_STR
];
2171 tigrc_system
= SYSCONFDIR
"/tigrc";
2172 load_option_file(tigrc_system
);
2175 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
2179 load_option_file(tigrc_user
);
2181 /* Add _after_ loading config files to avoid adding run requests
2182 * that conflict with keybindings. */
2183 add_builtin_run_requests();
2185 if (!opt_diff_args
&& tig_diff_opts
&& *tig_diff_opts
) {
2186 static const char *diff_opts
[SIZEOF_ARG
] = { NULL
};
2189 if (!string_format(buf
, "%s", tig_diff_opts
) ||
2190 !argv_from_string(diff_opts
, &argc
, buf
))
2191 die("TIG_DIFF_OPTS contains too many arguments");
2192 else if (!argv_copy(&opt_diff_args
, diff_opts
))
2193 die("Failed to format TIG_DIFF_OPTS arguments");
2207 /* The display array of active views and the index of the current view. */
2208 static struct view
*display
[2];
2209 static unsigned int current_view
;
2211 #define foreach_displayed_view(view, i) \
2212 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2214 #define displayed_views() (display[1] != NULL ? 2 : 1)
2216 /* Current head and commit ID */
2217 static char ref_blob
[SIZEOF_REF
] = "";
2218 static char ref_commit
[SIZEOF_REF
] = "HEAD";
2219 static char ref_head
[SIZEOF_REF
] = "HEAD";
2220 static char ref_branch
[SIZEOF_REF
] = "";
2237 enum view_type type
; /* View type */
2238 const char *name
; /* View name */
2239 const char *cmd_env
; /* Command line set via environment */
2240 const char *id
; /* Points to either of ref_{head,commit,blob} */
2242 struct view_ops
*ops
; /* View operations */
2244 enum keymap keymap
; /* What keymap does this view have */
2245 bool git_dir
; /* Whether the view requires a git directory. */
2247 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
2248 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
2250 int height
, width
; /* The width and height of the main window */
2251 WINDOW
*win
; /* The main window */
2252 WINDOW
*title
; /* The title window living below the main window */
2255 unsigned long offset
; /* Offset of the window top */
2256 unsigned long yoffset
; /* Offset from the window side. */
2257 unsigned long lineno
; /* Current line number */
2258 unsigned long p_offset
; /* Previous offset of the window top */
2259 unsigned long p_yoffset
;/* Previous offset from the window side */
2260 unsigned long p_lineno
; /* Previous current line number */
2261 bool p_restore
; /* Should the previous position be restored. */
2264 char grep
[SIZEOF_STR
]; /* Search string */
2265 regex_t
*regex
; /* Pre-compiled regexp */
2267 /* If non-NULL, points to the view that opened this view. If this view
2268 * is closed tig will switch back to the parent view. */
2269 struct view
*parent
;
2273 size_t lines
; /* Total number of lines */
2274 struct line
*line
; /* Line index */
2275 unsigned int digits
; /* Number of digits in the lines member. */
2278 struct line
*curline
; /* Line currently being drawn. */
2279 enum line_type curtype
; /* Attribute currently used for drawing. */
2280 unsigned long col
; /* Column when drawing. */
2281 bool has_scrolled
; /* View was scrolled. */
2284 const char **argv
; /* Shell command arguments. */
2285 const char *dir
; /* Directory from which to execute. */
2293 /* What type of content being displayed. Used in the title bar. */
2295 /* Default command arguments. */
2297 /* Open and reads in all view content. */
2298 bool (*open
)(struct view
*view
);
2299 /* Read one line; updates view->line. */
2300 bool (*read
)(struct view
*view
, char *data
);
2301 /* Draw one line; @lineno must be < view->height. */
2302 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
2303 /* Depending on view handle a special requests. */
2304 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
2305 /* Search for regexp in a line. */
2306 bool (*grep
)(struct view
*view
, struct line
*line
);
2308 void (*select
)(struct view
*view
, struct line
*line
);
2309 /* Prepare view for loading */
2310 bool (*prepare
)(struct view
*view
);
2313 static struct view_ops blame_ops
;
2314 static struct view_ops blob_ops
;
2315 static struct view_ops diff_ops
;
2316 static struct view_ops help_ops
;
2317 static struct view_ops log_ops
;
2318 static struct view_ops main_ops
;
2319 static struct view_ops pager_ops
;
2320 static struct view_ops stage_ops
;
2321 static struct view_ops status_ops
;
2322 static struct view_ops tree_ops
;
2323 static struct view_ops branch_ops
;
2325 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2326 { type, name, #env, ref, ops, map, git }
2328 #define VIEW_(id, name, ops, git, ref) \
2329 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2331 static struct view views
[] = {
2332 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
2333 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
2334 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
2335 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
2336 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
2337 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
2338 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
2339 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
2340 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, ""),
2341 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
2342 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
2345 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2347 #define foreach_view(view, i) \
2348 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2350 #define view_is_displayed(view) \
2351 (view == display[0] || view == display[1])
2354 view_request(struct view
*view
, enum request request
)
2356 if (!view
|| !view
->lines
)
2358 return view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
2367 set_view_attr(struct view
*view
, enum line_type type
)
2369 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
2370 (void) wattrset(view
->win
, get_line_attr(type
));
2371 wchgat(view
->win
, -1, 0, type
, NULL
);
2372 view
->curtype
= type
;
2377 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
2378 int max_len
, bool use_tilde
)
2380 static char out_buffer
[BUFSIZ
* 2];
2383 int trimmed
= FALSE
;
2384 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2389 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
, opt_tab_size
);
2391 set_view_attr(view
, type
);
2393 if (opt_iconv_out
!= ICONV_NONE
) {
2394 ICONV_CONST
char *inbuf
= (ICONV_CONST
char *) string
;
2395 size_t inlen
= len
+ 1;
2397 char *outbuf
= out_buffer
;
2398 size_t outlen
= sizeof(out_buffer
);
2402 ret
= iconv(opt_iconv_out
, &inbuf
, &inlen
, &outbuf
, &outlen
);
2403 if (ret
!= (size_t) -1) {
2404 string
= out_buffer
;
2405 len
= sizeof(out_buffer
) - outlen
;
2409 waddnstr(view
->win
, string
, len
);
2411 if (trimmed
&& use_tilde
) {
2412 set_view_attr(view
, LINE_DELIMITER
);
2413 waddch(view
->win
, '~');
2421 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
2423 static char space
[] = " ";
2426 spaces
= MIN(max
, spaces
);
2428 while (spaces
> 0) {
2429 int len
= MIN(spaces
, sizeof(space
) - 1);
2431 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
2439 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
2441 char text
[SIZEOF_STR
];
2444 size_t pos
= string_expand(text
, sizeof(text
), string
, opt_tab_size
);
2446 view
->col
+= draw_chars(view
, type
, text
, view
->width
+ view
->yoffset
- view
->col
, trim
);
2448 } while (*string
&& view
->width
+ view
->yoffset
> view
->col
);
2450 return view
->width
+ view
->yoffset
<= view
->col
;
2454 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
2456 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2457 int max
= view
->width
+ view
->yoffset
- view
->col
;
2463 set_view_attr(view
, type
);
2464 /* Using waddch() instead of waddnstr() ensures that
2465 * they'll be rendered correctly for the cursor line. */
2466 for (i
= skip
; i
< size
; i
++)
2467 waddch(view
->win
, graphic
[i
]);
2470 if (size
< max
&& skip
<= size
)
2471 waddch(view
->win
, ' ');
2474 return view
->width
+ view
->yoffset
<= view
->col
;
2478 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2480 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2484 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2486 col
= draw_space(view
, type
, max
- 1, max
- 1);
2489 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2490 return view
->width
+ view
->yoffset
<= view
->col
;
2494 draw_date(struct view
*view
, struct time
*time
)
2496 const char *date
= mkdate(time
, opt_date
);
2497 int cols
= opt_date
== DATE_SHORT
? DATE_SHORT_COLS
: DATE_COLS
;
2499 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2503 draw_author(struct view
*view
, const char *author
)
2505 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5;
2506 bool abbreviate
= opt_author
== AUTHOR_ABBREVIATED
|| !trim
;
2508 if (abbreviate
&& author
)
2509 author
= get_author_initials(author
);
2511 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2515 draw_mode(struct view
*view
, mode_t mode
)
2521 else if (S_ISLNK(mode
))
2523 else if (S_ISGITLINK(mode
))
2525 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2527 else if (S_ISREG(mode
))
2532 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2536 draw_lineno(struct view
*view
, unsigned int lineno
)
2539 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2540 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2542 chtype separator
= opt_line_graphics
? ACS_VLINE
: '|';
2544 lineno
+= view
->offset
+ 1;
2545 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2546 static char fmt
[] = "%1ld";
2548 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2549 if (string_format(number
, fmt
, lineno
))
2553 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2555 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2556 return draw_graphic(view
, LINE_DEFAULT
, &separator
, 1);
2560 draw_view_line(struct view
*view
, unsigned int lineno
)
2563 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2565 assert(view_is_displayed(view
));
2567 if (view
->offset
+ lineno
>= view
->lines
)
2570 line
= &view
->line
[view
->offset
+ lineno
];
2572 wmove(view
->win
, lineno
, 0);
2574 wclrtoeol(view
->win
);
2576 view
->curline
= line
;
2577 view
->curtype
= LINE_NONE
;
2578 line
->selected
= FALSE
;
2579 line
->dirty
= line
->cleareol
= 0;
2582 set_view_attr(view
, LINE_CURSOR
);
2583 line
->selected
= TRUE
;
2584 view
->ops
->select(view
, line
);
2587 return view
->ops
->draw(view
, line
, lineno
);
2591 redraw_view_dirty(struct view
*view
)
2596 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2597 if (view
->offset
+ lineno
>= view
->lines
)
2599 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2602 if (!draw_view_line(view
, lineno
))
2608 wnoutrefresh(view
->win
);
2612 redraw_view_from(struct view
*view
, int lineno
)
2614 assert(0 <= lineno
&& lineno
< view
->height
);
2616 for (; lineno
< view
->height
; lineno
++) {
2617 if (!draw_view_line(view
, lineno
))
2621 wnoutrefresh(view
->win
);
2625 redraw_view(struct view
*view
)
2628 redraw_view_from(view
, 0);
2633 update_view_title(struct view
*view
)
2635 char buf
[SIZEOF_STR
];
2636 char state
[SIZEOF_STR
];
2637 size_t bufpos
= 0, statelen
= 0;
2639 assert(view_is_displayed(view
));
2641 if (view
->type
!= VIEW_STATUS
&& view
->lines
) {
2642 unsigned int view_lines
= view
->offset
+ view
->height
;
2643 unsigned int lines
= view
->lines
2644 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2647 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2656 time_t secs
= time(NULL
) - view
->start_time
;
2658 /* Three git seconds are a long time ... */
2660 string_format_from(state
, &statelen
, " loading %lds", secs
);
2663 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2664 if (*view
->ref
&& bufpos
< view
->width
) {
2665 size_t refsize
= strlen(view
->ref
);
2666 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2668 if (minsize
< view
->width
)
2669 refsize
= view
->width
- minsize
+ 7;
2670 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2673 if (statelen
&& bufpos
< view
->width
) {
2674 string_format_from(buf
, &bufpos
, "%s", state
);
2677 if (view
== display
[current_view
])
2678 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2680 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2682 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2683 wclrtoeol(view
->title
);
2684 wnoutrefresh(view
->title
);
2688 apply_step(double step
, int value
)
2692 value
*= step
+ 0.01;
2693 return value
? value
: 1;
2697 resize_display(void)
2700 struct view
*base
= display
[0];
2701 struct view
*view
= display
[1] ? display
[1] : display
[0];
2703 /* Setup window dimensions */
2705 getmaxyx(stdscr
, base
->height
, base
->width
);
2707 /* Make room for the status window. */
2711 /* Horizontal split. */
2712 view
->width
= base
->width
;
2713 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2714 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2715 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2716 base
->height
-= view
->height
;
2718 /* Make room for the title bar. */
2722 /* Make room for the title bar. */
2727 foreach_displayed_view (view
, i
) {
2729 view
->win
= newwin(view
->height
, 0, offset
, 0);
2731 die("Failed to create %s view", view
->name
);
2733 scrollok(view
->win
, FALSE
);
2735 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2737 die("Failed to create title window");
2740 wresize(view
->win
, view
->height
, view
->width
);
2741 mvwin(view
->win
, offset
, 0);
2742 mvwin(view
->title
, offset
+ view
->height
, 0);
2745 offset
+= view
->height
+ 1;
2750 redraw_display(bool clear
)
2755 foreach_displayed_view (view
, i
) {
2759 update_view_title(view
);
2769 toggle_enum_option_do(unsigned int *opt
, const char *help
,
2770 const struct enum_map
*map
, size_t size
)
2772 *opt
= (*opt
+ 1) % size
;
2773 redraw_display(FALSE
);
2774 report("Displaying %s %s", enum_name(map
[*opt
]), help
);
2777 #define toggle_enum_option(opt, help, map) \
2778 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2780 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2781 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2784 toggle_view_option(bool *option
, const char *help
)
2787 redraw_display(FALSE
);
2788 report("%sabling %s", *option
? "En" : "Dis", help
);
2792 open_option_menu(void)
2794 const struct menu_item menu
[] = {
2795 { '.', "line numbers", &opt_line_number
},
2796 { 'D', "date display", &opt_date
},
2797 { 'A', "author display", &opt_author
},
2798 { 'g', "revision graph display", &opt_rev_graph
},
2799 { 'F', "reference display", &opt_show_refs
},
2804 if (prompt_menu("Toggle option", menu
, &selected
)) {
2805 if (menu
[selected
].data
== &opt_date
)
2807 else if (menu
[selected
].data
== &opt_author
)
2810 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2815 maximize_view(struct view
*view
)
2817 memset(display
, 0, sizeof(display
));
2819 display
[current_view
] = view
;
2821 redraw_display(FALSE
);
2831 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2833 if (lineno
>= view
->lines
)
2834 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2836 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2837 unsigned long half
= view
->height
/ 2;
2840 offset
= lineno
- half
;
2845 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2846 view
->offset
= offset
;
2847 view
->lineno
= lineno
;
2854 /* Scrolling backend */
2856 do_scroll_view(struct view
*view
, int lines
)
2858 bool redraw_current_line
= FALSE
;
2860 /* The rendering expects the new offset. */
2861 view
->offset
+= lines
;
2863 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2866 /* Move current line into the view. */
2867 if (view
->lineno
< view
->offset
) {
2868 view
->lineno
= view
->offset
;
2869 redraw_current_line
= TRUE
;
2870 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2871 view
->lineno
= view
->offset
+ view
->height
- 1;
2872 redraw_current_line
= TRUE
;
2875 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2877 /* Redraw the whole screen if scrolling is pointless. */
2878 if (view
->height
< ABS(lines
)) {
2882 int line
= lines
> 0 ? view
->height
- lines
: 0;
2883 int end
= line
+ ABS(lines
);
2885 scrollok(view
->win
, TRUE
);
2886 wscrl(view
->win
, lines
);
2887 scrollok(view
->win
, FALSE
);
2889 while (line
< end
&& draw_view_line(view
, line
))
2892 if (redraw_current_line
)
2893 draw_view_line(view
, view
->lineno
- view
->offset
);
2894 wnoutrefresh(view
->win
);
2897 view
->has_scrolled
= TRUE
;
2901 /* Scroll frontend */
2903 scroll_view(struct view
*view
, enum request request
)
2907 assert(view_is_displayed(view
));
2910 case REQ_SCROLL_FIRST_COL
:
2912 redraw_view_from(view
, 0);
2915 case REQ_SCROLL_LEFT
:
2916 if (view
->yoffset
== 0) {
2917 report("Cannot scroll beyond the first column");
2920 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2923 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2924 redraw_view_from(view
, 0);
2927 case REQ_SCROLL_RIGHT
:
2928 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2932 case REQ_SCROLL_PAGE_DOWN
:
2933 lines
= view
->height
;
2934 case REQ_SCROLL_LINE_DOWN
:
2935 if (view
->offset
+ lines
> view
->lines
)
2936 lines
= view
->lines
- view
->offset
;
2938 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2939 report("Cannot scroll beyond the last line");
2944 case REQ_SCROLL_PAGE_UP
:
2945 lines
= view
->height
;
2946 case REQ_SCROLL_LINE_UP
:
2947 if (lines
> view
->offset
)
2948 lines
= view
->offset
;
2951 report("Cannot scroll beyond the first line");
2959 die("request %d not handled in switch", request
);
2962 do_scroll_view(view
, lines
);
2967 move_view(struct view
*view
, enum request request
)
2969 int scroll_steps
= 0;
2973 case REQ_MOVE_FIRST_LINE
:
2974 steps
= -view
->lineno
;
2977 case REQ_MOVE_LAST_LINE
:
2978 steps
= view
->lines
- view
->lineno
- 1;
2981 case REQ_MOVE_PAGE_UP
:
2982 steps
= view
->height
> view
->lineno
2983 ? -view
->lineno
: -view
->height
;
2986 case REQ_MOVE_PAGE_DOWN
:
2987 steps
= view
->lineno
+ view
->height
>= view
->lines
2988 ? view
->lines
- view
->lineno
- 1 : view
->height
;
3000 die("request %d not handled in switch", request
);
3003 if (steps
<= 0 && view
->lineno
== 0) {
3004 report("Cannot move beyond the first line");
3007 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
3008 report("Cannot move beyond the last line");
3012 /* Move the current line */
3013 view
->lineno
+= steps
;
3014 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
3016 /* Check whether the view needs to be scrolled */
3017 if (view
->lineno
< view
->offset
||
3018 view
->lineno
>= view
->offset
+ view
->height
) {
3019 scroll_steps
= steps
;
3020 if (steps
< 0 && -steps
> view
->offset
) {
3021 scroll_steps
= -view
->offset
;
3023 } else if (steps
> 0) {
3024 if (view
->lineno
== view
->lines
- 1 &&
3025 view
->lines
> view
->height
) {
3026 scroll_steps
= view
->lines
- view
->offset
- 1;
3027 if (scroll_steps
>= view
->height
)
3028 scroll_steps
-= view
->height
- 1;
3033 if (!view_is_displayed(view
)) {
3034 view
->offset
+= scroll_steps
;
3035 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
3036 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
3040 /* Repaint the old "current" line if we be scrolling */
3041 if (ABS(steps
) < view
->height
)
3042 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
3045 do_scroll_view(view
, scroll_steps
);
3049 /* Draw the current line */
3050 draw_view_line(view
, view
->lineno
- view
->offset
);
3052 wnoutrefresh(view
->win
);
3061 static void search_view(struct view
*view
, enum request request
);
3064 grep_text(struct view
*view
, const char *text
[])
3069 for (i
= 0; text
[i
]; i
++)
3071 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
3077 select_view_line(struct view
*view
, unsigned long lineno
)
3079 unsigned long old_lineno
= view
->lineno
;
3080 unsigned long old_offset
= view
->offset
;
3082 if (goto_view_line(view
, view
->offset
, lineno
)) {
3083 if (view_is_displayed(view
)) {
3084 if (old_offset
!= view
->offset
) {
3087 draw_view_line(view
, old_lineno
- view
->offset
);
3088 draw_view_line(view
, view
->lineno
- view
->offset
);
3089 wnoutrefresh(view
->win
);
3092 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
3098 find_next(struct view
*view
, enum request request
)
3100 unsigned long lineno
= view
->lineno
;
3105 report("No previous search");
3107 search_view(view
, request
);
3117 case REQ_SEARCH_BACK
:
3126 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
3127 lineno
+= direction
;
3129 /* Note, lineno is unsigned long so will wrap around in which case it
3130 * will become bigger than view->lines. */
3131 for (; lineno
< view
->lines
; lineno
+= direction
) {
3132 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
3133 select_view_line(view
, lineno
);
3134 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
3139 report("No match found for '%s'", view
->grep
);
3143 search_view(struct view
*view
, enum request request
)
3148 regfree(view
->regex
);
3151 view
->regex
= calloc(1, sizeof(*view
->regex
));
3156 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
3157 if (regex_err
!= 0) {
3158 char buf
[SIZEOF_STR
] = "unknown error";
3160 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
3161 report("Search failed: %s", buf
);
3165 string_copy(view
->grep
, opt_search
);
3167 find_next(view
, request
);
3171 * Incremental updating
3175 reset_view(struct view
*view
)
3179 for (i
= 0; i
< view
->lines
; i
++)
3180 free(view
->line
[i
].data
);
3183 view
->p_offset
= view
->offset
;
3184 view
->p_yoffset
= view
->yoffset
;
3185 view
->p_lineno
= view
->lineno
;
3193 view
->update_secs
= 0;
3197 format_arg(const char *name
)
3203 const char *value_if_empty
;
3205 #define FORMAT_VAR(name, value, value_if_empty) \
3206 { name, STRING_SIZE(name), value, value_if_empty }
3207 FORMAT_VAR("%(directory)", opt_path
, ""),
3208 FORMAT_VAR("%(file)", opt_file
, ""),
3209 FORMAT_VAR("%(ref)", opt_ref
, "HEAD"),
3210 FORMAT_VAR("%(head)", ref_head
, ""),
3211 FORMAT_VAR("%(commit)", ref_commit
, ""),
3212 FORMAT_VAR("%(blob)", ref_blob
, ""),
3213 FORMAT_VAR("%(branch)", ref_branch
, ""),
3217 for (i
= 0; i
< ARRAY_SIZE(vars
); i
++)
3218 if (!strncmp(name
, vars
[i
].name
, vars
[i
].namelen
))
3219 return *vars
[i
].value
? vars
[i
].value
: vars
[i
].value_if_empty
;
3221 report("Unknown replacement: `%s`", name
);
3226 format_argv(const char ***dst_argv
, const char *src_argv
[], bool replace
)
3228 char buf
[SIZEOF_STR
];
3231 argv_free(*dst_argv
);
3233 for (argc
= 0; src_argv
[argc
]; argc
++) {
3234 const char *arg
= src_argv
[argc
];
3237 if (!strcmp(arg
, "%(fileargs)")) {
3238 if (!argv_append_array(dst_argv
, opt_file_args
))
3242 } else if (!strcmp(arg
, "%(diffargs)")) {
3243 if (!argv_append_array(dst_argv
, opt_diff_args
))
3247 } else if (!strcmp(arg
, "%(revargs)")) {
3248 if (!argv_append_array(dst_argv
, opt_rev_args
))
3254 char *next
= strstr(arg
, "%(");
3255 int len
= next
- arg
;
3258 if (!next
|| !replace
) {
3263 value
= format_arg(next
);
3270 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
3273 arg
= next
&& replace
? strchr(next
, ')') + 1 : NULL
;
3276 if (!argv_append(dst_argv
, buf
))
3280 return src_argv
[argc
] == NULL
;
3284 restore_view_position(struct view
*view
)
3286 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
3289 /* Changing the view position cancels the restoring. */
3290 /* FIXME: Changing back to the first line is not detected. */
3291 if (view
->offset
!= 0 || view
->lineno
!= 0) {
3292 view
->p_restore
= FALSE
;
3296 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
3297 view_is_displayed(view
))
3300 view
->yoffset
= view
->p_yoffset
;
3301 view
->p_restore
= FALSE
;
3307 end_update(struct view
*view
, bool force
)
3311 while (!view
->ops
->read(view
, NULL
))
3315 io_kill(view
->pipe
);
3316 io_done(view
->pipe
);
3321 setup_update(struct view
*view
, const char *vid
)
3324 string_copy_rev(view
->vid
, vid
);
3325 view
->pipe
= &view
->io
;
3326 view
->start_time
= time(NULL
);
3330 prepare_io(struct view
*view
, const char *dir
, const char *argv
[], bool replace
)
3333 return format_argv(&view
->argv
, argv
, replace
);
3337 prepare_update(struct view
*view
, const char *argv
[], const char *dir
)
3340 end_update(view
, TRUE
);
3341 return prepare_io(view
, dir
, argv
, FALSE
);
3345 start_update(struct view
*view
, const char **argv
, const char *dir
)
3348 io_done(view
->pipe
);
3349 return prepare_io(view
, dir
, argv
, FALSE
) &&
3350 io_run(&view
->io
, IO_RD
, dir
, view
->argv
);
3354 prepare_update_file(struct view
*view
, const char *name
)
3357 end_update(view
, TRUE
);
3358 argv_free(view
->argv
);
3359 return io_open(&view
->io
, "%s/%s", opt_cdup
[0] ? opt_cdup
: ".", name
);
3363 begin_update(struct view
*view
, bool refresh
)
3366 end_update(view
, TRUE
);
3369 if (view
->ops
->prepare
) {
3370 if (!view
->ops
->prepare(view
))
3372 } else if (!prepare_io(view
, NULL
, view
->ops
->argv
, TRUE
)) {
3376 /* Put the current ref_* value to the view title ref
3377 * member. This is needed by the blob view. Most other
3378 * views sets it automatically after loading because the
3379 * first line is a commit line. */
3380 string_copy_rev(view
->ref
, view
->id
);
3383 if (view
->argv
&& view
->argv
[0] &&
3384 !io_run(&view
->io
, IO_RD
, view
->dir
, view
->argv
))
3387 setup_update(view
, view
->id
);
3393 update_view(struct view
*view
)
3395 char out_buffer
[BUFSIZ
* 2];
3397 /* Clear the view and redraw everything since the tree sorting
3398 * might have rearranged things. */
3399 bool redraw
= view
->lines
== 0;
3400 bool can_read
= TRUE
;
3405 if (!io_can_read(view
->pipe
)) {
3406 if (view
->lines
== 0 && view_is_displayed(view
)) {
3407 time_t secs
= time(NULL
) - view
->start_time
;
3409 if (secs
> 1 && secs
> view
->update_secs
) {
3410 if (view
->update_secs
== 0)
3412 update_view_title(view
);
3413 view
->update_secs
= secs
;
3419 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
3420 if (opt_iconv_in
!= ICONV_NONE
) {
3421 ICONV_CONST
char *inbuf
= line
;
3422 size_t inlen
= strlen(line
) + 1;
3424 char *outbuf
= out_buffer
;
3425 size_t outlen
= sizeof(out_buffer
);
3429 ret
= iconv(opt_iconv_in
, &inbuf
, &inlen
, &outbuf
, &outlen
);
3430 if (ret
!= (size_t) -1)
3434 if (!view
->ops
->read(view
, line
)) {
3435 report("Allocation failure");
3436 end_update(view
, TRUE
);
3442 unsigned long lines
= view
->lines
;
3445 for (digits
= 0; lines
; digits
++)
3448 /* Keep the displayed view in sync with line number scaling. */
3449 if (digits
!= view
->digits
) {
3450 view
->digits
= digits
;
3451 if (opt_line_number
|| view
->type
== VIEW_BLAME
)
3456 if (io_error(view
->pipe
)) {
3457 report("Failed to read: %s", io_strerror(view
->pipe
));
3458 end_update(view
, TRUE
);
3460 } else if (io_eof(view
->pipe
)) {
3461 if (view_is_displayed(view
))
3463 end_update(view
, FALSE
);
3466 if (restore_view_position(view
))
3469 if (!view_is_displayed(view
))
3473 redraw_view_from(view
, 0);
3475 redraw_view_dirty(view
);
3477 /* Update the title _after_ the redraw so that if the redraw picks up a
3478 * commit reference in view->ref it'll be available here. */
3479 update_view_title(view
);
3483 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
3485 static struct line
*
3486 add_line_data(struct view
*view
, void *data
, enum line_type type
)
3490 if (!realloc_lines(&view
->line
, view
->lines
, 1))
3493 line
= &view
->line
[view
->lines
++];
3494 memset(line
, 0, sizeof(*line
));
3502 static struct line
*
3503 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
3505 char *data
= text
? strdup(text
) : NULL
;
3507 return data
? add_line_data(view
, data
, type
) : NULL
;
3510 static struct line
*
3511 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3513 char buf
[SIZEOF_STR
];
3516 va_start(args
, fmt
);
3517 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3521 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3529 OPEN_DEFAULT
= 0, /* Use default view switching. */
3530 OPEN_SPLIT
= 1, /* Split current view. */
3531 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3532 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3533 OPEN_PREPARED
= 32, /* Open already prepared command. */
3537 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3539 bool split
= !!(flags
& OPEN_SPLIT
);
3540 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3541 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3542 struct view
*view
= VIEW(request
);
3543 int nviews
= displayed_views();
3544 struct view
*base_view
= display
[0];
3546 if (view
== prev
&& nviews
== 1 && !reload
) {
3547 report("Already in %s view", view
->name
);
3551 if (view
->git_dir
&& !opt_git_dir
[0]) {
3552 report("The %s view is disabled in pager view", view
->name
);
3559 view
->parent
= prev
;
3560 } else if (!nomaximize
) {
3561 /* Maximize the current view. */
3562 memset(display
, 0, sizeof(display
));
3564 display
[current_view
] = view
;
3567 /* No prev signals that this is the first loaded view. */
3568 if (prev
&& view
!= prev
) {
3572 /* Resize the view when switching between split- and full-screen,
3573 * or when switching between two different full-screen views. */
3574 if (nviews
!= displayed_views() ||
3575 (nviews
== 1 && base_view
!= display
[0]))
3578 if (view
->ops
->open
) {
3580 end_update(view
, TRUE
);
3581 if (!view
->ops
->open(view
)) {
3582 report("Failed to load %s view", view
->name
);
3585 restore_view_position(view
);
3587 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3588 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3589 report("Failed to load %s view", view
->name
);
3593 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3594 /* Take the title line into account. */
3595 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3597 /* Scroll the view that was split if the current line is
3598 * outside the new limited view. */
3599 do_scroll_view(prev
, lines
);
3602 if (prev
&& view
!= prev
&& split
&& view_is_displayed(prev
)) {
3603 /* "Blur" the previous view. */
3604 update_view_title(prev
);
3607 if (view
->pipe
&& view
->lines
== 0) {
3608 /* Clear the old view and let the incremental updating refill
3611 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3613 } else if (view_is_displayed(view
)) {
3620 open_external_viewer(const char *argv
[], const char *dir
)
3622 def_prog_mode(); /* save current tty modes */
3623 endwin(); /* restore original tty modes */
3624 io_run_fg(argv
, dir
);
3625 fprintf(stderr
, "Press Enter to continue");
3628 redraw_display(TRUE
);
3632 open_mergetool(const char *file
)
3634 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3636 open_external_viewer(mergetool_argv
, opt_cdup
);
3640 open_editor(const char *file
)
3642 const char *editor_argv
[] = { "vi", file
, NULL
};
3645 editor
= getenv("GIT_EDITOR");
3646 if (!editor
&& *opt_editor
)
3647 editor
= opt_editor
;
3649 editor
= getenv("VISUAL");
3651 editor
= getenv("EDITOR");
3655 editor_argv
[0] = editor
;
3656 open_external_viewer(editor_argv
, opt_cdup
);
3660 open_run_request(enum request request
)
3662 struct run_request
*req
= get_run_request(request
);
3663 const char **argv
= NULL
;
3666 report("Unknown run request");
3670 if (format_argv(&argv
, req
->argv
, TRUE
))
3671 open_external_viewer(argv
, NULL
);
3678 * User request switch noodle
3682 view_driver(struct view
*view
, enum request request
)
3686 if (request
== REQ_NONE
)
3689 if (request
> REQ_NONE
) {
3690 open_run_request(request
);
3691 view_request(view
, REQ_REFRESH
);
3695 request
= view_request(view
, request
);
3696 if (request
== REQ_NONE
)
3702 case REQ_MOVE_PAGE_UP
:
3703 case REQ_MOVE_PAGE_DOWN
:
3704 case REQ_MOVE_FIRST_LINE
:
3705 case REQ_MOVE_LAST_LINE
:
3706 move_view(view
, request
);
3709 case REQ_SCROLL_FIRST_COL
:
3710 case REQ_SCROLL_LEFT
:
3711 case REQ_SCROLL_RIGHT
:
3712 case REQ_SCROLL_LINE_DOWN
:
3713 case REQ_SCROLL_LINE_UP
:
3714 case REQ_SCROLL_PAGE_DOWN
:
3715 case REQ_SCROLL_PAGE_UP
:
3716 scroll_view(view
, request
);
3719 case REQ_VIEW_BLAME
:
3721 report("No file chosen, press %s to open tree view",
3722 get_key(view
->keymap
, REQ_VIEW_TREE
));
3725 open_view(view
, request
, OPEN_DEFAULT
);
3730 report("No file chosen, press %s to open tree view",
3731 get_key(view
->keymap
, REQ_VIEW_TREE
));
3734 open_view(view
, request
, OPEN_DEFAULT
);
3737 case REQ_VIEW_PAGER
:
3739 if (!io_open(&VIEW(REQ_VIEW_PAGER
)->io
, ""))
3740 die("Failed to open stdin");
3741 open_view(view
, request
, OPEN_PREPARED
);
3745 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3746 report("No pager content, press %s to run command from prompt",
3747 get_key(view
->keymap
, REQ_PROMPT
));
3750 open_view(view
, request
, OPEN_DEFAULT
);
3753 case REQ_VIEW_STAGE
:
3754 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3755 report("No stage content, press %s to open the status view and choose file",
3756 get_key(view
->keymap
, REQ_VIEW_STATUS
));
3759 open_view(view
, request
, OPEN_DEFAULT
);
3762 case REQ_VIEW_STATUS
:
3763 if (opt_is_inside_work_tree
== FALSE
) {
3764 report("The status view requires a working tree");
3767 open_view(view
, request
, OPEN_DEFAULT
);
3775 case REQ_VIEW_BRANCH
:
3776 open_view(view
, request
, OPEN_DEFAULT
);
3781 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3786 view
= view
->parent
;
3787 line
= view
->lineno
;
3788 move_view(view
, request
);
3789 if (view_is_displayed(view
))
3790 update_view_title(view
);
3791 if (line
!= view
->lineno
)
3792 view_request(view
, REQ_ENTER
);
3794 move_view(view
, request
);
3800 int nviews
= displayed_views();
3801 int next_view
= (current_view
+ 1) % nviews
;
3803 if (next_view
== current_view
) {
3804 report("Only one view is displayed");
3808 current_view
= next_view
;
3809 /* Blur out the title of the previous view. */
3810 update_view_title(view
);
3815 report("Refreshing is not yet supported for the %s view", view
->name
);
3819 if (displayed_views() == 2)
3820 maximize_view(view
);
3827 case REQ_TOGGLE_LINENO
:
3828 toggle_view_option(&opt_line_number
, "line numbers");
3831 case REQ_TOGGLE_DATE
:
3835 case REQ_TOGGLE_AUTHOR
:
3839 case REQ_TOGGLE_REV_GRAPH
:
3840 toggle_view_option(&opt_rev_graph
, "revision graph display");
3843 case REQ_TOGGLE_REFS
:
3844 toggle_view_option(&opt_show_refs
, "reference display");
3847 case REQ_TOGGLE_SORT_FIELD
:
3848 case REQ_TOGGLE_SORT_ORDER
:
3849 report("Sorting is not yet supported for the %s view", view
->name
);
3853 case REQ_SEARCH_BACK
:
3854 search_view(view
, request
);
3859 find_next(view
, request
);
3862 case REQ_STOP_LOADING
:
3863 foreach_view(view
, i
) {
3865 report("Stopped loading the %s view", view
->name
),
3866 end_update(view
, TRUE
);
3870 case REQ_SHOW_VERSION
:
3871 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3874 case REQ_SCREEN_REDRAW
:
3875 redraw_display(TRUE
);
3879 report("Nothing to edit");
3883 report("Nothing to enter");
3886 case REQ_VIEW_CLOSE
:
3887 /* XXX: Mark closed views by letting view->prev point to the
3888 * view itself. Parents to closed view should never be
3890 if (view
->prev
&& view
->prev
!= view
) {
3891 maximize_view(view
->prev
);
3900 report("Unknown key, press %s for help",
3901 get_key(view
->keymap
, REQ_VIEW_HELP
));
3910 * View backend utilities
3920 const enum sort_field
*fields
;
3921 size_t size
, current
;
3925 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3926 #define get_sort_field(state) ((state).fields[(state).current])
3927 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3930 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3931 int (*compare
)(const void *, const void *))
3934 case REQ_TOGGLE_SORT_FIELD
:
3935 state
->current
= (state
->current
+ 1) % state
->size
;
3938 case REQ_TOGGLE_SORT_ORDER
:
3939 state
->reverse
= !state
->reverse
;
3942 die("Not a sort request");
3945 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3949 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3951 /* Small author cache to reduce memory consumption. It uses binary
3952 * search to lookup or find place to position new entries. No entries
3953 * are ever freed. */
3955 get_author(const char *name
)
3957 static const char **authors
;
3958 static size_t authors_size
;
3959 int from
= 0, to
= authors_size
- 1;
3961 while (from
<= to
) {
3962 size_t pos
= (to
+ from
) / 2;
3963 int cmp
= strcmp(name
, authors
[pos
]);
3966 return authors
[pos
];
3974 if (!realloc_authors(&authors
, authors_size
, 1))
3976 name
= strdup(name
);
3980 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3981 authors
[from
] = name
;
3988 parse_timesec(struct time
*time
, const char *sec
)
3990 time
->sec
= (time_t) atol(sec
);
3994 parse_timezone(struct time
*time
, const char *zone
)
3998 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3999 tz
+= ('0' - zone
[2]) * 60 * 60;
4000 tz
+= ('0' - zone
[3]) * 60 * 10;
4001 tz
+= ('0' - zone
[4]) * 60;
4010 /* Parse author lines where the name may be empty:
4011 * author <email@address.tld> 1138474660 +0100
4014 parse_author_line(char *ident
, const char **author
, struct time
*time
)
4016 char *nameend
= strchr(ident
, '<');
4017 char *emailend
= strchr(ident
, '>');
4019 if (nameend
&& emailend
)
4020 *nameend
= *emailend
= 0;
4021 ident
= chomp_string(ident
);
4024 ident
= chomp_string(nameend
+ 1);
4029 *author
= get_author(ident
);
4031 /* Parse epoch and timezone */
4032 if (emailend
&& emailend
[1] == ' ') {
4033 char *secs
= emailend
+ 2;
4034 char *zone
= strchr(secs
, ' ');
4036 parse_timesec(time
, secs
);
4038 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
4039 parse_timezone(time
, zone
+ 1);
4048 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4050 if (opt_line_number
&& draw_lineno(view
, lineno
))
4053 draw_text(view
, line
->type
, line
->data
, TRUE
);
4058 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
4060 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
4061 char ref
[SIZEOF_STR
];
4063 if (!io_run_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
4066 /* This is the only fatal call, since it can "corrupt" the buffer. */
4067 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
4074 add_pager_refs(struct view
*view
, struct line
*line
)
4076 char buf
[SIZEOF_STR
];
4077 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
4078 struct ref_list
*list
;
4079 size_t bufpos
= 0, i
;
4080 const char *sep
= "Refs: ";
4081 bool is_tag
= FALSE
;
4083 assert(line
->type
== LINE_COMMIT
);
4085 list
= get_ref_list(commit_id
);
4087 if (view
->type
== VIEW_DIFF
)
4088 goto try_add_describe_ref
;
4092 for (i
= 0; i
< list
->size
; i
++) {
4093 struct ref
*ref
= list
->refs
[i
];
4094 const char *fmt
= ref
->tag
? "%s[%s]" :
4095 ref
->remote
? "%s<%s>" : "%s%s";
4097 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
4104 if (!is_tag
&& view
->type
== VIEW_DIFF
) {
4105 try_add_describe_ref
:
4106 /* Add <tag>-g<commit_id> "fake" reference. */
4107 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
4114 add_line_text(view
, buf
, LINE_PP_REFS
);
4118 pager_read(struct view
*view
, char *data
)
4125 line
= add_line_text(view
, data
, get_line_type(data
));
4129 if (line
->type
== LINE_COMMIT
&&
4130 (view
->type
== VIEW_DIFF
||
4131 view
->type
== VIEW_LOG
))
4132 add_pager_refs(view
, line
);
4138 pager_request(struct view
*view
, enum request request
, struct line
*line
)
4142 if (request
!= REQ_ENTER
)
4145 if (line
->type
== LINE_COMMIT
&&
4146 (view
->type
== VIEW_LOG
||
4147 view
->type
== VIEW_PAGER
)) {
4148 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
4152 /* Always scroll the view even if it was split. That way
4153 * you can use Enter to scroll through the log view and
4154 * split open each commit diff. */
4155 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
4157 /* FIXME: A minor workaround. Scrolling the view will call report("")
4158 * but if we are scrolling a non-current view this won't properly
4159 * update the view title. */
4161 update_view_title(view
);
4167 pager_grep(struct view
*view
, struct line
*line
)
4169 const char *text
[] = { line
->data
, NULL
};
4171 return grep_text(view
, text
);
4175 pager_select(struct view
*view
, struct line
*line
)
4177 if (line
->type
== LINE_COMMIT
) {
4178 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
4180 if (view
->type
!= VIEW_PAGER
)
4181 string_copy_rev(view
->ref
, text
);
4182 string_copy_rev(ref_commit
, text
);
4186 static struct view_ops pager_ops
= {
4197 static const char *log_argv
[SIZEOF_ARG
] = {
4198 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4202 log_request(struct view
*view
, enum request request
, struct line
*line
)
4207 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
4210 return pager_request(view
, request
, line
);
4214 static struct view_ops log_ops
= {
4225 static const char *diff_argv
[SIZEOF_ARG
] = {
4226 "git", "show", "--pretty=fuller", "--no-color", "--root",
4227 "--patch-with-stat", "--find-copies-harder", "-C",
4228 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4232 diff_read(struct view
*view
, char *data
)
4235 /* Fall back to retry if no diff will be shown. */
4236 if (view
->lines
== 0 && opt_file_args
) {
4237 int pos
= argv_size(view
->argv
)
4238 - argv_size(opt_file_args
) - 1;
4240 if (pos
> 0 && !strcmp(view
->argv
[pos
], "--")) {
4241 for (; view
->argv
[pos
]; pos
++) {
4242 free((void *) view
->argv
[pos
]);
4243 view
->argv
[pos
] = NULL
;
4247 io_done(view
->pipe
);
4248 if (io_run(&view
->io
, IO_RD
, view
->dir
, view
->argv
))
4255 return pager_read(view
, data
);
4258 static struct view_ops diff_ops
= {
4273 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
4276 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
4280 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
4281 help_keymap_hidden
[keymap
] ? '+' : '-',
4282 enum_name(keymap_table
[keymap
]));
4284 line
->other
= keymap
;
4286 return help_keymap_hidden
[keymap
];
4290 help_open_keymap(struct view
*view
, enum keymap keymap
)
4292 const char *group
= NULL
;
4293 char buf
[SIZEOF_STR
];
4295 bool add_title
= TRUE
;
4298 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
4299 const char *key
= NULL
;
4301 if (req_info
[i
].request
== REQ_NONE
)
4304 if (!req_info
[i
].request
) {
4305 group
= req_info
[i
].help
;
4309 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4313 if (add_title
&& help_open_keymap_title(view
, keymap
))
4318 add_line_text(view
, group
, LINE_HELP_GROUP
);
4322 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4323 enum_name(req_info
[i
]), req_info
[i
].help
);
4326 group
= "External commands:";
4328 for (i
= 0; i
< run_requests
; i
++) {
4329 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4333 if (!req
|| req
->keymap
!= keymap
)
4336 key
= get_key_name(req
->key
);
4338 key
= "(no key defined)";
4340 if (add_title
&& help_open_keymap_title(view
, keymap
))
4343 add_line_text(view
, group
, LINE_HELP_GROUP
);
4347 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4348 if (!string_format_from(buf
, &bufpos
, "%s%s",
4349 argc
? " " : "", req
->argv
[argc
]))
4352 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4357 help_open(struct view
*view
)
4362 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4363 add_line_text(view
, "", LINE_DEFAULT
);
4365 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4366 help_open_keymap(view
, keymap
);
4372 help_request(struct view
*view
, enum request request
, struct line
*line
)
4376 if (line
->type
== LINE_HELP_KEYMAP
) {
4377 help_keymap_hidden
[line
->other
] =
4378 !help_keymap_hidden
[line
->other
];
4379 view
->p_restore
= TRUE
;
4380 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4385 return pager_request(view
, request
, line
);
4389 static struct view_ops help_ops
= {
4405 struct tree_stack_entry
{
4406 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4407 unsigned long lineno
; /* Line number to restore */
4408 char *name
; /* Position of name in opt_path */
4411 /* The top of the path stack. */
4412 static struct tree_stack_entry
*tree_stack
= NULL
;
4413 unsigned long tree_lineno
= 0;
4416 pop_tree_stack_entry(void)
4418 struct tree_stack_entry
*entry
= tree_stack
;
4420 tree_lineno
= entry
->lineno
;
4422 tree_stack
= entry
->prev
;
4427 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4429 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4430 size_t pathlen
= strlen(opt_path
);
4435 entry
->prev
= tree_stack
;
4436 entry
->name
= opt_path
+ pathlen
;
4439 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4440 pop_tree_stack_entry();
4444 /* Move the current line to the first tree entry. */
4446 entry
->lineno
= lineno
;
4449 /* Parse output from git-ls-tree(1):
4451 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4454 #define SIZEOF_TREE_ATTR \
4455 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4457 #define SIZEOF_TREE_MODE \
4458 STRING_SIZE("100644 ")
4460 #define TREE_ID_OFFSET \
4461 STRING_SIZE("100644 blob ")
4464 char id
[SIZEOF_REV
];
4466 struct time time
; /* Date from the author ident. */
4467 const char *author
; /* Author of the commit. */
4472 tree_path(const struct line
*line
)
4474 return ((struct tree_entry
*) line
->data
)->name
;
4478 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4480 if (line1
->type
!= line2
->type
)
4481 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4482 return strcmp(tree_path(line1
), tree_path(line2
));
4485 static const enum sort_field tree_sort_fields
[] = {
4486 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4488 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4491 tree_compare(const void *l1
, const void *l2
)
4493 const struct line
*line1
= (const struct line
*) l1
;
4494 const struct line
*line2
= (const struct line
*) l2
;
4495 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4496 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4498 if (line1
->type
== LINE_TREE_HEAD
)
4500 if (line2
->type
== LINE_TREE_HEAD
)
4503 switch (get_sort_field(tree_sort_state
)) {
4505 return sort_order(tree_sort_state
, timecmp(&entry1
->time
, &entry2
->time
));
4507 case ORDERBY_AUTHOR
:
4508 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4512 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4517 static struct line
*
4518 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4519 const char *mode
, const char *id
)
4521 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4522 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4524 if (!entry
|| !line
) {
4529 strncpy(entry
->name
, path
, strlen(path
));
4531 entry
->mode
= strtoul(mode
, NULL
, 8);
4533 string_copy_rev(entry
->id
, id
);
4539 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4541 static const char *author_name
;
4542 static struct time author_time
;
4544 if (!text
&& *read_date
) {
4549 char *path
= *opt_path
? opt_path
: ".";
4550 /* Find next entry to process */
4551 const char *log_file
[] = {
4552 "git", "log", "--no-color", "--pretty=raw",
4553 "--cc", "--raw", view
->id
, "--", path
, NULL
4557 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4558 report("Tree is empty");
4562 if (!start_update(view
, log_file
, opt_cdup
)) {
4563 report("Failed to load tree data");
4570 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4571 parse_author_line(text
+ STRING_SIZE("author "),
4572 &author_name
, &author_time
);
4574 } else if (*text
== ':') {
4576 size_t annotated
= 1;
4579 pos
= strchr(text
, '\t');
4583 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4584 text
+= strlen(opt_path
);
4585 pos
= strchr(text
, '/');
4589 for (i
= 1; i
< view
->lines
; i
++) {
4590 struct line
*line
= &view
->line
[i
];
4591 struct tree_entry
*entry
= line
->data
;
4593 annotated
+= !!entry
->author
;
4594 if (entry
->author
|| strcmp(entry
->name
, text
))
4597 entry
->author
= author_name
;
4598 entry
->time
= author_time
;
4603 if (annotated
== view
->lines
)
4604 io_kill(view
->pipe
);
4610 tree_read(struct view
*view
, char *text
)
4612 static bool read_date
= FALSE
;
4613 struct tree_entry
*data
;
4614 struct line
*entry
, *line
;
4615 enum line_type type
;
4616 size_t textlen
= text
? strlen(text
) : 0;
4617 char *path
= text
+ SIZEOF_TREE_ATTR
;
4619 if (read_date
|| !text
)
4620 return tree_read_date(view
, text
, &read_date
);
4622 if (textlen
<= SIZEOF_TREE_ATTR
)
4624 if (view
->lines
== 0 &&
4625 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4628 /* Strip the path part ... */
4630 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4631 size_t striplen
= strlen(opt_path
);
4633 if (pathlen
> striplen
)
4634 memmove(path
, path
+ striplen
,
4635 pathlen
- striplen
+ 1);
4637 /* Insert "link" to parent directory. */
4638 if (view
->lines
== 1 &&
4639 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4643 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4644 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4649 /* Skip "Directory ..." and ".." line. */
4650 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4651 if (tree_compare_entry(line
, entry
) <= 0)
4654 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4658 for (; line
<= entry
; line
++)
4659 line
->dirty
= line
->cleareol
= 1;
4663 if (tree_lineno
> view
->lineno
) {
4664 view
->lineno
= tree_lineno
;
4672 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4674 struct tree_entry
*entry
= line
->data
;
4676 if (line
->type
== LINE_TREE_HEAD
) {
4677 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4680 if (draw_mode(view
, entry
->mode
))
4683 if (opt_author
&& draw_author(view
, entry
->author
))
4686 if (opt_date
&& draw_date(view
, &entry
->time
))
4689 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4695 open_blob_editor(const char *id
)
4697 const char *blob_argv
[] = { "git", "cat-file", "blob", id
, NULL
};
4698 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4699 int fd
= mkstemp(file
);
4702 report("Failed to create temporary file");
4703 else if (!io_run_append(blob_argv
, fd
))
4704 report("Failed to save blob data to file");
4712 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4714 enum open_flags flags
;
4715 struct tree_entry
*entry
= line
->data
;
4718 case REQ_VIEW_BLAME
:
4719 if (line
->type
!= LINE_TREE_FILE
) {
4720 report("Blame only supported for files");
4724 string_copy(opt_ref
, view
->vid
);
4728 if (line
->type
!= LINE_TREE_FILE
) {
4729 report("Edit only supported for files");
4730 } else if (!is_head_commit(view
->vid
)) {
4731 open_blob_editor(entry
->id
);
4733 open_editor(opt_file
);
4737 case REQ_TOGGLE_SORT_FIELD
:
4738 case REQ_TOGGLE_SORT_ORDER
:
4739 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4744 /* quit view if at top of tree */
4745 return REQ_VIEW_CLOSE
;
4748 line
= &view
->line
[1];
4758 /* Cleanup the stack if the tree view is at a different tree. */
4759 while (!*opt_path
&& tree_stack
)
4760 pop_tree_stack_entry();
4762 switch (line
->type
) {
4764 /* Depending on whether it is a subdirectory or parent link
4765 * mangle the path buffer. */
4766 if (line
== &view
->line
[1] && *opt_path
) {
4767 pop_tree_stack_entry();
4770 const char *basename
= tree_path(line
);
4772 push_tree_stack_entry(basename
, view
->lineno
);
4775 /* Trees and subtrees share the same ID, so they are not not
4776 * unique like blobs. */
4777 flags
= OPEN_RELOAD
;
4778 request
= REQ_VIEW_TREE
;
4781 case LINE_TREE_FILE
:
4782 flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
4783 request
= REQ_VIEW_BLOB
;
4790 open_view(view
, request
, flags
);
4791 if (request
== REQ_VIEW_TREE
)
4792 view
->lineno
= tree_lineno
;
4798 tree_grep(struct view
*view
, struct line
*line
)
4800 struct tree_entry
*entry
= line
->data
;
4801 const char *text
[] = {
4803 opt_author
? entry
->author
: "",
4804 mkdate(&entry
->time
, opt_date
),
4808 return grep_text(view
, text
);
4812 tree_select(struct view
*view
, struct line
*line
)
4814 struct tree_entry
*entry
= line
->data
;
4816 if (line
->type
== LINE_TREE_FILE
) {
4817 string_copy_rev(ref_blob
, entry
->id
);
4818 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4820 } else if (line
->type
!= LINE_TREE_DIR
) {
4824 string_copy_rev(view
->ref
, entry
->id
);
4828 tree_prepare(struct view
*view
)
4830 if (view
->lines
== 0 && opt_prefix
[0]) {
4831 char *pos
= opt_prefix
;
4833 while (pos
&& *pos
) {
4834 char *end
= strchr(pos
, '/');
4838 push_tree_stack_entry(pos
, 0);
4846 } else if (strcmp(view
->vid
, view
->id
)) {
4850 return prepare_io(view
, opt_cdup
, view
->ops
->argv
, TRUE
);
4853 static const char *tree_argv
[SIZEOF_ARG
] = {
4854 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4857 static struct view_ops tree_ops
= {
4870 blob_read(struct view
*view
, char *line
)
4874 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4878 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4882 open_blob_editor(view
->vid
);
4885 return pager_request(view
, request
, line
);
4889 static const char *blob_argv
[SIZEOF_ARG
] = {
4890 "git", "cat-file", "blob", "%(blob)", NULL
4893 static struct view_ops blob_ops
= {
4907 * Loading the blame view is a two phase job:
4909 * 1. File content is read either using opt_file from the
4910 * filesystem or using git-cat-file.
4911 * 2. Then blame information is incrementally added by
4912 * reading output from git-blame.
4915 struct blame_commit
{
4916 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4917 char title
[128]; /* First line of the commit message. */
4918 const char *author
; /* Author of the commit. */
4919 struct time time
; /* Date from the author ident. */
4920 char filename
[128]; /* Name of file. */
4921 char parent_id
[SIZEOF_REV
]; /* Parent/previous SHA1 ID. */
4922 char parent_filename
[128]; /* Parent/previous name of file. */
4926 struct blame_commit
*commit
;
4927 unsigned long lineno
;
4932 blame_open(struct view
*view
)
4934 char path
[SIZEOF_STR
];
4937 if (!view
->prev
&& *opt_prefix
) {
4938 string_copy(path
, opt_file
);
4939 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4943 if (*opt_ref
|| !io_open(&view
->io
, "%s%s", opt_cdup
, opt_file
)) {
4944 const char *blame_cat_file_argv
[] = {
4945 "git", "cat-file", "blob", path
, NULL
4948 if (!string_format(path
, "%s:%s", opt_ref
, opt_file
) ||
4949 !start_update(view
, blame_cat_file_argv
, opt_cdup
))
4953 /* First pass: remove multiple references to the same commit. */
4954 for (i
= 0; i
< view
->lines
; i
++) {
4955 struct blame
*blame
= view
->line
[i
].data
;
4957 if (blame
->commit
&& blame
->commit
->id
[0])
4958 blame
->commit
->id
[0] = 0;
4960 blame
->commit
= NULL
;
4963 /* Second pass: free existing references. */
4964 for (i
= 0; i
< view
->lines
; i
++) {
4965 struct blame
*blame
= view
->line
[i
].data
;
4968 free(blame
->commit
);
4971 setup_update(view
, opt_file
);
4972 string_format(view
->ref
, "%s ...", opt_file
);
4977 static struct blame_commit
*
4978 get_blame_commit(struct view
*view
, const char *id
)
4982 for (i
= 0; i
< view
->lines
; i
++) {
4983 struct blame
*blame
= view
->line
[i
].data
;
4988 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4989 return blame
->commit
;
4993 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4996 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
5002 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
5004 const char *pos
= *posref
;
5007 pos
= strchr(pos
+ 1, ' ');
5008 if (!pos
|| !isdigit(pos
[1]))
5010 *number
= atoi(pos
+ 1);
5011 if (*number
< min
|| *number
> max
)
5018 static struct blame_commit
*
5019 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
5021 struct blame_commit
*commit
;
5022 struct blame
*blame
;
5023 const char *pos
= text
+ SIZEOF_REV
- 2;
5024 size_t orig_lineno
= 0;
5028 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
5031 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
5032 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
5033 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
5036 commit
= get_blame_commit(view
, text
);
5042 struct line
*line
= &view
->line
[lineno
+ group
- 1];
5045 blame
->commit
= commit
;
5046 blame
->lineno
= orig_lineno
+ group
- 1;
5054 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
5057 const char *blame_argv
[] = {
5058 "git", "blame", "--incremental",
5059 *opt_ref
? opt_ref
: "--incremental", "--", opt_file
, NULL
5062 if (view
->lines
== 0 && !view
->prev
)
5063 die("No blame exist for %s", view
->vid
);
5065 if (view
->lines
== 0 || !start_update(view
, blame_argv
, opt_cdup
)) {
5066 report("Failed to load blame data");
5074 size_t linelen
= strlen(line
);
5075 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
5080 blame
->commit
= NULL
;
5081 strncpy(blame
->text
, line
, linelen
);
5082 blame
->text
[linelen
] = 0;
5083 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
5088 match_blame_header(const char *name
, char **line
)
5090 size_t namelen
= strlen(name
);
5091 bool matched
= !strncmp(name
, *line
, namelen
);
5100 blame_read(struct view
*view
, char *line
)
5102 static struct blame_commit
*commit
= NULL
;
5103 static int blamed
= 0;
5104 static bool read_file
= TRUE
;
5107 return blame_read_file(view
, line
, &read_file
);
5114 string_format(view
->ref
, "%s", view
->vid
);
5115 if (view_is_displayed(view
)) {
5116 update_view_title(view
);
5117 redraw_view_from(view
, 0);
5123 commit
= parse_blame_commit(view
, line
, &blamed
);
5124 string_format(view
->ref
, "%s %2d%%", view
->vid
,
5125 view
->lines
? blamed
* 100 / view
->lines
: 0);
5127 } else if (match_blame_header("author ", &line
)) {
5128 commit
->author
= get_author(line
);
5130 } else if (match_blame_header("author-time ", &line
)) {
5131 parse_timesec(&commit
->time
, line
);
5133 } else if (match_blame_header("author-tz ", &line
)) {
5134 parse_timezone(&commit
->time
, line
);
5136 } else if (match_blame_header("summary ", &line
)) {
5137 string_ncopy(commit
->title
, line
, strlen(line
));
5139 } else if (match_blame_header("previous ", &line
)) {
5140 if (strlen(line
) <= SIZEOF_REV
)
5142 string_copy_rev(commit
->parent_id
, line
);
5144 string_ncopy(commit
->parent_filename
, line
, strlen(line
));
5146 } else if (match_blame_header("filename ", &line
)) {
5147 string_ncopy(commit
->filename
, line
, strlen(line
));
5155 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5157 struct blame
*blame
= line
->data
;
5158 struct time
*time
= NULL
;
5159 const char *id
= NULL
, *author
= NULL
;
5161 if (blame
->commit
&& *blame
->commit
->filename
) {
5162 id
= blame
->commit
->id
;
5163 author
= blame
->commit
->author
;
5164 time
= &blame
->commit
->time
;
5167 if (opt_date
&& draw_date(view
, time
))
5170 if (opt_author
&& draw_author(view
, author
))
5173 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
5176 if (draw_lineno(view
, lineno
))
5179 draw_text(view
, LINE_DEFAULT
, blame
->text
, TRUE
);
5184 check_blame_commit(struct blame
*blame
, bool check_null_id
)
5187 report("Commit data not loaded yet");
5188 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5189 report("No commit exist for the selected line");
5196 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
5198 char from
[SIZEOF_REF
+ SIZEOF_STR
];
5199 char to
[SIZEOF_REF
+ SIZEOF_STR
];
5200 const char *diff_tree_argv
[] = {
5201 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5202 "-U0", from
, to
, "--", NULL
5205 int parent_lineno
= -1;
5206 int blamed_lineno
= -1;
5209 if (!string_format(from
, "%s:%s", opt_ref
, opt_file
) ||
5210 !string_format(to
, "%s:%s", blame
->commit
->id
, blame
->commit
->filename
) ||
5211 !io_run(&io
, IO_RD
, NULL
, diff_tree_argv
))
5214 while ((line
= io_get(&io
, '\n', TRUE
))) {
5216 char *pos
= strchr(line
, '+');
5218 parent_lineno
= atoi(line
+ 4);
5220 blamed_lineno
= atoi(pos
+ 1);
5222 } else if (*line
== '+' && parent_lineno
!= -1) {
5223 if (blame
->lineno
== blamed_lineno
- 1 &&
5224 !strcmp(blame
->text
, line
+ 1)) {
5225 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
5236 blame_request(struct view
*view
, enum request request
, struct line
*line
)
5238 enum open_flags flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
5239 struct blame
*blame
= line
->data
;
5242 case REQ_VIEW_BLAME
:
5243 if (check_blame_commit(blame
, TRUE
)) {
5244 string_copy(opt_ref
, blame
->commit
->id
);
5245 string_copy(opt_file
, blame
->commit
->filename
);
5247 view
->lineno
= blame
->lineno
;
5248 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5253 if (!check_blame_commit(blame
, TRUE
))
5255 if (!*blame
->commit
->parent_id
) {
5256 report("The selected commit has no parents");
5258 string_copy_rev(opt_ref
, blame
->commit
->parent_id
);
5259 string_copy(opt_file
, blame
->commit
->parent_filename
);
5260 setup_blame_parent_line(view
, blame
);
5261 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5266 if (!check_blame_commit(blame
, FALSE
))
5269 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
5270 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
5273 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
5274 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
5275 const char *diff_index_argv
[] = {
5276 "git", "diff-index", "--root", "--patch-with-stat",
5277 "-C", "-M", "HEAD", "--", view
->vid
, NULL
5280 if (!*blame
->commit
->parent_id
) {
5281 diff_index_argv
[1] = "diff";
5282 diff_index_argv
[2] = "--no-color";
5283 diff_index_argv
[6] = "--";
5284 diff_index_argv
[7] = "/dev/null";
5287 if (!prepare_update(diff
, diff_index_argv
, NULL
)) {
5288 report("Failed to allocate diff command");
5291 flags
|= OPEN_PREPARED
;
5294 open_view(view
, REQ_VIEW_DIFF
, flags
);
5295 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5296 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
5307 blame_grep(struct view
*view
, struct line
*line
)
5309 struct blame
*blame
= line
->data
;
5310 struct blame_commit
*commit
= blame
->commit
;
5311 const char *text
[] = {
5313 commit
? commit
->title
: "",
5314 commit
? commit
->id
: "",
5315 commit
&& opt_author
? commit
->author
: "",
5316 commit
? mkdate(&commit
->time
, opt_date
) : "",
5320 return grep_text(view
, text
);
5324 blame_select(struct view
*view
, struct line
*line
)
5326 struct blame
*blame
= line
->data
;
5327 struct blame_commit
*commit
= blame
->commit
;
5332 if (!strcmp(commit
->id
, NULL_ID
))
5333 string_ncopy(ref_commit
, "HEAD", 4);
5335 string_copy_rev(ref_commit
, commit
->id
);
5338 static struct view_ops blame_ops
= {
5354 const char *author
; /* Author of the last commit. */
5355 struct time time
; /* Date of the last activity. */
5356 const struct ref
*ref
; /* Name and commit ID information. */
5359 static const struct ref branch_all
;
5361 static const enum sort_field branch_sort_fields
[] = {
5362 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5364 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5367 branch_compare(const void *l1
, const void *l2
)
5369 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5370 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5372 switch (get_sort_field(branch_sort_state
)) {
5374 return sort_order(branch_sort_state
, timecmp(&branch1
->time
, &branch2
->time
));
5376 case ORDERBY_AUTHOR
:
5377 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5381 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5386 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5388 struct branch
*branch
= line
->data
;
5389 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5391 if (opt_date
&& draw_date(view
, &branch
->time
))
5394 if (opt_author
&& draw_author(view
, branch
->author
))
5397 draw_text(view
, type
, branch
->ref
== &branch_all
? "All branches" : branch
->ref
->name
, TRUE
);
5402 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5404 struct branch
*branch
= line
->data
;
5409 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5412 case REQ_TOGGLE_SORT_FIELD
:
5413 case REQ_TOGGLE_SORT_ORDER
:
5414 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5419 const struct ref
*ref
= branch
->ref
;
5420 const char *all_branches_argv
[] = {
5421 "git", "log", "--no-color", "--pretty=raw", "--parents",
5423 ref
== &branch_all
? "--all" : ref
->name
, NULL
5425 struct view
*main_view
= VIEW(REQ_VIEW_MAIN
);
5427 if (!prepare_update(main_view
, all_branches_argv
, NULL
))
5428 report("Failed to load view of all branches");
5430 open_view(view
, REQ_VIEW_MAIN
, OPEN_PREPARED
| OPEN_SPLIT
);
5439 branch_read(struct view
*view
, char *line
)
5441 static char id
[SIZEOF_REV
];
5442 struct branch
*reference
;
5448 switch (get_line_type(line
)) {
5450 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5454 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5455 struct branch
*branch
= view
->line
[i
].data
;
5457 if (strcmp(branch
->ref
->id
, id
))
5460 view
->line
[i
].dirty
= TRUE
;
5462 branch
->author
= reference
->author
;
5463 branch
->time
= reference
->time
;
5467 parse_author_line(line
+ STRING_SIZE("author "),
5468 &branch
->author
, &branch
->time
);
5480 branch_open_visitor(void *data
, const struct ref
*ref
)
5482 struct view
*view
= data
;
5483 struct branch
*branch
;
5485 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5488 branch
= calloc(1, sizeof(*branch
));
5493 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5497 branch_open(struct view
*view
)
5499 const char *branch_log
[] = {
5500 "git", "log", "--no-color", "--pretty=raw",
5501 "--simplify-by-decoration", "--all", NULL
5504 if (!start_update(view
, branch_log
, NULL
)) {
5505 report("Failed to load branch data");
5509 setup_update(view
, view
->id
);
5510 branch_open_visitor(view
, &branch_all
);
5511 foreach_ref(branch_open_visitor
, view
);
5512 view
->p_restore
= TRUE
;
5518 branch_grep(struct view
*view
, struct line
*line
)
5520 struct branch
*branch
= line
->data
;
5521 const char *text
[] = {
5527 return grep_text(view
, text
);
5531 branch_select(struct view
*view
, struct line
*line
)
5533 struct branch
*branch
= line
->data
;
5535 string_copy_rev(view
->ref
, branch
->ref
->id
);
5536 string_copy_rev(ref_commit
, branch
->ref
->id
);
5537 string_copy_rev(ref_head
, branch
->ref
->id
);
5538 string_copy_rev(ref_branch
, branch
->ref
->name
);
5541 static struct view_ops branch_ops
= {
5560 char rev
[SIZEOF_REV
];
5561 char name
[SIZEOF_STR
];
5565 char rev
[SIZEOF_REV
];
5566 char name
[SIZEOF_STR
];
5570 static char status_onbranch
[SIZEOF_STR
];
5571 static struct status stage_status
;
5572 static enum line_type stage_line_type
;
5573 static size_t stage_chunks
;
5574 static int *stage_chunk
;
5576 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5578 /* This should work even for the "On branch" line. */
5580 status_has_none(struct view
*view
, struct line
*line
)
5582 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5585 /* Get fields from the diff line:
5586 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5589 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5591 const char *old_mode
= buf
+ 1;
5592 const char *new_mode
= buf
+ 8;
5593 const char *old_rev
= buf
+ 15;
5594 const char *new_rev
= buf
+ 56;
5595 const char *status
= buf
+ 97;
5598 old_mode
[-1] != ':' ||
5599 new_mode
[-1] != ' ' ||
5600 old_rev
[-1] != ' ' ||
5601 new_rev
[-1] != ' ' ||
5605 file
->status
= *status
;
5607 string_copy_rev(file
->old
.rev
, old_rev
);
5608 string_copy_rev(file
->new.rev
, new_rev
);
5610 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5611 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5613 file
->old
.name
[0] = file
->new.name
[0] = 0;
5619 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5621 struct status
*unmerged
= NULL
;
5625 if (!io_run(&io
, IO_RD
, opt_cdup
, argv
))
5628 add_line_data(view
, NULL
, type
);
5630 while ((buf
= io_get(&io
, 0, TRUE
))) {
5631 struct status
*file
= unmerged
;
5634 file
= calloc(1, sizeof(*file
));
5635 if (!file
|| !add_line_data(view
, file
, type
))
5639 /* Parse diff info part. */
5641 file
->status
= status
;
5643 string_copy(file
->old
.rev
, NULL_ID
);
5645 } else if (!file
->status
|| file
== unmerged
) {
5646 if (!status_get_diff(file
, buf
, strlen(buf
)))
5649 buf
= io_get(&io
, 0, TRUE
);
5653 /* Collapse all modified entries that follow an
5654 * associated unmerged entry. */
5655 if (unmerged
== file
) {
5656 unmerged
->status
= 'U';
5658 } else if (file
->status
== 'U') {
5663 /* Grab the old name for rename/copy. */
5664 if (!*file
->old
.name
&&
5665 (file
->status
== 'R' || file
->status
== 'C')) {
5666 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5668 buf
= io_get(&io
, 0, TRUE
);
5673 /* git-ls-files just delivers a NUL separated list of
5674 * file names similar to the second half of the
5675 * git-diff-* output. */
5676 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5677 if (!*file
->old
.name
)
5678 string_copy(file
->old
.name
, file
->new.name
);
5682 if (io_error(&io
)) {
5688 if (!view
->line
[view
->lines
- 1].data
)
5689 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5695 /* Don't show unmerged entries in the staged section. */
5696 static const char *status_diff_index_argv
[] = {
5697 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5698 "--cached", "-M", "HEAD", NULL
5701 static const char *status_diff_files_argv
[] = {
5702 "git", "diff-files", "-z", NULL
5705 static const char *status_list_other_argv
[] = {
5706 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix
, NULL
, NULL
,
5709 static const char *status_list_no_head_argv
[] = {
5710 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5713 static const char *update_index_argv
[] = {
5714 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5717 /* Restore the previous line number to stay in the context or select a
5718 * line with something that can be updated. */
5720 status_restore(struct view
*view
)
5722 if (view
->p_lineno
>= view
->lines
)
5723 view
->p_lineno
= view
->lines
- 1;
5724 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5726 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5729 /* If the above fails, always skip the "On branch" line. */
5730 if (view
->p_lineno
< view
->lines
)
5731 view
->lineno
= view
->p_lineno
;
5735 if (view
->lineno
< view
->offset
)
5736 view
->offset
= view
->lineno
;
5737 else if (view
->offset
+ view
->height
<= view
->lineno
)
5738 view
->offset
= view
->lineno
- view
->height
+ 1;
5740 view
->p_restore
= FALSE
;
5744 status_update_onbranch(void)
5746 static const char *paths
[][2] = {
5747 { "rebase-apply/rebasing", "Rebasing" },
5748 { "rebase-apply/applying", "Applying mailbox" },
5749 { "rebase-apply/", "Rebasing mailbox" },
5750 { "rebase-merge/interactive", "Interactive rebase" },
5751 { "rebase-merge/", "Rebase merge" },
5752 { "MERGE_HEAD", "Merging" },
5753 { "BISECT_LOG", "Bisecting" },
5754 { "HEAD", "On branch" },
5756 char buf
[SIZEOF_STR
];
5760 if (is_initial_commit()) {
5761 string_copy(status_onbranch
, "Initial commit");
5765 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5766 char *head
= opt_head
;
5768 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5769 lstat(buf
, &stat
) < 0)
5775 if (io_open(&io
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5776 io_read_buf(&io
, buf
, sizeof(buf
))) {
5778 if (!prefixcmp(head
, "refs/heads/"))
5779 head
+= STRING_SIZE("refs/heads/");
5783 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5784 string_copy(status_onbranch
, opt_head
);
5788 string_copy(status_onbranch
, "Not currently on any branch");
5791 /* First parse staged info using git-diff-index(1), then parse unstaged
5792 * info using git-diff-files(1), and finally untracked files using
5793 * git-ls-files(1). */
5795 status_open(struct view
*view
)
5799 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5800 status_update_onbranch();
5802 io_run_bg(update_index_argv
);
5804 if (is_initial_commit()) {
5805 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5807 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5811 if (!opt_untracked_dirs_content
)
5812 status_list_other_argv
[ARRAY_SIZE(status_list_other_argv
) - 2] = "--directory";
5814 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5815 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5818 /* Restore the exact position or use the specialized restore
5820 if (!view
->p_restore
)
5821 status_restore(view
);
5826 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5828 struct status
*status
= line
->data
;
5829 enum line_type type
;
5833 switch (line
->type
) {
5834 case LINE_STAT_STAGED
:
5835 type
= LINE_STAT_SECTION
;
5836 text
= "Changes to be committed:";
5839 case LINE_STAT_UNSTAGED
:
5840 type
= LINE_STAT_SECTION
;
5841 text
= "Changed but not updated:";
5844 case LINE_STAT_UNTRACKED
:
5845 type
= LINE_STAT_SECTION
;
5846 text
= "Untracked files:";
5849 case LINE_STAT_NONE
:
5850 type
= LINE_DEFAULT
;
5851 text
= " (no files)";
5854 case LINE_STAT_HEAD
:
5855 type
= LINE_STAT_HEAD
;
5856 text
= status_onbranch
;
5863 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5865 buf
[0] = status
->status
;
5866 if (draw_text(view
, line
->type
, buf
, TRUE
))
5868 type
= LINE_DEFAULT
;
5869 text
= status
->new.name
;
5872 draw_text(view
, type
, text
, TRUE
);
5877 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5879 if (displayed_views() == 2 || display
[current_view
] != view
)
5880 maximize_view(view
);
5881 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5886 status_enter(struct view
*view
, struct line
*line
)
5888 struct status
*status
= line
->data
;
5889 const char *oldpath
= status
? status
->old
.name
: NULL
;
5890 /* Diffs for unmerged entries are empty when passing the new
5891 * path, so leave it empty. */
5892 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5894 enum open_flags split
;
5895 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5897 if (line
->type
== LINE_STAT_NONE
||
5898 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5899 report("No file to diff");
5903 switch (line
->type
) {
5904 case LINE_STAT_STAGED
:
5905 if (is_initial_commit()) {
5906 const char *no_head_diff_argv
[] = {
5907 "git", "diff", "--no-color", "--patch-with-stat",
5908 "--", "/dev/null", newpath
, NULL
5911 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
))
5912 return status_load_error(view
, stage
, newpath
);
5914 const char *index_show_argv
[] = {
5915 "git", "diff-index", "--root", "--patch-with-stat",
5916 "-C", "-M", "--cached", "HEAD", "--",
5917 oldpath
, newpath
, NULL
5920 if (!prepare_update(stage
, index_show_argv
, opt_cdup
))
5921 return status_load_error(view
, stage
, newpath
);
5925 info
= "Staged changes to %s";
5927 info
= "Staged changes";
5930 case LINE_STAT_UNSTAGED
:
5932 const char *files_show_argv
[] = {
5933 "git", "diff-files", "--root", "--patch-with-stat",
5934 "-C", "-M", "--", oldpath
, newpath
, NULL
5937 if (!prepare_update(stage
, files_show_argv
, opt_cdup
))
5938 return status_load_error(view
, stage
, newpath
);
5940 info
= "Unstaged changes to %s";
5942 info
= "Unstaged changes";
5945 case LINE_STAT_UNTRACKED
:
5947 report("No file to show");
5951 if (!suffixcmp(status
->new.name
, -1, "/")) {
5952 report("Cannot display a directory");
5956 if (!prepare_update_file(stage
, newpath
))
5957 return status_load_error(view
, stage
, newpath
);
5958 info
= "Untracked file %s";
5961 case LINE_STAT_HEAD
:
5965 die("line type %d not handled in switch", line
->type
);
5968 split
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
5969 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5970 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5972 stage_status
= *status
;
5974 memset(&stage_status
, 0, sizeof(stage_status
));
5977 stage_line_type
= line
->type
;
5979 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5986 status_exists(struct status
*status
, enum line_type type
)
5988 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5989 unsigned long lineno
;
5991 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5992 struct line
*line
= &view
->line
[lineno
];
5993 struct status
*pos
= line
->data
;
5995 if (line
->type
!= type
)
5997 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5998 select_view_line(view
, lineno
);
6001 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
6002 select_view_line(view
, lineno
);
6012 status_update_prepare(struct io
*io
, enum line_type type
)
6014 const char *staged_argv
[] = {
6015 "git", "update-index", "-z", "--index-info", NULL
6017 const char *others_argv
[] = {
6018 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6022 case LINE_STAT_STAGED
:
6023 return io_run(io
, IO_WR
, opt_cdup
, staged_argv
);
6025 case LINE_STAT_UNSTAGED
:
6026 case LINE_STAT_UNTRACKED
:
6027 return io_run(io
, IO_WR
, opt_cdup
, others_argv
);
6030 die("line type %d not handled in switch", type
);
6036 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
6038 char buf
[SIZEOF_STR
];
6042 case LINE_STAT_STAGED
:
6043 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
6046 status
->old
.name
, 0))
6050 case LINE_STAT_UNSTAGED
:
6051 case LINE_STAT_UNTRACKED
:
6052 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
6057 die("line type %d not handled in switch", type
);
6060 return io_write(io
, buf
, bufsize
);
6064 status_update_file(struct status
*status
, enum line_type type
)
6069 if (!status_update_prepare(&io
, type
))
6072 result
= status_update_write(&io
, status
, type
);
6073 return io_done(&io
) && result
;
6077 status_update_files(struct view
*view
, struct line
*line
)
6079 char buf
[sizeof(view
->ref
)];
6082 struct line
*pos
= view
->line
+ view
->lines
;
6085 int cursor_y
= -1, cursor_x
= -1;
6087 if (!status_update_prepare(&io
, line
->type
))
6090 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
6093 string_copy(buf
, view
->ref
);
6094 getsyx(cursor_y
, cursor_x
);
6095 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
6096 int almost_done
= file
* 100 / files
;
6098 if (almost_done
> done
) {
6100 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
6102 update_view_title(view
);
6103 setsyx(cursor_y
, cursor_x
);
6106 result
= status_update_write(&io
, line
->data
, line
->type
);
6108 string_copy(view
->ref
, buf
);
6110 return io_done(&io
) && result
;
6114 status_update(struct view
*view
)
6116 struct line
*line
= &view
->line
[view
->lineno
];
6118 assert(view
->lines
);
6121 /* This should work even for the "On branch" line. */
6122 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
6123 report("Nothing to update");
6127 if (!status_update_files(view
, line
+ 1)) {
6128 report("Failed to update file status");
6132 } else if (!status_update_file(line
->data
, line
->type
)) {
6133 report("Failed to update file status");
6141 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
6143 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
6144 if (type
== LINE_STAT_STAGED
) {
6145 report("Cannot revert changes to staged files");
6146 } else if (type
== LINE_STAT_UNTRACKED
) {
6147 report("Cannot revert changes to untracked files");
6148 } else if (has_none
) {
6149 report("Nothing to revert");
6151 report("Cannot revert changes to multiple files");
6154 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6155 char mode
[10] = "100644";
6156 const char *reset_argv
[] = {
6157 "git", "update-index", "--cacheinfo", mode
,
6158 status
->old
.rev
, status
->old
.name
, NULL
6160 const char *checkout_argv
[] = {
6161 "git", "checkout", "--", status
->old
.name
, NULL
6164 if (status
->status
== 'U') {
6165 string_format(mode
, "%5o", status
->old
.mode
);
6167 if (status
->old
.mode
== 0 && status
->new.mode
== 0) {
6168 reset_argv
[2] = "--force-remove";
6169 reset_argv
[3] = status
->old
.name
;
6170 reset_argv
[4] = NULL
;
6173 if (!io_run_fg(reset_argv
, opt_cdup
))
6175 if (status
->old
.mode
== 0 && status
->new.mode
== 0)
6179 return io_run_fg(checkout_argv
, opt_cdup
);
6186 status_request(struct view
*view
, enum request request
, struct line
*line
)
6188 struct status
*status
= line
->data
;
6191 case REQ_STATUS_UPDATE
:
6192 if (!status_update(view
))
6196 case REQ_STATUS_REVERT
:
6197 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
6201 case REQ_STATUS_MERGE
:
6202 if (!status
|| status
->status
!= 'U') {
6203 report("Merging only possible for files with unmerged status ('U').");
6206 open_mergetool(status
->new.name
);
6212 if (status
->status
== 'D') {
6213 report("File has been deleted.");
6217 open_editor(status
->new.name
);
6220 case REQ_VIEW_BLAME
:
6226 /* After returning the status view has been split to
6227 * show the stage view. No further reloading is
6229 return status_enter(view
, line
);
6232 /* Simply reload the view. */
6239 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
6245 status_select(struct view
*view
, struct line
*line
)
6247 struct status
*status
= line
->data
;
6248 char file
[SIZEOF_STR
] = "all files";
6252 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
6255 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
6258 switch (line
->type
) {
6259 case LINE_STAT_STAGED
:
6260 text
= "Press %s to unstage %s for commit";
6263 case LINE_STAT_UNSTAGED
:
6264 text
= "Press %s to stage %s for commit";
6267 case LINE_STAT_UNTRACKED
:
6268 text
= "Press %s to stage %s for addition";
6271 case LINE_STAT_HEAD
:
6272 case LINE_STAT_NONE
:
6273 text
= "Nothing to update";
6277 die("line type %d not handled in switch", line
->type
);
6280 if (status
&& status
->status
== 'U') {
6281 text
= "Press %s to resolve conflict in %s";
6282 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
6285 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
6288 string_format(view
->ref
, text
, key
, file
);
6290 string_copy(opt_file
, status
->new.name
);
6294 status_grep(struct view
*view
, struct line
*line
)
6296 struct status
*status
= line
->data
;
6299 const char buf
[2] = { status
->status
, 0 };
6300 const char *text
[] = { status
->new.name
, buf
, NULL
};
6302 return grep_text(view
, text
);
6308 static struct view_ops status_ops
= {
6321 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
6323 while (line
< end
) {
6324 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
6325 !io_write(io
, "\n", 1))
6328 if (line
->type
== LINE_DIFF_CHUNK
||
6329 line
->type
== LINE_DIFF_HEADER
)
6336 static struct line
*
6337 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
6339 for (; view
->line
< line
; line
--)
6340 if (line
->type
== type
)
6347 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
6349 const char *apply_argv
[SIZEOF_ARG
] = {
6350 "git", "apply", "--whitespace=nowarn", NULL
6352 struct line
*diff_hdr
;
6356 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6361 apply_argv
[argc
++] = "--cached";
6362 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6363 apply_argv
[argc
++] = "-R";
6364 apply_argv
[argc
++] = "-";
6365 apply_argv
[argc
++] = NULL
;
6366 if (!io_run(&io
, IO_WR
, opt_cdup
, apply_argv
))
6369 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6370 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6374 io_run_bg(update_index_argv
);
6376 return chunk
? TRUE
: FALSE
;
6380 stage_update(struct view
*view
, struct line
*line
)
6382 struct line
*chunk
= NULL
;
6384 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6385 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6388 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6389 report("Failed to apply chunk");
6393 } else if (!stage_status
.status
) {
6394 view
= VIEW(REQ_VIEW_STATUS
);
6396 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6397 if (line
->type
== stage_line_type
)
6400 if (!status_update_files(view
, line
+ 1)) {
6401 report("Failed to update files");
6405 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6406 report("Failed to update file");
6414 stage_revert(struct view
*view
, struct line
*line
)
6416 struct line
*chunk
= NULL
;
6418 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6419 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6422 if (!prompt_yesno("Are you sure you want to revert changes?"))
6425 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6426 report("Failed to revert chunk");
6432 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6433 stage_line_type
, FALSE
);
6439 stage_next(struct view
*view
, struct line
*line
)
6443 if (!stage_chunks
) {
6444 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6445 if (line
->type
!= LINE_DIFF_CHUNK
)
6448 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6449 report("Allocation failure");
6453 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6457 for (i
= 0; i
< stage_chunks
; i
++) {
6458 if (stage_chunk
[i
] > view
->lineno
) {
6459 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6460 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6465 report("No next chunk found");
6469 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6472 case REQ_STATUS_UPDATE
:
6473 if (!stage_update(view
, line
))
6477 case REQ_STATUS_REVERT
:
6478 if (!stage_revert(view
, line
))
6482 case REQ_STAGE_NEXT
:
6483 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6484 report("File is untracked; press %s to add",
6485 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6488 stage_next(view
, line
);
6492 if (!stage_status
.new.name
[0])
6494 if (stage_status
.status
== 'D') {
6495 report("File has been deleted.");
6499 open_editor(stage_status
.new.name
);
6503 /* Reload everything ... */
6506 case REQ_VIEW_BLAME
:
6507 if (stage_status
.new.name
[0]) {
6508 string_copy(opt_file
, stage_status
.new.name
);
6514 return pager_request(view
, request
, line
);
6520 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6521 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6523 /* Check whether the staged entry still exists, and close the
6524 * stage view if it doesn't. */
6525 if (!status_exists(&stage_status
, stage_line_type
)) {
6526 status_restore(VIEW(REQ_VIEW_STATUS
));
6527 return REQ_VIEW_CLOSE
;
6530 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6531 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6532 report("Cannot display a directory");
6536 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6537 report("Failed to open file: %s", strerror(errno
));
6541 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6546 static struct view_ops stage_ops
= {
6563 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6564 char title
[128]; /* First line of the commit message. */
6565 const char *author
; /* Author of the commit. */
6566 struct time time
; /* Date from the author ident. */
6567 struct ref_list
*refs
; /* Repository references. */
6568 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6569 size_t graph_size
; /* The width of the graph array. */
6570 bool has_parents
; /* Rewritten --parents seen. */
6573 /* Size of rev graph with no "padding" columns */
6574 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6577 struct rev_graph
*prev
, *next
, *parents
;
6578 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6580 struct commit
*commit
;
6582 unsigned int boundary
:1;
6585 /* Parents of the commit being visualized. */
6586 static struct rev_graph graph_parents
[4];
6588 /* The current stack of revisions on the graph. */
6589 static struct rev_graph graph_stacks
[4] = {
6590 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6591 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6592 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6593 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6597 graph_parent_is_merge(struct rev_graph
*graph
)
6599 return graph
->parents
->size
> 1;
6603 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6605 struct commit
*commit
= graph
->commit
;
6607 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6608 commit
->graph
[commit
->graph_size
++] = symbol
;
6612 clear_rev_graph(struct rev_graph
*graph
)
6614 graph
->boundary
= 0;
6615 graph
->size
= graph
->pos
= 0;
6616 graph
->commit
= NULL
;
6617 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6621 done_rev_graph(struct rev_graph
*graph
)
6623 if (graph_parent_is_merge(graph
) &&
6624 graph
->pos
< graph
->size
- 1 &&
6625 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6626 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6628 graph
->commit
->graph_size
= i
* 2;
6629 while (i
< graph
->next
->size
- 1) {
6630 append_to_rev_graph(graph
, ' ');
6631 append_to_rev_graph(graph
, '\\');
6636 clear_rev_graph(graph
);
6640 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6644 /* "Collapse" duplicate parents lines.
6646 * FIXME: This needs to also update update the drawn graph but
6647 * for now it just serves as a method for pruning graph lines. */
6648 for (i
= 0; i
< graph
->size
; i
++)
6649 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6652 if (graph
->size
< SIZEOF_REVITEMS
) {
6653 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6658 get_rev_graph_symbol(struct rev_graph
*graph
)
6662 if (graph
->boundary
)
6663 symbol
= REVGRAPH_BOUND
;
6664 else if (graph
->parents
->size
== 0)
6665 symbol
= REVGRAPH_INIT
;
6666 else if (graph_parent_is_merge(graph
))
6667 symbol
= REVGRAPH_MERGE
;
6668 else if (graph
->pos
>= graph
->size
)
6669 symbol
= REVGRAPH_BRANCH
;
6671 symbol
= REVGRAPH_COMMIT
;
6677 draw_rev_graph(struct rev_graph
*graph
)
6680 chtype separator
, line
;
6682 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6683 static struct rev_filler fillers
[] = {
6689 chtype symbol
= get_rev_graph_symbol(graph
);
6690 struct rev_filler
*filler
;
6693 fillers
[DEFAULT
].line
= opt_line_graphics
? ACS_VLINE
: '|';
6694 filler
= &fillers
[DEFAULT
];
6696 for (i
= 0; i
< graph
->pos
; i
++) {
6697 append_to_rev_graph(graph
, filler
->line
);
6698 if (graph_parent_is_merge(graph
->prev
) &&
6699 graph
->prev
->pos
== i
)
6700 filler
= &fillers
[RSHARP
];
6702 append_to_rev_graph(graph
, filler
->separator
);
6705 /* Place the symbol for this revision. */
6706 append_to_rev_graph(graph
, symbol
);
6708 if (graph
->prev
->size
> graph
->size
)
6709 filler
= &fillers
[RDIAG
];
6711 filler
= &fillers
[DEFAULT
];
6715 for (; i
< graph
->size
; i
++) {
6716 append_to_rev_graph(graph
, filler
->separator
);
6717 append_to_rev_graph(graph
, filler
->line
);
6718 if (graph_parent_is_merge(graph
->prev
) &&
6719 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6720 filler
= &fillers
[RSHARP
];
6721 if (graph
->prev
->size
> graph
->size
)
6722 filler
= &fillers
[LDIAG
];
6725 if (graph
->prev
->size
> graph
->size
) {
6726 append_to_rev_graph(graph
, filler
->separator
);
6727 if (filler
->line
!= ' ')
6728 append_to_rev_graph(graph
, filler
->line
);
6732 /* Prepare the next rev graph */
6734 prepare_rev_graph(struct rev_graph
*graph
)
6738 /* First, traverse all lines of revisions up to the active one. */
6739 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6740 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6743 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6746 /* Interleave the new revision parent(s). */
6747 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6748 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6750 /* Lastly, put any remaining revisions. */
6751 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6752 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6756 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6758 /* If this is the finalizing update ... */
6760 prepare_rev_graph(graph
);
6762 /* Graph visualization needs a one rev look-ahead,
6763 * so the first update doesn't visualize anything. */
6764 if (!graph
->prev
->commit
)
6767 if (view
->lines
> 2)
6768 view
->line
[view
->lines
- 3].dirty
= 1;
6769 if (view
->lines
> 1)
6770 view
->line
[view
->lines
- 2].dirty
= 1;
6771 draw_rev_graph(graph
->prev
);
6772 done_rev_graph(graph
->prev
->prev
);
6780 static const char *main_argv
[SIZEOF_ARG
] = {
6781 "git", "log", "--no-color", "--pretty=raw", "--parents",
6782 "--topo-order", "%(diffargs)", "%(revargs)",
6783 "--", "%(fileargs)", NULL
6787 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6789 struct commit
*commit
= line
->data
;
6791 if (!commit
->author
)
6794 if (opt_date
&& draw_date(view
, &commit
->time
))
6797 if (opt_author
&& draw_author(view
, commit
->author
))
6800 if (opt_rev_graph
&& commit
->graph_size
&&
6801 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6804 if (opt_show_refs
&& commit
->refs
) {
6807 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6808 struct ref
*ref
= commit
->refs
->refs
[i
];
6809 enum line_type type
;
6812 type
= LINE_MAIN_HEAD
;
6814 type
= LINE_MAIN_LOCAL_TAG
;
6816 type
= LINE_MAIN_TAG
;
6817 else if (ref
->tracked
)
6818 type
= LINE_MAIN_TRACKED
;
6819 else if (ref
->remote
)
6820 type
= LINE_MAIN_REMOTE
;
6822 type
= LINE_MAIN_REF
;
6824 if (draw_text(view
, type
, "[", TRUE
) ||
6825 draw_text(view
, type
, ref
->name
, TRUE
) ||
6826 draw_text(view
, type
, "]", TRUE
))
6829 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6834 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6838 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6840 main_read(struct view
*view
, char *line
)
6842 static struct rev_graph
*graph
= graph_stacks
;
6843 enum line_type type
;
6844 struct commit
*commit
;
6849 if (!view
->lines
&& !view
->prev
)
6850 die("No revisions match the given arguments.");
6851 if (view
->lines
> 0) {
6852 commit
= view
->line
[view
->lines
- 1].data
;
6853 view
->line
[view
->lines
- 1].dirty
= 1;
6854 if (!commit
->author
) {
6857 graph
->commit
= NULL
;
6860 update_rev_graph(view
, graph
);
6862 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6863 clear_rev_graph(&graph_stacks
[i
]);
6867 type
= get_line_type(line
);
6868 if (type
== LINE_COMMIT
) {
6869 commit
= calloc(1, sizeof(struct commit
));
6873 line
+= STRING_SIZE("commit ");
6875 graph
->boundary
= 1;
6879 string_copy_rev(commit
->id
, line
);
6880 commit
->refs
= get_ref_list(commit
->id
);
6881 graph
->commit
= commit
;
6882 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6884 while ((line
= strchr(line
, ' '))) {
6886 push_rev_graph(graph
->parents
, line
);
6887 commit
->has_parents
= TRUE
;
6894 commit
= view
->line
[view
->lines
- 1].data
;
6898 if (commit
->has_parents
)
6900 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6904 parse_author_line(line
+ STRING_SIZE("author "),
6905 &commit
->author
, &commit
->time
);
6906 update_rev_graph(view
, graph
);
6907 graph
= graph
->next
;
6911 /* Fill in the commit title if it has not already been set. */
6912 if (commit
->title
[0])
6915 /* Require titles to start with a non-space character at the
6916 * offset used by git log. */
6917 if (strncmp(line
, " ", 4))
6920 /* Well, if the title starts with a whitespace character,
6921 * try to be forgiving. Otherwise we end up with no title. */
6922 while (isspace(*line
))
6926 /* FIXME: More graceful handling of titles; append "..." to
6927 * shortened titles, etc. */
6929 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6930 view
->line
[view
->lines
- 1].dirty
= 1;
6937 main_request(struct view
*view
, enum request request
, struct line
*line
)
6939 enum open_flags flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
6943 open_view(view
, REQ_VIEW_DIFF
, flags
);
6947 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6957 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6962 if (!opt_show_refs
|| !list
)
6965 for (i
= 0; i
< list
->size
; i
++) {
6966 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6974 main_grep(struct view
*view
, struct line
*line
)
6976 struct commit
*commit
= line
->data
;
6977 const char *text
[] = {
6979 opt_author
? commit
->author
: "",
6980 mkdate(&commit
->time
, opt_date
),
6984 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6988 main_select(struct view
*view
, struct line
*line
)
6990 struct commit
*commit
= line
->data
;
6992 string_copy_rev(view
->ref
, commit
->id
);
6993 string_copy_rev(ref_commit
, view
->ref
);
6996 static struct view_ops main_ops
= {
7012 /* Whether or not the curses interface has been initialized. */
7013 static bool cursed
= FALSE
;
7015 /* Terminal hacks and workarounds. */
7016 static bool use_scroll_redrawwin
;
7017 static bool use_scroll_status_wclear
;
7019 /* The status window is used for polling keystrokes. */
7020 static WINDOW
*status_win
;
7022 /* Reading from the prompt? */
7023 static bool input_mode
= FALSE
;
7025 static bool status_empty
= FALSE
;
7027 /* Update status and title window. */
7029 report(const char *msg
, ...)
7031 struct view
*view
= display
[current_view
];
7037 char buf
[SIZEOF_STR
];
7040 va_start(args
, msg
);
7041 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
7042 buf
[sizeof(buf
) - 1] = 0;
7043 buf
[sizeof(buf
) - 2] = '.';
7044 buf
[sizeof(buf
) - 3] = '.';
7045 buf
[sizeof(buf
) - 4] = '.';
7051 if (!status_empty
|| *msg
) {
7054 va_start(args
, msg
);
7056 wmove(status_win
, 0, 0);
7057 if (view
->has_scrolled
&& use_scroll_status_wclear
)
7060 vwprintw(status_win
, msg
, args
);
7061 status_empty
= FALSE
;
7063 status_empty
= TRUE
;
7065 wclrtoeol(status_win
);
7066 wnoutrefresh(status_win
);
7071 update_view_title(view
);
7080 /* Initialize the curses library */
7081 if (isatty(STDIN_FILENO
)) {
7082 cursed
= !!initscr();
7085 /* Leave stdin and stdout alone when acting as a pager. */
7086 opt_tty
= fopen("/dev/tty", "r+");
7088 die("Failed to open /dev/tty");
7089 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
7093 die("Failed to initialize curses");
7095 nonl(); /* Disable conversion and detect newlines from input. */
7096 cbreak(); /* Take input chars one at a time, no wait for \n */
7097 noecho(); /* Don't echo input */
7098 leaveok(stdscr
, FALSE
);
7103 getmaxyx(stdscr
, y
, x
);
7104 status_win
= newwin(1, 0, y
- 1, 0);
7106 die("Failed to create status window");
7108 /* Enable keyboard mapping */
7109 keypad(status_win
, TRUE
);
7110 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
7112 TABSIZE
= opt_tab_size
;
7114 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
7115 if (term
&& !strcmp(term
, "gnome-terminal")) {
7116 /* In the gnome-terminal-emulator, the message from
7117 * scrolling up one line when impossible followed by
7118 * scrolling down one line causes corruption of the
7119 * status line. This is fixed by calling wclear. */
7120 use_scroll_status_wclear
= TRUE
;
7121 use_scroll_redrawwin
= FALSE
;
7123 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
7124 /* No problems with full optimizations in xrvt-(unicode)
7126 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
7129 /* When scrolling in (u)xterm the last line in the
7130 * scrolling direction will update slowly. */
7131 use_scroll_redrawwin
= TRUE
;
7132 use_scroll_status_wclear
= FALSE
;
7137 get_input(int prompt_position
)
7140 int i
, key
, cursor_y
, cursor_x
;
7142 if (prompt_position
)
7146 bool loading
= FALSE
;
7148 foreach_view (view
, i
) {
7150 if (view_is_displayed(view
) && view
->has_scrolled
&&
7151 use_scroll_redrawwin
)
7152 redrawwin(view
->win
);
7153 view
->has_scrolled
= FALSE
;
7158 /* Update the cursor position. */
7159 if (prompt_position
) {
7160 getbegyx(status_win
, cursor_y
, cursor_x
);
7161 cursor_x
= prompt_position
;
7163 view
= display
[current_view
];
7164 getbegyx(view
->win
, cursor_y
, cursor_x
);
7165 cursor_x
= view
->width
- 1;
7166 cursor_y
+= view
->lineno
- view
->offset
;
7168 setsyx(cursor_y
, cursor_x
);
7170 /* Refresh, accept single keystroke of input */
7172 nodelay(status_win
, loading
);
7173 key
= wgetch(status_win
);
7175 /* wgetch() with nodelay() enabled returns ERR when
7176 * there's no input. */
7179 } else if (key
== KEY_RESIZE
) {
7182 getmaxyx(stdscr
, height
, width
);
7184 wresize(status_win
, 1, width
);
7185 mvwin(status_win
, height
- 1, 0);
7186 wnoutrefresh(status_win
);
7188 redraw_display(TRUE
);
7198 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7200 enum input_status status
= INPUT_OK
;
7201 static char buf
[SIZEOF_STR
];
7206 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7209 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7210 wclrtoeol(status_win
);
7212 key
= get_input(pos
+ 1);
7217 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7224 status
= INPUT_CANCEL
;
7228 status
= INPUT_CANCEL
;
7232 if (pos
>= sizeof(buf
)) {
7233 report("Input string too long");
7237 status
= handler(data
, buf
, key
);
7238 if (status
== INPUT_OK
)
7239 buf
[pos
++] = (char) key
;
7243 /* Clear the status window */
7244 status_empty
= FALSE
;
7247 if (status
== INPUT_CANCEL
)
7255 static enum input_status
7256 prompt_yesno_handler(void *data
, char *buf
, int c
)
7258 if (c
== 'y' || c
== 'Y')
7260 if (c
== 'n' || c
== 'N')
7261 return INPUT_CANCEL
;
7266 prompt_yesno(const char *prompt
)
7268 char prompt2
[SIZEOF_STR
];
7270 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7273 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7276 static enum input_status
7277 read_prompt_handler(void *data
, char *buf
, int c
)
7279 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7283 read_prompt(const char *prompt
)
7285 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7288 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7290 enum input_status status
= INPUT_OK
;
7293 while (items
[size
].text
)
7296 while (status
== INPUT_OK
) {
7297 const struct menu_item
*item
= &items
[*selected
];
7301 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7302 prompt
, *selected
+ 1, size
);
7304 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7305 wprintw(status_win
, "%s", item
->text
);
7306 wclrtoeol(status_win
);
7308 key
= get_input(COLS
- 1);
7313 status
= INPUT_STOP
;
7318 *selected
= *selected
- 1;
7320 *selected
= size
- 1;
7325 *selected
= (*selected
+ 1) % size
;
7329 status
= INPUT_CANCEL
;
7333 for (i
= 0; items
[i
].text
; i
++)
7334 if (items
[i
].hotkey
== key
) {
7336 status
= INPUT_STOP
;
7342 /* Clear the status window */
7343 status_empty
= FALSE
;
7346 return status
!= INPUT_CANCEL
;
7350 * Repository properties
7353 static struct ref
**refs
= NULL
;
7354 static size_t refs_size
= 0;
7355 static struct ref
*refs_head
= NULL
;
7357 static struct ref_list
**ref_lists
= NULL
;
7358 static size_t ref_lists_size
= 0;
7360 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7361 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7362 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7365 compare_refs(const void *ref1_
, const void *ref2_
)
7367 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7368 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7370 if (ref1
->tag
!= ref2
->tag
)
7371 return ref2
->tag
- ref1
->tag
;
7372 if (ref1
->ltag
!= ref2
->ltag
)
7373 return ref2
->ltag
- ref2
->ltag
;
7374 if (ref1
->head
!= ref2
->head
)
7375 return ref2
->head
- ref1
->head
;
7376 if (ref1
->tracked
!= ref2
->tracked
)
7377 return ref2
->tracked
- ref1
->tracked
;
7378 if (ref1
->remote
!= ref2
->remote
)
7379 return ref2
->remote
- ref1
->remote
;
7380 return strcmp(ref1
->name
, ref2
->name
);
7384 foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
)
7388 for (i
= 0; i
< refs_size
; i
++)
7389 if (!visitor(data
, refs
[i
]))
7399 static struct ref_list
*
7400 get_ref_list(const char *id
)
7402 struct ref_list
*list
;
7405 for (i
= 0; i
< ref_lists_size
; i
++)
7406 if (!strcmp(id
, ref_lists
[i
]->id
))
7407 return ref_lists
[i
];
7409 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7411 list
= calloc(1, sizeof(*list
));
7415 for (i
= 0; i
< refs_size
; i
++) {
7416 if (!strcmp(id
, refs
[i
]->id
) &&
7417 realloc_refs_list(&list
->refs
, list
->size
, 1))
7418 list
->refs
[list
->size
++] = refs
[i
];
7426 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7427 ref_lists
[ref_lists_size
++] = list
;
7432 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
7434 struct ref
*ref
= NULL
;
7437 bool remote
= FALSE
;
7438 bool tracked
= FALSE
;
7440 int from
= 0, to
= refs_size
- 1;
7442 if (!prefixcmp(name
, "refs/tags/")) {
7443 if (!suffixcmp(name
, namelen
, "^{}")) {
7451 namelen
-= STRING_SIZE("refs/tags/");
7452 name
+= STRING_SIZE("refs/tags/");
7454 } else if (!prefixcmp(name
, "refs/remotes/")) {
7456 namelen
-= STRING_SIZE("refs/remotes/");
7457 name
+= STRING_SIZE("refs/remotes/");
7458 tracked
= !strcmp(opt_remote
, name
);
7460 } else if (!prefixcmp(name
, "refs/heads/")) {
7461 namelen
-= STRING_SIZE("refs/heads/");
7462 name
+= STRING_SIZE("refs/heads/");
7463 if (!strncmp(opt_head
, name
, namelen
))
7466 } else if (!strcmp(name
, "HEAD")) {
7469 namelen
= strlen(opt_head
);
7474 /* If we are reloading or it's an annotated tag, replace the
7475 * previous SHA1 with the resolved commit id; relies on the fact
7476 * git-ls-remote lists the commit id of an annotated tag right
7477 * before the commit id it points to. */
7478 while (from
<= to
) {
7479 size_t pos
= (to
+ from
) / 2;
7480 int cmp
= strcmp(name
, refs
[pos
]->name
);
7494 if (!realloc_refs(&refs
, refs_size
, 1))
7496 ref
= calloc(1, sizeof(*ref
) + namelen
);
7499 memmove(refs
+ from
+ 1, refs
+ from
,
7500 (refs_size
- from
) * sizeof(*refs
));
7502 strncpy(ref
->name
, name
, namelen
);
7509 ref
->remote
= remote
;
7510 ref
->tracked
= tracked
;
7511 string_copy_rev(ref
->id
, id
);
7521 const char *head_argv
[] = {
7522 "git", "symbolic-ref", "HEAD", NULL
7524 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7525 "git", "ls-remote", opt_git_dir
, NULL
7527 static bool init
= FALSE
;
7531 if (!argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE"))
7532 die("TIG_LS_REMOTE contains too many arguments");
7539 if (io_run_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7540 !prefixcmp(opt_head
, "refs/heads/")) {
7541 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7543 memmove(opt_head
, offset
, strlen(offset
) + 1);
7547 for (i
= 0; i
< refs_size
; i
++)
7550 if (io_run_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7553 /* Update the ref lists to reflect changes. */
7554 for (i
= 0; i
< ref_lists_size
; i
++) {
7555 struct ref_list
*list
= ref_lists
[i
];
7558 for (old
= new = 0; old
< list
->size
; old
++)
7559 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7560 list
->refs
[new++] = list
->refs
[old
];
7568 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7570 if (!strcmp(name
, ".remote")) {
7571 string_ncopy(opt_remote
, value
, valuelen
);
7573 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7574 size_t from
= strlen(opt_remote
);
7576 if (!prefixcmp(value
, "refs/heads/"))
7577 value
+= STRING_SIZE("refs/heads/");
7579 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7585 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7587 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7588 int argc
= 1 + (cmd
== option_set_command
);
7591 if (!argv_from_string(argv
, &argc
, value
))
7592 config_msg
= "Too many option arguments";
7594 error
= cmd(argc
, argv
);
7597 warn("Option 'tig.%s': %s", name
, config_msg
);
7601 set_environment_variable(const char *name
, const char *value
)
7603 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7604 char *env
= malloc(len
);
7607 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7615 set_work_tree(const char *value
)
7617 char cwd
[SIZEOF_STR
];
7619 if (!getcwd(cwd
, sizeof(cwd
)))
7620 die("Failed to get cwd path: %s", strerror(errno
));
7621 if (chdir(opt_git_dir
) < 0)
7622 die("Failed to chdir(%s): %s", strerror(errno
));
7623 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7624 die("Failed to get git path: %s", strerror(errno
));
7626 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7627 if (chdir(value
) < 0)
7628 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7629 if (!getcwd(cwd
, sizeof(cwd
)))
7630 die("Failed to get cwd path: %s", strerror(errno
));
7631 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7632 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7633 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7634 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7635 opt_is_inside_work_tree
= TRUE
;
7639 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7641 if (!strcmp(name
, "i18n.commitencoding"))
7642 string_ncopy(opt_encoding
, value
, valuelen
);
7644 else if (!strcmp(name
, "core.editor"))
7645 string_ncopy(opt_editor
, value
, valuelen
);
7647 else if (!strcmp(name
, "core.worktree"))
7648 set_work_tree(value
);
7650 else if (!prefixcmp(name
, "tig.color."))
7651 set_repo_config_option(name
+ 10, value
, option_color_command
);
7653 else if (!prefixcmp(name
, "tig.bind."))
7654 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7656 else if (!prefixcmp(name
, "tig."))
7657 set_repo_config_option(name
+ 4, value
, option_set_command
);
7659 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7660 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7661 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7667 load_git_config(void)
7669 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7671 return io_run_load(config_list_argv
, "=", read_repo_config_option
);
7675 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7677 if (!opt_git_dir
[0]) {
7678 string_ncopy(opt_git_dir
, name
, namelen
);
7680 } else if (opt_is_inside_work_tree
== -1) {
7681 /* This can be 3 different values depending on the
7682 * version of git being used. If git-rev-parse does not
7683 * understand --is-inside-work-tree it will simply echo
7684 * the option else either "true" or "false" is printed.
7685 * Default to true for the unknown case. */
7686 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7688 } else if (*name
== '.') {
7689 string_ncopy(opt_cdup
, name
, namelen
);
7692 string_ncopy(opt_prefix
, name
, namelen
);
7699 load_repo_info(void)
7701 const char *rev_parse_argv
[] = {
7702 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7703 "--show-cdup", "--show-prefix", NULL
7706 return io_run_load(rev_parse_argv
, "=", read_repo_info
);
7714 static const char usage
[] =
7715 "tig " TIG_VERSION
" (" __DATE__
")\n"
7717 "Usage: tig [options] [revs] [--] [paths]\n"
7718 " or: tig show [options] [revs] [--] [paths]\n"
7719 " or: tig blame [rev] path\n"
7721 " or: tig < [git command output]\n"
7724 " -v, --version Show version and exit\n"
7725 " -h, --help Show help message and exit";
7727 static void __NORETURN
7730 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7736 static void __NORETURN
7737 die(const char *err
, ...)
7743 va_start(args
, err
);
7744 fputs("tig: ", stderr
);
7745 vfprintf(stderr
, err
, args
);
7746 fputs("\n", stderr
);
7753 warn(const char *msg
, ...)
7757 va_start(args
, msg
);
7758 fputs("tig warning: ", stderr
);
7759 vfprintf(stderr
, msg
, args
);
7760 fputs("\n", stderr
);
7764 static const char ***filter_args
;
7767 read_filter_args(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7769 return argv_append(filter_args
, name
) ? OK
: ERR
;
7773 filter_rev_parse(const char ***args
, const char *arg1
, const char *arg2
, const char *argv
[])
7775 const char *rev_parse_argv
[SIZEOF_ARG
] = { "git", "rev-parse", arg1
, arg2
};
7776 const char **all_argv
= NULL
;
7779 if (!argv_append_array(&all_argv
, rev_parse_argv
) ||
7780 !argv_append_array(&all_argv
, argv
) ||
7781 !io_run_load(all_argv
, "\n", read_filter_args
) == ERR
)
7782 die("Failed to split arguments");
7783 argv_free(all_argv
);
7788 filter_options(const char *argv
[])
7790 filter_rev_parse(&opt_file_args
, "--no-revs", "--no-flags", argv
);
7791 filter_rev_parse(&opt_diff_args
, "--no-revs", "--flags", argv
);
7792 filter_rev_parse(&opt_rev_args
, "--symbolic", "--revs-only", argv
);
7796 parse_options(int argc
, const char *argv
[])
7798 enum request request
= REQ_VIEW_MAIN
;
7799 const char *subcommand
;
7800 bool seen_dashdash
= FALSE
;
7801 const char **filter_argv
= NULL
;
7804 if (!isatty(STDIN_FILENO
))
7805 return REQ_VIEW_PAGER
;
7808 return REQ_VIEW_MAIN
;
7810 subcommand
= argv
[1];
7811 if (!strcmp(subcommand
, "status")) {
7813 warn("ignoring arguments after `%s'", subcommand
);
7814 return REQ_VIEW_STATUS
;
7816 } else if (!strcmp(subcommand
, "blame")) {
7817 if (argc
<= 2 || argc
> 4)
7818 die("invalid number of options to blame\n\n%s", usage
);
7822 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7826 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7827 return REQ_VIEW_BLAME
;
7829 } else if (!strcmp(subcommand
, "show")) {
7830 request
= REQ_VIEW_DIFF
;
7836 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7837 const char *opt
= argv
[i
];
7839 if (seen_dashdash
) {
7840 argv_append(&opt_file_args
, opt
);
7843 } else if (!strcmp(opt
, "--")) {
7844 seen_dashdash
= TRUE
;
7847 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7848 printf("tig version %s\n", TIG_VERSION
);
7851 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7852 printf("%s\n", usage
);
7855 } else if (!strcmp(opt
, "--all")) {
7856 argv_append(&opt_rev_args
, opt
);
7860 if (!argv_append(&filter_argv
, opt
))
7861 die("command too long");
7865 filter_options(filter_argv
);
7871 main(int argc
, const char *argv
[])
7873 const char *codeset
= "UTF-8";
7874 enum request request
= parse_options(argc
, argv
);
7878 signal(SIGINT
, quit
);
7879 signal(SIGPIPE
, SIG_IGN
);
7881 if (setlocale(LC_ALL
, "")) {
7882 codeset
= nl_langinfo(CODESET
);
7885 if (load_repo_info() == ERR
)
7886 die("Failed to load repo info.");
7888 if (load_options() == ERR
)
7889 die("Failed to load user config.");
7891 if (load_git_config() == ERR
)
7892 die("Failed to load repo config.");
7894 /* Require a git repository unless when running in pager mode. */
7895 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7896 die("Not a git repository");
7898 if (*opt_encoding
&& strcmp(codeset
, "UTF-8")) {
7899 opt_iconv_in
= iconv_open("UTF-8", opt_encoding
);
7900 if (opt_iconv_in
== ICONV_NONE
)
7901 die("Failed to initialize character set conversion");
7904 if (codeset
&& strcmp(codeset
, "UTF-8")) {
7905 opt_iconv_out
= iconv_open(codeset
, "UTF-8");
7906 if (opt_iconv_out
== ICONV_NONE
)
7907 die("Failed to initialize character set conversion");
7910 if (load_refs() == ERR
)
7911 die("Failed to load refs.");
7913 foreach_view (view
, i
) {
7914 if (getenv(view
->cmd_env
))
7915 warn("Use of the %s environment variable is deprecated,"
7916 " use options or TIG_DIFF_ARGS instead",
7918 if (!argv_from_env(view
->ops
->argv
, view
->cmd_env
))
7919 die("Too many arguments in the `%s` environment variable",
7925 while (view_driver(display
[current_view
], request
)) {
7926 int key
= get_input(0);
7928 view
= display
[current_view
];
7929 request
= get_keybinding(view
->keymap
, key
);
7931 /* Some low-level request handling. This keeps access to
7932 * status_win restricted. */
7935 report("Unknown key, press %s for help",
7936 get_key(view
->keymap
, REQ_VIEW_HELP
));
7940 char *cmd
= read_prompt(":");
7942 if (cmd
&& isdigit(*cmd
)) {
7943 int lineno
= view
->lineno
+ 1;
7945 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7946 select_view_line(view
, lineno
- 1);
7949 report("Unable to parse '%s' as a line number", cmd
);
7953 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7954 const char *argv
[SIZEOF_ARG
] = { "git" };
7957 /* When running random commands, initially show the
7958 * command in the title. However, it maybe later be
7959 * overwritten if a commit line is selected. */
7960 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7962 if (!argv_from_string(argv
, &argc
, cmd
)) {
7963 report("Too many arguments");
7964 } else if (!prepare_update(next
, argv
, NULL
)) {
7965 report("Failed to format command");
7967 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7975 case REQ_SEARCH_BACK
:
7977 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7978 char *search
= read_prompt(prompt
);
7981 string_ncopy(opt_search
, search
, strlen(search
));
7982 else if (*opt_search
)
7983 request
= request
== REQ_SEARCH
?