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
);
1047 typedef int (*io_read_fn
)(char *, size_t, char *, size_t, void *data
);
1050 io_load(struct io
*io
, const char *separators
,
1051 io_read_fn read_property
, void *data
)
1056 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
1061 name
= chomp_string(name
);
1062 namelen
= strcspn(name
, separators
);
1064 if (name
[namelen
]) {
1066 value
= chomp_string(name
+ namelen
+ 1);
1067 valuelen
= strlen(value
);
1074 state
= read_property(name
, namelen
, value
, valuelen
, data
);
1077 if (state
!= ERR
&& io_error(io
))
1085 io_run_load(const char **argv
, const char *separators
,
1086 io_read_fn read_property
, void *data
)
1090 if (!io_run(&io
, IO_RD
, NULL
, argv
))
1092 return io_load(&io
, separators
, read_property
, data
);
1101 /* XXX: Keep the view request first and in sync with views[]. */ \
1102 REQ_GROUP("View switching") \
1103 REQ_(VIEW_MAIN, "Show main view"), \
1104 REQ_(VIEW_DIFF, "Show diff view"), \
1105 REQ_(VIEW_LOG, "Show log view"), \
1106 REQ_(VIEW_TREE, "Show tree view"), \
1107 REQ_(VIEW_BLOB, "Show blob view"), \
1108 REQ_(VIEW_BLAME, "Show blame view"), \
1109 REQ_(VIEW_BRANCH, "Show branch view"), \
1110 REQ_(VIEW_HELP, "Show help page"), \
1111 REQ_(VIEW_PAGER, "Show pager view"), \
1112 REQ_(VIEW_STATUS, "Show status view"), \
1113 REQ_(VIEW_STAGE, "Show stage view"), \
1115 REQ_GROUP("View manipulation") \
1116 REQ_(ENTER, "Enter current line and scroll"), \
1117 REQ_(NEXT, "Move to next"), \
1118 REQ_(PREVIOUS, "Move to previous"), \
1119 REQ_(PARENT, "Move to parent"), \
1120 REQ_(VIEW_NEXT, "Move focus to next view"), \
1121 REQ_(REFRESH, "Reload and refresh"), \
1122 REQ_(MAXIMIZE, "Maximize the current view"), \
1123 REQ_(VIEW_CLOSE, "Close the current view"), \
1124 REQ_(QUIT, "Close all views and quit"), \
1126 REQ_GROUP("View specific requests") \
1127 REQ_(STATUS_UPDATE, "Update file status"), \
1128 REQ_(STATUS_REVERT, "Revert file changes"), \
1129 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1130 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1132 REQ_GROUP("Cursor navigation") \
1133 REQ_(MOVE_UP, "Move cursor one line up"), \
1134 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1135 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1136 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1137 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1138 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1140 REQ_GROUP("Scrolling") \
1141 REQ_(SCROLL_FIRST_COL, "Scroll to the first line columns"), \
1142 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1143 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1144 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1145 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1146 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1147 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1149 REQ_GROUP("Searching") \
1150 REQ_(SEARCH, "Search the view"), \
1151 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1152 REQ_(FIND_NEXT, "Find next search match"), \
1153 REQ_(FIND_PREV, "Find previous search match"), \
1155 REQ_GROUP("Option manipulation") \
1156 REQ_(OPTIONS, "Open option menu"), \
1157 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1158 REQ_(TOGGLE_DATE, "Toggle date display"), \
1159 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1160 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1161 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1162 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1163 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1166 REQ_(PROMPT, "Bring up the prompt"), \
1167 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1168 REQ_(SHOW_VERSION, "Show version information"), \
1169 REQ_(STOP_LOADING, "Stop all loading views"), \
1170 REQ_(EDIT, "Open in editor"), \
1171 REQ_(NONE, "Do nothing")
1174 /* User action requests. */
1176 #define REQ_GROUP(help)
1177 #define REQ_(req, help) REQ_##req
1179 /* Offset all requests to avoid conflicts with ncurses getch values. */
1180 REQ_UNKNOWN
= KEY_MAX
+ 1,
1188 struct request_info
{
1189 enum request request
;
1195 static const struct request_info req_info
[] = {
1196 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1197 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1204 get_request(const char *name
)
1206 int namelen
= strlen(name
);
1209 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
1210 if (enum_equals(req_info
[i
], name
, namelen
))
1211 return req_info
[i
].request
;
1221 /* Option and state variables. */
1222 static enum date opt_date
= DATE_DEFAULT
;
1223 static enum author opt_author
= AUTHOR_DEFAULT
;
1224 static bool opt_line_number
= FALSE
;
1225 static bool opt_line_graphics
= TRUE
;
1226 static bool opt_rev_graph
= FALSE
;
1227 static bool opt_show_refs
= TRUE
;
1228 static bool opt_untracked_dirs_content
= TRUE
;
1229 static int opt_num_interval
= 5;
1230 static double opt_hscroll
= 0.50;
1231 static double opt_scale_split_view
= 2.0 / 3.0;
1232 static int opt_tab_size
= 8;
1233 static int opt_author_cols
= AUTHOR_COLS
;
1234 static char opt_path
[SIZEOF_STR
] = "";
1235 static char opt_file
[SIZEOF_STR
] = "";
1236 static char opt_ref
[SIZEOF_REF
] = "";
1237 static char opt_head
[SIZEOF_REF
] = "";
1238 static char opt_remote
[SIZEOF_REF
] = "";
1239 static char opt_encoding
[20] = "UTF-8";
1240 static iconv_t opt_iconv_in
= ICONV_NONE
;
1241 static iconv_t opt_iconv_out
= ICONV_NONE
;
1242 static char opt_search
[SIZEOF_STR
] = "";
1243 static char opt_cdup
[SIZEOF_STR
] = "";
1244 static char opt_prefix
[SIZEOF_STR
] = "";
1245 static char opt_git_dir
[SIZEOF_STR
] = "";
1246 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
1247 static char opt_editor
[SIZEOF_STR
] = "";
1248 static FILE *opt_tty
= NULL
;
1249 static const char **opt_diff_argv
= NULL
;
1250 static const char **opt_rev_argv
= NULL
;
1251 static const char **opt_file_argv
= NULL
;
1253 #define is_initial_commit() (!get_ref_head())
1254 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1258 * Line-oriented content detection.
1262 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1271 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1272 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1273 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1274 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1275 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1276 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1277 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1278 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1279 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1280 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1281 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1282 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1283 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1284 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1285 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1286 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1287 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1288 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1289 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1290 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1291 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1292 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1293 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1294 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1295 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1296 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1297 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1298 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1299 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1300 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1301 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1302 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1303 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1304 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1305 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1306 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1307 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1308 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1309 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1310 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1311 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1312 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1313 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1314 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1315 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1316 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1317 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1318 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1319 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1320 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1323 #define LINE(type, line, fg, bg, attr) \
1331 const char *name
; /* Option name. */
1332 int namelen
; /* Size of option name. */
1333 const char *line
; /* The start of line to match. */
1334 int linelen
; /* Size of string to match. */
1335 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1338 static struct line_info line_info
[] = {
1339 #define LINE(type, line, fg, bg, attr) \
1340 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1345 static enum line_type
1346 get_line_type(const char *line
)
1348 int linelen
= strlen(line
);
1349 enum line_type type
;
1351 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1352 /* Case insensitive search matches Signed-off-by lines better. */
1353 if (linelen
>= line_info
[type
].linelen
&&
1354 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1357 return LINE_DEFAULT
;
1361 get_line_attr(enum line_type type
)
1363 assert(type
< ARRAY_SIZE(line_info
));
1364 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1367 static struct line_info
*
1368 get_line_info(const char *name
)
1370 size_t namelen
= strlen(name
);
1371 enum line_type type
;
1373 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1374 if (enum_equals(line_info
[type
], name
, namelen
))
1375 return &line_info
[type
];
1383 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1384 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1385 enum line_type type
;
1389 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1390 default_bg
= COLOR_BLACK
;
1391 default_fg
= COLOR_WHITE
;
1394 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1395 struct line_info
*info
= &line_info
[type
];
1396 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1397 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1399 init_pair(type
, fg
, bg
);
1404 enum line_type type
;
1407 unsigned int selected
:1;
1408 unsigned int dirty
:1;
1409 unsigned int cleareol
:1;
1410 unsigned int other
:16;
1412 void *data
; /* User data */
1422 enum request request
;
1425 static struct keybinding default_keybindings
[] = {
1426 /* View switching */
1427 { 'm', REQ_VIEW_MAIN
},
1428 { 'd', REQ_VIEW_DIFF
},
1429 { 'l', REQ_VIEW_LOG
},
1430 { 't', REQ_VIEW_TREE
},
1431 { 'f', REQ_VIEW_BLOB
},
1432 { 'B', REQ_VIEW_BLAME
},
1433 { 'H', REQ_VIEW_BRANCH
},
1434 { 'p', REQ_VIEW_PAGER
},
1435 { 'h', REQ_VIEW_HELP
},
1436 { 'S', REQ_VIEW_STATUS
},
1437 { 'c', REQ_VIEW_STAGE
},
1439 /* View manipulation */
1440 { 'q', REQ_VIEW_CLOSE
},
1441 { KEY_TAB
, REQ_VIEW_NEXT
},
1442 { KEY_RETURN
, REQ_ENTER
},
1443 { KEY_UP
, REQ_PREVIOUS
},
1444 { KEY_CTL('P'), REQ_PREVIOUS
},
1445 { KEY_DOWN
, REQ_NEXT
},
1446 { KEY_CTL('N'), REQ_NEXT
},
1447 { 'R', REQ_REFRESH
},
1448 { KEY_F(5), REQ_REFRESH
},
1449 { 'O', REQ_MAXIMIZE
},
1451 /* Cursor navigation */
1452 { 'k', REQ_MOVE_UP
},
1453 { 'j', REQ_MOVE_DOWN
},
1454 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1455 { KEY_END
, REQ_MOVE_LAST_LINE
},
1456 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1457 { KEY_CTL('D'), REQ_MOVE_PAGE_DOWN
},
1458 { ' ', REQ_MOVE_PAGE_DOWN
},
1459 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1460 { KEY_CTL('U'), REQ_MOVE_PAGE_UP
},
1461 { 'b', REQ_MOVE_PAGE_UP
},
1462 { '-', REQ_MOVE_PAGE_UP
},
1465 { '|', REQ_SCROLL_FIRST_COL
},
1466 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1467 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1468 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1469 { KEY_CTL('Y'), REQ_SCROLL_LINE_UP
},
1470 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1471 { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN
},
1472 { 'w', REQ_SCROLL_PAGE_UP
},
1473 { 's', REQ_SCROLL_PAGE_DOWN
},
1476 { '/', REQ_SEARCH
},
1477 { '?', REQ_SEARCH_BACK
},
1478 { 'n', REQ_FIND_NEXT
},
1479 { 'N', REQ_FIND_PREV
},
1483 { 'z', REQ_STOP_LOADING
},
1484 { 'v', REQ_SHOW_VERSION
},
1485 { 'r', REQ_SCREEN_REDRAW
},
1486 { KEY_CTL('L'), REQ_SCREEN_REDRAW
},
1487 { 'o', REQ_OPTIONS
},
1488 { '.', REQ_TOGGLE_LINENO
},
1489 { 'D', REQ_TOGGLE_DATE
},
1490 { 'A', REQ_TOGGLE_AUTHOR
},
1491 { 'g', REQ_TOGGLE_REV_GRAPH
},
1492 { 'F', REQ_TOGGLE_REFS
},
1493 { 'I', REQ_TOGGLE_SORT_ORDER
},
1494 { 'i', REQ_TOGGLE_SORT_FIELD
},
1495 { ':', REQ_PROMPT
},
1496 { 'u', REQ_STATUS_UPDATE
},
1497 { '!', REQ_STATUS_REVERT
},
1498 { 'M', REQ_STATUS_MERGE
},
1499 { '@', REQ_STAGE_NEXT
},
1500 { ',', REQ_PARENT
},
1504 #define KEYMAP_INFO \
1519 #define KEYMAP_(name) KEYMAP_##name
1524 static const struct enum_map keymap_table
[] = {
1525 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1530 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1532 struct keybinding_table
{
1533 struct keybinding
*data
;
1537 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1540 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1542 struct keybinding_table
*table
= &keybindings
[keymap
];
1545 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1546 if (keybindings
[keymap
].data
[i
].alias
== key
) {
1547 keybindings
[keymap
].data
[i
].request
= request
;
1552 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1554 die("Failed to allocate keybinding");
1555 table
->data
[table
->size
].alias
= key
;
1556 table
->data
[table
->size
++].request
= request
;
1558 if (request
== REQ_NONE
&& keymap
== KEYMAP_GENERIC
) {
1561 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1562 if (default_keybindings
[i
].alias
== key
)
1563 default_keybindings
[i
].request
= REQ_NONE
;
1567 /* Looks for a key binding first in the given map, then in the generic map, and
1568 * lastly in the default keybindings. */
1570 get_keybinding(enum keymap keymap
, int key
)
1574 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1575 if (keybindings
[keymap
].data
[i
].alias
== key
)
1576 return keybindings
[keymap
].data
[i
].request
;
1578 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1579 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1580 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1582 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1583 if (default_keybindings
[i
].alias
== key
)
1584 return default_keybindings
[i
].request
;
1586 return (enum request
) key
;
1595 static const struct key key_table
[] = {
1596 { "Enter", KEY_RETURN
},
1598 { "Backspace", KEY_BACKSPACE
},
1600 { "Escape", KEY_ESC
},
1601 { "Left", KEY_LEFT
},
1602 { "Right", KEY_RIGHT
},
1604 { "Down", KEY_DOWN
},
1605 { "Insert", KEY_IC
},
1606 { "Delete", KEY_DC
},
1608 { "Home", KEY_HOME
},
1610 { "PageUp", KEY_PPAGE
},
1611 { "PageDown", KEY_NPAGE
},
1621 { "F10", KEY_F(10) },
1622 { "F11", KEY_F(11) },
1623 { "F12", KEY_F(12) },
1627 get_key_value(const char *name
)
1631 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1632 if (!strcasecmp(key_table
[i
].name
, name
))
1633 return key_table
[i
].value
;
1635 if (strlen(name
) == 2 && name
[0] == '^' && isprint(*name
))
1636 return (int)name
[1] & 0x1f;
1637 if (strlen(name
) == 1 && isprint(*name
))
1643 get_key_name(int key_value
)
1645 static char key_char
[] = "'X'\0";
1646 const char *seq
= NULL
;
1649 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1650 if (key_table
[key
].value
== key_value
)
1651 seq
= key_table
[key
].name
;
1653 if (seq
== NULL
&& key_value
< 0x7f) {
1654 char *s
= key_char
+ 1;
1656 if (key_value
>= 0x20) {
1660 *s
++ = 0x40 | (key_value
& 0x1f);
1667 return seq
? seq
: "(no key)";
1671 append_key(char *buf
, size_t *pos
, const struct keybinding
*keybinding
)
1673 const char *sep
= *pos
> 0 ? ", " : "";
1674 const char *keyname
= get_key_name(keybinding
->alias
);
1676 return string_nformat(buf
, BUFSIZ
, pos
, "%s%s", sep
, keyname
);
1680 append_keymap_request_keys(char *buf
, size_t *pos
, enum request request
,
1681 enum keymap keymap
, bool all
)
1685 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1686 if (keybindings
[keymap
].data
[i
].request
== request
) {
1687 if (!append_key(buf
, pos
, &keybindings
[keymap
].data
[i
]))
1697 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1700 get_keys(enum keymap keymap
, enum request request
, bool all
)
1702 static char buf
[BUFSIZ
];
1708 if (!append_keymap_request_keys(buf
, &pos
, request
, keymap
, all
))
1709 return "Too many keybindings!";
1710 if (pos
> 0 && !all
)
1713 if (keymap
!= KEYMAP_GENERIC
) {
1714 /* Only the generic keymap includes the default keybindings when
1715 * listing all keys. */
1719 if (!append_keymap_request_keys(buf
, &pos
, request
, KEYMAP_GENERIC
, all
))
1720 return "Too many keybindings!";
1725 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1726 if (default_keybindings
[i
].request
== request
) {
1727 if (!append_key(buf
, &pos
, &default_keybindings
[i
]))
1728 return "Too many keybindings!";
1737 struct run_request
{
1743 static struct run_request
*run_request
;
1744 static size_t run_requests
;
1746 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1749 add_run_request(enum keymap keymap
, int key
, const char **argv
)
1751 struct run_request
*req
;
1753 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1756 req
= &run_request
[run_requests
];
1757 req
->keymap
= keymap
;
1761 if (!argv_copy(&req
->argv
, argv
))
1764 return REQ_NONE
+ ++run_requests
;
1767 static struct run_request
*
1768 get_run_request(enum request request
)
1770 if (request
<= REQ_NONE
)
1772 return &run_request
[request
- REQ_NONE
- 1];
1776 add_builtin_run_requests(void)
1778 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1779 const char *checkout
[] = { "git", "checkout", "%(branch)", NULL
};
1780 const char *commit
[] = { "git", "commit", NULL
};
1781 const char *gc
[] = { "git", "gc", NULL
};
1782 struct run_request reqs
[] = {
1783 { KEYMAP_MAIN
, 'C', cherry_pick
},
1784 { KEYMAP_STATUS
, 'C', commit
},
1785 { KEYMAP_BRANCH
, 'C', checkout
},
1786 { KEYMAP_GENERIC
, 'G', gc
},
1790 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1791 enum request req
= get_keybinding(reqs
[i
].keymap
, reqs
[i
].key
);
1793 if (req
!= reqs
[i
].key
)
1795 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argv
);
1796 if (req
!= REQ_NONE
)
1797 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1802 * User config file handling.
1805 static int config_lineno
;
1806 static bool config_errors
;
1807 static const char *config_msg
;
1809 static const struct enum_map color_map
[] = {
1810 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1822 static const struct enum_map attr_map
[] = {
1823 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1830 ATTR_MAP(UNDERLINE
),
1833 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1835 static int parse_step(double *opt
, const char *arg
)
1838 if (!strchr(arg
, '%'))
1841 /* "Shift down" so 100% and 1 does not conflict. */
1842 *opt
= (*opt
- 1) / 100;
1845 config_msg
= "Step value larger than 100%";
1850 config_msg
= "Invalid step value";
1857 parse_int(int *opt
, const char *arg
, int min
, int max
)
1859 int value
= atoi(arg
);
1861 if (min
<= value
&& value
<= max
) {
1866 config_msg
= "Integer value out of bound";
1871 set_color(int *color
, const char *name
)
1873 if (map_enum(color
, color_map
, name
))
1875 if (!prefixcmp(name
, "color"))
1876 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1880 /* Wants: object fgcolor bgcolor [attribute] */
1882 option_color_command(int argc
, const char *argv
[])
1884 struct line_info
*info
;
1887 config_msg
= "Wrong number of arguments given to color command";
1891 info
= get_line_info(argv
[0]);
1893 static const struct enum_map obsolete
[] = {
1894 ENUM_MAP("main-delim", LINE_DELIMITER
),
1895 ENUM_MAP("main-date", LINE_DATE
),
1896 ENUM_MAP("main-author", LINE_AUTHOR
),
1900 if (!map_enum(&index
, obsolete
, argv
[0])) {
1901 config_msg
= "Unknown color name";
1904 info
= &line_info
[index
];
1907 if (!set_color(&info
->fg
, argv
[1]) ||
1908 !set_color(&info
->bg
, argv
[2])) {
1909 config_msg
= "Unknown color";
1914 while (argc
-- > 3) {
1917 if (!set_attribute(&attr
, argv
[argc
])) {
1918 config_msg
= "Unknown attribute";
1927 static int parse_bool(bool *opt
, const char *arg
)
1929 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1934 static int parse_enum_do(unsigned int *opt
, const char *arg
,
1935 const struct enum_map
*map
, size_t map_size
)
1939 assert(map_size
> 1);
1941 if (map_enum_do(map
, map_size
, (int *) opt
, arg
))
1944 if (parse_bool(&is_true
, arg
) != OK
)
1947 *opt
= is_true
? map
[1].value
: map
[0].value
;
1951 #define parse_enum(opt, arg, map) \
1952 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1955 parse_string(char *opt
, const char *arg
, size_t optsize
)
1957 int arglen
= strlen(arg
);
1962 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1963 config_msg
= "Unmatched quotation";
1966 arg
+= 1; arglen
-= 2;
1968 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1973 /* Wants: name = value */
1975 option_set_command(int argc
, const char *argv
[])
1978 config_msg
= "Wrong number of arguments given to set command";
1982 if (strcmp(argv
[1], "=")) {
1983 config_msg
= "No value assigned";
1987 if (!strcmp(argv
[0], "show-author"))
1988 return parse_enum(&opt_author
, argv
[2], author_map
);
1990 if (!strcmp(argv
[0], "show-date"))
1991 return parse_enum(&opt_date
, argv
[2], date_map
);
1993 if (!strcmp(argv
[0], "show-rev-graph"))
1994 return parse_bool(&opt_rev_graph
, argv
[2]);
1996 if (!strcmp(argv
[0], "show-refs"))
1997 return parse_bool(&opt_show_refs
, argv
[2]);
1999 if (!strcmp(argv
[0], "show-line-numbers"))
2000 return parse_bool(&opt_line_number
, argv
[2]);
2002 if (!strcmp(argv
[0], "line-graphics"))
2003 return parse_bool(&opt_line_graphics
, argv
[2]);
2005 if (!strcmp(argv
[0], "line-number-interval"))
2006 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
2008 if (!strcmp(argv
[0], "author-width"))
2009 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
2011 if (!strcmp(argv
[0], "horizontal-scroll"))
2012 return parse_step(&opt_hscroll
, argv
[2]);
2014 if (!strcmp(argv
[0], "split-view-height"))
2015 return parse_step(&opt_scale_split_view
, argv
[2]);
2017 if (!strcmp(argv
[0], "tab-size"))
2018 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
2020 if (!strcmp(argv
[0], "commit-encoding"))
2021 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
2023 if (!strcmp(argv
[0], "status-untracked-dirs"))
2024 return parse_bool(&opt_untracked_dirs_content
, argv
[2]);
2026 config_msg
= "Unknown variable name";
2030 /* Wants: mode request key */
2032 option_bind_command(int argc
, const char *argv
[])
2034 enum request request
;
2039 config_msg
= "Wrong number of arguments given to bind command";
2043 if (!set_keymap(&keymap
, argv
[0])) {
2044 config_msg
= "Unknown key map";
2048 key
= get_key_value(argv
[1]);
2050 config_msg
= "Unknown key";
2054 request
= get_request(argv
[2]);
2055 if (request
== REQ_UNKNOWN
) {
2056 static const struct enum_map obsolete
[] = {
2057 ENUM_MAP("cherry-pick", REQ_NONE
),
2058 ENUM_MAP("screen-resize", REQ_NONE
),
2059 ENUM_MAP("tree-parent", REQ_PARENT
),
2063 if (map_enum(&alias
, obsolete
, argv
[2])) {
2064 if (alias
!= REQ_NONE
)
2065 add_keybinding(keymap
, alias
, key
);
2066 config_msg
= "Obsolete request name";
2070 if (request
== REQ_UNKNOWN
&& *argv
[2]++ == '!')
2071 request
= add_run_request(keymap
, key
, argv
+ 2);
2072 if (request
== REQ_UNKNOWN
) {
2073 config_msg
= "Unknown request name";
2077 add_keybinding(keymap
, request
, key
);
2083 set_option(const char *opt
, char *value
)
2085 const char *argv
[SIZEOF_ARG
];
2088 if (!argv_from_string(argv
, &argc
, value
)) {
2089 config_msg
= "Too many option arguments";
2093 if (!strcmp(opt
, "color"))
2094 return option_color_command(argc
, argv
);
2096 if (!strcmp(opt
, "set"))
2097 return option_set_command(argc
, argv
);
2099 if (!strcmp(opt
, "bind"))
2100 return option_bind_command(argc
, argv
);
2102 config_msg
= "Unknown option command";
2107 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
, void *data
)
2112 config_msg
= "Internal error";
2114 /* Check for comment markers, since read_properties() will
2115 * only ensure opt and value are split at first " \t". */
2116 optlen
= strcspn(opt
, "#");
2120 if (opt
[optlen
] != 0) {
2121 config_msg
= "No option value";
2125 /* Look for comment endings in the value. */
2126 size_t len
= strcspn(value
, "#");
2128 if (len
< valuelen
) {
2130 value
[valuelen
] = 0;
2133 status
= set_option(opt
, value
);
2136 if (status
== ERR
) {
2137 warn("Error on line %d, near '%.*s': %s",
2138 config_lineno
, (int) optlen
, opt
, config_msg
);
2139 config_errors
= TRUE
;
2142 /* Always keep going if errors are encountered. */
2147 load_option_file(const char *path
)
2151 /* It's OK that the file doesn't exist. */
2152 if (!io_open(&io
, "%s", path
))
2156 config_errors
= FALSE
;
2158 if (io_load(&io
, " \t", read_option
, NULL
) == ERR
||
2159 config_errors
== TRUE
)
2160 warn("Errors while loading %s.", path
);
2166 const char *home
= getenv("HOME");
2167 const char *tigrc_user
= getenv("TIGRC_USER");
2168 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
2169 const char *tig_diff_opts
= getenv("TIG_DIFF_OPTS");
2170 char buf
[SIZEOF_STR
];
2173 tigrc_system
= SYSCONFDIR
"/tigrc";
2174 load_option_file(tigrc_system
);
2177 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
2181 load_option_file(tigrc_user
);
2183 /* Add _after_ loading config files to avoid adding run requests
2184 * that conflict with keybindings. */
2185 add_builtin_run_requests();
2187 if (!opt_diff_argv
&& tig_diff_opts
&& *tig_diff_opts
) {
2188 static const char *diff_opts
[SIZEOF_ARG
] = { NULL
};
2191 if (!string_format(buf
, "%s", tig_diff_opts
) ||
2192 !argv_from_string(diff_opts
, &argc
, buf
))
2193 die("TIG_DIFF_OPTS contains too many arguments");
2194 else if (!argv_copy(&opt_diff_argv
, diff_opts
))
2195 die("Failed to format TIG_DIFF_OPTS arguments");
2209 /* The display array of active views and the index of the current view. */
2210 static struct view
*display
[2];
2211 static unsigned int current_view
;
2213 #define foreach_displayed_view(view, i) \
2214 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2216 #define displayed_views() (display[1] != NULL ? 2 : 1)
2218 /* Current head and commit ID */
2219 static char ref_blob
[SIZEOF_REF
] = "";
2220 static char ref_commit
[SIZEOF_REF
] = "HEAD";
2221 static char ref_head
[SIZEOF_REF
] = "HEAD";
2222 static char ref_branch
[SIZEOF_REF
] = "";
2239 enum view_type type
; /* View type */
2240 const char *name
; /* View name */
2241 const char *cmd_env
; /* Command line set via environment */
2242 const char *id
; /* Points to either of ref_{head,commit,blob} */
2244 struct view_ops
*ops
; /* View operations */
2246 enum keymap keymap
; /* What keymap does this view have */
2247 bool git_dir
; /* Whether the view requires a git directory. */
2249 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
2250 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
2252 int height
, width
; /* The width and height of the main window */
2253 WINDOW
*win
; /* The main window */
2254 WINDOW
*title
; /* The title window living below the main window */
2257 unsigned long offset
; /* Offset of the window top */
2258 unsigned long yoffset
; /* Offset from the window side. */
2259 unsigned long lineno
; /* Current line number */
2260 unsigned long p_offset
; /* Previous offset of the window top */
2261 unsigned long p_yoffset
;/* Previous offset from the window side */
2262 unsigned long p_lineno
; /* Previous current line number */
2263 bool p_restore
; /* Should the previous position be restored. */
2266 char grep
[SIZEOF_STR
]; /* Search string */
2267 regex_t
*regex
; /* Pre-compiled regexp */
2269 /* If non-NULL, points to the view that opened this view. If this view
2270 * is closed tig will switch back to the parent view. */
2271 struct view
*parent
;
2275 size_t lines
; /* Total number of lines */
2276 struct line
*line
; /* Line index */
2277 unsigned int digits
; /* Number of digits in the lines member. */
2280 struct line
*curline
; /* Line currently being drawn. */
2281 enum line_type curtype
; /* Attribute currently used for drawing. */
2282 unsigned long col
; /* Column when drawing. */
2283 bool has_scrolled
; /* View was scrolled. */
2286 const char **argv
; /* Shell command arguments. */
2287 const char *dir
; /* Directory from which to execute. */
2295 /* What type of content being displayed. Used in the title bar. */
2297 /* Default command arguments. */
2299 /* Open and reads in all view content. */
2300 bool (*open
)(struct view
*view
);
2301 /* Read one line; updates view->line. */
2302 bool (*read
)(struct view
*view
, char *data
);
2303 /* Draw one line; @lineno must be < view->height. */
2304 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
2305 /* Depending on view handle a special requests. */
2306 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
2307 /* Search for regexp in a line. */
2308 bool (*grep
)(struct view
*view
, struct line
*line
);
2310 void (*select
)(struct view
*view
, struct line
*line
);
2311 /* Prepare view for loading */
2312 bool (*prepare
)(struct view
*view
);
2315 static struct view_ops blame_ops
;
2316 static struct view_ops blob_ops
;
2317 static struct view_ops diff_ops
;
2318 static struct view_ops help_ops
;
2319 static struct view_ops log_ops
;
2320 static struct view_ops main_ops
;
2321 static struct view_ops pager_ops
;
2322 static struct view_ops stage_ops
;
2323 static struct view_ops status_ops
;
2324 static struct view_ops tree_ops
;
2325 static struct view_ops branch_ops
;
2327 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2328 { type, name, #env, ref, ops, map, git }
2330 #define VIEW_(id, name, ops, git, ref) \
2331 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2333 static struct view views
[] = {
2334 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
2335 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
2336 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
2337 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
2338 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
2339 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
2340 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
2341 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
2342 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, ""),
2343 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
2344 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
2347 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2349 #define foreach_view(view, i) \
2350 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2352 #define view_is_displayed(view) \
2353 (view == display[0] || view == display[1])
2356 view_request(struct view
*view
, enum request request
)
2358 if (!view
|| !view
->lines
)
2360 return view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
2369 set_view_attr(struct view
*view
, enum line_type type
)
2371 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
2372 (void) wattrset(view
->win
, get_line_attr(type
));
2373 wchgat(view
->win
, -1, 0, type
, NULL
);
2374 view
->curtype
= type
;
2379 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
2380 int max_len
, bool use_tilde
)
2382 static char out_buffer
[BUFSIZ
* 2];
2385 int trimmed
= FALSE
;
2386 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2391 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
, opt_tab_size
);
2393 set_view_attr(view
, type
);
2395 if (opt_iconv_out
!= ICONV_NONE
) {
2396 ICONV_CONST
char *inbuf
= (ICONV_CONST
char *) string
;
2397 size_t inlen
= len
+ 1;
2399 char *outbuf
= out_buffer
;
2400 size_t outlen
= sizeof(out_buffer
);
2404 ret
= iconv(opt_iconv_out
, &inbuf
, &inlen
, &outbuf
, &outlen
);
2405 if (ret
!= (size_t) -1) {
2406 string
= out_buffer
;
2407 len
= sizeof(out_buffer
) - outlen
;
2411 waddnstr(view
->win
, string
, len
);
2413 if (trimmed
&& use_tilde
) {
2414 set_view_attr(view
, LINE_DELIMITER
);
2415 waddch(view
->win
, '~');
2424 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
2426 static char space
[] = " ";
2429 spaces
= MIN(max
, spaces
);
2431 while (spaces
> 0) {
2432 int len
= MIN(spaces
, sizeof(space
) - 1);
2434 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
2442 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
2444 char text
[SIZEOF_STR
];
2447 size_t pos
= string_expand(text
, sizeof(text
), string
, opt_tab_size
);
2449 view
->col
+= draw_chars(view
, type
, text
, view
->width
+ view
->yoffset
- view
->col
, trim
);
2451 } while (*string
&& view
->width
+ view
->yoffset
> view
->col
);
2453 return view
->width
+ view
->yoffset
<= view
->col
;
2457 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
2459 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2460 int max
= view
->width
+ view
->yoffset
- view
->col
;
2466 set_view_attr(view
, type
);
2467 /* Using waddch() instead of waddnstr() ensures that
2468 * they'll be rendered correctly for the cursor line. */
2469 for (i
= skip
; i
< size
; i
++)
2470 waddch(view
->win
, graphic
[i
]);
2473 if (size
< max
&& skip
<= size
)
2474 waddch(view
->win
, ' ');
2477 return view
->width
+ view
->yoffset
<= view
->col
;
2481 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2483 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2487 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2489 col
= draw_space(view
, type
, max
- 1, max
- 1);
2492 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2493 return view
->width
+ view
->yoffset
<= view
->col
;
2497 draw_date(struct view
*view
, struct time
*time
)
2499 const char *date
= mkdate(time
, opt_date
);
2500 int cols
= opt_date
== DATE_SHORT
? DATE_SHORT_COLS
: DATE_COLS
;
2502 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2506 draw_author(struct view
*view
, const char *author
)
2508 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5;
2509 bool abbreviate
= opt_author
== AUTHOR_ABBREVIATED
|| !trim
;
2511 if (abbreviate
&& author
)
2512 author
= get_author_initials(author
);
2514 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2518 draw_mode(struct view
*view
, mode_t mode
)
2524 else if (S_ISLNK(mode
))
2526 else if (S_ISGITLINK(mode
))
2528 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2530 else if (S_ISREG(mode
))
2535 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2539 draw_lineno(struct view
*view
, unsigned int lineno
)
2542 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2543 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2545 chtype separator
= opt_line_graphics
? ACS_VLINE
: '|';
2547 lineno
+= view
->offset
+ 1;
2548 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2549 static char fmt
[] = "%1ld";
2551 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2552 if (string_format(number
, fmt
, lineno
))
2556 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2558 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2559 return draw_graphic(view
, LINE_DEFAULT
, &separator
, 1);
2563 draw_view_line(struct view
*view
, unsigned int lineno
)
2566 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2568 assert(view_is_displayed(view
));
2570 if (view
->offset
+ lineno
>= view
->lines
)
2573 line
= &view
->line
[view
->offset
+ lineno
];
2575 wmove(view
->win
, lineno
, 0);
2577 wclrtoeol(view
->win
);
2579 view
->curline
= line
;
2580 view
->curtype
= LINE_NONE
;
2581 line
->selected
= FALSE
;
2582 line
->dirty
= line
->cleareol
= 0;
2585 set_view_attr(view
, LINE_CURSOR
);
2586 line
->selected
= TRUE
;
2587 view
->ops
->select(view
, line
);
2590 return view
->ops
->draw(view
, line
, lineno
);
2594 redraw_view_dirty(struct view
*view
)
2599 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2600 if (view
->offset
+ lineno
>= view
->lines
)
2602 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2605 if (!draw_view_line(view
, lineno
))
2611 wnoutrefresh(view
->win
);
2615 redraw_view_from(struct view
*view
, int lineno
)
2617 assert(0 <= lineno
&& lineno
< view
->height
);
2619 for (; lineno
< view
->height
; lineno
++) {
2620 if (!draw_view_line(view
, lineno
))
2624 wnoutrefresh(view
->win
);
2628 redraw_view(struct view
*view
)
2631 redraw_view_from(view
, 0);
2636 update_view_title(struct view
*view
)
2638 char buf
[SIZEOF_STR
];
2639 char state
[SIZEOF_STR
];
2640 size_t bufpos
= 0, statelen
= 0;
2642 assert(view_is_displayed(view
));
2644 if (view
->type
!= VIEW_STATUS
&& view
->lines
) {
2645 unsigned int view_lines
= view
->offset
+ view
->height
;
2646 unsigned int lines
= view
->lines
2647 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2650 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2659 time_t secs
= time(NULL
) - view
->start_time
;
2661 /* Three git seconds are a long time ... */
2663 string_format_from(state
, &statelen
, " loading %lds", secs
);
2666 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2667 if (*view
->ref
&& bufpos
< view
->width
) {
2668 size_t refsize
= strlen(view
->ref
);
2669 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2671 if (minsize
< view
->width
)
2672 refsize
= view
->width
- minsize
+ 7;
2673 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2676 if (statelen
&& bufpos
< view
->width
) {
2677 string_format_from(buf
, &bufpos
, "%s", state
);
2680 if (view
== display
[current_view
])
2681 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2683 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2685 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2686 wclrtoeol(view
->title
);
2687 wnoutrefresh(view
->title
);
2691 apply_step(double step
, int value
)
2695 value
*= step
+ 0.01;
2696 return value
? value
: 1;
2700 resize_display(void)
2703 struct view
*base
= display
[0];
2704 struct view
*view
= display
[1] ? display
[1] : display
[0];
2706 /* Setup window dimensions */
2708 getmaxyx(stdscr
, base
->height
, base
->width
);
2710 /* Make room for the status window. */
2714 /* Horizontal split. */
2715 view
->width
= base
->width
;
2716 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2717 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2718 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2719 base
->height
-= view
->height
;
2721 /* Make room for the title bar. */
2725 /* Make room for the title bar. */
2730 foreach_displayed_view (view
, i
) {
2732 view
->win
= newwin(view
->height
, 0, offset
, 0);
2734 die("Failed to create %s view", view
->name
);
2736 scrollok(view
->win
, FALSE
);
2738 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2740 die("Failed to create title window");
2743 wresize(view
->win
, view
->height
, view
->width
);
2744 mvwin(view
->win
, offset
, 0);
2745 mvwin(view
->title
, offset
+ view
->height
, 0);
2748 offset
+= view
->height
+ 1;
2753 redraw_display(bool clear
)
2758 foreach_displayed_view (view
, i
) {
2762 update_view_title(view
);
2772 toggle_enum_option_do(unsigned int *opt
, const char *help
,
2773 const struct enum_map
*map
, size_t size
)
2775 *opt
= (*opt
+ 1) % size
;
2776 redraw_display(FALSE
);
2777 report("Displaying %s %s", enum_name(map
[*opt
]), help
);
2780 #define toggle_enum_option(opt, help, map) \
2781 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2783 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2784 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2787 toggle_view_option(bool *option
, const char *help
)
2790 redraw_display(FALSE
);
2791 report("%sabling %s", *option
? "En" : "Dis", help
);
2795 open_option_menu(void)
2797 const struct menu_item menu
[] = {
2798 { '.', "line numbers", &opt_line_number
},
2799 { 'D', "date display", &opt_date
},
2800 { 'A', "author display", &opt_author
},
2801 { 'g', "revision graph display", &opt_rev_graph
},
2802 { 'F', "reference display", &opt_show_refs
},
2807 if (prompt_menu("Toggle option", menu
, &selected
)) {
2808 if (menu
[selected
].data
== &opt_date
)
2810 else if (menu
[selected
].data
== &opt_author
)
2813 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2818 maximize_view(struct view
*view
)
2820 memset(display
, 0, sizeof(display
));
2822 display
[current_view
] = view
;
2824 redraw_display(FALSE
);
2834 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2836 if (lineno
>= view
->lines
)
2837 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2839 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2840 unsigned long half
= view
->height
/ 2;
2843 offset
= lineno
- half
;
2848 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2849 view
->offset
= offset
;
2850 view
->lineno
= lineno
;
2857 /* Scrolling backend */
2859 do_scroll_view(struct view
*view
, int lines
)
2861 bool redraw_current_line
= FALSE
;
2863 /* The rendering expects the new offset. */
2864 view
->offset
+= lines
;
2866 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2869 /* Move current line into the view. */
2870 if (view
->lineno
< view
->offset
) {
2871 view
->lineno
= view
->offset
;
2872 redraw_current_line
= TRUE
;
2873 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2874 view
->lineno
= view
->offset
+ view
->height
- 1;
2875 redraw_current_line
= TRUE
;
2878 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2880 /* Redraw the whole screen if scrolling is pointless. */
2881 if (view
->height
< ABS(lines
)) {
2885 int line
= lines
> 0 ? view
->height
- lines
: 0;
2886 int end
= line
+ ABS(lines
);
2888 scrollok(view
->win
, TRUE
);
2889 wscrl(view
->win
, lines
);
2890 scrollok(view
->win
, FALSE
);
2892 while (line
< end
&& draw_view_line(view
, line
))
2895 if (redraw_current_line
)
2896 draw_view_line(view
, view
->lineno
- view
->offset
);
2897 wnoutrefresh(view
->win
);
2900 view
->has_scrolled
= TRUE
;
2904 /* Scroll frontend */
2906 scroll_view(struct view
*view
, enum request request
)
2910 assert(view_is_displayed(view
));
2913 case REQ_SCROLL_FIRST_COL
:
2915 redraw_view_from(view
, 0);
2918 case REQ_SCROLL_LEFT
:
2919 if (view
->yoffset
== 0) {
2920 report("Cannot scroll beyond the first column");
2923 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2926 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2927 redraw_view_from(view
, 0);
2930 case REQ_SCROLL_RIGHT
:
2931 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2935 case REQ_SCROLL_PAGE_DOWN
:
2936 lines
= view
->height
;
2937 case REQ_SCROLL_LINE_DOWN
:
2938 if (view
->offset
+ lines
> view
->lines
)
2939 lines
= view
->lines
- view
->offset
;
2941 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2942 report("Cannot scroll beyond the last line");
2947 case REQ_SCROLL_PAGE_UP
:
2948 lines
= view
->height
;
2949 case REQ_SCROLL_LINE_UP
:
2950 if (lines
> view
->offset
)
2951 lines
= view
->offset
;
2954 report("Cannot scroll beyond the first line");
2962 die("request %d not handled in switch", request
);
2965 do_scroll_view(view
, lines
);
2970 move_view(struct view
*view
, enum request request
)
2972 int scroll_steps
= 0;
2976 case REQ_MOVE_FIRST_LINE
:
2977 steps
= -view
->lineno
;
2980 case REQ_MOVE_LAST_LINE
:
2981 steps
= view
->lines
- view
->lineno
- 1;
2984 case REQ_MOVE_PAGE_UP
:
2985 steps
= view
->height
> view
->lineno
2986 ? -view
->lineno
: -view
->height
;
2989 case REQ_MOVE_PAGE_DOWN
:
2990 steps
= view
->lineno
+ view
->height
>= view
->lines
2991 ? view
->lines
- view
->lineno
- 1 : view
->height
;
3003 die("request %d not handled in switch", request
);
3006 if (steps
<= 0 && view
->lineno
== 0) {
3007 report("Cannot move beyond the first line");
3010 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
3011 report("Cannot move beyond the last line");
3015 /* Move the current line */
3016 view
->lineno
+= steps
;
3017 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
3019 /* Check whether the view needs to be scrolled */
3020 if (view
->lineno
< view
->offset
||
3021 view
->lineno
>= view
->offset
+ view
->height
) {
3022 scroll_steps
= steps
;
3023 if (steps
< 0 && -steps
> view
->offset
) {
3024 scroll_steps
= -view
->offset
;
3026 } else if (steps
> 0) {
3027 if (view
->lineno
== view
->lines
- 1 &&
3028 view
->lines
> view
->height
) {
3029 scroll_steps
= view
->lines
- view
->offset
- 1;
3030 if (scroll_steps
>= view
->height
)
3031 scroll_steps
-= view
->height
- 1;
3036 if (!view_is_displayed(view
)) {
3037 view
->offset
+= scroll_steps
;
3038 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
3039 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
3043 /* Repaint the old "current" line if we be scrolling */
3044 if (ABS(steps
) < view
->height
)
3045 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
3048 do_scroll_view(view
, scroll_steps
);
3052 /* Draw the current line */
3053 draw_view_line(view
, view
->lineno
- view
->offset
);
3055 wnoutrefresh(view
->win
);
3064 static void search_view(struct view
*view
, enum request request
);
3067 grep_text(struct view
*view
, const char *text
[])
3072 for (i
= 0; text
[i
]; i
++)
3074 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
3080 select_view_line(struct view
*view
, unsigned long lineno
)
3082 unsigned long old_lineno
= view
->lineno
;
3083 unsigned long old_offset
= view
->offset
;
3085 if (goto_view_line(view
, view
->offset
, lineno
)) {
3086 if (view_is_displayed(view
)) {
3087 if (old_offset
!= view
->offset
) {
3090 draw_view_line(view
, old_lineno
- view
->offset
);
3091 draw_view_line(view
, view
->lineno
- view
->offset
);
3092 wnoutrefresh(view
->win
);
3095 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
3101 find_next(struct view
*view
, enum request request
)
3103 unsigned long lineno
= view
->lineno
;
3108 report("No previous search");
3110 search_view(view
, request
);
3120 case REQ_SEARCH_BACK
:
3129 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
3130 lineno
+= direction
;
3132 /* Note, lineno is unsigned long so will wrap around in which case it
3133 * will become bigger than view->lines. */
3134 for (; lineno
< view
->lines
; lineno
+= direction
) {
3135 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
3136 select_view_line(view
, lineno
);
3137 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
3142 report("No match found for '%s'", view
->grep
);
3146 search_view(struct view
*view
, enum request request
)
3151 regfree(view
->regex
);
3154 view
->regex
= calloc(1, sizeof(*view
->regex
));
3159 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
3160 if (regex_err
!= 0) {
3161 char buf
[SIZEOF_STR
] = "unknown error";
3163 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
3164 report("Search failed: %s", buf
);
3168 string_copy(view
->grep
, opt_search
);
3170 find_next(view
, request
);
3174 * Incremental updating
3178 reset_view(struct view
*view
)
3182 for (i
= 0; i
< view
->lines
; i
++)
3183 free(view
->line
[i
].data
);
3186 view
->p_offset
= view
->offset
;
3187 view
->p_yoffset
= view
->yoffset
;
3188 view
->p_lineno
= view
->lineno
;
3196 view
->update_secs
= 0;
3200 format_arg(const char *name
)
3206 const char *value_if_empty
;
3208 #define FORMAT_VAR(name, value, value_if_empty) \
3209 { name, STRING_SIZE(name), value, value_if_empty }
3210 FORMAT_VAR("%(directory)", opt_path
, ""),
3211 FORMAT_VAR("%(file)", opt_file
, ""),
3212 FORMAT_VAR("%(ref)", opt_ref
, "HEAD"),
3213 FORMAT_VAR("%(head)", ref_head
, ""),
3214 FORMAT_VAR("%(commit)", ref_commit
, ""),
3215 FORMAT_VAR("%(blob)", ref_blob
, ""),
3216 FORMAT_VAR("%(branch)", ref_branch
, ""),
3220 for (i
= 0; i
< ARRAY_SIZE(vars
); i
++)
3221 if (!strncmp(name
, vars
[i
].name
, vars
[i
].namelen
))
3222 return *vars
[i
].value
? vars
[i
].value
: vars
[i
].value_if_empty
;
3224 report("Unknown replacement: `%s`", name
);
3229 format_argv(const char ***dst_argv
, const char *src_argv
[], bool replace
, bool first
)
3231 char buf
[SIZEOF_STR
];
3234 argv_free(*dst_argv
);
3236 for (argc
= 0; src_argv
[argc
]; argc
++) {
3237 const char *arg
= src_argv
[argc
];
3240 if (!strcmp(arg
, "%(fileargs)")) {
3241 if (!argv_append_array(dst_argv
, opt_file_argv
))
3245 } else if (!strcmp(arg
, "%(diffargs)")) {
3246 if (!argv_append_array(dst_argv
, opt_diff_argv
))
3250 } else if (!strcmp(arg
, "%(revargs)") ||
3251 (first
&& !strcmp(arg
, "%(commit)"))) {
3252 if (!argv_append_array(dst_argv
, opt_rev_argv
))
3258 char *next
= strstr(arg
, "%(");
3259 int len
= next
- arg
;
3262 if (!next
|| !replace
) {
3267 value
= format_arg(next
);
3274 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
3277 arg
= next
&& replace
? strchr(next
, ')') + 1 : NULL
;
3280 if (!argv_append(dst_argv
, buf
))
3284 return src_argv
[argc
] == NULL
;
3288 restore_view_position(struct view
*view
)
3290 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
3293 /* Changing the view position cancels the restoring. */
3294 /* FIXME: Changing back to the first line is not detected. */
3295 if (view
->offset
!= 0 || view
->lineno
!= 0) {
3296 view
->p_restore
= FALSE
;
3300 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
3301 view_is_displayed(view
))
3304 view
->yoffset
= view
->p_yoffset
;
3305 view
->p_restore
= FALSE
;
3311 end_update(struct view
*view
, bool force
)
3315 while (!view
->ops
->read(view
, NULL
))
3319 io_kill(view
->pipe
);
3320 io_done(view
->pipe
);
3325 setup_update(struct view
*view
, const char *vid
)
3328 string_copy_rev(view
->vid
, vid
);
3329 view
->pipe
= &view
->io
;
3330 view
->start_time
= time(NULL
);
3334 prepare_io(struct view
*view
, const char *dir
, const char *argv
[], bool replace
)
3337 return format_argv(&view
->argv
, argv
, replace
, !view
->prev
);
3341 prepare_update(struct view
*view
, const char *argv
[], const char *dir
)
3344 end_update(view
, TRUE
);
3345 return prepare_io(view
, dir
, argv
, FALSE
);
3349 start_update(struct view
*view
, const char **argv
, const char *dir
)
3352 io_done(view
->pipe
);
3353 return prepare_io(view
, dir
, argv
, FALSE
) &&
3354 io_run(&view
->io
, IO_RD
, dir
, view
->argv
);
3358 prepare_update_file(struct view
*view
, const char *name
)
3361 end_update(view
, TRUE
);
3362 argv_free(view
->argv
);
3363 return io_open(&view
->io
, "%s/%s", opt_cdup
[0] ? opt_cdup
: ".", name
);
3367 begin_update(struct view
*view
, bool refresh
)
3370 end_update(view
, TRUE
);
3373 if (view
->ops
->prepare
) {
3374 if (!view
->ops
->prepare(view
))
3376 } else if (!prepare_io(view
, NULL
, view
->ops
->argv
, TRUE
)) {
3380 /* Put the current ref_* value to the view title ref
3381 * member. This is needed by the blob view. Most other
3382 * views sets it automatically after loading because the
3383 * first line is a commit line. */
3384 string_copy_rev(view
->ref
, view
->id
);
3387 if (view
->argv
&& view
->argv
[0] &&
3388 !io_run(&view
->io
, IO_RD
, view
->dir
, view
->argv
))
3391 setup_update(view
, view
->id
);
3397 update_view(struct view
*view
)
3399 char out_buffer
[BUFSIZ
* 2];
3401 /* Clear the view and redraw everything since the tree sorting
3402 * might have rearranged things. */
3403 bool redraw
= view
->lines
== 0;
3404 bool can_read
= TRUE
;
3409 if (!io_can_read(view
->pipe
)) {
3410 if (view
->lines
== 0 && view_is_displayed(view
)) {
3411 time_t secs
= time(NULL
) - view
->start_time
;
3413 if (secs
> 1 && secs
> view
->update_secs
) {
3414 if (view
->update_secs
== 0)
3416 update_view_title(view
);
3417 view
->update_secs
= secs
;
3423 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
3424 if (opt_iconv_in
!= ICONV_NONE
) {
3425 ICONV_CONST
char *inbuf
= line
;
3426 size_t inlen
= strlen(line
) + 1;
3428 char *outbuf
= out_buffer
;
3429 size_t outlen
= sizeof(out_buffer
);
3433 ret
= iconv(opt_iconv_in
, &inbuf
, &inlen
, &outbuf
, &outlen
);
3434 if (ret
!= (size_t) -1)
3438 if (!view
->ops
->read(view
, line
)) {
3439 report("Allocation failure");
3440 end_update(view
, TRUE
);
3446 unsigned long lines
= view
->lines
;
3449 for (digits
= 0; lines
; digits
++)
3452 /* Keep the displayed view in sync with line number scaling. */
3453 if (digits
!= view
->digits
) {
3454 view
->digits
= digits
;
3455 if (opt_line_number
|| view
->type
== VIEW_BLAME
)
3460 if (io_error(view
->pipe
)) {
3461 report("Failed to read: %s", io_strerror(view
->pipe
));
3462 end_update(view
, TRUE
);
3464 } else if (io_eof(view
->pipe
)) {
3465 if (view_is_displayed(view
))
3467 end_update(view
, FALSE
);
3470 if (restore_view_position(view
))
3473 if (!view_is_displayed(view
))
3477 redraw_view_from(view
, 0);
3479 redraw_view_dirty(view
);
3481 /* Update the title _after_ the redraw so that if the redraw picks up a
3482 * commit reference in view->ref it'll be available here. */
3483 update_view_title(view
);
3487 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
3489 static struct line
*
3490 add_line_data(struct view
*view
, void *data
, enum line_type type
)
3494 if (!realloc_lines(&view
->line
, view
->lines
, 1))
3497 line
= &view
->line
[view
->lines
++];
3498 memset(line
, 0, sizeof(*line
));
3506 static struct line
*
3507 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
3509 char *data
= text
? strdup(text
) : NULL
;
3511 return data
? add_line_data(view
, data
, type
) : NULL
;
3514 static struct line
*
3515 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3517 char buf
[SIZEOF_STR
];
3520 va_start(args
, fmt
);
3521 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3525 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3533 OPEN_DEFAULT
= 0, /* Use default view switching. */
3534 OPEN_SPLIT
= 1, /* Split current view. */
3535 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3536 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3537 OPEN_PREPARED
= 32, /* Open already prepared command. */
3541 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3543 bool split
= !!(flags
& OPEN_SPLIT
);
3544 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3545 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3546 struct view
*view
= VIEW(request
);
3547 int nviews
= displayed_views();
3548 struct view
*base_view
= display
[0];
3550 if (view
== prev
&& nviews
== 1 && !reload
) {
3551 report("Already in %s view", view
->name
);
3555 if (view
->git_dir
&& !opt_git_dir
[0]) {
3556 report("The %s view is disabled in pager view", view
->name
);
3563 view
->parent
= prev
;
3564 } else if (!nomaximize
) {
3565 /* Maximize the current view. */
3566 memset(display
, 0, sizeof(display
));
3568 display
[current_view
] = view
;
3571 /* No prev signals that this is the first loaded view. */
3572 if (prev
&& view
!= prev
) {
3576 /* Resize the view when switching between split- and full-screen,
3577 * or when switching between two different full-screen views. */
3578 if (nviews
!= displayed_views() ||
3579 (nviews
== 1 && base_view
!= display
[0]))
3582 if (view
->ops
->open
) {
3584 end_update(view
, TRUE
);
3585 if (!view
->ops
->open(view
)) {
3586 report("Failed to load %s view", view
->name
);
3589 restore_view_position(view
);
3591 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3592 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3593 report("Failed to load %s view", view
->name
);
3597 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3598 /* Take the title line into account. */
3599 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3601 /* Scroll the view that was split if the current line is
3602 * outside the new limited view. */
3603 do_scroll_view(prev
, lines
);
3606 if (prev
&& view
!= prev
&& split
&& view_is_displayed(prev
)) {
3607 /* "Blur" the previous view. */
3608 update_view_title(prev
);
3611 if (view
->pipe
&& view
->lines
== 0) {
3612 /* Clear the old view and let the incremental updating refill
3615 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3617 } else if (view_is_displayed(view
)) {
3624 open_external_viewer(const char *argv
[], const char *dir
)
3626 def_prog_mode(); /* save current tty modes */
3627 endwin(); /* restore original tty modes */
3628 io_run_fg(argv
, dir
);
3629 fprintf(stderr
, "Press Enter to continue");
3632 redraw_display(TRUE
);
3636 open_mergetool(const char *file
)
3638 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3640 open_external_viewer(mergetool_argv
, opt_cdup
);
3644 open_editor(const char *file
)
3646 const char *editor_argv
[] = { "vi", file
, NULL
};
3649 editor
= getenv("GIT_EDITOR");
3650 if (!editor
&& *opt_editor
)
3651 editor
= opt_editor
;
3653 editor
= getenv("VISUAL");
3655 editor
= getenv("EDITOR");
3659 editor_argv
[0] = editor
;
3660 open_external_viewer(editor_argv
, opt_cdup
);
3664 open_run_request(enum request request
)
3666 struct run_request
*req
= get_run_request(request
);
3667 const char **argv
= NULL
;
3670 report("Unknown run request");
3674 if (format_argv(&argv
, req
->argv
, TRUE
, FALSE
))
3675 open_external_viewer(argv
, NULL
);
3682 * User request switch noodle
3686 view_driver(struct view
*view
, enum request request
)
3690 if (request
== REQ_NONE
)
3693 if (request
> REQ_NONE
) {
3694 open_run_request(request
);
3695 view_request(view
, REQ_REFRESH
);
3699 request
= view_request(view
, request
);
3700 if (request
== REQ_NONE
)
3706 case REQ_MOVE_PAGE_UP
:
3707 case REQ_MOVE_PAGE_DOWN
:
3708 case REQ_MOVE_FIRST_LINE
:
3709 case REQ_MOVE_LAST_LINE
:
3710 move_view(view
, request
);
3713 case REQ_SCROLL_FIRST_COL
:
3714 case REQ_SCROLL_LEFT
:
3715 case REQ_SCROLL_RIGHT
:
3716 case REQ_SCROLL_LINE_DOWN
:
3717 case REQ_SCROLL_LINE_UP
:
3718 case REQ_SCROLL_PAGE_DOWN
:
3719 case REQ_SCROLL_PAGE_UP
:
3720 scroll_view(view
, request
);
3723 case REQ_VIEW_BLAME
:
3725 report("No file chosen, press %s to open tree view",
3726 get_key(view
->keymap
, REQ_VIEW_TREE
));
3729 open_view(view
, request
, OPEN_DEFAULT
);
3734 report("No file chosen, press %s to open tree view",
3735 get_key(view
->keymap
, REQ_VIEW_TREE
));
3738 open_view(view
, request
, OPEN_DEFAULT
);
3741 case REQ_VIEW_PAGER
:
3743 if (!io_open(&VIEW(REQ_VIEW_PAGER
)->io
, ""))
3744 die("Failed to open stdin");
3745 open_view(view
, request
, OPEN_PREPARED
);
3749 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3750 report("No pager content, press %s to run command from prompt",
3751 get_key(view
->keymap
, REQ_PROMPT
));
3754 open_view(view
, request
, OPEN_DEFAULT
);
3757 case REQ_VIEW_STAGE
:
3758 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3759 report("No stage content, press %s to open the status view and choose file",
3760 get_key(view
->keymap
, REQ_VIEW_STATUS
));
3763 open_view(view
, request
, OPEN_DEFAULT
);
3766 case REQ_VIEW_STATUS
:
3767 if (opt_is_inside_work_tree
== FALSE
) {
3768 report("The status view requires a working tree");
3771 open_view(view
, request
, OPEN_DEFAULT
);
3779 case REQ_VIEW_BRANCH
:
3780 open_view(view
, request
, OPEN_DEFAULT
);
3785 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3790 view
= view
->parent
;
3791 line
= view
->lineno
;
3792 move_view(view
, request
);
3793 if (view_is_displayed(view
))
3794 update_view_title(view
);
3795 if (line
!= view
->lineno
)
3796 view_request(view
, REQ_ENTER
);
3798 move_view(view
, request
);
3804 int nviews
= displayed_views();
3805 int next_view
= (current_view
+ 1) % nviews
;
3807 if (next_view
== current_view
) {
3808 report("Only one view is displayed");
3812 current_view
= next_view
;
3813 /* Blur out the title of the previous view. */
3814 update_view_title(view
);
3819 report("Refreshing is not yet supported for the %s view", view
->name
);
3823 if (displayed_views() == 2)
3824 maximize_view(view
);
3831 case REQ_TOGGLE_LINENO
:
3832 toggle_view_option(&opt_line_number
, "line numbers");
3835 case REQ_TOGGLE_DATE
:
3839 case REQ_TOGGLE_AUTHOR
:
3843 case REQ_TOGGLE_REV_GRAPH
:
3844 toggle_view_option(&opt_rev_graph
, "revision graph display");
3847 case REQ_TOGGLE_REFS
:
3848 toggle_view_option(&opt_show_refs
, "reference display");
3851 case REQ_TOGGLE_SORT_FIELD
:
3852 case REQ_TOGGLE_SORT_ORDER
:
3853 report("Sorting is not yet supported for the %s view", view
->name
);
3857 case REQ_SEARCH_BACK
:
3858 search_view(view
, request
);
3863 find_next(view
, request
);
3866 case REQ_STOP_LOADING
:
3867 foreach_view(view
, i
) {
3869 report("Stopped loading the %s view", view
->name
),
3870 end_update(view
, TRUE
);
3874 case REQ_SHOW_VERSION
:
3875 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3878 case REQ_SCREEN_REDRAW
:
3879 redraw_display(TRUE
);
3883 report("Nothing to edit");
3887 report("Nothing to enter");
3890 case REQ_VIEW_CLOSE
:
3891 /* XXX: Mark closed views by letting view->prev point to the
3892 * view itself. Parents to closed view should never be
3894 if (view
->prev
&& view
->prev
!= view
) {
3895 maximize_view(view
->prev
);
3904 report("Unknown key, press %s for help",
3905 get_key(view
->keymap
, REQ_VIEW_HELP
));
3914 * View backend utilities
3924 const enum sort_field
*fields
;
3925 size_t size
, current
;
3929 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3930 #define get_sort_field(state) ((state).fields[(state).current])
3931 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3934 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3935 int (*compare
)(const void *, const void *))
3938 case REQ_TOGGLE_SORT_FIELD
:
3939 state
->current
= (state
->current
+ 1) % state
->size
;
3942 case REQ_TOGGLE_SORT_ORDER
:
3943 state
->reverse
= !state
->reverse
;
3946 die("Not a sort request");
3949 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3953 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3955 /* Small author cache to reduce memory consumption. It uses binary
3956 * search to lookup or find place to position new entries. No entries
3957 * are ever freed. */
3959 get_author(const char *name
)
3961 static const char **authors
;
3962 static size_t authors_size
;
3963 int from
= 0, to
= authors_size
- 1;
3965 while (from
<= to
) {
3966 size_t pos
= (to
+ from
) / 2;
3967 int cmp
= strcmp(name
, authors
[pos
]);
3970 return authors
[pos
];
3978 if (!realloc_authors(&authors
, authors_size
, 1))
3980 name
= strdup(name
);
3984 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3985 authors
[from
] = name
;
3992 parse_timesec(struct time
*time
, const char *sec
)
3994 time
->sec
= (time_t) atol(sec
);
3998 parse_timezone(struct time
*time
, const char *zone
)
4002 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
4003 tz
+= ('0' - zone
[2]) * 60 * 60;
4004 tz
+= ('0' - zone
[3]) * 60 * 10;
4005 tz
+= ('0' - zone
[4]) * 60;
4014 /* Parse author lines where the name may be empty:
4015 * author <email@address.tld> 1138474660 +0100
4018 parse_author_line(char *ident
, const char **author
, struct time
*time
)
4020 char *nameend
= strchr(ident
, '<');
4021 char *emailend
= strchr(ident
, '>');
4023 if (nameend
&& emailend
)
4024 *nameend
= *emailend
= 0;
4025 ident
= chomp_string(ident
);
4028 ident
= chomp_string(nameend
+ 1);
4033 *author
= get_author(ident
);
4035 /* Parse epoch and timezone */
4036 if (emailend
&& emailend
[1] == ' ') {
4037 char *secs
= emailend
+ 2;
4038 char *zone
= strchr(secs
, ' ');
4040 parse_timesec(time
, secs
);
4042 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
4043 parse_timezone(time
, zone
+ 1);
4052 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4054 if (opt_line_number
&& draw_lineno(view
, lineno
))
4057 draw_text(view
, line
->type
, line
->data
, TRUE
);
4062 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
4064 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
4065 char ref
[SIZEOF_STR
];
4067 if (!io_run_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
4070 /* This is the only fatal call, since it can "corrupt" the buffer. */
4071 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
4078 add_pager_refs(struct view
*view
, struct line
*line
)
4080 char buf
[SIZEOF_STR
];
4081 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
4082 struct ref_list
*list
;
4083 size_t bufpos
= 0, i
;
4084 const char *sep
= "Refs: ";
4085 bool is_tag
= FALSE
;
4087 assert(line
->type
== LINE_COMMIT
);
4089 list
= get_ref_list(commit_id
);
4091 if (view
->type
== VIEW_DIFF
)
4092 goto try_add_describe_ref
;
4096 for (i
= 0; i
< list
->size
; i
++) {
4097 struct ref
*ref
= list
->refs
[i
];
4098 const char *fmt
= ref
->tag
? "%s[%s]" :
4099 ref
->remote
? "%s<%s>" : "%s%s";
4101 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
4108 if (!is_tag
&& view
->type
== VIEW_DIFF
) {
4109 try_add_describe_ref
:
4110 /* Add <tag>-g<commit_id> "fake" reference. */
4111 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
4118 add_line_text(view
, buf
, LINE_PP_REFS
);
4122 pager_read(struct view
*view
, char *data
)
4129 line
= add_line_text(view
, data
, get_line_type(data
));
4133 if (line
->type
== LINE_COMMIT
&&
4134 (view
->type
== VIEW_DIFF
||
4135 view
->type
== VIEW_LOG
))
4136 add_pager_refs(view
, line
);
4142 pager_request(struct view
*view
, enum request request
, struct line
*line
)
4146 if (request
!= REQ_ENTER
)
4149 if (line
->type
== LINE_COMMIT
&&
4150 (view
->type
== VIEW_LOG
||
4151 view
->type
== VIEW_PAGER
)) {
4152 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
4156 /* Always scroll the view even if it was split. That way
4157 * you can use Enter to scroll through the log view and
4158 * split open each commit diff. */
4159 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
4161 /* FIXME: A minor workaround. Scrolling the view will call report("")
4162 * but if we are scrolling a non-current view this won't properly
4163 * update the view title. */
4165 update_view_title(view
);
4171 pager_grep(struct view
*view
, struct line
*line
)
4173 const char *text
[] = { line
->data
, NULL
};
4175 return grep_text(view
, text
);
4179 pager_select(struct view
*view
, struct line
*line
)
4181 if (line
->type
== LINE_COMMIT
) {
4182 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
4184 if (view
->type
!= VIEW_PAGER
)
4185 string_copy_rev(view
->ref
, text
);
4186 string_copy_rev(ref_commit
, text
);
4190 static struct view_ops pager_ops
= {
4201 static const char *log_argv
[SIZEOF_ARG
] = {
4202 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4206 log_request(struct view
*view
, enum request request
, struct line
*line
)
4211 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
4214 return pager_request(view
, request
, line
);
4218 static struct view_ops log_ops
= {
4229 static const char *diff_argv
[SIZEOF_ARG
] = {
4230 "git", "show", "--pretty=fuller", "--no-color", "--root",
4231 "--patch-with-stat", "--find-copies-harder", "-C",
4232 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4236 diff_read(struct view
*view
, char *data
)
4239 /* Fall back to retry if no diff will be shown. */
4240 if (view
->lines
== 0 && opt_file_argv
) {
4241 int pos
= argv_size(view
->argv
)
4242 - argv_size(opt_file_argv
) - 1;
4244 if (pos
> 0 && !strcmp(view
->argv
[pos
], "--")) {
4245 for (; view
->argv
[pos
]; pos
++) {
4246 free((void *) view
->argv
[pos
]);
4247 view
->argv
[pos
] = NULL
;
4251 io_done(view
->pipe
);
4252 if (io_run(&view
->io
, IO_RD
, view
->dir
, view
->argv
))
4259 return pager_read(view
, data
);
4262 static struct view_ops diff_ops
= {
4277 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
4280 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
4284 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
4285 help_keymap_hidden
[keymap
] ? '+' : '-',
4286 enum_name(keymap_table
[keymap
]));
4288 line
->other
= keymap
;
4290 return help_keymap_hidden
[keymap
];
4294 help_open_keymap(struct view
*view
, enum keymap keymap
)
4296 const char *group
= NULL
;
4297 char buf
[SIZEOF_STR
];
4299 bool add_title
= TRUE
;
4302 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
4303 const char *key
= NULL
;
4305 if (req_info
[i
].request
== REQ_NONE
)
4308 if (!req_info
[i
].request
) {
4309 group
= req_info
[i
].help
;
4313 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4317 if (add_title
&& help_open_keymap_title(view
, keymap
))
4322 add_line_text(view
, group
, LINE_HELP_GROUP
);
4326 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4327 enum_name(req_info
[i
]), req_info
[i
].help
);
4330 group
= "External commands:";
4332 for (i
= 0; i
< run_requests
; i
++) {
4333 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4337 if (!req
|| req
->keymap
!= keymap
)
4340 key
= get_key_name(req
->key
);
4342 key
= "(no key defined)";
4344 if (add_title
&& help_open_keymap_title(view
, keymap
))
4347 add_line_text(view
, group
, LINE_HELP_GROUP
);
4351 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4352 if (!string_format_from(buf
, &bufpos
, "%s%s",
4353 argc
? " " : "", req
->argv
[argc
]))
4356 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4361 help_open(struct view
*view
)
4366 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4367 add_line_text(view
, "", LINE_DEFAULT
);
4369 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4370 help_open_keymap(view
, keymap
);
4376 help_request(struct view
*view
, enum request request
, struct line
*line
)
4380 if (line
->type
== LINE_HELP_KEYMAP
) {
4381 help_keymap_hidden
[line
->other
] =
4382 !help_keymap_hidden
[line
->other
];
4383 view
->p_restore
= TRUE
;
4384 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4389 return pager_request(view
, request
, line
);
4393 static struct view_ops help_ops
= {
4409 struct tree_stack_entry
{
4410 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4411 unsigned long lineno
; /* Line number to restore */
4412 char *name
; /* Position of name in opt_path */
4415 /* The top of the path stack. */
4416 static struct tree_stack_entry
*tree_stack
= NULL
;
4417 unsigned long tree_lineno
= 0;
4420 pop_tree_stack_entry(void)
4422 struct tree_stack_entry
*entry
= tree_stack
;
4424 tree_lineno
= entry
->lineno
;
4426 tree_stack
= entry
->prev
;
4431 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4433 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4434 size_t pathlen
= strlen(opt_path
);
4439 entry
->prev
= tree_stack
;
4440 entry
->name
= opt_path
+ pathlen
;
4443 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4444 pop_tree_stack_entry();
4448 /* Move the current line to the first tree entry. */
4450 entry
->lineno
= lineno
;
4453 /* Parse output from git-ls-tree(1):
4455 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4458 #define SIZEOF_TREE_ATTR \
4459 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4461 #define SIZEOF_TREE_MODE \
4462 STRING_SIZE("100644 ")
4464 #define TREE_ID_OFFSET \
4465 STRING_SIZE("100644 blob ")
4468 char id
[SIZEOF_REV
];
4470 struct time time
; /* Date from the author ident. */
4471 const char *author
; /* Author of the commit. */
4476 tree_path(const struct line
*line
)
4478 return ((struct tree_entry
*) line
->data
)->name
;
4482 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4484 if (line1
->type
!= line2
->type
)
4485 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4486 return strcmp(tree_path(line1
), tree_path(line2
));
4489 static const enum sort_field tree_sort_fields
[] = {
4490 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4492 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4495 tree_compare(const void *l1
, const void *l2
)
4497 const struct line
*line1
= (const struct line
*) l1
;
4498 const struct line
*line2
= (const struct line
*) l2
;
4499 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4500 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4502 if (line1
->type
== LINE_TREE_HEAD
)
4504 if (line2
->type
== LINE_TREE_HEAD
)
4507 switch (get_sort_field(tree_sort_state
)) {
4509 return sort_order(tree_sort_state
, timecmp(&entry1
->time
, &entry2
->time
));
4511 case ORDERBY_AUTHOR
:
4512 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4516 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4521 static struct line
*
4522 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4523 const char *mode
, const char *id
)
4525 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4526 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4528 if (!entry
|| !line
) {
4533 strncpy(entry
->name
, path
, strlen(path
));
4535 entry
->mode
= strtoul(mode
, NULL
, 8);
4537 string_copy_rev(entry
->id
, id
);
4543 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4545 static const char *author_name
;
4546 static struct time author_time
;
4548 if (!text
&& *read_date
) {
4553 char *path
= *opt_path
? opt_path
: ".";
4554 /* Find next entry to process */
4555 const char *log_file
[] = {
4556 "git", "log", "--no-color", "--pretty=raw",
4557 "--cc", "--raw", view
->id
, "--", path
, NULL
4561 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4562 report("Tree is empty");
4566 if (!start_update(view
, log_file
, opt_cdup
)) {
4567 report("Failed to load tree data");
4574 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4575 parse_author_line(text
+ STRING_SIZE("author "),
4576 &author_name
, &author_time
);
4578 } else if (*text
== ':') {
4580 size_t annotated
= 1;
4583 pos
= strchr(text
, '\t');
4587 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4588 text
+= strlen(opt_path
);
4589 pos
= strchr(text
, '/');
4593 for (i
= 1; i
< view
->lines
; i
++) {
4594 struct line
*line
= &view
->line
[i
];
4595 struct tree_entry
*entry
= line
->data
;
4597 annotated
+= !!entry
->author
;
4598 if (entry
->author
|| strcmp(entry
->name
, text
))
4601 entry
->author
= author_name
;
4602 entry
->time
= author_time
;
4607 if (annotated
== view
->lines
)
4608 io_kill(view
->pipe
);
4614 tree_read(struct view
*view
, char *text
)
4616 static bool read_date
= FALSE
;
4617 struct tree_entry
*data
;
4618 struct line
*entry
, *line
;
4619 enum line_type type
;
4620 size_t textlen
= text
? strlen(text
) : 0;
4621 char *path
= text
+ SIZEOF_TREE_ATTR
;
4623 if (read_date
|| !text
)
4624 return tree_read_date(view
, text
, &read_date
);
4626 if (textlen
<= SIZEOF_TREE_ATTR
)
4628 if (view
->lines
== 0 &&
4629 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4632 /* Strip the path part ... */
4634 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4635 size_t striplen
= strlen(opt_path
);
4637 if (pathlen
> striplen
)
4638 memmove(path
, path
+ striplen
,
4639 pathlen
- striplen
+ 1);
4641 /* Insert "link" to parent directory. */
4642 if (view
->lines
== 1 &&
4643 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4647 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4648 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4653 /* Skip "Directory ..." and ".." line. */
4654 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4655 if (tree_compare_entry(line
, entry
) <= 0)
4658 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4662 for (; line
<= entry
; line
++)
4663 line
->dirty
= line
->cleareol
= 1;
4667 if (tree_lineno
> view
->lineno
) {
4668 view
->lineno
= tree_lineno
;
4676 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4678 struct tree_entry
*entry
= line
->data
;
4680 if (line
->type
== LINE_TREE_HEAD
) {
4681 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4684 if (draw_mode(view
, entry
->mode
))
4687 if (opt_author
&& draw_author(view
, entry
->author
))
4690 if (opt_date
&& draw_date(view
, &entry
->time
))
4693 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4699 open_blob_editor(const char *id
)
4701 const char *blob_argv
[] = { "git", "cat-file", "blob", id
, NULL
};
4702 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4703 int fd
= mkstemp(file
);
4706 report("Failed to create temporary file");
4707 else if (!io_run_append(blob_argv
, fd
))
4708 report("Failed to save blob data to file");
4716 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4718 enum open_flags flags
;
4719 struct tree_entry
*entry
= line
->data
;
4722 case REQ_VIEW_BLAME
:
4723 if (line
->type
!= LINE_TREE_FILE
) {
4724 report("Blame only supported for files");
4728 string_copy(opt_ref
, view
->vid
);
4732 if (line
->type
!= LINE_TREE_FILE
) {
4733 report("Edit only supported for files");
4734 } else if (!is_head_commit(view
->vid
)) {
4735 open_blob_editor(entry
->id
);
4737 open_editor(opt_file
);
4741 case REQ_TOGGLE_SORT_FIELD
:
4742 case REQ_TOGGLE_SORT_ORDER
:
4743 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4748 /* quit view if at top of tree */
4749 return REQ_VIEW_CLOSE
;
4752 line
= &view
->line
[1];
4762 /* Cleanup the stack if the tree view is at a different tree. */
4763 while (!*opt_path
&& tree_stack
)
4764 pop_tree_stack_entry();
4766 switch (line
->type
) {
4768 /* Depending on whether it is a subdirectory or parent link
4769 * mangle the path buffer. */
4770 if (line
== &view
->line
[1] && *opt_path
) {
4771 pop_tree_stack_entry();
4774 const char *basename
= tree_path(line
);
4776 push_tree_stack_entry(basename
, view
->lineno
);
4779 /* Trees and subtrees share the same ID, so they are not not
4780 * unique like blobs. */
4781 flags
= OPEN_RELOAD
;
4782 request
= REQ_VIEW_TREE
;
4785 case LINE_TREE_FILE
:
4786 flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
4787 request
= REQ_VIEW_BLOB
;
4794 open_view(view
, request
, flags
);
4795 if (request
== REQ_VIEW_TREE
)
4796 view
->lineno
= tree_lineno
;
4802 tree_grep(struct view
*view
, struct line
*line
)
4804 struct tree_entry
*entry
= line
->data
;
4805 const char *text
[] = {
4807 opt_author
? entry
->author
: "",
4808 mkdate(&entry
->time
, opt_date
),
4812 return grep_text(view
, text
);
4816 tree_select(struct view
*view
, struct line
*line
)
4818 struct tree_entry
*entry
= line
->data
;
4820 if (line
->type
== LINE_TREE_FILE
) {
4821 string_copy_rev(ref_blob
, entry
->id
);
4822 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4824 } else if (line
->type
!= LINE_TREE_DIR
) {
4828 string_copy_rev(view
->ref
, entry
->id
);
4832 tree_prepare(struct view
*view
)
4834 if (view
->lines
== 0 && opt_prefix
[0]) {
4835 char *pos
= opt_prefix
;
4837 while (pos
&& *pos
) {
4838 char *end
= strchr(pos
, '/');
4842 push_tree_stack_entry(pos
, 0);
4850 } else if (strcmp(view
->vid
, view
->id
)) {
4854 return prepare_io(view
, opt_cdup
, view
->ops
->argv
, TRUE
);
4857 static const char *tree_argv
[SIZEOF_ARG
] = {
4858 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4861 static struct view_ops tree_ops
= {
4874 blob_read(struct view
*view
, char *line
)
4878 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4882 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4886 open_blob_editor(view
->vid
);
4889 return pager_request(view
, request
, line
);
4893 static const char *blob_argv
[SIZEOF_ARG
] = {
4894 "git", "cat-file", "blob", "%(blob)", NULL
4897 static struct view_ops blob_ops
= {
4911 * Loading the blame view is a two phase job:
4913 * 1. File content is read either using opt_file from the
4914 * filesystem or using git-cat-file.
4915 * 2. Then blame information is incrementally added by
4916 * reading output from git-blame.
4919 struct blame_commit
{
4920 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4921 char title
[128]; /* First line of the commit message. */
4922 const char *author
; /* Author of the commit. */
4923 struct time time
; /* Date from the author ident. */
4924 char filename
[128]; /* Name of file. */
4925 char parent_id
[SIZEOF_REV
]; /* Parent/previous SHA1 ID. */
4926 char parent_filename
[128]; /* Parent/previous name of file. */
4930 struct blame_commit
*commit
;
4931 unsigned long lineno
;
4936 blame_open(struct view
*view
)
4938 char path
[SIZEOF_STR
];
4941 if (!view
->prev
&& *opt_prefix
) {
4942 string_copy(path
, opt_file
);
4943 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4947 if (*opt_ref
|| !io_open(&view
->io
, "%s%s", opt_cdup
, opt_file
)) {
4948 const char *blame_cat_file_argv
[] = {
4949 "git", "cat-file", "blob", path
, NULL
4952 if (!string_format(path
, "%s:%s", opt_ref
, opt_file
) ||
4953 !start_update(view
, blame_cat_file_argv
, opt_cdup
))
4957 /* First pass: remove multiple references to the same commit. */
4958 for (i
= 0; i
< view
->lines
; i
++) {
4959 struct blame
*blame
= view
->line
[i
].data
;
4961 if (blame
->commit
&& blame
->commit
->id
[0])
4962 blame
->commit
->id
[0] = 0;
4964 blame
->commit
= NULL
;
4967 /* Second pass: free existing references. */
4968 for (i
= 0; i
< view
->lines
; i
++) {
4969 struct blame
*blame
= view
->line
[i
].data
;
4972 free(blame
->commit
);
4975 setup_update(view
, opt_file
);
4976 string_format(view
->ref
, "%s ...", opt_file
);
4981 static struct blame_commit
*
4982 get_blame_commit(struct view
*view
, const char *id
)
4986 for (i
= 0; i
< view
->lines
; i
++) {
4987 struct blame
*blame
= view
->line
[i
].data
;
4992 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4993 return blame
->commit
;
4997 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
5000 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
5006 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
5008 const char *pos
= *posref
;
5011 pos
= strchr(pos
+ 1, ' ');
5012 if (!pos
|| !isdigit(pos
[1]))
5014 *number
= atoi(pos
+ 1);
5015 if (*number
< min
|| *number
> max
)
5022 static struct blame_commit
*
5023 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
5025 struct blame_commit
*commit
;
5026 struct blame
*blame
;
5027 const char *pos
= text
+ SIZEOF_REV
- 2;
5028 size_t orig_lineno
= 0;
5032 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
5035 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
5036 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
5037 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
5040 commit
= get_blame_commit(view
, text
);
5046 struct line
*line
= &view
->line
[lineno
+ group
- 1];
5049 blame
->commit
= commit
;
5050 blame
->lineno
= orig_lineno
+ group
- 1;
5058 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
5061 const char *blame_argv
[] = {
5062 "git", "blame", "--incremental",
5063 *opt_ref
? opt_ref
: "--incremental", "--", opt_file
, NULL
5066 if (view
->lines
== 0 && !view
->prev
)
5067 die("No blame exist for %s", view
->vid
);
5069 if (view
->lines
== 0 || !start_update(view
, blame_argv
, opt_cdup
)) {
5070 report("Failed to load blame data");
5078 size_t linelen
= strlen(line
);
5079 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
5084 blame
->commit
= NULL
;
5085 strncpy(blame
->text
, line
, linelen
);
5086 blame
->text
[linelen
] = 0;
5087 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
5092 match_blame_header(const char *name
, char **line
)
5094 size_t namelen
= strlen(name
);
5095 bool matched
= !strncmp(name
, *line
, namelen
);
5104 blame_read(struct view
*view
, char *line
)
5106 static struct blame_commit
*commit
= NULL
;
5107 static int blamed
= 0;
5108 static bool read_file
= TRUE
;
5111 return blame_read_file(view
, line
, &read_file
);
5118 string_format(view
->ref
, "%s", view
->vid
);
5119 if (view_is_displayed(view
)) {
5120 update_view_title(view
);
5121 redraw_view_from(view
, 0);
5127 commit
= parse_blame_commit(view
, line
, &blamed
);
5128 string_format(view
->ref
, "%s %2d%%", view
->vid
,
5129 view
->lines
? blamed
* 100 / view
->lines
: 0);
5131 } else if (match_blame_header("author ", &line
)) {
5132 commit
->author
= get_author(line
);
5134 } else if (match_blame_header("author-time ", &line
)) {
5135 parse_timesec(&commit
->time
, line
);
5137 } else if (match_blame_header("author-tz ", &line
)) {
5138 parse_timezone(&commit
->time
, line
);
5140 } else if (match_blame_header("summary ", &line
)) {
5141 string_ncopy(commit
->title
, line
, strlen(line
));
5143 } else if (match_blame_header("previous ", &line
)) {
5144 if (strlen(line
) <= SIZEOF_REV
)
5146 string_copy_rev(commit
->parent_id
, line
);
5148 string_ncopy(commit
->parent_filename
, line
, strlen(line
));
5150 } else if (match_blame_header("filename ", &line
)) {
5151 string_ncopy(commit
->filename
, line
, strlen(line
));
5159 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5161 struct blame
*blame
= line
->data
;
5162 struct time
*time
= NULL
;
5163 const char *id
= NULL
, *author
= NULL
;
5165 if (blame
->commit
&& *blame
->commit
->filename
) {
5166 id
= blame
->commit
->id
;
5167 author
= blame
->commit
->author
;
5168 time
= &blame
->commit
->time
;
5171 if (opt_date
&& draw_date(view
, time
))
5174 if (opt_author
&& draw_author(view
, author
))
5177 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
5180 if (draw_lineno(view
, lineno
))
5183 draw_text(view
, LINE_DEFAULT
, blame
->text
, TRUE
);
5188 check_blame_commit(struct blame
*blame
, bool check_null_id
)
5191 report("Commit data not loaded yet");
5192 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5193 report("No commit exist for the selected line");
5200 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
5202 char from
[SIZEOF_REF
+ SIZEOF_STR
];
5203 char to
[SIZEOF_REF
+ SIZEOF_STR
];
5204 const char *diff_tree_argv
[] = {
5205 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5206 "-U0", from
, to
, "--", NULL
5209 int parent_lineno
= -1;
5210 int blamed_lineno
= -1;
5213 if (!string_format(from
, "%s:%s", opt_ref
, opt_file
) ||
5214 !string_format(to
, "%s:%s", blame
->commit
->id
, blame
->commit
->filename
) ||
5215 !io_run(&io
, IO_RD
, NULL
, diff_tree_argv
))
5218 while ((line
= io_get(&io
, '\n', TRUE
))) {
5220 char *pos
= strchr(line
, '+');
5222 parent_lineno
= atoi(line
+ 4);
5224 blamed_lineno
= atoi(pos
+ 1);
5226 } else if (*line
== '+' && parent_lineno
!= -1) {
5227 if (blame
->lineno
== blamed_lineno
- 1 &&
5228 !strcmp(blame
->text
, line
+ 1)) {
5229 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
5240 blame_request(struct view
*view
, enum request request
, struct line
*line
)
5242 enum open_flags flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
5243 struct blame
*blame
= line
->data
;
5246 case REQ_VIEW_BLAME
:
5247 if (check_blame_commit(blame
, TRUE
)) {
5248 string_copy(opt_ref
, blame
->commit
->id
);
5249 string_copy(opt_file
, blame
->commit
->filename
);
5251 view
->lineno
= blame
->lineno
;
5252 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5257 if (!check_blame_commit(blame
, TRUE
))
5259 if (!*blame
->commit
->parent_id
) {
5260 report("The selected commit has no parents");
5262 string_copy_rev(opt_ref
, blame
->commit
->parent_id
);
5263 string_copy(opt_file
, blame
->commit
->parent_filename
);
5264 setup_blame_parent_line(view
, blame
);
5265 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5270 if (!check_blame_commit(blame
, FALSE
))
5273 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
5274 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
5277 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
5278 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
5279 const char *diff_index_argv
[] = {
5280 "git", "diff-index", "--root", "--patch-with-stat",
5281 "-C", "-M", "HEAD", "--", view
->vid
, NULL
5284 if (!*blame
->commit
->parent_id
) {
5285 diff_index_argv
[1] = "diff";
5286 diff_index_argv
[2] = "--no-color";
5287 diff_index_argv
[6] = "--";
5288 diff_index_argv
[7] = "/dev/null";
5291 if (!prepare_update(diff
, diff_index_argv
, NULL
)) {
5292 report("Failed to allocate diff command");
5295 flags
|= OPEN_PREPARED
;
5298 open_view(view
, REQ_VIEW_DIFF
, flags
);
5299 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5300 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
5311 blame_grep(struct view
*view
, struct line
*line
)
5313 struct blame
*blame
= line
->data
;
5314 struct blame_commit
*commit
= blame
->commit
;
5315 const char *text
[] = {
5317 commit
? commit
->title
: "",
5318 commit
? commit
->id
: "",
5319 commit
&& opt_author
? commit
->author
: "",
5320 commit
? mkdate(&commit
->time
, opt_date
) : "",
5324 return grep_text(view
, text
);
5328 blame_select(struct view
*view
, struct line
*line
)
5330 struct blame
*blame
= line
->data
;
5331 struct blame_commit
*commit
= blame
->commit
;
5336 if (!strcmp(commit
->id
, NULL_ID
))
5337 string_ncopy(ref_commit
, "HEAD", 4);
5339 string_copy_rev(ref_commit
, commit
->id
);
5342 static struct view_ops blame_ops
= {
5358 const char *author
; /* Author of the last commit. */
5359 struct time time
; /* Date of the last activity. */
5360 const struct ref
*ref
; /* Name and commit ID information. */
5363 static const struct ref branch_all
;
5365 static const enum sort_field branch_sort_fields
[] = {
5366 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5368 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5371 branch_compare(const void *l1
, const void *l2
)
5373 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5374 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5376 switch (get_sort_field(branch_sort_state
)) {
5378 return sort_order(branch_sort_state
, timecmp(&branch1
->time
, &branch2
->time
));
5380 case ORDERBY_AUTHOR
:
5381 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5385 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5390 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5392 struct branch
*branch
= line
->data
;
5393 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5395 if (opt_date
&& draw_date(view
, &branch
->time
))
5398 if (opt_author
&& draw_author(view
, branch
->author
))
5401 draw_text(view
, type
, branch
->ref
== &branch_all
? "All branches" : branch
->ref
->name
, TRUE
);
5406 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5408 struct branch
*branch
= line
->data
;
5413 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5416 case REQ_TOGGLE_SORT_FIELD
:
5417 case REQ_TOGGLE_SORT_ORDER
:
5418 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5423 const struct ref
*ref
= branch
->ref
;
5424 const char *all_branches_argv
[] = {
5425 "git", "log", "--no-color", "--pretty=raw", "--parents",
5427 ref
== &branch_all
? "--all" : ref
->name
, NULL
5429 struct view
*main_view
= VIEW(REQ_VIEW_MAIN
);
5431 if (!prepare_update(main_view
, all_branches_argv
, NULL
))
5432 report("Failed to load view of all branches");
5434 open_view(view
, REQ_VIEW_MAIN
, OPEN_PREPARED
| OPEN_SPLIT
);
5443 branch_read(struct view
*view
, char *line
)
5445 static char id
[SIZEOF_REV
];
5446 struct branch
*reference
;
5452 switch (get_line_type(line
)) {
5454 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5458 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5459 struct branch
*branch
= view
->line
[i
].data
;
5461 if (strcmp(branch
->ref
->id
, id
))
5464 view
->line
[i
].dirty
= TRUE
;
5466 branch
->author
= reference
->author
;
5467 branch
->time
= reference
->time
;
5471 parse_author_line(line
+ STRING_SIZE("author "),
5472 &branch
->author
, &branch
->time
);
5484 branch_open_visitor(void *data
, const struct ref
*ref
)
5486 struct view
*view
= data
;
5487 struct branch
*branch
;
5489 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5492 branch
= calloc(1, sizeof(*branch
));
5497 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5501 branch_open(struct view
*view
)
5503 const char *branch_log
[] = {
5504 "git", "log", "--no-color", "--pretty=raw",
5505 "--simplify-by-decoration", "--all", NULL
5508 if (!start_update(view
, branch_log
, NULL
)) {
5509 report("Failed to load branch data");
5513 setup_update(view
, view
->id
);
5514 branch_open_visitor(view
, &branch_all
);
5515 foreach_ref(branch_open_visitor
, view
);
5516 view
->p_restore
= TRUE
;
5522 branch_grep(struct view
*view
, struct line
*line
)
5524 struct branch
*branch
= line
->data
;
5525 const char *text
[] = {
5531 return grep_text(view
, text
);
5535 branch_select(struct view
*view
, struct line
*line
)
5537 struct branch
*branch
= line
->data
;
5539 string_copy_rev(view
->ref
, branch
->ref
->id
);
5540 string_copy_rev(ref_commit
, branch
->ref
->id
);
5541 string_copy_rev(ref_head
, branch
->ref
->id
);
5542 string_copy_rev(ref_branch
, branch
->ref
->name
);
5545 static struct view_ops branch_ops
= {
5564 char rev
[SIZEOF_REV
];
5565 char name
[SIZEOF_STR
];
5569 char rev
[SIZEOF_REV
];
5570 char name
[SIZEOF_STR
];
5574 static char status_onbranch
[SIZEOF_STR
];
5575 static struct status stage_status
;
5576 static enum line_type stage_line_type
;
5577 static size_t stage_chunks
;
5578 static int *stage_chunk
;
5580 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5582 /* This should work even for the "On branch" line. */
5584 status_has_none(struct view
*view
, struct line
*line
)
5586 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5589 /* Get fields from the diff line:
5590 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5593 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5595 const char *old_mode
= buf
+ 1;
5596 const char *new_mode
= buf
+ 8;
5597 const char *old_rev
= buf
+ 15;
5598 const char *new_rev
= buf
+ 56;
5599 const char *status
= buf
+ 97;
5602 old_mode
[-1] != ':' ||
5603 new_mode
[-1] != ' ' ||
5604 old_rev
[-1] != ' ' ||
5605 new_rev
[-1] != ' ' ||
5609 file
->status
= *status
;
5611 string_copy_rev(file
->old
.rev
, old_rev
);
5612 string_copy_rev(file
->new.rev
, new_rev
);
5614 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5615 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5617 file
->old
.name
[0] = file
->new.name
[0] = 0;
5623 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5625 struct status
*unmerged
= NULL
;
5629 if (!io_run(&io
, IO_RD
, opt_cdup
, argv
))
5632 add_line_data(view
, NULL
, type
);
5634 while ((buf
= io_get(&io
, 0, TRUE
))) {
5635 struct status
*file
= unmerged
;
5638 file
= calloc(1, sizeof(*file
));
5639 if (!file
|| !add_line_data(view
, file
, type
))
5643 /* Parse diff info part. */
5645 file
->status
= status
;
5647 string_copy(file
->old
.rev
, NULL_ID
);
5649 } else if (!file
->status
|| file
== unmerged
) {
5650 if (!status_get_diff(file
, buf
, strlen(buf
)))
5653 buf
= io_get(&io
, 0, TRUE
);
5657 /* Collapse all modified entries that follow an
5658 * associated unmerged entry. */
5659 if (unmerged
== file
) {
5660 unmerged
->status
= 'U';
5662 } else if (file
->status
== 'U') {
5667 /* Grab the old name for rename/copy. */
5668 if (!*file
->old
.name
&&
5669 (file
->status
== 'R' || file
->status
== 'C')) {
5670 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5672 buf
= io_get(&io
, 0, TRUE
);
5677 /* git-ls-files just delivers a NUL separated list of
5678 * file names similar to the second half of the
5679 * git-diff-* output. */
5680 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5681 if (!*file
->old
.name
)
5682 string_copy(file
->old
.name
, file
->new.name
);
5686 if (io_error(&io
)) {
5692 if (!view
->line
[view
->lines
- 1].data
)
5693 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5699 /* Don't show unmerged entries in the staged section. */
5700 static const char *status_diff_index_argv
[] = {
5701 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5702 "--cached", "-M", "HEAD", NULL
5705 static const char *status_diff_files_argv
[] = {
5706 "git", "diff-files", "-z", NULL
5709 static const char *status_list_other_argv
[] = {
5710 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix
, NULL
, NULL
,
5713 static const char *status_list_no_head_argv
[] = {
5714 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5717 static const char *update_index_argv
[] = {
5718 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5721 /* Restore the previous line number to stay in the context or select a
5722 * line with something that can be updated. */
5724 status_restore(struct view
*view
)
5726 if (view
->p_lineno
>= view
->lines
)
5727 view
->p_lineno
= view
->lines
- 1;
5728 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5730 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5733 /* If the above fails, always skip the "On branch" line. */
5734 if (view
->p_lineno
< view
->lines
)
5735 view
->lineno
= view
->p_lineno
;
5739 if (view
->lineno
< view
->offset
)
5740 view
->offset
= view
->lineno
;
5741 else if (view
->offset
+ view
->height
<= view
->lineno
)
5742 view
->offset
= view
->lineno
- view
->height
+ 1;
5744 view
->p_restore
= FALSE
;
5748 status_update_onbranch(void)
5750 static const char *paths
[][2] = {
5751 { "rebase-apply/rebasing", "Rebasing" },
5752 { "rebase-apply/applying", "Applying mailbox" },
5753 { "rebase-apply/", "Rebasing mailbox" },
5754 { "rebase-merge/interactive", "Interactive rebase" },
5755 { "rebase-merge/", "Rebase merge" },
5756 { "MERGE_HEAD", "Merging" },
5757 { "BISECT_LOG", "Bisecting" },
5758 { "HEAD", "On branch" },
5760 char buf
[SIZEOF_STR
];
5764 if (is_initial_commit()) {
5765 string_copy(status_onbranch
, "Initial commit");
5769 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5770 char *head
= opt_head
;
5772 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5773 lstat(buf
, &stat
) < 0)
5779 if (io_open(&io
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5780 io_read_buf(&io
, buf
, sizeof(buf
))) {
5782 if (!prefixcmp(head
, "refs/heads/"))
5783 head
+= STRING_SIZE("refs/heads/");
5787 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5788 string_copy(status_onbranch
, opt_head
);
5792 string_copy(status_onbranch
, "Not currently on any branch");
5795 /* First parse staged info using git-diff-index(1), then parse unstaged
5796 * info using git-diff-files(1), and finally untracked files using
5797 * git-ls-files(1). */
5799 status_open(struct view
*view
)
5803 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5804 status_update_onbranch();
5806 io_run_bg(update_index_argv
);
5808 if (is_initial_commit()) {
5809 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5811 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5815 if (!opt_untracked_dirs_content
)
5816 status_list_other_argv
[ARRAY_SIZE(status_list_other_argv
) - 2] = "--directory";
5818 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5819 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5822 /* Restore the exact position or use the specialized restore
5824 if (!view
->p_restore
)
5825 status_restore(view
);
5830 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5832 struct status
*status
= line
->data
;
5833 enum line_type type
;
5837 switch (line
->type
) {
5838 case LINE_STAT_STAGED
:
5839 type
= LINE_STAT_SECTION
;
5840 text
= "Changes to be committed:";
5843 case LINE_STAT_UNSTAGED
:
5844 type
= LINE_STAT_SECTION
;
5845 text
= "Changed but not updated:";
5848 case LINE_STAT_UNTRACKED
:
5849 type
= LINE_STAT_SECTION
;
5850 text
= "Untracked files:";
5853 case LINE_STAT_NONE
:
5854 type
= LINE_DEFAULT
;
5855 text
= " (no files)";
5858 case LINE_STAT_HEAD
:
5859 type
= LINE_STAT_HEAD
;
5860 text
= status_onbranch
;
5867 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5869 buf
[0] = status
->status
;
5870 if (draw_text(view
, line
->type
, buf
, TRUE
))
5872 type
= LINE_DEFAULT
;
5873 text
= status
->new.name
;
5876 draw_text(view
, type
, text
, TRUE
);
5881 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5883 if (displayed_views() == 2 || display
[current_view
] != view
)
5884 maximize_view(view
);
5885 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5890 status_enter(struct view
*view
, struct line
*line
)
5892 struct status
*status
= line
->data
;
5893 const char *oldpath
= status
? status
->old
.name
: NULL
;
5894 /* Diffs for unmerged entries are empty when passing the new
5895 * path, so leave it empty. */
5896 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5898 enum open_flags split
;
5899 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5901 if (line
->type
== LINE_STAT_NONE
||
5902 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5903 report("No file to diff");
5907 switch (line
->type
) {
5908 case LINE_STAT_STAGED
:
5909 if (is_initial_commit()) {
5910 const char *no_head_diff_argv
[] = {
5911 "git", "diff", "--no-color", "--patch-with-stat",
5912 "--", "/dev/null", newpath
, NULL
5915 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
))
5916 return status_load_error(view
, stage
, newpath
);
5918 const char *index_show_argv
[] = {
5919 "git", "diff-index", "--root", "--patch-with-stat",
5920 "-C", "-M", "--cached", "HEAD", "--",
5921 oldpath
, newpath
, NULL
5924 if (!prepare_update(stage
, index_show_argv
, opt_cdup
))
5925 return status_load_error(view
, stage
, newpath
);
5929 info
= "Staged changes to %s";
5931 info
= "Staged changes";
5934 case LINE_STAT_UNSTAGED
:
5936 const char *files_show_argv
[] = {
5937 "git", "diff-files", "--root", "--patch-with-stat",
5938 "-C", "-M", "--", oldpath
, newpath
, NULL
5941 if (!prepare_update(stage
, files_show_argv
, opt_cdup
))
5942 return status_load_error(view
, stage
, newpath
);
5944 info
= "Unstaged changes to %s";
5946 info
= "Unstaged changes";
5949 case LINE_STAT_UNTRACKED
:
5951 report("No file to show");
5955 if (!suffixcmp(status
->new.name
, -1, "/")) {
5956 report("Cannot display a directory");
5960 if (!prepare_update_file(stage
, newpath
))
5961 return status_load_error(view
, stage
, newpath
);
5962 info
= "Untracked file %s";
5965 case LINE_STAT_HEAD
:
5969 die("line type %d not handled in switch", line
->type
);
5972 split
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
5973 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5974 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5976 stage_status
= *status
;
5978 memset(&stage_status
, 0, sizeof(stage_status
));
5981 stage_line_type
= line
->type
;
5983 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5990 status_exists(struct status
*status
, enum line_type type
)
5992 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5993 unsigned long lineno
;
5995 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5996 struct line
*line
= &view
->line
[lineno
];
5997 struct status
*pos
= line
->data
;
5999 if (line
->type
!= type
)
6001 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
6002 select_view_line(view
, lineno
);
6005 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
6006 select_view_line(view
, lineno
);
6016 status_update_prepare(struct io
*io
, enum line_type type
)
6018 const char *staged_argv
[] = {
6019 "git", "update-index", "-z", "--index-info", NULL
6021 const char *others_argv
[] = {
6022 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6026 case LINE_STAT_STAGED
:
6027 return io_run(io
, IO_WR
, opt_cdup
, staged_argv
);
6029 case LINE_STAT_UNSTAGED
:
6030 case LINE_STAT_UNTRACKED
:
6031 return io_run(io
, IO_WR
, opt_cdup
, others_argv
);
6034 die("line type %d not handled in switch", type
);
6040 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
6042 char buf
[SIZEOF_STR
];
6046 case LINE_STAT_STAGED
:
6047 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
6050 status
->old
.name
, 0))
6054 case LINE_STAT_UNSTAGED
:
6055 case LINE_STAT_UNTRACKED
:
6056 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
6061 die("line type %d not handled in switch", type
);
6064 return io_write(io
, buf
, bufsize
);
6068 status_update_file(struct status
*status
, enum line_type type
)
6073 if (!status_update_prepare(&io
, type
))
6076 result
= status_update_write(&io
, status
, type
);
6077 return io_done(&io
) && result
;
6081 status_update_files(struct view
*view
, struct line
*line
)
6083 char buf
[sizeof(view
->ref
)];
6086 struct line
*pos
= view
->line
+ view
->lines
;
6089 int cursor_y
= -1, cursor_x
= -1;
6091 if (!status_update_prepare(&io
, line
->type
))
6094 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
6097 string_copy(buf
, view
->ref
);
6098 getsyx(cursor_y
, cursor_x
);
6099 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
6100 int almost_done
= file
* 100 / files
;
6102 if (almost_done
> done
) {
6104 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
6106 update_view_title(view
);
6107 setsyx(cursor_y
, cursor_x
);
6110 result
= status_update_write(&io
, line
->data
, line
->type
);
6112 string_copy(view
->ref
, buf
);
6114 return io_done(&io
) && result
;
6118 status_update(struct view
*view
)
6120 struct line
*line
= &view
->line
[view
->lineno
];
6122 assert(view
->lines
);
6125 /* This should work even for the "On branch" line. */
6126 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
6127 report("Nothing to update");
6131 if (!status_update_files(view
, line
+ 1)) {
6132 report("Failed to update file status");
6136 } else if (!status_update_file(line
->data
, line
->type
)) {
6137 report("Failed to update file status");
6145 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
6147 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
6148 if (type
== LINE_STAT_STAGED
) {
6149 report("Cannot revert changes to staged files");
6150 } else if (type
== LINE_STAT_UNTRACKED
) {
6151 report("Cannot revert changes to untracked files");
6152 } else if (has_none
) {
6153 report("Nothing to revert");
6155 report("Cannot revert changes to multiple files");
6158 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6159 char mode
[10] = "100644";
6160 const char *reset_argv
[] = {
6161 "git", "update-index", "--cacheinfo", mode
,
6162 status
->old
.rev
, status
->old
.name
, NULL
6164 const char *checkout_argv
[] = {
6165 "git", "checkout", "--", status
->old
.name
, NULL
6168 if (status
->status
== 'U') {
6169 string_format(mode
, "%5o", status
->old
.mode
);
6171 if (status
->old
.mode
== 0 && status
->new.mode
== 0) {
6172 reset_argv
[2] = "--force-remove";
6173 reset_argv
[3] = status
->old
.name
;
6174 reset_argv
[4] = NULL
;
6177 if (!io_run_fg(reset_argv
, opt_cdup
))
6179 if (status
->old
.mode
== 0 && status
->new.mode
== 0)
6183 return io_run_fg(checkout_argv
, opt_cdup
);
6190 status_request(struct view
*view
, enum request request
, struct line
*line
)
6192 struct status
*status
= line
->data
;
6195 case REQ_STATUS_UPDATE
:
6196 if (!status_update(view
))
6200 case REQ_STATUS_REVERT
:
6201 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
6205 case REQ_STATUS_MERGE
:
6206 if (!status
|| status
->status
!= 'U') {
6207 report("Merging only possible for files with unmerged status ('U').");
6210 open_mergetool(status
->new.name
);
6216 if (status
->status
== 'D') {
6217 report("File has been deleted.");
6221 open_editor(status
->new.name
);
6224 case REQ_VIEW_BLAME
:
6230 /* After returning the status view has been split to
6231 * show the stage view. No further reloading is
6233 return status_enter(view
, line
);
6236 /* Simply reload the view. */
6243 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
6249 status_select(struct view
*view
, struct line
*line
)
6251 struct status
*status
= line
->data
;
6252 char file
[SIZEOF_STR
] = "all files";
6256 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
6259 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
6262 switch (line
->type
) {
6263 case LINE_STAT_STAGED
:
6264 text
= "Press %s to unstage %s for commit";
6267 case LINE_STAT_UNSTAGED
:
6268 text
= "Press %s to stage %s for commit";
6271 case LINE_STAT_UNTRACKED
:
6272 text
= "Press %s to stage %s for addition";
6275 case LINE_STAT_HEAD
:
6276 case LINE_STAT_NONE
:
6277 text
= "Nothing to update";
6281 die("line type %d not handled in switch", line
->type
);
6284 if (status
&& status
->status
== 'U') {
6285 text
= "Press %s to resolve conflict in %s";
6286 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
6289 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
6292 string_format(view
->ref
, text
, key
, file
);
6294 string_copy(opt_file
, status
->new.name
);
6298 status_grep(struct view
*view
, struct line
*line
)
6300 struct status
*status
= line
->data
;
6303 const char buf
[2] = { status
->status
, 0 };
6304 const char *text
[] = { status
->new.name
, buf
, NULL
};
6306 return grep_text(view
, text
);
6312 static struct view_ops status_ops
= {
6325 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
6327 while (line
< end
) {
6328 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
6329 !io_write(io
, "\n", 1))
6332 if (line
->type
== LINE_DIFF_CHUNK
||
6333 line
->type
== LINE_DIFF_HEADER
)
6340 static struct line
*
6341 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
6343 for (; view
->line
< line
; line
--)
6344 if (line
->type
== type
)
6351 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
6353 const char *apply_argv
[SIZEOF_ARG
] = {
6354 "git", "apply", "--whitespace=nowarn", NULL
6356 struct line
*diff_hdr
;
6360 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6365 apply_argv
[argc
++] = "--cached";
6366 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6367 apply_argv
[argc
++] = "-R";
6368 apply_argv
[argc
++] = "-";
6369 apply_argv
[argc
++] = NULL
;
6370 if (!io_run(&io
, IO_WR
, opt_cdup
, apply_argv
))
6373 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6374 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6378 io_run_bg(update_index_argv
);
6380 return chunk
? TRUE
: FALSE
;
6384 stage_update(struct view
*view
, struct line
*line
)
6386 struct line
*chunk
= NULL
;
6388 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6389 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6392 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6393 report("Failed to apply chunk");
6397 } else if (!stage_status
.status
) {
6398 view
= VIEW(REQ_VIEW_STATUS
);
6400 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6401 if (line
->type
== stage_line_type
)
6404 if (!status_update_files(view
, line
+ 1)) {
6405 report("Failed to update files");
6409 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6410 report("Failed to update file");
6418 stage_revert(struct view
*view
, struct line
*line
)
6420 struct line
*chunk
= NULL
;
6422 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6423 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6426 if (!prompt_yesno("Are you sure you want to revert changes?"))
6429 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6430 report("Failed to revert chunk");
6436 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6437 stage_line_type
, FALSE
);
6443 stage_next(struct view
*view
, struct line
*line
)
6447 if (!stage_chunks
) {
6448 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6449 if (line
->type
!= LINE_DIFF_CHUNK
)
6452 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6453 report("Allocation failure");
6457 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6461 for (i
= 0; i
< stage_chunks
; i
++) {
6462 if (stage_chunk
[i
] > view
->lineno
) {
6463 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6464 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6469 report("No next chunk found");
6473 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6476 case REQ_STATUS_UPDATE
:
6477 if (!stage_update(view
, line
))
6481 case REQ_STATUS_REVERT
:
6482 if (!stage_revert(view
, line
))
6486 case REQ_STAGE_NEXT
:
6487 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6488 report("File is untracked; press %s to add",
6489 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6492 stage_next(view
, line
);
6496 if (!stage_status
.new.name
[0])
6498 if (stage_status
.status
== 'D') {
6499 report("File has been deleted.");
6503 open_editor(stage_status
.new.name
);
6507 /* Reload everything ... */
6510 case REQ_VIEW_BLAME
:
6511 if (stage_status
.new.name
[0]) {
6512 string_copy(opt_file
, stage_status
.new.name
);
6518 return pager_request(view
, request
, line
);
6524 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6525 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6527 /* Check whether the staged entry still exists, and close the
6528 * stage view if it doesn't. */
6529 if (!status_exists(&stage_status
, stage_line_type
)) {
6530 status_restore(VIEW(REQ_VIEW_STATUS
));
6531 return REQ_VIEW_CLOSE
;
6534 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6535 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6536 report("Cannot display a directory");
6540 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6541 report("Failed to open file: %s", strerror(errno
));
6545 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6550 static struct view_ops stage_ops
= {
6567 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6568 char title
[128]; /* First line of the commit message. */
6569 const char *author
; /* Author of the commit. */
6570 struct time time
; /* Date from the author ident. */
6571 struct ref_list
*refs
; /* Repository references. */
6572 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6573 size_t graph_size
; /* The width of the graph array. */
6574 bool has_parents
; /* Rewritten --parents seen. */
6577 /* Size of rev graph with no "padding" columns */
6578 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6581 struct rev_graph
*prev
, *next
, *parents
;
6582 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6584 struct commit
*commit
;
6586 unsigned int boundary
:1;
6589 /* Parents of the commit being visualized. */
6590 static struct rev_graph graph_parents
[4];
6592 /* The current stack of revisions on the graph. */
6593 static struct rev_graph graph_stacks
[4] = {
6594 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6595 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6596 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6597 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6601 graph_parent_is_merge(struct rev_graph
*graph
)
6603 return graph
->parents
->size
> 1;
6607 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6609 struct commit
*commit
= graph
->commit
;
6611 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6612 commit
->graph
[commit
->graph_size
++] = symbol
;
6616 clear_rev_graph(struct rev_graph
*graph
)
6618 graph
->boundary
= 0;
6619 graph
->size
= graph
->pos
= 0;
6620 graph
->commit
= NULL
;
6621 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6625 done_rev_graph(struct rev_graph
*graph
)
6627 if (graph_parent_is_merge(graph
) &&
6628 graph
->pos
< graph
->size
- 1 &&
6629 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6630 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6632 graph
->commit
->graph_size
= i
* 2;
6633 while (i
< graph
->next
->size
- 1) {
6634 append_to_rev_graph(graph
, ' ');
6635 append_to_rev_graph(graph
, '\\');
6640 clear_rev_graph(graph
);
6644 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6648 /* "Collapse" duplicate parents lines.
6650 * FIXME: This needs to also update update the drawn graph but
6651 * for now it just serves as a method for pruning graph lines. */
6652 for (i
= 0; i
< graph
->size
; i
++)
6653 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6656 if (graph
->size
< SIZEOF_REVITEMS
) {
6657 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6662 get_rev_graph_symbol(struct rev_graph
*graph
)
6666 if (graph
->boundary
)
6667 symbol
= REVGRAPH_BOUND
;
6668 else if (graph
->parents
->size
== 0)
6669 symbol
= REVGRAPH_INIT
;
6670 else if (graph_parent_is_merge(graph
))
6671 symbol
= REVGRAPH_MERGE
;
6672 else if (graph
->pos
>= graph
->size
)
6673 symbol
= REVGRAPH_BRANCH
;
6675 symbol
= REVGRAPH_COMMIT
;
6681 draw_rev_graph(struct rev_graph
*graph
)
6684 chtype separator
, line
;
6686 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6687 static struct rev_filler fillers
[] = {
6693 chtype symbol
= get_rev_graph_symbol(graph
);
6694 struct rev_filler
*filler
;
6697 fillers
[DEFAULT
].line
= opt_line_graphics
? ACS_VLINE
: '|';
6698 filler
= &fillers
[DEFAULT
];
6700 for (i
= 0; i
< graph
->pos
; i
++) {
6701 append_to_rev_graph(graph
, filler
->line
);
6702 if (graph_parent_is_merge(graph
->prev
) &&
6703 graph
->prev
->pos
== i
)
6704 filler
= &fillers
[RSHARP
];
6706 append_to_rev_graph(graph
, filler
->separator
);
6709 /* Place the symbol for this revision. */
6710 append_to_rev_graph(graph
, symbol
);
6712 if (graph
->prev
->size
> graph
->size
)
6713 filler
= &fillers
[RDIAG
];
6715 filler
= &fillers
[DEFAULT
];
6719 for (; i
< graph
->size
; i
++) {
6720 append_to_rev_graph(graph
, filler
->separator
);
6721 append_to_rev_graph(graph
, filler
->line
);
6722 if (graph_parent_is_merge(graph
->prev
) &&
6723 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6724 filler
= &fillers
[RSHARP
];
6725 if (graph
->prev
->size
> graph
->size
)
6726 filler
= &fillers
[LDIAG
];
6729 if (graph
->prev
->size
> graph
->size
) {
6730 append_to_rev_graph(graph
, filler
->separator
);
6731 if (filler
->line
!= ' ')
6732 append_to_rev_graph(graph
, filler
->line
);
6736 /* Prepare the next rev graph */
6738 prepare_rev_graph(struct rev_graph
*graph
)
6742 /* First, traverse all lines of revisions up to the active one. */
6743 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6744 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6747 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6750 /* Interleave the new revision parent(s). */
6751 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6752 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6754 /* Lastly, put any remaining revisions. */
6755 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6756 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6760 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6762 /* If this is the finalizing update ... */
6764 prepare_rev_graph(graph
);
6766 /* Graph visualization needs a one rev look-ahead,
6767 * so the first update doesn't visualize anything. */
6768 if (!graph
->prev
->commit
)
6771 if (view
->lines
> 2)
6772 view
->line
[view
->lines
- 3].dirty
= 1;
6773 if (view
->lines
> 1)
6774 view
->line
[view
->lines
- 2].dirty
= 1;
6775 draw_rev_graph(graph
->prev
);
6776 done_rev_graph(graph
->prev
->prev
);
6784 static const char *main_argv
[SIZEOF_ARG
] = {
6785 "git", "log", "--no-color", "--pretty=raw", "--parents",
6786 "--topo-order", "%(diffargs)", "%(revargs)",
6787 "--", "%(fileargs)", NULL
6791 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6793 struct commit
*commit
= line
->data
;
6795 if (!commit
->author
)
6798 if (opt_date
&& draw_date(view
, &commit
->time
))
6801 if (opt_author
&& draw_author(view
, commit
->author
))
6804 if (opt_rev_graph
&& commit
->graph_size
&&
6805 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6808 if (opt_show_refs
&& commit
->refs
) {
6811 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6812 struct ref
*ref
= commit
->refs
->refs
[i
];
6813 enum line_type type
;
6816 type
= LINE_MAIN_HEAD
;
6818 type
= LINE_MAIN_LOCAL_TAG
;
6820 type
= LINE_MAIN_TAG
;
6821 else if (ref
->tracked
)
6822 type
= LINE_MAIN_TRACKED
;
6823 else if (ref
->remote
)
6824 type
= LINE_MAIN_REMOTE
;
6826 type
= LINE_MAIN_REF
;
6828 if (draw_text(view
, type
, "[", TRUE
) ||
6829 draw_text(view
, type
, ref
->name
, TRUE
) ||
6830 draw_text(view
, type
, "]", TRUE
))
6833 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6838 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6842 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6844 main_read(struct view
*view
, char *line
)
6846 static struct rev_graph
*graph
= graph_stacks
;
6847 enum line_type type
;
6848 struct commit
*commit
;
6853 if (!view
->lines
&& !view
->prev
)
6854 die("No revisions match the given arguments.");
6855 if (view
->lines
> 0) {
6856 commit
= view
->line
[view
->lines
- 1].data
;
6857 view
->line
[view
->lines
- 1].dirty
= 1;
6858 if (!commit
->author
) {
6861 graph
->commit
= NULL
;
6864 update_rev_graph(view
, graph
);
6866 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6867 clear_rev_graph(&graph_stacks
[i
]);
6871 type
= get_line_type(line
);
6872 if (type
== LINE_COMMIT
) {
6873 commit
= calloc(1, sizeof(struct commit
));
6877 line
+= STRING_SIZE("commit ");
6879 graph
->boundary
= 1;
6883 string_copy_rev(commit
->id
, line
);
6884 commit
->refs
= get_ref_list(commit
->id
);
6885 graph
->commit
= commit
;
6886 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6888 while ((line
= strchr(line
, ' '))) {
6890 push_rev_graph(graph
->parents
, line
);
6891 commit
->has_parents
= TRUE
;
6898 commit
= view
->line
[view
->lines
- 1].data
;
6902 if (commit
->has_parents
)
6904 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6908 parse_author_line(line
+ STRING_SIZE("author "),
6909 &commit
->author
, &commit
->time
);
6910 update_rev_graph(view
, graph
);
6911 graph
= graph
->next
;
6915 /* Fill in the commit title if it has not already been set. */
6916 if (commit
->title
[0])
6919 /* Require titles to start with a non-space character at the
6920 * offset used by git log. */
6921 if (strncmp(line
, " ", 4))
6924 /* Well, if the title starts with a whitespace character,
6925 * try to be forgiving. Otherwise we end up with no title. */
6926 while (isspace(*line
))
6930 /* FIXME: More graceful handling of titles; append "..." to
6931 * shortened titles, etc. */
6933 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6934 view
->line
[view
->lines
- 1].dirty
= 1;
6941 main_request(struct view
*view
, enum request request
, struct line
*line
)
6943 enum open_flags flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
6947 if (view_is_displayed(view
) && display
[0] != view
)
6948 maximize_view(view
);
6949 open_view(view
, REQ_VIEW_DIFF
, flags
);
6953 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6963 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6968 if (!opt_show_refs
|| !list
)
6971 for (i
= 0; i
< list
->size
; i
++) {
6972 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6980 main_grep(struct view
*view
, struct line
*line
)
6982 struct commit
*commit
= line
->data
;
6983 const char *text
[] = {
6985 opt_author
? commit
->author
: "",
6986 mkdate(&commit
->time
, opt_date
),
6990 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6994 main_select(struct view
*view
, struct line
*line
)
6996 struct commit
*commit
= line
->data
;
6998 string_copy_rev(view
->ref
, commit
->id
);
6999 string_copy_rev(ref_commit
, view
->ref
);
7002 static struct view_ops main_ops
= {
7018 /* Whether or not the curses interface has been initialized. */
7019 static bool cursed
= FALSE
;
7021 /* Terminal hacks and workarounds. */
7022 static bool use_scroll_redrawwin
;
7023 static bool use_scroll_status_wclear
;
7025 /* The status window is used for polling keystrokes. */
7026 static WINDOW
*status_win
;
7028 /* Reading from the prompt? */
7029 static bool input_mode
= FALSE
;
7031 static bool status_empty
= FALSE
;
7033 /* Update status and title window. */
7035 report(const char *msg
, ...)
7037 struct view
*view
= display
[current_view
];
7043 char buf
[SIZEOF_STR
];
7046 va_start(args
, msg
);
7047 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
7048 buf
[sizeof(buf
) - 1] = 0;
7049 buf
[sizeof(buf
) - 2] = '.';
7050 buf
[sizeof(buf
) - 3] = '.';
7051 buf
[sizeof(buf
) - 4] = '.';
7057 if (!status_empty
|| *msg
) {
7060 va_start(args
, msg
);
7062 wmove(status_win
, 0, 0);
7063 if (view
->has_scrolled
&& use_scroll_status_wclear
)
7066 vwprintw(status_win
, msg
, args
);
7067 status_empty
= FALSE
;
7069 status_empty
= TRUE
;
7071 wclrtoeol(status_win
);
7072 wnoutrefresh(status_win
);
7077 update_view_title(view
);
7086 /* Initialize the curses library */
7087 if (isatty(STDIN_FILENO
)) {
7088 cursed
= !!initscr();
7091 /* Leave stdin and stdout alone when acting as a pager. */
7092 opt_tty
= fopen("/dev/tty", "r+");
7094 die("Failed to open /dev/tty");
7095 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
7099 die("Failed to initialize curses");
7101 nonl(); /* Disable conversion and detect newlines from input. */
7102 cbreak(); /* Take input chars one at a time, no wait for \n */
7103 noecho(); /* Don't echo input */
7104 leaveok(stdscr
, FALSE
);
7109 getmaxyx(stdscr
, y
, x
);
7110 status_win
= newwin(1, 0, y
- 1, 0);
7112 die("Failed to create status window");
7114 /* Enable keyboard mapping */
7115 keypad(status_win
, TRUE
);
7116 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
7118 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
7119 set_tabsize(opt_tab_size
);
7121 TABSIZE
= opt_tab_size
;
7124 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
7125 if (term
&& !strcmp(term
, "gnome-terminal")) {
7126 /* In the gnome-terminal-emulator, the message from
7127 * scrolling up one line when impossible followed by
7128 * scrolling down one line causes corruption of the
7129 * status line. This is fixed by calling wclear. */
7130 use_scroll_status_wclear
= TRUE
;
7131 use_scroll_redrawwin
= FALSE
;
7133 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
7134 /* No problems with full optimizations in xrvt-(unicode)
7136 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
7139 /* When scrolling in (u)xterm the last line in the
7140 * scrolling direction will update slowly. */
7141 use_scroll_redrawwin
= TRUE
;
7142 use_scroll_status_wclear
= FALSE
;
7147 get_input(int prompt_position
)
7150 int i
, key
, cursor_y
, cursor_x
;
7152 if (prompt_position
)
7156 bool loading
= FALSE
;
7158 foreach_view (view
, i
) {
7160 if (view_is_displayed(view
) && view
->has_scrolled
&&
7161 use_scroll_redrawwin
)
7162 redrawwin(view
->win
);
7163 view
->has_scrolled
= FALSE
;
7168 /* Update the cursor position. */
7169 if (prompt_position
) {
7170 getbegyx(status_win
, cursor_y
, cursor_x
);
7171 cursor_x
= prompt_position
;
7173 view
= display
[current_view
];
7174 getbegyx(view
->win
, cursor_y
, cursor_x
);
7175 cursor_x
= view
->width
- 1;
7176 cursor_y
+= view
->lineno
- view
->offset
;
7178 setsyx(cursor_y
, cursor_x
);
7180 /* Refresh, accept single keystroke of input */
7182 nodelay(status_win
, loading
);
7183 key
= wgetch(status_win
);
7185 /* wgetch() with nodelay() enabled returns ERR when
7186 * there's no input. */
7189 } else if (key
== KEY_RESIZE
) {
7192 getmaxyx(stdscr
, height
, width
);
7194 wresize(status_win
, 1, width
);
7195 mvwin(status_win
, height
- 1, 0);
7196 wnoutrefresh(status_win
);
7198 redraw_display(TRUE
);
7208 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7210 enum input_status status
= INPUT_OK
;
7211 static char buf
[SIZEOF_STR
];
7216 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7219 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7220 wclrtoeol(status_win
);
7222 key
= get_input(pos
+ 1);
7227 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7234 status
= INPUT_CANCEL
;
7238 status
= INPUT_CANCEL
;
7242 if (pos
>= sizeof(buf
)) {
7243 report("Input string too long");
7247 status
= handler(data
, buf
, key
);
7248 if (status
== INPUT_OK
)
7249 buf
[pos
++] = (char) key
;
7253 /* Clear the status window */
7254 status_empty
= FALSE
;
7257 if (status
== INPUT_CANCEL
)
7265 static enum input_status
7266 prompt_yesno_handler(void *data
, char *buf
, int c
)
7268 if (c
== 'y' || c
== 'Y')
7270 if (c
== 'n' || c
== 'N')
7271 return INPUT_CANCEL
;
7276 prompt_yesno(const char *prompt
)
7278 char prompt2
[SIZEOF_STR
];
7280 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7283 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7286 static enum input_status
7287 read_prompt_handler(void *data
, char *buf
, int c
)
7289 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7293 read_prompt(const char *prompt
)
7295 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7298 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7300 enum input_status status
= INPUT_OK
;
7303 while (items
[size
].text
)
7306 while (status
== INPUT_OK
) {
7307 const struct menu_item
*item
= &items
[*selected
];
7311 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7312 prompt
, *selected
+ 1, size
);
7314 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7315 wprintw(status_win
, "%s", item
->text
);
7316 wclrtoeol(status_win
);
7318 key
= get_input(COLS
- 1);
7323 status
= INPUT_STOP
;
7328 *selected
= *selected
- 1;
7330 *selected
= size
- 1;
7335 *selected
= (*selected
+ 1) % size
;
7339 status
= INPUT_CANCEL
;
7343 for (i
= 0; items
[i
].text
; i
++)
7344 if (items
[i
].hotkey
== key
) {
7346 status
= INPUT_STOP
;
7352 /* Clear the status window */
7353 status_empty
= FALSE
;
7356 return status
!= INPUT_CANCEL
;
7360 * Repository properties
7363 static struct ref
**refs
= NULL
;
7364 static size_t refs_size
= 0;
7365 static struct ref
*refs_head
= NULL
;
7367 static struct ref_list
**ref_lists
= NULL
;
7368 static size_t ref_lists_size
= 0;
7370 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7371 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7372 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7375 compare_refs(const void *ref1_
, const void *ref2_
)
7377 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7378 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7380 if (ref1
->tag
!= ref2
->tag
)
7381 return ref2
->tag
- ref1
->tag
;
7382 if (ref1
->ltag
!= ref2
->ltag
)
7383 return ref2
->ltag
- ref2
->ltag
;
7384 if (ref1
->head
!= ref2
->head
)
7385 return ref2
->head
- ref1
->head
;
7386 if (ref1
->tracked
!= ref2
->tracked
)
7387 return ref2
->tracked
- ref1
->tracked
;
7388 if (ref1
->remote
!= ref2
->remote
)
7389 return ref2
->remote
- ref1
->remote
;
7390 return strcmp(ref1
->name
, ref2
->name
);
7394 foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
)
7398 for (i
= 0; i
< refs_size
; i
++)
7399 if (!visitor(data
, refs
[i
]))
7409 static struct ref_list
*
7410 get_ref_list(const char *id
)
7412 struct ref_list
*list
;
7415 for (i
= 0; i
< ref_lists_size
; i
++)
7416 if (!strcmp(id
, ref_lists
[i
]->id
))
7417 return ref_lists
[i
];
7419 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7421 list
= calloc(1, sizeof(*list
));
7425 for (i
= 0; i
< refs_size
; i
++) {
7426 if (!strcmp(id
, refs
[i
]->id
) &&
7427 realloc_refs_list(&list
->refs
, list
->size
, 1))
7428 list
->refs
[list
->size
++] = refs
[i
];
7436 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7437 ref_lists
[ref_lists_size
++] = list
;
7442 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
, void *data
)
7444 struct ref
*ref
= NULL
;
7447 bool remote
= FALSE
;
7448 bool tracked
= FALSE
;
7450 int from
= 0, to
= refs_size
- 1;
7452 if (!prefixcmp(name
, "refs/tags/")) {
7453 if (!suffixcmp(name
, namelen
, "^{}")) {
7461 namelen
-= STRING_SIZE("refs/tags/");
7462 name
+= STRING_SIZE("refs/tags/");
7464 } else if (!prefixcmp(name
, "refs/remotes/")) {
7466 namelen
-= STRING_SIZE("refs/remotes/");
7467 name
+= STRING_SIZE("refs/remotes/");
7468 tracked
= !strcmp(opt_remote
, name
);
7470 } else if (!prefixcmp(name
, "refs/heads/")) {
7471 namelen
-= STRING_SIZE("refs/heads/");
7472 name
+= STRING_SIZE("refs/heads/");
7473 if (!strncmp(opt_head
, name
, namelen
))
7476 } else if (!strcmp(name
, "HEAD")) {
7479 namelen
= strlen(opt_head
);
7484 /* If we are reloading or it's an annotated tag, replace the
7485 * previous SHA1 with the resolved commit id; relies on the fact
7486 * git-ls-remote lists the commit id of an annotated tag right
7487 * before the commit id it points to. */
7488 while (from
<= to
) {
7489 size_t pos
= (to
+ from
) / 2;
7490 int cmp
= strcmp(name
, refs
[pos
]->name
);
7504 if (!realloc_refs(&refs
, refs_size
, 1))
7506 ref
= calloc(1, sizeof(*ref
) + namelen
);
7509 memmove(refs
+ from
+ 1, refs
+ from
,
7510 (refs_size
- from
) * sizeof(*refs
));
7512 strncpy(ref
->name
, name
, namelen
);
7519 ref
->remote
= remote
;
7520 ref
->tracked
= tracked
;
7521 string_copy_rev(ref
->id
, id
);
7531 const char *head_argv
[] = {
7532 "git", "symbolic-ref", "HEAD", NULL
7534 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7535 "git", "ls-remote", opt_git_dir
, NULL
7537 static bool init
= FALSE
;
7541 if (!argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE"))
7542 die("TIG_LS_REMOTE contains too many arguments");
7549 if (io_run_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7550 !prefixcmp(opt_head
, "refs/heads/")) {
7551 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7553 memmove(opt_head
, offset
, strlen(offset
) + 1);
7557 for (i
= 0; i
< refs_size
; i
++)
7560 if (io_run_load(ls_remote_argv
, "\t", read_ref
, NULL
) == ERR
)
7563 /* Update the ref lists to reflect changes. */
7564 for (i
= 0; i
< ref_lists_size
; i
++) {
7565 struct ref_list
*list
= ref_lists
[i
];
7568 for (old
= new = 0; old
< list
->size
; old
++)
7569 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7570 list
->refs
[new++] = list
->refs
[old
];
7578 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7580 if (!strcmp(name
, ".remote")) {
7581 string_ncopy(opt_remote
, value
, valuelen
);
7583 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7584 size_t from
= strlen(opt_remote
);
7586 if (!prefixcmp(value
, "refs/heads/"))
7587 value
+= STRING_SIZE("refs/heads/");
7589 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7595 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7597 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7598 int argc
= 1 + (cmd
== option_set_command
);
7601 if (!argv_from_string(argv
, &argc
, value
))
7602 config_msg
= "Too many option arguments";
7604 error
= cmd(argc
, argv
);
7607 warn("Option 'tig.%s': %s", name
, config_msg
);
7611 set_environment_variable(const char *name
, const char *value
)
7613 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7614 char *env
= malloc(len
);
7617 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7625 set_work_tree(const char *value
)
7627 char cwd
[SIZEOF_STR
];
7629 if (!getcwd(cwd
, sizeof(cwd
)))
7630 die("Failed to get cwd path: %s", strerror(errno
));
7631 if (chdir(opt_git_dir
) < 0)
7632 die("Failed to chdir(%s): %s", strerror(errno
));
7633 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7634 die("Failed to get git path: %s", strerror(errno
));
7636 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7637 if (chdir(value
) < 0)
7638 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7639 if (!getcwd(cwd
, sizeof(cwd
)))
7640 die("Failed to get cwd path: %s", strerror(errno
));
7641 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7642 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7643 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7644 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7645 opt_is_inside_work_tree
= TRUE
;
7649 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
, void *data
)
7651 if (!strcmp(name
, "i18n.commitencoding"))
7652 string_ncopy(opt_encoding
, value
, valuelen
);
7654 else if (!strcmp(name
, "core.editor"))
7655 string_ncopy(opt_editor
, value
, valuelen
);
7657 else if (!strcmp(name
, "core.worktree"))
7658 set_work_tree(value
);
7660 else if (!prefixcmp(name
, "tig.color."))
7661 set_repo_config_option(name
+ 10, value
, option_color_command
);
7663 else if (!prefixcmp(name
, "tig.bind."))
7664 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7666 else if (!prefixcmp(name
, "tig."))
7667 set_repo_config_option(name
+ 4, value
, option_set_command
);
7669 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7670 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7671 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7677 load_git_config(void)
7679 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7681 return io_run_load(config_list_argv
, "=", read_repo_config_option
, NULL
);
7685 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
, void *data
)
7687 if (!opt_git_dir
[0]) {
7688 string_ncopy(opt_git_dir
, name
, namelen
);
7690 } else if (opt_is_inside_work_tree
== -1) {
7691 /* This can be 3 different values depending on the
7692 * version of git being used. If git-rev-parse does not
7693 * understand --is-inside-work-tree it will simply echo
7694 * the option else either "true" or "false" is printed.
7695 * Default to true for the unknown case. */
7696 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7698 } else if (*name
== '.') {
7699 string_ncopy(opt_cdup
, name
, namelen
);
7702 string_ncopy(opt_prefix
, name
, namelen
);
7709 load_repo_info(void)
7711 const char *rev_parse_argv
[] = {
7712 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7713 "--show-cdup", "--show-prefix", NULL
7716 return io_run_load(rev_parse_argv
, "=", read_repo_info
, NULL
);
7724 static const char usage
[] =
7725 "tig " TIG_VERSION
" (" __DATE__
")\n"
7727 "Usage: tig [options] [revs] [--] [paths]\n"
7728 " or: tig show [options] [revs] [--] [paths]\n"
7729 " or: tig blame [rev] path\n"
7731 " or: tig < [git command output]\n"
7734 " -v, --version Show version and exit\n"
7735 " -h, --help Show help message and exit";
7737 static void __NORETURN
7740 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7746 static void __NORETURN
7747 die(const char *err
, ...)
7753 va_start(args
, err
);
7754 fputs("tig: ", stderr
);
7755 vfprintf(stderr
, err
, args
);
7756 fputs("\n", stderr
);
7763 warn(const char *msg
, ...)
7767 va_start(args
, msg
);
7768 fputs("tig warning: ", stderr
);
7769 vfprintf(stderr
, msg
, args
);
7770 fputs("\n", stderr
);
7775 read_filter_args(char *name
, size_t namelen
, char *value
, size_t valuelen
, void *data
)
7777 const char ***filter_args
= data
;
7779 return argv_append(filter_args
, name
) ? OK
: ERR
;
7783 filter_rev_parse(const char ***args
, const char *arg1
, const char *arg2
, const char *argv
[])
7785 const char *rev_parse_argv
[SIZEOF_ARG
] = { "git", "rev-parse", arg1
, arg2
};
7786 const char **all_argv
= NULL
;
7788 if (!argv_append_array(&all_argv
, rev_parse_argv
) ||
7789 !argv_append_array(&all_argv
, argv
) ||
7790 !io_run_load(all_argv
, "\n", read_filter_args
, args
) == ERR
)
7791 die("Failed to split arguments");
7792 argv_free(all_argv
);
7797 filter_options(const char *argv
[])
7799 filter_rev_parse(&opt_file_argv
, "--no-revs", "--no-flags", argv
);
7800 filter_rev_parse(&opt_diff_argv
, "--no-revs", "--flags", argv
);
7801 filter_rev_parse(&opt_rev_argv
, "--symbolic", "--revs-only", argv
);
7805 parse_options(int argc
, const char *argv
[])
7807 enum request request
= REQ_VIEW_MAIN
;
7808 const char *subcommand
;
7809 bool seen_dashdash
= FALSE
;
7810 const char **filter_argv
= NULL
;
7813 if (!isatty(STDIN_FILENO
))
7814 return REQ_VIEW_PAGER
;
7817 return REQ_VIEW_MAIN
;
7819 subcommand
= argv
[1];
7820 if (!strcmp(subcommand
, "status")) {
7822 warn("ignoring arguments after `%s'", subcommand
);
7823 return REQ_VIEW_STATUS
;
7825 } else if (!strcmp(subcommand
, "blame")) {
7826 if (argc
<= 2 || argc
> 4)
7827 die("invalid number of options to blame\n\n%s", usage
);
7831 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7835 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7836 return REQ_VIEW_BLAME
;
7838 } else if (!strcmp(subcommand
, "show")) {
7839 request
= REQ_VIEW_DIFF
;
7845 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7846 const char *opt
= argv
[i
];
7848 if (seen_dashdash
) {
7849 argv_append(&opt_file_argv
, opt
);
7852 } else if (!strcmp(opt
, "--")) {
7853 seen_dashdash
= TRUE
;
7856 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7857 printf("tig version %s\n", TIG_VERSION
);
7860 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7861 printf("%s\n", usage
);
7864 } else if (!strcmp(opt
, "--all")) {
7865 argv_append(&opt_rev_argv
, opt
);
7869 if (!argv_append(&filter_argv
, opt
))
7870 die("command too long");
7874 filter_options(filter_argv
);
7880 main(int argc
, const char *argv
[])
7882 const char *codeset
= "UTF-8";
7883 enum request request
= parse_options(argc
, argv
);
7887 signal(SIGINT
, quit
);
7888 signal(SIGPIPE
, SIG_IGN
);
7890 if (setlocale(LC_ALL
, "")) {
7891 codeset
= nl_langinfo(CODESET
);
7894 if (load_repo_info() == ERR
)
7895 die("Failed to load repo info.");
7897 if (load_options() == ERR
)
7898 die("Failed to load user config.");
7900 if (load_git_config() == ERR
)
7901 die("Failed to load repo config.");
7903 /* Require a git repository unless when running in pager mode. */
7904 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7905 die("Not a git repository");
7907 if (*opt_encoding
&& strcmp(codeset
, "UTF-8")) {
7908 opt_iconv_in
= iconv_open("UTF-8", opt_encoding
);
7909 if (opt_iconv_in
== ICONV_NONE
)
7910 die("Failed to initialize character set conversion");
7913 if (codeset
&& strcmp(codeset
, "UTF-8")) {
7914 opt_iconv_out
= iconv_open(codeset
, "UTF-8");
7915 if (opt_iconv_out
== ICONV_NONE
)
7916 die("Failed to initialize character set conversion");
7919 if (load_refs() == ERR
)
7920 die("Failed to load refs.");
7922 foreach_view (view
, i
) {
7923 if (getenv(view
->cmd_env
))
7924 warn("Use of the %s environment variable is deprecated,"
7925 " use options or TIG_DIFF_ARGS instead",
7927 if (!argv_from_env(view
->ops
->argv
, view
->cmd_env
))
7928 die("Too many arguments in the `%s` environment variable",
7934 while (view_driver(display
[current_view
], request
)) {
7935 int key
= get_input(0);
7937 view
= display
[current_view
];
7938 request
= get_keybinding(view
->keymap
, key
);
7940 /* Some low-level request handling. This keeps access to
7941 * status_win restricted. */
7944 report("Unknown key, press %s for help",
7945 get_key(view
->keymap
, REQ_VIEW_HELP
));
7949 char *cmd
= read_prompt(":");
7951 if (cmd
&& isdigit(*cmd
)) {
7952 int lineno
= view
->lineno
+ 1;
7954 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7955 select_view_line(view
, lineno
- 1);
7958 report("Unable to parse '%s' as a line number", cmd
);
7962 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7963 const char *argv
[SIZEOF_ARG
] = { "git" };
7966 /* When running random commands, initially show the
7967 * command in the title. However, it maybe later be
7968 * overwritten if a commit line is selected. */
7969 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7971 if (!argv_from_string(argv
, &argc
, cmd
)) {
7972 report("Too many arguments");
7973 } else if (!prepare_update(next
, argv
, NULL
)) {
7974 report("Failed to format command");
7976 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7984 case REQ_SEARCH_BACK
:
7986 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7987 char *search
= read_prompt(prompt
);
7990 string_ncopy(opt_search
, search
, strlen(search
));
7991 else if (*opt_search
)
7992 request
= request
== REQ_SEARCH
?