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);
144 FORMAT_ALL
, /* Perform replacement in all arguments. */
145 FORMAT_DASH
, /* Perform replacement up until "--". */
146 FORMAT_NONE
/* No replacement should be performed. */
149 static bool format_argv(const char *dst
[], const char *src
[], enum format_flags flags
);
158 typedef enum input_status (*input_handler
)(void *data
, char *buf
, int c
);
160 static char *prompt_input(const char *prompt
, input_handler handler
, void *data
);
161 static bool prompt_yesno(const char *prompt
);
169 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
);
172 * Allocation helpers ... Entering macro hell to never be seen again.
175 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
177 name(type **mem, size_t size, size_t increase) \
179 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
180 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
183 if (mem == NULL || num_chunks != num_chunks_new) { \
184 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
197 string_ncopy_do(char *dst
, size_t dstlen
, const char *src
, size_t srclen
)
199 if (srclen
> dstlen
- 1)
202 strncpy(dst
, src
, srclen
);
206 /* Shorthands for safely copying into a fixed buffer. */
208 #define string_copy(dst, src) \
209 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
211 #define string_ncopy(dst, src, srclen) \
212 string_ncopy_do(dst, sizeof(dst), src, srclen)
214 #define string_copy_rev(dst, src) \
215 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
217 #define string_add(dst, from, src) \
218 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
221 string_expand(char *dst
, size_t dstlen
, const char *src
, int tabsize
)
225 for (size
= pos
= 0; size
< dstlen
- 1 && src
[pos
]; pos
++) {
226 if (src
[pos
] == '\t') {
227 size_t expanded
= tabsize
- (size
% tabsize
);
229 if (expanded
+ size
>= dstlen
- 1)
230 expanded
= dstlen
- size
- 1;
231 memcpy(dst
+ size
, " ", expanded
);
234 dst
[size
++] = src
[pos
];
242 chomp_string(char *name
)
246 while (isspace(*name
))
249 namelen
= strlen(name
) - 1;
250 while (namelen
> 0 && isspace(name
[namelen
]))
257 string_nformat(char *buf
, size_t bufsize
, size_t *bufpos
, const char *fmt
, ...)
260 size_t pos
= bufpos
? *bufpos
: 0;
263 pos
+= vsnprintf(buf
+ pos
, bufsize
- pos
, fmt
, args
);
269 return pos
>= bufsize
? FALSE
: TRUE
;
272 #define string_format(buf, fmt, args...) \
273 string_nformat(buf, sizeof(buf), NULL, fmt, args)
275 #define string_format_from(buf, from, fmt, args...) \
276 string_nformat(buf, sizeof(buf), from, fmt, args)
279 string_enum_compare(const char *str1
, const char *str2
, int len
)
283 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
285 /* Diff-Header == DIFF_HEADER */
286 for (i
= 0; i
< len
; i
++) {
287 if (toupper(str1
[i
]) == toupper(str2
[i
]))
290 if (string_enum_sep(str1
[i
]) &&
291 string_enum_sep(str2
[i
]))
294 return str1
[i
] - str2
[i
];
300 #define enum_equals(entry, str, len) \
301 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
309 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
312 enum_map_name(const char *name
, size_t namelen
)
314 static char buf
[SIZEOF_STR
];
317 for (bufpos
= 0; bufpos
<= namelen
; bufpos
++) {
318 buf
[bufpos
] = tolower(name
[bufpos
]);
319 if (buf
[bufpos
] == '_')
327 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
330 map_enum_do(const struct enum_map
*map
, size_t map_size
, int *value
, const char *name
)
332 size_t namelen
= strlen(name
);
335 for (i
= 0; i
< map_size
; i
++)
336 if (enum_equals(map
[i
], name
, namelen
)) {
337 *value
= map
[i
].value
;
344 #define map_enum(attr, map, name) \
345 map_enum_do(map, ARRAY_SIZE(map), attr, name)
347 #define prefixcmp(str1, str2) \
348 strncmp(str1, str2, STRING_SIZE(str2))
351 suffixcmp(const char *str
, int slen
, const char *suffix
)
353 size_t len
= slen
>= 0 ? slen
: strlen(str
);
354 size_t suffixlen
= strlen(suffix
);
356 return suffixlen
< len
? strcmp(str
+ len
- suffixlen
, suffix
) : -1;
361 * Unicode / UTF-8 handling
363 * NOTE: Much of the following code for dealing with Unicode is derived from
364 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
365 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
369 unicode_width(unsigned long c
, int tab_size
)
372 (c
<= 0x115f /* Hangul Jamo */
375 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
377 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
378 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
379 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
380 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
381 || (c
>= 0xffe0 && c
<= 0xffe6)
382 || (c
>= 0x20000 && c
<= 0x2fffd)
383 || (c
>= 0x30000 && c
<= 0x3fffd)))
392 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
393 * Illegal bytes are set one. */
394 static const unsigned char utf8_bytes
[256] = {
395 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,
396 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,
397 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,
398 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,
399 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,
400 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,
401 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,
402 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,
405 static inline unsigned char
406 utf8_char_length(const char *string
, const char *end
)
408 int c
= *(unsigned char *) string
;
410 return utf8_bytes
[c
];
413 /* Decode UTF-8 multi-byte representation into a Unicode character. */
414 static inline unsigned long
415 utf8_to_unicode(const char *string
, size_t length
)
417 unsigned long unicode
;
424 unicode
= (string
[0] & 0x1f) << 6;
425 unicode
+= (string
[1] & 0x3f);
428 unicode
= (string
[0] & 0x0f) << 12;
429 unicode
+= ((string
[1] & 0x3f) << 6);
430 unicode
+= (string
[2] & 0x3f);
433 unicode
= (string
[0] & 0x0f) << 18;
434 unicode
+= ((string
[1] & 0x3f) << 12);
435 unicode
+= ((string
[2] & 0x3f) << 6);
436 unicode
+= (string
[3] & 0x3f);
439 unicode
= (string
[0] & 0x0f) << 24;
440 unicode
+= ((string
[1] & 0x3f) << 18);
441 unicode
+= ((string
[2] & 0x3f) << 12);
442 unicode
+= ((string
[3] & 0x3f) << 6);
443 unicode
+= (string
[4] & 0x3f);
446 unicode
= (string
[0] & 0x01) << 30;
447 unicode
+= ((string
[1] & 0x3f) << 24);
448 unicode
+= ((string
[2] & 0x3f) << 18);
449 unicode
+= ((string
[3] & 0x3f) << 12);
450 unicode
+= ((string
[4] & 0x3f) << 6);
451 unicode
+= (string
[5] & 0x3f);
457 /* Invalid characters could return the special 0xfffd value but NUL
458 * should be just as good. */
459 return unicode
> 0xffff ? 0 : unicode
;
462 /* Calculates how much of string can be shown within the given maximum width
463 * and sets trimmed parameter to non-zero value if all of string could not be
464 * shown. If the reserve flag is TRUE, it will reserve at least one
465 * trailing character, which can be useful when drawing a delimiter.
467 * Returns the number of bytes to output from string to satisfy max_width. */
469 utf8_length(const char **start
, size_t skip
, int *width
, size_t max_width
, int *trimmed
, bool reserve
, int tab_size
)
471 const char *string
= *start
;
472 const char *end
= strchr(string
, '\0');
473 unsigned char last_bytes
= 0;
474 size_t last_ucwidth
= 0;
479 while (string
< end
) {
480 unsigned char bytes
= utf8_char_length(string
, end
);
482 unsigned long unicode
;
484 if (string
+ bytes
> end
)
487 /* Change representation to figure out whether
488 * it is a single- or double-width character. */
490 unicode
= utf8_to_unicode(string
, bytes
);
491 /* FIXME: Graceful handling of invalid Unicode character. */
495 ucwidth
= unicode_width(unicode
, tab_size
);
497 skip
-= ucwidth
<= skip
? ucwidth
: skip
;
501 if (*width
> max_width
) {
504 if (reserve
&& *width
== max_width
) {
505 string
-= last_bytes
;
506 *width
-= last_ucwidth
;
512 last_bytes
= ucwidth
? bytes
: 0;
513 last_ucwidth
= ucwidth
;
516 return string
- *start
;
527 #define DATE_(name) DATE_##name
532 static const struct enum_map date_map
[] = {
533 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
543 static inline int timecmp(const struct time
*t1
, const struct time
*t2
)
545 return t1
->sec
- t2
->sec
;
549 mkdate(const struct time
*time
, enum date date
)
551 static char buf
[DATE_COLS
+ 1];
552 static const struct enum_map reldate
[] = {
553 { "second", 1, 60 * 2 },
554 { "minute", 60, 60 * 60 * 2 },
555 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
556 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
557 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
558 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
562 if (!date
|| !time
|| !time
->sec
)
565 if (date
== DATE_RELATIVE
) {
567 time_t date
= time
->sec
+ time
->tz
;
571 gettimeofday(&now
, NULL
);
572 seconds
= now
.tv_sec
< date
? date
- now
.tv_sec
: now
.tv_sec
- date
;
573 for (i
= 0; i
< ARRAY_SIZE(reldate
); i
++) {
574 if (seconds
>= reldate
[i
].value
)
577 seconds
/= reldate
[i
].namelen
;
578 if (!string_format(buf
, "%ld %s%s %s",
579 seconds
, reldate
[i
].name
,
580 seconds
> 1 ? "s" : "",
581 now
.tv_sec
>= date
? "ago" : "ahead"))
587 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
);
677 * Executing external commands.
681 IO_FD
, /* File descriptor based IO. */
682 IO_BG
, /* Execute command in the background. */
683 IO_FG
, /* Execute command with same std{in,out,err}. */
684 IO_RD
, /* Read only fork+exec IO. */
685 IO_WR
, /* Write only fork+exec IO. */
686 IO_AP
, /* Append fork+exec output to file. */
690 enum io_type type
; /* The requested type of pipe. */
691 const char *dir
; /* Directory from which to execute. */
692 pid_t pid
; /* PID of spawned process. */
693 int pipe
; /* Pipe end for reading or writing. */
694 int error
; /* Error status. */
695 const char *argv
[SIZEOF_ARG
]; /* Shell command arguments. */
696 char *buf
; /* Read buffer. */
697 size_t bufalloc
; /* Allocated buffer size. */
698 size_t bufsize
; /* Buffer content size. */
699 char *bufpos
; /* Current buffer position. */
700 unsigned int eof
:1; /* Has end of file been reached. */
704 io_reset(struct io
*io
)
708 io
->buf
= io
->bufpos
= NULL
;
709 io
->bufalloc
= io
->bufsize
= 0;
715 io_init(struct io
*io
, const char *dir
, enum io_type type
)
723 io_format(struct io
*io
, const char *dir
, enum io_type type
,
724 const char *argv
[], enum format_flags flags
)
726 io_init(io
, dir
, type
);
727 return format_argv(io
->argv
, argv
, flags
);
731 io_open(struct io
*io
, const char *fmt
, ...)
733 char name
[SIZEOF_STR
] = "";
737 io_init(io
, NULL
, IO_FD
);
740 fits
= vsnprintf(name
, sizeof(name
), fmt
, args
) < sizeof(name
);
744 io
->error
= ENAMETOOLONG
;
747 io
->pipe
= *name
? open(name
, O_RDONLY
) : STDIN_FILENO
;
750 return io
->pipe
!= -1;
754 io_kill(struct io
*io
)
756 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
760 io_done(struct io
*io
)
771 pid_t waiting
= waitpid(pid
, &status
, 0);
780 return waiting
== pid
&&
781 !WIFSIGNALED(status
) &&
783 !WEXITSTATUS(status
);
790 io_start(struct io
*io
)
792 int pipefds
[2] = { -1, -1 };
794 if (io
->type
== IO_FD
)
797 if ((io
->type
== IO_RD
|| io
->type
== IO_WR
) && pipe(pipefds
) < 0) {
800 } else if (io
->type
== IO_AP
) {
801 pipefds
[1] = io
->pipe
;
804 if ((io
->pid
= fork())) {
807 if (pipefds
[!(io
->type
== IO_WR
)] != -1)
808 close(pipefds
[!(io
->type
== IO_WR
)]);
810 io
->pipe
= pipefds
[!!(io
->type
== IO_WR
)];
815 if (io
->type
!= IO_FG
) {
816 int devnull
= open("/dev/null", O_RDWR
);
817 int readfd
= io
->type
== IO_WR
? pipefds
[0] : devnull
;
818 int writefd
= (io
->type
== IO_RD
|| io
->type
== IO_AP
)
819 ? pipefds
[1] : devnull
;
821 dup2(readfd
, STDIN_FILENO
);
822 dup2(writefd
, STDOUT_FILENO
);
823 dup2(devnull
, STDERR_FILENO
);
826 if (pipefds
[0] != -1)
828 if (pipefds
[1] != -1)
832 if (io
->dir
&& *io
->dir
&& chdir(io
->dir
) == -1)
835 execvp(io
->argv
[0], (char *const*) io
->argv
);
839 if (pipefds
[!!(io
->type
== IO_WR
)] != -1)
840 close(pipefds
[!!(io
->type
== IO_WR
)]);
845 io_run(struct io
*io
, const char **argv
, const char *dir
, enum io_type type
)
847 io_init(io
, dir
, type
);
848 if (!format_argv(io
->argv
, argv
, FORMAT_NONE
))
854 io_complete(struct io
*io
)
856 return io_start(io
) && io_done(io
);
860 io_run_bg(const char **argv
)
864 if (!io_format(&io
, NULL
, IO_BG
, argv
, FORMAT_NONE
))
866 return io_complete(&io
);
870 io_run_fg(const char **argv
, const char *dir
)
874 if (!io_format(&io
, dir
, IO_FG
, argv
, FORMAT_NONE
))
876 return io_complete(&io
);
880 io_run_append(const char **argv
, enum format_flags flags
, int fd
)
884 if (!io_format(&io
, NULL
, IO_AP
, argv
, flags
)) {
890 return io_complete(&io
);
894 io_run_rd(struct io
*io
, const char **argv
, const char *dir
, enum format_flags flags
)
896 return io_format(io
, dir
, IO_RD
, argv
, flags
) && io_start(io
);
900 io_eof(struct io
*io
)
906 io_error(struct io
*io
)
912 io_strerror(struct io
*io
)
914 return strerror(io
->error
);
918 io_can_read(struct io
*io
)
920 struct timeval tv
= { 0, 500 };
924 FD_SET(io
->pipe
, &fds
);
926 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
930 io_read(struct io
*io
, void *buf
, size_t bufsize
)
933 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
935 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
937 else if (readsize
== -1)
939 else if (readsize
== 0)
945 DEFINE_ALLOCATOR(io_realloc_buf
, char, BUFSIZ
)
948 io_get(struct io
*io
, int c
, bool can_read
)
954 if (io
->bufsize
> 0) {
955 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
957 char *line
= io
->bufpos
;
960 io
->bufpos
= eol
+ 1;
961 io
->bufsize
-= io
->bufpos
- line
;
968 io
->bufpos
[io
->bufsize
] = 0;
978 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
979 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
981 if (io
->bufalloc
== io
->bufsize
) {
982 if (!io_realloc_buf(&io
->buf
, io
->bufalloc
, BUFSIZ
))
984 io
->bufalloc
+= BUFSIZ
;
987 io
->bufpos
= io
->buf
;
988 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
991 io
->bufsize
+= readsize
;
996 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
1000 while (!io_error(io
) && written
< bufsize
) {
1003 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
1004 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
1006 else if (size
== -1)
1012 return written
== bufsize
;
1016 io_read_buf(struct io
*io
, char buf
[], size_t bufsize
)
1018 char *result
= io_get(io
, '\n', TRUE
);
1021 result
= chomp_string(result
);
1022 string_ncopy_do(buf
, bufsize
, result
, strlen(result
));
1025 return io_done(io
) && result
;
1029 io_run_buf(const char **argv
, char buf
[], size_t bufsize
)
1033 return io_run_rd(&io
, argv
, NULL
, FORMAT_NONE
)
1034 && io_read_buf(&io
, buf
, bufsize
);
1038 io_load(struct io
*io
, const char *separators
,
1039 int (*read_property
)(char *, size_t, char *, size_t))
1047 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
1052 name
= chomp_string(name
);
1053 namelen
= strcspn(name
, separators
);
1055 if (name
[namelen
]) {
1057 value
= chomp_string(name
+ namelen
+ 1);
1058 valuelen
= strlen(value
);
1065 state
= read_property(name
, namelen
, value
, valuelen
);
1068 if (state
!= ERR
&& io_error(io
))
1076 io_run_load(const char **argv
, const char *separators
,
1077 int (*read_property
)(char *, size_t, char *, size_t))
1081 return io_format(&io
, NULL
, IO_RD
, argv
, FORMAT_NONE
)
1082 ? io_load(&io
, separators
, read_property
) : ERR
;
1091 /* XXX: Keep the view request first and in sync with views[]. */ \
1092 REQ_GROUP("View switching") \
1093 REQ_(VIEW_MAIN, "Show main view"), \
1094 REQ_(VIEW_DIFF, "Show diff view"), \
1095 REQ_(VIEW_LOG, "Show log view"), \
1096 REQ_(VIEW_TREE, "Show tree view"), \
1097 REQ_(VIEW_BLOB, "Show blob view"), \
1098 REQ_(VIEW_BLAME, "Show blame view"), \
1099 REQ_(VIEW_BRANCH, "Show branch view"), \
1100 REQ_(VIEW_HELP, "Show help page"), \
1101 REQ_(VIEW_PAGER, "Show pager view"), \
1102 REQ_(VIEW_STATUS, "Show status view"), \
1103 REQ_(VIEW_STAGE, "Show stage view"), \
1105 REQ_GROUP("View manipulation") \
1106 REQ_(ENTER, "Enter current line and scroll"), \
1107 REQ_(NEXT, "Move to next"), \
1108 REQ_(PREVIOUS, "Move to previous"), \
1109 REQ_(PARENT, "Move to parent"), \
1110 REQ_(VIEW_NEXT, "Move focus to next view"), \
1111 REQ_(REFRESH, "Reload and refresh"), \
1112 REQ_(MAXIMIZE, "Maximize the current view"), \
1113 REQ_(VIEW_CLOSE, "Close the current view"), \
1114 REQ_(QUIT, "Close all views and quit"), \
1116 REQ_GROUP("View specific requests") \
1117 REQ_(STATUS_UPDATE, "Update file status"), \
1118 REQ_(STATUS_REVERT, "Revert file changes"), \
1119 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1120 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1122 REQ_GROUP("Cursor navigation") \
1123 REQ_(MOVE_UP, "Move cursor one line up"), \
1124 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1125 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1126 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1127 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1128 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1130 REQ_GROUP("Scrolling") \
1131 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1132 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1133 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1134 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1135 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1136 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1138 REQ_GROUP("Searching") \
1139 REQ_(SEARCH, "Search the view"), \
1140 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1141 REQ_(FIND_NEXT, "Find next search match"), \
1142 REQ_(FIND_PREV, "Find previous search match"), \
1144 REQ_GROUP("Option manipulation") \
1145 REQ_(OPTIONS, "Open option menu"), \
1146 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1147 REQ_(TOGGLE_DATE, "Toggle date display"), \
1148 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1149 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1150 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1151 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1152 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1153 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1156 REQ_(PROMPT, "Bring up the prompt"), \
1157 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1158 REQ_(SHOW_VERSION, "Show version information"), \
1159 REQ_(STOP_LOADING, "Stop all loading views"), \
1160 REQ_(EDIT, "Open in editor"), \
1161 REQ_(NONE, "Do nothing")
1164 /* User action requests. */
1166 #define REQ_GROUP(help)
1167 #define REQ_(req, help) REQ_##req
1169 /* Offset all requests to avoid conflicts with ncurses getch values. */
1170 REQ_OFFSET
= 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
;
1238 #define is_initial_commit() (!get_ref_head())
1239 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1243 * Line-oriented content detection.
1247 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1248 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1249 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1250 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1251 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1252 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1253 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1254 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1261 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1262 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1263 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1264 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1268 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1269 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1270 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1271 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1272 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1273 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1274 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1275 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1276 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1277 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1278 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1279 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1280 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1281 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1282 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1283 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1284 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1285 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1286 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1287 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1288 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1289 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1290 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1291 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1292 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1293 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1294 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1295 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1296 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1297 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1298 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1299 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1300 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1301 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1302 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1303 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1306 #define LINE(type, line, fg, bg, attr) \
1314 const char *name
; /* Option name. */
1315 int namelen
; /* Size of option name. */
1316 const char *line
; /* The start of line to match. */
1317 int linelen
; /* Size of string to match. */
1318 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1321 static struct line_info line_info
[] = {
1322 #define LINE(type, line, fg, bg, attr) \
1323 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1328 static enum line_type
1329 get_line_type(const char *line
)
1331 int linelen
= strlen(line
);
1332 enum line_type type
;
1334 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1335 /* Case insensitive search matches Signed-off-by lines better. */
1336 if (linelen
>= line_info
[type
].linelen
&&
1337 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1340 return LINE_DEFAULT
;
1344 get_line_attr(enum line_type type
)
1346 assert(type
< ARRAY_SIZE(line_info
));
1347 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1350 static struct line_info
*
1351 get_line_info(const char *name
)
1353 size_t namelen
= strlen(name
);
1354 enum line_type type
;
1356 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1357 if (enum_equals(line_info
[type
], name
, namelen
))
1358 return &line_info
[type
];
1366 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1367 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1368 enum line_type type
;
1372 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1373 default_bg
= COLOR_BLACK
;
1374 default_fg
= COLOR_WHITE
;
1377 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1378 struct line_info
*info
= &line_info
[type
];
1379 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1380 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1382 init_pair(type
, fg
, bg
);
1387 enum line_type type
;
1390 unsigned int selected
:1;
1391 unsigned int dirty
:1;
1392 unsigned int cleareol
:1;
1393 unsigned int other
:16;
1395 void *data
; /* User data */
1405 enum request request
;
1408 static const struct keybinding default_keybindings
[] = {
1409 /* View switching */
1410 { 'm', REQ_VIEW_MAIN
},
1411 { 'd', REQ_VIEW_DIFF
},
1412 { 'l', REQ_VIEW_LOG
},
1413 { 't', REQ_VIEW_TREE
},
1414 { 'f', REQ_VIEW_BLOB
},
1415 { 'B', REQ_VIEW_BLAME
},
1416 { 'H', REQ_VIEW_BRANCH
},
1417 { 'p', REQ_VIEW_PAGER
},
1418 { 'h', REQ_VIEW_HELP
},
1419 { 'S', REQ_VIEW_STATUS
},
1420 { 'c', REQ_VIEW_STAGE
},
1422 /* View manipulation */
1423 { 'q', REQ_VIEW_CLOSE
},
1424 { KEY_TAB
, REQ_VIEW_NEXT
},
1425 { KEY_RETURN
, REQ_ENTER
},
1426 { KEY_UP
, REQ_PREVIOUS
},
1427 { KEY_DOWN
, REQ_NEXT
},
1428 { 'R', REQ_REFRESH
},
1429 { KEY_F(5), REQ_REFRESH
},
1430 { 'O', REQ_MAXIMIZE
},
1432 /* Cursor navigation */
1433 { 'k', REQ_MOVE_UP
},
1434 { 'j', REQ_MOVE_DOWN
},
1435 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1436 { KEY_END
, REQ_MOVE_LAST_LINE
},
1437 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1438 { ' ', REQ_MOVE_PAGE_DOWN
},
1439 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1440 { 'b', REQ_MOVE_PAGE_UP
},
1441 { '-', REQ_MOVE_PAGE_UP
},
1444 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1445 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1446 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1447 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1448 { 'w', REQ_SCROLL_PAGE_UP
},
1449 { 's', REQ_SCROLL_PAGE_DOWN
},
1452 { '/', REQ_SEARCH
},
1453 { '?', REQ_SEARCH_BACK
},
1454 { 'n', REQ_FIND_NEXT
},
1455 { 'N', REQ_FIND_PREV
},
1459 { 'z', REQ_STOP_LOADING
},
1460 { 'v', REQ_SHOW_VERSION
},
1461 { 'r', REQ_SCREEN_REDRAW
},
1462 { 'o', REQ_OPTIONS
},
1463 { '.', REQ_TOGGLE_LINENO
},
1464 { 'D', REQ_TOGGLE_DATE
},
1465 { 'A', REQ_TOGGLE_AUTHOR
},
1466 { 'g', REQ_TOGGLE_REV_GRAPH
},
1467 { 'F', REQ_TOGGLE_REFS
},
1468 { 'I', REQ_TOGGLE_SORT_ORDER
},
1469 { 'i', REQ_TOGGLE_SORT_FIELD
},
1470 { ':', REQ_PROMPT
},
1471 { 'u', REQ_STATUS_UPDATE
},
1472 { '!', REQ_STATUS_REVERT
},
1473 { 'M', REQ_STATUS_MERGE
},
1474 { '@', REQ_STAGE_NEXT
},
1475 { ',', REQ_PARENT
},
1479 #define KEYMAP_INFO \
1494 #define KEYMAP_(name) KEYMAP_##name
1499 static const struct enum_map keymap_table
[] = {
1500 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1505 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1507 struct keybinding_table
{
1508 struct keybinding
*data
;
1512 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1515 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1517 struct keybinding_table
*table
= &keybindings
[keymap
];
1519 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1521 die("Failed to allocate keybinding");
1522 table
->data
[table
->size
].alias
= key
;
1523 table
->data
[table
->size
++].request
= request
;
1526 /* Looks for a key binding first in the given map, then in the generic map, and
1527 * lastly in the default keybindings. */
1529 get_keybinding(enum keymap keymap
, int key
)
1533 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1534 if (keybindings
[keymap
].data
[i
].alias
== key
)
1535 return keybindings
[keymap
].data
[i
].request
;
1537 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1538 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1539 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1541 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1542 if (default_keybindings
[i
].alias
== key
)
1543 return default_keybindings
[i
].request
;
1545 return (enum request
) key
;
1554 static const struct key key_table
[] = {
1555 { "Enter", KEY_RETURN
},
1557 { "Backspace", KEY_BACKSPACE
},
1559 { "Escape", KEY_ESC
},
1560 { "Left", KEY_LEFT
},
1561 { "Right", KEY_RIGHT
},
1563 { "Down", KEY_DOWN
},
1564 { "Insert", KEY_IC
},
1565 { "Delete", KEY_DC
},
1567 { "Home", KEY_HOME
},
1569 { "PageUp", KEY_PPAGE
},
1570 { "PageDown", KEY_NPAGE
},
1580 { "F10", KEY_F(10) },
1581 { "F11", KEY_F(11) },
1582 { "F12", KEY_F(12) },
1586 get_key_value(const char *name
)
1590 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1591 if (!strcasecmp(key_table
[i
].name
, name
))
1592 return key_table
[i
].value
;
1594 if (strlen(name
) == 1 && isprint(*name
))
1601 get_key_name(int key_value
)
1603 static char key_char
[] = "'X'";
1604 const char *seq
= NULL
;
1607 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1608 if (key_table
[key
].value
== key_value
)
1609 seq
= key_table
[key
].name
;
1613 isprint(key_value
)) {
1614 key_char
[1] = (char) key_value
;
1618 return seq
? seq
: "(no key)";
1622 append_key(char *buf
, size_t *pos
, const struct keybinding
*keybinding
)
1624 const char *sep
= *pos
> 0 ? ", " : "";
1625 const char *keyname
= get_key_name(keybinding
->alias
);
1627 return string_nformat(buf
, BUFSIZ
, pos
, "%s%s", sep
, keyname
);
1631 append_keymap_request_keys(char *buf
, size_t *pos
, enum request request
,
1632 enum keymap keymap
, bool all
)
1636 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1637 if (keybindings
[keymap
].data
[i
].request
== request
) {
1638 if (!append_key(buf
, pos
, &keybindings
[keymap
].data
[i
]))
1648 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1651 get_keys(enum keymap keymap
, enum request request
, bool all
)
1653 static char buf
[BUFSIZ
];
1659 if (!append_keymap_request_keys(buf
, &pos
, request
, keymap
, all
))
1660 return "Too many keybindings!";
1661 if (pos
> 0 && !all
)
1664 if (keymap
!= KEYMAP_GENERIC
) {
1665 /* Only the generic keymap includes the default keybindings when
1666 * listing all keys. */
1670 if (!append_keymap_request_keys(buf
, &pos
, request
, KEYMAP_GENERIC
, all
))
1671 return "Too many keybindings!";
1676 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1677 if (default_keybindings
[i
].request
== request
) {
1678 if (!append_key(buf
, &pos
, &default_keybindings
[i
]))
1679 return "Too many keybindings!";
1688 struct run_request
{
1691 const char *argv
[SIZEOF_ARG
];
1694 static struct run_request
*run_request
;
1695 static size_t run_requests
;
1697 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1700 add_run_request(enum keymap keymap
, int key
, int argc
, const char **argv
)
1702 struct run_request
*req
;
1704 if (argc
>= ARRAY_SIZE(req
->argv
) - 1)
1707 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1710 req
= &run_request
[run_requests
];
1711 req
->keymap
= keymap
;
1713 req
->argv
[0] = NULL
;
1715 if (!format_argv(req
->argv
, argv
, FORMAT_NONE
))
1718 return REQ_NONE
+ ++run_requests
;
1721 static struct run_request
*
1722 get_run_request(enum request request
)
1724 if (request
<= REQ_NONE
)
1726 return &run_request
[request
- REQ_NONE
- 1];
1730 add_builtin_run_requests(void)
1732 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1733 const char *checkout
[] = { "git", "checkout", "%(branch)", NULL
};
1734 const char *commit
[] = { "git", "commit", NULL
};
1735 const char *gc
[] = { "git", "gc", NULL
};
1742 { KEYMAP_MAIN
, 'C', ARRAY_SIZE(cherry_pick
) - 1, cherry_pick
},
1743 { KEYMAP_STATUS
, 'C', ARRAY_SIZE(commit
) - 1, commit
},
1744 { KEYMAP_BRANCH
, 'C', ARRAY_SIZE(checkout
) - 1, checkout
},
1745 { KEYMAP_GENERIC
, 'G', ARRAY_SIZE(gc
) - 1, gc
},
1749 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1752 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argc
, reqs
[i
].argv
);
1753 if (req
!= REQ_NONE
)
1754 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1759 * User config file handling.
1762 static int config_lineno
;
1763 static bool config_errors
;
1764 static const char *config_msg
;
1766 static const struct enum_map color_map
[] = {
1767 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1779 static const struct enum_map attr_map
[] = {
1780 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1787 ATTR_MAP(UNDERLINE
),
1790 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1792 static int parse_step(double *opt
, const char *arg
)
1795 if (!strchr(arg
, '%'))
1798 /* "Shift down" so 100% and 1 does not conflict. */
1799 *opt
= (*opt
- 1) / 100;
1802 config_msg
= "Step value larger than 100%";
1807 config_msg
= "Invalid step value";
1814 parse_int(int *opt
, const char *arg
, int min
, int max
)
1816 int value
= atoi(arg
);
1818 if (min
<= value
&& value
<= max
) {
1823 config_msg
= "Integer value out of bound";
1828 set_color(int *color
, const char *name
)
1830 if (map_enum(color
, color_map
, name
))
1832 if (!prefixcmp(name
, "color"))
1833 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1837 /* Wants: object fgcolor bgcolor [attribute] */
1839 option_color_command(int argc
, const char *argv
[])
1841 struct line_info
*info
;
1844 config_msg
= "Wrong number of arguments given to color command";
1848 info
= get_line_info(argv
[0]);
1850 static const struct enum_map obsolete
[] = {
1851 ENUM_MAP("main-delim", LINE_DELIMITER
),
1852 ENUM_MAP("main-date", LINE_DATE
),
1853 ENUM_MAP("main-author", LINE_AUTHOR
),
1857 if (!map_enum(&index
, obsolete
, argv
[0])) {
1858 config_msg
= "Unknown color name";
1861 info
= &line_info
[index
];
1864 if (!set_color(&info
->fg
, argv
[1]) ||
1865 !set_color(&info
->bg
, argv
[2])) {
1866 config_msg
= "Unknown color";
1871 while (argc
-- > 3) {
1874 if (!set_attribute(&attr
, argv
[argc
])) {
1875 config_msg
= "Unknown attribute";
1884 static int parse_bool(bool *opt
, const char *arg
)
1886 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1891 static int parse_enum_do(unsigned int *opt
, const char *arg
,
1892 const struct enum_map
*map
, size_t map_size
)
1896 assert(map_size
> 1);
1898 if (map_enum_do(map
, map_size
, (int *) opt
, arg
))
1901 if (parse_bool(&is_true
, arg
) != OK
)
1904 *opt
= is_true
? map
[1].value
: map
[0].value
;
1908 #define parse_enum(opt, arg, map) \
1909 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1912 parse_string(char *opt
, const char *arg
, size_t optsize
)
1914 int arglen
= strlen(arg
);
1919 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1920 config_msg
= "Unmatched quotation";
1923 arg
+= 1; arglen
-= 2;
1925 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1930 /* Wants: name = value */
1932 option_set_command(int argc
, const char *argv
[])
1935 config_msg
= "Wrong number of arguments given to set command";
1939 if (strcmp(argv
[1], "=")) {
1940 config_msg
= "No value assigned";
1944 if (!strcmp(argv
[0], "show-author"))
1945 return parse_enum(&opt_author
, argv
[2], author_map
);
1947 if (!strcmp(argv
[0], "show-date"))
1948 return parse_enum(&opt_date
, argv
[2], date_map
);
1950 if (!strcmp(argv
[0], "show-rev-graph"))
1951 return parse_bool(&opt_rev_graph
, argv
[2]);
1953 if (!strcmp(argv
[0], "show-refs"))
1954 return parse_bool(&opt_show_refs
, argv
[2]);
1956 if (!strcmp(argv
[0], "show-line-numbers"))
1957 return parse_bool(&opt_line_number
, argv
[2]);
1959 if (!strcmp(argv
[0], "line-graphics"))
1960 return parse_bool(&opt_line_graphics
, argv
[2]);
1962 if (!strcmp(argv
[0], "line-number-interval"))
1963 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
1965 if (!strcmp(argv
[0], "author-width"))
1966 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
1968 if (!strcmp(argv
[0], "horizontal-scroll"))
1969 return parse_step(&opt_hscroll
, argv
[2]);
1971 if (!strcmp(argv
[0], "split-view-height"))
1972 return parse_step(&opt_scale_split_view
, argv
[2]);
1974 if (!strcmp(argv
[0], "tab-size"))
1975 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
1977 if (!strcmp(argv
[0], "commit-encoding"))
1978 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
1980 config_msg
= "Unknown variable name";
1984 /* Wants: mode request key */
1986 option_bind_command(int argc
, const char *argv
[])
1988 enum request request
;
1993 config_msg
= "Wrong number of arguments given to bind command";
1997 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
1998 config_msg
= "Unknown key map";
2002 key
= get_key_value(argv
[1]);
2004 config_msg
= "Unknown key";
2008 request
= get_request(argv
[2]);
2009 if (request
== REQ_NONE
) {
2010 static const struct enum_map obsolete
[] = {
2011 ENUM_MAP("cherry-pick", REQ_NONE
),
2012 ENUM_MAP("screen-resize", REQ_NONE
),
2013 ENUM_MAP("tree-parent", REQ_PARENT
),
2017 if (map_enum(&alias
, obsolete
, argv
[2])) {
2018 if (alias
!= REQ_NONE
)
2019 add_keybinding(keymap
, alias
, key
);
2020 config_msg
= "Obsolete request name";
2024 if (request
== REQ_NONE
&& *argv
[2]++ == '!')
2025 request
= add_run_request(keymap
, key
, argc
- 2, argv
+ 2);
2026 if (request
== REQ_NONE
) {
2027 config_msg
= "Unknown request name";
2031 add_keybinding(keymap
, request
, key
);
2037 set_option(const char *opt
, char *value
)
2039 const char *argv
[SIZEOF_ARG
];
2042 if (!argv_from_string(argv
, &argc
, value
)) {
2043 config_msg
= "Too many option arguments";
2047 if (!strcmp(opt
, "color"))
2048 return option_color_command(argc
, argv
);
2050 if (!strcmp(opt
, "set"))
2051 return option_set_command(argc
, argv
);
2053 if (!strcmp(opt
, "bind"))
2054 return option_bind_command(argc
, argv
);
2056 config_msg
= "Unknown option command";
2061 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
2066 config_msg
= "Internal error";
2068 /* Check for comment markers, since read_properties() will
2069 * only ensure opt and value are split at first " \t". */
2070 optlen
= strcspn(opt
, "#");
2074 if (opt
[optlen
] != 0) {
2075 config_msg
= "No option value";
2079 /* Look for comment endings in the value. */
2080 size_t len
= strcspn(value
, "#");
2082 if (len
< valuelen
) {
2084 value
[valuelen
] = 0;
2087 status
= set_option(opt
, value
);
2090 if (status
== ERR
) {
2091 warn("Error on line %d, near '%.*s': %s",
2092 config_lineno
, (int) optlen
, opt
, config_msg
);
2093 config_errors
= TRUE
;
2096 /* Always keep going if errors are encountered. */
2101 load_option_file(const char *path
)
2105 /* It's OK that the file doesn't exist. */
2106 if (!io_open(&io
, "%s", path
))
2110 config_errors
= FALSE
;
2112 if (io_load(&io
, " \t", read_option
) == ERR
||
2113 config_errors
== TRUE
)
2114 warn("Errors while loading %s.", path
);
2120 const char *home
= getenv("HOME");
2121 const char *tigrc_user
= getenv("TIGRC_USER");
2122 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
2123 char buf
[SIZEOF_STR
];
2125 add_builtin_run_requests();
2128 tigrc_system
= SYSCONFDIR
"/tigrc";
2129 load_option_file(tigrc_system
);
2132 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
2136 load_option_file(tigrc_user
);
2149 /* The display array of active views and the index of the current view. */
2150 static struct view
*display
[2];
2151 static unsigned int current_view
;
2153 #define foreach_displayed_view(view, i) \
2154 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2156 #define displayed_views() (display[1] != NULL ? 2 : 1)
2158 /* Current head and commit ID */
2159 static char ref_blob
[SIZEOF_REF
] = "";
2160 static char ref_commit
[SIZEOF_REF
] = "HEAD";
2161 static char ref_head
[SIZEOF_REF
] = "HEAD";
2162 static char ref_branch
[SIZEOF_REF
] = "";
2165 const char *name
; /* View name */
2166 const char *cmd_env
; /* Command line set via environment */
2167 const char *id
; /* Points to either of ref_{head,commit,blob} */
2169 struct view_ops
*ops
; /* View operations */
2171 enum keymap keymap
; /* What keymap does this view have */
2172 bool git_dir
; /* Whether the view requires a git directory. */
2174 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
2175 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
2177 int height
, width
; /* The width and height of the main window */
2178 WINDOW
*win
; /* The main window */
2179 WINDOW
*title
; /* The title window living below the main window */
2182 unsigned long offset
; /* Offset of the window top */
2183 unsigned long yoffset
; /* Offset from the window side. */
2184 unsigned long lineno
; /* Current line number */
2185 unsigned long p_offset
; /* Previous offset of the window top */
2186 unsigned long p_yoffset
;/* Previous offset from the window side */
2187 unsigned long p_lineno
; /* Previous current line number */
2188 bool p_restore
; /* Should the previous position be restored. */
2191 char grep
[SIZEOF_STR
]; /* Search string */
2192 regex_t
*regex
; /* Pre-compiled regexp */
2194 /* If non-NULL, points to the view that opened this view. If this view
2195 * is closed tig will switch back to the parent view. */
2196 struct view
*parent
;
2199 size_t lines
; /* Total number of lines */
2200 struct line
*line
; /* Line index */
2201 unsigned int digits
; /* Number of digits in the lines member. */
2204 struct line
*curline
; /* Line currently being drawn. */
2205 enum line_type curtype
; /* Attribute currently used for drawing. */
2206 unsigned long col
; /* Column when drawing. */
2207 bool has_scrolled
; /* View was scrolled. */
2217 /* What type of content being displayed. Used in the title bar. */
2219 /* Default command arguments. */
2221 /* Open and reads in all view content. */
2222 bool (*open
)(struct view
*view
);
2223 /* Read one line; updates view->line. */
2224 bool (*read
)(struct view
*view
, char *data
);
2225 /* Draw one line; @lineno must be < view->height. */
2226 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
2227 /* Depending on view handle a special requests. */
2228 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
2229 /* Search for regexp in a line. */
2230 bool (*grep
)(struct view
*view
, struct line
*line
);
2232 void (*select
)(struct view
*view
, struct line
*line
);
2233 /* Prepare view for loading */
2234 bool (*prepare
)(struct view
*view
);
2237 static struct view_ops blame_ops
;
2238 static struct view_ops blob_ops
;
2239 static struct view_ops diff_ops
;
2240 static struct view_ops help_ops
;
2241 static struct view_ops log_ops
;
2242 static struct view_ops main_ops
;
2243 static struct view_ops pager_ops
;
2244 static struct view_ops stage_ops
;
2245 static struct view_ops status_ops
;
2246 static struct view_ops tree_ops
;
2247 static struct view_ops branch_ops
;
2249 #define VIEW_STR(name, env, ref, ops, map, git) \
2250 { name, #env, ref, ops, map, git }
2252 #define VIEW_(id, name, ops, git, ref) \
2253 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2256 static struct view views
[] = {
2257 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
2258 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
2259 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
2260 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
2261 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
2262 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
2263 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
2264 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
2265 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
2266 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
2267 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
2270 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2271 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2273 #define foreach_view(view, i) \
2274 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2276 #define view_is_displayed(view) \
2277 (view == display[0] || view == display[1])
2281 set_view_attr(struct view
*view
, enum line_type type
)
2283 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
2284 (void) wattrset(view
->win
, get_line_attr(type
));
2285 wchgat(view
->win
, -1, 0, type
, NULL
);
2286 view
->curtype
= type
;
2291 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
2292 int max_len
, bool use_tilde
)
2294 static char out_buffer
[BUFSIZ
* 2];
2297 int trimmed
= FALSE
;
2298 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2303 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
, opt_tab_size
);
2305 set_view_attr(view
, type
);
2307 if (opt_iconv_out
!= ICONV_NONE
) {
2308 ICONV_CONST
char *inbuf
= (ICONV_CONST
char *) string
;
2309 size_t inlen
= len
+ 1;
2311 char *outbuf
= out_buffer
;
2312 size_t outlen
= sizeof(out_buffer
);
2316 ret
= iconv(opt_iconv_out
, &inbuf
, &inlen
, &outbuf
, &outlen
);
2317 if (ret
!= (size_t) -1) {
2318 string
= out_buffer
;
2319 len
= sizeof(out_buffer
) - outlen
;
2323 waddnstr(view
->win
, string
, len
);
2325 if (trimmed
&& use_tilde
) {
2326 set_view_attr(view
, LINE_DELIMITER
);
2327 waddch(view
->win
, '~');
2335 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
2337 static char space
[] = " ";
2340 spaces
= MIN(max
, spaces
);
2342 while (spaces
> 0) {
2343 int len
= MIN(spaces
, sizeof(space
) - 1);
2345 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
2353 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
2355 view
->col
+= draw_chars(view
, type
, string
, view
->width
+ view
->yoffset
- view
->col
, trim
);
2356 return view
->width
+ view
->yoffset
<= view
->col
;
2360 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
2362 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2363 int max
= view
->width
+ view
->yoffset
- view
->col
;
2369 set_view_attr(view
, type
);
2370 /* Using waddch() instead of waddnstr() ensures that
2371 * they'll be rendered correctly for the cursor line. */
2372 for (i
= skip
; i
< size
; i
++)
2373 waddch(view
->win
, graphic
[i
]);
2376 if (size
< max
&& skip
<= size
)
2377 waddch(view
->win
, ' ');
2380 return view
->width
+ view
->yoffset
<= view
->col
;
2384 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2386 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2390 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2392 col
= draw_space(view
, type
, max
- 1, max
- 1);
2395 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2396 return view
->width
+ view
->yoffset
<= view
->col
;
2400 draw_date(struct view
*view
, struct time
*time
)
2402 const char *date
= mkdate(time
, opt_date
);
2403 int cols
= opt_date
== DATE_SHORT
? DATE_SHORT_COLS
: DATE_COLS
;
2405 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2409 draw_author(struct view
*view
, const char *author
)
2411 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5;
2412 bool abbreviate
= opt_author
== AUTHOR_ABBREVIATED
|| !trim
;
2414 if (abbreviate
&& author
)
2415 author
= get_author_initials(author
);
2417 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2421 draw_mode(struct view
*view
, mode_t mode
)
2427 else if (S_ISLNK(mode
))
2429 else if (S_ISGITLINK(mode
))
2431 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2433 else if (S_ISREG(mode
))
2438 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2442 draw_lineno(struct view
*view
, unsigned int lineno
)
2445 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2446 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2448 chtype separator
= opt_line_graphics
? ACS_VLINE
: '|';
2450 lineno
+= view
->offset
+ 1;
2451 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2452 static char fmt
[] = "%1ld";
2454 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2455 if (string_format(number
, fmt
, lineno
))
2459 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2461 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2462 return draw_graphic(view
, LINE_DEFAULT
, &separator
, 1);
2466 draw_view_line(struct view
*view
, unsigned int lineno
)
2469 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2471 assert(view_is_displayed(view
));
2473 if (view
->offset
+ lineno
>= view
->lines
)
2476 line
= &view
->line
[view
->offset
+ lineno
];
2478 wmove(view
->win
, lineno
, 0);
2480 wclrtoeol(view
->win
);
2482 view
->curline
= line
;
2483 view
->curtype
= LINE_NONE
;
2484 line
->selected
= FALSE
;
2485 line
->dirty
= line
->cleareol
= 0;
2488 set_view_attr(view
, LINE_CURSOR
);
2489 line
->selected
= TRUE
;
2490 view
->ops
->select(view
, line
);
2493 return view
->ops
->draw(view
, line
, lineno
);
2497 redraw_view_dirty(struct view
*view
)
2502 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2503 if (view
->offset
+ lineno
>= view
->lines
)
2505 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2508 if (!draw_view_line(view
, lineno
))
2514 wnoutrefresh(view
->win
);
2518 redraw_view_from(struct view
*view
, int lineno
)
2520 assert(0 <= lineno
&& lineno
< view
->height
);
2522 for (; lineno
< view
->height
; lineno
++) {
2523 if (!draw_view_line(view
, lineno
))
2527 wnoutrefresh(view
->win
);
2531 redraw_view(struct view
*view
)
2534 redraw_view_from(view
, 0);
2539 update_view_title(struct view
*view
)
2541 char buf
[SIZEOF_STR
];
2542 char state
[SIZEOF_STR
];
2543 size_t bufpos
= 0, statelen
= 0;
2545 assert(view_is_displayed(view
));
2547 if (view
!= VIEW(REQ_VIEW_STATUS
) && view
->lines
) {
2548 unsigned int view_lines
= view
->offset
+ view
->height
;
2549 unsigned int lines
= view
->lines
2550 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2553 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2562 time_t secs
= time(NULL
) - view
->start_time
;
2564 /* Three git seconds are a long time ... */
2566 string_format_from(state
, &statelen
, " loading %lds", secs
);
2569 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2570 if (*view
->ref
&& bufpos
< view
->width
) {
2571 size_t refsize
= strlen(view
->ref
);
2572 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2574 if (minsize
< view
->width
)
2575 refsize
= view
->width
- minsize
+ 7;
2576 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2579 if (statelen
&& bufpos
< view
->width
) {
2580 string_format_from(buf
, &bufpos
, "%s", state
);
2583 if (view
== display
[current_view
])
2584 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2586 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2588 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2589 wclrtoeol(view
->title
);
2590 wnoutrefresh(view
->title
);
2594 apply_step(double step
, int value
)
2598 value
*= step
+ 0.01;
2599 return value
? value
: 1;
2603 resize_display(void)
2606 struct view
*base
= display
[0];
2607 struct view
*view
= display
[1] ? display
[1] : display
[0];
2609 /* Setup window dimensions */
2611 getmaxyx(stdscr
, base
->height
, base
->width
);
2613 /* Make room for the status window. */
2617 /* Horizontal split. */
2618 view
->width
= base
->width
;
2619 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2620 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2621 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2622 base
->height
-= view
->height
;
2624 /* Make room for the title bar. */
2628 /* Make room for the title bar. */
2633 foreach_displayed_view (view
, i
) {
2635 view
->win
= newwin(view
->height
, 0, offset
, 0);
2637 die("Failed to create %s view", view
->name
);
2639 scrollok(view
->win
, FALSE
);
2641 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2643 die("Failed to create title window");
2646 wresize(view
->win
, view
->height
, view
->width
);
2647 mvwin(view
->win
, offset
, 0);
2648 mvwin(view
->title
, offset
+ view
->height
, 0);
2651 offset
+= view
->height
+ 1;
2656 redraw_display(bool clear
)
2661 foreach_displayed_view (view
, i
) {
2665 update_view_title(view
);
2670 toggle_enum_option_do(unsigned int *opt
, const char *help
,
2671 const struct enum_map
*map
, size_t size
)
2673 *opt
= (*opt
+ 1) % size
;
2674 redraw_display(FALSE
);
2675 report("Displaying %s %s", enum_name(map
[*opt
]), help
);
2678 #define toggle_enum_option(opt, help, map) \
2679 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2681 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2682 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2685 toggle_view_option(bool *option
, const char *help
)
2688 redraw_display(FALSE
);
2689 report("%sabling %s", *option
? "En" : "Dis", help
);
2693 open_option_menu(void)
2695 const struct menu_item menu
[] = {
2696 { '.', "line numbers", &opt_line_number
},
2697 { 'D', "date display", &opt_date
},
2698 { 'A', "author display", &opt_author
},
2699 { 'g', "revision graph display", &opt_rev_graph
},
2700 { 'F', "reference display", &opt_show_refs
},
2705 if (prompt_menu("Toggle option", menu
, &selected
)) {
2706 if (menu
[selected
].data
== &opt_date
)
2708 else if (menu
[selected
].data
== &opt_author
)
2711 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2716 maximize_view(struct view
*view
)
2718 memset(display
, 0, sizeof(display
));
2720 display
[current_view
] = view
;
2722 redraw_display(FALSE
);
2732 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2734 if (lineno
>= view
->lines
)
2735 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2737 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2738 unsigned long half
= view
->height
/ 2;
2741 offset
= lineno
- half
;
2746 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2747 view
->offset
= offset
;
2748 view
->lineno
= lineno
;
2755 /* Scrolling backend */
2757 do_scroll_view(struct view
*view
, int lines
)
2759 bool redraw_current_line
= FALSE
;
2761 /* The rendering expects the new offset. */
2762 view
->offset
+= lines
;
2764 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2767 /* Move current line into the view. */
2768 if (view
->lineno
< view
->offset
) {
2769 view
->lineno
= view
->offset
;
2770 redraw_current_line
= TRUE
;
2771 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2772 view
->lineno
= view
->offset
+ view
->height
- 1;
2773 redraw_current_line
= TRUE
;
2776 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2778 /* Redraw the whole screen if scrolling is pointless. */
2779 if (view
->height
< ABS(lines
)) {
2783 int line
= lines
> 0 ? view
->height
- lines
: 0;
2784 int end
= line
+ ABS(lines
);
2786 scrollok(view
->win
, TRUE
);
2787 wscrl(view
->win
, lines
);
2788 scrollok(view
->win
, FALSE
);
2790 while (line
< end
&& draw_view_line(view
, line
))
2793 if (redraw_current_line
)
2794 draw_view_line(view
, view
->lineno
- view
->offset
);
2795 wnoutrefresh(view
->win
);
2798 view
->has_scrolled
= TRUE
;
2802 /* Scroll frontend */
2804 scroll_view(struct view
*view
, enum request request
)
2808 assert(view_is_displayed(view
));
2811 case REQ_SCROLL_LEFT
:
2812 if (view
->yoffset
== 0) {
2813 report("Cannot scroll beyond the first column");
2816 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2819 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2820 redraw_view_from(view
, 0);
2823 case REQ_SCROLL_RIGHT
:
2824 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2828 case REQ_SCROLL_PAGE_DOWN
:
2829 lines
= view
->height
;
2830 case REQ_SCROLL_LINE_DOWN
:
2831 if (view
->offset
+ lines
> view
->lines
)
2832 lines
= view
->lines
- view
->offset
;
2834 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2835 report("Cannot scroll beyond the last line");
2840 case REQ_SCROLL_PAGE_UP
:
2841 lines
= view
->height
;
2842 case REQ_SCROLL_LINE_UP
:
2843 if (lines
> view
->offset
)
2844 lines
= view
->offset
;
2847 report("Cannot scroll beyond the first line");
2855 die("request %d not handled in switch", request
);
2858 do_scroll_view(view
, lines
);
2863 move_view(struct view
*view
, enum request request
)
2865 int scroll_steps
= 0;
2869 case REQ_MOVE_FIRST_LINE
:
2870 steps
= -view
->lineno
;
2873 case REQ_MOVE_LAST_LINE
:
2874 steps
= view
->lines
- view
->lineno
- 1;
2877 case REQ_MOVE_PAGE_UP
:
2878 steps
= view
->height
> view
->lineno
2879 ? -view
->lineno
: -view
->height
;
2882 case REQ_MOVE_PAGE_DOWN
:
2883 steps
= view
->lineno
+ view
->height
>= view
->lines
2884 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2896 die("request %d not handled in switch", request
);
2899 if (steps
<= 0 && view
->lineno
== 0) {
2900 report("Cannot move beyond the first line");
2903 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2904 report("Cannot move beyond the last line");
2908 /* Move the current line */
2909 view
->lineno
+= steps
;
2910 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2912 /* Check whether the view needs to be scrolled */
2913 if (view
->lineno
< view
->offset
||
2914 view
->lineno
>= view
->offset
+ view
->height
) {
2915 scroll_steps
= steps
;
2916 if (steps
< 0 && -steps
> view
->offset
) {
2917 scroll_steps
= -view
->offset
;
2919 } else if (steps
> 0) {
2920 if (view
->lineno
== view
->lines
- 1 &&
2921 view
->lines
> view
->height
) {
2922 scroll_steps
= view
->lines
- view
->offset
- 1;
2923 if (scroll_steps
>= view
->height
)
2924 scroll_steps
-= view
->height
- 1;
2929 if (!view_is_displayed(view
)) {
2930 view
->offset
+= scroll_steps
;
2931 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2932 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2936 /* Repaint the old "current" line if we be scrolling */
2937 if (ABS(steps
) < view
->height
)
2938 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
2941 do_scroll_view(view
, scroll_steps
);
2945 /* Draw the current line */
2946 draw_view_line(view
, view
->lineno
- view
->offset
);
2948 wnoutrefresh(view
->win
);
2957 static void search_view(struct view
*view
, enum request request
);
2960 grep_text(struct view
*view
, const char *text
[])
2965 for (i
= 0; text
[i
]; i
++)
2967 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
2973 select_view_line(struct view
*view
, unsigned long lineno
)
2975 unsigned long old_lineno
= view
->lineno
;
2976 unsigned long old_offset
= view
->offset
;
2978 if (goto_view_line(view
, view
->offset
, lineno
)) {
2979 if (view_is_displayed(view
)) {
2980 if (old_offset
!= view
->offset
) {
2983 draw_view_line(view
, old_lineno
- view
->offset
);
2984 draw_view_line(view
, view
->lineno
- view
->offset
);
2985 wnoutrefresh(view
->win
);
2988 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2994 find_next(struct view
*view
, enum request request
)
2996 unsigned long lineno
= view
->lineno
;
3001 report("No previous search");
3003 search_view(view
, request
);
3013 case REQ_SEARCH_BACK
:
3022 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
3023 lineno
+= direction
;
3025 /* Note, lineno is unsigned long so will wrap around in which case it
3026 * will become bigger than view->lines. */
3027 for (; lineno
< view
->lines
; lineno
+= direction
) {
3028 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
3029 select_view_line(view
, lineno
);
3030 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
3035 report("No match found for '%s'", view
->grep
);
3039 search_view(struct view
*view
, enum request request
)
3044 regfree(view
->regex
);
3047 view
->regex
= calloc(1, sizeof(*view
->regex
));
3052 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
3053 if (regex_err
!= 0) {
3054 char buf
[SIZEOF_STR
] = "unknown error";
3056 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
3057 report("Search failed: %s", buf
);
3061 string_copy(view
->grep
, opt_search
);
3063 find_next(view
, request
);
3067 * Incremental updating
3071 reset_view(struct view
*view
)
3075 for (i
= 0; i
< view
->lines
; i
++)
3076 free(view
->line
[i
].data
);
3079 view
->p_offset
= view
->offset
;
3080 view
->p_yoffset
= view
->yoffset
;
3081 view
->p_lineno
= view
->lineno
;
3089 view
->update_secs
= 0;
3093 free_argv(const char *argv
[])
3097 for (argc
= 0; argv
[argc
]; argc
++)
3098 free((void *) argv
[argc
]);
3102 format_arg(const char *name
)
3108 const char *value_if_empty
;
3110 #define FORMAT_VAR(name, value, value_if_empty) \
3111 { name, STRING_SIZE(name), value, value_if_empty }
3112 FORMAT_VAR("%(directory)", opt_path
, ""),
3113 FORMAT_VAR("%(file)", opt_file
, ""),
3114 FORMAT_VAR("%(ref)", opt_ref
, "HEAD"),
3115 FORMAT_VAR("%(head)", ref_head
, ""),
3116 FORMAT_VAR("%(commit)", ref_commit
, ""),
3117 FORMAT_VAR("%(blob)", ref_blob
, ""),
3118 FORMAT_VAR("%(branch)", ref_branch
, ""),
3122 for (i
= 0; i
< ARRAY_SIZE(vars
); i
++)
3123 if (!strncmp(name
, vars
[i
].name
, vars
[i
].namelen
))
3124 return *vars
[i
].value
? vars
[i
].value
: vars
[i
].value_if_empty
;
3126 report("Unknown replacement: `%s`", name
);
3131 format_argv(const char *dst_argv
[], const char *src_argv
[], enum format_flags flags
)
3133 char buf
[SIZEOF_STR
];
3135 bool noreplace
= flags
== FORMAT_NONE
;
3137 free_argv(dst_argv
);
3139 for (argc
= 0; src_argv
[argc
]; argc
++) {
3140 const char *arg
= src_argv
[argc
];
3144 char *next
= strstr(arg
, "%(");
3145 int len
= next
- arg
;
3148 if (!next
|| noreplace
) {
3149 if (flags
== FORMAT_DASH
&& !strcmp(arg
, "--"))
3155 value
= format_arg(next
);
3162 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
3165 arg
= next
&& !noreplace
? strchr(next
, ')') + 1 : NULL
;
3168 dst_argv
[argc
] = strdup(buf
);
3169 if (!dst_argv
[argc
])
3173 dst_argv
[argc
] = NULL
;
3175 return src_argv
[argc
] == NULL
;
3179 restore_view_position(struct view
*view
)
3181 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
3184 /* Changing the view position cancels the restoring. */
3185 /* FIXME: Changing back to the first line is not detected. */
3186 if (view
->offset
!= 0 || view
->lineno
!= 0) {
3187 view
->p_restore
= FALSE
;
3191 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
3192 view_is_displayed(view
))
3195 view
->yoffset
= view
->p_yoffset
;
3196 view
->p_restore
= FALSE
;
3202 end_update(struct view
*view
, bool force
)
3206 while (!view
->ops
->read(view
, NULL
))
3210 io_kill(view
->pipe
);
3211 io_done(view
->pipe
);
3216 setup_update(struct view
*view
, const char *vid
)
3219 string_copy_rev(view
->vid
, vid
);
3220 view
->pipe
= &view
->io
;
3221 view
->start_time
= time(NULL
);
3225 prepare_update(struct view
*view
, const char *argv
[], const char *dir
)
3228 end_update(view
, TRUE
);
3229 return io_format(&view
->io
, dir
, IO_RD
, argv
, FORMAT_NONE
);
3233 prepare_update_file(struct view
*view
, const char *name
)
3236 end_update(view
, TRUE
);
3237 return io_open(&view
->io
, "%s/%s", opt_cdup
[0] ? opt_cdup
: ".", name
);
3241 begin_update(struct view
*view
, bool refresh
)
3244 end_update(view
, TRUE
);
3247 if (view
->ops
->prepare
) {
3248 if (!view
->ops
->prepare(view
))
3250 } else if (!io_format(&view
->io
, NULL
, IO_RD
, view
->ops
->argv
, FORMAT_ALL
)) {
3254 /* Put the current ref_* value to the view title ref
3255 * member. This is needed by the blob view. Most other
3256 * views sets it automatically after loading because the
3257 * first line is a commit line. */
3258 string_copy_rev(view
->ref
, view
->id
);
3261 if (!io_start(&view
->io
))
3264 setup_update(view
, view
->id
);
3270 update_view(struct view
*view
)
3272 char out_buffer
[BUFSIZ
* 2];
3274 /* Clear the view and redraw everything since the tree sorting
3275 * might have rearranged things. */
3276 bool redraw
= view
->lines
== 0;
3277 bool can_read
= TRUE
;
3282 if (!io_can_read(view
->pipe
)) {
3283 if (view
->lines
== 0 && view_is_displayed(view
)) {
3284 time_t secs
= time(NULL
) - view
->start_time
;
3286 if (secs
> 1 && secs
> view
->update_secs
) {
3287 if (view
->update_secs
== 0)
3289 update_view_title(view
);
3290 view
->update_secs
= secs
;
3296 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
3297 if (opt_iconv_in
!= ICONV_NONE
) {
3298 ICONV_CONST
char *inbuf
= line
;
3299 size_t inlen
= strlen(line
) + 1;
3301 char *outbuf
= out_buffer
;
3302 size_t outlen
= sizeof(out_buffer
);
3306 ret
= iconv(opt_iconv_in
, &inbuf
, &inlen
, &outbuf
, &outlen
);
3307 if (ret
!= (size_t) -1)
3311 if (!view
->ops
->read(view
, line
)) {
3312 report("Allocation failure");
3313 end_update(view
, TRUE
);
3319 unsigned long lines
= view
->lines
;
3322 for (digits
= 0; lines
; digits
++)
3325 /* Keep the displayed view in sync with line number scaling. */
3326 if (digits
!= view
->digits
) {
3327 view
->digits
= digits
;
3328 if (opt_line_number
|| view
== VIEW(REQ_VIEW_BLAME
))
3333 if (io_error(view
->pipe
)) {
3334 report("Failed to read: %s", io_strerror(view
->pipe
));
3335 end_update(view
, TRUE
);
3337 } else if (io_eof(view
->pipe
)) {
3339 end_update(view
, FALSE
);
3342 if (restore_view_position(view
))
3345 if (!view_is_displayed(view
))
3349 redraw_view_from(view
, 0);
3351 redraw_view_dirty(view
);
3353 /* Update the title _after_ the redraw so that if the redraw picks up a
3354 * commit reference in view->ref it'll be available here. */
3355 update_view_title(view
);
3359 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
3361 static struct line
*
3362 add_line_data(struct view
*view
, void *data
, enum line_type type
)
3366 if (!realloc_lines(&view
->line
, view
->lines
, 1))
3369 line
= &view
->line
[view
->lines
++];
3370 memset(line
, 0, sizeof(*line
));
3378 static struct line
*
3379 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
3381 char *data
= text
? strdup(text
) : NULL
;
3383 return data
? add_line_data(view
, data
, type
) : NULL
;
3386 static struct line
*
3387 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3389 char buf
[SIZEOF_STR
];
3392 va_start(args
, fmt
);
3393 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3397 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3405 OPEN_DEFAULT
= 0, /* Use default view switching. */
3406 OPEN_SPLIT
= 1, /* Split current view. */
3407 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3408 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3409 OPEN_PREPARED
= 32, /* Open already prepared command. */
3413 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3415 bool split
= !!(flags
& OPEN_SPLIT
);
3416 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3417 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3418 struct view
*view
= VIEW(request
);
3419 int nviews
= displayed_views();
3420 struct view
*base_view
= display
[0];
3422 if (view
== prev
&& nviews
== 1 && !reload
) {
3423 report("Already in %s view", view
->name
);
3427 if (view
->git_dir
&& !opt_git_dir
[0]) {
3428 report("The %s view is disabled in pager view", view
->name
);
3435 } else if (!nomaximize
) {
3436 /* Maximize the current view. */
3437 memset(display
, 0, sizeof(display
));
3439 display
[current_view
] = view
;
3442 /* No parent signals that this is the first loaded view. */
3443 if (prev
&& view
!= prev
) {
3444 view
->parent
= prev
;
3447 /* Resize the view when switching between split- and full-screen,
3448 * or when switching between two different full-screen views. */
3449 if (nviews
!= displayed_views() ||
3450 (nviews
== 1 && base_view
!= display
[0]))
3453 if (view
->ops
->open
) {
3455 end_update(view
, TRUE
);
3456 if (!view
->ops
->open(view
)) {
3457 report("Failed to load %s view", view
->name
);
3460 restore_view_position(view
);
3462 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3463 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3464 report("Failed to load %s view", view
->name
);
3468 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3469 /* Take the title line into account. */
3470 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3472 /* Scroll the view that was split if the current line is
3473 * outside the new limited view. */
3474 do_scroll_view(prev
, lines
);
3477 if (prev
&& view
!= prev
&& split
&& view_is_displayed(prev
)) {
3478 /* "Blur" the previous view. */
3479 update_view_title(prev
);
3482 if (view
->pipe
&& view
->lines
== 0) {
3483 /* Clear the old view and let the incremental updating refill
3486 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3488 } else if (view_is_displayed(view
)) {
3495 open_external_viewer(const char *argv
[], const char *dir
)
3497 def_prog_mode(); /* save current tty modes */
3498 endwin(); /* restore original tty modes */
3499 io_run_fg(argv
, dir
);
3500 fprintf(stderr
, "Press Enter to continue");
3503 redraw_display(TRUE
);
3507 open_mergetool(const char *file
)
3509 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3511 open_external_viewer(mergetool_argv
, opt_cdup
);
3515 open_editor(const char *file
)
3517 const char *editor_argv
[] = { "vi", file
, NULL
};
3520 editor
= getenv("GIT_EDITOR");
3521 if (!editor
&& *opt_editor
)
3522 editor
= opt_editor
;
3524 editor
= getenv("VISUAL");
3526 editor
= getenv("EDITOR");
3530 editor_argv
[0] = editor
;
3531 open_external_viewer(editor_argv
, opt_cdup
);
3535 open_run_request(enum request request
)
3537 struct run_request
*req
= get_run_request(request
);
3538 const char *argv
[ARRAY_SIZE(req
->argv
)] = { NULL
};
3541 report("Unknown run request");
3545 if (format_argv(argv
, req
->argv
, FORMAT_ALL
))
3546 open_external_viewer(argv
, NULL
);
3551 * User request switch noodle
3555 view_driver(struct view
*view
, enum request request
)
3559 if (request
== REQ_NONE
)
3562 if (request
> REQ_NONE
) {
3563 open_run_request(request
);
3564 /* FIXME: When all views can refresh always do this. */
3565 if (view
== VIEW(REQ_VIEW_STATUS
) ||
3566 view
== VIEW(REQ_VIEW_MAIN
) ||
3567 view
== VIEW(REQ_VIEW_LOG
) ||
3568 view
== VIEW(REQ_VIEW_BRANCH
) ||
3569 view
== VIEW(REQ_VIEW_STAGE
))
3570 request
= REQ_REFRESH
;
3575 if (view
&& view
->lines
) {
3576 request
= view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
3577 if (request
== REQ_NONE
)
3584 case REQ_MOVE_PAGE_UP
:
3585 case REQ_MOVE_PAGE_DOWN
:
3586 case REQ_MOVE_FIRST_LINE
:
3587 case REQ_MOVE_LAST_LINE
:
3588 move_view(view
, request
);
3591 case REQ_SCROLL_LEFT
:
3592 case REQ_SCROLL_RIGHT
:
3593 case REQ_SCROLL_LINE_DOWN
:
3594 case REQ_SCROLL_LINE_UP
:
3595 case REQ_SCROLL_PAGE_DOWN
:
3596 case REQ_SCROLL_PAGE_UP
:
3597 scroll_view(view
, request
);
3600 case REQ_VIEW_BLAME
:
3602 report("No file chosen, press %s to open tree view",
3603 get_key(view
->keymap
, REQ_VIEW_TREE
));
3606 open_view(view
, request
, OPEN_DEFAULT
);
3611 report("No file chosen, press %s to open tree view",
3612 get_key(view
->keymap
, REQ_VIEW_TREE
));
3615 open_view(view
, request
, OPEN_DEFAULT
);
3618 case REQ_VIEW_PAGER
:
3619 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3620 report("No pager content, press %s to run command from prompt",
3621 get_key(view
->keymap
, REQ_PROMPT
));
3624 open_view(view
, request
, OPEN_DEFAULT
);
3627 case REQ_VIEW_STAGE
:
3628 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3629 report("No stage content, press %s to open the status view and choose file",
3630 get_key(view
->keymap
, REQ_VIEW_STATUS
));
3633 open_view(view
, request
, OPEN_DEFAULT
);
3636 case REQ_VIEW_STATUS
:
3637 if (opt_is_inside_work_tree
== FALSE
) {
3638 report("The status view requires a working tree");
3641 open_view(view
, request
, OPEN_DEFAULT
);
3649 case REQ_VIEW_BRANCH
:
3650 open_view(view
, request
, OPEN_DEFAULT
);
3655 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3657 if ((view
== VIEW(REQ_VIEW_DIFF
) &&
3658 view
->parent
== VIEW(REQ_VIEW_MAIN
)) ||
3659 (view
== VIEW(REQ_VIEW_DIFF
) &&
3660 view
->parent
== VIEW(REQ_VIEW_BLAME
)) ||
3661 (view
== VIEW(REQ_VIEW_STAGE
) &&
3662 view
->parent
== VIEW(REQ_VIEW_STATUS
)) ||
3663 (view
== VIEW(REQ_VIEW_BLOB
) &&
3664 view
->parent
== VIEW(REQ_VIEW_TREE
)) ||
3665 (view
== VIEW(REQ_VIEW_MAIN
) &&
3666 view
->parent
== VIEW(REQ_VIEW_BRANCH
))) {
3669 view
= view
->parent
;
3670 line
= view
->lineno
;
3671 move_view(view
, request
);
3672 if (view_is_displayed(view
))
3673 update_view_title(view
);
3674 if (line
!= view
->lineno
)
3675 view
->ops
->request(view
, REQ_ENTER
,
3676 &view
->line
[view
->lineno
]);
3679 move_view(view
, request
);
3685 int nviews
= displayed_views();
3686 int next_view
= (current_view
+ 1) % nviews
;
3688 if (next_view
== current_view
) {
3689 report("Only one view is displayed");
3693 current_view
= next_view
;
3694 /* Blur out the title of the previous view. */
3695 update_view_title(view
);
3700 report("Refreshing is not yet supported for the %s view", view
->name
);
3704 if (displayed_views() == 2)
3705 maximize_view(view
);
3712 case REQ_TOGGLE_LINENO
:
3713 toggle_view_option(&opt_line_number
, "line numbers");
3716 case REQ_TOGGLE_DATE
:
3720 case REQ_TOGGLE_AUTHOR
:
3724 case REQ_TOGGLE_REV_GRAPH
:
3725 toggle_view_option(&opt_rev_graph
, "revision graph display");
3728 case REQ_TOGGLE_REFS
:
3729 toggle_view_option(&opt_show_refs
, "reference display");
3732 case REQ_TOGGLE_SORT_FIELD
:
3733 case REQ_TOGGLE_SORT_ORDER
:
3734 report("Sorting is not yet supported for the %s view", view
->name
);
3738 case REQ_SEARCH_BACK
:
3739 search_view(view
, request
);
3744 find_next(view
, request
);
3747 case REQ_STOP_LOADING
:
3748 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
3751 report("Stopped loading the %s view", view
->name
),
3752 end_update(view
, TRUE
);
3756 case REQ_SHOW_VERSION
:
3757 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3760 case REQ_SCREEN_REDRAW
:
3761 redraw_display(TRUE
);
3765 report("Nothing to edit");
3769 report("Nothing to enter");
3772 case REQ_VIEW_CLOSE
:
3773 /* XXX: Mark closed views by letting view->parent point to the
3774 * view itself. Parents to closed view should never be
3777 view
->parent
->parent
!= view
->parent
) {
3778 maximize_view(view
->parent
);
3779 view
->parent
= view
;
3787 report("Unknown key, press %s for help",
3788 get_key(view
->keymap
, REQ_VIEW_HELP
));
3797 * View backend utilities
3807 const enum sort_field
*fields
;
3808 size_t size
, current
;
3812 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3813 #define get_sort_field(state) ((state).fields[(state).current])
3814 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3817 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3818 int (*compare
)(const void *, const void *))
3821 case REQ_TOGGLE_SORT_FIELD
:
3822 state
->current
= (state
->current
+ 1) % state
->size
;
3825 case REQ_TOGGLE_SORT_ORDER
:
3826 state
->reverse
= !state
->reverse
;
3829 die("Not a sort request");
3832 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3836 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3838 /* Small author cache to reduce memory consumption. It uses binary
3839 * search to lookup or find place to position new entries. No entries
3840 * are ever freed. */
3842 get_author(const char *name
)
3844 static const char **authors
;
3845 static size_t authors_size
;
3846 int from
= 0, to
= authors_size
- 1;
3848 while (from
<= to
) {
3849 size_t pos
= (to
+ from
) / 2;
3850 int cmp
= strcmp(name
, authors
[pos
]);
3853 return authors
[pos
];
3861 if (!realloc_authors(&authors
, authors_size
, 1))
3863 name
= strdup(name
);
3867 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3868 authors
[from
] = name
;
3875 parse_timesec(struct time
*time
, const char *sec
)
3877 time
->sec
= (time_t) atol(sec
);
3881 parse_timezone(struct time
*time
, const char *zone
)
3885 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3886 tz
+= ('0' - zone
[2]) * 60 * 60;
3887 tz
+= ('0' - zone
[3]) * 60;
3888 tz
+= ('0' - zone
[4]);
3897 /* Parse author lines where the name may be empty:
3898 * author <email@address.tld> 1138474660 +0100
3901 parse_author_line(char *ident
, const char **author
, struct time
*time
)
3903 char *nameend
= strchr(ident
, '<');
3904 char *emailend
= strchr(ident
, '>');
3906 if (nameend
&& emailend
)
3907 *nameend
= *emailend
= 0;
3908 ident
= chomp_string(ident
);
3911 ident
= chomp_string(nameend
+ 1);
3916 *author
= get_author(ident
);
3918 /* Parse epoch and timezone */
3919 if (emailend
&& emailend
[1] == ' ') {
3920 char *secs
= emailend
+ 2;
3921 char *zone
= strchr(secs
, ' ');
3923 parse_timesec(time
, secs
);
3925 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
3926 parse_timezone(time
, zone
+ 1);
3931 open_commit_parent_menu(char buf
[SIZEOF_STR
], int *parents
)
3933 char rev
[SIZEOF_REV
];
3934 const char *revlist_argv
[] = {
3935 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev
, NULL
3937 struct menu_item
*items
;
3938 char text
[SIZEOF_STR
];
3942 items
= calloc(*parents
+ 1, sizeof(*items
));
3946 for (i
= 0; i
< *parents
; i
++) {
3947 string_copy_rev(rev
, &buf
[SIZEOF_REV
* i
]);
3948 if (!io_run_buf(revlist_argv
, text
, sizeof(text
)) ||
3949 !(items
[i
].text
= strdup(text
))) {
3957 ok
= prompt_menu("Select parent", items
, parents
);
3959 for (i
= 0; items
[i
].text
; i
++)
3960 free((char *) items
[i
].text
);
3966 select_commit_parent(const char *id
, char rev
[SIZEOF_REV
], const char *path
)
3968 char buf
[SIZEOF_STR
* 4];
3969 const char *revlist_argv
[] = {
3970 "git", "log", "--no-color", "-1",
3971 "--pretty=format:%P", id
, "--", path
, NULL
3975 if (!io_run_buf(revlist_argv
, buf
, sizeof(buf
)) ||
3976 (parents
= strlen(buf
) / 40) < 0) {
3977 report("Failed to get parent information");
3980 } else if (parents
== 0) {
3982 report("Path '%s' does not exist in the parent", path
);
3984 report("The selected commit has no parents");
3988 if (parents
> 1 && !open_commit_parent_menu(buf
, &parents
))
3991 string_copy_rev(rev
, &buf
[41 * parents
]);
4000 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4002 char text
[SIZEOF_STR
];
4004 if (opt_line_number
&& draw_lineno(view
, lineno
))
4007 string_expand(text
, sizeof(text
), line
->data
, opt_tab_size
);
4008 draw_text(view
, line
->type
, text
, TRUE
);
4013 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
4015 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
4016 char ref
[SIZEOF_STR
];
4018 if (!io_run_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
4021 /* This is the only fatal call, since it can "corrupt" the buffer. */
4022 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
4029 add_pager_refs(struct view
*view
, struct line
*line
)
4031 char buf
[SIZEOF_STR
];
4032 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
4033 struct ref_list
*list
;
4034 size_t bufpos
= 0, i
;
4035 const char *sep
= "Refs: ";
4036 bool is_tag
= FALSE
;
4038 assert(line
->type
== LINE_COMMIT
);
4040 list
= get_ref_list(commit_id
);
4042 if (view
== VIEW(REQ_VIEW_DIFF
))
4043 goto try_add_describe_ref
;
4047 for (i
= 0; i
< list
->size
; i
++) {
4048 struct ref
*ref
= list
->refs
[i
];
4049 const char *fmt
= ref
->tag
? "%s[%s]" :
4050 ref
->remote
? "%s<%s>" : "%s%s";
4052 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
4059 if (!is_tag
&& view
== VIEW(REQ_VIEW_DIFF
)) {
4060 try_add_describe_ref
:
4061 /* Add <tag>-g<commit_id> "fake" reference. */
4062 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
4069 add_line_text(view
, buf
, LINE_PP_REFS
);
4073 pager_read(struct view
*view
, char *data
)
4080 line
= add_line_text(view
, data
, get_line_type(data
));
4084 if (line
->type
== LINE_COMMIT
&&
4085 (view
== VIEW(REQ_VIEW_DIFF
) ||
4086 view
== VIEW(REQ_VIEW_LOG
)))
4087 add_pager_refs(view
, line
);
4093 pager_request(struct view
*view
, enum request request
, struct line
*line
)
4097 if (request
!= REQ_ENTER
)
4100 if (line
->type
== LINE_COMMIT
&&
4101 (view
== VIEW(REQ_VIEW_LOG
) ||
4102 view
== VIEW(REQ_VIEW_PAGER
))) {
4103 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
4107 /* Always scroll the view even if it was split. That way
4108 * you can use Enter to scroll through the log view and
4109 * split open each commit diff. */
4110 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
4112 /* FIXME: A minor workaround. Scrolling the view will call report("")
4113 * but if we are scrolling a non-current view this won't properly
4114 * update the view title. */
4116 update_view_title(view
);
4122 pager_grep(struct view
*view
, struct line
*line
)
4124 const char *text
[] = { line
->data
, NULL
};
4126 return grep_text(view
, text
);
4130 pager_select(struct view
*view
, struct line
*line
)
4132 if (line
->type
== LINE_COMMIT
) {
4133 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
4135 if (view
!= VIEW(REQ_VIEW_PAGER
))
4136 string_copy_rev(view
->ref
, text
);
4137 string_copy_rev(ref_commit
, text
);
4141 static struct view_ops pager_ops
= {
4152 static const char *log_argv
[SIZEOF_ARG
] = {
4153 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4157 log_request(struct view
*view
, enum request request
, struct line
*line
)
4162 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
4165 return pager_request(view
, request
, line
);
4169 static struct view_ops log_ops
= {
4180 static const char *diff_argv
[SIZEOF_ARG
] = {
4181 "git", "show", "--pretty=fuller", "--no-color", "--root",
4182 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4185 static struct view_ops diff_ops
= {
4200 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
4203 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
4207 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
4208 help_keymap_hidden
[keymap
] ? '+' : '-',
4209 enum_name(keymap_table
[keymap
]));
4211 line
->other
= keymap
;
4213 return help_keymap_hidden
[keymap
];
4217 help_open_keymap(struct view
*view
, enum keymap keymap
)
4219 const char *group
= NULL
;
4220 char buf
[SIZEOF_STR
];
4222 bool add_title
= TRUE
;
4225 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
4226 const char *key
= NULL
;
4228 if (req_info
[i
].request
== REQ_NONE
)
4231 if (!req_info
[i
].request
) {
4232 group
= req_info
[i
].help
;
4236 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4240 if (add_title
&& help_open_keymap_title(view
, keymap
))
4245 add_line_text(view
, group
, LINE_HELP_GROUP
);
4249 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4250 enum_name(req_info
[i
]), req_info
[i
].help
);
4253 group
= "External commands:";
4255 for (i
= 0; i
< run_requests
; i
++) {
4256 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4260 if (!req
|| req
->keymap
!= keymap
)
4263 key
= get_key_name(req
->key
);
4265 key
= "(no key defined)";
4267 if (add_title
&& help_open_keymap_title(view
, keymap
))
4270 add_line_text(view
, group
, LINE_HELP_GROUP
);
4274 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4275 if (!string_format_from(buf
, &bufpos
, "%s%s",
4276 argc
? " " : "", req
->argv
[argc
]))
4279 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4284 help_open(struct view
*view
)
4289 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4290 add_line_text(view
, "", LINE_DEFAULT
);
4292 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4293 help_open_keymap(view
, keymap
);
4299 help_request(struct view
*view
, enum request request
, struct line
*line
)
4303 if (line
->type
== LINE_HELP_KEYMAP
) {
4304 help_keymap_hidden
[line
->other
] =
4305 !help_keymap_hidden
[line
->other
];
4306 view
->p_restore
= TRUE
;
4307 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4312 return pager_request(view
, request
, line
);
4316 static struct view_ops help_ops
= {
4332 struct tree_stack_entry
{
4333 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4334 unsigned long lineno
; /* Line number to restore */
4335 char *name
; /* Position of name in opt_path */
4338 /* The top of the path stack. */
4339 static struct tree_stack_entry
*tree_stack
= NULL
;
4340 unsigned long tree_lineno
= 0;
4343 pop_tree_stack_entry(void)
4345 struct tree_stack_entry
*entry
= tree_stack
;
4347 tree_lineno
= entry
->lineno
;
4349 tree_stack
= entry
->prev
;
4354 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4356 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4357 size_t pathlen
= strlen(opt_path
);
4362 entry
->prev
= tree_stack
;
4363 entry
->name
= opt_path
+ pathlen
;
4366 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4367 pop_tree_stack_entry();
4371 /* Move the current line to the first tree entry. */
4373 entry
->lineno
= lineno
;
4376 /* Parse output from git-ls-tree(1):
4378 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4381 #define SIZEOF_TREE_ATTR \
4382 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4384 #define SIZEOF_TREE_MODE \
4385 STRING_SIZE("100644 ")
4387 #define TREE_ID_OFFSET \
4388 STRING_SIZE("100644 blob ")
4391 char id
[SIZEOF_REV
];
4393 struct time time
; /* Date from the author ident. */
4394 const char *author
; /* Author of the commit. */
4399 tree_path(const struct line
*line
)
4401 return ((struct tree_entry
*) line
->data
)->name
;
4405 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4407 if (line1
->type
!= line2
->type
)
4408 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4409 return strcmp(tree_path(line1
), tree_path(line2
));
4412 static const enum sort_field tree_sort_fields
[] = {
4413 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4415 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4418 tree_compare(const void *l1
, const void *l2
)
4420 const struct line
*line1
= (const struct line
*) l1
;
4421 const struct line
*line2
= (const struct line
*) l2
;
4422 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4423 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4425 if (line1
->type
== LINE_TREE_HEAD
)
4427 if (line2
->type
== LINE_TREE_HEAD
)
4430 switch (get_sort_field(tree_sort_state
)) {
4432 return sort_order(tree_sort_state
, timecmp(&entry1
->time
, &entry2
->time
));
4434 case ORDERBY_AUTHOR
:
4435 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4439 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4444 static struct line
*
4445 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4446 const char *mode
, const char *id
)
4448 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4449 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4451 if (!entry
|| !line
) {
4456 strncpy(entry
->name
, path
, strlen(path
));
4458 entry
->mode
= strtoul(mode
, NULL
, 8);
4460 string_copy_rev(entry
->id
, id
);
4466 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4468 static const char *author_name
;
4469 static struct time author_time
;
4471 if (!text
&& *read_date
) {
4476 char *path
= *opt_path
? opt_path
: ".";
4477 /* Find next entry to process */
4478 const char *log_file
[] = {
4479 "git", "log", "--no-color", "--pretty=raw",
4480 "--cc", "--raw", view
->id
, "--", path
, NULL
4485 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4486 report("Tree is empty");
4490 if (!io_run_rd(&io
, log_file
, opt_cdup
, FORMAT_NONE
)) {
4491 report("Failed to load tree data");
4495 io_done(view
->pipe
);
4500 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4501 parse_author_line(text
+ STRING_SIZE("author "),
4502 &author_name
, &author_time
);
4504 } else if (*text
== ':') {
4506 size_t annotated
= 1;
4509 pos
= strchr(text
, '\t');
4513 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4514 text
+= strlen(opt_path
);
4515 pos
= strchr(text
, '/');
4519 for (i
= 1; i
< view
->lines
; i
++) {
4520 struct line
*line
= &view
->line
[i
];
4521 struct tree_entry
*entry
= line
->data
;
4523 annotated
+= !!entry
->author
;
4524 if (entry
->author
|| strcmp(entry
->name
, text
))
4527 entry
->author
= author_name
;
4528 entry
->time
= author_time
;
4533 if (annotated
== view
->lines
)
4534 io_kill(view
->pipe
);
4540 tree_read(struct view
*view
, char *text
)
4542 static bool read_date
= FALSE
;
4543 struct tree_entry
*data
;
4544 struct line
*entry
, *line
;
4545 enum line_type type
;
4546 size_t textlen
= text
? strlen(text
) : 0;
4547 char *path
= text
+ SIZEOF_TREE_ATTR
;
4549 if (read_date
|| !text
)
4550 return tree_read_date(view
, text
, &read_date
);
4552 if (textlen
<= SIZEOF_TREE_ATTR
)
4554 if (view
->lines
== 0 &&
4555 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4558 /* Strip the path part ... */
4560 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4561 size_t striplen
= strlen(opt_path
);
4563 if (pathlen
> striplen
)
4564 memmove(path
, path
+ striplen
,
4565 pathlen
- striplen
+ 1);
4567 /* Insert "link" to parent directory. */
4568 if (view
->lines
== 1 &&
4569 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4573 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4574 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4579 /* Skip "Directory ..." and ".." line. */
4580 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4581 if (tree_compare_entry(line
, entry
) <= 0)
4584 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4588 for (; line
<= entry
; line
++)
4589 line
->dirty
= line
->cleareol
= 1;
4593 if (tree_lineno
> view
->lineno
) {
4594 view
->lineno
= tree_lineno
;
4602 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4604 struct tree_entry
*entry
= line
->data
;
4606 if (line
->type
== LINE_TREE_HEAD
) {
4607 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4610 if (draw_mode(view
, entry
->mode
))
4613 if (opt_author
&& draw_author(view
, entry
->author
))
4616 if (opt_date
&& draw_date(view
, &entry
->time
))
4619 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4627 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4628 int fd
= mkstemp(file
);
4631 report("Failed to create temporary file");
4632 else if (!io_run_append(blob_ops
.argv
, FORMAT_ALL
, fd
))
4633 report("Failed to save blob data to file");
4641 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4643 enum open_flags flags
;
4646 case REQ_VIEW_BLAME
:
4647 if (line
->type
!= LINE_TREE_FILE
) {
4648 report("Blame only supported for files");
4652 string_copy(opt_ref
, view
->vid
);
4656 if (line
->type
!= LINE_TREE_FILE
) {
4657 report("Edit only supported for files");
4658 } else if (!is_head_commit(view
->vid
)) {
4661 open_editor(opt_file
);
4665 case REQ_TOGGLE_SORT_FIELD
:
4666 case REQ_TOGGLE_SORT_ORDER
:
4667 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4672 /* quit view if at top of tree */
4673 return REQ_VIEW_CLOSE
;
4676 line
= &view
->line
[1];
4686 /* Cleanup the stack if the tree view is at a different tree. */
4687 while (!*opt_path
&& tree_stack
)
4688 pop_tree_stack_entry();
4690 switch (line
->type
) {
4692 /* Depending on whether it is a subdirectory or parent link
4693 * mangle the path buffer. */
4694 if (line
== &view
->line
[1] && *opt_path
) {
4695 pop_tree_stack_entry();
4698 const char *basename
= tree_path(line
);
4700 push_tree_stack_entry(basename
, view
->lineno
);
4703 /* Trees and subtrees share the same ID, so they are not not
4704 * unique like blobs. */
4705 flags
= OPEN_RELOAD
;
4706 request
= REQ_VIEW_TREE
;
4709 case LINE_TREE_FILE
:
4710 flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4711 request
= REQ_VIEW_BLOB
;
4718 open_view(view
, request
, flags
);
4719 if (request
== REQ_VIEW_TREE
)
4720 view
->lineno
= tree_lineno
;
4726 tree_grep(struct view
*view
, struct line
*line
)
4728 struct tree_entry
*entry
= line
->data
;
4729 const char *text
[] = {
4731 opt_author
? entry
->author
: "",
4732 mkdate(&entry
->time
, opt_date
),
4736 return grep_text(view
, text
);
4740 tree_select(struct view
*view
, struct line
*line
)
4742 struct tree_entry
*entry
= line
->data
;
4744 if (line
->type
== LINE_TREE_FILE
) {
4745 string_copy_rev(ref_blob
, entry
->id
);
4746 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4748 } else if (line
->type
!= LINE_TREE_DIR
) {
4752 string_copy_rev(view
->ref
, entry
->id
);
4756 tree_prepare(struct view
*view
)
4758 if (view
->lines
== 0 && opt_prefix
[0]) {
4759 char *pos
= opt_prefix
;
4761 while (pos
&& *pos
) {
4762 char *end
= strchr(pos
, '/');
4766 push_tree_stack_entry(pos
, 0);
4774 } else if (strcmp(view
->vid
, view
->id
)) {
4778 return io_format(&view
->io
, opt_cdup
, IO_RD
, view
->ops
->argv
, FORMAT_ALL
);
4781 static const char *tree_argv
[SIZEOF_ARG
] = {
4782 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4785 static struct view_ops tree_ops
= {
4798 blob_read(struct view
*view
, char *line
)
4802 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4806 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4813 return pager_request(view
, request
, line
);
4817 static const char *blob_argv
[SIZEOF_ARG
] = {
4818 "git", "cat-file", "blob", "%(blob)", NULL
4821 static struct view_ops blob_ops
= {
4835 * Loading the blame view is a two phase job:
4837 * 1. File content is read either using opt_file from the
4838 * filesystem or using git-cat-file.
4839 * 2. Then blame information is incrementally added by
4840 * reading output from git-blame.
4843 static const char *blame_head_argv
[] = {
4844 "git", "blame", "--incremental", "--", "%(file)", NULL
4847 static const char *blame_ref_argv
[] = {
4848 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4851 static const char *blame_cat_file_argv
[] = {
4852 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4855 struct blame_commit
{
4856 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4857 char title
[128]; /* First line of the commit message. */
4858 const char *author
; /* Author of the commit. */
4859 struct time time
; /* Date from the author ident. */
4860 char filename
[128]; /* Name of file. */
4861 bool has_previous
; /* Was a "previous" line detected. */
4865 struct blame_commit
*commit
;
4866 unsigned long lineno
;
4871 blame_open(struct view
*view
)
4873 char path
[SIZEOF_STR
];
4875 if (!view
->parent
&& *opt_prefix
) {
4876 string_copy(path
, opt_file
);
4877 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4881 if (*opt_ref
|| !io_open(&view
->io
, "%s%s", opt_cdup
, opt_file
)) {
4882 if (!io_run_rd(&view
->io
, blame_cat_file_argv
, opt_cdup
, FORMAT_ALL
))
4886 setup_update(view
, opt_file
);
4887 string_format(view
->ref
, "%s ...", opt_file
);
4892 static struct blame_commit
*
4893 get_blame_commit(struct view
*view
, const char *id
)
4897 for (i
= 0; i
< view
->lines
; i
++) {
4898 struct blame
*blame
= view
->line
[i
].data
;
4903 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4904 return blame
->commit
;
4908 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4911 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4917 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4919 const char *pos
= *posref
;
4922 pos
= strchr(pos
+ 1, ' ');
4923 if (!pos
|| !isdigit(pos
[1]))
4925 *number
= atoi(pos
+ 1);
4926 if (*number
< min
|| *number
> max
)
4933 static struct blame_commit
*
4934 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4936 struct blame_commit
*commit
;
4937 struct blame
*blame
;
4938 const char *pos
= text
+ SIZEOF_REV
- 2;
4939 size_t orig_lineno
= 0;
4943 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4946 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4947 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4948 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4951 commit
= get_blame_commit(view
, text
);
4957 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4960 blame
->commit
= commit
;
4961 blame
->lineno
= orig_lineno
+ group
- 1;
4969 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4972 const char **argv
= *opt_ref
? blame_ref_argv
: blame_head_argv
;
4975 if (view
->lines
== 0 && !view
->parent
)
4976 die("No blame exist for %s", view
->vid
);
4978 if (view
->lines
== 0 || !io_run_rd(&io
, argv
, opt_cdup
, FORMAT_ALL
)) {
4979 report("Failed to load blame data");
4983 io_done(view
->pipe
);
4989 size_t linelen
= strlen(line
);
4990 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
4995 blame
->commit
= NULL
;
4996 strncpy(blame
->text
, line
, linelen
);
4997 blame
->text
[linelen
] = 0;
4998 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
5003 match_blame_header(const char *name
, char **line
)
5005 size_t namelen
= strlen(name
);
5006 bool matched
= !strncmp(name
, *line
, namelen
);
5015 blame_read(struct view
*view
, char *line
)
5017 static struct blame_commit
*commit
= NULL
;
5018 static int blamed
= 0;
5019 static bool read_file
= TRUE
;
5022 return blame_read_file(view
, line
, &read_file
);
5029 string_format(view
->ref
, "%s", view
->vid
);
5030 if (view_is_displayed(view
)) {
5031 update_view_title(view
);
5032 redraw_view_from(view
, 0);
5038 commit
= parse_blame_commit(view
, line
, &blamed
);
5039 string_format(view
->ref
, "%s %2d%%", view
->vid
,
5040 view
->lines
? blamed
* 100 / view
->lines
: 0);
5042 } else if (match_blame_header("author ", &line
)) {
5043 commit
->author
= get_author(line
);
5045 } else if (match_blame_header("author-time ", &line
)) {
5046 parse_timesec(&commit
->time
, line
);
5048 } else if (match_blame_header("author-tz ", &line
)) {
5049 parse_timezone(&commit
->time
, line
);
5051 } else if (match_blame_header("summary ", &line
)) {
5052 string_ncopy(commit
->title
, line
, strlen(line
));
5054 } else if (match_blame_header("previous ", &line
)) {
5055 commit
->has_previous
= TRUE
;
5057 } else if (match_blame_header("filename ", &line
)) {
5058 string_ncopy(commit
->filename
, line
, strlen(line
));
5066 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5068 struct blame
*blame
= line
->data
;
5069 struct time
*time
= NULL
;
5070 const char *id
= NULL
, *author
= NULL
;
5071 char text
[SIZEOF_STR
];
5073 if (blame
->commit
&& *blame
->commit
->filename
) {
5074 id
= blame
->commit
->id
;
5075 author
= blame
->commit
->author
;
5076 time
= &blame
->commit
->time
;
5079 if (opt_date
&& draw_date(view
, time
))
5082 if (opt_author
&& draw_author(view
, author
))
5085 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
5088 if (draw_lineno(view
, lineno
))
5091 string_expand(text
, sizeof(text
), blame
->text
, opt_tab_size
);
5092 draw_text(view
, LINE_DEFAULT
, text
, TRUE
);
5097 check_blame_commit(struct blame
*blame
, bool check_null_id
)
5100 report("Commit data not loaded yet");
5101 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5102 report("No commit exist for the selected line");
5109 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
5111 const char *diff_tree_argv
[] = {
5112 "git", "diff-tree", "-U0", blame
->commit
->id
,
5113 "--", blame
->commit
->filename
, NULL
5116 int parent_lineno
= -1;
5117 int blamed_lineno
= -1;
5120 if (!io_run(&io
, diff_tree_argv
, NULL
, IO_RD
))
5123 while ((line
= io_get(&io
, '\n', TRUE
))) {
5125 char *pos
= strchr(line
, '+');
5127 parent_lineno
= atoi(line
+ 4);
5129 blamed_lineno
= atoi(pos
+ 1);
5131 } else if (*line
== '+' && parent_lineno
!= -1) {
5132 if (blame
->lineno
== blamed_lineno
- 1 &&
5133 !strcmp(blame
->text
, line
+ 1)) {
5134 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
5145 blame_request(struct view
*view
, enum request request
, struct line
*line
)
5147 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
5148 struct blame
*blame
= line
->data
;
5151 case REQ_VIEW_BLAME
:
5152 if (check_blame_commit(blame
, TRUE
)) {
5153 string_copy(opt_ref
, blame
->commit
->id
);
5154 string_copy(opt_file
, blame
->commit
->filename
);
5156 view
->lineno
= blame
->lineno
;
5157 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5162 if (check_blame_commit(blame
, TRUE
) &&
5163 select_commit_parent(blame
->commit
->id
, opt_ref
,
5164 blame
->commit
->filename
)) {
5165 string_copy(opt_file
, blame
->commit
->filename
);
5166 setup_blame_parent_line(view
, blame
);
5167 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5172 if (!check_blame_commit(blame
, FALSE
))
5175 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
5176 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
5179 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
5180 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
5181 const char *diff_index_argv
[] = {
5182 "git", "diff-index", "--root", "--patch-with-stat",
5183 "-C", "-M", "HEAD", "--", view
->vid
, NULL
5186 if (!blame
->commit
->has_previous
) {
5187 diff_index_argv
[1] = "diff";
5188 diff_index_argv
[2] = "--no-color";
5189 diff_index_argv
[6] = "--";
5190 diff_index_argv
[7] = "/dev/null";
5193 if (!prepare_update(diff
, diff_index_argv
, NULL
)) {
5194 report("Failed to allocate diff command");
5197 flags
|= OPEN_PREPARED
;
5200 open_view(view
, REQ_VIEW_DIFF
, flags
);
5201 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5202 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
5213 blame_grep(struct view
*view
, struct line
*line
)
5215 struct blame
*blame
= line
->data
;
5216 struct blame_commit
*commit
= blame
->commit
;
5217 const char *text
[] = {
5219 commit
? commit
->title
: "",
5220 commit
? commit
->id
: "",
5221 commit
&& opt_author
? commit
->author
: "",
5222 commit
? mkdate(&commit
->time
, opt_date
) : "",
5226 return grep_text(view
, text
);
5230 blame_select(struct view
*view
, struct line
*line
)
5232 struct blame
*blame
= line
->data
;
5233 struct blame_commit
*commit
= blame
->commit
;
5238 if (!strcmp(commit
->id
, NULL_ID
))
5239 string_ncopy(ref_commit
, "HEAD", 4);
5241 string_copy_rev(ref_commit
, commit
->id
);
5244 static struct view_ops blame_ops
= {
5260 const char *author
; /* Author of the last commit. */
5261 struct time time
; /* Date of the last activity. */
5262 const struct ref
*ref
; /* Name and commit ID information. */
5265 static const struct ref branch_all
;
5267 static const enum sort_field branch_sort_fields
[] = {
5268 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5270 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5273 branch_compare(const void *l1
, const void *l2
)
5275 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5276 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5278 switch (get_sort_field(branch_sort_state
)) {
5280 return sort_order(branch_sort_state
, timecmp(&branch1
->time
, &branch2
->time
));
5282 case ORDERBY_AUTHOR
:
5283 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5287 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5292 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5294 struct branch
*branch
= line
->data
;
5295 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5297 if (opt_date
&& draw_date(view
, &branch
->time
))
5300 if (opt_author
&& draw_author(view
, branch
->author
))
5303 draw_text(view
, type
, branch
->ref
== &branch_all
? "All branches" : branch
->ref
->name
, TRUE
);
5308 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5310 struct branch
*branch
= line
->data
;
5315 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5318 case REQ_TOGGLE_SORT_FIELD
:
5319 case REQ_TOGGLE_SORT_ORDER
:
5320 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5324 if (branch
->ref
== &branch_all
) {
5325 const char *all_branches_argv
[] = {
5326 "git", "log", "--no-color", "--pretty=raw", "--parents",
5327 "--topo-order", "--all", NULL
5329 struct view
*main_view
= VIEW(REQ_VIEW_MAIN
);
5331 if (!prepare_update(main_view
, all_branches_argv
, NULL
)) {
5332 report("Failed to load view of all branches");
5335 open_view(view
, REQ_VIEW_MAIN
, OPEN_PREPARED
| OPEN_SPLIT
);
5337 open_view(view
, REQ_VIEW_MAIN
, OPEN_SPLIT
);
5347 branch_read(struct view
*view
, char *line
)
5349 static char id
[SIZEOF_REV
];
5350 struct branch
*reference
;
5356 switch (get_line_type(line
)) {
5358 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5362 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5363 struct branch
*branch
= view
->line
[i
].data
;
5365 if (strcmp(branch
->ref
->id
, id
))
5368 view
->line
[i
].dirty
= TRUE
;
5370 branch
->author
= reference
->author
;
5371 branch
->time
= reference
->time
;
5375 parse_author_line(line
+ STRING_SIZE("author "),
5376 &branch
->author
, &branch
->time
);
5388 branch_open_visitor(void *data
, const struct ref
*ref
)
5390 struct view
*view
= data
;
5391 struct branch
*branch
;
5393 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5396 branch
= calloc(1, sizeof(*branch
));
5401 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5405 branch_open(struct view
*view
)
5407 const char *branch_log
[] = {
5408 "git", "log", "--no-color", "--pretty=raw",
5409 "--simplify-by-decoration", "--all", NULL
5412 if (!io_run_rd(&view
->io
, branch_log
, NULL
, FORMAT_NONE
)) {
5413 report("Failed to load branch data");
5417 setup_update(view
, view
->id
);
5418 branch_open_visitor(view
, &branch_all
);
5419 foreach_ref(branch_open_visitor
, view
);
5420 view
->p_restore
= TRUE
;
5426 branch_grep(struct view
*view
, struct line
*line
)
5428 struct branch
*branch
= line
->data
;
5429 const char *text
[] = {
5435 return grep_text(view
, text
);
5439 branch_select(struct view
*view
, struct line
*line
)
5441 struct branch
*branch
= line
->data
;
5443 string_copy_rev(view
->ref
, branch
->ref
->id
);
5444 string_copy_rev(ref_commit
, branch
->ref
->id
);
5445 string_copy_rev(ref_head
, branch
->ref
->id
);
5446 string_copy_rev(ref_branch
, branch
->ref
->name
);
5449 static struct view_ops branch_ops
= {
5468 char rev
[SIZEOF_REV
];
5469 char name
[SIZEOF_STR
];
5473 char rev
[SIZEOF_REV
];
5474 char name
[SIZEOF_STR
];
5478 static char status_onbranch
[SIZEOF_STR
];
5479 static struct status stage_status
;
5480 static enum line_type stage_line_type
;
5481 static size_t stage_chunks
;
5482 static int *stage_chunk
;
5484 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5486 /* This should work even for the "On branch" line. */
5488 status_has_none(struct view
*view
, struct line
*line
)
5490 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5493 /* Get fields from the diff line:
5494 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5497 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5499 const char *old_mode
= buf
+ 1;
5500 const char *new_mode
= buf
+ 8;
5501 const char *old_rev
= buf
+ 15;
5502 const char *new_rev
= buf
+ 56;
5503 const char *status
= buf
+ 97;
5506 old_mode
[-1] != ':' ||
5507 new_mode
[-1] != ' ' ||
5508 old_rev
[-1] != ' ' ||
5509 new_rev
[-1] != ' ' ||
5513 file
->status
= *status
;
5515 string_copy_rev(file
->old
.rev
, old_rev
);
5516 string_copy_rev(file
->new.rev
, new_rev
);
5518 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5519 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5521 file
->old
.name
[0] = file
->new.name
[0] = 0;
5527 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5529 struct status
*unmerged
= NULL
;
5533 if (!io_run(&io
, argv
, opt_cdup
, IO_RD
))
5536 add_line_data(view
, NULL
, type
);
5538 while ((buf
= io_get(&io
, 0, TRUE
))) {
5539 struct status
*file
= unmerged
;
5542 file
= calloc(1, sizeof(*file
));
5543 if (!file
|| !add_line_data(view
, file
, type
))
5547 /* Parse diff info part. */
5549 file
->status
= status
;
5551 string_copy(file
->old
.rev
, NULL_ID
);
5553 } else if (!file
->status
|| file
== unmerged
) {
5554 if (!status_get_diff(file
, buf
, strlen(buf
)))
5557 buf
= io_get(&io
, 0, TRUE
);
5561 /* Collapse all modified entries that follow an
5562 * associated unmerged entry. */
5563 if (unmerged
== file
) {
5564 unmerged
->status
= 'U';
5566 } else if (file
->status
== 'U') {
5571 /* Grab the old name for rename/copy. */
5572 if (!*file
->old
.name
&&
5573 (file
->status
== 'R' || file
->status
== 'C')) {
5574 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5576 buf
= io_get(&io
, 0, TRUE
);
5581 /* git-ls-files just delivers a NUL separated list of
5582 * file names similar to the second half of the
5583 * git-diff-* output. */
5584 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5585 if (!*file
->old
.name
)
5586 string_copy(file
->old
.name
, file
->new.name
);
5590 if (io_error(&io
)) {
5596 if (!view
->line
[view
->lines
- 1].data
)
5597 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5603 /* Don't show unmerged entries in the staged section. */
5604 static const char *status_diff_index_argv
[] = {
5605 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5606 "--cached", "-M", "HEAD", NULL
5609 static const char *status_diff_files_argv
[] = {
5610 "git", "diff-files", "-z", NULL
5613 static const char *status_list_other_argv
[] = {
5614 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix
, NULL
5617 static const char *status_list_no_head_argv
[] = {
5618 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5621 static const char *update_index_argv
[] = {
5622 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5625 /* Restore the previous line number to stay in the context or select a
5626 * line with something that can be updated. */
5628 status_restore(struct view
*view
)
5630 if (view
->p_lineno
>= view
->lines
)
5631 view
->p_lineno
= view
->lines
- 1;
5632 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5634 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5637 /* If the above fails, always skip the "On branch" line. */
5638 if (view
->p_lineno
< view
->lines
)
5639 view
->lineno
= view
->p_lineno
;
5643 if (view
->lineno
< view
->offset
)
5644 view
->offset
= view
->lineno
;
5645 else if (view
->offset
+ view
->height
<= view
->lineno
)
5646 view
->offset
= view
->lineno
- view
->height
+ 1;
5648 view
->p_restore
= FALSE
;
5652 status_update_onbranch(void)
5654 static const char *paths
[][2] = {
5655 { "rebase-apply/rebasing", "Rebasing" },
5656 { "rebase-apply/applying", "Applying mailbox" },
5657 { "rebase-apply/", "Rebasing mailbox" },
5658 { "rebase-merge/interactive", "Interactive rebase" },
5659 { "rebase-merge/", "Rebase merge" },
5660 { "MERGE_HEAD", "Merging" },
5661 { "BISECT_LOG", "Bisecting" },
5662 { "HEAD", "On branch" },
5664 char buf
[SIZEOF_STR
];
5668 if (is_initial_commit()) {
5669 string_copy(status_onbranch
, "Initial commit");
5673 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5674 char *head
= opt_head
;
5676 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5677 lstat(buf
, &stat
) < 0)
5683 if (io_open(&io
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5684 io_read_buf(&io
, buf
, sizeof(buf
))) {
5686 if (!prefixcmp(head
, "refs/heads/"))
5687 head
+= STRING_SIZE("refs/heads/");
5691 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5692 string_copy(status_onbranch
, opt_head
);
5696 string_copy(status_onbranch
, "Not currently on any branch");
5699 /* First parse staged info using git-diff-index(1), then parse unstaged
5700 * info using git-diff-files(1), and finally untracked files using
5701 * git-ls-files(1). */
5703 status_open(struct view
*view
)
5707 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5708 status_update_onbranch();
5710 io_run_bg(update_index_argv
);
5712 if (is_initial_commit()) {
5713 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5715 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5719 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5720 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5723 /* Restore the exact position or use the specialized restore
5725 if (!view
->p_restore
)
5726 status_restore(view
);
5731 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5733 struct status
*status
= line
->data
;
5734 enum line_type type
;
5738 switch (line
->type
) {
5739 case LINE_STAT_STAGED
:
5740 type
= LINE_STAT_SECTION
;
5741 text
= "Changes to be committed:";
5744 case LINE_STAT_UNSTAGED
:
5745 type
= LINE_STAT_SECTION
;
5746 text
= "Changed but not updated:";
5749 case LINE_STAT_UNTRACKED
:
5750 type
= LINE_STAT_SECTION
;
5751 text
= "Untracked files:";
5754 case LINE_STAT_NONE
:
5755 type
= LINE_DEFAULT
;
5756 text
= " (no files)";
5759 case LINE_STAT_HEAD
:
5760 type
= LINE_STAT_HEAD
;
5761 text
= status_onbranch
;
5768 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5770 buf
[0] = status
->status
;
5771 if (draw_text(view
, line
->type
, buf
, TRUE
))
5773 type
= LINE_DEFAULT
;
5774 text
= status
->new.name
;
5777 draw_text(view
, type
, text
, TRUE
);
5782 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5784 if (displayed_views() == 2 || display
[current_view
] != view
)
5785 maximize_view(view
);
5786 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5791 status_enter(struct view
*view
, struct line
*line
)
5793 struct status
*status
= line
->data
;
5794 const char *oldpath
= status
? status
->old
.name
: NULL
;
5795 /* Diffs for unmerged entries are empty when passing the new
5796 * path, so leave it empty. */
5797 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5799 enum open_flags split
;
5800 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5802 if (line
->type
== LINE_STAT_NONE
||
5803 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5804 report("No file to diff");
5808 switch (line
->type
) {
5809 case LINE_STAT_STAGED
:
5810 if (is_initial_commit()) {
5811 const char *no_head_diff_argv
[] = {
5812 "git", "diff", "--no-color", "--patch-with-stat",
5813 "--", "/dev/null", newpath
, NULL
5816 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
))
5817 return status_load_error(view
, stage
, newpath
);
5819 const char *index_show_argv
[] = {
5820 "git", "diff-index", "--root", "--patch-with-stat",
5821 "-C", "-M", "--cached", "HEAD", "--",
5822 oldpath
, newpath
, NULL
5825 if (!prepare_update(stage
, index_show_argv
, opt_cdup
))
5826 return status_load_error(view
, stage
, newpath
);
5830 info
= "Staged changes to %s";
5832 info
= "Staged changes";
5835 case LINE_STAT_UNSTAGED
:
5837 const char *files_show_argv
[] = {
5838 "git", "diff-files", "--root", "--patch-with-stat",
5839 "-C", "-M", "--", oldpath
, newpath
, NULL
5842 if (!prepare_update(stage
, files_show_argv
, opt_cdup
))
5843 return status_load_error(view
, stage
, newpath
);
5845 info
= "Unstaged changes to %s";
5847 info
= "Unstaged changes";
5850 case LINE_STAT_UNTRACKED
:
5852 report("No file to show");
5856 if (!suffixcmp(status
->new.name
, -1, "/")) {
5857 report("Cannot display a directory");
5861 if (!prepare_update_file(stage
, newpath
))
5862 return status_load_error(view
, stage
, newpath
);
5863 info
= "Untracked file %s";
5866 case LINE_STAT_HEAD
:
5870 die("line type %d not handled in switch", line
->type
);
5873 split
= view_is_displayed(view
) ? OPEN_SPLIT
: 0;
5874 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5875 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5877 stage_status
= *status
;
5879 memset(&stage_status
, 0, sizeof(stage_status
));
5882 stage_line_type
= line
->type
;
5884 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5891 status_exists(struct status
*status
, enum line_type type
)
5893 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5894 unsigned long lineno
;
5896 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5897 struct line
*line
= &view
->line
[lineno
];
5898 struct status
*pos
= line
->data
;
5900 if (line
->type
!= type
)
5902 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5903 select_view_line(view
, lineno
);
5906 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5907 select_view_line(view
, lineno
);
5917 status_update_prepare(struct io
*io
, enum line_type type
)
5919 const char *staged_argv
[] = {
5920 "git", "update-index", "-z", "--index-info", NULL
5922 const char *others_argv
[] = {
5923 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5927 case LINE_STAT_STAGED
:
5928 return io_run(io
, staged_argv
, opt_cdup
, IO_WR
);
5930 case LINE_STAT_UNSTAGED
:
5931 case LINE_STAT_UNTRACKED
:
5932 return io_run(io
, others_argv
, opt_cdup
, IO_WR
);
5935 die("line type %d not handled in switch", type
);
5941 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5943 char buf
[SIZEOF_STR
];
5947 case LINE_STAT_STAGED
:
5948 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5951 status
->old
.name
, 0))
5955 case LINE_STAT_UNSTAGED
:
5956 case LINE_STAT_UNTRACKED
:
5957 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
5962 die("line type %d not handled in switch", type
);
5965 return io_write(io
, buf
, bufsize
);
5969 status_update_file(struct status
*status
, enum line_type type
)
5974 if (!status_update_prepare(&io
, type
))
5977 result
= status_update_write(&io
, status
, type
);
5978 return io_done(&io
) && result
;
5982 status_update_files(struct view
*view
, struct line
*line
)
5984 char buf
[sizeof(view
->ref
)];
5987 struct line
*pos
= view
->line
+ view
->lines
;
5990 int cursor_y
= -1, cursor_x
= -1;
5992 if (!status_update_prepare(&io
, line
->type
))
5995 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
5998 string_copy(buf
, view
->ref
);
5999 getsyx(cursor_y
, cursor_x
);
6000 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
6001 int almost_done
= file
* 100 / files
;
6003 if (almost_done
> done
) {
6005 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
6007 update_view_title(view
);
6008 setsyx(cursor_y
, cursor_x
);
6011 result
= status_update_write(&io
, line
->data
, line
->type
);
6013 string_copy(view
->ref
, buf
);
6015 return io_done(&io
) && result
;
6019 status_update(struct view
*view
)
6021 struct line
*line
= &view
->line
[view
->lineno
];
6023 assert(view
->lines
);
6026 /* This should work even for the "On branch" line. */
6027 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
6028 report("Nothing to update");
6032 if (!status_update_files(view
, line
+ 1)) {
6033 report("Failed to update file status");
6037 } else if (!status_update_file(line
->data
, line
->type
)) {
6038 report("Failed to update file status");
6046 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
6048 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
6049 if (type
== LINE_STAT_STAGED
) {
6050 report("Cannot revert changes to staged files");
6051 } else if (type
== LINE_STAT_UNTRACKED
) {
6052 report("Cannot revert changes to untracked files");
6053 } else if (has_none
) {
6054 report("Nothing to revert");
6056 report("Cannot revert changes to multiple files");
6059 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6060 char mode
[10] = "100644";
6061 const char *reset_argv
[] = {
6062 "git", "update-index", "--cacheinfo", mode
,
6063 status
->old
.rev
, status
->old
.name
, NULL
6065 const char *checkout_argv
[] = {
6066 "git", "checkout", "--", status
->old
.name
, NULL
6069 if (status
->status
== 'U') {
6070 string_format(mode
, "%5o", status
->old
.mode
);
6072 if (status
->old
.mode
== 0 && status
->new.mode
== 0) {
6073 reset_argv
[2] = "--force-remove";
6074 reset_argv
[3] = status
->old
.name
;
6075 reset_argv
[4] = NULL
;
6078 if (!io_run_fg(reset_argv
, opt_cdup
))
6080 if (status
->old
.mode
== 0 && status
->new.mode
== 0)
6084 return io_run_fg(checkout_argv
, opt_cdup
);
6091 status_request(struct view
*view
, enum request request
, struct line
*line
)
6093 struct status
*status
= line
->data
;
6096 case REQ_STATUS_UPDATE
:
6097 if (!status_update(view
))
6101 case REQ_STATUS_REVERT
:
6102 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
6106 case REQ_STATUS_MERGE
:
6107 if (!status
|| status
->status
!= 'U') {
6108 report("Merging only possible for files with unmerged status ('U').");
6111 open_mergetool(status
->new.name
);
6117 if (status
->status
== 'D') {
6118 report("File has been deleted.");
6122 open_editor(status
->new.name
);
6125 case REQ_VIEW_BLAME
:
6131 /* After returning the status view has been split to
6132 * show the stage view. No further reloading is
6134 return status_enter(view
, line
);
6137 /* Simply reload the view. */
6144 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
6150 status_select(struct view
*view
, struct line
*line
)
6152 struct status
*status
= line
->data
;
6153 char file
[SIZEOF_STR
] = "all files";
6157 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
6160 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
6163 switch (line
->type
) {
6164 case LINE_STAT_STAGED
:
6165 text
= "Press %s to unstage %s for commit";
6168 case LINE_STAT_UNSTAGED
:
6169 text
= "Press %s to stage %s for commit";
6172 case LINE_STAT_UNTRACKED
:
6173 text
= "Press %s to stage %s for addition";
6176 case LINE_STAT_HEAD
:
6177 case LINE_STAT_NONE
:
6178 text
= "Nothing to update";
6182 die("line type %d not handled in switch", line
->type
);
6185 if (status
&& status
->status
== 'U') {
6186 text
= "Press %s to resolve conflict in %s";
6187 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
6190 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
6193 string_format(view
->ref
, text
, key
, file
);
6195 string_copy(opt_file
, status
->new.name
);
6199 status_grep(struct view
*view
, struct line
*line
)
6201 struct status
*status
= line
->data
;
6204 const char buf
[2] = { status
->status
, 0 };
6205 const char *text
[] = { status
->new.name
, buf
, NULL
};
6207 return grep_text(view
, text
);
6213 static struct view_ops status_ops
= {
6226 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
6228 while (line
< end
) {
6229 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
6230 !io_write(io
, "\n", 1))
6233 if (line
->type
== LINE_DIFF_CHUNK
||
6234 line
->type
== LINE_DIFF_HEADER
)
6241 static struct line
*
6242 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
6244 for (; view
->line
< line
; line
--)
6245 if (line
->type
== type
)
6252 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
6254 const char *apply_argv
[SIZEOF_ARG
] = {
6255 "git", "apply", "--whitespace=nowarn", NULL
6257 struct line
*diff_hdr
;
6261 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6266 apply_argv
[argc
++] = "--cached";
6267 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6268 apply_argv
[argc
++] = "-R";
6269 apply_argv
[argc
++] = "-";
6270 apply_argv
[argc
++] = NULL
;
6271 if (!io_run(&io
, apply_argv
, opt_cdup
, IO_WR
))
6274 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6275 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6279 io_run_bg(update_index_argv
);
6281 return chunk
? TRUE
: FALSE
;
6285 stage_update(struct view
*view
, struct line
*line
)
6287 struct line
*chunk
= NULL
;
6289 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6290 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6293 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6294 report("Failed to apply chunk");
6298 } else if (!stage_status
.status
) {
6299 view
= VIEW(REQ_VIEW_STATUS
);
6301 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6302 if (line
->type
== stage_line_type
)
6305 if (!status_update_files(view
, line
+ 1)) {
6306 report("Failed to update files");
6310 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6311 report("Failed to update file");
6319 stage_revert(struct view
*view
, struct line
*line
)
6321 struct line
*chunk
= NULL
;
6323 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6324 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6327 if (!prompt_yesno("Are you sure you want to revert changes?"))
6330 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6331 report("Failed to revert chunk");
6337 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6338 stage_line_type
, FALSE
);
6344 stage_next(struct view
*view
, struct line
*line
)
6348 if (!stage_chunks
) {
6349 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6350 if (line
->type
!= LINE_DIFF_CHUNK
)
6353 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6354 report("Allocation failure");
6358 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6362 for (i
= 0; i
< stage_chunks
; i
++) {
6363 if (stage_chunk
[i
] > view
->lineno
) {
6364 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6365 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6370 report("No next chunk found");
6374 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6377 case REQ_STATUS_UPDATE
:
6378 if (!stage_update(view
, line
))
6382 case REQ_STATUS_REVERT
:
6383 if (!stage_revert(view
, line
))
6387 case REQ_STAGE_NEXT
:
6388 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6389 report("File is untracked; press %s to add",
6390 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6393 stage_next(view
, line
);
6397 if (!stage_status
.new.name
[0])
6399 if (stage_status
.status
== 'D') {
6400 report("File has been deleted.");
6404 open_editor(stage_status
.new.name
);
6408 /* Reload everything ... */
6411 case REQ_VIEW_BLAME
:
6412 if (stage_status
.new.name
[0]) {
6413 string_copy(opt_file
, stage_status
.new.name
);
6419 return pager_request(view
, request
, line
);
6425 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6426 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6428 /* Check whether the staged entry still exists, and close the
6429 * stage view if it doesn't. */
6430 if (!status_exists(&stage_status
, stage_line_type
)) {
6431 status_restore(VIEW(REQ_VIEW_STATUS
));
6432 return REQ_VIEW_CLOSE
;
6435 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6436 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6437 report("Cannot display a directory");
6441 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6442 report("Failed to open file: %s", strerror(errno
));
6446 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6451 static struct view_ops stage_ops
= {
6468 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6469 char title
[128]; /* First line of the commit message. */
6470 const char *author
; /* Author of the commit. */
6471 struct time time
; /* Date from the author ident. */
6472 struct ref_list
*refs
; /* Repository references. */
6473 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6474 size_t graph_size
; /* The width of the graph array. */
6475 bool has_parents
; /* Rewritten --parents seen. */
6478 /* Size of rev graph with no "padding" columns */
6479 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6482 struct rev_graph
*prev
, *next
, *parents
;
6483 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6485 struct commit
*commit
;
6487 unsigned int boundary
:1;
6490 /* Parents of the commit being visualized. */
6491 static struct rev_graph graph_parents
[4];
6493 /* The current stack of revisions on the graph. */
6494 static struct rev_graph graph_stacks
[4] = {
6495 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6496 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6497 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6498 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6502 graph_parent_is_merge(struct rev_graph
*graph
)
6504 return graph
->parents
->size
> 1;
6508 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6510 struct commit
*commit
= graph
->commit
;
6512 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6513 commit
->graph
[commit
->graph_size
++] = symbol
;
6517 clear_rev_graph(struct rev_graph
*graph
)
6519 graph
->boundary
= 0;
6520 graph
->size
= graph
->pos
= 0;
6521 graph
->commit
= NULL
;
6522 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6526 done_rev_graph(struct rev_graph
*graph
)
6528 if (graph_parent_is_merge(graph
) &&
6529 graph
->pos
< graph
->size
- 1 &&
6530 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6531 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6533 graph
->commit
->graph_size
= i
* 2;
6534 while (i
< graph
->next
->size
- 1) {
6535 append_to_rev_graph(graph
, ' ');
6536 append_to_rev_graph(graph
, '\\');
6541 clear_rev_graph(graph
);
6545 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6549 /* "Collapse" duplicate parents lines.
6551 * FIXME: This needs to also update update the drawn graph but
6552 * for now it just serves as a method for pruning graph lines. */
6553 for (i
= 0; i
< graph
->size
; i
++)
6554 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6557 if (graph
->size
< SIZEOF_REVITEMS
) {
6558 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6563 get_rev_graph_symbol(struct rev_graph
*graph
)
6567 if (graph
->boundary
)
6568 symbol
= REVGRAPH_BOUND
;
6569 else if (graph
->parents
->size
== 0)
6570 symbol
= REVGRAPH_INIT
;
6571 else if (graph_parent_is_merge(graph
))
6572 symbol
= REVGRAPH_MERGE
;
6573 else if (graph
->pos
>= graph
->size
)
6574 symbol
= REVGRAPH_BRANCH
;
6576 symbol
= REVGRAPH_COMMIT
;
6582 draw_rev_graph(struct rev_graph
*graph
)
6585 chtype separator
, line
;
6587 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6588 static struct rev_filler fillers
[] = {
6594 chtype symbol
= get_rev_graph_symbol(graph
);
6595 struct rev_filler
*filler
;
6598 fillers
[DEFAULT
].line
= opt_line_graphics
? ACS_VLINE
: '|';
6599 filler
= &fillers
[DEFAULT
];
6601 for (i
= 0; i
< graph
->pos
; i
++) {
6602 append_to_rev_graph(graph
, filler
->line
);
6603 if (graph_parent_is_merge(graph
->prev
) &&
6604 graph
->prev
->pos
== i
)
6605 filler
= &fillers
[RSHARP
];
6607 append_to_rev_graph(graph
, filler
->separator
);
6610 /* Place the symbol for this revision. */
6611 append_to_rev_graph(graph
, symbol
);
6613 if (graph
->prev
->size
> graph
->size
)
6614 filler
= &fillers
[RDIAG
];
6616 filler
= &fillers
[DEFAULT
];
6620 for (; i
< graph
->size
; i
++) {
6621 append_to_rev_graph(graph
, filler
->separator
);
6622 append_to_rev_graph(graph
, filler
->line
);
6623 if (graph_parent_is_merge(graph
->prev
) &&
6624 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6625 filler
= &fillers
[RSHARP
];
6626 if (graph
->prev
->size
> graph
->size
)
6627 filler
= &fillers
[LDIAG
];
6630 if (graph
->prev
->size
> graph
->size
) {
6631 append_to_rev_graph(graph
, filler
->separator
);
6632 if (filler
->line
!= ' ')
6633 append_to_rev_graph(graph
, filler
->line
);
6637 /* Prepare the next rev graph */
6639 prepare_rev_graph(struct rev_graph
*graph
)
6643 /* First, traverse all lines of revisions up to the active one. */
6644 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6645 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6648 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6651 /* Interleave the new revision parent(s). */
6652 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6653 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6655 /* Lastly, put any remaining revisions. */
6656 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6657 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6661 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6663 /* If this is the finalizing update ... */
6665 prepare_rev_graph(graph
);
6667 /* Graph visualization needs a one rev look-ahead,
6668 * so the first update doesn't visualize anything. */
6669 if (!graph
->prev
->commit
)
6672 if (view
->lines
> 2)
6673 view
->line
[view
->lines
- 3].dirty
= 1;
6674 if (view
->lines
> 1)
6675 view
->line
[view
->lines
- 2].dirty
= 1;
6676 draw_rev_graph(graph
->prev
);
6677 done_rev_graph(graph
->prev
->prev
);
6685 static const char *main_argv
[SIZEOF_ARG
] = {
6686 "git", "log", "--no-color", "--pretty=raw", "--parents",
6687 "--topo-order", "%(head)", NULL
6691 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6693 struct commit
*commit
= line
->data
;
6695 if (!commit
->author
)
6698 if (opt_date
&& draw_date(view
, &commit
->time
))
6701 if (opt_author
&& draw_author(view
, commit
->author
))
6704 if (opt_rev_graph
&& commit
->graph_size
&&
6705 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6708 if (opt_show_refs
&& commit
->refs
) {
6711 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6712 struct ref
*ref
= commit
->refs
->refs
[i
];
6713 enum line_type type
;
6716 type
= LINE_MAIN_HEAD
;
6718 type
= LINE_MAIN_LOCAL_TAG
;
6720 type
= LINE_MAIN_TAG
;
6721 else if (ref
->tracked
)
6722 type
= LINE_MAIN_TRACKED
;
6723 else if (ref
->remote
)
6724 type
= LINE_MAIN_REMOTE
;
6726 type
= LINE_MAIN_REF
;
6728 if (draw_text(view
, type
, "[", TRUE
) ||
6729 draw_text(view
, type
, ref
->name
, TRUE
) ||
6730 draw_text(view
, type
, "]", TRUE
))
6733 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6738 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6742 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6744 main_read(struct view
*view
, char *line
)
6746 static struct rev_graph
*graph
= graph_stacks
;
6747 enum line_type type
;
6748 struct commit
*commit
;
6753 if (!view
->lines
&& !view
->parent
)
6754 die("No revisions match the given arguments.");
6755 if (view
->lines
> 0) {
6756 commit
= view
->line
[view
->lines
- 1].data
;
6757 view
->line
[view
->lines
- 1].dirty
= 1;
6758 if (!commit
->author
) {
6761 graph
->commit
= NULL
;
6764 update_rev_graph(view
, graph
);
6766 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6767 clear_rev_graph(&graph_stacks
[i
]);
6771 type
= get_line_type(line
);
6772 if (type
== LINE_COMMIT
) {
6773 commit
= calloc(1, sizeof(struct commit
));
6777 line
+= STRING_SIZE("commit ");
6779 graph
->boundary
= 1;
6783 string_copy_rev(commit
->id
, line
);
6784 commit
->refs
= get_ref_list(commit
->id
);
6785 graph
->commit
= commit
;
6786 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6788 while ((line
= strchr(line
, ' '))) {
6790 push_rev_graph(graph
->parents
, line
);
6791 commit
->has_parents
= TRUE
;
6798 commit
= view
->line
[view
->lines
- 1].data
;
6802 if (commit
->has_parents
)
6804 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6808 parse_author_line(line
+ STRING_SIZE("author "),
6809 &commit
->author
, &commit
->time
);
6810 update_rev_graph(view
, graph
);
6811 graph
= graph
->next
;
6815 /* Fill in the commit title if it has not already been set. */
6816 if (commit
->title
[0])
6819 /* Require titles to start with a non-space character at the
6820 * offset used by git log. */
6821 if (strncmp(line
, " ", 4))
6824 /* Well, if the title starts with a whitespace character,
6825 * try to be forgiving. Otherwise we end up with no title. */
6826 while (isspace(*line
))
6830 /* FIXME: More graceful handling of titles; append "..." to
6831 * shortened titles, etc. */
6833 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6834 view
->line
[view
->lines
- 1].dirty
= 1;
6841 main_request(struct view
*view
, enum request request
, struct line
*line
)
6843 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
6847 open_view(view
, REQ_VIEW_DIFF
, flags
);
6851 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6861 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6866 if (!opt_show_refs
|| !list
)
6869 for (i
= 0; i
< list
->size
; i
++) {
6870 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6878 main_grep(struct view
*view
, struct line
*line
)
6880 struct commit
*commit
= line
->data
;
6881 const char *text
[] = {
6883 opt_author
? commit
->author
: "",
6884 mkdate(&commit
->time
, opt_date
),
6888 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6892 main_select(struct view
*view
, struct line
*line
)
6894 struct commit
*commit
= line
->data
;
6896 string_copy_rev(view
->ref
, commit
->id
);
6897 string_copy_rev(ref_commit
, view
->ref
);
6900 static struct view_ops main_ops
= {
6916 /* Whether or not the curses interface has been initialized. */
6917 static bool cursed
= FALSE
;
6919 /* Terminal hacks and workarounds. */
6920 static bool use_scroll_redrawwin
;
6921 static bool use_scroll_status_wclear
;
6923 /* The status window is used for polling keystrokes. */
6924 static WINDOW
*status_win
;
6926 /* Reading from the prompt? */
6927 static bool input_mode
= FALSE
;
6929 static bool status_empty
= FALSE
;
6931 /* Update status and title window. */
6933 report(const char *msg
, ...)
6935 struct view
*view
= display
[current_view
];
6941 char buf
[SIZEOF_STR
];
6944 va_start(args
, msg
);
6945 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6946 buf
[sizeof(buf
) - 1] = 0;
6947 buf
[sizeof(buf
) - 2] = '.';
6948 buf
[sizeof(buf
) - 3] = '.';
6949 buf
[sizeof(buf
) - 4] = '.';
6955 if (!status_empty
|| *msg
) {
6958 va_start(args
, msg
);
6960 wmove(status_win
, 0, 0);
6961 if (view
->has_scrolled
&& use_scroll_status_wclear
)
6964 vwprintw(status_win
, msg
, args
);
6965 status_empty
= FALSE
;
6967 status_empty
= TRUE
;
6969 wclrtoeol(status_win
);
6970 wnoutrefresh(status_win
);
6975 update_view_title(view
);
6984 /* Initialize the curses library */
6985 if (isatty(STDIN_FILENO
)) {
6986 cursed
= !!initscr();
6989 /* Leave stdin and stdout alone when acting as a pager. */
6990 opt_tty
= fopen("/dev/tty", "r+");
6992 die("Failed to open /dev/tty");
6993 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
6997 die("Failed to initialize curses");
6999 nonl(); /* Disable conversion and detect newlines from input. */
7000 cbreak(); /* Take input chars one at a time, no wait for \n */
7001 noecho(); /* Don't echo input */
7002 leaveok(stdscr
, FALSE
);
7007 getmaxyx(stdscr
, y
, x
);
7008 status_win
= newwin(1, 0, y
- 1, 0);
7010 die("Failed to create status window");
7012 /* Enable keyboard mapping */
7013 keypad(status_win
, TRUE
);
7014 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
7016 TABSIZE
= opt_tab_size
;
7018 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
7019 if (term
&& !strcmp(term
, "gnome-terminal")) {
7020 /* In the gnome-terminal-emulator, the message from
7021 * scrolling up one line when impossible followed by
7022 * scrolling down one line causes corruption of the
7023 * status line. This is fixed by calling wclear. */
7024 use_scroll_status_wclear
= TRUE
;
7025 use_scroll_redrawwin
= FALSE
;
7027 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
7028 /* No problems with full optimizations in xrvt-(unicode)
7030 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
7033 /* When scrolling in (u)xterm the last line in the
7034 * scrolling direction will update slowly. */
7035 use_scroll_redrawwin
= TRUE
;
7036 use_scroll_status_wclear
= FALSE
;
7041 get_input(int prompt_position
)
7044 int i
, key
, cursor_y
, cursor_x
;
7045 bool loading
= FALSE
;
7047 if (prompt_position
)
7051 foreach_view (view
, i
) {
7053 if (view_is_displayed(view
) && view
->has_scrolled
&&
7054 use_scroll_redrawwin
)
7055 redrawwin(view
->win
);
7056 view
->has_scrolled
= FALSE
;
7061 /* Update the cursor position. */
7062 if (prompt_position
) {
7063 getbegyx(status_win
, cursor_y
, cursor_x
);
7064 cursor_x
= prompt_position
;
7066 view
= display
[current_view
];
7067 getbegyx(view
->win
, cursor_y
, cursor_x
);
7068 cursor_x
= view
->width
- 1;
7069 cursor_y
+= view
->lineno
- view
->offset
;
7071 setsyx(cursor_y
, cursor_x
);
7073 /* Refresh, accept single keystroke of input */
7075 nodelay(status_win
, loading
);
7076 key
= wgetch(status_win
);
7078 /* wgetch() with nodelay() enabled returns ERR when
7079 * there's no input. */
7082 } else if (key
== KEY_RESIZE
) {
7085 getmaxyx(stdscr
, height
, width
);
7087 wresize(status_win
, 1, width
);
7088 mvwin(status_win
, height
- 1, 0);
7089 wnoutrefresh(status_win
);
7091 redraw_display(TRUE
);
7101 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7103 enum input_status status
= INPUT_OK
;
7104 static char buf
[SIZEOF_STR
];
7109 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7112 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7113 wclrtoeol(status_win
);
7115 key
= get_input(pos
+ 1);
7120 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7127 status
= INPUT_CANCEL
;
7131 status
= INPUT_CANCEL
;
7135 if (pos
>= sizeof(buf
)) {
7136 report("Input string too long");
7140 status
= handler(data
, buf
, key
);
7141 if (status
== INPUT_OK
)
7142 buf
[pos
++] = (char) key
;
7146 /* Clear the status window */
7147 status_empty
= FALSE
;
7150 if (status
== INPUT_CANCEL
)
7158 static enum input_status
7159 prompt_yesno_handler(void *data
, char *buf
, int c
)
7161 if (c
== 'y' || c
== 'Y')
7163 if (c
== 'n' || c
== 'N')
7164 return INPUT_CANCEL
;
7169 prompt_yesno(const char *prompt
)
7171 char prompt2
[SIZEOF_STR
];
7173 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7176 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7179 static enum input_status
7180 read_prompt_handler(void *data
, char *buf
, int c
)
7182 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7186 read_prompt(const char *prompt
)
7188 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7191 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7193 enum input_status status
= INPUT_OK
;
7196 while (items
[size
].text
)
7199 while (status
== INPUT_OK
) {
7200 const struct menu_item
*item
= &items
[*selected
];
7204 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7205 prompt
, *selected
+ 1, size
);
7207 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7208 wprintw(status_win
, "%s", item
->text
);
7209 wclrtoeol(status_win
);
7211 key
= get_input(COLS
- 1);
7216 status
= INPUT_STOP
;
7221 *selected
= *selected
- 1;
7223 *selected
= size
- 1;
7228 *selected
= (*selected
+ 1) % size
;
7232 status
= INPUT_CANCEL
;
7236 for (i
= 0; items
[i
].text
; i
++)
7237 if (items
[i
].hotkey
== key
) {
7239 status
= INPUT_STOP
;
7245 /* Clear the status window */
7246 status_empty
= FALSE
;
7249 return status
!= INPUT_CANCEL
;
7253 * Repository properties
7256 static struct ref
**refs
= NULL
;
7257 static size_t refs_size
= 0;
7258 static struct ref
*refs_head
= NULL
;
7260 static struct ref_list
**ref_lists
= NULL
;
7261 static size_t ref_lists_size
= 0;
7263 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7264 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7265 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7268 compare_refs(const void *ref1_
, const void *ref2_
)
7270 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7271 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7273 if (ref1
->tag
!= ref2
->tag
)
7274 return ref2
->tag
- ref1
->tag
;
7275 if (ref1
->ltag
!= ref2
->ltag
)
7276 return ref2
->ltag
- ref2
->ltag
;
7277 if (ref1
->head
!= ref2
->head
)
7278 return ref2
->head
- ref1
->head
;
7279 if (ref1
->tracked
!= ref2
->tracked
)
7280 return ref2
->tracked
- ref1
->tracked
;
7281 if (ref1
->remote
!= ref2
->remote
)
7282 return ref2
->remote
- ref1
->remote
;
7283 return strcmp(ref1
->name
, ref2
->name
);
7287 foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
)
7291 for (i
= 0; i
< refs_size
; i
++)
7292 if (!visitor(data
, refs
[i
]))
7302 static struct ref_list
*
7303 get_ref_list(const char *id
)
7305 struct ref_list
*list
;
7308 for (i
= 0; i
< ref_lists_size
; i
++)
7309 if (!strcmp(id
, ref_lists
[i
]->id
))
7310 return ref_lists
[i
];
7312 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7314 list
= calloc(1, sizeof(*list
));
7318 for (i
= 0; i
< refs_size
; i
++) {
7319 if (!strcmp(id
, refs
[i
]->id
) &&
7320 realloc_refs_list(&list
->refs
, list
->size
, 1))
7321 list
->refs
[list
->size
++] = refs
[i
];
7329 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7330 ref_lists
[ref_lists_size
++] = list
;
7335 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
7337 struct ref
*ref
= NULL
;
7340 bool remote
= FALSE
;
7341 bool tracked
= FALSE
;
7343 int from
= 0, to
= refs_size
- 1;
7345 if (!prefixcmp(name
, "refs/tags/")) {
7346 if (!suffixcmp(name
, namelen
, "^{}")) {
7354 namelen
-= STRING_SIZE("refs/tags/");
7355 name
+= STRING_SIZE("refs/tags/");
7357 } else if (!prefixcmp(name
, "refs/remotes/")) {
7359 namelen
-= STRING_SIZE("refs/remotes/");
7360 name
+= STRING_SIZE("refs/remotes/");
7361 tracked
= !strcmp(opt_remote
, name
);
7363 } else if (!prefixcmp(name
, "refs/heads/")) {
7364 namelen
-= STRING_SIZE("refs/heads/");
7365 name
+= STRING_SIZE("refs/heads/");
7366 if (!strncmp(opt_head
, name
, namelen
))
7369 } else if (!strcmp(name
, "HEAD")) {
7372 namelen
= strlen(opt_head
);
7377 /* If we are reloading or it's an annotated tag, replace the
7378 * previous SHA1 with the resolved commit id; relies on the fact
7379 * git-ls-remote lists the commit id of an annotated tag right
7380 * before the commit id it points to. */
7381 while (from
<= to
) {
7382 size_t pos
= (to
+ from
) / 2;
7383 int cmp
= strcmp(name
, refs
[pos
]->name
);
7397 if (!realloc_refs(&refs
, refs_size
, 1))
7399 ref
= calloc(1, sizeof(*ref
) + namelen
);
7402 memmove(refs
+ from
+ 1, refs
+ from
,
7403 (refs_size
- from
) * sizeof(*refs
));
7405 strncpy(ref
->name
, name
, namelen
);
7412 ref
->remote
= remote
;
7413 ref
->tracked
= tracked
;
7414 string_copy_rev(ref
->id
, id
);
7424 const char *head_argv
[] = {
7425 "git", "symbolic-ref", "HEAD", NULL
7427 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7428 "git", "ls-remote", opt_git_dir
, NULL
7430 static bool init
= FALSE
;
7434 if (!argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE"))
7435 die("TIG_LS_REMOTE contains too many arguments");
7442 if (io_run_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7443 !prefixcmp(opt_head
, "refs/heads/")) {
7444 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7446 memmove(opt_head
, offset
, strlen(offset
) + 1);
7450 for (i
= 0; i
< refs_size
; i
++)
7453 if (io_run_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7456 /* Update the ref lists to reflect changes. */
7457 for (i
= 0; i
< ref_lists_size
; i
++) {
7458 struct ref_list
*list
= ref_lists
[i
];
7461 for (old
= new = 0; old
< list
->size
; old
++)
7462 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7463 list
->refs
[new++] = list
->refs
[old
];
7471 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7473 if (!strcmp(name
, ".remote")) {
7474 string_ncopy(opt_remote
, value
, valuelen
);
7476 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7477 size_t from
= strlen(opt_remote
);
7479 if (!prefixcmp(value
, "refs/heads/"))
7480 value
+= STRING_SIZE("refs/heads/");
7482 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7488 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7490 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7491 int argc
= 1 + (cmd
== option_set_command
);
7494 if (!argv_from_string(argv
, &argc
, value
))
7495 config_msg
= "Too many option arguments";
7497 error
= cmd(argc
, argv
);
7500 warn("Option 'tig.%s': %s", name
, config_msg
);
7504 set_environment_variable(const char *name
, const char *value
)
7506 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7507 char *env
= malloc(len
);
7510 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7518 set_work_tree(const char *value
)
7520 char cwd
[SIZEOF_STR
];
7522 if (!getcwd(cwd
, sizeof(cwd
)))
7523 die("Failed to get cwd path: %s", strerror(errno
));
7524 if (chdir(opt_git_dir
) < 0)
7525 die("Failed to chdir(%s): %s", strerror(errno
));
7526 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7527 die("Failed to get git path: %s", strerror(errno
));
7529 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7530 if (chdir(value
) < 0)
7531 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7532 if (!getcwd(cwd
, sizeof(cwd
)))
7533 die("Failed to get cwd path: %s", strerror(errno
));
7534 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7535 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7536 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7537 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7538 opt_is_inside_work_tree
= TRUE
;
7542 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7544 if (!strcmp(name
, "i18n.commitencoding"))
7545 string_ncopy(opt_encoding
, value
, valuelen
);
7547 else if (!strcmp(name
, "core.editor"))
7548 string_ncopy(opt_editor
, value
, valuelen
);
7550 else if (!strcmp(name
, "core.worktree"))
7551 set_work_tree(value
);
7553 else if (!prefixcmp(name
, "tig.color."))
7554 set_repo_config_option(name
+ 10, value
, option_color_command
);
7556 else if (!prefixcmp(name
, "tig.bind."))
7557 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7559 else if (!prefixcmp(name
, "tig."))
7560 set_repo_config_option(name
+ 4, value
, option_set_command
);
7562 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7563 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7564 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7570 load_git_config(void)
7572 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7574 return io_run_load(config_list_argv
, "=", read_repo_config_option
);
7578 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7580 if (!opt_git_dir
[0]) {
7581 string_ncopy(opt_git_dir
, name
, namelen
);
7583 } else if (opt_is_inside_work_tree
== -1) {
7584 /* This can be 3 different values depending on the
7585 * version of git being used. If git-rev-parse does not
7586 * understand --is-inside-work-tree it will simply echo
7587 * the option else either "true" or "false" is printed.
7588 * Default to true for the unknown case. */
7589 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7591 } else if (*name
== '.') {
7592 string_ncopy(opt_cdup
, name
, namelen
);
7595 string_ncopy(opt_prefix
, name
, namelen
);
7602 load_repo_info(void)
7604 const char *rev_parse_argv
[] = {
7605 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7606 "--show-cdup", "--show-prefix", NULL
7609 return io_run_load(rev_parse_argv
, "=", read_repo_info
);
7617 static const char usage
[] =
7618 "tig " TIG_VERSION
" (" __DATE__
")\n"
7620 "Usage: tig [options] [revs] [--] [paths]\n"
7621 " or: tig show [options] [revs] [--] [paths]\n"
7622 " or: tig blame [rev] path\n"
7624 " or: tig < [git command output]\n"
7627 " -v, --version Show version and exit\n"
7628 " -h, --help Show help message and exit";
7630 static void __NORETURN
7633 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7639 static void __NORETURN
7640 die(const char *err
, ...)
7646 va_start(args
, err
);
7647 fputs("tig: ", stderr
);
7648 vfprintf(stderr
, err
, args
);
7649 fputs("\n", stderr
);
7656 warn(const char *msg
, ...)
7660 va_start(args
, msg
);
7661 fputs("tig warning: ", stderr
);
7662 vfprintf(stderr
, msg
, args
);
7663 fputs("\n", stderr
);
7668 parse_options(int argc
, const char *argv
[])
7670 enum request request
= REQ_VIEW_MAIN
;
7671 const char *subcommand
;
7672 bool seen_dashdash
= FALSE
;
7673 /* XXX: This is vulnerable to the user overriding options
7674 * required for the main view parser. */
7675 const char *custom_argv
[SIZEOF_ARG
] = {
7676 "git", "log", "--no-color", "--pretty=raw", "--parents",
7677 "--topo-order", NULL
7681 if (!isatty(STDIN_FILENO
)) {
7682 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7683 return REQ_VIEW_PAGER
;
7689 subcommand
= argv
[1];
7690 if (!strcmp(subcommand
, "status")) {
7692 warn("ignoring arguments after `%s'", subcommand
);
7693 return REQ_VIEW_STATUS
;
7695 } else if (!strcmp(subcommand
, "blame")) {
7696 if (argc
<= 2 || argc
> 4)
7697 die("invalid number of options to blame\n\n%s", usage
);
7701 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7705 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7706 return REQ_VIEW_BLAME
;
7708 } else if (!strcmp(subcommand
, "show")) {
7709 request
= REQ_VIEW_DIFF
;
7716 custom_argv
[1] = subcommand
;
7720 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7721 const char *opt
= argv
[i
];
7723 if (seen_dashdash
|| !strcmp(opt
, "--")) {
7724 seen_dashdash
= TRUE
;
7726 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7727 printf("tig version %s\n", TIG_VERSION
);
7730 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7731 printf("%s\n", usage
);
7735 custom_argv
[j
++] = opt
;
7736 if (j
>= ARRAY_SIZE(custom_argv
))
7737 die("command too long");
7740 if (!prepare_update(VIEW(request
), custom_argv
, NULL
))
7741 die("Failed to format arguments");
7747 main(int argc
, const char *argv
[])
7749 const char *codeset
= "UTF-8";
7750 enum request request
= parse_options(argc
, argv
);
7754 signal(SIGINT
, quit
);
7755 signal(SIGPIPE
, SIG_IGN
);
7757 if (setlocale(LC_ALL
, "")) {
7758 codeset
= nl_langinfo(CODESET
);
7761 if (load_repo_info() == ERR
)
7762 die("Failed to load repo info.");
7764 if (load_options() == ERR
)
7765 die("Failed to load user config.");
7767 if (load_git_config() == ERR
)
7768 die("Failed to load repo config.");
7770 /* Require a git repository unless when running in pager mode. */
7771 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7772 die("Not a git repository");
7774 if (*opt_encoding
&& strcmp(codeset
, "UTF-8")) {
7775 opt_iconv_in
= iconv_open("UTF-8", opt_encoding
);
7776 if (opt_iconv_in
== ICONV_NONE
)
7777 die("Failed to initialize character set conversion");
7780 if (codeset
&& strcmp(codeset
, "UTF-8")) {
7781 opt_iconv_out
= iconv_open(codeset
, "UTF-8");
7782 if (opt_iconv_out
== ICONV_NONE
)
7783 die("Failed to initialize character set conversion");
7786 if (load_refs() == ERR
)
7787 die("Failed to load refs.");
7789 foreach_view (view
, i
)
7790 if (!argv_from_env(view
->ops
->argv
, view
->cmd_env
))
7791 die("Too many arguments in the `%s` environment variable",
7796 if (request
!= REQ_NONE
)
7797 open_view(NULL
, request
, OPEN_PREPARED
);
7798 request
= request
== REQ_NONE
? REQ_VIEW_MAIN
: REQ_NONE
;
7800 while (view_driver(display
[current_view
], request
)) {
7801 int key
= get_input(0);
7803 view
= display
[current_view
];
7804 request
= get_keybinding(view
->keymap
, key
);
7806 /* Some low-level request handling. This keeps access to
7807 * status_win restricted. */
7811 char *cmd
= read_prompt(":");
7813 if (cmd
&& isdigit(*cmd
)) {
7814 int lineno
= view
->lineno
+ 1;
7816 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7817 select_view_line(view
, lineno
- 1);
7820 report("Unable to parse '%s' as a line number", cmd
);
7824 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7825 const char *argv
[SIZEOF_ARG
] = { "git" };
7828 /* When running random commands, initially show the
7829 * command in the title. However, it maybe later be
7830 * overwritten if a commit line is selected. */
7831 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7833 if (!argv_from_string(argv
, &argc
, cmd
)) {
7834 report("Too many arguments");
7835 } else if (!prepare_update(next
, argv
, NULL
)) {
7836 report("Failed to format command");
7838 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7846 case REQ_SEARCH_BACK
:
7848 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7849 char *search
= read_prompt(prompt
);
7852 string_ncopy(opt_search
, search
, strlen(search
));
7853 else if (*opt_search
)
7854 request
= request
== REQ_SEARCH
?