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
]);
687 DEFINE_ALLOCATOR(argv_realloc
, const char *, SIZEOF_ARG
)
690 argv_append(const char ***argv
, const char *arg
)
694 while (*argv
&& (*argv
)[argc
])
697 if (!argv_realloc(argv
, argc
, 2))
700 (*argv
)[argc
++] = strdup(arg
);
701 (*argv
)[argc
] = NULL
;
706 argv_append_array(const char ***dst_argv
, const char *src_argv
[])
710 for (i
= 0; src_argv
&& src_argv
[i
]; i
++)
711 if (!argv_append(dst_argv
, src_argv
[i
]))
717 argv_copy(const char ***dst
, const char *src
[])
721 for (argc
= 0; src
[argc
]; argc
++)
722 if (!argv_append(dst
, src
[argc
]))
729 * Executing external commands.
733 IO_FD
, /* File descriptor based IO. */
734 IO_BG
, /* Execute command in the background. */
735 IO_FG
, /* Execute command with same std{in,out,err}. */
736 IO_RD
, /* Read only fork+exec IO. */
737 IO_WR
, /* Write only fork+exec IO. */
738 IO_AP
, /* Append fork+exec output to file. */
742 int pipe
; /* Pipe end for reading or writing. */
743 pid_t pid
; /* PID of spawned process. */
744 int error
; /* Error status. */
745 char *buf
; /* Read buffer. */
746 size_t bufalloc
; /* Allocated buffer size. */
747 size_t bufsize
; /* Buffer content size. */
748 char *bufpos
; /* Current buffer position. */
749 unsigned int eof
:1; /* Has end of file been reached. */
753 io_init(struct io
*io
)
755 memset(io
, 0, sizeof(*io
));
760 io_open(struct io
*io
, const char *fmt
, ...)
762 char name
[SIZEOF_STR
] = "";
769 fits
= vsnprintf(name
, sizeof(name
), fmt
, args
) < sizeof(name
);
773 io
->error
= ENAMETOOLONG
;
776 io
->pipe
= *name
? open(name
, O_RDONLY
) : STDIN_FILENO
;
779 return io
->pipe
!= -1;
783 io_kill(struct io
*io
)
785 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
789 io_done(struct io
*io
)
800 pid_t waiting
= waitpid(pid
, &status
, 0);
809 return waiting
== pid
&&
810 !WIFSIGNALED(status
) &&
812 !WEXITSTATUS(status
);
819 io_run(struct io
*io
, enum io_type type
, const char *dir
, const char *argv
[], ...)
821 int pipefds
[2] = { -1, -1 };
826 if ((type
== IO_RD
|| type
== IO_WR
) && pipe(pipefds
) < 0) {
829 } else if (type
== IO_AP
) {
830 va_start(args
, argv
);
831 pipefds
[1] = va_arg(args
, int);
835 if ((io
->pid
= fork())) {
838 if (pipefds
[!(type
== IO_WR
)] != -1)
839 close(pipefds
[!(type
== IO_WR
)]);
841 io
->pipe
= pipefds
[!!(type
== IO_WR
)];
847 int devnull
= open("/dev/null", O_RDWR
);
848 int readfd
= type
== IO_WR
? pipefds
[0] : devnull
;
849 int writefd
= (type
== IO_RD
|| type
== IO_AP
)
850 ? pipefds
[1] : devnull
;
852 dup2(readfd
, STDIN_FILENO
);
853 dup2(writefd
, STDOUT_FILENO
);
854 dup2(devnull
, STDERR_FILENO
);
857 if (pipefds
[0] != -1)
859 if (pipefds
[1] != -1)
863 if (dir
&& *dir
&& chdir(dir
) == -1)
866 execvp(argv
[0], (char *const*) argv
);
870 if (pipefds
[!!(type
== IO_WR
)] != -1)
871 close(pipefds
[!!(type
== IO_WR
)]);
876 io_complete(enum io_type type
, const char **argv
, const char *dir
, int fd
)
880 return io_run(&io
, type
, dir
, argv
, fd
) && io_done(&io
);
884 io_run_bg(const char **argv
)
886 return io_complete(IO_BG
, argv
, NULL
, -1);
890 io_run_fg(const char **argv
, const char *dir
)
892 return io_complete(IO_FG
, argv
, dir
, -1);
896 io_run_append(const char **argv
, int fd
)
898 return io_complete(IO_AP
, argv
, NULL
, fd
);
902 io_eof(struct io
*io
)
908 io_error(struct io
*io
)
914 io_strerror(struct io
*io
)
916 return strerror(io
->error
);
920 io_can_read(struct io
*io
)
922 struct timeval tv
= { 0, 500 };
926 FD_SET(io
->pipe
, &fds
);
928 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
932 io_read(struct io
*io
, void *buf
, size_t bufsize
)
935 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
937 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
939 else if (readsize
== -1)
941 else if (readsize
== 0)
947 DEFINE_ALLOCATOR(io_realloc_buf
, char, BUFSIZ
)
950 io_get(struct io
*io
, int c
, bool can_read
)
956 if (io
->bufsize
> 0) {
957 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
959 char *line
= io
->bufpos
;
962 io
->bufpos
= eol
+ 1;
963 io
->bufsize
-= io
->bufpos
- line
;
970 io
->bufpos
[io
->bufsize
] = 0;
980 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
981 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
983 if (io
->bufalloc
== io
->bufsize
) {
984 if (!io_realloc_buf(&io
->buf
, io
->bufalloc
, BUFSIZ
))
986 io
->bufalloc
+= BUFSIZ
;
989 io
->bufpos
= io
->buf
;
990 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
993 io
->bufsize
+= readsize
;
998 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
1002 while (!io_error(io
) && written
< bufsize
) {
1005 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
1006 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
1008 else if (size
== -1)
1014 return written
== bufsize
;
1018 io_read_buf(struct io
*io
, char buf
[], size_t bufsize
)
1020 char *result
= io_get(io
, '\n', TRUE
);
1023 result
= chomp_string(result
);
1024 string_ncopy_do(buf
, bufsize
, result
, strlen(result
));
1027 return io_done(io
) && result
;
1031 io_run_buf(const char **argv
, char buf
[], size_t bufsize
)
1035 return io_run(&io
, IO_RD
, NULL
, argv
) && io_read_buf(&io
, buf
, bufsize
);
1039 io_load(struct io
*io
, const char *separators
,
1040 int (*read_property
)(char *, size_t, char *, size_t))
1045 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
1050 name
= chomp_string(name
);
1051 namelen
= strcspn(name
, separators
);
1053 if (name
[namelen
]) {
1055 value
= chomp_string(name
+ namelen
+ 1);
1056 valuelen
= strlen(value
);
1063 state
= read_property(name
, namelen
, value
, valuelen
);
1066 if (state
!= ERR
&& io_error(io
))
1074 io_run_load(const char **argv
, const char *separators
,
1075 int (*read_property
)(char *, size_t, char *, size_t))
1079 if (!io_run(&io
, IO_RD
, NULL
, argv
))
1081 return io_load(&io
, separators
, read_property
);
1090 /* XXX: Keep the view request first and in sync with views[]. */ \
1091 REQ_GROUP("View switching") \
1092 REQ_(VIEW_MAIN, "Show main view"), \
1093 REQ_(VIEW_DIFF, "Show diff view"), \
1094 REQ_(VIEW_LOG, "Show log view"), \
1095 REQ_(VIEW_TREE, "Show tree view"), \
1096 REQ_(VIEW_BLOB, "Show blob view"), \
1097 REQ_(VIEW_BLAME, "Show blame view"), \
1098 REQ_(VIEW_BRANCH, "Show branch view"), \
1099 REQ_(VIEW_HELP, "Show help page"), \
1100 REQ_(VIEW_PAGER, "Show pager view"), \
1101 REQ_(VIEW_STATUS, "Show status view"), \
1102 REQ_(VIEW_STAGE, "Show stage view"), \
1104 REQ_GROUP("View manipulation") \
1105 REQ_(ENTER, "Enter current line and scroll"), \
1106 REQ_(NEXT, "Move to next"), \
1107 REQ_(PREVIOUS, "Move to previous"), \
1108 REQ_(PARENT, "Move to parent"), \
1109 REQ_(VIEW_NEXT, "Move focus to next view"), \
1110 REQ_(REFRESH, "Reload and refresh"), \
1111 REQ_(MAXIMIZE, "Maximize the current view"), \
1112 REQ_(VIEW_CLOSE, "Close the current view"), \
1113 REQ_(QUIT, "Close all views and quit"), \
1115 REQ_GROUP("View specific requests") \
1116 REQ_(STATUS_UPDATE, "Update file status"), \
1117 REQ_(STATUS_REVERT, "Revert file changes"), \
1118 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1119 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1121 REQ_GROUP("Cursor navigation") \
1122 REQ_(MOVE_UP, "Move cursor one line up"), \
1123 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1124 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1125 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1126 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1127 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1129 REQ_GROUP("Scrolling") \
1130 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1131 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1132 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1133 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1134 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1135 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1137 REQ_GROUP("Searching") \
1138 REQ_(SEARCH, "Search the view"), \
1139 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1140 REQ_(FIND_NEXT, "Find next search match"), \
1141 REQ_(FIND_PREV, "Find previous search match"), \
1143 REQ_GROUP("Option manipulation") \
1144 REQ_(OPTIONS, "Open option menu"), \
1145 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1146 REQ_(TOGGLE_DATE, "Toggle date display"), \
1147 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1148 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1149 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1150 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1151 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1152 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1155 REQ_(PROMPT, "Bring up the prompt"), \
1156 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1157 REQ_(SHOW_VERSION, "Show version information"), \
1158 REQ_(STOP_LOADING, "Stop all loading views"), \
1159 REQ_(EDIT, "Open in editor"), \
1160 REQ_(NONE, "Do nothing")
1163 /* User action requests. */
1165 #define REQ_GROUP(help)
1166 #define REQ_(req, help) REQ_##req
1168 /* Offset all requests to avoid conflicts with ncurses getch values. */
1169 REQ_UNKNOWN
= KEY_MAX
+ 1,
1177 struct request_info
{
1178 enum request request
;
1184 static const struct request_info req_info
[] = {
1185 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1186 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1193 get_request(const char *name
)
1195 int namelen
= strlen(name
);
1198 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
1199 if (enum_equals(req_info
[i
], name
, namelen
))
1200 return req_info
[i
].request
;
1210 /* Option and state variables. */
1211 static enum date opt_date
= DATE_DEFAULT
;
1212 static enum author opt_author
= AUTHOR_DEFAULT
;
1213 static bool opt_line_number
= FALSE
;
1214 static bool opt_line_graphics
= TRUE
;
1215 static bool opt_rev_graph
= FALSE
;
1216 static bool opt_show_refs
= TRUE
;
1217 static int opt_num_interval
= 5;
1218 static double opt_hscroll
= 0.50;
1219 static double opt_scale_split_view
= 2.0 / 3.0;
1220 static int opt_tab_size
= 8;
1221 static int opt_author_cols
= AUTHOR_COLS
;
1222 static char opt_path
[SIZEOF_STR
] = "";
1223 static char opt_file
[SIZEOF_STR
] = "";
1224 static char opt_ref
[SIZEOF_REF
] = "";
1225 static char opt_head
[SIZEOF_REF
] = "";
1226 static char opt_remote
[SIZEOF_REF
] = "";
1227 static char opt_encoding
[20] = "UTF-8";
1228 static iconv_t opt_iconv_in
= ICONV_NONE
;
1229 static iconv_t opt_iconv_out
= ICONV_NONE
;
1230 static char opt_search
[SIZEOF_STR
] = "";
1231 static char opt_cdup
[SIZEOF_STR
] = "";
1232 static char opt_prefix
[SIZEOF_STR
] = "";
1233 static char opt_git_dir
[SIZEOF_STR
] = "";
1234 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
1235 static char opt_editor
[SIZEOF_STR
] = "";
1236 static FILE *opt_tty
= NULL
;
1237 static const char **opt_diff_args
= NULL
;
1238 static const char **opt_rev_args
= NULL
;
1239 static const char **opt_file_args
= NULL
;
1241 #define is_initial_commit() (!get_ref_head())
1242 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1246 * Line-oriented content detection.
1250 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1251 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1252 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1253 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1254 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1264 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1265 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1266 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1267 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1271 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1272 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1273 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1274 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1275 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1276 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1277 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1279 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1280 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1281 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1282 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1283 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1284 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1285 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1286 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1287 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1288 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1289 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1290 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1291 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1292 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1293 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1294 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1295 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1296 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1297 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1298 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1299 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1300 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1301 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1302 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1303 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1304 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1305 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1306 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1307 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1308 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1311 #define LINE(type, line, fg, bg, attr) \
1319 const char *name
; /* Option name. */
1320 int namelen
; /* Size of option name. */
1321 const char *line
; /* The start of line to match. */
1322 int linelen
; /* Size of string to match. */
1323 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1326 static struct line_info line_info
[] = {
1327 #define LINE(type, line, fg, bg, attr) \
1328 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1333 static enum line_type
1334 get_line_type(const char *line
)
1336 int linelen
= strlen(line
);
1337 enum line_type type
;
1339 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1340 /* Case insensitive search matches Signed-off-by lines better. */
1341 if (linelen
>= line_info
[type
].linelen
&&
1342 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1345 return LINE_DEFAULT
;
1349 get_line_attr(enum line_type type
)
1351 assert(type
< ARRAY_SIZE(line_info
));
1352 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1355 static struct line_info
*
1356 get_line_info(const char *name
)
1358 size_t namelen
= strlen(name
);
1359 enum line_type type
;
1361 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1362 if (enum_equals(line_info
[type
], name
, namelen
))
1363 return &line_info
[type
];
1371 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1372 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1373 enum line_type type
;
1377 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1378 default_bg
= COLOR_BLACK
;
1379 default_fg
= COLOR_WHITE
;
1382 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1383 struct line_info
*info
= &line_info
[type
];
1384 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1385 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1387 init_pair(type
, fg
, bg
);
1392 enum line_type type
;
1395 unsigned int selected
:1;
1396 unsigned int dirty
:1;
1397 unsigned int cleareol
:1;
1398 unsigned int other
:16;
1400 void *data
; /* User data */
1410 enum request request
;
1413 static struct keybinding default_keybindings
[] = {
1414 /* View switching */
1415 { 'm', REQ_VIEW_MAIN
},
1416 { 'd', REQ_VIEW_DIFF
},
1417 { 'l', REQ_VIEW_LOG
},
1418 { 't', REQ_VIEW_TREE
},
1419 { 'f', REQ_VIEW_BLOB
},
1420 { 'B', REQ_VIEW_BLAME
},
1421 { 'H', REQ_VIEW_BRANCH
},
1422 { 'p', REQ_VIEW_PAGER
},
1423 { 'h', REQ_VIEW_HELP
},
1424 { 'S', REQ_VIEW_STATUS
},
1425 { 'c', REQ_VIEW_STAGE
},
1427 /* View manipulation */
1428 { 'q', REQ_VIEW_CLOSE
},
1429 { KEY_TAB
, REQ_VIEW_NEXT
},
1430 { KEY_RETURN
, REQ_ENTER
},
1431 { KEY_UP
, REQ_PREVIOUS
},
1432 { KEY_DOWN
, REQ_NEXT
},
1433 { 'R', REQ_REFRESH
},
1434 { KEY_F(5), REQ_REFRESH
},
1435 { 'O', REQ_MAXIMIZE
},
1437 /* Cursor navigation */
1438 { 'k', REQ_MOVE_UP
},
1439 { 'j', REQ_MOVE_DOWN
},
1440 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1441 { KEY_END
, REQ_MOVE_LAST_LINE
},
1442 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1443 { ' ', REQ_MOVE_PAGE_DOWN
},
1444 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1445 { 'b', REQ_MOVE_PAGE_UP
},
1446 { '-', REQ_MOVE_PAGE_UP
},
1449 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1450 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1451 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1452 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1453 { 'w', REQ_SCROLL_PAGE_UP
},
1454 { 's', REQ_SCROLL_PAGE_DOWN
},
1457 { '/', REQ_SEARCH
},
1458 { '?', REQ_SEARCH_BACK
},
1459 { 'n', REQ_FIND_NEXT
},
1460 { 'N', REQ_FIND_PREV
},
1464 { 'z', REQ_STOP_LOADING
},
1465 { 'v', REQ_SHOW_VERSION
},
1466 { 'r', REQ_SCREEN_REDRAW
},
1467 { 'o', REQ_OPTIONS
},
1468 { '.', REQ_TOGGLE_LINENO
},
1469 { 'D', REQ_TOGGLE_DATE
},
1470 { 'A', REQ_TOGGLE_AUTHOR
},
1471 { 'g', REQ_TOGGLE_REV_GRAPH
},
1472 { 'F', REQ_TOGGLE_REFS
},
1473 { 'I', REQ_TOGGLE_SORT_ORDER
},
1474 { 'i', REQ_TOGGLE_SORT_FIELD
},
1475 { ':', REQ_PROMPT
},
1476 { 'u', REQ_STATUS_UPDATE
},
1477 { '!', REQ_STATUS_REVERT
},
1478 { 'M', REQ_STATUS_MERGE
},
1479 { '@', REQ_STAGE_NEXT
},
1480 { ',', REQ_PARENT
},
1484 #define KEYMAP_INFO \
1499 #define KEYMAP_(name) KEYMAP_##name
1504 static const struct enum_map keymap_table
[] = {
1505 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1510 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1512 struct keybinding_table
{
1513 struct keybinding
*data
;
1517 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1520 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1522 struct keybinding_table
*table
= &keybindings
[keymap
];
1525 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1526 if (keybindings
[keymap
].data
[i
].alias
== key
) {
1527 keybindings
[keymap
].data
[i
].request
= request
;
1532 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1534 die("Failed to allocate keybinding");
1535 table
->data
[table
->size
].alias
= key
;
1536 table
->data
[table
->size
++].request
= request
;
1538 if (request
== REQ_NONE
&& keymap
== KEYMAP_GENERIC
) {
1541 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1542 if (default_keybindings
[i
].alias
== key
)
1543 default_keybindings
[i
].request
= REQ_NONE
;
1547 /* Looks for a key binding first in the given map, then in the generic map, and
1548 * lastly in the default keybindings. */
1550 get_keybinding(enum keymap keymap
, int key
)
1554 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1555 if (keybindings
[keymap
].data
[i
].alias
== key
)
1556 return keybindings
[keymap
].data
[i
].request
;
1558 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1559 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1560 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1562 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1563 if (default_keybindings
[i
].alias
== key
)
1564 return default_keybindings
[i
].request
;
1566 return (enum request
) key
;
1575 static const struct key key_table
[] = {
1576 { "Enter", KEY_RETURN
},
1578 { "Backspace", KEY_BACKSPACE
},
1580 { "Escape", KEY_ESC
},
1581 { "Left", KEY_LEFT
},
1582 { "Right", KEY_RIGHT
},
1584 { "Down", KEY_DOWN
},
1585 { "Insert", KEY_IC
},
1586 { "Delete", KEY_DC
},
1588 { "Home", KEY_HOME
},
1590 { "PageUp", KEY_PPAGE
},
1591 { "PageDown", KEY_NPAGE
},
1601 { "F10", KEY_F(10) },
1602 { "F11", KEY_F(11) },
1603 { "F12", KEY_F(12) },
1607 get_key_value(const char *name
)
1611 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1612 if (!strcasecmp(key_table
[i
].name
, name
))
1613 return key_table
[i
].value
;
1615 if (strlen(name
) == 1 && isprint(*name
))
1622 get_key_name(int key_value
)
1624 static char key_char
[] = "'X'";
1625 const char *seq
= NULL
;
1628 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1629 if (key_table
[key
].value
== key_value
)
1630 seq
= key_table
[key
].name
;
1634 isprint(key_value
)) {
1635 key_char
[1] = (char) key_value
;
1639 return seq
? seq
: "(no key)";
1643 append_key(char *buf
, size_t *pos
, const struct keybinding
*keybinding
)
1645 const char *sep
= *pos
> 0 ? ", " : "";
1646 const char *keyname
= get_key_name(keybinding
->alias
);
1648 return string_nformat(buf
, BUFSIZ
, pos
, "%s%s", sep
, keyname
);
1652 append_keymap_request_keys(char *buf
, size_t *pos
, enum request request
,
1653 enum keymap keymap
, bool all
)
1657 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1658 if (keybindings
[keymap
].data
[i
].request
== request
) {
1659 if (!append_key(buf
, pos
, &keybindings
[keymap
].data
[i
]))
1669 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1672 get_keys(enum keymap keymap
, enum request request
, bool all
)
1674 static char buf
[BUFSIZ
];
1680 if (!append_keymap_request_keys(buf
, &pos
, request
, keymap
, all
))
1681 return "Too many keybindings!";
1682 if (pos
> 0 && !all
)
1685 if (keymap
!= KEYMAP_GENERIC
) {
1686 /* Only the generic keymap includes the default keybindings when
1687 * listing all keys. */
1691 if (!append_keymap_request_keys(buf
, &pos
, request
, KEYMAP_GENERIC
, all
))
1692 return "Too many keybindings!";
1697 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1698 if (default_keybindings
[i
].request
== request
) {
1699 if (!append_key(buf
, &pos
, &default_keybindings
[i
]))
1700 return "Too many keybindings!";
1709 struct run_request
{
1715 static struct run_request
*run_request
;
1716 static size_t run_requests
;
1718 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1721 add_run_request(enum keymap keymap
, int key
, const char **argv
)
1723 struct run_request
*req
;
1725 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1728 req
= &run_request
[run_requests
];
1729 req
->keymap
= keymap
;
1733 if (!argv_copy(&req
->argv
, argv
))
1736 return REQ_NONE
+ ++run_requests
;
1739 static struct run_request
*
1740 get_run_request(enum request request
)
1742 if (request
<= REQ_NONE
)
1744 return &run_request
[request
- REQ_NONE
- 1];
1748 add_builtin_run_requests(void)
1750 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1751 const char *checkout
[] = { "git", "checkout", "%(branch)", NULL
};
1752 const char *commit
[] = { "git", "commit", NULL
};
1753 const char *gc
[] = { "git", "gc", NULL
};
1754 struct run_request reqs
[] = {
1755 { KEYMAP_MAIN
, 'C', cherry_pick
},
1756 { KEYMAP_STATUS
, 'C', commit
},
1757 { KEYMAP_BRANCH
, 'C', checkout
},
1758 { KEYMAP_GENERIC
, 'G', gc
},
1762 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1763 enum request req
= get_keybinding(reqs
[i
].keymap
, reqs
[i
].key
);
1765 if (req
!= reqs
[i
].key
)
1767 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argv
);
1768 if (req
!= REQ_NONE
)
1769 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1774 * User config file handling.
1777 static int config_lineno
;
1778 static bool config_errors
;
1779 static const char *config_msg
;
1781 static const struct enum_map color_map
[] = {
1782 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1794 static const struct enum_map attr_map
[] = {
1795 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1802 ATTR_MAP(UNDERLINE
),
1805 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1807 static int parse_step(double *opt
, const char *arg
)
1810 if (!strchr(arg
, '%'))
1813 /* "Shift down" so 100% and 1 does not conflict. */
1814 *opt
= (*opt
- 1) / 100;
1817 config_msg
= "Step value larger than 100%";
1822 config_msg
= "Invalid step value";
1829 parse_int(int *opt
, const char *arg
, int min
, int max
)
1831 int value
= atoi(arg
);
1833 if (min
<= value
&& value
<= max
) {
1838 config_msg
= "Integer value out of bound";
1843 set_color(int *color
, const char *name
)
1845 if (map_enum(color
, color_map
, name
))
1847 if (!prefixcmp(name
, "color"))
1848 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1852 /* Wants: object fgcolor bgcolor [attribute] */
1854 option_color_command(int argc
, const char *argv
[])
1856 struct line_info
*info
;
1859 config_msg
= "Wrong number of arguments given to color command";
1863 info
= get_line_info(argv
[0]);
1865 static const struct enum_map obsolete
[] = {
1866 ENUM_MAP("main-delim", LINE_DELIMITER
),
1867 ENUM_MAP("main-date", LINE_DATE
),
1868 ENUM_MAP("main-author", LINE_AUTHOR
),
1872 if (!map_enum(&index
, obsolete
, argv
[0])) {
1873 config_msg
= "Unknown color name";
1876 info
= &line_info
[index
];
1879 if (!set_color(&info
->fg
, argv
[1]) ||
1880 !set_color(&info
->bg
, argv
[2])) {
1881 config_msg
= "Unknown color";
1886 while (argc
-- > 3) {
1889 if (!set_attribute(&attr
, argv
[argc
])) {
1890 config_msg
= "Unknown attribute";
1899 static int parse_bool(bool *opt
, const char *arg
)
1901 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1906 static int parse_enum_do(unsigned int *opt
, const char *arg
,
1907 const struct enum_map
*map
, size_t map_size
)
1911 assert(map_size
> 1);
1913 if (map_enum_do(map
, map_size
, (int *) opt
, arg
))
1916 if (parse_bool(&is_true
, arg
) != OK
)
1919 *opt
= is_true
? map
[1].value
: map
[0].value
;
1923 #define parse_enum(opt, arg, map) \
1924 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1927 parse_string(char *opt
, const char *arg
, size_t optsize
)
1929 int arglen
= strlen(arg
);
1934 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1935 config_msg
= "Unmatched quotation";
1938 arg
+= 1; arglen
-= 2;
1940 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1945 /* Wants: name = value */
1947 option_set_command(int argc
, const char *argv
[])
1950 config_msg
= "Wrong number of arguments given to set command";
1954 if (strcmp(argv
[1], "=")) {
1955 config_msg
= "No value assigned";
1959 if (!strcmp(argv
[0], "show-author"))
1960 return parse_enum(&opt_author
, argv
[2], author_map
);
1962 if (!strcmp(argv
[0], "show-date"))
1963 return parse_enum(&opt_date
, argv
[2], date_map
);
1965 if (!strcmp(argv
[0], "show-rev-graph"))
1966 return parse_bool(&opt_rev_graph
, argv
[2]);
1968 if (!strcmp(argv
[0], "show-refs"))
1969 return parse_bool(&opt_show_refs
, argv
[2]);
1971 if (!strcmp(argv
[0], "show-line-numbers"))
1972 return parse_bool(&opt_line_number
, argv
[2]);
1974 if (!strcmp(argv
[0], "line-graphics"))
1975 return parse_bool(&opt_line_graphics
, argv
[2]);
1977 if (!strcmp(argv
[0], "line-number-interval"))
1978 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
1980 if (!strcmp(argv
[0], "author-width"))
1981 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
1983 if (!strcmp(argv
[0], "horizontal-scroll"))
1984 return parse_step(&opt_hscroll
, argv
[2]);
1986 if (!strcmp(argv
[0], "split-view-height"))
1987 return parse_step(&opt_scale_split_view
, argv
[2]);
1989 if (!strcmp(argv
[0], "tab-size"))
1990 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
1992 if (!strcmp(argv
[0], "commit-encoding"))
1993 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
1995 config_msg
= "Unknown variable name";
1999 /* Wants: mode request key */
2001 option_bind_command(int argc
, const char *argv
[])
2003 enum request request
;
2008 config_msg
= "Wrong number of arguments given to bind command";
2012 if (!set_keymap(&keymap
, argv
[0])) {
2013 config_msg
= "Unknown key map";
2017 key
= get_key_value(argv
[1]);
2019 config_msg
= "Unknown key";
2023 request
= get_request(argv
[2]);
2024 if (request
== REQ_UNKNOWN
) {
2025 static const struct enum_map obsolete
[] = {
2026 ENUM_MAP("cherry-pick", REQ_NONE
),
2027 ENUM_MAP("screen-resize", REQ_NONE
),
2028 ENUM_MAP("tree-parent", REQ_PARENT
),
2032 if (map_enum(&alias
, obsolete
, argv
[2])) {
2033 if (alias
!= REQ_NONE
)
2034 add_keybinding(keymap
, alias
, key
);
2035 config_msg
= "Obsolete request name";
2039 if (request
== REQ_UNKNOWN
&& *argv
[2]++ == '!')
2040 request
= add_run_request(keymap
, key
, argv
+ 2);
2041 if (request
== REQ_UNKNOWN
) {
2042 config_msg
= "Unknown request name";
2046 add_keybinding(keymap
, request
, key
);
2052 set_option(const char *opt
, char *value
)
2054 const char *argv
[SIZEOF_ARG
];
2057 if (!argv_from_string(argv
, &argc
, value
)) {
2058 config_msg
= "Too many option arguments";
2062 if (!strcmp(opt
, "color"))
2063 return option_color_command(argc
, argv
);
2065 if (!strcmp(opt
, "set"))
2066 return option_set_command(argc
, argv
);
2068 if (!strcmp(opt
, "bind"))
2069 return option_bind_command(argc
, argv
);
2071 config_msg
= "Unknown option command";
2076 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
2081 config_msg
= "Internal error";
2083 /* Check for comment markers, since read_properties() will
2084 * only ensure opt and value are split at first " \t". */
2085 optlen
= strcspn(opt
, "#");
2089 if (opt
[optlen
] != 0) {
2090 config_msg
= "No option value";
2094 /* Look for comment endings in the value. */
2095 size_t len
= strcspn(value
, "#");
2097 if (len
< valuelen
) {
2099 value
[valuelen
] = 0;
2102 status
= set_option(opt
, value
);
2105 if (status
== ERR
) {
2106 warn("Error on line %d, near '%.*s': %s",
2107 config_lineno
, (int) optlen
, opt
, config_msg
);
2108 config_errors
= TRUE
;
2111 /* Always keep going if errors are encountered. */
2116 load_option_file(const char *path
)
2120 /* It's OK that the file doesn't exist. */
2121 if (!io_open(&io
, "%s", path
))
2125 config_errors
= FALSE
;
2127 if (io_load(&io
, " \t", read_option
) == ERR
||
2128 config_errors
== TRUE
)
2129 warn("Errors while loading %s.", path
);
2135 const char *home
= getenv("HOME");
2136 const char *tigrc_user
= getenv("TIGRC_USER");
2137 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
2138 const char *tig_diff_opts
= getenv("TIG_DIFF_OPTS");
2139 char buf
[SIZEOF_STR
];
2142 tigrc_system
= SYSCONFDIR
"/tigrc";
2143 load_option_file(tigrc_system
);
2146 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
2150 load_option_file(tigrc_user
);
2152 /* Add _after_ loading config files to avoid adding run requests
2153 * that conflict with keybindings. */
2154 add_builtin_run_requests();
2156 if (!opt_diff_args
&& tig_diff_opts
&& *tig_diff_opts
) {
2157 static const char *diff_opts
[SIZEOF_ARG
] = { NULL
};
2160 if (!string_format(buf
, "%s", tig_diff_opts
) ||
2161 !argv_from_string(diff_opts
, &argc
, buf
))
2162 die("TIG_DIFF_OPTS contains too many arguments");
2163 else if (!argv_copy(&opt_diff_args
, diff_opts
))
2164 die("Failed to format TIG_DIFF_OPTS arguments");
2178 /* The display array of active views and the index of the current view. */
2179 static struct view
*display
[2];
2180 static unsigned int current_view
;
2182 #define foreach_displayed_view(view, i) \
2183 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2185 #define displayed_views() (display[1] != NULL ? 2 : 1)
2187 /* Current head and commit ID */
2188 static char ref_blob
[SIZEOF_REF
] = "";
2189 static char ref_commit
[SIZEOF_REF
] = "HEAD";
2190 static char ref_head
[SIZEOF_REF
] = "HEAD";
2191 static char ref_branch
[SIZEOF_REF
] = "";
2208 enum view_type type
; /* View type */
2209 const char *name
; /* View name */
2210 const char *cmd_env
; /* Command line set via environment */
2211 const char *id
; /* Points to either of ref_{head,commit,blob} */
2213 struct view_ops
*ops
; /* View operations */
2215 enum keymap keymap
; /* What keymap does this view have */
2216 bool git_dir
; /* Whether the view requires a git directory. */
2218 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
2219 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
2221 int height
, width
; /* The width and height of the main window */
2222 WINDOW
*win
; /* The main window */
2223 WINDOW
*title
; /* The title window living below the main window */
2226 unsigned long offset
; /* Offset of the window top */
2227 unsigned long yoffset
; /* Offset from the window side. */
2228 unsigned long lineno
; /* Current line number */
2229 unsigned long p_offset
; /* Previous offset of the window top */
2230 unsigned long p_yoffset
;/* Previous offset from the window side */
2231 unsigned long p_lineno
; /* Previous current line number */
2232 bool p_restore
; /* Should the previous position be restored. */
2235 char grep
[SIZEOF_STR
]; /* Search string */
2236 regex_t
*regex
; /* Pre-compiled regexp */
2238 /* If non-NULL, points to the view that opened this view. If this view
2239 * is closed tig will switch back to the parent view. */
2240 struct view
*parent
;
2244 size_t lines
; /* Total number of lines */
2245 struct line
*line
; /* Line index */
2246 unsigned int digits
; /* Number of digits in the lines member. */
2249 struct line
*curline
; /* Line currently being drawn. */
2250 enum line_type curtype
; /* Attribute currently used for drawing. */
2251 unsigned long col
; /* Column when drawing. */
2252 bool has_scrolled
; /* View was scrolled. */
2255 const char **argv
; /* Shell command arguments. */
2256 const char *dir
; /* Directory from which to execute. */
2264 /* What type of content being displayed. Used in the title bar. */
2266 /* Default command arguments. */
2268 /* Open and reads in all view content. */
2269 bool (*open
)(struct view
*view
);
2270 /* Read one line; updates view->line. */
2271 bool (*read
)(struct view
*view
, char *data
);
2272 /* Draw one line; @lineno must be < view->height. */
2273 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
2274 /* Depending on view handle a special requests. */
2275 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
2276 /* Search for regexp in a line. */
2277 bool (*grep
)(struct view
*view
, struct line
*line
);
2279 void (*select
)(struct view
*view
, struct line
*line
);
2280 /* Prepare view for loading */
2281 bool (*prepare
)(struct view
*view
);
2284 static struct view_ops blame_ops
;
2285 static struct view_ops blob_ops
;
2286 static struct view_ops diff_ops
;
2287 static struct view_ops help_ops
;
2288 static struct view_ops log_ops
;
2289 static struct view_ops main_ops
;
2290 static struct view_ops pager_ops
;
2291 static struct view_ops stage_ops
;
2292 static struct view_ops status_ops
;
2293 static struct view_ops tree_ops
;
2294 static struct view_ops branch_ops
;
2296 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2297 { type, name, #env, ref, ops, map, git }
2299 #define VIEW_(id, name, ops, git, ref) \
2300 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2302 static struct view views
[] = {
2303 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
2304 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
2305 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
2306 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
2307 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
2308 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
2309 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
2310 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
2311 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
2312 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
2313 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
2316 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2318 #define foreach_view(view, i) \
2319 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2321 #define view_is_displayed(view) \
2322 (view == display[0] || view == display[1])
2325 view_request(struct view
*view
, enum request request
)
2327 if (!view
|| !view
->lines
)
2329 return view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
2338 set_view_attr(struct view
*view
, enum line_type type
)
2340 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
2341 (void) wattrset(view
->win
, get_line_attr(type
));
2342 wchgat(view
->win
, -1, 0, type
, NULL
);
2343 view
->curtype
= type
;
2348 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
2349 int max_len
, bool use_tilde
)
2351 static char out_buffer
[BUFSIZ
* 2];
2354 int trimmed
= FALSE
;
2355 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2360 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
, opt_tab_size
);
2362 set_view_attr(view
, type
);
2364 if (opt_iconv_out
!= ICONV_NONE
) {
2365 ICONV_CONST
char *inbuf
= (ICONV_CONST
char *) string
;
2366 size_t inlen
= len
+ 1;
2368 char *outbuf
= out_buffer
;
2369 size_t outlen
= sizeof(out_buffer
);
2373 ret
= iconv(opt_iconv_out
, &inbuf
, &inlen
, &outbuf
, &outlen
);
2374 if (ret
!= (size_t) -1) {
2375 string
= out_buffer
;
2376 len
= sizeof(out_buffer
) - outlen
;
2380 waddnstr(view
->win
, string
, len
);
2382 if (trimmed
&& use_tilde
) {
2383 set_view_attr(view
, LINE_DELIMITER
);
2384 waddch(view
->win
, '~');
2392 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
2394 static char space
[] = " ";
2397 spaces
= MIN(max
, spaces
);
2399 while (spaces
> 0) {
2400 int len
= MIN(spaces
, sizeof(space
) - 1);
2402 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
2410 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
2412 char text
[SIZEOF_STR
];
2415 size_t pos
= string_expand(text
, sizeof(text
), string
, opt_tab_size
);
2417 view
->col
+= draw_chars(view
, type
, text
, view
->width
+ view
->yoffset
- view
->col
, trim
);
2419 } while (*string
&& view
->width
+ view
->yoffset
> view
->col
);
2421 return view
->width
+ view
->yoffset
<= view
->col
;
2425 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
2427 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2428 int max
= view
->width
+ view
->yoffset
- view
->col
;
2434 set_view_attr(view
, type
);
2435 /* Using waddch() instead of waddnstr() ensures that
2436 * they'll be rendered correctly for the cursor line. */
2437 for (i
= skip
; i
< size
; i
++)
2438 waddch(view
->win
, graphic
[i
]);
2441 if (size
< max
&& skip
<= size
)
2442 waddch(view
->win
, ' ');
2445 return view
->width
+ view
->yoffset
<= view
->col
;
2449 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2451 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2455 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2457 col
= draw_space(view
, type
, max
- 1, max
- 1);
2460 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2461 return view
->width
+ view
->yoffset
<= view
->col
;
2465 draw_date(struct view
*view
, struct time
*time
)
2467 const char *date
= mkdate(time
, opt_date
);
2468 int cols
= opt_date
== DATE_SHORT
? DATE_SHORT_COLS
: DATE_COLS
;
2470 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2474 draw_author(struct view
*view
, const char *author
)
2476 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5;
2477 bool abbreviate
= opt_author
== AUTHOR_ABBREVIATED
|| !trim
;
2479 if (abbreviate
&& author
)
2480 author
= get_author_initials(author
);
2482 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2486 draw_mode(struct view
*view
, mode_t mode
)
2492 else if (S_ISLNK(mode
))
2494 else if (S_ISGITLINK(mode
))
2496 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2498 else if (S_ISREG(mode
))
2503 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2507 draw_lineno(struct view
*view
, unsigned int lineno
)
2510 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2511 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2513 chtype separator
= opt_line_graphics
? ACS_VLINE
: '|';
2515 lineno
+= view
->offset
+ 1;
2516 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2517 static char fmt
[] = "%1ld";
2519 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2520 if (string_format(number
, fmt
, lineno
))
2524 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2526 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2527 return draw_graphic(view
, LINE_DEFAULT
, &separator
, 1);
2531 draw_view_line(struct view
*view
, unsigned int lineno
)
2534 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2536 assert(view_is_displayed(view
));
2538 if (view
->offset
+ lineno
>= view
->lines
)
2541 line
= &view
->line
[view
->offset
+ lineno
];
2543 wmove(view
->win
, lineno
, 0);
2545 wclrtoeol(view
->win
);
2547 view
->curline
= line
;
2548 view
->curtype
= LINE_NONE
;
2549 line
->selected
= FALSE
;
2550 line
->dirty
= line
->cleareol
= 0;
2553 set_view_attr(view
, LINE_CURSOR
);
2554 line
->selected
= TRUE
;
2555 view
->ops
->select(view
, line
);
2558 return view
->ops
->draw(view
, line
, lineno
);
2562 redraw_view_dirty(struct view
*view
)
2567 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2568 if (view
->offset
+ lineno
>= view
->lines
)
2570 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2573 if (!draw_view_line(view
, lineno
))
2579 wnoutrefresh(view
->win
);
2583 redraw_view_from(struct view
*view
, int lineno
)
2585 assert(0 <= lineno
&& lineno
< view
->height
);
2587 for (; lineno
< view
->height
; lineno
++) {
2588 if (!draw_view_line(view
, lineno
))
2592 wnoutrefresh(view
->win
);
2596 redraw_view(struct view
*view
)
2599 redraw_view_from(view
, 0);
2604 update_view_title(struct view
*view
)
2606 char buf
[SIZEOF_STR
];
2607 char state
[SIZEOF_STR
];
2608 size_t bufpos
= 0, statelen
= 0;
2610 assert(view_is_displayed(view
));
2612 if (view
->type
!= VIEW_STATUS
&& view
->lines
) {
2613 unsigned int view_lines
= view
->offset
+ view
->height
;
2614 unsigned int lines
= view
->lines
2615 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2618 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2627 time_t secs
= time(NULL
) - view
->start_time
;
2629 /* Three git seconds are a long time ... */
2631 string_format_from(state
, &statelen
, " loading %lds", secs
);
2634 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2635 if (*view
->ref
&& bufpos
< view
->width
) {
2636 size_t refsize
= strlen(view
->ref
);
2637 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2639 if (minsize
< view
->width
)
2640 refsize
= view
->width
- minsize
+ 7;
2641 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2644 if (statelen
&& bufpos
< view
->width
) {
2645 string_format_from(buf
, &bufpos
, "%s", state
);
2648 if (view
== display
[current_view
])
2649 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2651 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2653 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2654 wclrtoeol(view
->title
);
2655 wnoutrefresh(view
->title
);
2659 apply_step(double step
, int value
)
2663 value
*= step
+ 0.01;
2664 return value
? value
: 1;
2668 resize_display(void)
2671 struct view
*base
= display
[0];
2672 struct view
*view
= display
[1] ? display
[1] : display
[0];
2674 /* Setup window dimensions */
2676 getmaxyx(stdscr
, base
->height
, base
->width
);
2678 /* Make room for the status window. */
2682 /* Horizontal split. */
2683 view
->width
= base
->width
;
2684 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2685 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2686 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2687 base
->height
-= view
->height
;
2689 /* Make room for the title bar. */
2693 /* Make room for the title bar. */
2698 foreach_displayed_view (view
, i
) {
2700 view
->win
= newwin(view
->height
, 0, offset
, 0);
2702 die("Failed to create %s view", view
->name
);
2704 scrollok(view
->win
, FALSE
);
2706 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2708 die("Failed to create title window");
2711 wresize(view
->win
, view
->height
, view
->width
);
2712 mvwin(view
->win
, offset
, 0);
2713 mvwin(view
->title
, offset
+ view
->height
, 0);
2716 offset
+= view
->height
+ 1;
2721 redraw_display(bool clear
)
2726 foreach_displayed_view (view
, i
) {
2730 update_view_title(view
);
2740 toggle_enum_option_do(unsigned int *opt
, const char *help
,
2741 const struct enum_map
*map
, size_t size
)
2743 *opt
= (*opt
+ 1) % size
;
2744 redraw_display(FALSE
);
2745 report("Displaying %s %s", enum_name(map
[*opt
]), help
);
2748 #define toggle_enum_option(opt, help, map) \
2749 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2751 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2752 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2755 toggle_view_option(bool *option
, const char *help
)
2758 redraw_display(FALSE
);
2759 report("%sabling %s", *option
? "En" : "Dis", help
);
2763 open_option_menu(void)
2765 const struct menu_item menu
[] = {
2766 { '.', "line numbers", &opt_line_number
},
2767 { 'D', "date display", &opt_date
},
2768 { 'A', "author display", &opt_author
},
2769 { 'g', "revision graph display", &opt_rev_graph
},
2770 { 'F', "reference display", &opt_show_refs
},
2775 if (prompt_menu("Toggle option", menu
, &selected
)) {
2776 if (menu
[selected
].data
== &opt_date
)
2778 else if (menu
[selected
].data
== &opt_author
)
2781 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2786 maximize_view(struct view
*view
)
2788 memset(display
, 0, sizeof(display
));
2790 display
[current_view
] = view
;
2792 redraw_display(FALSE
);
2802 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2804 if (lineno
>= view
->lines
)
2805 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2807 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2808 unsigned long half
= view
->height
/ 2;
2811 offset
= lineno
- half
;
2816 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2817 view
->offset
= offset
;
2818 view
->lineno
= lineno
;
2825 /* Scrolling backend */
2827 do_scroll_view(struct view
*view
, int lines
)
2829 bool redraw_current_line
= FALSE
;
2831 /* The rendering expects the new offset. */
2832 view
->offset
+= lines
;
2834 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2837 /* Move current line into the view. */
2838 if (view
->lineno
< view
->offset
) {
2839 view
->lineno
= view
->offset
;
2840 redraw_current_line
= TRUE
;
2841 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2842 view
->lineno
= view
->offset
+ view
->height
- 1;
2843 redraw_current_line
= TRUE
;
2846 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2848 /* Redraw the whole screen if scrolling is pointless. */
2849 if (view
->height
< ABS(lines
)) {
2853 int line
= lines
> 0 ? view
->height
- lines
: 0;
2854 int end
= line
+ ABS(lines
);
2856 scrollok(view
->win
, TRUE
);
2857 wscrl(view
->win
, lines
);
2858 scrollok(view
->win
, FALSE
);
2860 while (line
< end
&& draw_view_line(view
, line
))
2863 if (redraw_current_line
)
2864 draw_view_line(view
, view
->lineno
- view
->offset
);
2865 wnoutrefresh(view
->win
);
2868 view
->has_scrolled
= TRUE
;
2872 /* Scroll frontend */
2874 scroll_view(struct view
*view
, enum request request
)
2878 assert(view_is_displayed(view
));
2881 case REQ_SCROLL_LEFT
:
2882 if (view
->yoffset
== 0) {
2883 report("Cannot scroll beyond the first column");
2886 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2889 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2890 redraw_view_from(view
, 0);
2893 case REQ_SCROLL_RIGHT
:
2894 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2898 case REQ_SCROLL_PAGE_DOWN
:
2899 lines
= view
->height
;
2900 case REQ_SCROLL_LINE_DOWN
:
2901 if (view
->offset
+ lines
> view
->lines
)
2902 lines
= view
->lines
- view
->offset
;
2904 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2905 report("Cannot scroll beyond the last line");
2910 case REQ_SCROLL_PAGE_UP
:
2911 lines
= view
->height
;
2912 case REQ_SCROLL_LINE_UP
:
2913 if (lines
> view
->offset
)
2914 lines
= view
->offset
;
2917 report("Cannot scroll beyond the first line");
2925 die("request %d not handled in switch", request
);
2928 do_scroll_view(view
, lines
);
2933 move_view(struct view
*view
, enum request request
)
2935 int scroll_steps
= 0;
2939 case REQ_MOVE_FIRST_LINE
:
2940 steps
= -view
->lineno
;
2943 case REQ_MOVE_LAST_LINE
:
2944 steps
= view
->lines
- view
->lineno
- 1;
2947 case REQ_MOVE_PAGE_UP
:
2948 steps
= view
->height
> view
->lineno
2949 ? -view
->lineno
: -view
->height
;
2952 case REQ_MOVE_PAGE_DOWN
:
2953 steps
= view
->lineno
+ view
->height
>= view
->lines
2954 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2966 die("request %d not handled in switch", request
);
2969 if (steps
<= 0 && view
->lineno
== 0) {
2970 report("Cannot move beyond the first line");
2973 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2974 report("Cannot move beyond the last line");
2978 /* Move the current line */
2979 view
->lineno
+= steps
;
2980 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2982 /* Check whether the view needs to be scrolled */
2983 if (view
->lineno
< view
->offset
||
2984 view
->lineno
>= view
->offset
+ view
->height
) {
2985 scroll_steps
= steps
;
2986 if (steps
< 0 && -steps
> view
->offset
) {
2987 scroll_steps
= -view
->offset
;
2989 } else if (steps
> 0) {
2990 if (view
->lineno
== view
->lines
- 1 &&
2991 view
->lines
> view
->height
) {
2992 scroll_steps
= view
->lines
- view
->offset
- 1;
2993 if (scroll_steps
>= view
->height
)
2994 scroll_steps
-= view
->height
- 1;
2999 if (!view_is_displayed(view
)) {
3000 view
->offset
+= scroll_steps
;
3001 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
3002 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
3006 /* Repaint the old "current" line if we be scrolling */
3007 if (ABS(steps
) < view
->height
)
3008 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
3011 do_scroll_view(view
, scroll_steps
);
3015 /* Draw the current line */
3016 draw_view_line(view
, view
->lineno
- view
->offset
);
3018 wnoutrefresh(view
->win
);
3027 static void search_view(struct view
*view
, enum request request
);
3030 grep_text(struct view
*view
, const char *text
[])
3035 for (i
= 0; text
[i
]; i
++)
3037 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
3043 select_view_line(struct view
*view
, unsigned long lineno
)
3045 unsigned long old_lineno
= view
->lineno
;
3046 unsigned long old_offset
= view
->offset
;
3048 if (goto_view_line(view
, view
->offset
, lineno
)) {
3049 if (view_is_displayed(view
)) {
3050 if (old_offset
!= view
->offset
) {
3053 draw_view_line(view
, old_lineno
- view
->offset
);
3054 draw_view_line(view
, view
->lineno
- view
->offset
);
3055 wnoutrefresh(view
->win
);
3058 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
3064 find_next(struct view
*view
, enum request request
)
3066 unsigned long lineno
= view
->lineno
;
3071 report("No previous search");
3073 search_view(view
, request
);
3083 case REQ_SEARCH_BACK
:
3092 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
3093 lineno
+= direction
;
3095 /* Note, lineno is unsigned long so will wrap around in which case it
3096 * will become bigger than view->lines. */
3097 for (; lineno
< view
->lines
; lineno
+= direction
) {
3098 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
3099 select_view_line(view
, lineno
);
3100 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
3105 report("No match found for '%s'", view
->grep
);
3109 search_view(struct view
*view
, enum request request
)
3114 regfree(view
->regex
);
3117 view
->regex
= calloc(1, sizeof(*view
->regex
));
3122 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
3123 if (regex_err
!= 0) {
3124 char buf
[SIZEOF_STR
] = "unknown error";
3126 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
3127 report("Search failed: %s", buf
);
3131 string_copy(view
->grep
, opt_search
);
3133 find_next(view
, request
);
3137 * Incremental updating
3141 reset_view(struct view
*view
)
3145 for (i
= 0; i
< view
->lines
; i
++)
3146 free(view
->line
[i
].data
);
3149 view
->p_offset
= view
->offset
;
3150 view
->p_yoffset
= view
->yoffset
;
3151 view
->p_lineno
= view
->lineno
;
3159 view
->update_secs
= 0;
3163 format_arg(const char *name
)
3169 const char *value_if_empty
;
3171 #define FORMAT_VAR(name, value, value_if_empty) \
3172 { name, STRING_SIZE(name), value, value_if_empty }
3173 FORMAT_VAR("%(directory)", opt_path
, ""),
3174 FORMAT_VAR("%(file)", opt_file
, ""),
3175 FORMAT_VAR("%(ref)", opt_ref
, "HEAD"),
3176 FORMAT_VAR("%(head)", ref_head
, ""),
3177 FORMAT_VAR("%(commit)", ref_commit
, ""),
3178 FORMAT_VAR("%(blob)", ref_blob
, ""),
3179 FORMAT_VAR("%(branch)", ref_branch
, ""),
3183 for (i
= 0; i
< ARRAY_SIZE(vars
); i
++)
3184 if (!strncmp(name
, vars
[i
].name
, vars
[i
].namelen
))
3185 return *vars
[i
].value
? vars
[i
].value
: vars
[i
].value_if_empty
;
3187 report("Unknown replacement: `%s`", name
);
3192 format_argv(const char ***dst_argv
, const char *src_argv
[], bool replace
)
3194 char buf
[SIZEOF_STR
];
3197 argv_free(*dst_argv
);
3199 for (argc
= 0; src_argv
[argc
]; argc
++) {
3200 const char *arg
= src_argv
[argc
];
3203 if (!strcmp(arg
, "%(file-args)")) {
3204 if (!argv_append_array(dst_argv
, opt_file_args
))
3208 } else if (!strcmp(arg
, "%(diff-args)")) {
3209 if (!argv_append_array(dst_argv
, opt_diff_args
))
3213 } else if (!strcmp(arg
, "%(rev-args)")) {
3214 if (!argv_append_array(dst_argv
, opt_rev_args
))
3220 char *next
= strstr(arg
, "%(");
3221 int len
= next
- arg
;
3224 if (!next
|| !replace
) {
3229 value
= format_arg(next
);
3236 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
3239 arg
= next
&& replace
? strchr(next
, ')') + 1 : NULL
;
3242 if (!argv_append(dst_argv
, buf
))
3246 return src_argv
[argc
] == NULL
;
3250 restore_view_position(struct view
*view
)
3252 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
3255 /* Changing the view position cancels the restoring. */
3256 /* FIXME: Changing back to the first line is not detected. */
3257 if (view
->offset
!= 0 || view
->lineno
!= 0) {
3258 view
->p_restore
= FALSE
;
3262 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
3263 view_is_displayed(view
))
3266 view
->yoffset
= view
->p_yoffset
;
3267 view
->p_restore
= FALSE
;
3273 end_update(struct view
*view
, bool force
)
3277 while (!view
->ops
->read(view
, NULL
))
3281 io_kill(view
->pipe
);
3282 io_done(view
->pipe
);
3287 setup_update(struct view
*view
, const char *vid
)
3290 string_copy_rev(view
->vid
, vid
);
3291 view
->pipe
= &view
->io
;
3292 view
->start_time
= time(NULL
);
3296 prepare_io(struct view
*view
, const char *dir
, const char *argv
[], bool replace
)
3299 return format_argv(&view
->argv
, argv
, replace
);
3303 prepare_update(struct view
*view
, const char *argv
[], const char *dir
)
3306 end_update(view
, TRUE
);
3307 return prepare_io(view
, dir
, argv
, FALSE
);
3311 start_update(struct view
*view
, const char **argv
, const char *dir
)
3314 io_done(view
->pipe
);
3315 return prepare_io(view
, dir
, argv
, FALSE
) &&
3316 io_run(&view
->io
, IO_RD
, dir
, view
->argv
);
3320 prepare_update_file(struct view
*view
, const char *name
)
3323 end_update(view
, TRUE
);
3324 argv_free(view
->argv
);
3325 return io_open(&view
->io
, "%s/%s", opt_cdup
[0] ? opt_cdup
: ".", name
);
3329 begin_update(struct view
*view
, bool refresh
)
3332 end_update(view
, TRUE
);
3335 if (view
->ops
->prepare
) {
3336 if (!view
->ops
->prepare(view
))
3338 } else if (!prepare_io(view
, NULL
, view
->ops
->argv
, TRUE
)) {
3342 /* Put the current ref_* value to the view title ref
3343 * member. This is needed by the blob view. Most other
3344 * views sets it automatically after loading because the
3345 * first line is a commit line. */
3346 string_copy_rev(view
->ref
, view
->id
);
3349 if (view
->argv
&& view
->argv
[0] &&
3350 !io_run(&view
->io
, IO_RD
, view
->dir
, view
->argv
))
3353 setup_update(view
, view
->id
);
3359 update_view(struct view
*view
)
3361 char out_buffer
[BUFSIZ
* 2];
3363 /* Clear the view and redraw everything since the tree sorting
3364 * might have rearranged things. */
3365 bool redraw
= view
->lines
== 0;
3366 bool can_read
= TRUE
;
3371 if (!io_can_read(view
->pipe
)) {
3372 if (view
->lines
== 0 && view_is_displayed(view
)) {
3373 time_t secs
= time(NULL
) - view
->start_time
;
3375 if (secs
> 1 && secs
> view
->update_secs
) {
3376 if (view
->update_secs
== 0)
3378 update_view_title(view
);
3379 view
->update_secs
= secs
;
3385 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
3386 if (opt_iconv_in
!= ICONV_NONE
) {
3387 ICONV_CONST
char *inbuf
= line
;
3388 size_t inlen
= strlen(line
) + 1;
3390 char *outbuf
= out_buffer
;
3391 size_t outlen
= sizeof(out_buffer
);
3395 ret
= iconv(opt_iconv_in
, &inbuf
, &inlen
, &outbuf
, &outlen
);
3396 if (ret
!= (size_t) -1)
3400 if (!view
->ops
->read(view
, line
)) {
3401 report("Allocation failure");
3402 end_update(view
, TRUE
);
3408 unsigned long lines
= view
->lines
;
3411 for (digits
= 0; lines
; digits
++)
3414 /* Keep the displayed view in sync with line number scaling. */
3415 if (digits
!= view
->digits
) {
3416 view
->digits
= digits
;
3417 if (opt_line_number
|| view
->type
== VIEW_BLAME
)
3422 if (io_error(view
->pipe
)) {
3423 report("Failed to read: %s", io_strerror(view
->pipe
));
3424 end_update(view
, TRUE
);
3426 } else if (io_eof(view
->pipe
)) {
3427 if (view_is_displayed(view
))
3429 end_update(view
, FALSE
);
3432 if (restore_view_position(view
))
3435 if (!view_is_displayed(view
))
3439 redraw_view_from(view
, 0);
3441 redraw_view_dirty(view
);
3443 /* Update the title _after_ the redraw so that if the redraw picks up a
3444 * commit reference in view->ref it'll be available here. */
3445 update_view_title(view
);
3449 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
3451 static struct line
*
3452 add_line_data(struct view
*view
, void *data
, enum line_type type
)
3456 if (!realloc_lines(&view
->line
, view
->lines
, 1))
3459 line
= &view
->line
[view
->lines
++];
3460 memset(line
, 0, sizeof(*line
));
3468 static struct line
*
3469 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
3471 char *data
= text
? strdup(text
) : NULL
;
3473 return data
? add_line_data(view
, data
, type
) : NULL
;
3476 static struct line
*
3477 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3479 char buf
[SIZEOF_STR
];
3482 va_start(args
, fmt
);
3483 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3487 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3495 OPEN_DEFAULT
= 0, /* Use default view switching. */
3496 OPEN_SPLIT
= 1, /* Split current view. */
3497 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3498 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3499 OPEN_PREPARED
= 32, /* Open already prepared command. */
3503 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3505 bool split
= !!(flags
& OPEN_SPLIT
);
3506 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3507 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3508 struct view
*view
= VIEW(request
);
3509 int nviews
= displayed_views();
3510 struct view
*base_view
= display
[0];
3512 if (view
== prev
&& nviews
== 1 && !reload
) {
3513 report("Already in %s view", view
->name
);
3517 if (view
->git_dir
&& !opt_git_dir
[0]) {
3518 report("The %s view is disabled in pager view", view
->name
);
3525 view
->parent
= prev
;
3526 } else if (!nomaximize
) {
3527 /* Maximize the current view. */
3528 memset(display
, 0, sizeof(display
));
3530 display
[current_view
] = view
;
3533 /* No prev signals that this is the first loaded view. */
3534 if (prev
&& view
!= prev
) {
3538 /* Resize the view when switching between split- and full-screen,
3539 * or when switching between two different full-screen views. */
3540 if (nviews
!= displayed_views() ||
3541 (nviews
== 1 && base_view
!= display
[0]))
3544 if (view
->ops
->open
) {
3546 end_update(view
, TRUE
);
3547 if (!view
->ops
->open(view
)) {
3548 report("Failed to load %s view", view
->name
);
3551 restore_view_position(view
);
3553 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3554 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3555 report("Failed to load %s view", view
->name
);
3559 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3560 /* Take the title line into account. */
3561 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3563 /* Scroll the view that was split if the current line is
3564 * outside the new limited view. */
3565 do_scroll_view(prev
, lines
);
3568 if (prev
&& view
!= prev
&& split
&& view_is_displayed(prev
)) {
3569 /* "Blur" the previous view. */
3570 update_view_title(prev
);
3573 if (view
->pipe
&& view
->lines
== 0) {
3574 /* Clear the old view and let the incremental updating refill
3577 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3579 } else if (view_is_displayed(view
)) {
3586 open_external_viewer(const char *argv
[], const char *dir
)
3588 def_prog_mode(); /* save current tty modes */
3589 endwin(); /* restore original tty modes */
3590 io_run_fg(argv
, dir
);
3591 fprintf(stderr
, "Press Enter to continue");
3594 redraw_display(TRUE
);
3598 open_mergetool(const char *file
)
3600 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3602 open_external_viewer(mergetool_argv
, opt_cdup
);
3606 open_editor(const char *file
)
3608 const char *editor_argv
[] = { "vi", file
, NULL
};
3611 editor
= getenv("GIT_EDITOR");
3612 if (!editor
&& *opt_editor
)
3613 editor
= opt_editor
;
3615 editor
= getenv("VISUAL");
3617 editor
= getenv("EDITOR");
3621 editor_argv
[0] = editor
;
3622 open_external_viewer(editor_argv
, opt_cdup
);
3626 open_run_request(enum request request
)
3628 struct run_request
*req
= get_run_request(request
);
3629 const char **argv
= NULL
;
3632 report("Unknown run request");
3636 if (format_argv(&argv
, req
->argv
, TRUE
))
3637 open_external_viewer(argv
, NULL
);
3644 * User request switch noodle
3648 view_driver(struct view
*view
, enum request request
)
3652 if (request
== REQ_NONE
)
3655 if (request
> REQ_NONE
) {
3656 open_run_request(request
);
3657 view_request(view
, REQ_REFRESH
);
3661 request
= view_request(view
, request
);
3662 if (request
== REQ_NONE
)
3668 case REQ_MOVE_PAGE_UP
:
3669 case REQ_MOVE_PAGE_DOWN
:
3670 case REQ_MOVE_FIRST_LINE
:
3671 case REQ_MOVE_LAST_LINE
:
3672 move_view(view
, request
);
3675 case REQ_SCROLL_LEFT
:
3676 case REQ_SCROLL_RIGHT
:
3677 case REQ_SCROLL_LINE_DOWN
:
3678 case REQ_SCROLL_LINE_UP
:
3679 case REQ_SCROLL_PAGE_DOWN
:
3680 case REQ_SCROLL_PAGE_UP
:
3681 scroll_view(view
, request
);
3684 case REQ_VIEW_BLAME
:
3686 report("No file chosen, press %s to open tree view",
3687 get_key(view
->keymap
, REQ_VIEW_TREE
));
3690 open_view(view
, request
, OPEN_DEFAULT
);
3695 report("No file chosen, press %s to open tree view",
3696 get_key(view
->keymap
, REQ_VIEW_TREE
));
3699 open_view(view
, request
, OPEN_DEFAULT
);
3702 case REQ_VIEW_PAGER
:
3703 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3704 report("No pager content, press %s to run command from prompt",
3705 get_key(view
->keymap
, REQ_PROMPT
));
3708 open_view(view
, request
, OPEN_DEFAULT
);
3711 case REQ_VIEW_STAGE
:
3712 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3713 report("No stage content, press %s to open the status view and choose file",
3714 get_key(view
->keymap
, REQ_VIEW_STATUS
));
3717 open_view(view
, request
, OPEN_DEFAULT
);
3720 case REQ_VIEW_STATUS
:
3721 if (opt_is_inside_work_tree
== FALSE
) {
3722 report("The status view requires a working tree");
3725 open_view(view
, request
, OPEN_DEFAULT
);
3733 case REQ_VIEW_BRANCH
:
3734 open_view(view
, request
, OPEN_DEFAULT
);
3739 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3744 view
= view
->parent
;
3745 line
= view
->lineno
;
3746 move_view(view
, request
);
3747 if (view_is_displayed(view
))
3748 update_view_title(view
);
3749 if (line
!= view
->lineno
)
3750 view_request(view
, REQ_ENTER
);
3752 move_view(view
, request
);
3758 int nviews
= displayed_views();
3759 int next_view
= (current_view
+ 1) % nviews
;
3761 if (next_view
== current_view
) {
3762 report("Only one view is displayed");
3766 current_view
= next_view
;
3767 /* Blur out the title of the previous view. */
3768 update_view_title(view
);
3773 report("Refreshing is not yet supported for the %s view", view
->name
);
3777 if (displayed_views() == 2)
3778 maximize_view(view
);
3785 case REQ_TOGGLE_LINENO
:
3786 toggle_view_option(&opt_line_number
, "line numbers");
3789 case REQ_TOGGLE_DATE
:
3793 case REQ_TOGGLE_AUTHOR
:
3797 case REQ_TOGGLE_REV_GRAPH
:
3798 toggle_view_option(&opt_rev_graph
, "revision graph display");
3801 case REQ_TOGGLE_REFS
:
3802 toggle_view_option(&opt_show_refs
, "reference display");
3805 case REQ_TOGGLE_SORT_FIELD
:
3806 case REQ_TOGGLE_SORT_ORDER
:
3807 report("Sorting is not yet supported for the %s view", view
->name
);
3811 case REQ_SEARCH_BACK
:
3812 search_view(view
, request
);
3817 find_next(view
, request
);
3820 case REQ_STOP_LOADING
:
3821 foreach_view(view
, i
) {
3823 report("Stopped loading the %s view", view
->name
),
3824 end_update(view
, TRUE
);
3828 case REQ_SHOW_VERSION
:
3829 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3832 case REQ_SCREEN_REDRAW
:
3833 redraw_display(TRUE
);
3837 report("Nothing to edit");
3841 report("Nothing to enter");
3844 case REQ_VIEW_CLOSE
:
3845 /* XXX: Mark closed views by letting view->prev point to the
3846 * view itself. Parents to closed view should never be
3848 if (view
->prev
&& view
->prev
!= view
) {
3849 maximize_view(view
->prev
);
3858 report("Unknown key, press %s for help",
3859 get_key(view
->keymap
, REQ_VIEW_HELP
));
3868 * View backend utilities
3878 const enum sort_field
*fields
;
3879 size_t size
, current
;
3883 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3884 #define get_sort_field(state) ((state).fields[(state).current])
3885 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3888 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3889 int (*compare
)(const void *, const void *))
3892 case REQ_TOGGLE_SORT_FIELD
:
3893 state
->current
= (state
->current
+ 1) % state
->size
;
3896 case REQ_TOGGLE_SORT_ORDER
:
3897 state
->reverse
= !state
->reverse
;
3900 die("Not a sort request");
3903 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3907 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3909 /* Small author cache to reduce memory consumption. It uses binary
3910 * search to lookup or find place to position new entries. No entries
3911 * are ever freed. */
3913 get_author(const char *name
)
3915 static const char **authors
;
3916 static size_t authors_size
;
3917 int from
= 0, to
= authors_size
- 1;
3919 while (from
<= to
) {
3920 size_t pos
= (to
+ from
) / 2;
3921 int cmp
= strcmp(name
, authors
[pos
]);
3924 return authors
[pos
];
3932 if (!realloc_authors(&authors
, authors_size
, 1))
3934 name
= strdup(name
);
3938 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3939 authors
[from
] = name
;
3946 parse_timesec(struct time
*time
, const char *sec
)
3948 time
->sec
= (time_t) atol(sec
);
3952 parse_timezone(struct time
*time
, const char *zone
)
3956 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3957 tz
+= ('0' - zone
[2]) * 60 * 60;
3958 tz
+= ('0' - zone
[3]) * 60 * 10;
3959 tz
+= ('0' - zone
[4]) * 60;
3968 /* Parse author lines where the name may be empty:
3969 * author <email@address.tld> 1138474660 +0100
3972 parse_author_line(char *ident
, const char **author
, struct time
*time
)
3974 char *nameend
= strchr(ident
, '<');
3975 char *emailend
= strchr(ident
, '>');
3977 if (nameend
&& emailend
)
3978 *nameend
= *emailend
= 0;
3979 ident
= chomp_string(ident
);
3982 ident
= chomp_string(nameend
+ 1);
3987 *author
= get_author(ident
);
3989 /* Parse epoch and timezone */
3990 if (emailend
&& emailend
[1] == ' ') {
3991 char *secs
= emailend
+ 2;
3992 char *zone
= strchr(secs
, ' ');
3994 parse_timesec(time
, secs
);
3996 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
3997 parse_timezone(time
, zone
+ 1);
4006 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4008 if (opt_line_number
&& draw_lineno(view
, lineno
))
4011 draw_text(view
, line
->type
, line
->data
, TRUE
);
4016 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
4018 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
4019 char ref
[SIZEOF_STR
];
4021 if (!io_run_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
4024 /* This is the only fatal call, since it can "corrupt" the buffer. */
4025 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
4032 add_pager_refs(struct view
*view
, struct line
*line
)
4034 char buf
[SIZEOF_STR
];
4035 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
4036 struct ref_list
*list
;
4037 size_t bufpos
= 0, i
;
4038 const char *sep
= "Refs: ";
4039 bool is_tag
= FALSE
;
4041 assert(line
->type
== LINE_COMMIT
);
4043 list
= get_ref_list(commit_id
);
4045 if (view
->type
== VIEW_DIFF
)
4046 goto try_add_describe_ref
;
4050 for (i
= 0; i
< list
->size
; i
++) {
4051 struct ref
*ref
= list
->refs
[i
];
4052 const char *fmt
= ref
->tag
? "%s[%s]" :
4053 ref
->remote
? "%s<%s>" : "%s%s";
4055 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
4062 if (!is_tag
&& view
->type
== VIEW_DIFF
) {
4063 try_add_describe_ref
:
4064 /* Add <tag>-g<commit_id> "fake" reference. */
4065 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
4072 add_line_text(view
, buf
, LINE_PP_REFS
);
4076 pager_read(struct view
*view
, char *data
)
4083 line
= add_line_text(view
, data
, get_line_type(data
));
4087 if (line
->type
== LINE_COMMIT
&&
4088 (view
->type
== VIEW_DIFF
||
4089 view
->type
== VIEW_LOG
))
4090 add_pager_refs(view
, line
);
4096 pager_request(struct view
*view
, enum request request
, struct line
*line
)
4100 if (request
!= REQ_ENTER
)
4103 if (line
->type
== LINE_COMMIT
&&
4104 (view
->type
== VIEW_LOG
||
4105 view
->type
== VIEW_PAGER
)) {
4106 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
4110 /* Always scroll the view even if it was split. That way
4111 * you can use Enter to scroll through the log view and
4112 * split open each commit diff. */
4113 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
4115 /* FIXME: A minor workaround. Scrolling the view will call report("")
4116 * but if we are scrolling a non-current view this won't properly
4117 * update the view title. */
4119 update_view_title(view
);
4125 pager_grep(struct view
*view
, struct line
*line
)
4127 const char *text
[] = { line
->data
, NULL
};
4129 return grep_text(view
, text
);
4133 pager_select(struct view
*view
, struct line
*line
)
4135 if (line
->type
== LINE_COMMIT
) {
4136 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
4138 if (view
->type
!= VIEW_PAGER
)
4139 string_copy_rev(view
->ref
, text
);
4140 string_copy_rev(ref_commit
, text
);
4144 static struct view_ops pager_ops
= {
4155 static const char *log_argv
[SIZEOF_ARG
] = {
4156 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4160 log_request(struct view
*view
, enum request request
, struct line
*line
)
4165 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
4168 return pager_request(view
, request
, line
);
4172 static struct view_ops log_ops
= {
4183 static const char *diff_argv
[SIZEOF_ARG
] = {
4184 "git", "show", "--pretty=fuller", "--no-color", "--root",
4185 "--patch-with-stat", "--find-copies-harder", "-C",
4186 "%(diff-args)", "%(commit)", "--", "%(file-args)", NULL
4189 static struct view_ops diff_ops
= {
4204 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
4207 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
4211 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
4212 help_keymap_hidden
[keymap
] ? '+' : '-',
4213 enum_name(keymap_table
[keymap
]));
4215 line
->other
= keymap
;
4217 return help_keymap_hidden
[keymap
];
4221 help_open_keymap(struct view
*view
, enum keymap keymap
)
4223 const char *group
= NULL
;
4224 char buf
[SIZEOF_STR
];
4226 bool add_title
= TRUE
;
4229 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
4230 const char *key
= NULL
;
4232 if (req_info
[i
].request
== REQ_NONE
)
4235 if (!req_info
[i
].request
) {
4236 group
= req_info
[i
].help
;
4240 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4244 if (add_title
&& help_open_keymap_title(view
, keymap
))
4249 add_line_text(view
, group
, LINE_HELP_GROUP
);
4253 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4254 enum_name(req_info
[i
]), req_info
[i
].help
);
4257 group
= "External commands:";
4259 for (i
= 0; i
< run_requests
; i
++) {
4260 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4264 if (!req
|| req
->keymap
!= keymap
)
4267 key
= get_key_name(req
->key
);
4269 key
= "(no key defined)";
4271 if (add_title
&& help_open_keymap_title(view
, keymap
))
4274 add_line_text(view
, group
, LINE_HELP_GROUP
);
4278 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4279 if (!string_format_from(buf
, &bufpos
, "%s%s",
4280 argc
? " " : "", req
->argv
[argc
]))
4283 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4288 help_open(struct view
*view
)
4293 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4294 add_line_text(view
, "", LINE_DEFAULT
);
4296 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4297 help_open_keymap(view
, keymap
);
4303 help_request(struct view
*view
, enum request request
, struct line
*line
)
4307 if (line
->type
== LINE_HELP_KEYMAP
) {
4308 help_keymap_hidden
[line
->other
] =
4309 !help_keymap_hidden
[line
->other
];
4310 view
->p_restore
= TRUE
;
4311 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4316 return pager_request(view
, request
, line
);
4320 static struct view_ops help_ops
= {
4336 struct tree_stack_entry
{
4337 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4338 unsigned long lineno
; /* Line number to restore */
4339 char *name
; /* Position of name in opt_path */
4342 /* The top of the path stack. */
4343 static struct tree_stack_entry
*tree_stack
= NULL
;
4344 unsigned long tree_lineno
= 0;
4347 pop_tree_stack_entry(void)
4349 struct tree_stack_entry
*entry
= tree_stack
;
4351 tree_lineno
= entry
->lineno
;
4353 tree_stack
= entry
->prev
;
4358 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4360 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4361 size_t pathlen
= strlen(opt_path
);
4366 entry
->prev
= tree_stack
;
4367 entry
->name
= opt_path
+ pathlen
;
4370 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4371 pop_tree_stack_entry();
4375 /* Move the current line to the first tree entry. */
4377 entry
->lineno
= lineno
;
4380 /* Parse output from git-ls-tree(1):
4382 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4385 #define SIZEOF_TREE_ATTR \
4386 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4388 #define SIZEOF_TREE_MODE \
4389 STRING_SIZE("100644 ")
4391 #define TREE_ID_OFFSET \
4392 STRING_SIZE("100644 blob ")
4395 char id
[SIZEOF_REV
];
4397 struct time time
; /* Date from the author ident. */
4398 const char *author
; /* Author of the commit. */
4403 tree_path(const struct line
*line
)
4405 return ((struct tree_entry
*) line
->data
)->name
;
4409 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4411 if (line1
->type
!= line2
->type
)
4412 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4413 return strcmp(tree_path(line1
), tree_path(line2
));
4416 static const enum sort_field tree_sort_fields
[] = {
4417 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4419 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4422 tree_compare(const void *l1
, const void *l2
)
4424 const struct line
*line1
= (const struct line
*) l1
;
4425 const struct line
*line2
= (const struct line
*) l2
;
4426 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4427 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4429 if (line1
->type
== LINE_TREE_HEAD
)
4431 if (line2
->type
== LINE_TREE_HEAD
)
4434 switch (get_sort_field(tree_sort_state
)) {
4436 return sort_order(tree_sort_state
, timecmp(&entry1
->time
, &entry2
->time
));
4438 case ORDERBY_AUTHOR
:
4439 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4443 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4448 static struct line
*
4449 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4450 const char *mode
, const char *id
)
4452 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4453 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4455 if (!entry
|| !line
) {
4460 strncpy(entry
->name
, path
, strlen(path
));
4462 entry
->mode
= strtoul(mode
, NULL
, 8);
4464 string_copy_rev(entry
->id
, id
);
4470 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4472 static const char *author_name
;
4473 static struct time author_time
;
4475 if (!text
&& *read_date
) {
4480 char *path
= *opt_path
? opt_path
: ".";
4481 /* Find next entry to process */
4482 const char *log_file
[] = {
4483 "git", "log", "--no-color", "--pretty=raw",
4484 "--cc", "--raw", view
->id
, "--", path
, NULL
4488 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4489 report("Tree is empty");
4493 if (!start_update(view
, log_file
, opt_cdup
)) {
4494 report("Failed to load tree data");
4501 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4502 parse_author_line(text
+ STRING_SIZE("author "),
4503 &author_name
, &author_time
);
4505 } else if (*text
== ':') {
4507 size_t annotated
= 1;
4510 pos
= strchr(text
, '\t');
4514 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4515 text
+= strlen(opt_path
);
4516 pos
= strchr(text
, '/');
4520 for (i
= 1; i
< view
->lines
; i
++) {
4521 struct line
*line
= &view
->line
[i
];
4522 struct tree_entry
*entry
= line
->data
;
4524 annotated
+= !!entry
->author
;
4525 if (entry
->author
|| strcmp(entry
->name
, text
))
4528 entry
->author
= author_name
;
4529 entry
->time
= author_time
;
4534 if (annotated
== view
->lines
)
4535 io_kill(view
->pipe
);
4541 tree_read(struct view
*view
, char *text
)
4543 static bool read_date
= FALSE
;
4544 struct tree_entry
*data
;
4545 struct line
*entry
, *line
;
4546 enum line_type type
;
4547 size_t textlen
= text
? strlen(text
) : 0;
4548 char *path
= text
+ SIZEOF_TREE_ATTR
;
4550 if (read_date
|| !text
)
4551 return tree_read_date(view
, text
, &read_date
);
4553 if (textlen
<= SIZEOF_TREE_ATTR
)
4555 if (view
->lines
== 0 &&
4556 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4559 /* Strip the path part ... */
4561 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4562 size_t striplen
= strlen(opt_path
);
4564 if (pathlen
> striplen
)
4565 memmove(path
, path
+ striplen
,
4566 pathlen
- striplen
+ 1);
4568 /* Insert "link" to parent directory. */
4569 if (view
->lines
== 1 &&
4570 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4574 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4575 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4580 /* Skip "Directory ..." and ".." line. */
4581 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4582 if (tree_compare_entry(line
, entry
) <= 0)
4585 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4589 for (; line
<= entry
; line
++)
4590 line
->dirty
= line
->cleareol
= 1;
4594 if (tree_lineno
> view
->lineno
) {
4595 view
->lineno
= tree_lineno
;
4603 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4605 struct tree_entry
*entry
= line
->data
;
4607 if (line
->type
== LINE_TREE_HEAD
) {
4608 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4611 if (draw_mode(view
, entry
->mode
))
4614 if (opt_author
&& draw_author(view
, entry
->author
))
4617 if (opt_date
&& draw_date(view
, &entry
->time
))
4620 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4626 open_blob_editor(const char *id
)
4628 const char *blob_argv
[] = { "git", "cat-file", "blob", id
, NULL
};
4629 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4630 int fd
= mkstemp(file
);
4633 report("Failed to create temporary file");
4634 else if (!io_run_append(blob_argv
, fd
))
4635 report("Failed to save blob data to file");
4643 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4645 enum open_flags flags
;
4646 struct tree_entry
*entry
= line
->data
;
4649 case REQ_VIEW_BLAME
:
4650 if (line
->type
!= LINE_TREE_FILE
) {
4651 report("Blame only supported for files");
4655 string_copy(opt_ref
, view
->vid
);
4659 if (line
->type
!= LINE_TREE_FILE
) {
4660 report("Edit only supported for files");
4661 } else if (!is_head_commit(view
->vid
)) {
4662 open_blob_editor(entry
->id
);
4664 open_editor(opt_file
);
4668 case REQ_TOGGLE_SORT_FIELD
:
4669 case REQ_TOGGLE_SORT_ORDER
:
4670 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4675 /* quit view if at top of tree */
4676 return REQ_VIEW_CLOSE
;
4679 line
= &view
->line
[1];
4689 /* Cleanup the stack if the tree view is at a different tree. */
4690 while (!*opt_path
&& tree_stack
)
4691 pop_tree_stack_entry();
4693 switch (line
->type
) {
4695 /* Depending on whether it is a subdirectory or parent link
4696 * mangle the path buffer. */
4697 if (line
== &view
->line
[1] && *opt_path
) {
4698 pop_tree_stack_entry();
4701 const char *basename
= tree_path(line
);
4703 push_tree_stack_entry(basename
, view
->lineno
);
4706 /* Trees and subtrees share the same ID, so they are not not
4707 * unique like blobs. */
4708 flags
= OPEN_RELOAD
;
4709 request
= REQ_VIEW_TREE
;
4712 case LINE_TREE_FILE
:
4713 flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
4714 request
= REQ_VIEW_BLOB
;
4721 open_view(view
, request
, flags
);
4722 if (request
== REQ_VIEW_TREE
)
4723 view
->lineno
= tree_lineno
;
4729 tree_grep(struct view
*view
, struct line
*line
)
4731 struct tree_entry
*entry
= line
->data
;
4732 const char *text
[] = {
4734 opt_author
? entry
->author
: "",
4735 mkdate(&entry
->time
, opt_date
),
4739 return grep_text(view
, text
);
4743 tree_select(struct view
*view
, struct line
*line
)
4745 struct tree_entry
*entry
= line
->data
;
4747 if (line
->type
== LINE_TREE_FILE
) {
4748 string_copy_rev(ref_blob
, entry
->id
);
4749 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4751 } else if (line
->type
!= LINE_TREE_DIR
) {
4755 string_copy_rev(view
->ref
, entry
->id
);
4759 tree_prepare(struct view
*view
)
4761 if (view
->lines
== 0 && opt_prefix
[0]) {
4762 char *pos
= opt_prefix
;
4764 while (pos
&& *pos
) {
4765 char *end
= strchr(pos
, '/');
4769 push_tree_stack_entry(pos
, 0);
4777 } else if (strcmp(view
->vid
, view
->id
)) {
4781 return prepare_io(view
, opt_cdup
, view
->ops
->argv
, TRUE
);
4784 static const char *tree_argv
[SIZEOF_ARG
] = {
4785 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4788 static struct view_ops tree_ops
= {
4801 blob_read(struct view
*view
, char *line
)
4805 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4809 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4813 open_blob_editor(view
->vid
);
4816 return pager_request(view
, request
, line
);
4820 static const char *blob_argv
[SIZEOF_ARG
] = {
4821 "git", "cat-file", "blob", "%(blob)", NULL
4824 static struct view_ops blob_ops
= {
4838 * Loading the blame view is a two phase job:
4840 * 1. File content is read either using opt_file from the
4841 * filesystem or using git-cat-file.
4842 * 2. Then blame information is incrementally added by
4843 * reading output from git-blame.
4846 struct blame_commit
{
4847 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4848 char title
[128]; /* First line of the commit message. */
4849 const char *author
; /* Author of the commit. */
4850 struct time time
; /* Date from the author ident. */
4851 char filename
[128]; /* Name of file. */
4852 char parent_id
[SIZEOF_REV
]; /* Parent/previous SHA1 ID. */
4853 char parent_filename
[128]; /* Parent/previous name of file. */
4857 struct blame_commit
*commit
;
4858 unsigned long lineno
;
4863 blame_open(struct view
*view
)
4865 char path
[SIZEOF_STR
];
4868 if (!view
->prev
&& *opt_prefix
) {
4869 string_copy(path
, opt_file
);
4870 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4874 if (*opt_ref
|| !io_open(&view
->io
, "%s%s", opt_cdup
, opt_file
)) {
4875 const char *blame_cat_file_argv
[] = {
4876 "git", "cat-file", "blob", path
, NULL
4879 if (!string_format(path
, "%s:%s", opt_ref
, opt_file
) ||
4880 !start_update(view
, blame_cat_file_argv
, opt_cdup
))
4884 /* First pass: remove multiple references to the same commit. */
4885 for (i
= 0; i
< view
->lines
; i
++) {
4886 struct blame
*blame
= view
->line
[i
].data
;
4888 if (blame
->commit
&& blame
->commit
->id
[0])
4889 blame
->commit
->id
[0] = 0;
4891 blame
->commit
= NULL
;
4894 /* Second pass: free existing references. */
4895 for (i
= 0; i
< view
->lines
; i
++) {
4896 struct blame
*blame
= view
->line
[i
].data
;
4899 free(blame
->commit
);
4902 setup_update(view
, opt_file
);
4903 string_format(view
->ref
, "%s ...", opt_file
);
4908 static struct blame_commit
*
4909 get_blame_commit(struct view
*view
, const char *id
)
4913 for (i
= 0; i
< view
->lines
; i
++) {
4914 struct blame
*blame
= view
->line
[i
].data
;
4919 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4920 return blame
->commit
;
4924 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4927 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4933 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4935 const char *pos
= *posref
;
4938 pos
= strchr(pos
+ 1, ' ');
4939 if (!pos
|| !isdigit(pos
[1]))
4941 *number
= atoi(pos
+ 1);
4942 if (*number
< min
|| *number
> max
)
4949 static struct blame_commit
*
4950 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4952 struct blame_commit
*commit
;
4953 struct blame
*blame
;
4954 const char *pos
= text
+ SIZEOF_REV
- 2;
4955 size_t orig_lineno
= 0;
4959 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4962 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4963 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4964 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4967 commit
= get_blame_commit(view
, text
);
4973 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4976 blame
->commit
= commit
;
4977 blame
->lineno
= orig_lineno
+ group
- 1;
4985 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4988 const char *blame_argv
[] = {
4989 "git", "blame", "--incremental",
4990 *opt_ref
? opt_ref
: "--incremental", "--", opt_file
, NULL
4993 if (view
->lines
== 0 && !view
->prev
)
4994 die("No blame exist for %s", view
->vid
);
4996 if (view
->lines
== 0 || !start_update(view
, blame_argv
, opt_cdup
)) {
4997 report("Failed to load blame data");
5005 size_t linelen
= strlen(line
);
5006 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
5011 blame
->commit
= NULL
;
5012 strncpy(blame
->text
, line
, linelen
);
5013 blame
->text
[linelen
] = 0;
5014 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
5019 match_blame_header(const char *name
, char **line
)
5021 size_t namelen
= strlen(name
);
5022 bool matched
= !strncmp(name
, *line
, namelen
);
5031 blame_read(struct view
*view
, char *line
)
5033 static struct blame_commit
*commit
= NULL
;
5034 static int blamed
= 0;
5035 static bool read_file
= TRUE
;
5038 return blame_read_file(view
, line
, &read_file
);
5045 string_format(view
->ref
, "%s", view
->vid
);
5046 if (view_is_displayed(view
)) {
5047 update_view_title(view
);
5048 redraw_view_from(view
, 0);
5054 commit
= parse_blame_commit(view
, line
, &blamed
);
5055 string_format(view
->ref
, "%s %2d%%", view
->vid
,
5056 view
->lines
? blamed
* 100 / view
->lines
: 0);
5058 } else if (match_blame_header("author ", &line
)) {
5059 commit
->author
= get_author(line
);
5061 } else if (match_blame_header("author-time ", &line
)) {
5062 parse_timesec(&commit
->time
, line
);
5064 } else if (match_blame_header("author-tz ", &line
)) {
5065 parse_timezone(&commit
->time
, line
);
5067 } else if (match_blame_header("summary ", &line
)) {
5068 string_ncopy(commit
->title
, line
, strlen(line
));
5070 } else if (match_blame_header("previous ", &line
)) {
5071 if (strlen(line
) <= SIZEOF_REV
)
5073 string_copy_rev(commit
->parent_id
, line
);
5075 string_ncopy(commit
->parent_filename
, line
, strlen(line
));
5077 } else if (match_blame_header("filename ", &line
)) {
5078 string_ncopy(commit
->filename
, line
, strlen(line
));
5086 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5088 struct blame
*blame
= line
->data
;
5089 struct time
*time
= NULL
;
5090 const char *id
= NULL
, *author
= NULL
;
5092 if (blame
->commit
&& *blame
->commit
->filename
) {
5093 id
= blame
->commit
->id
;
5094 author
= blame
->commit
->author
;
5095 time
= &blame
->commit
->time
;
5098 if (opt_date
&& draw_date(view
, time
))
5101 if (opt_author
&& draw_author(view
, author
))
5104 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
5107 if (draw_lineno(view
, lineno
))
5110 draw_text(view
, LINE_DEFAULT
, blame
->text
, TRUE
);
5115 check_blame_commit(struct blame
*blame
, bool check_null_id
)
5118 report("Commit data not loaded yet");
5119 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5120 report("No commit exist for the selected line");
5127 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
5129 char from
[SIZEOF_REF
+ SIZEOF_STR
];
5130 char to
[SIZEOF_REF
+ SIZEOF_STR
];
5131 const char *diff_tree_argv
[] = {
5132 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5133 "-U0", from
, to
, "--", NULL
5136 int parent_lineno
= -1;
5137 int blamed_lineno
= -1;
5140 if (!string_format(from
, "%s:%s", opt_ref
, opt_file
) ||
5141 !string_format(to
, "%s:%s", blame
->commit
->id
, blame
->commit
->filename
) ||
5142 !io_run(&io
, IO_RD
, NULL
, diff_tree_argv
))
5145 while ((line
= io_get(&io
, '\n', TRUE
))) {
5147 char *pos
= strchr(line
, '+');
5149 parent_lineno
= atoi(line
+ 4);
5151 blamed_lineno
= atoi(pos
+ 1);
5153 } else if (*line
== '+' && parent_lineno
!= -1) {
5154 if (blame
->lineno
== blamed_lineno
- 1 &&
5155 !strcmp(blame
->text
, line
+ 1)) {
5156 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
5167 blame_request(struct view
*view
, enum request request
, struct line
*line
)
5169 enum open_flags flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
5170 struct blame
*blame
= line
->data
;
5173 case REQ_VIEW_BLAME
:
5174 if (check_blame_commit(blame
, TRUE
)) {
5175 string_copy(opt_ref
, blame
->commit
->id
);
5176 string_copy(opt_file
, blame
->commit
->filename
);
5178 view
->lineno
= blame
->lineno
;
5179 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5184 if (!check_blame_commit(blame
, TRUE
))
5186 if (!*blame
->commit
->parent_id
) {
5187 report("The selected commit has no parents");
5189 string_copy_rev(opt_ref
, blame
->commit
->parent_id
);
5190 string_copy(opt_file
, blame
->commit
->parent_filename
);
5191 setup_blame_parent_line(view
, blame
);
5192 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5197 if (!check_blame_commit(blame
, FALSE
))
5200 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
5201 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
5204 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
5205 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
5206 const char *diff_index_argv
[] = {
5207 "git", "diff-index", "--root", "--patch-with-stat",
5208 "-C", "-M", "HEAD", "--", view
->vid
, NULL
5211 if (!*blame
->commit
->parent_id
) {
5212 diff_index_argv
[1] = "diff";
5213 diff_index_argv
[2] = "--no-color";
5214 diff_index_argv
[6] = "--";
5215 diff_index_argv
[7] = "/dev/null";
5218 if (!prepare_update(diff
, diff_index_argv
, NULL
)) {
5219 report("Failed to allocate diff command");
5222 flags
|= OPEN_PREPARED
;
5225 open_view(view
, REQ_VIEW_DIFF
, flags
);
5226 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5227 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
5238 blame_grep(struct view
*view
, struct line
*line
)
5240 struct blame
*blame
= line
->data
;
5241 struct blame_commit
*commit
= blame
->commit
;
5242 const char *text
[] = {
5244 commit
? commit
->title
: "",
5245 commit
? commit
->id
: "",
5246 commit
&& opt_author
? commit
->author
: "",
5247 commit
? mkdate(&commit
->time
, opt_date
) : "",
5251 return grep_text(view
, text
);
5255 blame_select(struct view
*view
, struct line
*line
)
5257 struct blame
*blame
= line
->data
;
5258 struct blame_commit
*commit
= blame
->commit
;
5263 if (!strcmp(commit
->id
, NULL_ID
))
5264 string_ncopy(ref_commit
, "HEAD", 4);
5266 string_copy_rev(ref_commit
, commit
->id
);
5269 static struct view_ops blame_ops
= {
5285 const char *author
; /* Author of the last commit. */
5286 struct time time
; /* Date of the last activity. */
5287 const struct ref
*ref
; /* Name and commit ID information. */
5290 static const struct ref branch_all
;
5292 static const enum sort_field branch_sort_fields
[] = {
5293 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5295 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5298 branch_compare(const void *l1
, const void *l2
)
5300 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5301 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5303 switch (get_sort_field(branch_sort_state
)) {
5305 return sort_order(branch_sort_state
, timecmp(&branch1
->time
, &branch2
->time
));
5307 case ORDERBY_AUTHOR
:
5308 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5312 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5317 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5319 struct branch
*branch
= line
->data
;
5320 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5322 if (opt_date
&& draw_date(view
, &branch
->time
))
5325 if (opt_author
&& draw_author(view
, branch
->author
))
5328 draw_text(view
, type
, branch
->ref
== &branch_all
? "All branches" : branch
->ref
->name
, TRUE
);
5333 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5335 struct branch
*branch
= line
->data
;
5340 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5343 case REQ_TOGGLE_SORT_FIELD
:
5344 case REQ_TOGGLE_SORT_ORDER
:
5345 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5350 const struct ref
*ref
= branch
->ref
;
5351 const char *all_branches_argv
[] = {
5352 "git", "log", "--no-color", "--pretty=raw", "--parents",
5354 ref
== &branch_all
? "--all" : ref
->name
, NULL
5356 struct view
*main_view
= VIEW(REQ_VIEW_MAIN
);
5358 if (!prepare_update(main_view
, all_branches_argv
, NULL
))
5359 report("Failed to load view of all branches");
5361 open_view(view
, REQ_VIEW_MAIN
, OPEN_PREPARED
| OPEN_SPLIT
);
5370 branch_read(struct view
*view
, char *line
)
5372 static char id
[SIZEOF_REV
];
5373 struct branch
*reference
;
5379 switch (get_line_type(line
)) {
5381 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5385 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5386 struct branch
*branch
= view
->line
[i
].data
;
5388 if (strcmp(branch
->ref
->id
, id
))
5391 view
->line
[i
].dirty
= TRUE
;
5393 branch
->author
= reference
->author
;
5394 branch
->time
= reference
->time
;
5398 parse_author_line(line
+ STRING_SIZE("author "),
5399 &branch
->author
, &branch
->time
);
5411 branch_open_visitor(void *data
, const struct ref
*ref
)
5413 struct view
*view
= data
;
5414 struct branch
*branch
;
5416 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5419 branch
= calloc(1, sizeof(*branch
));
5424 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5428 branch_open(struct view
*view
)
5430 const char *branch_log
[] = {
5431 "git", "log", "--no-color", "--pretty=raw",
5432 "--simplify-by-decoration", "--all", NULL
5435 if (!start_update(view
, branch_log
, NULL
)) {
5436 report("Failed to load branch data");
5440 setup_update(view
, view
->id
);
5441 branch_open_visitor(view
, &branch_all
);
5442 foreach_ref(branch_open_visitor
, view
);
5443 view
->p_restore
= TRUE
;
5449 branch_grep(struct view
*view
, struct line
*line
)
5451 struct branch
*branch
= line
->data
;
5452 const char *text
[] = {
5458 return grep_text(view
, text
);
5462 branch_select(struct view
*view
, struct line
*line
)
5464 struct branch
*branch
= line
->data
;
5466 string_copy_rev(view
->ref
, branch
->ref
->id
);
5467 string_copy_rev(ref_commit
, branch
->ref
->id
);
5468 string_copy_rev(ref_head
, branch
->ref
->id
);
5469 string_copy_rev(ref_branch
, branch
->ref
->name
);
5472 static struct view_ops branch_ops
= {
5491 char rev
[SIZEOF_REV
];
5492 char name
[SIZEOF_STR
];
5496 char rev
[SIZEOF_REV
];
5497 char name
[SIZEOF_STR
];
5501 static char status_onbranch
[SIZEOF_STR
];
5502 static struct status stage_status
;
5503 static enum line_type stage_line_type
;
5504 static size_t stage_chunks
;
5505 static int *stage_chunk
;
5507 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5509 /* This should work even for the "On branch" line. */
5511 status_has_none(struct view
*view
, struct line
*line
)
5513 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5516 /* Get fields from the diff line:
5517 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5520 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5522 const char *old_mode
= buf
+ 1;
5523 const char *new_mode
= buf
+ 8;
5524 const char *old_rev
= buf
+ 15;
5525 const char *new_rev
= buf
+ 56;
5526 const char *status
= buf
+ 97;
5529 old_mode
[-1] != ':' ||
5530 new_mode
[-1] != ' ' ||
5531 old_rev
[-1] != ' ' ||
5532 new_rev
[-1] != ' ' ||
5536 file
->status
= *status
;
5538 string_copy_rev(file
->old
.rev
, old_rev
);
5539 string_copy_rev(file
->new.rev
, new_rev
);
5541 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5542 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5544 file
->old
.name
[0] = file
->new.name
[0] = 0;
5550 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5552 struct status
*unmerged
= NULL
;
5556 if (!io_run(&io
, IO_RD
, opt_cdup
, argv
))
5559 add_line_data(view
, NULL
, type
);
5561 while ((buf
= io_get(&io
, 0, TRUE
))) {
5562 struct status
*file
= unmerged
;
5565 file
= calloc(1, sizeof(*file
));
5566 if (!file
|| !add_line_data(view
, file
, type
))
5570 /* Parse diff info part. */
5572 file
->status
= status
;
5574 string_copy(file
->old
.rev
, NULL_ID
);
5576 } else if (!file
->status
|| file
== unmerged
) {
5577 if (!status_get_diff(file
, buf
, strlen(buf
)))
5580 buf
= io_get(&io
, 0, TRUE
);
5584 /* Collapse all modified entries that follow an
5585 * associated unmerged entry. */
5586 if (unmerged
== file
) {
5587 unmerged
->status
= 'U';
5589 } else if (file
->status
== 'U') {
5594 /* Grab the old name for rename/copy. */
5595 if (!*file
->old
.name
&&
5596 (file
->status
== 'R' || file
->status
== 'C')) {
5597 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5599 buf
= io_get(&io
, 0, TRUE
);
5604 /* git-ls-files just delivers a NUL separated list of
5605 * file names similar to the second half of the
5606 * git-diff-* output. */
5607 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5608 if (!*file
->old
.name
)
5609 string_copy(file
->old
.name
, file
->new.name
);
5613 if (io_error(&io
)) {
5619 if (!view
->line
[view
->lines
- 1].data
)
5620 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5626 /* Don't show unmerged entries in the staged section. */
5627 static const char *status_diff_index_argv
[] = {
5628 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5629 "--cached", "-M", "HEAD", NULL
5632 static const char *status_diff_files_argv
[] = {
5633 "git", "diff-files", "-z", NULL
5636 static const char *status_list_other_argv
[] = {
5637 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix
, NULL
5640 static const char *status_list_no_head_argv
[] = {
5641 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5644 static const char *update_index_argv
[] = {
5645 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5648 /* Restore the previous line number to stay in the context or select a
5649 * line with something that can be updated. */
5651 status_restore(struct view
*view
)
5653 if (view
->p_lineno
>= view
->lines
)
5654 view
->p_lineno
= view
->lines
- 1;
5655 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5657 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5660 /* If the above fails, always skip the "On branch" line. */
5661 if (view
->p_lineno
< view
->lines
)
5662 view
->lineno
= view
->p_lineno
;
5666 if (view
->lineno
< view
->offset
)
5667 view
->offset
= view
->lineno
;
5668 else if (view
->offset
+ view
->height
<= view
->lineno
)
5669 view
->offset
= view
->lineno
- view
->height
+ 1;
5671 view
->p_restore
= FALSE
;
5675 status_update_onbranch(void)
5677 static const char *paths
[][2] = {
5678 { "rebase-apply/rebasing", "Rebasing" },
5679 { "rebase-apply/applying", "Applying mailbox" },
5680 { "rebase-apply/", "Rebasing mailbox" },
5681 { "rebase-merge/interactive", "Interactive rebase" },
5682 { "rebase-merge/", "Rebase merge" },
5683 { "MERGE_HEAD", "Merging" },
5684 { "BISECT_LOG", "Bisecting" },
5685 { "HEAD", "On branch" },
5687 char buf
[SIZEOF_STR
];
5691 if (is_initial_commit()) {
5692 string_copy(status_onbranch
, "Initial commit");
5696 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5697 char *head
= opt_head
;
5699 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5700 lstat(buf
, &stat
) < 0)
5706 if (io_open(&io
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5707 io_read_buf(&io
, buf
, sizeof(buf
))) {
5709 if (!prefixcmp(head
, "refs/heads/"))
5710 head
+= STRING_SIZE("refs/heads/");
5714 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5715 string_copy(status_onbranch
, opt_head
);
5719 string_copy(status_onbranch
, "Not currently on any branch");
5722 /* First parse staged info using git-diff-index(1), then parse unstaged
5723 * info using git-diff-files(1), and finally untracked files using
5724 * git-ls-files(1). */
5726 status_open(struct view
*view
)
5730 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5731 status_update_onbranch();
5733 io_run_bg(update_index_argv
);
5735 if (is_initial_commit()) {
5736 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5738 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5742 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5743 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5746 /* Restore the exact position or use the specialized restore
5748 if (!view
->p_restore
)
5749 status_restore(view
);
5754 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5756 struct status
*status
= line
->data
;
5757 enum line_type type
;
5761 switch (line
->type
) {
5762 case LINE_STAT_STAGED
:
5763 type
= LINE_STAT_SECTION
;
5764 text
= "Changes to be committed:";
5767 case LINE_STAT_UNSTAGED
:
5768 type
= LINE_STAT_SECTION
;
5769 text
= "Changed but not updated:";
5772 case LINE_STAT_UNTRACKED
:
5773 type
= LINE_STAT_SECTION
;
5774 text
= "Untracked files:";
5777 case LINE_STAT_NONE
:
5778 type
= LINE_DEFAULT
;
5779 text
= " (no files)";
5782 case LINE_STAT_HEAD
:
5783 type
= LINE_STAT_HEAD
;
5784 text
= status_onbranch
;
5791 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5793 buf
[0] = status
->status
;
5794 if (draw_text(view
, line
->type
, buf
, TRUE
))
5796 type
= LINE_DEFAULT
;
5797 text
= status
->new.name
;
5800 draw_text(view
, type
, text
, TRUE
);
5805 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5807 if (displayed_views() == 2 || display
[current_view
] != view
)
5808 maximize_view(view
);
5809 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5814 status_enter(struct view
*view
, struct line
*line
)
5816 struct status
*status
= line
->data
;
5817 const char *oldpath
= status
? status
->old
.name
: NULL
;
5818 /* Diffs for unmerged entries are empty when passing the new
5819 * path, so leave it empty. */
5820 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5822 enum open_flags split
;
5823 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5825 if (line
->type
== LINE_STAT_NONE
||
5826 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5827 report("No file to diff");
5831 switch (line
->type
) {
5832 case LINE_STAT_STAGED
:
5833 if (is_initial_commit()) {
5834 const char *no_head_diff_argv
[] = {
5835 "git", "diff", "--no-color", "--patch-with-stat",
5836 "--", "/dev/null", newpath
, NULL
5839 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
))
5840 return status_load_error(view
, stage
, newpath
);
5842 const char *index_show_argv
[] = {
5843 "git", "diff-index", "--root", "--patch-with-stat",
5844 "-C", "-M", "--cached", "HEAD", "--",
5845 oldpath
, newpath
, NULL
5848 if (!prepare_update(stage
, index_show_argv
, opt_cdup
))
5849 return status_load_error(view
, stage
, newpath
);
5853 info
= "Staged changes to %s";
5855 info
= "Staged changes";
5858 case LINE_STAT_UNSTAGED
:
5860 const char *files_show_argv
[] = {
5861 "git", "diff-files", "--root", "--patch-with-stat",
5862 "-C", "-M", "--", oldpath
, newpath
, NULL
5865 if (!prepare_update(stage
, files_show_argv
, opt_cdup
))
5866 return status_load_error(view
, stage
, newpath
);
5868 info
= "Unstaged changes to %s";
5870 info
= "Unstaged changes";
5873 case LINE_STAT_UNTRACKED
:
5875 report("No file to show");
5879 if (!suffixcmp(status
->new.name
, -1, "/")) {
5880 report("Cannot display a directory");
5884 if (!prepare_update_file(stage
, newpath
))
5885 return status_load_error(view
, stage
, newpath
);
5886 info
= "Untracked file %s";
5889 case LINE_STAT_HEAD
:
5893 die("line type %d not handled in switch", line
->type
);
5896 split
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
5897 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5898 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5900 stage_status
= *status
;
5902 memset(&stage_status
, 0, sizeof(stage_status
));
5905 stage_line_type
= line
->type
;
5907 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5914 status_exists(struct status
*status
, enum line_type type
)
5916 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5917 unsigned long lineno
;
5919 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5920 struct line
*line
= &view
->line
[lineno
];
5921 struct status
*pos
= line
->data
;
5923 if (line
->type
!= type
)
5925 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5926 select_view_line(view
, lineno
);
5929 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5930 select_view_line(view
, lineno
);
5940 status_update_prepare(struct io
*io
, enum line_type type
)
5942 const char *staged_argv
[] = {
5943 "git", "update-index", "-z", "--index-info", NULL
5945 const char *others_argv
[] = {
5946 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5950 case LINE_STAT_STAGED
:
5951 return io_run(io
, IO_WR
, opt_cdup
, staged_argv
);
5953 case LINE_STAT_UNSTAGED
:
5954 case LINE_STAT_UNTRACKED
:
5955 return io_run(io
, IO_WR
, opt_cdup
, others_argv
);
5958 die("line type %d not handled in switch", type
);
5964 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5966 char buf
[SIZEOF_STR
];
5970 case LINE_STAT_STAGED
:
5971 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5974 status
->old
.name
, 0))
5978 case LINE_STAT_UNSTAGED
:
5979 case LINE_STAT_UNTRACKED
:
5980 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
5985 die("line type %d not handled in switch", type
);
5988 return io_write(io
, buf
, bufsize
);
5992 status_update_file(struct status
*status
, enum line_type type
)
5997 if (!status_update_prepare(&io
, type
))
6000 result
= status_update_write(&io
, status
, type
);
6001 return io_done(&io
) && result
;
6005 status_update_files(struct view
*view
, struct line
*line
)
6007 char buf
[sizeof(view
->ref
)];
6010 struct line
*pos
= view
->line
+ view
->lines
;
6013 int cursor_y
= -1, cursor_x
= -1;
6015 if (!status_update_prepare(&io
, line
->type
))
6018 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
6021 string_copy(buf
, view
->ref
);
6022 getsyx(cursor_y
, cursor_x
);
6023 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
6024 int almost_done
= file
* 100 / files
;
6026 if (almost_done
> done
) {
6028 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
6030 update_view_title(view
);
6031 setsyx(cursor_y
, cursor_x
);
6034 result
= status_update_write(&io
, line
->data
, line
->type
);
6036 string_copy(view
->ref
, buf
);
6038 return io_done(&io
) && result
;
6042 status_update(struct view
*view
)
6044 struct line
*line
= &view
->line
[view
->lineno
];
6046 assert(view
->lines
);
6049 /* This should work even for the "On branch" line. */
6050 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
6051 report("Nothing to update");
6055 if (!status_update_files(view
, line
+ 1)) {
6056 report("Failed to update file status");
6060 } else if (!status_update_file(line
->data
, line
->type
)) {
6061 report("Failed to update file status");
6069 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
6071 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
6072 if (type
== LINE_STAT_STAGED
) {
6073 report("Cannot revert changes to staged files");
6074 } else if (type
== LINE_STAT_UNTRACKED
) {
6075 report("Cannot revert changes to untracked files");
6076 } else if (has_none
) {
6077 report("Nothing to revert");
6079 report("Cannot revert changes to multiple files");
6082 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6083 char mode
[10] = "100644";
6084 const char *reset_argv
[] = {
6085 "git", "update-index", "--cacheinfo", mode
,
6086 status
->old
.rev
, status
->old
.name
, NULL
6088 const char *checkout_argv
[] = {
6089 "git", "checkout", "--", status
->old
.name
, NULL
6092 if (status
->status
== 'U') {
6093 string_format(mode
, "%5o", status
->old
.mode
);
6095 if (status
->old
.mode
== 0 && status
->new.mode
== 0) {
6096 reset_argv
[2] = "--force-remove";
6097 reset_argv
[3] = status
->old
.name
;
6098 reset_argv
[4] = NULL
;
6101 if (!io_run_fg(reset_argv
, opt_cdup
))
6103 if (status
->old
.mode
== 0 && status
->new.mode
== 0)
6107 return io_run_fg(checkout_argv
, opt_cdup
);
6114 status_request(struct view
*view
, enum request request
, struct line
*line
)
6116 struct status
*status
= line
->data
;
6119 case REQ_STATUS_UPDATE
:
6120 if (!status_update(view
))
6124 case REQ_STATUS_REVERT
:
6125 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
6129 case REQ_STATUS_MERGE
:
6130 if (!status
|| status
->status
!= 'U') {
6131 report("Merging only possible for files with unmerged status ('U').");
6134 open_mergetool(status
->new.name
);
6140 if (status
->status
== 'D') {
6141 report("File has been deleted.");
6145 open_editor(status
->new.name
);
6148 case REQ_VIEW_BLAME
:
6154 /* After returning the status view has been split to
6155 * show the stage view. No further reloading is
6157 return status_enter(view
, line
);
6160 /* Simply reload the view. */
6167 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
6173 status_select(struct view
*view
, struct line
*line
)
6175 struct status
*status
= line
->data
;
6176 char file
[SIZEOF_STR
] = "all files";
6180 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
6183 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
6186 switch (line
->type
) {
6187 case LINE_STAT_STAGED
:
6188 text
= "Press %s to unstage %s for commit";
6191 case LINE_STAT_UNSTAGED
:
6192 text
= "Press %s to stage %s for commit";
6195 case LINE_STAT_UNTRACKED
:
6196 text
= "Press %s to stage %s for addition";
6199 case LINE_STAT_HEAD
:
6200 case LINE_STAT_NONE
:
6201 text
= "Nothing to update";
6205 die("line type %d not handled in switch", line
->type
);
6208 if (status
&& status
->status
== 'U') {
6209 text
= "Press %s to resolve conflict in %s";
6210 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
6213 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
6216 string_format(view
->ref
, text
, key
, file
);
6218 string_copy(opt_file
, status
->new.name
);
6222 status_grep(struct view
*view
, struct line
*line
)
6224 struct status
*status
= line
->data
;
6227 const char buf
[2] = { status
->status
, 0 };
6228 const char *text
[] = { status
->new.name
, buf
, NULL
};
6230 return grep_text(view
, text
);
6236 static struct view_ops status_ops
= {
6249 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
6251 while (line
< end
) {
6252 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
6253 !io_write(io
, "\n", 1))
6256 if (line
->type
== LINE_DIFF_CHUNK
||
6257 line
->type
== LINE_DIFF_HEADER
)
6264 static struct line
*
6265 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
6267 for (; view
->line
< line
; line
--)
6268 if (line
->type
== type
)
6275 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
6277 const char *apply_argv
[SIZEOF_ARG
] = {
6278 "git", "apply", "--whitespace=nowarn", NULL
6280 struct line
*diff_hdr
;
6284 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6289 apply_argv
[argc
++] = "--cached";
6290 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6291 apply_argv
[argc
++] = "-R";
6292 apply_argv
[argc
++] = "-";
6293 apply_argv
[argc
++] = NULL
;
6294 if (!io_run(&io
, IO_WR
, opt_cdup
, apply_argv
))
6297 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6298 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6302 io_run_bg(update_index_argv
);
6304 return chunk
? TRUE
: FALSE
;
6308 stage_update(struct view
*view
, struct line
*line
)
6310 struct line
*chunk
= NULL
;
6312 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6313 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6316 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6317 report("Failed to apply chunk");
6321 } else if (!stage_status
.status
) {
6322 view
= VIEW(REQ_VIEW_STATUS
);
6324 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6325 if (line
->type
== stage_line_type
)
6328 if (!status_update_files(view
, line
+ 1)) {
6329 report("Failed to update files");
6333 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6334 report("Failed to update file");
6342 stage_revert(struct view
*view
, struct line
*line
)
6344 struct line
*chunk
= NULL
;
6346 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6347 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6350 if (!prompt_yesno("Are you sure you want to revert changes?"))
6353 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6354 report("Failed to revert chunk");
6360 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6361 stage_line_type
, FALSE
);
6367 stage_next(struct view
*view
, struct line
*line
)
6371 if (!stage_chunks
) {
6372 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6373 if (line
->type
!= LINE_DIFF_CHUNK
)
6376 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6377 report("Allocation failure");
6381 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6385 for (i
= 0; i
< stage_chunks
; i
++) {
6386 if (stage_chunk
[i
] > view
->lineno
) {
6387 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6388 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6393 report("No next chunk found");
6397 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6400 case REQ_STATUS_UPDATE
:
6401 if (!stage_update(view
, line
))
6405 case REQ_STATUS_REVERT
:
6406 if (!stage_revert(view
, line
))
6410 case REQ_STAGE_NEXT
:
6411 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6412 report("File is untracked; press %s to add",
6413 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6416 stage_next(view
, line
);
6420 if (!stage_status
.new.name
[0])
6422 if (stage_status
.status
== 'D') {
6423 report("File has been deleted.");
6427 open_editor(stage_status
.new.name
);
6431 /* Reload everything ... */
6434 case REQ_VIEW_BLAME
:
6435 if (stage_status
.new.name
[0]) {
6436 string_copy(opt_file
, stage_status
.new.name
);
6442 return pager_request(view
, request
, line
);
6448 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6449 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6451 /* Check whether the staged entry still exists, and close the
6452 * stage view if it doesn't. */
6453 if (!status_exists(&stage_status
, stage_line_type
)) {
6454 status_restore(VIEW(REQ_VIEW_STATUS
));
6455 return REQ_VIEW_CLOSE
;
6458 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6459 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6460 report("Cannot display a directory");
6464 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6465 report("Failed to open file: %s", strerror(errno
));
6469 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6474 static struct view_ops stage_ops
= {
6491 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6492 char title
[128]; /* First line of the commit message. */
6493 const char *author
; /* Author of the commit. */
6494 struct time time
; /* Date from the author ident. */
6495 struct ref_list
*refs
; /* Repository references. */
6496 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6497 size_t graph_size
; /* The width of the graph array. */
6498 bool has_parents
; /* Rewritten --parents seen. */
6501 /* Size of rev graph with no "padding" columns */
6502 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6505 struct rev_graph
*prev
, *next
, *parents
;
6506 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6508 struct commit
*commit
;
6510 unsigned int boundary
:1;
6513 /* Parents of the commit being visualized. */
6514 static struct rev_graph graph_parents
[4];
6516 /* The current stack of revisions on the graph. */
6517 static struct rev_graph graph_stacks
[4] = {
6518 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6519 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6520 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6521 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6525 graph_parent_is_merge(struct rev_graph
*graph
)
6527 return graph
->parents
->size
> 1;
6531 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6533 struct commit
*commit
= graph
->commit
;
6535 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6536 commit
->graph
[commit
->graph_size
++] = symbol
;
6540 clear_rev_graph(struct rev_graph
*graph
)
6542 graph
->boundary
= 0;
6543 graph
->size
= graph
->pos
= 0;
6544 graph
->commit
= NULL
;
6545 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6549 done_rev_graph(struct rev_graph
*graph
)
6551 if (graph_parent_is_merge(graph
) &&
6552 graph
->pos
< graph
->size
- 1 &&
6553 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6554 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6556 graph
->commit
->graph_size
= i
* 2;
6557 while (i
< graph
->next
->size
- 1) {
6558 append_to_rev_graph(graph
, ' ');
6559 append_to_rev_graph(graph
, '\\');
6564 clear_rev_graph(graph
);
6568 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6572 /* "Collapse" duplicate parents lines.
6574 * FIXME: This needs to also update update the drawn graph but
6575 * for now it just serves as a method for pruning graph lines. */
6576 for (i
= 0; i
< graph
->size
; i
++)
6577 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6580 if (graph
->size
< SIZEOF_REVITEMS
) {
6581 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6586 get_rev_graph_symbol(struct rev_graph
*graph
)
6590 if (graph
->boundary
)
6591 symbol
= REVGRAPH_BOUND
;
6592 else if (graph
->parents
->size
== 0)
6593 symbol
= REVGRAPH_INIT
;
6594 else if (graph_parent_is_merge(graph
))
6595 symbol
= REVGRAPH_MERGE
;
6596 else if (graph
->pos
>= graph
->size
)
6597 symbol
= REVGRAPH_BRANCH
;
6599 symbol
= REVGRAPH_COMMIT
;
6605 draw_rev_graph(struct rev_graph
*graph
)
6608 chtype separator
, line
;
6610 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6611 static struct rev_filler fillers
[] = {
6617 chtype symbol
= get_rev_graph_symbol(graph
);
6618 struct rev_filler
*filler
;
6621 fillers
[DEFAULT
].line
= opt_line_graphics
? ACS_VLINE
: '|';
6622 filler
= &fillers
[DEFAULT
];
6624 for (i
= 0; i
< graph
->pos
; i
++) {
6625 append_to_rev_graph(graph
, filler
->line
);
6626 if (graph_parent_is_merge(graph
->prev
) &&
6627 graph
->prev
->pos
== i
)
6628 filler
= &fillers
[RSHARP
];
6630 append_to_rev_graph(graph
, filler
->separator
);
6633 /* Place the symbol for this revision. */
6634 append_to_rev_graph(graph
, symbol
);
6636 if (graph
->prev
->size
> graph
->size
)
6637 filler
= &fillers
[RDIAG
];
6639 filler
= &fillers
[DEFAULT
];
6643 for (; i
< graph
->size
; i
++) {
6644 append_to_rev_graph(graph
, filler
->separator
);
6645 append_to_rev_graph(graph
, filler
->line
);
6646 if (graph_parent_is_merge(graph
->prev
) &&
6647 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6648 filler
= &fillers
[RSHARP
];
6649 if (graph
->prev
->size
> graph
->size
)
6650 filler
= &fillers
[LDIAG
];
6653 if (graph
->prev
->size
> graph
->size
) {
6654 append_to_rev_graph(graph
, filler
->separator
);
6655 if (filler
->line
!= ' ')
6656 append_to_rev_graph(graph
, filler
->line
);
6660 /* Prepare the next rev graph */
6662 prepare_rev_graph(struct rev_graph
*graph
)
6666 /* First, traverse all lines of revisions up to the active one. */
6667 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6668 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6671 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6674 /* Interleave the new revision parent(s). */
6675 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6676 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6678 /* Lastly, put any remaining revisions. */
6679 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6680 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6684 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6686 /* If this is the finalizing update ... */
6688 prepare_rev_graph(graph
);
6690 /* Graph visualization needs a one rev look-ahead,
6691 * so the first update doesn't visualize anything. */
6692 if (!graph
->prev
->commit
)
6695 if (view
->lines
> 2)
6696 view
->line
[view
->lines
- 3].dirty
= 1;
6697 if (view
->lines
> 1)
6698 view
->line
[view
->lines
- 2].dirty
= 1;
6699 draw_rev_graph(graph
->prev
);
6700 done_rev_graph(graph
->prev
->prev
);
6708 static const char *main_argv
[SIZEOF_ARG
] = {
6709 "git", "log", "--no-color", "--pretty=raw", "--parents",
6710 "--topo-order", "%(diff-args)", "%(rev-args)",
6711 "--", "%(file-args)", NULL
6715 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6717 struct commit
*commit
= line
->data
;
6719 if (!commit
->author
)
6722 if (opt_date
&& draw_date(view
, &commit
->time
))
6725 if (opt_author
&& draw_author(view
, commit
->author
))
6728 if (opt_rev_graph
&& commit
->graph_size
&&
6729 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6732 if (opt_show_refs
&& commit
->refs
) {
6735 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6736 struct ref
*ref
= commit
->refs
->refs
[i
];
6737 enum line_type type
;
6740 type
= LINE_MAIN_HEAD
;
6742 type
= LINE_MAIN_LOCAL_TAG
;
6744 type
= LINE_MAIN_TAG
;
6745 else if (ref
->tracked
)
6746 type
= LINE_MAIN_TRACKED
;
6747 else if (ref
->remote
)
6748 type
= LINE_MAIN_REMOTE
;
6750 type
= LINE_MAIN_REF
;
6752 if (draw_text(view
, type
, "[", TRUE
) ||
6753 draw_text(view
, type
, ref
->name
, TRUE
) ||
6754 draw_text(view
, type
, "]", TRUE
))
6757 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6762 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6766 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6768 main_read(struct view
*view
, char *line
)
6770 static struct rev_graph
*graph
= graph_stacks
;
6771 enum line_type type
;
6772 struct commit
*commit
;
6777 if (!view
->lines
&& !view
->prev
)
6778 die("No revisions match the given arguments.");
6779 if (view
->lines
> 0) {
6780 commit
= view
->line
[view
->lines
- 1].data
;
6781 view
->line
[view
->lines
- 1].dirty
= 1;
6782 if (!commit
->author
) {
6785 graph
->commit
= NULL
;
6788 update_rev_graph(view
, graph
);
6790 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6791 clear_rev_graph(&graph_stacks
[i
]);
6795 type
= get_line_type(line
);
6796 if (type
== LINE_COMMIT
) {
6797 commit
= calloc(1, sizeof(struct commit
));
6801 line
+= STRING_SIZE("commit ");
6803 graph
->boundary
= 1;
6807 string_copy_rev(commit
->id
, line
);
6808 commit
->refs
= get_ref_list(commit
->id
);
6809 graph
->commit
= commit
;
6810 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6812 while ((line
= strchr(line
, ' '))) {
6814 push_rev_graph(graph
->parents
, line
);
6815 commit
->has_parents
= TRUE
;
6822 commit
= view
->line
[view
->lines
- 1].data
;
6826 if (commit
->has_parents
)
6828 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6832 parse_author_line(line
+ STRING_SIZE("author "),
6833 &commit
->author
, &commit
->time
);
6834 update_rev_graph(view
, graph
);
6835 graph
= graph
->next
;
6839 /* Fill in the commit title if it has not already been set. */
6840 if (commit
->title
[0])
6843 /* Require titles to start with a non-space character at the
6844 * offset used by git log. */
6845 if (strncmp(line
, " ", 4))
6848 /* Well, if the title starts with a whitespace character,
6849 * try to be forgiving. Otherwise we end up with no title. */
6850 while (isspace(*line
))
6854 /* FIXME: More graceful handling of titles; append "..." to
6855 * shortened titles, etc. */
6857 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6858 view
->line
[view
->lines
- 1].dirty
= 1;
6865 main_request(struct view
*view
, enum request request
, struct line
*line
)
6867 enum open_flags flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
6871 open_view(view
, REQ_VIEW_DIFF
, flags
);
6875 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6885 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6890 if (!opt_show_refs
|| !list
)
6893 for (i
= 0; i
< list
->size
; i
++) {
6894 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6902 main_grep(struct view
*view
, struct line
*line
)
6904 struct commit
*commit
= line
->data
;
6905 const char *text
[] = {
6907 opt_author
? commit
->author
: "",
6908 mkdate(&commit
->time
, opt_date
),
6912 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6916 main_select(struct view
*view
, struct line
*line
)
6918 struct commit
*commit
= line
->data
;
6920 string_copy_rev(view
->ref
, commit
->id
);
6921 string_copy_rev(ref_commit
, view
->ref
);
6924 static struct view_ops main_ops
= {
6940 /* Whether or not the curses interface has been initialized. */
6941 static bool cursed
= FALSE
;
6943 /* Terminal hacks and workarounds. */
6944 static bool use_scroll_redrawwin
;
6945 static bool use_scroll_status_wclear
;
6947 /* The status window is used for polling keystrokes. */
6948 static WINDOW
*status_win
;
6950 /* Reading from the prompt? */
6951 static bool input_mode
= FALSE
;
6953 static bool status_empty
= FALSE
;
6955 /* Update status and title window. */
6957 report(const char *msg
, ...)
6959 struct view
*view
= display
[current_view
];
6965 char buf
[SIZEOF_STR
];
6968 va_start(args
, msg
);
6969 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6970 buf
[sizeof(buf
) - 1] = 0;
6971 buf
[sizeof(buf
) - 2] = '.';
6972 buf
[sizeof(buf
) - 3] = '.';
6973 buf
[sizeof(buf
) - 4] = '.';
6979 if (!status_empty
|| *msg
) {
6982 va_start(args
, msg
);
6984 wmove(status_win
, 0, 0);
6985 if (view
->has_scrolled
&& use_scroll_status_wclear
)
6988 vwprintw(status_win
, msg
, args
);
6989 status_empty
= FALSE
;
6991 status_empty
= TRUE
;
6993 wclrtoeol(status_win
);
6994 wnoutrefresh(status_win
);
6999 update_view_title(view
);
7008 /* Initialize the curses library */
7009 if (isatty(STDIN_FILENO
)) {
7010 cursed
= !!initscr();
7013 /* Leave stdin and stdout alone when acting as a pager. */
7014 opt_tty
= fopen("/dev/tty", "r+");
7016 die("Failed to open /dev/tty");
7017 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
7021 die("Failed to initialize curses");
7023 nonl(); /* Disable conversion and detect newlines from input. */
7024 cbreak(); /* Take input chars one at a time, no wait for \n */
7025 noecho(); /* Don't echo input */
7026 leaveok(stdscr
, FALSE
);
7031 getmaxyx(stdscr
, y
, x
);
7032 status_win
= newwin(1, 0, y
- 1, 0);
7034 die("Failed to create status window");
7036 /* Enable keyboard mapping */
7037 keypad(status_win
, TRUE
);
7038 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
7040 TABSIZE
= opt_tab_size
;
7042 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
7043 if (term
&& !strcmp(term
, "gnome-terminal")) {
7044 /* In the gnome-terminal-emulator, the message from
7045 * scrolling up one line when impossible followed by
7046 * scrolling down one line causes corruption of the
7047 * status line. This is fixed by calling wclear. */
7048 use_scroll_status_wclear
= TRUE
;
7049 use_scroll_redrawwin
= FALSE
;
7051 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
7052 /* No problems with full optimizations in xrvt-(unicode)
7054 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
7057 /* When scrolling in (u)xterm the last line in the
7058 * scrolling direction will update slowly. */
7059 use_scroll_redrawwin
= TRUE
;
7060 use_scroll_status_wclear
= FALSE
;
7065 get_input(int prompt_position
)
7068 int i
, key
, cursor_y
, cursor_x
;
7070 if (prompt_position
)
7074 bool loading
= FALSE
;
7076 foreach_view (view
, i
) {
7078 if (view_is_displayed(view
) && view
->has_scrolled
&&
7079 use_scroll_redrawwin
)
7080 redrawwin(view
->win
);
7081 view
->has_scrolled
= FALSE
;
7086 /* Update the cursor position. */
7087 if (prompt_position
) {
7088 getbegyx(status_win
, cursor_y
, cursor_x
);
7089 cursor_x
= prompt_position
;
7091 view
= display
[current_view
];
7092 getbegyx(view
->win
, cursor_y
, cursor_x
);
7093 cursor_x
= view
->width
- 1;
7094 cursor_y
+= view
->lineno
- view
->offset
;
7096 setsyx(cursor_y
, cursor_x
);
7098 /* Refresh, accept single keystroke of input */
7100 nodelay(status_win
, loading
);
7101 key
= wgetch(status_win
);
7103 /* wgetch() with nodelay() enabled returns ERR when
7104 * there's no input. */
7107 } else if (key
== KEY_RESIZE
) {
7110 getmaxyx(stdscr
, height
, width
);
7112 wresize(status_win
, 1, width
);
7113 mvwin(status_win
, height
- 1, 0);
7114 wnoutrefresh(status_win
);
7116 redraw_display(TRUE
);
7126 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7128 enum input_status status
= INPUT_OK
;
7129 static char buf
[SIZEOF_STR
];
7134 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7137 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7138 wclrtoeol(status_win
);
7140 key
= get_input(pos
+ 1);
7145 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7152 status
= INPUT_CANCEL
;
7156 status
= INPUT_CANCEL
;
7160 if (pos
>= sizeof(buf
)) {
7161 report("Input string too long");
7165 status
= handler(data
, buf
, key
);
7166 if (status
== INPUT_OK
)
7167 buf
[pos
++] = (char) key
;
7171 /* Clear the status window */
7172 status_empty
= FALSE
;
7175 if (status
== INPUT_CANCEL
)
7183 static enum input_status
7184 prompt_yesno_handler(void *data
, char *buf
, int c
)
7186 if (c
== 'y' || c
== 'Y')
7188 if (c
== 'n' || c
== 'N')
7189 return INPUT_CANCEL
;
7194 prompt_yesno(const char *prompt
)
7196 char prompt2
[SIZEOF_STR
];
7198 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7201 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7204 static enum input_status
7205 read_prompt_handler(void *data
, char *buf
, int c
)
7207 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7211 read_prompt(const char *prompt
)
7213 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7216 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7218 enum input_status status
= INPUT_OK
;
7221 while (items
[size
].text
)
7224 while (status
== INPUT_OK
) {
7225 const struct menu_item
*item
= &items
[*selected
];
7229 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7230 prompt
, *selected
+ 1, size
);
7232 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7233 wprintw(status_win
, "%s", item
->text
);
7234 wclrtoeol(status_win
);
7236 key
= get_input(COLS
- 1);
7241 status
= INPUT_STOP
;
7246 *selected
= *selected
- 1;
7248 *selected
= size
- 1;
7253 *selected
= (*selected
+ 1) % size
;
7257 status
= INPUT_CANCEL
;
7261 for (i
= 0; items
[i
].text
; i
++)
7262 if (items
[i
].hotkey
== key
) {
7264 status
= INPUT_STOP
;
7270 /* Clear the status window */
7271 status_empty
= FALSE
;
7274 return status
!= INPUT_CANCEL
;
7278 * Repository properties
7281 static struct ref
**refs
= NULL
;
7282 static size_t refs_size
= 0;
7283 static struct ref
*refs_head
= NULL
;
7285 static struct ref_list
**ref_lists
= NULL
;
7286 static size_t ref_lists_size
= 0;
7288 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7289 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7290 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7293 compare_refs(const void *ref1_
, const void *ref2_
)
7295 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7296 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7298 if (ref1
->tag
!= ref2
->tag
)
7299 return ref2
->tag
- ref1
->tag
;
7300 if (ref1
->ltag
!= ref2
->ltag
)
7301 return ref2
->ltag
- ref2
->ltag
;
7302 if (ref1
->head
!= ref2
->head
)
7303 return ref2
->head
- ref1
->head
;
7304 if (ref1
->tracked
!= ref2
->tracked
)
7305 return ref2
->tracked
- ref1
->tracked
;
7306 if (ref1
->remote
!= ref2
->remote
)
7307 return ref2
->remote
- ref1
->remote
;
7308 return strcmp(ref1
->name
, ref2
->name
);
7312 foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
)
7316 for (i
= 0; i
< refs_size
; i
++)
7317 if (!visitor(data
, refs
[i
]))
7327 static struct ref_list
*
7328 get_ref_list(const char *id
)
7330 struct ref_list
*list
;
7333 for (i
= 0; i
< ref_lists_size
; i
++)
7334 if (!strcmp(id
, ref_lists
[i
]->id
))
7335 return ref_lists
[i
];
7337 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7339 list
= calloc(1, sizeof(*list
));
7343 for (i
= 0; i
< refs_size
; i
++) {
7344 if (!strcmp(id
, refs
[i
]->id
) &&
7345 realloc_refs_list(&list
->refs
, list
->size
, 1))
7346 list
->refs
[list
->size
++] = refs
[i
];
7354 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7355 ref_lists
[ref_lists_size
++] = list
;
7360 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
7362 struct ref
*ref
= NULL
;
7365 bool remote
= FALSE
;
7366 bool tracked
= FALSE
;
7368 int from
= 0, to
= refs_size
- 1;
7370 if (!prefixcmp(name
, "refs/tags/")) {
7371 if (!suffixcmp(name
, namelen
, "^{}")) {
7379 namelen
-= STRING_SIZE("refs/tags/");
7380 name
+= STRING_SIZE("refs/tags/");
7382 } else if (!prefixcmp(name
, "refs/remotes/")) {
7384 namelen
-= STRING_SIZE("refs/remotes/");
7385 name
+= STRING_SIZE("refs/remotes/");
7386 tracked
= !strcmp(opt_remote
, name
);
7388 } else if (!prefixcmp(name
, "refs/heads/")) {
7389 namelen
-= STRING_SIZE("refs/heads/");
7390 name
+= STRING_SIZE("refs/heads/");
7391 if (!strncmp(opt_head
, name
, namelen
))
7394 } else if (!strcmp(name
, "HEAD")) {
7397 namelen
= strlen(opt_head
);
7402 /* If we are reloading or it's an annotated tag, replace the
7403 * previous SHA1 with the resolved commit id; relies on the fact
7404 * git-ls-remote lists the commit id of an annotated tag right
7405 * before the commit id it points to. */
7406 while (from
<= to
) {
7407 size_t pos
= (to
+ from
) / 2;
7408 int cmp
= strcmp(name
, refs
[pos
]->name
);
7422 if (!realloc_refs(&refs
, refs_size
, 1))
7424 ref
= calloc(1, sizeof(*ref
) + namelen
);
7427 memmove(refs
+ from
+ 1, refs
+ from
,
7428 (refs_size
- from
) * sizeof(*refs
));
7430 strncpy(ref
->name
, name
, namelen
);
7437 ref
->remote
= remote
;
7438 ref
->tracked
= tracked
;
7439 string_copy_rev(ref
->id
, id
);
7449 const char *head_argv
[] = {
7450 "git", "symbolic-ref", "HEAD", NULL
7452 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7453 "git", "ls-remote", opt_git_dir
, NULL
7455 static bool init
= FALSE
;
7459 if (!argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE"))
7460 die("TIG_LS_REMOTE contains too many arguments");
7467 if (io_run_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7468 !prefixcmp(opt_head
, "refs/heads/")) {
7469 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7471 memmove(opt_head
, offset
, strlen(offset
) + 1);
7475 for (i
= 0; i
< refs_size
; i
++)
7478 if (io_run_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7481 /* Update the ref lists to reflect changes. */
7482 for (i
= 0; i
< ref_lists_size
; i
++) {
7483 struct ref_list
*list
= ref_lists
[i
];
7486 for (old
= new = 0; old
< list
->size
; old
++)
7487 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7488 list
->refs
[new++] = list
->refs
[old
];
7496 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7498 if (!strcmp(name
, ".remote")) {
7499 string_ncopy(opt_remote
, value
, valuelen
);
7501 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7502 size_t from
= strlen(opt_remote
);
7504 if (!prefixcmp(value
, "refs/heads/"))
7505 value
+= STRING_SIZE("refs/heads/");
7507 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7513 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7515 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7516 int argc
= 1 + (cmd
== option_set_command
);
7519 if (!argv_from_string(argv
, &argc
, value
))
7520 config_msg
= "Too many option arguments";
7522 error
= cmd(argc
, argv
);
7525 warn("Option 'tig.%s': %s", name
, config_msg
);
7529 set_environment_variable(const char *name
, const char *value
)
7531 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7532 char *env
= malloc(len
);
7535 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7543 set_work_tree(const char *value
)
7545 char cwd
[SIZEOF_STR
];
7547 if (!getcwd(cwd
, sizeof(cwd
)))
7548 die("Failed to get cwd path: %s", strerror(errno
));
7549 if (chdir(opt_git_dir
) < 0)
7550 die("Failed to chdir(%s): %s", strerror(errno
));
7551 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7552 die("Failed to get git path: %s", strerror(errno
));
7554 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7555 if (chdir(value
) < 0)
7556 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7557 if (!getcwd(cwd
, sizeof(cwd
)))
7558 die("Failed to get cwd path: %s", strerror(errno
));
7559 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7560 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7561 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7562 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7563 opt_is_inside_work_tree
= TRUE
;
7567 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7569 if (!strcmp(name
, "i18n.commitencoding"))
7570 string_ncopy(opt_encoding
, value
, valuelen
);
7572 else if (!strcmp(name
, "core.editor"))
7573 string_ncopy(opt_editor
, value
, valuelen
);
7575 else if (!strcmp(name
, "core.worktree"))
7576 set_work_tree(value
);
7578 else if (!prefixcmp(name
, "tig.color."))
7579 set_repo_config_option(name
+ 10, value
, option_color_command
);
7581 else if (!prefixcmp(name
, "tig.bind."))
7582 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7584 else if (!prefixcmp(name
, "tig."))
7585 set_repo_config_option(name
+ 4, value
, option_set_command
);
7587 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7588 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7589 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7595 load_git_config(void)
7597 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7599 return io_run_load(config_list_argv
, "=", read_repo_config_option
);
7603 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7605 if (!opt_git_dir
[0]) {
7606 string_ncopy(opt_git_dir
, name
, namelen
);
7608 } else if (opt_is_inside_work_tree
== -1) {
7609 /* This can be 3 different values depending on the
7610 * version of git being used. If git-rev-parse does not
7611 * understand --is-inside-work-tree it will simply echo
7612 * the option else either "true" or "false" is printed.
7613 * Default to true for the unknown case. */
7614 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7616 } else if (*name
== '.') {
7617 string_ncopy(opt_cdup
, name
, namelen
);
7620 string_ncopy(opt_prefix
, name
, namelen
);
7627 load_repo_info(void)
7629 const char *rev_parse_argv
[] = {
7630 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7631 "--show-cdup", "--show-prefix", NULL
7634 return io_run_load(rev_parse_argv
, "=", read_repo_info
);
7642 static const char usage
[] =
7643 "tig " TIG_VERSION
" (" __DATE__
")\n"
7645 "Usage: tig [options] [revs] [--] [paths]\n"
7646 " or: tig show [options] [revs] [--] [paths]\n"
7647 " or: tig blame [rev] path\n"
7649 " or: tig < [git command output]\n"
7652 " -v, --version Show version and exit\n"
7653 " -h, --help Show help message and exit";
7655 static void __NORETURN
7658 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7664 static void __NORETURN
7665 die(const char *err
, ...)
7671 va_start(args
, err
);
7672 fputs("tig: ", stderr
);
7673 vfprintf(stderr
, err
, args
);
7674 fputs("\n", stderr
);
7681 warn(const char *msg
, ...)
7685 va_start(args
, msg
);
7686 fputs("tig warning: ", stderr
);
7687 vfprintf(stderr
, msg
, args
);
7688 fputs("\n", stderr
);
7692 static const char ***filter_args
;
7695 read_filter_args(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7697 return argv_append(filter_args
, name
) ? OK
: ERR
;
7701 filter_rev_parse(const char ***args
, const char *arg1
, const char *arg2
, const char *argv
[])
7703 const char *rev_parse_argv
[SIZEOF_ARG
] = { "git", "rev-parse", arg1
, arg2
};
7704 const char **all_argv
= NULL
;
7707 if (!argv_append_array(&all_argv
, rev_parse_argv
) ||
7708 !argv_append_array(&all_argv
, argv
) ||
7709 !io_run_load(all_argv
, "\n", read_filter_args
) == ERR
)
7710 die("Failed to split arguments");
7711 argv_free(all_argv
);
7716 filter_options(const char *argv
[])
7718 filter_rev_parse(&opt_file_args
, "--no-revs", "--no-flags", argv
);
7719 filter_rev_parse(&opt_diff_args
, "--no-revs", "--flags", argv
);
7720 filter_rev_parse(&opt_rev_args
, "--symbolic", "--revs-only", argv
);
7724 parse_options(int argc
, const char *argv
[])
7726 enum request request
= REQ_VIEW_MAIN
;
7727 const char *subcommand
;
7728 bool seen_dashdash
= FALSE
;
7729 const char **filter_argv
= NULL
;
7732 if (!isatty(STDIN_FILENO
)) {
7733 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7734 return REQ_VIEW_PAGER
;
7738 return REQ_VIEW_MAIN
;
7740 subcommand
= argv
[1];
7741 if (!strcmp(subcommand
, "status")) {
7743 warn("ignoring arguments after `%s'", subcommand
);
7744 return REQ_VIEW_STATUS
;
7746 } else if (!strcmp(subcommand
, "blame")) {
7747 if (argc
<= 2 || argc
> 4)
7748 die("invalid number of options to blame\n\n%s", usage
);
7752 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7756 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7757 return REQ_VIEW_BLAME
;
7759 } else if (!strcmp(subcommand
, "show")) {
7760 request
= REQ_VIEW_DIFF
;
7766 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7767 const char *opt
= argv
[i
];
7769 if (seen_dashdash
) {
7770 argv_append(&opt_file_args
, opt
);
7773 } else if (!strcmp(opt
, "--")) {
7774 seen_dashdash
= TRUE
;
7777 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7778 printf("tig version %s\n", TIG_VERSION
);
7781 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7782 printf("%s\n", usage
);
7785 } else if (!strcmp(opt
, "--all")) {
7786 argv_append(&opt_rev_args
, opt
);
7790 if (!argv_append(&filter_argv
, opt
))
7791 die("command too long");
7795 filter_options(filter_argv
);
7801 main(int argc
, const char *argv
[])
7803 const char *codeset
= "UTF-8";
7804 enum request request
= parse_options(argc
, argv
);
7808 signal(SIGINT
, quit
);
7809 signal(SIGPIPE
, SIG_IGN
);
7811 if (setlocale(LC_ALL
, "")) {
7812 codeset
= nl_langinfo(CODESET
);
7815 if (load_repo_info() == ERR
)
7816 die("Failed to load repo info.");
7818 if (load_options() == ERR
)
7819 die("Failed to load user config.");
7821 if (load_git_config() == ERR
)
7822 die("Failed to load repo config.");
7824 /* Require a git repository unless when running in pager mode. */
7825 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7826 die("Not a git repository");
7828 if (*opt_encoding
&& strcmp(codeset
, "UTF-8")) {
7829 opt_iconv_in
= iconv_open("UTF-8", opt_encoding
);
7830 if (opt_iconv_in
== ICONV_NONE
)
7831 die("Failed to initialize character set conversion");
7834 if (codeset
&& strcmp(codeset
, "UTF-8")) {
7835 opt_iconv_out
= iconv_open(codeset
, "UTF-8");
7836 if (opt_iconv_out
== ICONV_NONE
)
7837 die("Failed to initialize character set conversion");
7840 if (load_refs() == ERR
)
7841 die("Failed to load refs.");
7843 foreach_view (view
, i
) {
7844 if (getenv(view
->cmd_env
))
7845 warn("Use of the %s environment variable is deprecated,"
7846 " use options or TIG_DIFF_ARGS instead",
7848 if (!argv_from_env(view
->ops
->argv
, view
->cmd_env
))
7849 die("Too many arguments in the `%s` environment variable",
7855 while (view_driver(display
[current_view
], request
)) {
7856 int key
= get_input(0);
7858 view
= display
[current_view
];
7859 request
= get_keybinding(view
->keymap
, key
);
7861 /* Some low-level request handling. This keeps access to
7862 * status_win restricted. */
7865 report("Unknown key, press %s for help",
7866 get_key(view
->keymap
, REQ_VIEW_HELP
));
7870 char *cmd
= read_prompt(":");
7872 if (cmd
&& isdigit(*cmd
)) {
7873 int lineno
= view
->lineno
+ 1;
7875 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7876 select_view_line(view
, lineno
- 1);
7879 report("Unable to parse '%s' as a line number", cmd
);
7883 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7884 const char *argv
[SIZEOF_ARG
] = { "git" };
7887 /* When running random commands, initially show the
7888 * command in the title. However, it maybe later be
7889 * overwritten if a commit line is selected. */
7890 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7892 if (!argv_from_string(argv
, &argc
, cmd
)) {
7893 report("Too many arguments");
7894 } else if (!prepare_update(next
, argv
, NULL
)) {
7895 report("Failed to format command");
7897 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7905 case REQ_SEARCH_BACK
:
7907 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7908 char *search
= read_prompt(prompt
);
7911 string_ncopy(opt_search
, search
, strlen(search
));
7912 else if (*opt_search
)
7913 request
= request
== REQ_SEARCH
?