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. */
118 #define KEY_RETURN '\r'
123 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
124 unsigned int head
:1; /* Is it the current HEAD? */
125 unsigned int tag
:1; /* Is it a tag? */
126 unsigned int ltag
:1; /* If so, is the tag local? */
127 unsigned int remote
:1; /* Is it a remote ref? */
128 unsigned int tracked
:1; /* Is it the remote for the current HEAD? */
129 char name
[1]; /* Ref name; tag or head names are shortened. */
133 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
134 size_t size
; /* Number of refs. */
135 struct ref
**refs
; /* References for this ID. */
138 static struct ref
*get_ref_head();
139 static struct ref_list
*get_ref_list(const char *id
);
140 static void foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
);
141 static int load_refs(void);
150 typedef enum input_status (*input_handler
)(void *data
, char *buf
, int c
);
152 static char *prompt_input(const char *prompt
, input_handler handler
, void *data
);
153 static bool prompt_yesno(const char *prompt
);
161 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
);
164 * Allocation helpers ... Entering macro hell to never be seen again.
167 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
169 name(type **mem, size_t size, size_t increase) \
171 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
172 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
175 if (mem == NULL || num_chunks != num_chunks_new) { \
176 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
189 string_ncopy_do(char *dst
, size_t dstlen
, const char *src
, size_t srclen
)
191 if (srclen
> dstlen
- 1)
194 strncpy(dst
, src
, srclen
);
198 /* Shorthands for safely copying into a fixed buffer. */
200 #define string_copy(dst, src) \
201 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
203 #define string_ncopy(dst, src, srclen) \
204 string_ncopy_do(dst, sizeof(dst), src, srclen)
206 #define string_copy_rev(dst, src) \
207 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
209 #define string_add(dst, from, src) \
210 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 string_expand(char *dst
, size_t dstlen
, const char *src
, int tabsize
)
217 for (size
= pos
= 0; size
< dstlen
- 1 && src
[pos
]; pos
++) {
218 if (src
[pos
] == '\t') {
219 size_t expanded
= tabsize
- (size
% tabsize
);
221 if (expanded
+ size
>= dstlen
- 1)
222 expanded
= dstlen
- size
- 1;
223 memcpy(dst
+ size
, " ", expanded
);
226 dst
[size
++] = src
[pos
];
235 chomp_string(char *name
)
239 while (isspace(*name
))
242 namelen
= strlen(name
) - 1;
243 while (namelen
> 0 && isspace(name
[namelen
]))
250 string_nformat(char *buf
, size_t bufsize
, size_t *bufpos
, const char *fmt
, ...)
253 size_t pos
= bufpos
? *bufpos
: 0;
256 pos
+= vsnprintf(buf
+ pos
, bufsize
- pos
, fmt
, args
);
262 return pos
>= bufsize
? FALSE
: TRUE
;
265 #define string_format(buf, fmt, args...) \
266 string_nformat(buf, sizeof(buf), NULL, fmt, args)
268 #define string_format_from(buf, from, fmt, args...) \
269 string_nformat(buf, sizeof(buf), from, fmt, args)
272 string_enum_compare(const char *str1
, const char *str2
, int len
)
276 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
278 /* Diff-Header == DIFF_HEADER */
279 for (i
= 0; i
< len
; i
++) {
280 if (toupper(str1
[i
]) == toupper(str2
[i
]))
283 if (string_enum_sep(str1
[i
]) &&
284 string_enum_sep(str2
[i
]))
287 return str1
[i
] - str2
[i
];
293 #define enum_equals(entry, str, len) \
294 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
302 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
305 enum_map_name(const char *name
, size_t namelen
)
307 static char buf
[SIZEOF_STR
];
310 for (bufpos
= 0; bufpos
<= namelen
; bufpos
++) {
311 buf
[bufpos
] = tolower(name
[bufpos
]);
312 if (buf
[bufpos
] == '_')
320 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
323 map_enum_do(const struct enum_map
*map
, size_t map_size
, int *value
, const char *name
)
325 size_t namelen
= strlen(name
);
328 for (i
= 0; i
< map_size
; i
++)
329 if (enum_equals(map
[i
], name
, namelen
)) {
330 *value
= map
[i
].value
;
337 #define map_enum(attr, map, name) \
338 map_enum_do(map, ARRAY_SIZE(map), attr, name)
340 #define prefixcmp(str1, str2) \
341 strncmp(str1, str2, STRING_SIZE(str2))
344 suffixcmp(const char *str
, int slen
, const char *suffix
)
346 size_t len
= slen
>= 0 ? slen
: strlen(str
);
347 size_t suffixlen
= strlen(suffix
);
349 return suffixlen
< len
? strcmp(str
+ len
- suffixlen
, suffix
) : -1;
354 * Unicode / UTF-8 handling
356 * NOTE: Much of the following code for dealing with Unicode is derived from
357 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
358 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
362 unicode_width(unsigned long c
, int tab_size
)
365 (c
<= 0x115f /* Hangul Jamo */
368 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
370 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
371 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
372 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
373 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
374 || (c
>= 0xffe0 && c
<= 0xffe6)
375 || (c
>= 0x20000 && c
<= 0x2fffd)
376 || (c
>= 0x30000 && c
<= 0x3fffd)))
385 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
386 * Illegal bytes are set one. */
387 static const unsigned char utf8_bytes
[256] = {
388 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,
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 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,
395 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,
398 static inline unsigned char
399 utf8_char_length(const char *string
, const char *end
)
401 int c
= *(unsigned char *) string
;
403 return utf8_bytes
[c
];
406 /* Decode UTF-8 multi-byte representation into a Unicode character. */
407 static inline unsigned long
408 utf8_to_unicode(const char *string
, size_t length
)
410 unsigned long unicode
;
417 unicode
= (string
[0] & 0x1f) << 6;
418 unicode
+= (string
[1] & 0x3f);
421 unicode
= (string
[0] & 0x0f) << 12;
422 unicode
+= ((string
[1] & 0x3f) << 6);
423 unicode
+= (string
[2] & 0x3f);
426 unicode
= (string
[0] & 0x0f) << 18;
427 unicode
+= ((string
[1] & 0x3f) << 12);
428 unicode
+= ((string
[2] & 0x3f) << 6);
429 unicode
+= (string
[3] & 0x3f);
432 unicode
= (string
[0] & 0x0f) << 24;
433 unicode
+= ((string
[1] & 0x3f) << 18);
434 unicode
+= ((string
[2] & 0x3f) << 12);
435 unicode
+= ((string
[3] & 0x3f) << 6);
436 unicode
+= (string
[4] & 0x3f);
439 unicode
= (string
[0] & 0x01) << 30;
440 unicode
+= ((string
[1] & 0x3f) << 24);
441 unicode
+= ((string
[2] & 0x3f) << 18);
442 unicode
+= ((string
[3] & 0x3f) << 12);
443 unicode
+= ((string
[4] & 0x3f) << 6);
444 unicode
+= (string
[5] & 0x3f);
450 /* Invalid characters could return the special 0xfffd value but NUL
451 * should be just as good. */
452 return unicode
> 0xffff ? 0 : unicode
;
455 /* Calculates how much of string can be shown within the given maximum width
456 * and sets trimmed parameter to non-zero value if all of string could not be
457 * shown. If the reserve flag is TRUE, it will reserve at least one
458 * trailing character, which can be useful when drawing a delimiter.
460 * Returns the number of bytes to output from string to satisfy max_width. */
462 utf8_length(const char **start
, size_t skip
, int *width
, size_t max_width
, int *trimmed
, bool reserve
, int tab_size
)
464 const char *string
= *start
;
465 const char *end
= strchr(string
, '\0');
466 unsigned char last_bytes
= 0;
467 size_t last_ucwidth
= 0;
472 while (string
< end
) {
473 unsigned char bytes
= utf8_char_length(string
, end
);
475 unsigned long unicode
;
477 if (string
+ bytes
> end
)
480 /* Change representation to figure out whether
481 * it is a single- or double-width character. */
483 unicode
= utf8_to_unicode(string
, bytes
);
484 /* FIXME: Graceful handling of invalid Unicode character. */
488 ucwidth
= unicode_width(unicode
, tab_size
);
490 skip
-= ucwidth
<= skip
? ucwidth
: skip
;
494 if (*width
> max_width
) {
497 if (reserve
&& *width
== max_width
) {
498 string
-= last_bytes
;
499 *width
-= last_ucwidth
;
505 last_bytes
= ucwidth
? bytes
: 0;
506 last_ucwidth
= ucwidth
;
509 return string
- *start
;
521 #define DATE_(name) DATE_##name
526 static const struct enum_map date_map
[] = {
527 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
537 static inline int timecmp(const struct time
*t1
, const struct time
*t2
)
539 return t1
->sec
- t2
->sec
;
543 mkdate(const struct time
*time
, enum date date
)
545 static char buf
[DATE_COLS
+ 1];
546 static const struct enum_map reldate
[] = {
547 { "second", 1, 60 * 2 },
548 { "minute", 60, 60 * 60 * 2 },
549 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
550 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
551 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
552 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
556 if (!date
|| !time
|| !time
->sec
)
559 if (date
== DATE_RELATIVE
) {
561 time_t date
= time
->sec
+ time
->tz
;
565 gettimeofday(&now
, NULL
);
566 seconds
= now
.tv_sec
< date
? date
- now
.tv_sec
: now
.tv_sec
- date
;
567 for (i
= 0; i
< ARRAY_SIZE(reldate
); i
++) {
568 if (seconds
>= reldate
[i
].value
)
571 seconds
/= reldate
[i
].namelen
;
572 if (!string_format(buf
, "%ld %s%s %s",
573 seconds
, reldate
[i
].name
,
574 seconds
> 1 ? "s" : "",
575 now
.tv_sec
>= date
? "ago" : "ahead"))
581 if (date
== DATE_LOCAL
) {
582 time_t date
= time
->sec
+ time
->tz
;
583 localtime_r(&date
, &tm
);
586 gmtime_r(&time
->sec
, &tm
);
588 return strftime(buf
, sizeof(buf
), DATE_FORMAT
, &tm
) ? buf
: NULL
;
592 #define AUTHOR_VALUES \
598 #define AUTHOR_(name) AUTHOR_##name
601 AUTHOR_DEFAULT
= AUTHOR_FULL
604 static const struct enum_map author_map
[] = {
605 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
611 get_author_initials(const char *author
)
613 static char initials
[AUTHOR_COLS
* 6 + 1];
615 const char *end
= strchr(author
, '\0');
617 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
619 memset(initials
, 0, sizeof(initials
));
620 while (author
< end
) {
624 while (is_initial_sep(*author
))
627 bytes
= utf8_char_length(author
, end
);
628 if (bytes
< sizeof(initials
) - 1 - pos
) {
630 initials
[pos
++] = *author
++;
634 for (i
= pos
; author
< end
&& !is_initial_sep(*author
); author
++) {
635 if (i
< sizeof(initials
) - 1)
636 initials
[i
++] = *author
;
647 argv_from_string(const char *argv
[SIZEOF_ARG
], int *argc
, char *cmd
)
651 while (*cmd
&& *argc
< SIZEOF_ARG
&& (valuelen
= strcspn(cmd
, " \t"))) {
652 bool advance
= cmd
[valuelen
] != 0;
655 argv
[(*argc
)++] = chomp_string(cmd
);
656 cmd
= chomp_string(cmd
+ valuelen
+ advance
);
659 if (*argc
< SIZEOF_ARG
)
661 return *argc
< SIZEOF_ARG
;
665 argv_from_env(const char **argv
, const char *name
)
667 char *env
= argv
? getenv(name
) : NULL
;
672 return !env
|| argv_from_string(argv
, &argc
, env
);
676 argv_free(const char *argv
[])
682 for (argc
= 0; argv
[argc
]; argc
++)
683 free((void *) argv
[argc
]);
688 argv_size(const char **argv
)
692 while (argv
&& argv
[argc
])
698 DEFINE_ALLOCATOR(argv_realloc
, const char *, SIZEOF_ARG
)
701 argv_append(const char ***argv
, const char *arg
)
703 size_t argc
= argv_size(*argv
);
705 if (!argv_realloc(argv
, argc
, 2))
708 (*argv
)[argc
++] = strdup(arg
);
709 (*argv
)[argc
] = NULL
;
714 argv_append_array(const char ***dst_argv
, const char *src_argv
[])
718 for (i
= 0; src_argv
&& src_argv
[i
]; i
++)
719 if (!argv_append(dst_argv
, src_argv
[i
]))
725 argv_copy(const char ***dst
, const char *src
[])
729 for (argc
= 0; src
[argc
]; argc
++)
730 if (!argv_append(dst
, src
[argc
]))
737 * Executing external commands.
741 IO_FD
, /* File descriptor based IO. */
742 IO_BG
, /* Execute command in the background. */
743 IO_FG
, /* Execute command with same std{in,out,err}. */
744 IO_RD
, /* Read only fork+exec IO. */
745 IO_WR
, /* Write only fork+exec IO. */
746 IO_AP
, /* Append fork+exec output to file. */
750 int pipe
; /* Pipe end for reading or writing. */
751 pid_t pid
; /* PID of spawned process. */
752 int error
; /* Error status. */
753 char *buf
; /* Read buffer. */
754 size_t bufalloc
; /* Allocated buffer size. */
755 size_t bufsize
; /* Buffer content size. */
756 char *bufpos
; /* Current buffer position. */
757 unsigned int eof
:1; /* Has end of file been reached. */
761 io_init(struct io
*io
)
763 memset(io
, 0, sizeof(*io
));
768 io_open(struct io
*io
, const char *fmt
, ...)
770 char name
[SIZEOF_STR
] = "";
777 fits
= vsnprintf(name
, sizeof(name
), fmt
, args
) < sizeof(name
);
781 io
->error
= ENAMETOOLONG
;
784 io
->pipe
= *name
? open(name
, O_RDONLY
) : STDIN_FILENO
;
787 return io
->pipe
!= -1;
791 io_kill(struct io
*io
)
793 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
797 io_done(struct io
*io
)
808 pid_t waiting
= waitpid(pid
, &status
, 0);
817 return waiting
== pid
&&
818 !WIFSIGNALED(status
) &&
820 !WEXITSTATUS(status
);
827 io_run(struct io
*io
, enum io_type type
, const char *dir
, const char *argv
[], ...)
829 int pipefds
[2] = { -1, -1 };
834 if ((type
== IO_RD
|| type
== IO_WR
) && pipe(pipefds
) < 0) {
837 } else if (type
== IO_AP
) {
838 va_start(args
, argv
);
839 pipefds
[1] = va_arg(args
, int);
843 if ((io
->pid
= fork())) {
846 if (pipefds
[!(type
== IO_WR
)] != -1)
847 close(pipefds
[!(type
== IO_WR
)]);
849 io
->pipe
= pipefds
[!!(type
== IO_WR
)];
855 int devnull
= open("/dev/null", O_RDWR
);
856 int readfd
= type
== IO_WR
? pipefds
[0] : devnull
;
857 int writefd
= (type
== IO_RD
|| type
== IO_AP
)
858 ? pipefds
[1] : devnull
;
860 dup2(readfd
, STDIN_FILENO
);
861 dup2(writefd
, STDOUT_FILENO
);
862 dup2(devnull
, STDERR_FILENO
);
865 if (pipefds
[0] != -1)
867 if (pipefds
[1] != -1)
871 if (dir
&& *dir
&& chdir(dir
) == -1)
874 execvp(argv
[0], (char *const*) argv
);
878 if (pipefds
[!!(type
== IO_WR
)] != -1)
879 close(pipefds
[!!(type
== IO_WR
)]);
884 io_complete(enum io_type type
, const char **argv
, const char *dir
, int fd
)
888 return io_run(&io
, type
, dir
, argv
, fd
) && io_done(&io
);
892 io_run_bg(const char **argv
)
894 return io_complete(IO_BG
, argv
, NULL
, -1);
898 io_run_fg(const char **argv
, const char *dir
)
900 return io_complete(IO_FG
, argv
, dir
, -1);
904 io_run_append(const char **argv
, int fd
)
906 return io_complete(IO_AP
, argv
, NULL
, fd
);
910 io_eof(struct io
*io
)
916 io_error(struct io
*io
)
922 io_strerror(struct io
*io
)
924 return strerror(io
->error
);
928 io_can_read(struct io
*io
)
930 struct timeval tv
= { 0, 500 };
934 FD_SET(io
->pipe
, &fds
);
936 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
940 io_read(struct io
*io
, void *buf
, size_t bufsize
)
943 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
945 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
947 else if (readsize
== -1)
949 else if (readsize
== 0)
955 DEFINE_ALLOCATOR(io_realloc_buf
, char, BUFSIZ
)
958 io_get(struct io
*io
, int c
, bool can_read
)
964 if (io
->bufsize
> 0) {
965 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
967 char *line
= io
->bufpos
;
970 io
->bufpos
= eol
+ 1;
971 io
->bufsize
-= io
->bufpos
- line
;
978 io
->bufpos
[io
->bufsize
] = 0;
988 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
989 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
991 if (io
->bufalloc
== io
->bufsize
) {
992 if (!io_realloc_buf(&io
->buf
, io
->bufalloc
, BUFSIZ
))
994 io
->bufalloc
+= BUFSIZ
;
997 io
->bufpos
= io
->buf
;
998 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
1001 io
->bufsize
+= readsize
;
1006 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
1010 while (!io_error(io
) && written
< bufsize
) {
1013 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
1014 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
1016 else if (size
== -1)
1022 return written
== bufsize
;
1026 io_read_buf(struct io
*io
, char buf
[], size_t bufsize
)
1028 char *result
= io_get(io
, '\n', TRUE
);
1031 result
= chomp_string(result
);
1032 string_ncopy_do(buf
, bufsize
, result
, strlen(result
));
1035 return io_done(io
) && result
;
1039 io_run_buf(const char **argv
, char buf
[], size_t bufsize
)
1043 return io_run(&io
, IO_RD
, NULL
, argv
) && io_read_buf(&io
, buf
, bufsize
);
1047 io_load(struct io
*io
, const char *separators
,
1048 int (*read_property
)(char *, size_t, char *, size_t))
1053 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
1058 name
= chomp_string(name
);
1059 namelen
= strcspn(name
, separators
);
1061 if (name
[namelen
]) {
1063 value
= chomp_string(name
+ namelen
+ 1);
1064 valuelen
= strlen(value
);
1071 state
= read_property(name
, namelen
, value
, valuelen
);
1074 if (state
!= ERR
&& io_error(io
))
1082 io_run_load(const char **argv
, const char *separators
,
1083 int (*read_property
)(char *, size_t, char *, size_t))
1087 if (!io_run(&io
, IO_RD
, NULL
, argv
))
1089 return io_load(&io
, separators
, read_property
);
1098 /* XXX: Keep the view request first and in sync with views[]. */ \
1099 REQ_GROUP("View switching") \
1100 REQ_(VIEW_MAIN, "Show main view"), \
1101 REQ_(VIEW_DIFF, "Show diff view"), \
1102 REQ_(VIEW_LOG, "Show log view"), \
1103 REQ_(VIEW_TREE, "Show tree view"), \
1104 REQ_(VIEW_BLOB, "Show blob view"), \
1105 REQ_(VIEW_BLAME, "Show blame view"), \
1106 REQ_(VIEW_BRANCH, "Show branch view"), \
1107 REQ_(VIEW_HELP, "Show help page"), \
1108 REQ_(VIEW_PAGER, "Show pager view"), \
1109 REQ_(VIEW_STATUS, "Show status view"), \
1110 REQ_(VIEW_STAGE, "Show stage view"), \
1112 REQ_GROUP("View manipulation") \
1113 REQ_(ENTER, "Enter current line and scroll"), \
1114 REQ_(NEXT, "Move to next"), \
1115 REQ_(PREVIOUS, "Move to previous"), \
1116 REQ_(PARENT, "Move to parent"), \
1117 REQ_(VIEW_NEXT, "Move focus to next view"), \
1118 REQ_(REFRESH, "Reload and refresh"), \
1119 REQ_(MAXIMIZE, "Maximize the current view"), \
1120 REQ_(VIEW_CLOSE, "Close the current view"), \
1121 REQ_(QUIT, "Close all views and quit"), \
1123 REQ_GROUP("View specific requests") \
1124 REQ_(STATUS_UPDATE, "Update file status"), \
1125 REQ_(STATUS_REVERT, "Revert file changes"), \
1126 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1127 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1129 REQ_GROUP("Cursor navigation") \
1130 REQ_(MOVE_UP, "Move cursor one line up"), \
1131 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1132 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1133 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1134 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1135 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1137 REQ_GROUP("Scrolling") \
1138 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1139 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1140 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1141 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1142 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1143 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1145 REQ_GROUP("Searching") \
1146 REQ_(SEARCH, "Search the view"), \
1147 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1148 REQ_(FIND_NEXT, "Find next search match"), \
1149 REQ_(FIND_PREV, "Find previous search match"), \
1151 REQ_GROUP("Option manipulation") \
1152 REQ_(OPTIONS, "Open option menu"), \
1153 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1154 REQ_(TOGGLE_DATE, "Toggle date display"), \
1155 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1156 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1157 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1158 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1159 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1160 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1163 REQ_(PROMPT, "Bring up the prompt"), \
1164 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1165 REQ_(SHOW_VERSION, "Show version information"), \
1166 REQ_(STOP_LOADING, "Stop all loading views"), \
1167 REQ_(EDIT, "Open in editor"), \
1168 REQ_(NONE, "Do nothing")
1171 /* User action requests. */
1173 #define REQ_GROUP(help)
1174 #define REQ_(req, help) REQ_##req
1176 /* Offset all requests to avoid conflicts with ncurses getch values. */
1177 REQ_UNKNOWN
= KEY_MAX
+ 1,
1185 struct request_info
{
1186 enum request request
;
1192 static const struct request_info req_info
[] = {
1193 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1194 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1201 get_request(const char *name
)
1203 int namelen
= strlen(name
);
1206 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
1207 if (enum_equals(req_info
[i
], name
, namelen
))
1208 return req_info
[i
].request
;
1218 /* Option and state variables. */
1219 static enum date opt_date
= DATE_DEFAULT
;
1220 static enum author opt_author
= AUTHOR_DEFAULT
;
1221 static bool opt_line_number
= FALSE
;
1222 static bool opt_line_graphics
= TRUE
;
1223 static bool opt_rev_graph
= FALSE
;
1224 static bool opt_show_refs
= TRUE
;
1225 static int opt_num_interval
= 5;
1226 static double opt_hscroll
= 0.50;
1227 static double opt_scale_split_view
= 2.0 / 3.0;
1228 static int opt_tab_size
= 8;
1229 static int opt_author_cols
= AUTHOR_COLS
;
1230 static char opt_path
[SIZEOF_STR
] = "";
1231 static char opt_file
[SIZEOF_STR
] = "";
1232 static char opt_ref
[SIZEOF_REF
] = "";
1233 static char opt_head
[SIZEOF_REF
] = "";
1234 static char opt_remote
[SIZEOF_REF
] = "";
1235 static char opt_encoding
[20] = "UTF-8";
1236 static iconv_t opt_iconv_in
= ICONV_NONE
;
1237 static iconv_t opt_iconv_out
= ICONV_NONE
;
1238 static char opt_search
[SIZEOF_STR
] = "";
1239 static char opt_cdup
[SIZEOF_STR
] = "";
1240 static char opt_prefix
[SIZEOF_STR
] = "";
1241 static char opt_git_dir
[SIZEOF_STR
] = "";
1242 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
1243 static char opt_editor
[SIZEOF_STR
] = "";
1244 static FILE *opt_tty
= NULL
;
1245 static const char **opt_diff_args
= NULL
;
1246 static const char **opt_rev_args
= NULL
;
1247 static const char **opt_file_args
= NULL
;
1249 #define is_initial_commit() (!get_ref_head())
1250 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1254 * Line-oriented content detection.
1258 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1271 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1272 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1273 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1274 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1275 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1276 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1277 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1279 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1280 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1281 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1282 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1283 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1284 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1285 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1286 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1287 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1288 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1289 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1290 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1291 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1292 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1293 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1294 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1295 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1296 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1297 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1298 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1299 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1300 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1301 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1302 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1303 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1304 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1305 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1306 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1307 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1308 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1309 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1310 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1311 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1312 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1313 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1314 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1315 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1316 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1319 #define LINE(type, line, fg, bg, attr) \
1327 const char *name
; /* Option name. */
1328 int namelen
; /* Size of option name. */
1329 const char *line
; /* The start of line to match. */
1330 int linelen
; /* Size of string to match. */
1331 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1334 static struct line_info line_info
[] = {
1335 #define LINE(type, line, fg, bg, attr) \
1336 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1341 static enum line_type
1342 get_line_type(const char *line
)
1344 int linelen
= strlen(line
);
1345 enum line_type type
;
1347 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1348 /* Case insensitive search matches Signed-off-by lines better. */
1349 if (linelen
>= line_info
[type
].linelen
&&
1350 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1353 return LINE_DEFAULT
;
1357 get_line_attr(enum line_type type
)
1359 assert(type
< ARRAY_SIZE(line_info
));
1360 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1363 static struct line_info
*
1364 get_line_info(const char *name
)
1366 size_t namelen
= strlen(name
);
1367 enum line_type type
;
1369 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1370 if (enum_equals(line_info
[type
], name
, namelen
))
1371 return &line_info
[type
];
1379 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1380 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1381 enum line_type type
;
1385 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1386 default_bg
= COLOR_BLACK
;
1387 default_fg
= COLOR_WHITE
;
1390 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1391 struct line_info
*info
= &line_info
[type
];
1392 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1393 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1395 init_pair(type
, fg
, bg
);
1400 enum line_type type
;
1403 unsigned int selected
:1;
1404 unsigned int dirty
:1;
1405 unsigned int cleareol
:1;
1406 unsigned int other
:16;
1408 void *data
; /* User data */
1418 enum request request
;
1421 static struct keybinding default_keybindings
[] = {
1422 /* View switching */
1423 { 'm', REQ_VIEW_MAIN
},
1424 { 'd', REQ_VIEW_DIFF
},
1425 { 'l', REQ_VIEW_LOG
},
1426 { 't', REQ_VIEW_TREE
},
1427 { 'f', REQ_VIEW_BLOB
},
1428 { 'B', REQ_VIEW_BLAME
},
1429 { 'H', REQ_VIEW_BRANCH
},
1430 { 'p', REQ_VIEW_PAGER
},
1431 { 'h', REQ_VIEW_HELP
},
1432 { 'S', REQ_VIEW_STATUS
},
1433 { 'c', REQ_VIEW_STAGE
},
1435 /* View manipulation */
1436 { 'q', REQ_VIEW_CLOSE
},
1437 { KEY_TAB
, REQ_VIEW_NEXT
},
1438 { KEY_RETURN
, REQ_ENTER
},
1439 { KEY_UP
, REQ_PREVIOUS
},
1440 { KEY_DOWN
, REQ_NEXT
},
1441 { 'R', REQ_REFRESH
},
1442 { KEY_F(5), REQ_REFRESH
},
1443 { 'O', REQ_MAXIMIZE
},
1445 /* Cursor navigation */
1446 { 'k', REQ_MOVE_UP
},
1447 { 'j', REQ_MOVE_DOWN
},
1448 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1449 { KEY_END
, REQ_MOVE_LAST_LINE
},
1450 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1451 { ' ', REQ_MOVE_PAGE_DOWN
},
1452 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1453 { 'b', REQ_MOVE_PAGE_UP
},
1454 { '-', REQ_MOVE_PAGE_UP
},
1457 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1458 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1459 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1460 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1461 { 'w', REQ_SCROLL_PAGE_UP
},
1462 { 's', REQ_SCROLL_PAGE_DOWN
},
1465 { '/', REQ_SEARCH
},
1466 { '?', REQ_SEARCH_BACK
},
1467 { 'n', REQ_FIND_NEXT
},
1468 { 'N', REQ_FIND_PREV
},
1472 { 'z', REQ_STOP_LOADING
},
1473 { 'v', REQ_SHOW_VERSION
},
1474 { 'r', REQ_SCREEN_REDRAW
},
1475 { 'o', REQ_OPTIONS
},
1476 { '.', REQ_TOGGLE_LINENO
},
1477 { 'D', REQ_TOGGLE_DATE
},
1478 { 'A', REQ_TOGGLE_AUTHOR
},
1479 { 'g', REQ_TOGGLE_REV_GRAPH
},
1480 { 'F', REQ_TOGGLE_REFS
},
1481 { 'I', REQ_TOGGLE_SORT_ORDER
},
1482 { 'i', REQ_TOGGLE_SORT_FIELD
},
1483 { ':', REQ_PROMPT
},
1484 { 'u', REQ_STATUS_UPDATE
},
1485 { '!', REQ_STATUS_REVERT
},
1486 { 'M', REQ_STATUS_MERGE
},
1487 { '@', REQ_STAGE_NEXT
},
1488 { ',', REQ_PARENT
},
1492 #define KEYMAP_INFO \
1507 #define KEYMAP_(name) KEYMAP_##name
1512 static const struct enum_map keymap_table
[] = {
1513 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1518 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1520 struct keybinding_table
{
1521 struct keybinding
*data
;
1525 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1528 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1530 struct keybinding_table
*table
= &keybindings
[keymap
];
1533 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1534 if (keybindings
[keymap
].data
[i
].alias
== key
) {
1535 keybindings
[keymap
].data
[i
].request
= request
;
1540 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1542 die("Failed to allocate keybinding");
1543 table
->data
[table
->size
].alias
= key
;
1544 table
->data
[table
->size
++].request
= request
;
1546 if (request
== REQ_NONE
&& keymap
== KEYMAP_GENERIC
) {
1549 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1550 if (default_keybindings
[i
].alias
== key
)
1551 default_keybindings
[i
].request
= REQ_NONE
;
1555 /* Looks for a key binding first in the given map, then in the generic map, and
1556 * lastly in the default keybindings. */
1558 get_keybinding(enum keymap keymap
, int key
)
1562 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1563 if (keybindings
[keymap
].data
[i
].alias
== key
)
1564 return keybindings
[keymap
].data
[i
].request
;
1566 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1567 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1568 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1570 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1571 if (default_keybindings
[i
].alias
== key
)
1572 return default_keybindings
[i
].request
;
1574 return (enum request
) key
;
1583 static const struct key key_table
[] = {
1584 { "Enter", KEY_RETURN
},
1586 { "Backspace", KEY_BACKSPACE
},
1588 { "Escape", KEY_ESC
},
1589 { "Left", KEY_LEFT
},
1590 { "Right", KEY_RIGHT
},
1592 { "Down", KEY_DOWN
},
1593 { "Insert", KEY_IC
},
1594 { "Delete", KEY_DC
},
1596 { "Home", KEY_HOME
},
1598 { "PageUp", KEY_PPAGE
},
1599 { "PageDown", KEY_NPAGE
},
1609 { "F10", KEY_F(10) },
1610 { "F11", KEY_F(11) },
1611 { "F12", KEY_F(12) },
1615 get_key_value(const char *name
)
1619 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1620 if (!strcasecmp(key_table
[i
].name
, name
))
1621 return key_table
[i
].value
;
1623 if (strlen(name
) == 1 && isprint(*name
))
1630 get_key_name(int key_value
)
1632 static char key_char
[] = "'X'";
1633 const char *seq
= NULL
;
1636 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1637 if (key_table
[key
].value
== key_value
)
1638 seq
= key_table
[key
].name
;
1642 isprint(key_value
)) {
1643 key_char
[1] = (char) key_value
;
1647 return seq
? seq
: "(no key)";
1651 append_key(char *buf
, size_t *pos
, const struct keybinding
*keybinding
)
1653 const char *sep
= *pos
> 0 ? ", " : "";
1654 const char *keyname
= get_key_name(keybinding
->alias
);
1656 return string_nformat(buf
, BUFSIZ
, pos
, "%s%s", sep
, keyname
);
1660 append_keymap_request_keys(char *buf
, size_t *pos
, enum request request
,
1661 enum keymap keymap
, bool all
)
1665 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1666 if (keybindings
[keymap
].data
[i
].request
== request
) {
1667 if (!append_key(buf
, pos
, &keybindings
[keymap
].data
[i
]))
1677 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1680 get_keys(enum keymap keymap
, enum request request
, bool all
)
1682 static char buf
[BUFSIZ
];
1688 if (!append_keymap_request_keys(buf
, &pos
, request
, keymap
, all
))
1689 return "Too many keybindings!";
1690 if (pos
> 0 && !all
)
1693 if (keymap
!= KEYMAP_GENERIC
) {
1694 /* Only the generic keymap includes the default keybindings when
1695 * listing all keys. */
1699 if (!append_keymap_request_keys(buf
, &pos
, request
, KEYMAP_GENERIC
, all
))
1700 return "Too many keybindings!";
1705 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1706 if (default_keybindings
[i
].request
== request
) {
1707 if (!append_key(buf
, &pos
, &default_keybindings
[i
]))
1708 return "Too many keybindings!";
1717 struct run_request
{
1723 static struct run_request
*run_request
;
1724 static size_t run_requests
;
1726 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1729 add_run_request(enum keymap keymap
, int key
, const char **argv
)
1731 struct run_request
*req
;
1733 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1736 req
= &run_request
[run_requests
];
1737 req
->keymap
= keymap
;
1741 if (!argv_copy(&req
->argv
, argv
))
1744 return REQ_NONE
+ ++run_requests
;
1747 static struct run_request
*
1748 get_run_request(enum request request
)
1750 if (request
<= REQ_NONE
)
1752 return &run_request
[request
- REQ_NONE
- 1];
1756 add_builtin_run_requests(void)
1758 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1759 const char *checkout
[] = { "git", "checkout", "%(branch)", NULL
};
1760 const char *commit
[] = { "git", "commit", NULL
};
1761 const char *gc
[] = { "git", "gc", NULL
};
1762 struct run_request reqs
[] = {
1763 { KEYMAP_MAIN
, 'C', cherry_pick
},
1764 { KEYMAP_STATUS
, 'C', commit
},
1765 { KEYMAP_BRANCH
, 'C', checkout
},
1766 { KEYMAP_GENERIC
, 'G', gc
},
1770 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1771 enum request req
= get_keybinding(reqs
[i
].keymap
, reqs
[i
].key
);
1773 if (req
!= reqs
[i
].key
)
1775 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argv
);
1776 if (req
!= REQ_NONE
)
1777 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1782 * User config file handling.
1785 static int config_lineno
;
1786 static bool config_errors
;
1787 static const char *config_msg
;
1789 static const struct enum_map color_map
[] = {
1790 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1802 static const struct enum_map attr_map
[] = {
1803 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1810 ATTR_MAP(UNDERLINE
),
1813 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1815 static int parse_step(double *opt
, const char *arg
)
1818 if (!strchr(arg
, '%'))
1821 /* "Shift down" so 100% and 1 does not conflict. */
1822 *opt
= (*opt
- 1) / 100;
1825 config_msg
= "Step value larger than 100%";
1830 config_msg
= "Invalid step value";
1837 parse_int(int *opt
, const char *arg
, int min
, int max
)
1839 int value
= atoi(arg
);
1841 if (min
<= value
&& value
<= max
) {
1846 config_msg
= "Integer value out of bound";
1851 set_color(int *color
, const char *name
)
1853 if (map_enum(color
, color_map
, name
))
1855 if (!prefixcmp(name
, "color"))
1856 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1860 /* Wants: object fgcolor bgcolor [attribute] */
1862 option_color_command(int argc
, const char *argv
[])
1864 struct line_info
*info
;
1867 config_msg
= "Wrong number of arguments given to color command";
1871 info
= get_line_info(argv
[0]);
1873 static const struct enum_map obsolete
[] = {
1874 ENUM_MAP("main-delim", LINE_DELIMITER
),
1875 ENUM_MAP("main-date", LINE_DATE
),
1876 ENUM_MAP("main-author", LINE_AUTHOR
),
1880 if (!map_enum(&index
, obsolete
, argv
[0])) {
1881 config_msg
= "Unknown color name";
1884 info
= &line_info
[index
];
1887 if (!set_color(&info
->fg
, argv
[1]) ||
1888 !set_color(&info
->bg
, argv
[2])) {
1889 config_msg
= "Unknown color";
1894 while (argc
-- > 3) {
1897 if (!set_attribute(&attr
, argv
[argc
])) {
1898 config_msg
= "Unknown attribute";
1907 static int parse_bool(bool *opt
, const char *arg
)
1909 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1914 static int parse_enum_do(unsigned int *opt
, const char *arg
,
1915 const struct enum_map
*map
, size_t map_size
)
1919 assert(map_size
> 1);
1921 if (map_enum_do(map
, map_size
, (int *) opt
, arg
))
1924 if (parse_bool(&is_true
, arg
) != OK
)
1927 *opt
= is_true
? map
[1].value
: map
[0].value
;
1931 #define parse_enum(opt, arg, map) \
1932 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1935 parse_string(char *opt
, const char *arg
, size_t optsize
)
1937 int arglen
= strlen(arg
);
1942 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1943 config_msg
= "Unmatched quotation";
1946 arg
+= 1; arglen
-= 2;
1948 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1953 /* Wants: name = value */
1955 option_set_command(int argc
, const char *argv
[])
1958 config_msg
= "Wrong number of arguments given to set command";
1962 if (strcmp(argv
[1], "=")) {
1963 config_msg
= "No value assigned";
1967 if (!strcmp(argv
[0], "show-author"))
1968 return parse_enum(&opt_author
, argv
[2], author_map
);
1970 if (!strcmp(argv
[0], "show-date"))
1971 return parse_enum(&opt_date
, argv
[2], date_map
);
1973 if (!strcmp(argv
[0], "show-rev-graph"))
1974 return parse_bool(&opt_rev_graph
, argv
[2]);
1976 if (!strcmp(argv
[0], "show-refs"))
1977 return parse_bool(&opt_show_refs
, argv
[2]);
1979 if (!strcmp(argv
[0], "show-line-numbers"))
1980 return parse_bool(&opt_line_number
, argv
[2]);
1982 if (!strcmp(argv
[0], "line-graphics"))
1983 return parse_bool(&opt_line_graphics
, argv
[2]);
1985 if (!strcmp(argv
[0], "line-number-interval"))
1986 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
1988 if (!strcmp(argv
[0], "author-width"))
1989 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
1991 if (!strcmp(argv
[0], "horizontal-scroll"))
1992 return parse_step(&opt_hscroll
, argv
[2]);
1994 if (!strcmp(argv
[0], "split-view-height"))
1995 return parse_step(&opt_scale_split_view
, argv
[2]);
1997 if (!strcmp(argv
[0], "tab-size"))
1998 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
2000 if (!strcmp(argv
[0], "commit-encoding"))
2001 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
2003 config_msg
= "Unknown variable name";
2007 /* Wants: mode request key */
2009 option_bind_command(int argc
, const char *argv
[])
2011 enum request request
;
2016 config_msg
= "Wrong number of arguments given to bind command";
2020 if (!set_keymap(&keymap
, argv
[0])) {
2021 config_msg
= "Unknown key map";
2025 key
= get_key_value(argv
[1]);
2027 config_msg
= "Unknown key";
2031 request
= get_request(argv
[2]);
2032 if (request
== REQ_UNKNOWN
) {
2033 static const struct enum_map obsolete
[] = {
2034 ENUM_MAP("cherry-pick", REQ_NONE
),
2035 ENUM_MAP("screen-resize", REQ_NONE
),
2036 ENUM_MAP("tree-parent", REQ_PARENT
),
2040 if (map_enum(&alias
, obsolete
, argv
[2])) {
2041 if (alias
!= REQ_NONE
)
2042 add_keybinding(keymap
, alias
, key
);
2043 config_msg
= "Obsolete request name";
2047 if (request
== REQ_UNKNOWN
&& *argv
[2]++ == '!')
2048 request
= add_run_request(keymap
, key
, argv
+ 2);
2049 if (request
== REQ_UNKNOWN
) {
2050 config_msg
= "Unknown request name";
2054 add_keybinding(keymap
, request
, key
);
2060 set_option(const char *opt
, char *value
)
2062 const char *argv
[SIZEOF_ARG
];
2065 if (!argv_from_string(argv
, &argc
, value
)) {
2066 config_msg
= "Too many option arguments";
2070 if (!strcmp(opt
, "color"))
2071 return option_color_command(argc
, argv
);
2073 if (!strcmp(opt
, "set"))
2074 return option_set_command(argc
, argv
);
2076 if (!strcmp(opt
, "bind"))
2077 return option_bind_command(argc
, argv
);
2079 config_msg
= "Unknown option command";
2084 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
2089 config_msg
= "Internal error";
2091 /* Check for comment markers, since read_properties() will
2092 * only ensure opt and value are split at first " \t". */
2093 optlen
= strcspn(opt
, "#");
2097 if (opt
[optlen
] != 0) {
2098 config_msg
= "No option value";
2102 /* Look for comment endings in the value. */
2103 size_t len
= strcspn(value
, "#");
2105 if (len
< valuelen
) {
2107 value
[valuelen
] = 0;
2110 status
= set_option(opt
, value
);
2113 if (status
== ERR
) {
2114 warn("Error on line %d, near '%.*s': %s",
2115 config_lineno
, (int) optlen
, opt
, config_msg
);
2116 config_errors
= TRUE
;
2119 /* Always keep going if errors are encountered. */
2124 load_option_file(const char *path
)
2128 /* It's OK that the file doesn't exist. */
2129 if (!io_open(&io
, "%s", path
))
2133 config_errors
= FALSE
;
2135 if (io_load(&io
, " \t", read_option
) == ERR
||
2136 config_errors
== TRUE
)
2137 warn("Errors while loading %s.", path
);
2143 const char *home
= getenv("HOME");
2144 const char *tigrc_user
= getenv("TIGRC_USER");
2145 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
2146 const char *tig_diff_opts
= getenv("TIG_DIFF_OPTS");
2147 char buf
[SIZEOF_STR
];
2150 tigrc_system
= SYSCONFDIR
"/tigrc";
2151 load_option_file(tigrc_system
);
2154 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
2158 load_option_file(tigrc_user
);
2160 /* Add _after_ loading config files to avoid adding run requests
2161 * that conflict with keybindings. */
2162 add_builtin_run_requests();
2164 if (!opt_diff_args
&& tig_diff_opts
&& *tig_diff_opts
) {
2165 static const char *diff_opts
[SIZEOF_ARG
] = { NULL
};
2168 if (!string_format(buf
, "%s", tig_diff_opts
) ||
2169 !argv_from_string(diff_opts
, &argc
, buf
))
2170 die("TIG_DIFF_OPTS contains too many arguments");
2171 else if (!argv_copy(&opt_diff_args
, diff_opts
))
2172 die("Failed to format TIG_DIFF_OPTS arguments");
2186 /* The display array of active views and the index of the current view. */
2187 static struct view
*display
[2];
2188 static unsigned int current_view
;
2190 #define foreach_displayed_view(view, i) \
2191 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2193 #define displayed_views() (display[1] != NULL ? 2 : 1)
2195 /* Current head and commit ID */
2196 static char ref_blob
[SIZEOF_REF
] = "";
2197 static char ref_commit
[SIZEOF_REF
] = "HEAD";
2198 static char ref_head
[SIZEOF_REF
] = "HEAD";
2199 static char ref_branch
[SIZEOF_REF
] = "";
2216 enum view_type type
; /* View type */
2217 const char *name
; /* View name */
2218 const char *cmd_env
; /* Command line set via environment */
2219 const char *id
; /* Points to either of ref_{head,commit,blob} */
2221 struct view_ops
*ops
; /* View operations */
2223 enum keymap keymap
; /* What keymap does this view have */
2224 bool git_dir
; /* Whether the view requires a git directory. */
2226 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
2227 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
2229 int height
, width
; /* The width and height of the main window */
2230 WINDOW
*win
; /* The main window */
2231 WINDOW
*title
; /* The title window living below the main window */
2234 unsigned long offset
; /* Offset of the window top */
2235 unsigned long yoffset
; /* Offset from the window side. */
2236 unsigned long lineno
; /* Current line number */
2237 unsigned long p_offset
; /* Previous offset of the window top */
2238 unsigned long p_yoffset
;/* Previous offset from the window side */
2239 unsigned long p_lineno
; /* Previous current line number */
2240 bool p_restore
; /* Should the previous position be restored. */
2243 char grep
[SIZEOF_STR
]; /* Search string */
2244 regex_t
*regex
; /* Pre-compiled regexp */
2246 /* If non-NULL, points to the view that opened this view. If this view
2247 * is closed tig will switch back to the parent view. */
2248 struct view
*parent
;
2252 size_t lines
; /* Total number of lines */
2253 struct line
*line
; /* Line index */
2254 unsigned int digits
; /* Number of digits in the lines member. */
2257 struct line
*curline
; /* Line currently being drawn. */
2258 enum line_type curtype
; /* Attribute currently used for drawing. */
2259 unsigned long col
; /* Column when drawing. */
2260 bool has_scrolled
; /* View was scrolled. */
2263 const char **argv
; /* Shell command arguments. */
2264 const char *dir
; /* Directory from which to execute. */
2272 /* What type of content being displayed. Used in the title bar. */
2274 /* Default command arguments. */
2276 /* Open and reads in all view content. */
2277 bool (*open
)(struct view
*view
);
2278 /* Read one line; updates view->line. */
2279 bool (*read
)(struct view
*view
, char *data
);
2280 /* Draw one line; @lineno must be < view->height. */
2281 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
2282 /* Depending on view handle a special requests. */
2283 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
2284 /* Search for regexp in a line. */
2285 bool (*grep
)(struct view
*view
, struct line
*line
);
2287 void (*select
)(struct view
*view
, struct line
*line
);
2288 /* Prepare view for loading */
2289 bool (*prepare
)(struct view
*view
);
2292 static struct view_ops blame_ops
;
2293 static struct view_ops blob_ops
;
2294 static struct view_ops diff_ops
;
2295 static struct view_ops help_ops
;
2296 static struct view_ops log_ops
;
2297 static struct view_ops main_ops
;
2298 static struct view_ops pager_ops
;
2299 static struct view_ops stage_ops
;
2300 static struct view_ops status_ops
;
2301 static struct view_ops tree_ops
;
2302 static struct view_ops branch_ops
;
2304 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2305 { type, name, #env, ref, ops, map, git }
2307 #define VIEW_(id, name, ops, git, ref) \
2308 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2310 static struct view views
[] = {
2311 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
2312 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
2313 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
2314 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
2315 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
2316 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
2317 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
2318 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
2319 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
2320 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
2321 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
2324 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2326 #define foreach_view(view, i) \
2327 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2329 #define view_is_displayed(view) \
2330 (view == display[0] || view == display[1])
2333 view_request(struct view
*view
, enum request request
)
2335 if (!view
|| !view
->lines
)
2337 return view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
2346 set_view_attr(struct view
*view
, enum line_type type
)
2348 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
2349 (void) wattrset(view
->win
, get_line_attr(type
));
2350 wchgat(view
->win
, -1, 0, type
, NULL
);
2351 view
->curtype
= type
;
2356 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
2357 int max_len
, bool use_tilde
)
2359 static char out_buffer
[BUFSIZ
* 2];
2362 int trimmed
= FALSE
;
2363 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2368 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
, opt_tab_size
);
2370 set_view_attr(view
, type
);
2372 if (opt_iconv_out
!= ICONV_NONE
) {
2373 ICONV_CONST
char *inbuf
= (ICONV_CONST
char *) string
;
2374 size_t inlen
= len
+ 1;
2376 char *outbuf
= out_buffer
;
2377 size_t outlen
= sizeof(out_buffer
);
2381 ret
= iconv(opt_iconv_out
, &inbuf
, &inlen
, &outbuf
, &outlen
);
2382 if (ret
!= (size_t) -1) {
2383 string
= out_buffer
;
2384 len
= sizeof(out_buffer
) - outlen
;
2388 waddnstr(view
->win
, string
, len
);
2390 if (trimmed
&& use_tilde
) {
2391 set_view_attr(view
, LINE_DELIMITER
);
2392 waddch(view
->win
, '~');
2400 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
2402 static char space
[] = " ";
2405 spaces
= MIN(max
, spaces
);
2407 while (spaces
> 0) {
2408 int len
= MIN(spaces
, sizeof(space
) - 1);
2410 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
2418 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
2420 char text
[SIZEOF_STR
];
2423 size_t pos
= string_expand(text
, sizeof(text
), string
, opt_tab_size
);
2425 view
->col
+= draw_chars(view
, type
, text
, view
->width
+ view
->yoffset
- view
->col
, trim
);
2427 } while (*string
&& view
->width
+ view
->yoffset
> view
->col
);
2429 return view
->width
+ view
->yoffset
<= view
->col
;
2433 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
2435 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2436 int max
= view
->width
+ view
->yoffset
- view
->col
;
2442 set_view_attr(view
, type
);
2443 /* Using waddch() instead of waddnstr() ensures that
2444 * they'll be rendered correctly for the cursor line. */
2445 for (i
= skip
; i
< size
; i
++)
2446 waddch(view
->win
, graphic
[i
]);
2449 if (size
< max
&& skip
<= size
)
2450 waddch(view
->win
, ' ');
2453 return view
->width
+ view
->yoffset
<= view
->col
;
2457 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2459 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2463 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2465 col
= draw_space(view
, type
, max
- 1, max
- 1);
2468 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2469 return view
->width
+ view
->yoffset
<= view
->col
;
2473 draw_date(struct view
*view
, struct time
*time
)
2475 const char *date
= mkdate(time
, opt_date
);
2476 int cols
= opt_date
== DATE_SHORT
? DATE_SHORT_COLS
: DATE_COLS
;
2478 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2482 draw_author(struct view
*view
, const char *author
)
2484 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5;
2485 bool abbreviate
= opt_author
== AUTHOR_ABBREVIATED
|| !trim
;
2487 if (abbreviate
&& author
)
2488 author
= get_author_initials(author
);
2490 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2494 draw_mode(struct view
*view
, mode_t mode
)
2500 else if (S_ISLNK(mode
))
2502 else if (S_ISGITLINK(mode
))
2504 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2506 else if (S_ISREG(mode
))
2511 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2515 draw_lineno(struct view
*view
, unsigned int lineno
)
2518 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2519 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2521 chtype separator
= opt_line_graphics
? ACS_VLINE
: '|';
2523 lineno
+= view
->offset
+ 1;
2524 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2525 static char fmt
[] = "%1ld";
2527 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2528 if (string_format(number
, fmt
, lineno
))
2532 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2534 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2535 return draw_graphic(view
, LINE_DEFAULT
, &separator
, 1);
2539 draw_view_line(struct view
*view
, unsigned int lineno
)
2542 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2544 assert(view_is_displayed(view
));
2546 if (view
->offset
+ lineno
>= view
->lines
)
2549 line
= &view
->line
[view
->offset
+ lineno
];
2551 wmove(view
->win
, lineno
, 0);
2553 wclrtoeol(view
->win
);
2555 view
->curline
= line
;
2556 view
->curtype
= LINE_NONE
;
2557 line
->selected
= FALSE
;
2558 line
->dirty
= line
->cleareol
= 0;
2561 set_view_attr(view
, LINE_CURSOR
);
2562 line
->selected
= TRUE
;
2563 view
->ops
->select(view
, line
);
2566 return view
->ops
->draw(view
, line
, lineno
);
2570 redraw_view_dirty(struct view
*view
)
2575 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2576 if (view
->offset
+ lineno
>= view
->lines
)
2578 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2581 if (!draw_view_line(view
, lineno
))
2587 wnoutrefresh(view
->win
);
2591 redraw_view_from(struct view
*view
, int lineno
)
2593 assert(0 <= lineno
&& lineno
< view
->height
);
2595 for (; lineno
< view
->height
; lineno
++) {
2596 if (!draw_view_line(view
, lineno
))
2600 wnoutrefresh(view
->win
);
2604 redraw_view(struct view
*view
)
2607 redraw_view_from(view
, 0);
2612 update_view_title(struct view
*view
)
2614 char buf
[SIZEOF_STR
];
2615 char state
[SIZEOF_STR
];
2616 size_t bufpos
= 0, statelen
= 0;
2618 assert(view_is_displayed(view
));
2620 if (view
->type
!= VIEW_STATUS
&& view
->lines
) {
2621 unsigned int view_lines
= view
->offset
+ view
->height
;
2622 unsigned int lines
= view
->lines
2623 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2626 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2635 time_t secs
= time(NULL
) - view
->start_time
;
2637 /* Three git seconds are a long time ... */
2639 string_format_from(state
, &statelen
, " loading %lds", secs
);
2642 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2643 if (*view
->ref
&& bufpos
< view
->width
) {
2644 size_t refsize
= strlen(view
->ref
);
2645 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2647 if (minsize
< view
->width
)
2648 refsize
= view
->width
- minsize
+ 7;
2649 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2652 if (statelen
&& bufpos
< view
->width
) {
2653 string_format_from(buf
, &bufpos
, "%s", state
);
2656 if (view
== display
[current_view
])
2657 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2659 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2661 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2662 wclrtoeol(view
->title
);
2663 wnoutrefresh(view
->title
);
2667 apply_step(double step
, int value
)
2671 value
*= step
+ 0.01;
2672 return value
? value
: 1;
2676 resize_display(void)
2679 struct view
*base
= display
[0];
2680 struct view
*view
= display
[1] ? display
[1] : display
[0];
2682 /* Setup window dimensions */
2684 getmaxyx(stdscr
, base
->height
, base
->width
);
2686 /* Make room for the status window. */
2690 /* Horizontal split. */
2691 view
->width
= base
->width
;
2692 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2693 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2694 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2695 base
->height
-= view
->height
;
2697 /* Make room for the title bar. */
2701 /* Make room for the title bar. */
2706 foreach_displayed_view (view
, i
) {
2708 view
->win
= newwin(view
->height
, 0, offset
, 0);
2710 die("Failed to create %s view", view
->name
);
2712 scrollok(view
->win
, FALSE
);
2714 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2716 die("Failed to create title window");
2719 wresize(view
->win
, view
->height
, view
->width
);
2720 mvwin(view
->win
, offset
, 0);
2721 mvwin(view
->title
, offset
+ view
->height
, 0);
2724 offset
+= view
->height
+ 1;
2729 redraw_display(bool clear
)
2734 foreach_displayed_view (view
, i
) {
2738 update_view_title(view
);
2748 toggle_enum_option_do(unsigned int *opt
, const char *help
,
2749 const struct enum_map
*map
, size_t size
)
2751 *opt
= (*opt
+ 1) % size
;
2752 redraw_display(FALSE
);
2753 report("Displaying %s %s", enum_name(map
[*opt
]), help
);
2756 #define toggle_enum_option(opt, help, map) \
2757 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2759 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2760 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2763 toggle_view_option(bool *option
, const char *help
)
2766 redraw_display(FALSE
);
2767 report("%sabling %s", *option
? "En" : "Dis", help
);
2771 open_option_menu(void)
2773 const struct menu_item menu
[] = {
2774 { '.', "line numbers", &opt_line_number
},
2775 { 'D', "date display", &opt_date
},
2776 { 'A', "author display", &opt_author
},
2777 { 'g', "revision graph display", &opt_rev_graph
},
2778 { 'F', "reference display", &opt_show_refs
},
2783 if (prompt_menu("Toggle option", menu
, &selected
)) {
2784 if (menu
[selected
].data
== &opt_date
)
2786 else if (menu
[selected
].data
== &opt_author
)
2789 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2794 maximize_view(struct view
*view
)
2796 memset(display
, 0, sizeof(display
));
2798 display
[current_view
] = view
;
2800 redraw_display(FALSE
);
2810 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2812 if (lineno
>= view
->lines
)
2813 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2815 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2816 unsigned long half
= view
->height
/ 2;
2819 offset
= lineno
- half
;
2824 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2825 view
->offset
= offset
;
2826 view
->lineno
= lineno
;
2833 /* Scrolling backend */
2835 do_scroll_view(struct view
*view
, int lines
)
2837 bool redraw_current_line
= FALSE
;
2839 /* The rendering expects the new offset. */
2840 view
->offset
+= lines
;
2842 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2845 /* Move current line into the view. */
2846 if (view
->lineno
< view
->offset
) {
2847 view
->lineno
= view
->offset
;
2848 redraw_current_line
= TRUE
;
2849 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2850 view
->lineno
= view
->offset
+ view
->height
- 1;
2851 redraw_current_line
= TRUE
;
2854 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2856 /* Redraw the whole screen if scrolling is pointless. */
2857 if (view
->height
< ABS(lines
)) {
2861 int line
= lines
> 0 ? view
->height
- lines
: 0;
2862 int end
= line
+ ABS(lines
);
2864 scrollok(view
->win
, TRUE
);
2865 wscrl(view
->win
, lines
);
2866 scrollok(view
->win
, FALSE
);
2868 while (line
< end
&& draw_view_line(view
, line
))
2871 if (redraw_current_line
)
2872 draw_view_line(view
, view
->lineno
- view
->offset
);
2873 wnoutrefresh(view
->win
);
2876 view
->has_scrolled
= TRUE
;
2880 /* Scroll frontend */
2882 scroll_view(struct view
*view
, enum request request
)
2886 assert(view_is_displayed(view
));
2889 case REQ_SCROLL_LEFT
:
2890 if (view
->yoffset
== 0) {
2891 report("Cannot scroll beyond the first column");
2894 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2897 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2898 redraw_view_from(view
, 0);
2901 case REQ_SCROLL_RIGHT
:
2902 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2906 case REQ_SCROLL_PAGE_DOWN
:
2907 lines
= view
->height
;
2908 case REQ_SCROLL_LINE_DOWN
:
2909 if (view
->offset
+ lines
> view
->lines
)
2910 lines
= view
->lines
- view
->offset
;
2912 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2913 report("Cannot scroll beyond the last line");
2918 case REQ_SCROLL_PAGE_UP
:
2919 lines
= view
->height
;
2920 case REQ_SCROLL_LINE_UP
:
2921 if (lines
> view
->offset
)
2922 lines
= view
->offset
;
2925 report("Cannot scroll beyond the first line");
2933 die("request %d not handled in switch", request
);
2936 do_scroll_view(view
, lines
);
2941 move_view(struct view
*view
, enum request request
)
2943 int scroll_steps
= 0;
2947 case REQ_MOVE_FIRST_LINE
:
2948 steps
= -view
->lineno
;
2951 case REQ_MOVE_LAST_LINE
:
2952 steps
= view
->lines
- view
->lineno
- 1;
2955 case REQ_MOVE_PAGE_UP
:
2956 steps
= view
->height
> view
->lineno
2957 ? -view
->lineno
: -view
->height
;
2960 case REQ_MOVE_PAGE_DOWN
:
2961 steps
= view
->lineno
+ view
->height
>= view
->lines
2962 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2974 die("request %d not handled in switch", request
);
2977 if (steps
<= 0 && view
->lineno
== 0) {
2978 report("Cannot move beyond the first line");
2981 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2982 report("Cannot move beyond the last line");
2986 /* Move the current line */
2987 view
->lineno
+= steps
;
2988 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2990 /* Check whether the view needs to be scrolled */
2991 if (view
->lineno
< view
->offset
||
2992 view
->lineno
>= view
->offset
+ view
->height
) {
2993 scroll_steps
= steps
;
2994 if (steps
< 0 && -steps
> view
->offset
) {
2995 scroll_steps
= -view
->offset
;
2997 } else if (steps
> 0) {
2998 if (view
->lineno
== view
->lines
- 1 &&
2999 view
->lines
> view
->height
) {
3000 scroll_steps
= view
->lines
- view
->offset
- 1;
3001 if (scroll_steps
>= view
->height
)
3002 scroll_steps
-= view
->height
- 1;
3007 if (!view_is_displayed(view
)) {
3008 view
->offset
+= scroll_steps
;
3009 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
3010 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
3014 /* Repaint the old "current" line if we be scrolling */
3015 if (ABS(steps
) < view
->height
)
3016 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
3019 do_scroll_view(view
, scroll_steps
);
3023 /* Draw the current line */
3024 draw_view_line(view
, view
->lineno
- view
->offset
);
3026 wnoutrefresh(view
->win
);
3035 static void search_view(struct view
*view
, enum request request
);
3038 grep_text(struct view
*view
, const char *text
[])
3043 for (i
= 0; text
[i
]; i
++)
3045 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
3051 select_view_line(struct view
*view
, unsigned long lineno
)
3053 unsigned long old_lineno
= view
->lineno
;
3054 unsigned long old_offset
= view
->offset
;
3056 if (goto_view_line(view
, view
->offset
, lineno
)) {
3057 if (view_is_displayed(view
)) {
3058 if (old_offset
!= view
->offset
) {
3061 draw_view_line(view
, old_lineno
- view
->offset
);
3062 draw_view_line(view
, view
->lineno
- view
->offset
);
3063 wnoutrefresh(view
->win
);
3066 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
3072 find_next(struct view
*view
, enum request request
)
3074 unsigned long lineno
= view
->lineno
;
3079 report("No previous search");
3081 search_view(view
, request
);
3091 case REQ_SEARCH_BACK
:
3100 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
3101 lineno
+= direction
;
3103 /* Note, lineno is unsigned long so will wrap around in which case it
3104 * will become bigger than view->lines. */
3105 for (; lineno
< view
->lines
; lineno
+= direction
) {
3106 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
3107 select_view_line(view
, lineno
);
3108 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
3113 report("No match found for '%s'", view
->grep
);
3117 search_view(struct view
*view
, enum request request
)
3122 regfree(view
->regex
);
3125 view
->regex
= calloc(1, sizeof(*view
->regex
));
3130 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
3131 if (regex_err
!= 0) {
3132 char buf
[SIZEOF_STR
] = "unknown error";
3134 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
3135 report("Search failed: %s", buf
);
3139 string_copy(view
->grep
, opt_search
);
3141 find_next(view
, request
);
3145 * Incremental updating
3149 reset_view(struct view
*view
)
3153 for (i
= 0; i
< view
->lines
; i
++)
3154 free(view
->line
[i
].data
);
3157 view
->p_offset
= view
->offset
;
3158 view
->p_yoffset
= view
->yoffset
;
3159 view
->p_lineno
= view
->lineno
;
3167 view
->update_secs
= 0;
3171 format_arg(const char *name
)
3177 const char *value_if_empty
;
3179 #define FORMAT_VAR(name, value, value_if_empty) \
3180 { name, STRING_SIZE(name), value, value_if_empty }
3181 FORMAT_VAR("%(directory)", opt_path
, ""),
3182 FORMAT_VAR("%(file)", opt_file
, ""),
3183 FORMAT_VAR("%(ref)", opt_ref
, "HEAD"),
3184 FORMAT_VAR("%(head)", ref_head
, ""),
3185 FORMAT_VAR("%(commit)", ref_commit
, ""),
3186 FORMAT_VAR("%(blob)", ref_blob
, ""),
3187 FORMAT_VAR("%(branch)", ref_branch
, ""),
3191 for (i
= 0; i
< ARRAY_SIZE(vars
); i
++)
3192 if (!strncmp(name
, vars
[i
].name
, vars
[i
].namelen
))
3193 return *vars
[i
].value
? vars
[i
].value
: vars
[i
].value_if_empty
;
3195 report("Unknown replacement: `%s`", name
);
3200 format_argv(const char ***dst_argv
, const char *src_argv
[], bool replace
)
3202 char buf
[SIZEOF_STR
];
3205 argv_free(*dst_argv
);
3207 for (argc
= 0; src_argv
[argc
]; argc
++) {
3208 const char *arg
= src_argv
[argc
];
3211 if (!strcmp(arg
, "%(fileargs)")) {
3212 if (!argv_append_array(dst_argv
, opt_file_args
))
3216 } else if (!strcmp(arg
, "%(diffargs)")) {
3217 if (!argv_append_array(dst_argv
, opt_diff_args
))
3221 } else if (!strcmp(arg
, "%(revargs)")) {
3222 if (!argv_append_array(dst_argv
, opt_rev_args
))
3228 char *next
= strstr(arg
, "%(");
3229 int len
= next
- arg
;
3232 if (!next
|| !replace
) {
3237 value
= format_arg(next
);
3244 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
3247 arg
= next
&& replace
? strchr(next
, ')') + 1 : NULL
;
3250 if (!argv_append(dst_argv
, buf
))
3254 return src_argv
[argc
] == NULL
;
3258 restore_view_position(struct view
*view
)
3260 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
3263 /* Changing the view position cancels the restoring. */
3264 /* FIXME: Changing back to the first line is not detected. */
3265 if (view
->offset
!= 0 || view
->lineno
!= 0) {
3266 view
->p_restore
= FALSE
;
3270 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
3271 view_is_displayed(view
))
3274 view
->yoffset
= view
->p_yoffset
;
3275 view
->p_restore
= FALSE
;
3281 end_update(struct view
*view
, bool force
)
3285 while (!view
->ops
->read(view
, NULL
))
3289 io_kill(view
->pipe
);
3290 io_done(view
->pipe
);
3295 setup_update(struct view
*view
, const char *vid
)
3298 string_copy_rev(view
->vid
, vid
);
3299 view
->pipe
= &view
->io
;
3300 view
->start_time
= time(NULL
);
3304 prepare_io(struct view
*view
, const char *dir
, const char *argv
[], bool replace
)
3307 return format_argv(&view
->argv
, argv
, replace
);
3311 prepare_update(struct view
*view
, const char *argv
[], const char *dir
)
3314 end_update(view
, TRUE
);
3315 return prepare_io(view
, dir
, argv
, FALSE
);
3319 start_update(struct view
*view
, const char **argv
, const char *dir
)
3322 io_done(view
->pipe
);
3323 return prepare_io(view
, dir
, argv
, FALSE
) &&
3324 io_run(&view
->io
, IO_RD
, dir
, view
->argv
);
3328 prepare_update_file(struct view
*view
, const char *name
)
3331 end_update(view
, TRUE
);
3332 argv_free(view
->argv
);
3333 return io_open(&view
->io
, "%s/%s", opt_cdup
[0] ? opt_cdup
: ".", name
);
3337 begin_update(struct view
*view
, bool refresh
)
3340 end_update(view
, TRUE
);
3343 if (view
->ops
->prepare
) {
3344 if (!view
->ops
->prepare(view
))
3346 } else if (!prepare_io(view
, NULL
, view
->ops
->argv
, TRUE
)) {
3350 /* Put the current ref_* value to the view title ref
3351 * member. This is needed by the blob view. Most other
3352 * views sets it automatically after loading because the
3353 * first line is a commit line. */
3354 string_copy_rev(view
->ref
, view
->id
);
3357 if (view
->argv
&& view
->argv
[0] &&
3358 !io_run(&view
->io
, IO_RD
, view
->dir
, view
->argv
))
3361 setup_update(view
, view
->id
);
3367 update_view(struct view
*view
)
3369 char out_buffer
[BUFSIZ
* 2];
3371 /* Clear the view and redraw everything since the tree sorting
3372 * might have rearranged things. */
3373 bool redraw
= view
->lines
== 0;
3374 bool can_read
= TRUE
;
3379 if (!io_can_read(view
->pipe
)) {
3380 if (view
->lines
== 0 && view_is_displayed(view
)) {
3381 time_t secs
= time(NULL
) - view
->start_time
;
3383 if (secs
> 1 && secs
> view
->update_secs
) {
3384 if (view
->update_secs
== 0)
3386 update_view_title(view
);
3387 view
->update_secs
= secs
;
3393 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
3394 if (opt_iconv_in
!= ICONV_NONE
) {
3395 ICONV_CONST
char *inbuf
= line
;
3396 size_t inlen
= strlen(line
) + 1;
3398 char *outbuf
= out_buffer
;
3399 size_t outlen
= sizeof(out_buffer
);
3403 ret
= iconv(opt_iconv_in
, &inbuf
, &inlen
, &outbuf
, &outlen
);
3404 if (ret
!= (size_t) -1)
3408 if (!view
->ops
->read(view
, line
)) {
3409 report("Allocation failure");
3410 end_update(view
, TRUE
);
3416 unsigned long lines
= view
->lines
;
3419 for (digits
= 0; lines
; digits
++)
3422 /* Keep the displayed view in sync with line number scaling. */
3423 if (digits
!= view
->digits
) {
3424 view
->digits
= digits
;
3425 if (opt_line_number
|| view
->type
== VIEW_BLAME
)
3430 if (io_error(view
->pipe
)) {
3431 report("Failed to read: %s", io_strerror(view
->pipe
));
3432 end_update(view
, TRUE
);
3434 } else if (io_eof(view
->pipe
)) {
3435 if (view_is_displayed(view
))
3437 end_update(view
, FALSE
);
3440 if (restore_view_position(view
))
3443 if (!view_is_displayed(view
))
3447 redraw_view_from(view
, 0);
3449 redraw_view_dirty(view
);
3451 /* Update the title _after_ the redraw so that if the redraw picks up a
3452 * commit reference in view->ref it'll be available here. */
3453 update_view_title(view
);
3457 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
3459 static struct line
*
3460 add_line_data(struct view
*view
, void *data
, enum line_type type
)
3464 if (!realloc_lines(&view
->line
, view
->lines
, 1))
3467 line
= &view
->line
[view
->lines
++];
3468 memset(line
, 0, sizeof(*line
));
3476 static struct line
*
3477 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
3479 char *data
= text
? strdup(text
) : NULL
;
3481 return data
? add_line_data(view
, data
, type
) : NULL
;
3484 static struct line
*
3485 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3487 char buf
[SIZEOF_STR
];
3490 va_start(args
, fmt
);
3491 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3495 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3503 OPEN_DEFAULT
= 0, /* Use default view switching. */
3504 OPEN_SPLIT
= 1, /* Split current view. */
3505 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3506 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3507 OPEN_PREPARED
= 32, /* Open already prepared command. */
3511 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3513 bool split
= !!(flags
& OPEN_SPLIT
);
3514 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3515 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3516 struct view
*view
= VIEW(request
);
3517 int nviews
= displayed_views();
3518 struct view
*base_view
= display
[0];
3520 if (view
== prev
&& nviews
== 1 && !reload
) {
3521 report("Already in %s view", view
->name
);
3525 if (view
->git_dir
&& !opt_git_dir
[0]) {
3526 report("The %s view is disabled in pager view", view
->name
);
3533 view
->parent
= prev
;
3534 } else if (!nomaximize
) {
3535 /* Maximize the current view. */
3536 memset(display
, 0, sizeof(display
));
3538 display
[current_view
] = view
;
3541 /* No prev signals that this is the first loaded view. */
3542 if (prev
&& view
!= prev
) {
3546 /* Resize the view when switching between split- and full-screen,
3547 * or when switching between two different full-screen views. */
3548 if (nviews
!= displayed_views() ||
3549 (nviews
== 1 && base_view
!= display
[0]))
3552 if (view
->ops
->open
) {
3554 end_update(view
, TRUE
);
3555 if (!view
->ops
->open(view
)) {
3556 report("Failed to load %s view", view
->name
);
3559 restore_view_position(view
);
3561 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3562 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3563 report("Failed to load %s view", view
->name
);
3567 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3568 /* Take the title line into account. */
3569 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3571 /* Scroll the view that was split if the current line is
3572 * outside the new limited view. */
3573 do_scroll_view(prev
, lines
);
3576 if (prev
&& view
!= prev
&& split
&& view_is_displayed(prev
)) {
3577 /* "Blur" the previous view. */
3578 update_view_title(prev
);
3581 if (view
->pipe
&& view
->lines
== 0) {
3582 /* Clear the old view and let the incremental updating refill
3585 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3587 } else if (view_is_displayed(view
)) {
3594 open_external_viewer(const char *argv
[], const char *dir
)
3596 def_prog_mode(); /* save current tty modes */
3597 endwin(); /* restore original tty modes */
3598 io_run_fg(argv
, dir
);
3599 fprintf(stderr
, "Press Enter to continue");
3602 redraw_display(TRUE
);
3606 open_mergetool(const char *file
)
3608 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3610 open_external_viewer(mergetool_argv
, opt_cdup
);
3614 open_editor(const char *file
)
3616 const char *editor_argv
[] = { "vi", file
, NULL
};
3619 editor
= getenv("GIT_EDITOR");
3620 if (!editor
&& *opt_editor
)
3621 editor
= opt_editor
;
3623 editor
= getenv("VISUAL");
3625 editor
= getenv("EDITOR");
3629 editor_argv
[0] = editor
;
3630 open_external_viewer(editor_argv
, opt_cdup
);
3634 open_run_request(enum request request
)
3636 struct run_request
*req
= get_run_request(request
);
3637 const char **argv
= NULL
;
3640 report("Unknown run request");
3644 if (format_argv(&argv
, req
->argv
, TRUE
))
3645 open_external_viewer(argv
, NULL
);
3652 * User request switch noodle
3656 view_driver(struct view
*view
, enum request request
)
3660 if (request
== REQ_NONE
)
3663 if (request
> REQ_NONE
) {
3664 open_run_request(request
);
3665 view_request(view
, REQ_REFRESH
);
3669 request
= view_request(view
, request
);
3670 if (request
== REQ_NONE
)
3676 case REQ_MOVE_PAGE_UP
:
3677 case REQ_MOVE_PAGE_DOWN
:
3678 case REQ_MOVE_FIRST_LINE
:
3679 case REQ_MOVE_LAST_LINE
:
3680 move_view(view
, request
);
3683 case REQ_SCROLL_LEFT
:
3684 case REQ_SCROLL_RIGHT
:
3685 case REQ_SCROLL_LINE_DOWN
:
3686 case REQ_SCROLL_LINE_UP
:
3687 case REQ_SCROLL_PAGE_DOWN
:
3688 case REQ_SCROLL_PAGE_UP
:
3689 scroll_view(view
, request
);
3692 case REQ_VIEW_BLAME
:
3694 report("No file chosen, press %s to open tree view",
3695 get_key(view
->keymap
, REQ_VIEW_TREE
));
3698 open_view(view
, request
, OPEN_DEFAULT
);
3703 report("No file chosen, press %s to open tree view",
3704 get_key(view
->keymap
, REQ_VIEW_TREE
));
3707 open_view(view
, request
, OPEN_DEFAULT
);
3710 case REQ_VIEW_PAGER
:
3711 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3712 report("No pager content, press %s to run command from prompt",
3713 get_key(view
->keymap
, REQ_PROMPT
));
3716 open_view(view
, request
, OPEN_DEFAULT
);
3719 case REQ_VIEW_STAGE
:
3720 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3721 report("No stage content, press %s to open the status view and choose file",
3722 get_key(view
->keymap
, REQ_VIEW_STATUS
));
3725 open_view(view
, request
, OPEN_DEFAULT
);
3728 case REQ_VIEW_STATUS
:
3729 if (opt_is_inside_work_tree
== FALSE
) {
3730 report("The status view requires a working tree");
3733 open_view(view
, request
, OPEN_DEFAULT
);
3741 case REQ_VIEW_BRANCH
:
3742 open_view(view
, request
, OPEN_DEFAULT
);
3747 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3752 view
= view
->parent
;
3753 line
= view
->lineno
;
3754 move_view(view
, request
);
3755 if (view_is_displayed(view
))
3756 update_view_title(view
);
3757 if (line
!= view
->lineno
)
3758 view_request(view
, REQ_ENTER
);
3760 move_view(view
, request
);
3766 int nviews
= displayed_views();
3767 int next_view
= (current_view
+ 1) % nviews
;
3769 if (next_view
== current_view
) {
3770 report("Only one view is displayed");
3774 current_view
= next_view
;
3775 /* Blur out the title of the previous view. */
3776 update_view_title(view
);
3781 report("Refreshing is not yet supported for the %s view", view
->name
);
3785 if (displayed_views() == 2)
3786 maximize_view(view
);
3793 case REQ_TOGGLE_LINENO
:
3794 toggle_view_option(&opt_line_number
, "line numbers");
3797 case REQ_TOGGLE_DATE
:
3801 case REQ_TOGGLE_AUTHOR
:
3805 case REQ_TOGGLE_REV_GRAPH
:
3806 toggle_view_option(&opt_rev_graph
, "revision graph display");
3809 case REQ_TOGGLE_REFS
:
3810 toggle_view_option(&opt_show_refs
, "reference display");
3813 case REQ_TOGGLE_SORT_FIELD
:
3814 case REQ_TOGGLE_SORT_ORDER
:
3815 report("Sorting is not yet supported for the %s view", view
->name
);
3819 case REQ_SEARCH_BACK
:
3820 search_view(view
, request
);
3825 find_next(view
, request
);
3828 case REQ_STOP_LOADING
:
3829 foreach_view(view
, i
) {
3831 report("Stopped loading the %s view", view
->name
),
3832 end_update(view
, TRUE
);
3836 case REQ_SHOW_VERSION
:
3837 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3840 case REQ_SCREEN_REDRAW
:
3841 redraw_display(TRUE
);
3845 report("Nothing to edit");
3849 report("Nothing to enter");
3852 case REQ_VIEW_CLOSE
:
3853 /* XXX: Mark closed views by letting view->prev point to the
3854 * view itself. Parents to closed view should never be
3856 if (view
->prev
&& view
->prev
!= view
) {
3857 maximize_view(view
->prev
);
3866 report("Unknown key, press %s for help",
3867 get_key(view
->keymap
, REQ_VIEW_HELP
));
3876 * View backend utilities
3886 const enum sort_field
*fields
;
3887 size_t size
, current
;
3891 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3892 #define get_sort_field(state) ((state).fields[(state).current])
3893 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3896 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3897 int (*compare
)(const void *, const void *))
3900 case REQ_TOGGLE_SORT_FIELD
:
3901 state
->current
= (state
->current
+ 1) % state
->size
;
3904 case REQ_TOGGLE_SORT_ORDER
:
3905 state
->reverse
= !state
->reverse
;
3908 die("Not a sort request");
3911 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3915 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3917 /* Small author cache to reduce memory consumption. It uses binary
3918 * search to lookup or find place to position new entries. No entries
3919 * are ever freed. */
3921 get_author(const char *name
)
3923 static const char **authors
;
3924 static size_t authors_size
;
3925 int from
= 0, to
= authors_size
- 1;
3927 while (from
<= to
) {
3928 size_t pos
= (to
+ from
) / 2;
3929 int cmp
= strcmp(name
, authors
[pos
]);
3932 return authors
[pos
];
3940 if (!realloc_authors(&authors
, authors_size
, 1))
3942 name
= strdup(name
);
3946 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3947 authors
[from
] = name
;
3954 parse_timesec(struct time
*time
, const char *sec
)
3956 time
->sec
= (time_t) atol(sec
);
3960 parse_timezone(struct time
*time
, const char *zone
)
3964 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3965 tz
+= ('0' - zone
[2]) * 60 * 60;
3966 tz
+= ('0' - zone
[3]) * 60 * 10;
3967 tz
+= ('0' - zone
[4]) * 60;
3976 /* Parse author lines where the name may be empty:
3977 * author <email@address.tld> 1138474660 +0100
3980 parse_author_line(char *ident
, const char **author
, struct time
*time
)
3982 char *nameend
= strchr(ident
, '<');
3983 char *emailend
= strchr(ident
, '>');
3985 if (nameend
&& emailend
)
3986 *nameend
= *emailend
= 0;
3987 ident
= chomp_string(ident
);
3990 ident
= chomp_string(nameend
+ 1);
3995 *author
= get_author(ident
);
3997 /* Parse epoch and timezone */
3998 if (emailend
&& emailend
[1] == ' ') {
3999 char *secs
= emailend
+ 2;
4000 char *zone
= strchr(secs
, ' ');
4002 parse_timesec(time
, secs
);
4004 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
4005 parse_timezone(time
, zone
+ 1);
4014 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4016 if (opt_line_number
&& draw_lineno(view
, lineno
))
4019 draw_text(view
, line
->type
, line
->data
, TRUE
);
4024 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
4026 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
4027 char ref
[SIZEOF_STR
];
4029 if (!io_run_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
4032 /* This is the only fatal call, since it can "corrupt" the buffer. */
4033 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
4040 add_pager_refs(struct view
*view
, struct line
*line
)
4042 char buf
[SIZEOF_STR
];
4043 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
4044 struct ref_list
*list
;
4045 size_t bufpos
= 0, i
;
4046 const char *sep
= "Refs: ";
4047 bool is_tag
= FALSE
;
4049 assert(line
->type
== LINE_COMMIT
);
4051 list
= get_ref_list(commit_id
);
4053 if (view
->type
== VIEW_DIFF
)
4054 goto try_add_describe_ref
;
4058 for (i
= 0; i
< list
->size
; i
++) {
4059 struct ref
*ref
= list
->refs
[i
];
4060 const char *fmt
= ref
->tag
? "%s[%s]" :
4061 ref
->remote
? "%s<%s>" : "%s%s";
4063 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
4070 if (!is_tag
&& view
->type
== VIEW_DIFF
) {
4071 try_add_describe_ref
:
4072 /* Add <tag>-g<commit_id> "fake" reference. */
4073 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
4080 add_line_text(view
, buf
, LINE_PP_REFS
);
4084 pager_read(struct view
*view
, char *data
)
4091 line
= add_line_text(view
, data
, get_line_type(data
));
4095 if (line
->type
== LINE_COMMIT
&&
4096 (view
->type
== VIEW_DIFF
||
4097 view
->type
== VIEW_LOG
))
4098 add_pager_refs(view
, line
);
4104 pager_request(struct view
*view
, enum request request
, struct line
*line
)
4108 if (request
!= REQ_ENTER
)
4111 if (line
->type
== LINE_COMMIT
&&
4112 (view
->type
== VIEW_LOG
||
4113 view
->type
== VIEW_PAGER
)) {
4114 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
4118 /* Always scroll the view even if it was split. That way
4119 * you can use Enter to scroll through the log view and
4120 * split open each commit diff. */
4121 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
4123 /* FIXME: A minor workaround. Scrolling the view will call report("")
4124 * but if we are scrolling a non-current view this won't properly
4125 * update the view title. */
4127 update_view_title(view
);
4133 pager_grep(struct view
*view
, struct line
*line
)
4135 const char *text
[] = { line
->data
, NULL
};
4137 return grep_text(view
, text
);
4141 pager_select(struct view
*view
, struct line
*line
)
4143 if (line
->type
== LINE_COMMIT
) {
4144 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
4146 if (view
->type
!= VIEW_PAGER
)
4147 string_copy_rev(view
->ref
, text
);
4148 string_copy_rev(ref_commit
, text
);
4152 static struct view_ops pager_ops
= {
4163 static const char *log_argv
[SIZEOF_ARG
] = {
4164 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4168 log_request(struct view
*view
, enum request request
, struct line
*line
)
4173 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
4176 return pager_request(view
, request
, line
);
4180 static struct view_ops log_ops
= {
4191 static const char *diff_argv
[SIZEOF_ARG
] = {
4192 "git", "show", "--pretty=fuller", "--no-color", "--root",
4193 "--patch-with-stat", "--find-copies-harder", "-C",
4194 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4197 static struct view_ops diff_ops
= {
4212 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
4215 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
4219 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
4220 help_keymap_hidden
[keymap
] ? '+' : '-',
4221 enum_name(keymap_table
[keymap
]));
4223 line
->other
= keymap
;
4225 return help_keymap_hidden
[keymap
];
4229 help_open_keymap(struct view
*view
, enum keymap keymap
)
4231 const char *group
= NULL
;
4232 char buf
[SIZEOF_STR
];
4234 bool add_title
= TRUE
;
4237 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
4238 const char *key
= NULL
;
4240 if (req_info
[i
].request
== REQ_NONE
)
4243 if (!req_info
[i
].request
) {
4244 group
= req_info
[i
].help
;
4248 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4252 if (add_title
&& help_open_keymap_title(view
, keymap
))
4257 add_line_text(view
, group
, LINE_HELP_GROUP
);
4261 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4262 enum_name(req_info
[i
]), req_info
[i
].help
);
4265 group
= "External commands:";
4267 for (i
= 0; i
< run_requests
; i
++) {
4268 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4272 if (!req
|| req
->keymap
!= keymap
)
4275 key
= get_key_name(req
->key
);
4277 key
= "(no key defined)";
4279 if (add_title
&& help_open_keymap_title(view
, keymap
))
4282 add_line_text(view
, group
, LINE_HELP_GROUP
);
4286 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4287 if (!string_format_from(buf
, &bufpos
, "%s%s",
4288 argc
? " " : "", req
->argv
[argc
]))
4291 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4296 help_open(struct view
*view
)
4301 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4302 add_line_text(view
, "", LINE_DEFAULT
);
4304 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4305 help_open_keymap(view
, keymap
);
4311 help_request(struct view
*view
, enum request request
, struct line
*line
)
4315 if (line
->type
== LINE_HELP_KEYMAP
) {
4316 help_keymap_hidden
[line
->other
] =
4317 !help_keymap_hidden
[line
->other
];
4318 view
->p_restore
= TRUE
;
4319 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4324 return pager_request(view
, request
, line
);
4328 static struct view_ops help_ops
= {
4344 struct tree_stack_entry
{
4345 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4346 unsigned long lineno
; /* Line number to restore */
4347 char *name
; /* Position of name in opt_path */
4350 /* The top of the path stack. */
4351 static struct tree_stack_entry
*tree_stack
= NULL
;
4352 unsigned long tree_lineno
= 0;
4355 pop_tree_stack_entry(void)
4357 struct tree_stack_entry
*entry
= tree_stack
;
4359 tree_lineno
= entry
->lineno
;
4361 tree_stack
= entry
->prev
;
4366 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4368 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4369 size_t pathlen
= strlen(opt_path
);
4374 entry
->prev
= tree_stack
;
4375 entry
->name
= opt_path
+ pathlen
;
4378 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4379 pop_tree_stack_entry();
4383 /* Move the current line to the first tree entry. */
4385 entry
->lineno
= lineno
;
4388 /* Parse output from git-ls-tree(1):
4390 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4393 #define SIZEOF_TREE_ATTR \
4394 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4396 #define SIZEOF_TREE_MODE \
4397 STRING_SIZE("100644 ")
4399 #define TREE_ID_OFFSET \
4400 STRING_SIZE("100644 blob ")
4403 char id
[SIZEOF_REV
];
4405 struct time time
; /* Date from the author ident. */
4406 const char *author
; /* Author of the commit. */
4411 tree_path(const struct line
*line
)
4413 return ((struct tree_entry
*) line
->data
)->name
;
4417 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4419 if (line1
->type
!= line2
->type
)
4420 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4421 return strcmp(tree_path(line1
), tree_path(line2
));
4424 static const enum sort_field tree_sort_fields
[] = {
4425 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4427 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4430 tree_compare(const void *l1
, const void *l2
)
4432 const struct line
*line1
= (const struct line
*) l1
;
4433 const struct line
*line2
= (const struct line
*) l2
;
4434 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4435 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4437 if (line1
->type
== LINE_TREE_HEAD
)
4439 if (line2
->type
== LINE_TREE_HEAD
)
4442 switch (get_sort_field(tree_sort_state
)) {
4444 return sort_order(tree_sort_state
, timecmp(&entry1
->time
, &entry2
->time
));
4446 case ORDERBY_AUTHOR
:
4447 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4451 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4456 static struct line
*
4457 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4458 const char *mode
, const char *id
)
4460 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4461 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4463 if (!entry
|| !line
) {
4468 strncpy(entry
->name
, path
, strlen(path
));
4470 entry
->mode
= strtoul(mode
, NULL
, 8);
4472 string_copy_rev(entry
->id
, id
);
4478 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4480 static const char *author_name
;
4481 static struct time author_time
;
4483 if (!text
&& *read_date
) {
4488 char *path
= *opt_path
? opt_path
: ".";
4489 /* Find next entry to process */
4490 const char *log_file
[] = {
4491 "git", "log", "--no-color", "--pretty=raw",
4492 "--cc", "--raw", view
->id
, "--", path
, NULL
4496 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4497 report("Tree is empty");
4501 if (!start_update(view
, log_file
, opt_cdup
)) {
4502 report("Failed to load tree data");
4509 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4510 parse_author_line(text
+ STRING_SIZE("author "),
4511 &author_name
, &author_time
);
4513 } else if (*text
== ':') {
4515 size_t annotated
= 1;
4518 pos
= strchr(text
, '\t');
4522 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4523 text
+= strlen(opt_path
);
4524 pos
= strchr(text
, '/');
4528 for (i
= 1; i
< view
->lines
; i
++) {
4529 struct line
*line
= &view
->line
[i
];
4530 struct tree_entry
*entry
= line
->data
;
4532 annotated
+= !!entry
->author
;
4533 if (entry
->author
|| strcmp(entry
->name
, text
))
4536 entry
->author
= author_name
;
4537 entry
->time
= author_time
;
4542 if (annotated
== view
->lines
)
4543 io_kill(view
->pipe
);
4549 tree_read(struct view
*view
, char *text
)
4551 static bool read_date
= FALSE
;
4552 struct tree_entry
*data
;
4553 struct line
*entry
, *line
;
4554 enum line_type type
;
4555 size_t textlen
= text
? strlen(text
) : 0;
4556 char *path
= text
+ SIZEOF_TREE_ATTR
;
4558 if (read_date
|| !text
)
4559 return tree_read_date(view
, text
, &read_date
);
4561 if (textlen
<= SIZEOF_TREE_ATTR
)
4563 if (view
->lines
== 0 &&
4564 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4567 /* Strip the path part ... */
4569 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4570 size_t striplen
= strlen(opt_path
);
4572 if (pathlen
> striplen
)
4573 memmove(path
, path
+ striplen
,
4574 pathlen
- striplen
+ 1);
4576 /* Insert "link" to parent directory. */
4577 if (view
->lines
== 1 &&
4578 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4582 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4583 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4588 /* Skip "Directory ..." and ".." line. */
4589 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4590 if (tree_compare_entry(line
, entry
) <= 0)
4593 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4597 for (; line
<= entry
; line
++)
4598 line
->dirty
= line
->cleareol
= 1;
4602 if (tree_lineno
> view
->lineno
) {
4603 view
->lineno
= tree_lineno
;
4611 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4613 struct tree_entry
*entry
= line
->data
;
4615 if (line
->type
== LINE_TREE_HEAD
) {
4616 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4619 if (draw_mode(view
, entry
->mode
))
4622 if (opt_author
&& draw_author(view
, entry
->author
))
4625 if (opt_date
&& draw_date(view
, &entry
->time
))
4628 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4634 open_blob_editor(const char *id
)
4636 const char *blob_argv
[] = { "git", "cat-file", "blob", id
, NULL
};
4637 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4638 int fd
= mkstemp(file
);
4641 report("Failed to create temporary file");
4642 else if (!io_run_append(blob_argv
, fd
))
4643 report("Failed to save blob data to file");
4651 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4653 enum open_flags flags
;
4654 struct tree_entry
*entry
= line
->data
;
4657 case REQ_VIEW_BLAME
:
4658 if (line
->type
!= LINE_TREE_FILE
) {
4659 report("Blame only supported for files");
4663 string_copy(opt_ref
, view
->vid
);
4667 if (line
->type
!= LINE_TREE_FILE
) {
4668 report("Edit only supported for files");
4669 } else if (!is_head_commit(view
->vid
)) {
4670 open_blob_editor(entry
->id
);
4672 open_editor(opt_file
);
4676 case REQ_TOGGLE_SORT_FIELD
:
4677 case REQ_TOGGLE_SORT_ORDER
:
4678 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4683 /* quit view if at top of tree */
4684 return REQ_VIEW_CLOSE
;
4687 line
= &view
->line
[1];
4697 /* Cleanup the stack if the tree view is at a different tree. */
4698 while (!*opt_path
&& tree_stack
)
4699 pop_tree_stack_entry();
4701 switch (line
->type
) {
4703 /* Depending on whether it is a subdirectory or parent link
4704 * mangle the path buffer. */
4705 if (line
== &view
->line
[1] && *opt_path
) {
4706 pop_tree_stack_entry();
4709 const char *basename
= tree_path(line
);
4711 push_tree_stack_entry(basename
, view
->lineno
);
4714 /* Trees and subtrees share the same ID, so they are not not
4715 * unique like blobs. */
4716 flags
= OPEN_RELOAD
;
4717 request
= REQ_VIEW_TREE
;
4720 case LINE_TREE_FILE
:
4721 flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
4722 request
= REQ_VIEW_BLOB
;
4729 open_view(view
, request
, flags
);
4730 if (request
== REQ_VIEW_TREE
)
4731 view
->lineno
= tree_lineno
;
4737 tree_grep(struct view
*view
, struct line
*line
)
4739 struct tree_entry
*entry
= line
->data
;
4740 const char *text
[] = {
4742 opt_author
? entry
->author
: "",
4743 mkdate(&entry
->time
, opt_date
),
4747 return grep_text(view
, text
);
4751 tree_select(struct view
*view
, struct line
*line
)
4753 struct tree_entry
*entry
= line
->data
;
4755 if (line
->type
== LINE_TREE_FILE
) {
4756 string_copy_rev(ref_blob
, entry
->id
);
4757 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4759 } else if (line
->type
!= LINE_TREE_DIR
) {
4763 string_copy_rev(view
->ref
, entry
->id
);
4767 tree_prepare(struct view
*view
)
4769 if (view
->lines
== 0 && opt_prefix
[0]) {
4770 char *pos
= opt_prefix
;
4772 while (pos
&& *pos
) {
4773 char *end
= strchr(pos
, '/');
4777 push_tree_stack_entry(pos
, 0);
4785 } else if (strcmp(view
->vid
, view
->id
)) {
4789 return prepare_io(view
, opt_cdup
, view
->ops
->argv
, TRUE
);
4792 static const char *tree_argv
[SIZEOF_ARG
] = {
4793 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4796 static struct view_ops tree_ops
= {
4809 blob_read(struct view
*view
, char *line
)
4813 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4817 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4821 open_blob_editor(view
->vid
);
4824 return pager_request(view
, request
, line
);
4828 static const char *blob_argv
[SIZEOF_ARG
] = {
4829 "git", "cat-file", "blob", "%(blob)", NULL
4832 static struct view_ops blob_ops
= {
4846 * Loading the blame view is a two phase job:
4848 * 1. File content is read either using opt_file from the
4849 * filesystem or using git-cat-file.
4850 * 2. Then blame information is incrementally added by
4851 * reading output from git-blame.
4854 struct blame_commit
{
4855 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4856 char title
[128]; /* First line of the commit message. */
4857 const char *author
; /* Author of the commit. */
4858 struct time time
; /* Date from the author ident. */
4859 char filename
[128]; /* Name of file. */
4860 char parent_id
[SIZEOF_REV
]; /* Parent/previous SHA1 ID. */
4861 char parent_filename
[128]; /* Parent/previous name of file. */
4865 struct blame_commit
*commit
;
4866 unsigned long lineno
;
4871 blame_open(struct view
*view
)
4873 char path
[SIZEOF_STR
];
4876 if (!view
->prev
&& *opt_prefix
) {
4877 string_copy(path
, opt_file
);
4878 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4882 if (*opt_ref
|| !io_open(&view
->io
, "%s%s", opt_cdup
, opt_file
)) {
4883 const char *blame_cat_file_argv
[] = {
4884 "git", "cat-file", "blob", path
, NULL
4887 if (!string_format(path
, "%s:%s", opt_ref
, opt_file
) ||
4888 !start_update(view
, blame_cat_file_argv
, opt_cdup
))
4892 /* First pass: remove multiple references to the same commit. */
4893 for (i
= 0; i
< view
->lines
; i
++) {
4894 struct blame
*blame
= view
->line
[i
].data
;
4896 if (blame
->commit
&& blame
->commit
->id
[0])
4897 blame
->commit
->id
[0] = 0;
4899 blame
->commit
= NULL
;
4902 /* Second pass: free existing references. */
4903 for (i
= 0; i
< view
->lines
; i
++) {
4904 struct blame
*blame
= view
->line
[i
].data
;
4907 free(blame
->commit
);
4910 setup_update(view
, opt_file
);
4911 string_format(view
->ref
, "%s ...", opt_file
);
4916 static struct blame_commit
*
4917 get_blame_commit(struct view
*view
, const char *id
)
4921 for (i
= 0; i
< view
->lines
; i
++) {
4922 struct blame
*blame
= view
->line
[i
].data
;
4927 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4928 return blame
->commit
;
4932 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4935 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4941 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4943 const char *pos
= *posref
;
4946 pos
= strchr(pos
+ 1, ' ');
4947 if (!pos
|| !isdigit(pos
[1]))
4949 *number
= atoi(pos
+ 1);
4950 if (*number
< min
|| *number
> max
)
4957 static struct blame_commit
*
4958 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4960 struct blame_commit
*commit
;
4961 struct blame
*blame
;
4962 const char *pos
= text
+ SIZEOF_REV
- 2;
4963 size_t orig_lineno
= 0;
4967 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4970 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4971 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4972 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4975 commit
= get_blame_commit(view
, text
);
4981 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4984 blame
->commit
= commit
;
4985 blame
->lineno
= orig_lineno
+ group
- 1;
4993 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4996 const char *blame_argv
[] = {
4997 "git", "blame", "--incremental",
4998 *opt_ref
? opt_ref
: "--incremental", "--", opt_file
, NULL
5001 if (view
->lines
== 0 && !view
->prev
)
5002 die("No blame exist for %s", view
->vid
);
5004 if (view
->lines
== 0 || !start_update(view
, blame_argv
, opt_cdup
)) {
5005 report("Failed to load blame data");
5013 size_t linelen
= strlen(line
);
5014 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
5019 blame
->commit
= NULL
;
5020 strncpy(blame
->text
, line
, linelen
);
5021 blame
->text
[linelen
] = 0;
5022 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
5027 match_blame_header(const char *name
, char **line
)
5029 size_t namelen
= strlen(name
);
5030 bool matched
= !strncmp(name
, *line
, namelen
);
5039 blame_read(struct view
*view
, char *line
)
5041 static struct blame_commit
*commit
= NULL
;
5042 static int blamed
= 0;
5043 static bool read_file
= TRUE
;
5046 return blame_read_file(view
, line
, &read_file
);
5053 string_format(view
->ref
, "%s", view
->vid
);
5054 if (view_is_displayed(view
)) {
5055 update_view_title(view
);
5056 redraw_view_from(view
, 0);
5062 commit
= parse_blame_commit(view
, line
, &blamed
);
5063 string_format(view
->ref
, "%s %2d%%", view
->vid
,
5064 view
->lines
? blamed
* 100 / view
->lines
: 0);
5066 } else if (match_blame_header("author ", &line
)) {
5067 commit
->author
= get_author(line
);
5069 } else if (match_blame_header("author-time ", &line
)) {
5070 parse_timesec(&commit
->time
, line
);
5072 } else if (match_blame_header("author-tz ", &line
)) {
5073 parse_timezone(&commit
->time
, line
);
5075 } else if (match_blame_header("summary ", &line
)) {
5076 string_ncopy(commit
->title
, line
, strlen(line
));
5078 } else if (match_blame_header("previous ", &line
)) {
5079 if (strlen(line
) <= SIZEOF_REV
)
5081 string_copy_rev(commit
->parent_id
, line
);
5083 string_ncopy(commit
->parent_filename
, line
, strlen(line
));
5085 } else if (match_blame_header("filename ", &line
)) {
5086 string_ncopy(commit
->filename
, line
, strlen(line
));
5094 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5096 struct blame
*blame
= line
->data
;
5097 struct time
*time
= NULL
;
5098 const char *id
= NULL
, *author
= NULL
;
5100 if (blame
->commit
&& *blame
->commit
->filename
) {
5101 id
= blame
->commit
->id
;
5102 author
= blame
->commit
->author
;
5103 time
= &blame
->commit
->time
;
5106 if (opt_date
&& draw_date(view
, time
))
5109 if (opt_author
&& draw_author(view
, author
))
5112 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
5115 if (draw_lineno(view
, lineno
))
5118 draw_text(view
, LINE_DEFAULT
, blame
->text
, TRUE
);
5123 check_blame_commit(struct blame
*blame
, bool check_null_id
)
5126 report("Commit data not loaded yet");
5127 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5128 report("No commit exist for the selected line");
5135 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
5137 char from
[SIZEOF_REF
+ SIZEOF_STR
];
5138 char to
[SIZEOF_REF
+ SIZEOF_STR
];
5139 const char *diff_tree_argv
[] = {
5140 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5141 "-U0", from
, to
, "--", NULL
5144 int parent_lineno
= -1;
5145 int blamed_lineno
= -1;
5148 if (!string_format(from
, "%s:%s", opt_ref
, opt_file
) ||
5149 !string_format(to
, "%s:%s", blame
->commit
->id
, blame
->commit
->filename
) ||
5150 !io_run(&io
, IO_RD
, NULL
, diff_tree_argv
))
5153 while ((line
= io_get(&io
, '\n', TRUE
))) {
5155 char *pos
= strchr(line
, '+');
5157 parent_lineno
= atoi(line
+ 4);
5159 blamed_lineno
= atoi(pos
+ 1);
5161 } else if (*line
== '+' && parent_lineno
!= -1) {
5162 if (blame
->lineno
== blamed_lineno
- 1 &&
5163 !strcmp(blame
->text
, line
+ 1)) {
5164 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
5175 blame_request(struct view
*view
, enum request request
, struct line
*line
)
5177 enum open_flags flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
5178 struct blame
*blame
= line
->data
;
5181 case REQ_VIEW_BLAME
:
5182 if (check_blame_commit(blame
, TRUE
)) {
5183 string_copy(opt_ref
, blame
->commit
->id
);
5184 string_copy(opt_file
, blame
->commit
->filename
);
5186 view
->lineno
= blame
->lineno
;
5187 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5192 if (!check_blame_commit(blame
, TRUE
))
5194 if (!*blame
->commit
->parent_id
) {
5195 report("The selected commit has no parents");
5197 string_copy_rev(opt_ref
, blame
->commit
->parent_id
);
5198 string_copy(opt_file
, blame
->commit
->parent_filename
);
5199 setup_blame_parent_line(view
, blame
);
5200 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5205 if (!check_blame_commit(blame
, FALSE
))
5208 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
5209 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
5212 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
5213 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
5214 const char *diff_index_argv
[] = {
5215 "git", "diff-index", "--root", "--patch-with-stat",
5216 "-C", "-M", "HEAD", "--", view
->vid
, NULL
5219 if (!*blame
->commit
->parent_id
) {
5220 diff_index_argv
[1] = "diff";
5221 diff_index_argv
[2] = "--no-color";
5222 diff_index_argv
[6] = "--";
5223 diff_index_argv
[7] = "/dev/null";
5226 if (!prepare_update(diff
, diff_index_argv
, NULL
)) {
5227 report("Failed to allocate diff command");
5230 flags
|= OPEN_PREPARED
;
5233 open_view(view
, REQ_VIEW_DIFF
, flags
);
5234 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5235 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
5246 blame_grep(struct view
*view
, struct line
*line
)
5248 struct blame
*blame
= line
->data
;
5249 struct blame_commit
*commit
= blame
->commit
;
5250 const char *text
[] = {
5252 commit
? commit
->title
: "",
5253 commit
? commit
->id
: "",
5254 commit
&& opt_author
? commit
->author
: "",
5255 commit
? mkdate(&commit
->time
, opt_date
) : "",
5259 return grep_text(view
, text
);
5263 blame_select(struct view
*view
, struct line
*line
)
5265 struct blame
*blame
= line
->data
;
5266 struct blame_commit
*commit
= blame
->commit
;
5271 if (!strcmp(commit
->id
, NULL_ID
))
5272 string_ncopy(ref_commit
, "HEAD", 4);
5274 string_copy_rev(ref_commit
, commit
->id
);
5277 static struct view_ops blame_ops
= {
5293 const char *author
; /* Author of the last commit. */
5294 struct time time
; /* Date of the last activity. */
5295 const struct ref
*ref
; /* Name and commit ID information. */
5298 static const struct ref branch_all
;
5300 static const enum sort_field branch_sort_fields
[] = {
5301 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5303 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5306 branch_compare(const void *l1
, const void *l2
)
5308 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5309 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5311 switch (get_sort_field(branch_sort_state
)) {
5313 return sort_order(branch_sort_state
, timecmp(&branch1
->time
, &branch2
->time
));
5315 case ORDERBY_AUTHOR
:
5316 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5320 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5325 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5327 struct branch
*branch
= line
->data
;
5328 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5330 if (opt_date
&& draw_date(view
, &branch
->time
))
5333 if (opt_author
&& draw_author(view
, branch
->author
))
5336 draw_text(view
, type
, branch
->ref
== &branch_all
? "All branches" : branch
->ref
->name
, TRUE
);
5341 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5343 struct branch
*branch
= line
->data
;
5348 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5351 case REQ_TOGGLE_SORT_FIELD
:
5352 case REQ_TOGGLE_SORT_ORDER
:
5353 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5358 const struct ref
*ref
= branch
->ref
;
5359 const char *all_branches_argv
[] = {
5360 "git", "log", "--no-color", "--pretty=raw", "--parents",
5362 ref
== &branch_all
? "--all" : ref
->name
, NULL
5364 struct view
*main_view
= VIEW(REQ_VIEW_MAIN
);
5366 if (!prepare_update(main_view
, all_branches_argv
, NULL
))
5367 report("Failed to load view of all branches");
5369 open_view(view
, REQ_VIEW_MAIN
, OPEN_PREPARED
| OPEN_SPLIT
);
5378 branch_read(struct view
*view
, char *line
)
5380 static char id
[SIZEOF_REV
];
5381 struct branch
*reference
;
5387 switch (get_line_type(line
)) {
5389 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5393 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5394 struct branch
*branch
= view
->line
[i
].data
;
5396 if (strcmp(branch
->ref
->id
, id
))
5399 view
->line
[i
].dirty
= TRUE
;
5401 branch
->author
= reference
->author
;
5402 branch
->time
= reference
->time
;
5406 parse_author_line(line
+ STRING_SIZE("author "),
5407 &branch
->author
, &branch
->time
);
5419 branch_open_visitor(void *data
, const struct ref
*ref
)
5421 struct view
*view
= data
;
5422 struct branch
*branch
;
5424 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5427 branch
= calloc(1, sizeof(*branch
));
5432 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5436 branch_open(struct view
*view
)
5438 const char *branch_log
[] = {
5439 "git", "log", "--no-color", "--pretty=raw",
5440 "--simplify-by-decoration", "--all", NULL
5443 if (!start_update(view
, branch_log
, NULL
)) {
5444 report("Failed to load branch data");
5448 setup_update(view
, view
->id
);
5449 branch_open_visitor(view
, &branch_all
);
5450 foreach_ref(branch_open_visitor
, view
);
5451 view
->p_restore
= TRUE
;
5457 branch_grep(struct view
*view
, struct line
*line
)
5459 struct branch
*branch
= line
->data
;
5460 const char *text
[] = {
5466 return grep_text(view
, text
);
5470 branch_select(struct view
*view
, struct line
*line
)
5472 struct branch
*branch
= line
->data
;
5474 string_copy_rev(view
->ref
, branch
->ref
->id
);
5475 string_copy_rev(ref_commit
, branch
->ref
->id
);
5476 string_copy_rev(ref_head
, branch
->ref
->id
);
5477 string_copy_rev(ref_branch
, branch
->ref
->name
);
5480 static struct view_ops branch_ops
= {
5499 char rev
[SIZEOF_REV
];
5500 char name
[SIZEOF_STR
];
5504 char rev
[SIZEOF_REV
];
5505 char name
[SIZEOF_STR
];
5509 static char status_onbranch
[SIZEOF_STR
];
5510 static struct status stage_status
;
5511 static enum line_type stage_line_type
;
5512 static size_t stage_chunks
;
5513 static int *stage_chunk
;
5515 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5517 /* This should work even for the "On branch" line. */
5519 status_has_none(struct view
*view
, struct line
*line
)
5521 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5524 /* Get fields from the diff line:
5525 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5528 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5530 const char *old_mode
= buf
+ 1;
5531 const char *new_mode
= buf
+ 8;
5532 const char *old_rev
= buf
+ 15;
5533 const char *new_rev
= buf
+ 56;
5534 const char *status
= buf
+ 97;
5537 old_mode
[-1] != ':' ||
5538 new_mode
[-1] != ' ' ||
5539 old_rev
[-1] != ' ' ||
5540 new_rev
[-1] != ' ' ||
5544 file
->status
= *status
;
5546 string_copy_rev(file
->old
.rev
, old_rev
);
5547 string_copy_rev(file
->new.rev
, new_rev
);
5549 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5550 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5552 file
->old
.name
[0] = file
->new.name
[0] = 0;
5558 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5560 struct status
*unmerged
= NULL
;
5564 if (!io_run(&io
, IO_RD
, opt_cdup
, argv
))
5567 add_line_data(view
, NULL
, type
);
5569 while ((buf
= io_get(&io
, 0, TRUE
))) {
5570 struct status
*file
= unmerged
;
5573 file
= calloc(1, sizeof(*file
));
5574 if (!file
|| !add_line_data(view
, file
, type
))
5578 /* Parse diff info part. */
5580 file
->status
= status
;
5582 string_copy(file
->old
.rev
, NULL_ID
);
5584 } else if (!file
->status
|| file
== unmerged
) {
5585 if (!status_get_diff(file
, buf
, strlen(buf
)))
5588 buf
= io_get(&io
, 0, TRUE
);
5592 /* Collapse all modified entries that follow an
5593 * associated unmerged entry. */
5594 if (unmerged
== file
) {
5595 unmerged
->status
= 'U';
5597 } else if (file
->status
== 'U') {
5602 /* Grab the old name for rename/copy. */
5603 if (!*file
->old
.name
&&
5604 (file
->status
== 'R' || file
->status
== 'C')) {
5605 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5607 buf
= io_get(&io
, 0, TRUE
);
5612 /* git-ls-files just delivers a NUL separated list of
5613 * file names similar to the second half of the
5614 * git-diff-* output. */
5615 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5616 if (!*file
->old
.name
)
5617 string_copy(file
->old
.name
, file
->new.name
);
5621 if (io_error(&io
)) {
5627 if (!view
->line
[view
->lines
- 1].data
)
5628 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5634 /* Don't show unmerged entries in the staged section. */
5635 static const char *status_diff_index_argv
[] = {
5636 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5637 "--cached", "-M", "HEAD", NULL
5640 static const char *status_diff_files_argv
[] = {
5641 "git", "diff-files", "-z", NULL
5644 static const char *status_list_other_argv
[] = {
5645 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix
, NULL
5648 static const char *status_list_no_head_argv
[] = {
5649 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5652 static const char *update_index_argv
[] = {
5653 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5656 /* Restore the previous line number to stay in the context or select a
5657 * line with something that can be updated. */
5659 status_restore(struct view
*view
)
5661 if (view
->p_lineno
>= view
->lines
)
5662 view
->p_lineno
= view
->lines
- 1;
5663 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5665 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5668 /* If the above fails, always skip the "On branch" line. */
5669 if (view
->p_lineno
< view
->lines
)
5670 view
->lineno
= view
->p_lineno
;
5674 if (view
->lineno
< view
->offset
)
5675 view
->offset
= view
->lineno
;
5676 else if (view
->offset
+ view
->height
<= view
->lineno
)
5677 view
->offset
= view
->lineno
- view
->height
+ 1;
5679 view
->p_restore
= FALSE
;
5683 status_update_onbranch(void)
5685 static const char *paths
[][2] = {
5686 { "rebase-apply/rebasing", "Rebasing" },
5687 { "rebase-apply/applying", "Applying mailbox" },
5688 { "rebase-apply/", "Rebasing mailbox" },
5689 { "rebase-merge/interactive", "Interactive rebase" },
5690 { "rebase-merge/", "Rebase merge" },
5691 { "MERGE_HEAD", "Merging" },
5692 { "BISECT_LOG", "Bisecting" },
5693 { "HEAD", "On branch" },
5695 char buf
[SIZEOF_STR
];
5699 if (is_initial_commit()) {
5700 string_copy(status_onbranch
, "Initial commit");
5704 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5705 char *head
= opt_head
;
5707 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5708 lstat(buf
, &stat
) < 0)
5714 if (io_open(&io
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5715 io_read_buf(&io
, buf
, sizeof(buf
))) {
5717 if (!prefixcmp(head
, "refs/heads/"))
5718 head
+= STRING_SIZE("refs/heads/");
5722 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5723 string_copy(status_onbranch
, opt_head
);
5727 string_copy(status_onbranch
, "Not currently on any branch");
5730 /* First parse staged info using git-diff-index(1), then parse unstaged
5731 * info using git-diff-files(1), and finally untracked files using
5732 * git-ls-files(1). */
5734 status_open(struct view
*view
)
5738 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5739 status_update_onbranch();
5741 io_run_bg(update_index_argv
);
5743 if (is_initial_commit()) {
5744 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5746 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5750 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5751 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5754 /* Restore the exact position or use the specialized restore
5756 if (!view
->p_restore
)
5757 status_restore(view
);
5762 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5764 struct status
*status
= line
->data
;
5765 enum line_type type
;
5769 switch (line
->type
) {
5770 case LINE_STAT_STAGED
:
5771 type
= LINE_STAT_SECTION
;
5772 text
= "Changes to be committed:";
5775 case LINE_STAT_UNSTAGED
:
5776 type
= LINE_STAT_SECTION
;
5777 text
= "Changed but not updated:";
5780 case LINE_STAT_UNTRACKED
:
5781 type
= LINE_STAT_SECTION
;
5782 text
= "Untracked files:";
5785 case LINE_STAT_NONE
:
5786 type
= LINE_DEFAULT
;
5787 text
= " (no files)";
5790 case LINE_STAT_HEAD
:
5791 type
= LINE_STAT_HEAD
;
5792 text
= status_onbranch
;
5799 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5801 buf
[0] = status
->status
;
5802 if (draw_text(view
, line
->type
, buf
, TRUE
))
5804 type
= LINE_DEFAULT
;
5805 text
= status
->new.name
;
5808 draw_text(view
, type
, text
, TRUE
);
5813 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5815 if (displayed_views() == 2 || display
[current_view
] != view
)
5816 maximize_view(view
);
5817 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5822 status_enter(struct view
*view
, struct line
*line
)
5824 struct status
*status
= line
->data
;
5825 const char *oldpath
= status
? status
->old
.name
: NULL
;
5826 /* Diffs for unmerged entries are empty when passing the new
5827 * path, so leave it empty. */
5828 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5830 enum open_flags split
;
5831 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5833 if (line
->type
== LINE_STAT_NONE
||
5834 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5835 report("No file to diff");
5839 switch (line
->type
) {
5840 case LINE_STAT_STAGED
:
5841 if (is_initial_commit()) {
5842 const char *no_head_diff_argv
[] = {
5843 "git", "diff", "--no-color", "--patch-with-stat",
5844 "--", "/dev/null", newpath
, NULL
5847 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
))
5848 return status_load_error(view
, stage
, newpath
);
5850 const char *index_show_argv
[] = {
5851 "git", "diff-index", "--root", "--patch-with-stat",
5852 "-C", "-M", "--cached", "HEAD", "--",
5853 oldpath
, newpath
, NULL
5856 if (!prepare_update(stage
, index_show_argv
, opt_cdup
))
5857 return status_load_error(view
, stage
, newpath
);
5861 info
= "Staged changes to %s";
5863 info
= "Staged changes";
5866 case LINE_STAT_UNSTAGED
:
5868 const char *files_show_argv
[] = {
5869 "git", "diff-files", "--root", "--patch-with-stat",
5870 "-C", "-M", "--", oldpath
, newpath
, NULL
5873 if (!prepare_update(stage
, files_show_argv
, opt_cdup
))
5874 return status_load_error(view
, stage
, newpath
);
5876 info
= "Unstaged changes to %s";
5878 info
= "Unstaged changes";
5881 case LINE_STAT_UNTRACKED
:
5883 report("No file to show");
5887 if (!suffixcmp(status
->new.name
, -1, "/")) {
5888 report("Cannot display a directory");
5892 if (!prepare_update_file(stage
, newpath
))
5893 return status_load_error(view
, stage
, newpath
);
5894 info
= "Untracked file %s";
5897 case LINE_STAT_HEAD
:
5901 die("line type %d not handled in switch", line
->type
);
5904 split
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
5905 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5906 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5908 stage_status
= *status
;
5910 memset(&stage_status
, 0, sizeof(stage_status
));
5913 stage_line_type
= line
->type
;
5915 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5922 status_exists(struct status
*status
, enum line_type type
)
5924 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5925 unsigned long lineno
;
5927 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5928 struct line
*line
= &view
->line
[lineno
];
5929 struct status
*pos
= line
->data
;
5931 if (line
->type
!= type
)
5933 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5934 select_view_line(view
, lineno
);
5937 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5938 select_view_line(view
, lineno
);
5948 status_update_prepare(struct io
*io
, enum line_type type
)
5950 const char *staged_argv
[] = {
5951 "git", "update-index", "-z", "--index-info", NULL
5953 const char *others_argv
[] = {
5954 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5958 case LINE_STAT_STAGED
:
5959 return io_run(io
, IO_WR
, opt_cdup
, staged_argv
);
5961 case LINE_STAT_UNSTAGED
:
5962 case LINE_STAT_UNTRACKED
:
5963 return io_run(io
, IO_WR
, opt_cdup
, others_argv
);
5966 die("line type %d not handled in switch", type
);
5972 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5974 char buf
[SIZEOF_STR
];
5978 case LINE_STAT_STAGED
:
5979 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5982 status
->old
.name
, 0))
5986 case LINE_STAT_UNSTAGED
:
5987 case LINE_STAT_UNTRACKED
:
5988 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
5993 die("line type %d not handled in switch", type
);
5996 return io_write(io
, buf
, bufsize
);
6000 status_update_file(struct status
*status
, enum line_type type
)
6005 if (!status_update_prepare(&io
, type
))
6008 result
= status_update_write(&io
, status
, type
);
6009 return io_done(&io
) && result
;
6013 status_update_files(struct view
*view
, struct line
*line
)
6015 char buf
[sizeof(view
->ref
)];
6018 struct line
*pos
= view
->line
+ view
->lines
;
6021 int cursor_y
= -1, cursor_x
= -1;
6023 if (!status_update_prepare(&io
, line
->type
))
6026 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
6029 string_copy(buf
, view
->ref
);
6030 getsyx(cursor_y
, cursor_x
);
6031 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
6032 int almost_done
= file
* 100 / files
;
6034 if (almost_done
> done
) {
6036 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
6038 update_view_title(view
);
6039 setsyx(cursor_y
, cursor_x
);
6042 result
= status_update_write(&io
, line
->data
, line
->type
);
6044 string_copy(view
->ref
, buf
);
6046 return io_done(&io
) && result
;
6050 status_update(struct view
*view
)
6052 struct line
*line
= &view
->line
[view
->lineno
];
6054 assert(view
->lines
);
6057 /* This should work even for the "On branch" line. */
6058 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
6059 report("Nothing to update");
6063 if (!status_update_files(view
, line
+ 1)) {
6064 report("Failed to update file status");
6068 } else if (!status_update_file(line
->data
, line
->type
)) {
6069 report("Failed to update file status");
6077 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
6079 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
6080 if (type
== LINE_STAT_STAGED
) {
6081 report("Cannot revert changes to staged files");
6082 } else if (type
== LINE_STAT_UNTRACKED
) {
6083 report("Cannot revert changes to untracked files");
6084 } else if (has_none
) {
6085 report("Nothing to revert");
6087 report("Cannot revert changes to multiple files");
6090 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6091 char mode
[10] = "100644";
6092 const char *reset_argv
[] = {
6093 "git", "update-index", "--cacheinfo", mode
,
6094 status
->old
.rev
, status
->old
.name
, NULL
6096 const char *checkout_argv
[] = {
6097 "git", "checkout", "--", status
->old
.name
, NULL
6100 if (status
->status
== 'U') {
6101 string_format(mode
, "%5o", status
->old
.mode
);
6103 if (status
->old
.mode
== 0 && status
->new.mode
== 0) {
6104 reset_argv
[2] = "--force-remove";
6105 reset_argv
[3] = status
->old
.name
;
6106 reset_argv
[4] = NULL
;
6109 if (!io_run_fg(reset_argv
, opt_cdup
))
6111 if (status
->old
.mode
== 0 && status
->new.mode
== 0)
6115 return io_run_fg(checkout_argv
, opt_cdup
);
6122 status_request(struct view
*view
, enum request request
, struct line
*line
)
6124 struct status
*status
= line
->data
;
6127 case REQ_STATUS_UPDATE
:
6128 if (!status_update(view
))
6132 case REQ_STATUS_REVERT
:
6133 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
6137 case REQ_STATUS_MERGE
:
6138 if (!status
|| status
->status
!= 'U') {
6139 report("Merging only possible for files with unmerged status ('U').");
6142 open_mergetool(status
->new.name
);
6148 if (status
->status
== 'D') {
6149 report("File has been deleted.");
6153 open_editor(status
->new.name
);
6156 case REQ_VIEW_BLAME
:
6162 /* After returning the status view has been split to
6163 * show the stage view. No further reloading is
6165 return status_enter(view
, line
);
6168 /* Simply reload the view. */
6175 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
6181 status_select(struct view
*view
, struct line
*line
)
6183 struct status
*status
= line
->data
;
6184 char file
[SIZEOF_STR
] = "all files";
6188 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
6191 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
6194 switch (line
->type
) {
6195 case LINE_STAT_STAGED
:
6196 text
= "Press %s to unstage %s for commit";
6199 case LINE_STAT_UNSTAGED
:
6200 text
= "Press %s to stage %s for commit";
6203 case LINE_STAT_UNTRACKED
:
6204 text
= "Press %s to stage %s for addition";
6207 case LINE_STAT_HEAD
:
6208 case LINE_STAT_NONE
:
6209 text
= "Nothing to update";
6213 die("line type %d not handled in switch", line
->type
);
6216 if (status
&& status
->status
== 'U') {
6217 text
= "Press %s to resolve conflict in %s";
6218 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
6221 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
6224 string_format(view
->ref
, text
, key
, file
);
6226 string_copy(opt_file
, status
->new.name
);
6230 status_grep(struct view
*view
, struct line
*line
)
6232 struct status
*status
= line
->data
;
6235 const char buf
[2] = { status
->status
, 0 };
6236 const char *text
[] = { status
->new.name
, buf
, NULL
};
6238 return grep_text(view
, text
);
6244 static struct view_ops status_ops
= {
6257 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
6259 while (line
< end
) {
6260 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
6261 !io_write(io
, "\n", 1))
6264 if (line
->type
== LINE_DIFF_CHUNK
||
6265 line
->type
== LINE_DIFF_HEADER
)
6272 static struct line
*
6273 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
6275 for (; view
->line
< line
; line
--)
6276 if (line
->type
== type
)
6283 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
6285 const char *apply_argv
[SIZEOF_ARG
] = {
6286 "git", "apply", "--whitespace=nowarn", NULL
6288 struct line
*diff_hdr
;
6292 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6297 apply_argv
[argc
++] = "--cached";
6298 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6299 apply_argv
[argc
++] = "-R";
6300 apply_argv
[argc
++] = "-";
6301 apply_argv
[argc
++] = NULL
;
6302 if (!io_run(&io
, IO_WR
, opt_cdup
, apply_argv
))
6305 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6306 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6310 io_run_bg(update_index_argv
);
6312 return chunk
? TRUE
: FALSE
;
6316 stage_update(struct view
*view
, struct line
*line
)
6318 struct line
*chunk
= NULL
;
6320 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6321 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6324 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6325 report("Failed to apply chunk");
6329 } else if (!stage_status
.status
) {
6330 view
= VIEW(REQ_VIEW_STATUS
);
6332 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6333 if (line
->type
== stage_line_type
)
6336 if (!status_update_files(view
, line
+ 1)) {
6337 report("Failed to update files");
6341 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6342 report("Failed to update file");
6350 stage_revert(struct view
*view
, struct line
*line
)
6352 struct line
*chunk
= NULL
;
6354 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6355 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6358 if (!prompt_yesno("Are you sure you want to revert changes?"))
6361 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6362 report("Failed to revert chunk");
6368 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6369 stage_line_type
, FALSE
);
6375 stage_next(struct view
*view
, struct line
*line
)
6379 if (!stage_chunks
) {
6380 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6381 if (line
->type
!= LINE_DIFF_CHUNK
)
6384 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6385 report("Allocation failure");
6389 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6393 for (i
= 0; i
< stage_chunks
; i
++) {
6394 if (stage_chunk
[i
] > view
->lineno
) {
6395 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6396 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6401 report("No next chunk found");
6405 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6408 case REQ_STATUS_UPDATE
:
6409 if (!stage_update(view
, line
))
6413 case REQ_STATUS_REVERT
:
6414 if (!stage_revert(view
, line
))
6418 case REQ_STAGE_NEXT
:
6419 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6420 report("File is untracked; press %s to add",
6421 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6424 stage_next(view
, line
);
6428 if (!stage_status
.new.name
[0])
6430 if (stage_status
.status
== 'D') {
6431 report("File has been deleted.");
6435 open_editor(stage_status
.new.name
);
6439 /* Reload everything ... */
6442 case REQ_VIEW_BLAME
:
6443 if (stage_status
.new.name
[0]) {
6444 string_copy(opt_file
, stage_status
.new.name
);
6450 return pager_request(view
, request
, line
);
6456 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6457 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6459 /* Check whether the staged entry still exists, and close the
6460 * stage view if it doesn't. */
6461 if (!status_exists(&stage_status
, stage_line_type
)) {
6462 status_restore(VIEW(REQ_VIEW_STATUS
));
6463 return REQ_VIEW_CLOSE
;
6466 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6467 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6468 report("Cannot display a directory");
6472 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6473 report("Failed to open file: %s", strerror(errno
));
6477 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6482 static struct view_ops stage_ops
= {
6499 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6500 char title
[128]; /* First line of the commit message. */
6501 const char *author
; /* Author of the commit. */
6502 struct time time
; /* Date from the author ident. */
6503 struct ref_list
*refs
; /* Repository references. */
6504 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6505 size_t graph_size
; /* The width of the graph array. */
6506 bool has_parents
; /* Rewritten --parents seen. */
6509 /* Size of rev graph with no "padding" columns */
6510 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6513 struct rev_graph
*prev
, *next
, *parents
;
6514 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6516 struct commit
*commit
;
6518 unsigned int boundary
:1;
6521 /* Parents of the commit being visualized. */
6522 static struct rev_graph graph_parents
[4];
6524 /* The current stack of revisions on the graph. */
6525 static struct rev_graph graph_stacks
[4] = {
6526 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6527 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6528 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6529 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6533 graph_parent_is_merge(struct rev_graph
*graph
)
6535 return graph
->parents
->size
> 1;
6539 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6541 struct commit
*commit
= graph
->commit
;
6543 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6544 commit
->graph
[commit
->graph_size
++] = symbol
;
6548 clear_rev_graph(struct rev_graph
*graph
)
6550 graph
->boundary
= 0;
6551 graph
->size
= graph
->pos
= 0;
6552 graph
->commit
= NULL
;
6553 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6557 done_rev_graph(struct rev_graph
*graph
)
6559 if (graph_parent_is_merge(graph
) &&
6560 graph
->pos
< graph
->size
- 1 &&
6561 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6562 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6564 graph
->commit
->graph_size
= i
* 2;
6565 while (i
< graph
->next
->size
- 1) {
6566 append_to_rev_graph(graph
, ' ');
6567 append_to_rev_graph(graph
, '\\');
6572 clear_rev_graph(graph
);
6576 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6580 /* "Collapse" duplicate parents lines.
6582 * FIXME: This needs to also update update the drawn graph but
6583 * for now it just serves as a method for pruning graph lines. */
6584 for (i
= 0; i
< graph
->size
; i
++)
6585 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6588 if (graph
->size
< SIZEOF_REVITEMS
) {
6589 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6594 get_rev_graph_symbol(struct rev_graph
*graph
)
6598 if (graph
->boundary
)
6599 symbol
= REVGRAPH_BOUND
;
6600 else if (graph
->parents
->size
== 0)
6601 symbol
= REVGRAPH_INIT
;
6602 else if (graph_parent_is_merge(graph
))
6603 symbol
= REVGRAPH_MERGE
;
6604 else if (graph
->pos
>= graph
->size
)
6605 symbol
= REVGRAPH_BRANCH
;
6607 symbol
= REVGRAPH_COMMIT
;
6613 draw_rev_graph(struct rev_graph
*graph
)
6616 chtype separator
, line
;
6618 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6619 static struct rev_filler fillers
[] = {
6625 chtype symbol
= get_rev_graph_symbol(graph
);
6626 struct rev_filler
*filler
;
6629 fillers
[DEFAULT
].line
= opt_line_graphics
? ACS_VLINE
: '|';
6630 filler
= &fillers
[DEFAULT
];
6632 for (i
= 0; i
< graph
->pos
; i
++) {
6633 append_to_rev_graph(graph
, filler
->line
);
6634 if (graph_parent_is_merge(graph
->prev
) &&
6635 graph
->prev
->pos
== i
)
6636 filler
= &fillers
[RSHARP
];
6638 append_to_rev_graph(graph
, filler
->separator
);
6641 /* Place the symbol for this revision. */
6642 append_to_rev_graph(graph
, symbol
);
6644 if (graph
->prev
->size
> graph
->size
)
6645 filler
= &fillers
[RDIAG
];
6647 filler
= &fillers
[DEFAULT
];
6651 for (; i
< graph
->size
; i
++) {
6652 append_to_rev_graph(graph
, filler
->separator
);
6653 append_to_rev_graph(graph
, filler
->line
);
6654 if (graph_parent_is_merge(graph
->prev
) &&
6655 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6656 filler
= &fillers
[RSHARP
];
6657 if (graph
->prev
->size
> graph
->size
)
6658 filler
= &fillers
[LDIAG
];
6661 if (graph
->prev
->size
> graph
->size
) {
6662 append_to_rev_graph(graph
, filler
->separator
);
6663 if (filler
->line
!= ' ')
6664 append_to_rev_graph(graph
, filler
->line
);
6668 /* Prepare the next rev graph */
6670 prepare_rev_graph(struct rev_graph
*graph
)
6674 /* First, traverse all lines of revisions up to the active one. */
6675 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6676 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6679 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6682 /* Interleave the new revision parent(s). */
6683 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6684 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6686 /* Lastly, put any remaining revisions. */
6687 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6688 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6692 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6694 /* If this is the finalizing update ... */
6696 prepare_rev_graph(graph
);
6698 /* Graph visualization needs a one rev look-ahead,
6699 * so the first update doesn't visualize anything. */
6700 if (!graph
->prev
->commit
)
6703 if (view
->lines
> 2)
6704 view
->line
[view
->lines
- 3].dirty
= 1;
6705 if (view
->lines
> 1)
6706 view
->line
[view
->lines
- 2].dirty
= 1;
6707 draw_rev_graph(graph
->prev
);
6708 done_rev_graph(graph
->prev
->prev
);
6716 static const char *main_argv
[SIZEOF_ARG
] = {
6717 "git", "log", "--no-color", "--pretty=raw", "--parents",
6718 "--topo-order", "%(diffargs)", "%(revargs)",
6719 "--", "%(fileargs)", NULL
6723 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6725 struct commit
*commit
= line
->data
;
6727 if (!commit
->author
)
6730 if (opt_date
&& draw_date(view
, &commit
->time
))
6733 if (opt_author
&& draw_author(view
, commit
->author
))
6736 if (opt_rev_graph
&& commit
->graph_size
&&
6737 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6740 if (opt_show_refs
&& commit
->refs
) {
6743 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6744 struct ref
*ref
= commit
->refs
->refs
[i
];
6745 enum line_type type
;
6748 type
= LINE_MAIN_HEAD
;
6750 type
= LINE_MAIN_LOCAL_TAG
;
6752 type
= LINE_MAIN_TAG
;
6753 else if (ref
->tracked
)
6754 type
= LINE_MAIN_TRACKED
;
6755 else if (ref
->remote
)
6756 type
= LINE_MAIN_REMOTE
;
6758 type
= LINE_MAIN_REF
;
6760 if (draw_text(view
, type
, "[", TRUE
) ||
6761 draw_text(view
, type
, ref
->name
, TRUE
) ||
6762 draw_text(view
, type
, "]", TRUE
))
6765 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6770 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6774 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6776 main_read(struct view
*view
, char *line
)
6778 static struct rev_graph
*graph
= graph_stacks
;
6779 enum line_type type
;
6780 struct commit
*commit
;
6785 if (!view
->lines
&& !view
->prev
)
6786 die("No revisions match the given arguments.");
6787 if (view
->lines
> 0) {
6788 commit
= view
->line
[view
->lines
- 1].data
;
6789 view
->line
[view
->lines
- 1].dirty
= 1;
6790 if (!commit
->author
) {
6793 graph
->commit
= NULL
;
6796 update_rev_graph(view
, graph
);
6798 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6799 clear_rev_graph(&graph_stacks
[i
]);
6803 type
= get_line_type(line
);
6804 if (type
== LINE_COMMIT
) {
6805 commit
= calloc(1, sizeof(struct commit
));
6809 line
+= STRING_SIZE("commit ");
6811 graph
->boundary
= 1;
6815 string_copy_rev(commit
->id
, line
);
6816 commit
->refs
= get_ref_list(commit
->id
);
6817 graph
->commit
= commit
;
6818 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6820 while ((line
= strchr(line
, ' '))) {
6822 push_rev_graph(graph
->parents
, line
);
6823 commit
->has_parents
= TRUE
;
6830 commit
= view
->line
[view
->lines
- 1].data
;
6834 if (commit
->has_parents
)
6836 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6840 parse_author_line(line
+ STRING_SIZE("author "),
6841 &commit
->author
, &commit
->time
);
6842 update_rev_graph(view
, graph
);
6843 graph
= graph
->next
;
6847 /* Fill in the commit title if it has not already been set. */
6848 if (commit
->title
[0])
6851 /* Require titles to start with a non-space character at the
6852 * offset used by git log. */
6853 if (strncmp(line
, " ", 4))
6856 /* Well, if the title starts with a whitespace character,
6857 * try to be forgiving. Otherwise we end up with no title. */
6858 while (isspace(*line
))
6862 /* FIXME: More graceful handling of titles; append "..." to
6863 * shortened titles, etc. */
6865 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6866 view
->line
[view
->lines
- 1].dirty
= 1;
6873 main_request(struct view
*view
, enum request request
, struct line
*line
)
6875 enum open_flags flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
6879 open_view(view
, REQ_VIEW_DIFF
, flags
);
6883 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6893 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6898 if (!opt_show_refs
|| !list
)
6901 for (i
= 0; i
< list
->size
; i
++) {
6902 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6910 main_grep(struct view
*view
, struct line
*line
)
6912 struct commit
*commit
= line
->data
;
6913 const char *text
[] = {
6915 opt_author
? commit
->author
: "",
6916 mkdate(&commit
->time
, opt_date
),
6920 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6924 main_select(struct view
*view
, struct line
*line
)
6926 struct commit
*commit
= line
->data
;
6928 string_copy_rev(view
->ref
, commit
->id
);
6929 string_copy_rev(ref_commit
, view
->ref
);
6932 static struct view_ops main_ops
= {
6948 /* Whether or not the curses interface has been initialized. */
6949 static bool cursed
= FALSE
;
6951 /* Terminal hacks and workarounds. */
6952 static bool use_scroll_redrawwin
;
6953 static bool use_scroll_status_wclear
;
6955 /* The status window is used for polling keystrokes. */
6956 static WINDOW
*status_win
;
6958 /* Reading from the prompt? */
6959 static bool input_mode
= FALSE
;
6961 static bool status_empty
= FALSE
;
6963 /* Update status and title window. */
6965 report(const char *msg
, ...)
6967 struct view
*view
= display
[current_view
];
6973 char buf
[SIZEOF_STR
];
6976 va_start(args
, msg
);
6977 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6978 buf
[sizeof(buf
) - 1] = 0;
6979 buf
[sizeof(buf
) - 2] = '.';
6980 buf
[sizeof(buf
) - 3] = '.';
6981 buf
[sizeof(buf
) - 4] = '.';
6987 if (!status_empty
|| *msg
) {
6990 va_start(args
, msg
);
6992 wmove(status_win
, 0, 0);
6993 if (view
->has_scrolled
&& use_scroll_status_wclear
)
6996 vwprintw(status_win
, msg
, args
);
6997 status_empty
= FALSE
;
6999 status_empty
= TRUE
;
7001 wclrtoeol(status_win
);
7002 wnoutrefresh(status_win
);
7007 update_view_title(view
);
7016 /* Initialize the curses library */
7017 if (isatty(STDIN_FILENO
)) {
7018 cursed
= !!initscr();
7021 /* Leave stdin and stdout alone when acting as a pager. */
7022 opt_tty
= fopen("/dev/tty", "r+");
7024 die("Failed to open /dev/tty");
7025 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
7029 die("Failed to initialize curses");
7031 nonl(); /* Disable conversion and detect newlines from input. */
7032 cbreak(); /* Take input chars one at a time, no wait for \n */
7033 noecho(); /* Don't echo input */
7034 leaveok(stdscr
, FALSE
);
7039 getmaxyx(stdscr
, y
, x
);
7040 status_win
= newwin(1, 0, y
- 1, 0);
7042 die("Failed to create status window");
7044 /* Enable keyboard mapping */
7045 keypad(status_win
, TRUE
);
7046 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
7048 TABSIZE
= opt_tab_size
;
7050 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
7051 if (term
&& !strcmp(term
, "gnome-terminal")) {
7052 /* In the gnome-terminal-emulator, the message from
7053 * scrolling up one line when impossible followed by
7054 * scrolling down one line causes corruption of the
7055 * status line. This is fixed by calling wclear. */
7056 use_scroll_status_wclear
= TRUE
;
7057 use_scroll_redrawwin
= FALSE
;
7059 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
7060 /* No problems with full optimizations in xrvt-(unicode)
7062 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
7065 /* When scrolling in (u)xterm the last line in the
7066 * scrolling direction will update slowly. */
7067 use_scroll_redrawwin
= TRUE
;
7068 use_scroll_status_wclear
= FALSE
;
7073 get_input(int prompt_position
)
7076 int i
, key
, cursor_y
, cursor_x
;
7078 if (prompt_position
)
7082 bool loading
= FALSE
;
7084 foreach_view (view
, i
) {
7086 if (view_is_displayed(view
) && view
->has_scrolled
&&
7087 use_scroll_redrawwin
)
7088 redrawwin(view
->win
);
7089 view
->has_scrolled
= FALSE
;
7094 /* Update the cursor position. */
7095 if (prompt_position
) {
7096 getbegyx(status_win
, cursor_y
, cursor_x
);
7097 cursor_x
= prompt_position
;
7099 view
= display
[current_view
];
7100 getbegyx(view
->win
, cursor_y
, cursor_x
);
7101 cursor_x
= view
->width
- 1;
7102 cursor_y
+= view
->lineno
- view
->offset
;
7104 setsyx(cursor_y
, cursor_x
);
7106 /* Refresh, accept single keystroke of input */
7108 nodelay(status_win
, loading
);
7109 key
= wgetch(status_win
);
7111 /* wgetch() with nodelay() enabled returns ERR when
7112 * there's no input. */
7115 } else if (key
== KEY_RESIZE
) {
7118 getmaxyx(stdscr
, height
, width
);
7120 wresize(status_win
, 1, width
);
7121 mvwin(status_win
, height
- 1, 0);
7122 wnoutrefresh(status_win
);
7124 redraw_display(TRUE
);
7134 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7136 enum input_status status
= INPUT_OK
;
7137 static char buf
[SIZEOF_STR
];
7142 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7145 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7146 wclrtoeol(status_win
);
7148 key
= get_input(pos
+ 1);
7153 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7160 status
= INPUT_CANCEL
;
7164 status
= INPUT_CANCEL
;
7168 if (pos
>= sizeof(buf
)) {
7169 report("Input string too long");
7173 status
= handler(data
, buf
, key
);
7174 if (status
== INPUT_OK
)
7175 buf
[pos
++] = (char) key
;
7179 /* Clear the status window */
7180 status_empty
= FALSE
;
7183 if (status
== INPUT_CANCEL
)
7191 static enum input_status
7192 prompt_yesno_handler(void *data
, char *buf
, int c
)
7194 if (c
== 'y' || c
== 'Y')
7196 if (c
== 'n' || c
== 'N')
7197 return INPUT_CANCEL
;
7202 prompt_yesno(const char *prompt
)
7204 char prompt2
[SIZEOF_STR
];
7206 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7209 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7212 static enum input_status
7213 read_prompt_handler(void *data
, char *buf
, int c
)
7215 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7219 read_prompt(const char *prompt
)
7221 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7224 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7226 enum input_status status
= INPUT_OK
;
7229 while (items
[size
].text
)
7232 while (status
== INPUT_OK
) {
7233 const struct menu_item
*item
= &items
[*selected
];
7237 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7238 prompt
, *selected
+ 1, size
);
7240 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7241 wprintw(status_win
, "%s", item
->text
);
7242 wclrtoeol(status_win
);
7244 key
= get_input(COLS
- 1);
7249 status
= INPUT_STOP
;
7254 *selected
= *selected
- 1;
7256 *selected
= size
- 1;
7261 *selected
= (*selected
+ 1) % size
;
7265 status
= INPUT_CANCEL
;
7269 for (i
= 0; items
[i
].text
; i
++)
7270 if (items
[i
].hotkey
== key
) {
7272 status
= INPUT_STOP
;
7278 /* Clear the status window */
7279 status_empty
= FALSE
;
7282 return status
!= INPUT_CANCEL
;
7286 * Repository properties
7289 static struct ref
**refs
= NULL
;
7290 static size_t refs_size
= 0;
7291 static struct ref
*refs_head
= NULL
;
7293 static struct ref_list
**ref_lists
= NULL
;
7294 static size_t ref_lists_size
= 0;
7296 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7297 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7298 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7301 compare_refs(const void *ref1_
, const void *ref2_
)
7303 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7304 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7306 if (ref1
->tag
!= ref2
->tag
)
7307 return ref2
->tag
- ref1
->tag
;
7308 if (ref1
->ltag
!= ref2
->ltag
)
7309 return ref2
->ltag
- ref2
->ltag
;
7310 if (ref1
->head
!= ref2
->head
)
7311 return ref2
->head
- ref1
->head
;
7312 if (ref1
->tracked
!= ref2
->tracked
)
7313 return ref2
->tracked
- ref1
->tracked
;
7314 if (ref1
->remote
!= ref2
->remote
)
7315 return ref2
->remote
- ref1
->remote
;
7316 return strcmp(ref1
->name
, ref2
->name
);
7320 foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
)
7324 for (i
= 0; i
< refs_size
; i
++)
7325 if (!visitor(data
, refs
[i
]))
7335 static struct ref_list
*
7336 get_ref_list(const char *id
)
7338 struct ref_list
*list
;
7341 for (i
= 0; i
< ref_lists_size
; i
++)
7342 if (!strcmp(id
, ref_lists
[i
]->id
))
7343 return ref_lists
[i
];
7345 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7347 list
= calloc(1, sizeof(*list
));
7351 for (i
= 0; i
< refs_size
; i
++) {
7352 if (!strcmp(id
, refs
[i
]->id
) &&
7353 realloc_refs_list(&list
->refs
, list
->size
, 1))
7354 list
->refs
[list
->size
++] = refs
[i
];
7362 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7363 ref_lists
[ref_lists_size
++] = list
;
7368 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
7370 struct ref
*ref
= NULL
;
7373 bool remote
= FALSE
;
7374 bool tracked
= FALSE
;
7376 int from
= 0, to
= refs_size
- 1;
7378 if (!prefixcmp(name
, "refs/tags/")) {
7379 if (!suffixcmp(name
, namelen
, "^{}")) {
7387 namelen
-= STRING_SIZE("refs/tags/");
7388 name
+= STRING_SIZE("refs/tags/");
7390 } else if (!prefixcmp(name
, "refs/remotes/")) {
7392 namelen
-= STRING_SIZE("refs/remotes/");
7393 name
+= STRING_SIZE("refs/remotes/");
7394 tracked
= !strcmp(opt_remote
, name
);
7396 } else if (!prefixcmp(name
, "refs/heads/")) {
7397 namelen
-= STRING_SIZE("refs/heads/");
7398 name
+= STRING_SIZE("refs/heads/");
7399 if (!strncmp(opt_head
, name
, namelen
))
7402 } else if (!strcmp(name
, "HEAD")) {
7405 namelen
= strlen(opt_head
);
7410 /* If we are reloading or it's an annotated tag, replace the
7411 * previous SHA1 with the resolved commit id; relies on the fact
7412 * git-ls-remote lists the commit id of an annotated tag right
7413 * before the commit id it points to. */
7414 while (from
<= to
) {
7415 size_t pos
= (to
+ from
) / 2;
7416 int cmp
= strcmp(name
, refs
[pos
]->name
);
7430 if (!realloc_refs(&refs
, refs_size
, 1))
7432 ref
= calloc(1, sizeof(*ref
) + namelen
);
7435 memmove(refs
+ from
+ 1, refs
+ from
,
7436 (refs_size
- from
) * sizeof(*refs
));
7438 strncpy(ref
->name
, name
, namelen
);
7445 ref
->remote
= remote
;
7446 ref
->tracked
= tracked
;
7447 string_copy_rev(ref
->id
, id
);
7457 const char *head_argv
[] = {
7458 "git", "symbolic-ref", "HEAD", NULL
7460 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7461 "git", "ls-remote", opt_git_dir
, NULL
7463 static bool init
= FALSE
;
7467 if (!argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE"))
7468 die("TIG_LS_REMOTE contains too many arguments");
7475 if (io_run_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7476 !prefixcmp(opt_head
, "refs/heads/")) {
7477 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7479 memmove(opt_head
, offset
, strlen(offset
) + 1);
7483 for (i
= 0; i
< refs_size
; i
++)
7486 if (io_run_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7489 /* Update the ref lists to reflect changes. */
7490 for (i
= 0; i
< ref_lists_size
; i
++) {
7491 struct ref_list
*list
= ref_lists
[i
];
7494 for (old
= new = 0; old
< list
->size
; old
++)
7495 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7496 list
->refs
[new++] = list
->refs
[old
];
7504 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7506 if (!strcmp(name
, ".remote")) {
7507 string_ncopy(opt_remote
, value
, valuelen
);
7509 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7510 size_t from
= strlen(opt_remote
);
7512 if (!prefixcmp(value
, "refs/heads/"))
7513 value
+= STRING_SIZE("refs/heads/");
7515 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7521 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7523 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7524 int argc
= 1 + (cmd
== option_set_command
);
7527 if (!argv_from_string(argv
, &argc
, value
))
7528 config_msg
= "Too many option arguments";
7530 error
= cmd(argc
, argv
);
7533 warn("Option 'tig.%s': %s", name
, config_msg
);
7537 set_environment_variable(const char *name
, const char *value
)
7539 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7540 char *env
= malloc(len
);
7543 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7551 set_work_tree(const char *value
)
7553 char cwd
[SIZEOF_STR
];
7555 if (!getcwd(cwd
, sizeof(cwd
)))
7556 die("Failed to get cwd path: %s", strerror(errno
));
7557 if (chdir(opt_git_dir
) < 0)
7558 die("Failed to chdir(%s): %s", strerror(errno
));
7559 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7560 die("Failed to get git path: %s", strerror(errno
));
7562 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7563 if (chdir(value
) < 0)
7564 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7565 if (!getcwd(cwd
, sizeof(cwd
)))
7566 die("Failed to get cwd path: %s", strerror(errno
));
7567 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7568 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7569 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7570 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7571 opt_is_inside_work_tree
= TRUE
;
7575 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7577 if (!strcmp(name
, "i18n.commitencoding"))
7578 string_ncopy(opt_encoding
, value
, valuelen
);
7580 else if (!strcmp(name
, "core.editor"))
7581 string_ncopy(opt_editor
, value
, valuelen
);
7583 else if (!strcmp(name
, "core.worktree"))
7584 set_work_tree(value
);
7586 else if (!prefixcmp(name
, "tig.color."))
7587 set_repo_config_option(name
+ 10, value
, option_color_command
);
7589 else if (!prefixcmp(name
, "tig.bind."))
7590 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7592 else if (!prefixcmp(name
, "tig."))
7593 set_repo_config_option(name
+ 4, value
, option_set_command
);
7595 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7596 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7597 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7603 load_git_config(void)
7605 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7607 return io_run_load(config_list_argv
, "=", read_repo_config_option
);
7611 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7613 if (!opt_git_dir
[0]) {
7614 string_ncopy(opt_git_dir
, name
, namelen
);
7616 } else if (opt_is_inside_work_tree
== -1) {
7617 /* This can be 3 different values depending on the
7618 * version of git being used. If git-rev-parse does not
7619 * understand --is-inside-work-tree it will simply echo
7620 * the option else either "true" or "false" is printed.
7621 * Default to true for the unknown case. */
7622 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7624 } else if (*name
== '.') {
7625 string_ncopy(opt_cdup
, name
, namelen
);
7628 string_ncopy(opt_prefix
, name
, namelen
);
7635 load_repo_info(void)
7637 const char *rev_parse_argv
[] = {
7638 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7639 "--show-cdup", "--show-prefix", NULL
7642 return io_run_load(rev_parse_argv
, "=", read_repo_info
);
7650 static const char usage
[] =
7651 "tig " TIG_VERSION
" (" __DATE__
")\n"
7653 "Usage: tig [options] [revs] [--] [paths]\n"
7654 " or: tig show [options] [revs] [--] [paths]\n"
7655 " or: tig blame [rev] path\n"
7657 " or: tig < [git command output]\n"
7660 " -v, --version Show version and exit\n"
7661 " -h, --help Show help message and exit";
7663 static void __NORETURN
7666 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7672 static void __NORETURN
7673 die(const char *err
, ...)
7679 va_start(args
, err
);
7680 fputs("tig: ", stderr
);
7681 vfprintf(stderr
, err
, args
);
7682 fputs("\n", stderr
);
7689 warn(const char *msg
, ...)
7693 va_start(args
, msg
);
7694 fputs("tig warning: ", stderr
);
7695 vfprintf(stderr
, msg
, args
);
7696 fputs("\n", stderr
);
7700 static const char ***filter_args
;
7703 read_filter_args(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7705 return argv_append(filter_args
, name
) ? OK
: ERR
;
7709 filter_rev_parse(const char ***args
, const char *arg1
, const char *arg2
, const char *argv
[])
7711 const char *rev_parse_argv
[SIZEOF_ARG
] = { "git", "rev-parse", arg1
, arg2
};
7712 const char **all_argv
= NULL
;
7715 if (!argv_append_array(&all_argv
, rev_parse_argv
) ||
7716 !argv_append_array(&all_argv
, argv
) ||
7717 !io_run_load(all_argv
, "\n", read_filter_args
) == ERR
)
7718 die("Failed to split arguments");
7719 argv_free(all_argv
);
7724 filter_options(const char *argv
[])
7726 filter_rev_parse(&opt_file_args
, "--no-revs", "--no-flags", argv
);
7727 filter_rev_parse(&opt_diff_args
, "--no-revs", "--flags", argv
);
7728 filter_rev_parse(&opt_rev_args
, "--symbolic", "--revs-only", argv
);
7732 parse_options(int argc
, const char *argv
[])
7734 enum request request
= REQ_VIEW_MAIN
;
7735 const char *subcommand
;
7736 bool seen_dashdash
= FALSE
;
7737 const char **filter_argv
= NULL
;
7740 if (!isatty(STDIN_FILENO
)) {
7741 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7742 return REQ_VIEW_PAGER
;
7746 return REQ_VIEW_MAIN
;
7748 subcommand
= argv
[1];
7749 if (!strcmp(subcommand
, "status")) {
7751 warn("ignoring arguments after `%s'", subcommand
);
7752 return REQ_VIEW_STATUS
;
7754 } else if (!strcmp(subcommand
, "blame")) {
7755 if (argc
<= 2 || argc
> 4)
7756 die("invalid number of options to blame\n\n%s", usage
);
7760 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7764 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7765 return REQ_VIEW_BLAME
;
7767 } else if (!strcmp(subcommand
, "show")) {
7768 request
= REQ_VIEW_DIFF
;
7774 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7775 const char *opt
= argv
[i
];
7777 if (seen_dashdash
) {
7778 argv_append(&opt_file_args
, opt
);
7781 } else if (!strcmp(opt
, "--")) {
7782 seen_dashdash
= TRUE
;
7785 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7786 printf("tig version %s\n", TIG_VERSION
);
7789 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7790 printf("%s\n", usage
);
7793 } else if (!strcmp(opt
, "--all")) {
7794 argv_append(&opt_rev_args
, opt
);
7798 if (!argv_append(&filter_argv
, opt
))
7799 die("command too long");
7803 filter_options(filter_argv
);
7809 main(int argc
, const char *argv
[])
7811 const char *codeset
= "UTF-8";
7812 enum request request
= parse_options(argc
, argv
);
7816 signal(SIGINT
, quit
);
7817 signal(SIGPIPE
, SIG_IGN
);
7819 if (setlocale(LC_ALL
, "")) {
7820 codeset
= nl_langinfo(CODESET
);
7823 if (load_repo_info() == ERR
)
7824 die("Failed to load repo info.");
7826 if (load_options() == ERR
)
7827 die("Failed to load user config.");
7829 if (load_git_config() == ERR
)
7830 die("Failed to load repo config.");
7832 /* Require a git repository unless when running in pager mode. */
7833 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7834 die("Not a git repository");
7836 if (*opt_encoding
&& strcmp(codeset
, "UTF-8")) {
7837 opt_iconv_in
= iconv_open("UTF-8", opt_encoding
);
7838 if (opt_iconv_in
== ICONV_NONE
)
7839 die("Failed to initialize character set conversion");
7842 if (codeset
&& strcmp(codeset
, "UTF-8")) {
7843 opt_iconv_out
= iconv_open(codeset
, "UTF-8");
7844 if (opt_iconv_out
== ICONV_NONE
)
7845 die("Failed to initialize character set conversion");
7848 if (load_refs() == ERR
)
7849 die("Failed to load refs.");
7851 foreach_view (view
, i
) {
7852 if (getenv(view
->cmd_env
))
7853 warn("Use of the %s environment variable is deprecated,"
7854 " use options or TIG_DIFF_ARGS instead",
7856 if (!argv_from_env(view
->ops
->argv
, view
->cmd_env
))
7857 die("Too many arguments in the `%s` environment variable",
7863 while (view_driver(display
[current_view
], request
)) {
7864 int key
= get_input(0);
7866 view
= display
[current_view
];
7867 request
= get_keybinding(view
->keymap
, key
);
7869 /* Some low-level request handling. This keeps access to
7870 * status_win restricted. */
7873 report("Unknown key, press %s for help",
7874 get_key(view
->keymap
, REQ_VIEW_HELP
));
7878 char *cmd
= read_prompt(":");
7880 if (cmd
&& isdigit(*cmd
)) {
7881 int lineno
= view
->lineno
+ 1;
7883 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7884 select_view_line(view
, lineno
- 1);
7887 report("Unable to parse '%s' as a line number", cmd
);
7891 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7892 const char *argv
[SIZEOF_ARG
] = { "git" };
7895 /* When running random commands, initially show the
7896 * command in the title. However, it maybe later be
7897 * overwritten if a commit line is selected. */
7898 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7900 if (!argv_from_string(argv
, &argc
, cmd
)) {
7901 report("Too many arguments");
7902 } else if (!prepare_update(next
, argv
, NULL
)) {
7903 report("Failed to format command");
7905 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7913 case REQ_SEARCH_BACK
:
7915 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7916 char *search
= read_prompt(prompt
);
7919 string_ncopy(opt_search
, search
, strlen(search
));
7920 else if (*opt_search
)
7921 request
= request
== REQ_SEARCH
?