1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
37 #include <sys/select.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
63 #define __NORETURN __attribute__((__noreturn__))
68 static void __NORETURN
die(const char *err
, ...);
69 static void warn(const char *msg
, ...);
70 static void report(const char *msg
, ...);
72 #define ABS(x) ((x) >= 0 ? (x) : -(x))
73 #define MIN(x, y) ((x) < (y) ? (x) : (y))
74 #define MAX(x, y) ((x) > (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
99 #define ICONV_CONST /* nothing */
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
108 #define AUTHOR_COLS 19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
118 #define KEY_RETURN '\r'
123 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
124 unsigned int head
:1; /* Is it the current HEAD? */
125 unsigned int tag
:1; /* Is it a tag? */
126 unsigned int ltag
:1; /* If so, is the tag local? */
127 unsigned int remote
:1; /* Is it a remote ref? */
128 unsigned int tracked
:1; /* Is it the remote for the current HEAD? */
129 char name
[1]; /* Ref name; tag or head names are shortened. */
133 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
134 size_t size
; /* Number of refs. */
135 struct ref
**refs
; /* References for this ID. */
138 static struct ref
*get_ref_head();
139 static struct ref_list
*get_ref_list(const char *id
);
140 static void foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
);
141 static int load_refs(void);
150 typedef enum input_status (*input_handler
)(void *data
, char *buf
, int c
);
152 static char *prompt_input(const char *prompt
, input_handler handler
, void *data
);
153 static bool prompt_yesno(const char *prompt
);
161 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
);
164 * Allocation helpers ... Entering macro hell to never be seen again.
167 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
169 name(type **mem, size_t size, size_t increase) \
171 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
172 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
175 if (mem == NULL || num_chunks != num_chunks_new) { \
176 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
189 string_ncopy_do(char *dst
, size_t dstlen
, const char *src
, size_t srclen
)
191 if (srclen
> dstlen
- 1)
194 strncpy(dst
, src
, srclen
);
198 /* Shorthands for safely copying into a fixed buffer. */
200 #define string_copy(dst, src) \
201 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
203 #define string_ncopy(dst, src, srclen) \
204 string_ncopy_do(dst, sizeof(dst), src, srclen)
206 #define string_copy_rev(dst, src) \
207 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
209 #define string_add(dst, from, src) \
210 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 string_expand(char *dst
, size_t dstlen
, const char *src
, int tabsize
)
217 for (size
= pos
= 0; size
< dstlen
- 1 && src
[pos
]; pos
++) {
218 if (src
[pos
] == '\t') {
219 size_t expanded
= tabsize
- (size
% tabsize
);
221 if (expanded
+ size
>= dstlen
- 1)
222 expanded
= dstlen
- size
- 1;
223 memcpy(dst
+ size
, " ", expanded
);
226 dst
[size
++] = src
[pos
];
234 chomp_string(char *name
)
238 while (isspace(*name
))
241 namelen
= strlen(name
) - 1;
242 while (namelen
> 0 && isspace(name
[namelen
]))
249 string_nformat(char *buf
, size_t bufsize
, size_t *bufpos
, const char *fmt
, ...)
252 size_t pos
= bufpos
? *bufpos
: 0;
255 pos
+= vsnprintf(buf
+ pos
, bufsize
- pos
, fmt
, args
);
261 return pos
>= bufsize
? FALSE
: TRUE
;
264 #define string_format(buf, fmt, args...) \
265 string_nformat(buf, sizeof(buf), NULL, fmt, args)
267 #define string_format_from(buf, from, fmt, args...) \
268 string_nformat(buf, sizeof(buf), from, fmt, args)
271 string_enum_compare(const char *str1
, const char *str2
, int len
)
275 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
277 /* Diff-Header == DIFF_HEADER */
278 for (i
= 0; i
< len
; i
++) {
279 if (toupper(str1
[i
]) == toupper(str2
[i
]))
282 if (string_enum_sep(str1
[i
]) &&
283 string_enum_sep(str2
[i
]))
286 return str1
[i
] - str2
[i
];
292 #define enum_equals(entry, str, len) \
293 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
301 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
304 enum_map_name(const char *name
, size_t namelen
)
306 static char buf
[SIZEOF_STR
];
309 for (bufpos
= 0; bufpos
<= namelen
; bufpos
++) {
310 buf
[bufpos
] = tolower(name
[bufpos
]);
311 if (buf
[bufpos
] == '_')
319 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
322 map_enum_do(const struct enum_map
*map
, size_t map_size
, int *value
, const char *name
)
324 size_t namelen
= strlen(name
);
327 for (i
= 0; i
< map_size
; i
++)
328 if (enum_equals(map
[i
], name
, namelen
)) {
329 *value
= map
[i
].value
;
336 #define map_enum(attr, map, name) \
337 map_enum_do(map, ARRAY_SIZE(map), attr, name)
339 #define prefixcmp(str1, str2) \
340 strncmp(str1, str2, STRING_SIZE(str2))
343 suffixcmp(const char *str
, int slen
, const char *suffix
)
345 size_t len
= slen
>= 0 ? slen
: strlen(str
);
346 size_t suffixlen
= strlen(suffix
);
348 return suffixlen
< len
? strcmp(str
+ len
- suffixlen
, suffix
) : -1;
353 * Unicode / UTF-8 handling
355 * NOTE: Much of the following code for dealing with Unicode is derived from
356 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
357 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
361 unicode_width(unsigned long c
, int tab_size
)
364 (c
<= 0x115f /* Hangul Jamo */
367 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
369 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
370 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
371 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
372 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
373 || (c
>= 0xffe0 && c
<= 0xffe6)
374 || (c
>= 0x20000 && c
<= 0x2fffd)
375 || (c
>= 0x30000 && c
<= 0x3fffd)))
384 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
385 * Illegal bytes are set one. */
386 static const unsigned char utf8_bytes
[256] = {
387 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,
388 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
389 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
390 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
391 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
392 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
393 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,
394 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,
397 static inline unsigned char
398 utf8_char_length(const char *string
, const char *end
)
400 int c
= *(unsigned char *) string
;
402 return utf8_bytes
[c
];
405 /* Decode UTF-8 multi-byte representation into a Unicode character. */
406 static inline unsigned long
407 utf8_to_unicode(const char *string
, size_t length
)
409 unsigned long unicode
;
416 unicode
= (string
[0] & 0x1f) << 6;
417 unicode
+= (string
[1] & 0x3f);
420 unicode
= (string
[0] & 0x0f) << 12;
421 unicode
+= ((string
[1] & 0x3f) << 6);
422 unicode
+= (string
[2] & 0x3f);
425 unicode
= (string
[0] & 0x0f) << 18;
426 unicode
+= ((string
[1] & 0x3f) << 12);
427 unicode
+= ((string
[2] & 0x3f) << 6);
428 unicode
+= (string
[3] & 0x3f);
431 unicode
= (string
[0] & 0x0f) << 24;
432 unicode
+= ((string
[1] & 0x3f) << 18);
433 unicode
+= ((string
[2] & 0x3f) << 12);
434 unicode
+= ((string
[3] & 0x3f) << 6);
435 unicode
+= (string
[4] & 0x3f);
438 unicode
= (string
[0] & 0x01) << 30;
439 unicode
+= ((string
[1] & 0x3f) << 24);
440 unicode
+= ((string
[2] & 0x3f) << 18);
441 unicode
+= ((string
[3] & 0x3f) << 12);
442 unicode
+= ((string
[4] & 0x3f) << 6);
443 unicode
+= (string
[5] & 0x3f);
449 /* Invalid characters could return the special 0xfffd value but NUL
450 * should be just as good. */
451 return unicode
> 0xffff ? 0 : unicode
;
454 /* Calculates how much of string can be shown within the given maximum width
455 * and sets trimmed parameter to non-zero value if all of string could not be
456 * shown. If the reserve flag is TRUE, it will reserve at least one
457 * trailing character, which can be useful when drawing a delimiter.
459 * Returns the number of bytes to output from string to satisfy max_width. */
461 utf8_length(const char **start
, size_t skip
, int *width
, size_t max_width
, int *trimmed
, bool reserve
, int tab_size
)
463 const char *string
= *start
;
464 const char *end
= strchr(string
, '\0');
465 unsigned char last_bytes
= 0;
466 size_t last_ucwidth
= 0;
471 while (string
< end
) {
472 unsigned char bytes
= utf8_char_length(string
, end
);
474 unsigned long unicode
;
476 if (string
+ bytes
> end
)
479 /* Change representation to figure out whether
480 * it is a single- or double-width character. */
482 unicode
= utf8_to_unicode(string
, bytes
);
483 /* FIXME: Graceful handling of invalid Unicode character. */
487 ucwidth
= unicode_width(unicode
, tab_size
);
489 skip
-= ucwidth
<= skip
? ucwidth
: skip
;
493 if (*width
> max_width
) {
496 if (reserve
&& *width
== max_width
) {
497 string
-= last_bytes
;
498 *width
-= last_ucwidth
;
504 last_bytes
= ucwidth
? bytes
: 0;
505 last_ucwidth
= ucwidth
;
508 return string
- *start
;
520 #define DATE_(name) DATE_##name
525 static const struct enum_map date_map
[] = {
526 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
536 static inline int timecmp(const struct time
*t1
, const struct time
*t2
)
538 return t1
->sec
- t2
->sec
;
542 mkdate(const struct time
*time
, enum date date
)
544 static char buf
[DATE_COLS
+ 1];
545 static const struct enum_map reldate
[] = {
546 { "second", 1, 60 * 2 },
547 { "minute", 60, 60 * 60 * 2 },
548 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
549 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
550 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
551 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
555 if (!date
|| !time
|| !time
->sec
)
558 if (date
== DATE_RELATIVE
) {
560 time_t date
= time
->sec
+ time
->tz
;
564 gettimeofday(&now
, NULL
);
565 seconds
= now
.tv_sec
< date
? date
- now
.tv_sec
: now
.tv_sec
- date
;
566 for (i
= 0; i
< ARRAY_SIZE(reldate
); i
++) {
567 if (seconds
>= reldate
[i
].value
)
570 seconds
/= reldate
[i
].namelen
;
571 if (!string_format(buf
, "%ld %s%s %s",
572 seconds
, reldate
[i
].name
,
573 seconds
> 1 ? "s" : "",
574 now
.tv_sec
>= date
? "ago" : "ahead"))
580 if (date
== DATE_LOCAL
) {
581 time_t date
= time
->sec
+ time
->tz
;
582 localtime_r(&date
, &tm
);
585 gmtime_r(&time
->sec
, &tm
);
587 return strftime(buf
, sizeof(buf
), DATE_FORMAT
, &tm
) ? buf
: NULL
;
591 #define AUTHOR_VALUES \
597 #define AUTHOR_(name) AUTHOR_##name
600 AUTHOR_DEFAULT
= AUTHOR_FULL
603 static const struct enum_map author_map
[] = {
604 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
610 get_author_initials(const char *author
)
612 static char initials
[AUTHOR_COLS
* 6 + 1];
614 const char *end
= strchr(author
, '\0');
616 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
618 memset(initials
, 0, sizeof(initials
));
619 while (author
< end
) {
623 while (is_initial_sep(*author
))
626 bytes
= utf8_char_length(author
, end
);
627 if (bytes
< sizeof(initials
) - 1 - pos
) {
629 initials
[pos
++] = *author
++;
633 for (i
= pos
; author
< end
&& !is_initial_sep(*author
); author
++) {
634 if (i
< sizeof(initials
) - 1)
635 initials
[i
++] = *author
;
646 argv_from_string(const char *argv
[SIZEOF_ARG
], int *argc
, char *cmd
)
650 while (*cmd
&& *argc
< SIZEOF_ARG
&& (valuelen
= strcspn(cmd
, " \t"))) {
651 bool advance
= cmd
[valuelen
] != 0;
654 argv
[(*argc
)++] = chomp_string(cmd
);
655 cmd
= chomp_string(cmd
+ valuelen
+ advance
);
658 if (*argc
< SIZEOF_ARG
)
660 return *argc
< SIZEOF_ARG
;
664 argv_from_env(const char **argv
, const char *name
)
666 char *env
= argv
? getenv(name
) : NULL
;
671 return !env
|| argv_from_string(argv
, &argc
, env
);
675 argv_free(const char *argv
[])
681 for (argc
= 0; argv
[argc
]; argc
++)
682 free((void *) argv
[argc
]);
686 DEFINE_ALLOCATOR(argv_realloc
, const char *, SIZEOF_ARG
)
689 argv_append(const char ***argv
, const char *arg
)
693 while (*argv
&& (*argv
)[argc
])
696 if (!argv_realloc(argv
, argc
, 2))
699 (*argv
)[argc
++] = strdup(arg
);
700 (*argv
)[argc
] = NULL
;
705 argv_copy(const char ***dst
, const char *src
[])
709 for (argc
= 0; src
[argc
]; argc
++)
710 if (!argv_append(dst
, src
[argc
]))
717 * Executing external commands.
721 IO_FD
, /* File descriptor based IO. */
722 IO_BG
, /* Execute command in the background. */
723 IO_FG
, /* Execute command with same std{in,out,err}. */
724 IO_RD
, /* Read only fork+exec IO. */
725 IO_WR
, /* Write only fork+exec IO. */
726 IO_AP
, /* Append fork+exec output to file. */
730 int pipe
; /* Pipe end for reading or writing. */
731 pid_t pid
; /* PID of spawned process. */
732 int error
; /* Error status. */
733 char *buf
; /* Read buffer. */
734 size_t bufalloc
; /* Allocated buffer size. */
735 size_t bufsize
; /* Buffer content size. */
736 char *bufpos
; /* Current buffer position. */
737 unsigned int eof
:1; /* Has end of file been reached. */
741 io_init(struct io
*io
)
743 memset(io
, 0, sizeof(*io
));
748 io_open(struct io
*io
, const char *fmt
, ...)
750 char name
[SIZEOF_STR
] = "";
757 fits
= vsnprintf(name
, sizeof(name
), fmt
, args
) < sizeof(name
);
761 io
->error
= ENAMETOOLONG
;
764 io
->pipe
= *name
? open(name
, O_RDONLY
) : STDIN_FILENO
;
767 return io
->pipe
!= -1;
771 io_kill(struct io
*io
)
773 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
777 io_done(struct io
*io
)
788 pid_t waiting
= waitpid(pid
, &status
, 0);
797 return waiting
== pid
&&
798 !WIFSIGNALED(status
) &&
800 !WEXITSTATUS(status
);
807 io_run(struct io
*io
, enum io_type type
, const char *dir
, const char *argv
[], ...)
809 int pipefds
[2] = { -1, -1 };
814 if ((type
== IO_RD
|| type
== IO_WR
) && pipe(pipefds
) < 0) {
817 } else if (type
== IO_AP
) {
818 va_start(args
, argv
);
819 pipefds
[1] = va_arg(args
, int);
823 if ((io
->pid
= fork())) {
826 if (pipefds
[!(type
== IO_WR
)] != -1)
827 close(pipefds
[!(type
== IO_WR
)]);
829 io
->pipe
= pipefds
[!!(type
== IO_WR
)];
835 int devnull
= open("/dev/null", O_RDWR
);
836 int readfd
= type
== IO_WR
? pipefds
[0] : devnull
;
837 int writefd
= (type
== IO_RD
|| type
== IO_AP
)
838 ? pipefds
[1] : devnull
;
840 dup2(readfd
, STDIN_FILENO
);
841 dup2(writefd
, STDOUT_FILENO
);
842 dup2(devnull
, STDERR_FILENO
);
845 if (pipefds
[0] != -1)
847 if (pipefds
[1] != -1)
851 if (dir
&& *dir
&& chdir(dir
) == -1)
854 execvp(argv
[0], (char *const*) argv
);
858 if (pipefds
[!!(type
== IO_WR
)] != -1)
859 close(pipefds
[!!(type
== IO_WR
)]);
864 io_complete(enum io_type type
, const char **argv
, const char *dir
, int fd
)
868 return io_run(&io
, type
, dir
, argv
, fd
) && io_done(&io
);
872 io_run_bg(const char **argv
)
874 return io_complete(IO_BG
, argv
, NULL
, -1);
878 io_run_fg(const char **argv
, const char *dir
)
880 return io_complete(IO_FG
, argv
, dir
, -1);
884 io_run_append(const char **argv
, int fd
)
886 return io_complete(IO_AP
, argv
, NULL
, fd
);
890 io_eof(struct io
*io
)
896 io_error(struct io
*io
)
902 io_strerror(struct io
*io
)
904 return strerror(io
->error
);
908 io_can_read(struct io
*io
)
910 struct timeval tv
= { 0, 500 };
914 FD_SET(io
->pipe
, &fds
);
916 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
920 io_read(struct io
*io
, void *buf
, size_t bufsize
)
923 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
925 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
927 else if (readsize
== -1)
929 else if (readsize
== 0)
935 DEFINE_ALLOCATOR(io_realloc_buf
, char, BUFSIZ
)
938 io_get(struct io
*io
, int c
, bool can_read
)
944 if (io
->bufsize
> 0) {
945 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
947 char *line
= io
->bufpos
;
950 io
->bufpos
= eol
+ 1;
951 io
->bufsize
-= io
->bufpos
- line
;
958 io
->bufpos
[io
->bufsize
] = 0;
968 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
969 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
971 if (io
->bufalloc
== io
->bufsize
) {
972 if (!io_realloc_buf(&io
->buf
, io
->bufalloc
, BUFSIZ
))
974 io
->bufalloc
+= BUFSIZ
;
977 io
->bufpos
= io
->buf
;
978 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
981 io
->bufsize
+= readsize
;
986 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
990 while (!io_error(io
) && written
< bufsize
) {
993 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
994 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
1002 return written
== bufsize
;
1006 io_read_buf(struct io
*io
, char buf
[], size_t bufsize
)
1008 char *result
= io_get(io
, '\n', TRUE
);
1011 result
= chomp_string(result
);
1012 string_ncopy_do(buf
, bufsize
, result
, strlen(result
));
1015 return io_done(io
) && result
;
1019 io_run_buf(const char **argv
, char buf
[], size_t bufsize
)
1023 return io_run(&io
, IO_RD
, NULL
, argv
) && io_read_buf(&io
, buf
, bufsize
);
1027 io_load(struct io
*io
, const char *separators
,
1028 int (*read_property
)(char *, size_t, char *, size_t))
1033 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
1038 name
= chomp_string(name
);
1039 namelen
= strcspn(name
, separators
);
1041 if (name
[namelen
]) {
1043 value
= chomp_string(name
+ namelen
+ 1);
1044 valuelen
= strlen(value
);
1051 state
= read_property(name
, namelen
, value
, valuelen
);
1054 if (state
!= ERR
&& io_error(io
))
1062 io_run_load(const char **argv
, const char *separators
,
1063 int (*read_property
)(char *, size_t, char *, size_t))
1067 if (!io_run(&io
, IO_RD
, NULL
, argv
))
1069 return io_load(&io
, separators
, read_property
);
1078 /* XXX: Keep the view request first and in sync with views[]. */ \
1079 REQ_GROUP("View switching") \
1080 REQ_(VIEW_MAIN, "Show main view"), \
1081 REQ_(VIEW_DIFF, "Show diff view"), \
1082 REQ_(VIEW_LOG, "Show log view"), \
1083 REQ_(VIEW_TREE, "Show tree view"), \
1084 REQ_(VIEW_BLOB, "Show blob view"), \
1085 REQ_(VIEW_BLAME, "Show blame view"), \
1086 REQ_(VIEW_BRANCH, "Show branch view"), \
1087 REQ_(VIEW_HELP, "Show help page"), \
1088 REQ_(VIEW_PAGER, "Show pager view"), \
1089 REQ_(VIEW_STATUS, "Show status view"), \
1090 REQ_(VIEW_STAGE, "Show stage view"), \
1092 REQ_GROUP("View manipulation") \
1093 REQ_(ENTER, "Enter current line and scroll"), \
1094 REQ_(NEXT, "Move to next"), \
1095 REQ_(PREVIOUS, "Move to previous"), \
1096 REQ_(PARENT, "Move to parent"), \
1097 REQ_(VIEW_NEXT, "Move focus to next view"), \
1098 REQ_(REFRESH, "Reload and refresh"), \
1099 REQ_(MAXIMIZE, "Maximize the current view"), \
1100 REQ_(VIEW_CLOSE, "Close the current view"), \
1101 REQ_(QUIT, "Close all views and quit"), \
1103 REQ_GROUP("View specific requests") \
1104 REQ_(STATUS_UPDATE, "Update file status"), \
1105 REQ_(STATUS_REVERT, "Revert file changes"), \
1106 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1107 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1109 REQ_GROUP("Cursor navigation") \
1110 REQ_(MOVE_UP, "Move cursor one line up"), \
1111 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1112 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1113 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1114 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1115 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1117 REQ_GROUP("Scrolling") \
1118 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1119 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1120 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1121 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1122 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1123 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1125 REQ_GROUP("Searching") \
1126 REQ_(SEARCH, "Search the view"), \
1127 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1128 REQ_(FIND_NEXT, "Find next search match"), \
1129 REQ_(FIND_PREV, "Find previous search match"), \
1131 REQ_GROUP("Option manipulation") \
1132 REQ_(OPTIONS, "Open option menu"), \
1133 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1134 REQ_(TOGGLE_DATE, "Toggle date display"), \
1135 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1136 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1137 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1138 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1139 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1140 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1143 REQ_(PROMPT, "Bring up the prompt"), \
1144 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1145 REQ_(SHOW_VERSION, "Show version information"), \
1146 REQ_(STOP_LOADING, "Stop all loading views"), \
1147 REQ_(EDIT, "Open in editor"), \
1148 REQ_(NONE, "Do nothing")
1151 /* User action requests. */
1153 #define REQ_GROUP(help)
1154 #define REQ_(req, help) REQ_##req
1156 /* Offset all requests to avoid conflicts with ncurses getch values. */
1157 REQ_UNKNOWN
= KEY_MAX
+ 1,
1165 struct request_info
{
1166 enum request request
;
1172 static const struct request_info req_info
[] = {
1173 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1174 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1181 get_request(const char *name
)
1183 int namelen
= strlen(name
);
1186 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
1187 if (enum_equals(req_info
[i
], name
, namelen
))
1188 return req_info
[i
].request
;
1198 /* Option and state variables. */
1199 static enum date opt_date
= DATE_DEFAULT
;
1200 static enum author opt_author
= AUTHOR_DEFAULT
;
1201 static bool opt_line_number
= FALSE
;
1202 static bool opt_line_graphics
= TRUE
;
1203 static bool opt_rev_graph
= FALSE
;
1204 static bool opt_show_refs
= TRUE
;
1205 static int opt_num_interval
= 5;
1206 static double opt_hscroll
= 0.50;
1207 static double opt_scale_split_view
= 2.0 / 3.0;
1208 static int opt_tab_size
= 8;
1209 static int opt_author_cols
= AUTHOR_COLS
;
1210 static char opt_path
[SIZEOF_STR
] = "";
1211 static char opt_file
[SIZEOF_STR
] = "";
1212 static char opt_ref
[SIZEOF_REF
] = "";
1213 static char opt_head
[SIZEOF_REF
] = "";
1214 static char opt_remote
[SIZEOF_REF
] = "";
1215 static char opt_encoding
[20] = "UTF-8";
1216 static iconv_t opt_iconv_in
= ICONV_NONE
;
1217 static iconv_t opt_iconv_out
= ICONV_NONE
;
1218 static char opt_search
[SIZEOF_STR
] = "";
1219 static char opt_cdup
[SIZEOF_STR
] = "";
1220 static char opt_prefix
[SIZEOF_STR
] = "";
1221 static char opt_git_dir
[SIZEOF_STR
] = "";
1222 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
1223 static char opt_editor
[SIZEOF_STR
] = "";
1224 static FILE *opt_tty
= NULL
;
1226 #define is_initial_commit() (!get_ref_head())
1227 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1231 * Line-oriented content detection.
1235 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1236 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1237 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1238 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1239 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1240 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1241 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1242 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1243 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1244 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1245 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1246 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1247 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1248 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1249 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1250 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1251 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1252 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1253 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1254 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1256 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1257 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1258 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1259 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1260 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1261 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1262 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1263 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1264 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1266 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1267 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1268 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1269 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1270 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1271 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1272 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1273 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1274 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1275 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1276 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1277 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1279 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1280 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1281 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1282 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1283 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1284 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1285 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1286 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1287 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1288 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1289 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1290 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1291 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1292 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1293 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1296 #define LINE(type, line, fg, bg, attr) \
1304 const char *name
; /* Option name. */
1305 int namelen
; /* Size of option name. */
1306 const char *line
; /* The start of line to match. */
1307 int linelen
; /* Size of string to match. */
1308 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1311 static struct line_info line_info
[] = {
1312 #define LINE(type, line, fg, bg, attr) \
1313 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1318 static enum line_type
1319 get_line_type(const char *line
)
1321 int linelen
= strlen(line
);
1322 enum line_type type
;
1324 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1325 /* Case insensitive search matches Signed-off-by lines better. */
1326 if (linelen
>= line_info
[type
].linelen
&&
1327 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1330 return LINE_DEFAULT
;
1334 get_line_attr(enum line_type type
)
1336 assert(type
< ARRAY_SIZE(line_info
));
1337 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1340 static struct line_info
*
1341 get_line_info(const char *name
)
1343 size_t namelen
= strlen(name
);
1344 enum line_type type
;
1346 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1347 if (enum_equals(line_info
[type
], name
, namelen
))
1348 return &line_info
[type
];
1356 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1357 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1358 enum line_type type
;
1362 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1363 default_bg
= COLOR_BLACK
;
1364 default_fg
= COLOR_WHITE
;
1367 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1368 struct line_info
*info
= &line_info
[type
];
1369 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1370 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1372 init_pair(type
, fg
, bg
);
1377 enum line_type type
;
1380 unsigned int selected
:1;
1381 unsigned int dirty
:1;
1382 unsigned int cleareol
:1;
1383 unsigned int other
:16;
1385 void *data
; /* User data */
1395 enum request request
;
1398 static struct keybinding default_keybindings
[] = {
1399 /* View switching */
1400 { 'm', REQ_VIEW_MAIN
},
1401 { 'd', REQ_VIEW_DIFF
},
1402 { 'l', REQ_VIEW_LOG
},
1403 { 't', REQ_VIEW_TREE
},
1404 { 'f', REQ_VIEW_BLOB
},
1405 { 'B', REQ_VIEW_BLAME
},
1406 { 'H', REQ_VIEW_BRANCH
},
1407 { 'p', REQ_VIEW_PAGER
},
1408 { 'h', REQ_VIEW_HELP
},
1409 { 'S', REQ_VIEW_STATUS
},
1410 { 'c', REQ_VIEW_STAGE
},
1412 /* View manipulation */
1413 { 'q', REQ_VIEW_CLOSE
},
1414 { KEY_TAB
, REQ_VIEW_NEXT
},
1415 { KEY_RETURN
, REQ_ENTER
},
1416 { KEY_UP
, REQ_PREVIOUS
},
1417 { KEY_DOWN
, REQ_NEXT
},
1418 { 'R', REQ_REFRESH
},
1419 { KEY_F(5), REQ_REFRESH
},
1420 { 'O', REQ_MAXIMIZE
},
1422 /* Cursor navigation */
1423 { 'k', REQ_MOVE_UP
},
1424 { 'j', REQ_MOVE_DOWN
},
1425 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1426 { KEY_END
, REQ_MOVE_LAST_LINE
},
1427 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1428 { ' ', REQ_MOVE_PAGE_DOWN
},
1429 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1430 { 'b', REQ_MOVE_PAGE_UP
},
1431 { '-', REQ_MOVE_PAGE_UP
},
1434 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1435 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1436 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1437 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1438 { 'w', REQ_SCROLL_PAGE_UP
},
1439 { 's', REQ_SCROLL_PAGE_DOWN
},
1442 { '/', REQ_SEARCH
},
1443 { '?', REQ_SEARCH_BACK
},
1444 { 'n', REQ_FIND_NEXT
},
1445 { 'N', REQ_FIND_PREV
},
1449 { 'z', REQ_STOP_LOADING
},
1450 { 'v', REQ_SHOW_VERSION
},
1451 { 'r', REQ_SCREEN_REDRAW
},
1452 { 'o', REQ_OPTIONS
},
1453 { '.', REQ_TOGGLE_LINENO
},
1454 { 'D', REQ_TOGGLE_DATE
},
1455 { 'A', REQ_TOGGLE_AUTHOR
},
1456 { 'g', REQ_TOGGLE_REV_GRAPH
},
1457 { 'F', REQ_TOGGLE_REFS
},
1458 { 'I', REQ_TOGGLE_SORT_ORDER
},
1459 { 'i', REQ_TOGGLE_SORT_FIELD
},
1460 { ':', REQ_PROMPT
},
1461 { 'u', REQ_STATUS_UPDATE
},
1462 { '!', REQ_STATUS_REVERT
},
1463 { 'M', REQ_STATUS_MERGE
},
1464 { '@', REQ_STAGE_NEXT
},
1465 { ',', REQ_PARENT
},
1469 #define KEYMAP_INFO \
1484 #define KEYMAP_(name) KEYMAP_##name
1489 static const struct enum_map keymap_table
[] = {
1490 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1495 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1497 struct keybinding_table
{
1498 struct keybinding
*data
;
1502 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1505 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1507 struct keybinding_table
*table
= &keybindings
[keymap
];
1510 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1511 if (keybindings
[keymap
].data
[i
].alias
== key
) {
1512 keybindings
[keymap
].data
[i
].request
= request
;
1517 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1519 die("Failed to allocate keybinding");
1520 table
->data
[table
->size
].alias
= key
;
1521 table
->data
[table
->size
++].request
= request
;
1523 if (request
== REQ_NONE
&& keymap
== KEYMAP_GENERIC
) {
1526 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1527 if (default_keybindings
[i
].alias
== key
)
1528 default_keybindings
[i
].request
= REQ_NONE
;
1532 /* Looks for a key binding first in the given map, then in the generic map, and
1533 * lastly in the default keybindings. */
1535 get_keybinding(enum keymap keymap
, int key
)
1539 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1540 if (keybindings
[keymap
].data
[i
].alias
== key
)
1541 return keybindings
[keymap
].data
[i
].request
;
1543 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1544 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1545 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1547 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1548 if (default_keybindings
[i
].alias
== key
)
1549 return default_keybindings
[i
].request
;
1551 return (enum request
) key
;
1560 static const struct key key_table
[] = {
1561 { "Enter", KEY_RETURN
},
1563 { "Backspace", KEY_BACKSPACE
},
1565 { "Escape", KEY_ESC
},
1566 { "Left", KEY_LEFT
},
1567 { "Right", KEY_RIGHT
},
1569 { "Down", KEY_DOWN
},
1570 { "Insert", KEY_IC
},
1571 { "Delete", KEY_DC
},
1573 { "Home", KEY_HOME
},
1575 { "PageUp", KEY_PPAGE
},
1576 { "PageDown", KEY_NPAGE
},
1586 { "F10", KEY_F(10) },
1587 { "F11", KEY_F(11) },
1588 { "F12", KEY_F(12) },
1592 get_key_value(const char *name
)
1596 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1597 if (!strcasecmp(key_table
[i
].name
, name
))
1598 return key_table
[i
].value
;
1600 if (strlen(name
) == 1 && isprint(*name
))
1607 get_key_name(int key_value
)
1609 static char key_char
[] = "'X'";
1610 const char *seq
= NULL
;
1613 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1614 if (key_table
[key
].value
== key_value
)
1615 seq
= key_table
[key
].name
;
1619 isprint(key_value
)) {
1620 key_char
[1] = (char) key_value
;
1624 return seq
? seq
: "(no key)";
1628 append_key(char *buf
, size_t *pos
, const struct keybinding
*keybinding
)
1630 const char *sep
= *pos
> 0 ? ", " : "";
1631 const char *keyname
= get_key_name(keybinding
->alias
);
1633 return string_nformat(buf
, BUFSIZ
, pos
, "%s%s", sep
, keyname
);
1637 append_keymap_request_keys(char *buf
, size_t *pos
, enum request request
,
1638 enum keymap keymap
, bool all
)
1642 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1643 if (keybindings
[keymap
].data
[i
].request
== request
) {
1644 if (!append_key(buf
, pos
, &keybindings
[keymap
].data
[i
]))
1654 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1657 get_keys(enum keymap keymap
, enum request request
, bool all
)
1659 static char buf
[BUFSIZ
];
1665 if (!append_keymap_request_keys(buf
, &pos
, request
, keymap
, all
))
1666 return "Too many keybindings!";
1667 if (pos
> 0 && !all
)
1670 if (keymap
!= KEYMAP_GENERIC
) {
1671 /* Only the generic keymap includes the default keybindings when
1672 * listing all keys. */
1676 if (!append_keymap_request_keys(buf
, &pos
, request
, KEYMAP_GENERIC
, all
))
1677 return "Too many keybindings!";
1682 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1683 if (default_keybindings
[i
].request
== request
) {
1684 if (!append_key(buf
, &pos
, &default_keybindings
[i
]))
1685 return "Too many keybindings!";
1694 struct run_request
{
1700 static struct run_request
*run_request
;
1701 static size_t run_requests
;
1703 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1706 add_run_request(enum keymap keymap
, int key
, const char **argv
)
1708 struct run_request
*req
;
1710 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1713 req
= &run_request
[run_requests
];
1714 req
->keymap
= keymap
;
1718 if (!argv_copy(&req
->argv
, argv
))
1721 return REQ_NONE
+ ++run_requests
;
1724 static struct run_request
*
1725 get_run_request(enum request request
)
1727 if (request
<= REQ_NONE
)
1729 return &run_request
[request
- REQ_NONE
- 1];
1733 add_builtin_run_requests(void)
1735 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1736 const char *checkout
[] = { "git", "checkout", "%(branch)", NULL
};
1737 const char *commit
[] = { "git", "commit", NULL
};
1738 const char *gc
[] = { "git", "gc", NULL
};
1739 struct run_request reqs
[] = {
1740 { KEYMAP_MAIN
, 'C', cherry_pick
},
1741 { KEYMAP_STATUS
, 'C', commit
},
1742 { KEYMAP_BRANCH
, 'C', checkout
},
1743 { KEYMAP_GENERIC
, 'G', gc
},
1747 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1748 enum request req
= get_keybinding(reqs
[i
].keymap
, reqs
[i
].key
);
1750 if (req
!= reqs
[i
].key
)
1752 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, 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])) {
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_UNKNOWN
) {
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_UNKNOWN
&& *argv
[2]++ == '!')
2025 request
= add_run_request(keymap
, key
, argv
+ 2);
2026 if (request
== REQ_UNKNOWN
) {
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
];
2126 tigrc_system
= SYSCONFDIR
"/tigrc";
2127 load_option_file(tigrc_system
);
2130 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
2134 load_option_file(tigrc_user
);
2136 /* Add _after_ loading config files to avoid adding run requests
2137 * that conflict with keybindings. */
2138 add_builtin_run_requests();
2151 /* The display array of active views and the index of the current view. */
2152 static struct view
*display
[2];
2153 static unsigned int current_view
;
2155 #define foreach_displayed_view(view, i) \
2156 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2158 #define displayed_views() (display[1] != NULL ? 2 : 1)
2160 /* Current head and commit ID */
2161 static char ref_blob
[SIZEOF_REF
] = "";
2162 static char ref_commit
[SIZEOF_REF
] = "HEAD";
2163 static char ref_head
[SIZEOF_REF
] = "HEAD";
2164 static char ref_branch
[SIZEOF_REF
] = "";
2181 enum view_type type
; /* View type */
2182 const char *name
; /* View name */
2183 const char *cmd_env
; /* Command line set via environment */
2184 const char *id
; /* Points to either of ref_{head,commit,blob} */
2186 struct view_ops
*ops
; /* View operations */
2188 enum keymap keymap
; /* What keymap does this view have */
2189 bool git_dir
; /* Whether the view requires a git directory. */
2191 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
2192 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
2194 int height
, width
; /* The width and height of the main window */
2195 WINDOW
*win
; /* The main window */
2196 WINDOW
*title
; /* The title window living below the main window */
2199 unsigned long offset
; /* Offset of the window top */
2200 unsigned long yoffset
; /* Offset from the window side. */
2201 unsigned long lineno
; /* Current line number */
2202 unsigned long p_offset
; /* Previous offset of the window top */
2203 unsigned long p_yoffset
;/* Previous offset from the window side */
2204 unsigned long p_lineno
; /* Previous current line number */
2205 bool p_restore
; /* Should the previous position be restored. */
2208 char grep
[SIZEOF_STR
]; /* Search string */
2209 regex_t
*regex
; /* Pre-compiled regexp */
2211 /* If non-NULL, points to the view that opened this view. If this view
2212 * is closed tig will switch back to the parent view. */
2213 struct view
*parent
;
2217 size_t lines
; /* Total number of lines */
2218 struct line
*line
; /* Line index */
2219 unsigned int digits
; /* Number of digits in the lines member. */
2222 struct line
*curline
; /* Line currently being drawn. */
2223 enum line_type curtype
; /* Attribute currently used for drawing. */
2224 unsigned long col
; /* Column when drawing. */
2225 bool has_scrolled
; /* View was scrolled. */
2228 const char **argv
; /* Shell command arguments. */
2229 const char *dir
; /* Directory from which to execute. */
2237 /* What type of content being displayed. Used in the title bar. */
2239 /* Default command arguments. */
2241 /* Open and reads in all view content. */
2242 bool (*open
)(struct view
*view
);
2243 /* Read one line; updates view->line. */
2244 bool (*read
)(struct view
*view
, char *data
);
2245 /* Draw one line; @lineno must be < view->height. */
2246 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
2247 /* Depending on view handle a special requests. */
2248 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
2249 /* Search for regexp in a line. */
2250 bool (*grep
)(struct view
*view
, struct line
*line
);
2252 void (*select
)(struct view
*view
, struct line
*line
);
2253 /* Prepare view for loading */
2254 bool (*prepare
)(struct view
*view
);
2257 static struct view_ops blame_ops
;
2258 static struct view_ops blob_ops
;
2259 static struct view_ops diff_ops
;
2260 static struct view_ops help_ops
;
2261 static struct view_ops log_ops
;
2262 static struct view_ops main_ops
;
2263 static struct view_ops pager_ops
;
2264 static struct view_ops stage_ops
;
2265 static struct view_ops status_ops
;
2266 static struct view_ops tree_ops
;
2267 static struct view_ops branch_ops
;
2269 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2270 { type, name, #env, ref, ops, map, git }
2272 #define VIEW_(id, name, ops, git, ref) \
2273 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2275 static struct view views
[] = {
2276 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
2277 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
2278 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
2279 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
2280 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
2281 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
2282 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
2283 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
2284 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
2285 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
2286 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
2289 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2291 #define foreach_view(view, i) \
2292 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2294 #define view_is_displayed(view) \
2295 (view == display[0] || view == display[1])
2298 view_request(struct view
*view
, enum request request
)
2300 if (!view
|| !view
->lines
)
2302 return view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
2311 set_view_attr(struct view
*view
, enum line_type type
)
2313 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
2314 (void) wattrset(view
->win
, get_line_attr(type
));
2315 wchgat(view
->win
, -1, 0, type
, NULL
);
2316 view
->curtype
= type
;
2321 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
2322 int max_len
, bool use_tilde
)
2324 static char out_buffer
[BUFSIZ
* 2];
2327 int trimmed
= FALSE
;
2328 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2333 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
, opt_tab_size
);
2335 set_view_attr(view
, type
);
2337 if (opt_iconv_out
!= ICONV_NONE
) {
2338 ICONV_CONST
char *inbuf
= (ICONV_CONST
char *) string
;
2339 size_t inlen
= len
+ 1;
2341 char *outbuf
= out_buffer
;
2342 size_t outlen
= sizeof(out_buffer
);
2346 ret
= iconv(opt_iconv_out
, &inbuf
, &inlen
, &outbuf
, &outlen
);
2347 if (ret
!= (size_t) -1) {
2348 string
= out_buffer
;
2349 len
= sizeof(out_buffer
) - outlen
;
2353 waddnstr(view
->win
, string
, len
);
2355 if (trimmed
&& use_tilde
) {
2356 set_view_attr(view
, LINE_DELIMITER
);
2357 waddch(view
->win
, '~');
2365 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
2367 static char space
[] = " ";
2370 spaces
= MIN(max
, spaces
);
2372 while (spaces
> 0) {
2373 int len
= MIN(spaces
, sizeof(space
) - 1);
2375 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
2383 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
2385 view
->col
+= draw_chars(view
, type
, string
, view
->width
+ view
->yoffset
- view
->col
, trim
);
2386 return view
->width
+ view
->yoffset
<= view
->col
;
2390 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
2392 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2393 int max
= view
->width
+ view
->yoffset
- view
->col
;
2399 set_view_attr(view
, type
);
2400 /* Using waddch() instead of waddnstr() ensures that
2401 * they'll be rendered correctly for the cursor line. */
2402 for (i
= skip
; i
< size
; i
++)
2403 waddch(view
->win
, graphic
[i
]);
2406 if (size
< max
&& skip
<= size
)
2407 waddch(view
->win
, ' ');
2410 return view
->width
+ view
->yoffset
<= view
->col
;
2414 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2416 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2420 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2422 col
= draw_space(view
, type
, max
- 1, max
- 1);
2425 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2426 return view
->width
+ view
->yoffset
<= view
->col
;
2430 draw_date(struct view
*view
, struct time
*time
)
2432 const char *date
= mkdate(time
, opt_date
);
2433 int cols
= opt_date
== DATE_SHORT
? DATE_SHORT_COLS
: DATE_COLS
;
2435 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2439 draw_author(struct view
*view
, const char *author
)
2441 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5;
2442 bool abbreviate
= opt_author
== AUTHOR_ABBREVIATED
|| !trim
;
2444 if (abbreviate
&& author
)
2445 author
= get_author_initials(author
);
2447 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2451 draw_mode(struct view
*view
, mode_t mode
)
2457 else if (S_ISLNK(mode
))
2459 else if (S_ISGITLINK(mode
))
2461 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2463 else if (S_ISREG(mode
))
2468 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2472 draw_lineno(struct view
*view
, unsigned int lineno
)
2475 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2476 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2478 chtype separator
= opt_line_graphics
? ACS_VLINE
: '|';
2480 lineno
+= view
->offset
+ 1;
2481 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2482 static char fmt
[] = "%1ld";
2484 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2485 if (string_format(number
, fmt
, lineno
))
2489 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2491 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2492 return draw_graphic(view
, LINE_DEFAULT
, &separator
, 1);
2496 draw_view_line(struct view
*view
, unsigned int lineno
)
2499 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2501 assert(view_is_displayed(view
));
2503 if (view
->offset
+ lineno
>= view
->lines
)
2506 line
= &view
->line
[view
->offset
+ lineno
];
2508 wmove(view
->win
, lineno
, 0);
2510 wclrtoeol(view
->win
);
2512 view
->curline
= line
;
2513 view
->curtype
= LINE_NONE
;
2514 line
->selected
= FALSE
;
2515 line
->dirty
= line
->cleareol
= 0;
2518 set_view_attr(view
, LINE_CURSOR
);
2519 line
->selected
= TRUE
;
2520 view
->ops
->select(view
, line
);
2523 return view
->ops
->draw(view
, line
, lineno
);
2527 redraw_view_dirty(struct view
*view
)
2532 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2533 if (view
->offset
+ lineno
>= view
->lines
)
2535 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2538 if (!draw_view_line(view
, lineno
))
2544 wnoutrefresh(view
->win
);
2548 redraw_view_from(struct view
*view
, int lineno
)
2550 assert(0 <= lineno
&& lineno
< view
->height
);
2552 for (; lineno
< view
->height
; lineno
++) {
2553 if (!draw_view_line(view
, lineno
))
2557 wnoutrefresh(view
->win
);
2561 redraw_view(struct view
*view
)
2564 redraw_view_from(view
, 0);
2569 update_view_title(struct view
*view
)
2571 char buf
[SIZEOF_STR
];
2572 char state
[SIZEOF_STR
];
2573 size_t bufpos
= 0, statelen
= 0;
2575 assert(view_is_displayed(view
));
2577 if (view
->type
!= VIEW_STATUS
&& view
->lines
) {
2578 unsigned int view_lines
= view
->offset
+ view
->height
;
2579 unsigned int lines
= view
->lines
2580 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2583 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2592 time_t secs
= time(NULL
) - view
->start_time
;
2594 /* Three git seconds are a long time ... */
2596 string_format_from(state
, &statelen
, " loading %lds", secs
);
2599 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2600 if (*view
->ref
&& bufpos
< view
->width
) {
2601 size_t refsize
= strlen(view
->ref
);
2602 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2604 if (minsize
< view
->width
)
2605 refsize
= view
->width
- minsize
+ 7;
2606 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2609 if (statelen
&& bufpos
< view
->width
) {
2610 string_format_from(buf
, &bufpos
, "%s", state
);
2613 if (view
== display
[current_view
])
2614 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2616 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2618 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2619 wclrtoeol(view
->title
);
2620 wnoutrefresh(view
->title
);
2624 apply_step(double step
, int value
)
2628 value
*= step
+ 0.01;
2629 return value
? value
: 1;
2633 resize_display(void)
2636 struct view
*base
= display
[0];
2637 struct view
*view
= display
[1] ? display
[1] : display
[0];
2639 /* Setup window dimensions */
2641 getmaxyx(stdscr
, base
->height
, base
->width
);
2643 /* Make room for the status window. */
2647 /* Horizontal split. */
2648 view
->width
= base
->width
;
2649 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2650 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2651 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2652 base
->height
-= view
->height
;
2654 /* Make room for the title bar. */
2658 /* Make room for the title bar. */
2663 foreach_displayed_view (view
, i
) {
2665 view
->win
= newwin(view
->height
, 0, offset
, 0);
2667 die("Failed to create %s view", view
->name
);
2669 scrollok(view
->win
, FALSE
);
2671 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2673 die("Failed to create title window");
2676 wresize(view
->win
, view
->height
, view
->width
);
2677 mvwin(view
->win
, offset
, 0);
2678 mvwin(view
->title
, offset
+ view
->height
, 0);
2681 offset
+= view
->height
+ 1;
2686 redraw_display(bool clear
)
2691 foreach_displayed_view (view
, i
) {
2695 update_view_title(view
);
2705 toggle_enum_option_do(unsigned int *opt
, const char *help
,
2706 const struct enum_map
*map
, size_t size
)
2708 *opt
= (*opt
+ 1) % size
;
2709 redraw_display(FALSE
);
2710 report("Displaying %s %s", enum_name(map
[*opt
]), help
);
2713 #define toggle_enum_option(opt, help, map) \
2714 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2716 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2717 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2720 toggle_view_option(bool *option
, const char *help
)
2723 redraw_display(FALSE
);
2724 report("%sabling %s", *option
? "En" : "Dis", help
);
2728 open_option_menu(void)
2730 const struct menu_item menu
[] = {
2731 { '.', "line numbers", &opt_line_number
},
2732 { 'D', "date display", &opt_date
},
2733 { 'A', "author display", &opt_author
},
2734 { 'g', "revision graph display", &opt_rev_graph
},
2735 { 'F', "reference display", &opt_show_refs
},
2740 if (prompt_menu("Toggle option", menu
, &selected
)) {
2741 if (menu
[selected
].data
== &opt_date
)
2743 else if (menu
[selected
].data
== &opt_author
)
2746 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2751 maximize_view(struct view
*view
)
2753 memset(display
, 0, sizeof(display
));
2755 display
[current_view
] = view
;
2757 redraw_display(FALSE
);
2767 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2769 if (lineno
>= view
->lines
)
2770 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2772 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2773 unsigned long half
= view
->height
/ 2;
2776 offset
= lineno
- half
;
2781 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2782 view
->offset
= offset
;
2783 view
->lineno
= lineno
;
2790 /* Scrolling backend */
2792 do_scroll_view(struct view
*view
, int lines
)
2794 bool redraw_current_line
= FALSE
;
2796 /* The rendering expects the new offset. */
2797 view
->offset
+= lines
;
2799 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2802 /* Move current line into the view. */
2803 if (view
->lineno
< view
->offset
) {
2804 view
->lineno
= view
->offset
;
2805 redraw_current_line
= TRUE
;
2806 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2807 view
->lineno
= view
->offset
+ view
->height
- 1;
2808 redraw_current_line
= TRUE
;
2811 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2813 /* Redraw the whole screen if scrolling is pointless. */
2814 if (view
->height
< ABS(lines
)) {
2818 int line
= lines
> 0 ? view
->height
- lines
: 0;
2819 int end
= line
+ ABS(lines
);
2821 scrollok(view
->win
, TRUE
);
2822 wscrl(view
->win
, lines
);
2823 scrollok(view
->win
, FALSE
);
2825 while (line
< end
&& draw_view_line(view
, line
))
2828 if (redraw_current_line
)
2829 draw_view_line(view
, view
->lineno
- view
->offset
);
2830 wnoutrefresh(view
->win
);
2833 view
->has_scrolled
= TRUE
;
2837 /* Scroll frontend */
2839 scroll_view(struct view
*view
, enum request request
)
2843 assert(view_is_displayed(view
));
2846 case REQ_SCROLL_LEFT
:
2847 if (view
->yoffset
== 0) {
2848 report("Cannot scroll beyond the first column");
2851 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2854 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2855 redraw_view_from(view
, 0);
2858 case REQ_SCROLL_RIGHT
:
2859 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2863 case REQ_SCROLL_PAGE_DOWN
:
2864 lines
= view
->height
;
2865 case REQ_SCROLL_LINE_DOWN
:
2866 if (view
->offset
+ lines
> view
->lines
)
2867 lines
= view
->lines
- view
->offset
;
2869 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2870 report("Cannot scroll beyond the last line");
2875 case REQ_SCROLL_PAGE_UP
:
2876 lines
= view
->height
;
2877 case REQ_SCROLL_LINE_UP
:
2878 if (lines
> view
->offset
)
2879 lines
= view
->offset
;
2882 report("Cannot scroll beyond the first line");
2890 die("request %d not handled in switch", request
);
2893 do_scroll_view(view
, lines
);
2898 move_view(struct view
*view
, enum request request
)
2900 int scroll_steps
= 0;
2904 case REQ_MOVE_FIRST_LINE
:
2905 steps
= -view
->lineno
;
2908 case REQ_MOVE_LAST_LINE
:
2909 steps
= view
->lines
- view
->lineno
- 1;
2912 case REQ_MOVE_PAGE_UP
:
2913 steps
= view
->height
> view
->lineno
2914 ? -view
->lineno
: -view
->height
;
2917 case REQ_MOVE_PAGE_DOWN
:
2918 steps
= view
->lineno
+ view
->height
>= view
->lines
2919 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2931 die("request %d not handled in switch", request
);
2934 if (steps
<= 0 && view
->lineno
== 0) {
2935 report("Cannot move beyond the first line");
2938 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2939 report("Cannot move beyond the last line");
2943 /* Move the current line */
2944 view
->lineno
+= steps
;
2945 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2947 /* Check whether the view needs to be scrolled */
2948 if (view
->lineno
< view
->offset
||
2949 view
->lineno
>= view
->offset
+ view
->height
) {
2950 scroll_steps
= steps
;
2951 if (steps
< 0 && -steps
> view
->offset
) {
2952 scroll_steps
= -view
->offset
;
2954 } else if (steps
> 0) {
2955 if (view
->lineno
== view
->lines
- 1 &&
2956 view
->lines
> view
->height
) {
2957 scroll_steps
= view
->lines
- view
->offset
- 1;
2958 if (scroll_steps
>= view
->height
)
2959 scroll_steps
-= view
->height
- 1;
2964 if (!view_is_displayed(view
)) {
2965 view
->offset
+= scroll_steps
;
2966 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2967 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2971 /* Repaint the old "current" line if we be scrolling */
2972 if (ABS(steps
) < view
->height
)
2973 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
2976 do_scroll_view(view
, scroll_steps
);
2980 /* Draw the current line */
2981 draw_view_line(view
, view
->lineno
- view
->offset
);
2983 wnoutrefresh(view
->win
);
2992 static void search_view(struct view
*view
, enum request request
);
2995 grep_text(struct view
*view
, const char *text
[])
3000 for (i
= 0; text
[i
]; i
++)
3002 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
3008 select_view_line(struct view
*view
, unsigned long lineno
)
3010 unsigned long old_lineno
= view
->lineno
;
3011 unsigned long old_offset
= view
->offset
;
3013 if (goto_view_line(view
, view
->offset
, lineno
)) {
3014 if (view_is_displayed(view
)) {
3015 if (old_offset
!= view
->offset
) {
3018 draw_view_line(view
, old_lineno
- view
->offset
);
3019 draw_view_line(view
, view
->lineno
- view
->offset
);
3020 wnoutrefresh(view
->win
);
3023 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
3029 find_next(struct view
*view
, enum request request
)
3031 unsigned long lineno
= view
->lineno
;
3036 report("No previous search");
3038 search_view(view
, request
);
3048 case REQ_SEARCH_BACK
:
3057 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
3058 lineno
+= direction
;
3060 /* Note, lineno is unsigned long so will wrap around in which case it
3061 * will become bigger than view->lines. */
3062 for (; lineno
< view
->lines
; lineno
+= direction
) {
3063 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
3064 select_view_line(view
, lineno
);
3065 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
3070 report("No match found for '%s'", view
->grep
);
3074 search_view(struct view
*view
, enum request request
)
3079 regfree(view
->regex
);
3082 view
->regex
= calloc(1, sizeof(*view
->regex
));
3087 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
3088 if (regex_err
!= 0) {
3089 char buf
[SIZEOF_STR
] = "unknown error";
3091 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
3092 report("Search failed: %s", buf
);
3096 string_copy(view
->grep
, opt_search
);
3098 find_next(view
, request
);
3102 * Incremental updating
3106 reset_view(struct view
*view
)
3110 for (i
= 0; i
< view
->lines
; i
++)
3111 free(view
->line
[i
].data
);
3114 view
->p_offset
= view
->offset
;
3115 view
->p_yoffset
= view
->yoffset
;
3116 view
->p_lineno
= view
->lineno
;
3124 view
->update_secs
= 0;
3128 format_arg(const char *name
)
3134 const char *value_if_empty
;
3136 #define FORMAT_VAR(name, value, value_if_empty) \
3137 { name, STRING_SIZE(name), value, value_if_empty }
3138 FORMAT_VAR("%(directory)", opt_path
, ""),
3139 FORMAT_VAR("%(file)", opt_file
, ""),
3140 FORMAT_VAR("%(ref)", opt_ref
, "HEAD"),
3141 FORMAT_VAR("%(head)", ref_head
, ""),
3142 FORMAT_VAR("%(commit)", ref_commit
, ""),
3143 FORMAT_VAR("%(blob)", ref_blob
, ""),
3144 FORMAT_VAR("%(branch)", ref_branch
, ""),
3148 for (i
= 0; i
< ARRAY_SIZE(vars
); i
++)
3149 if (!strncmp(name
, vars
[i
].name
, vars
[i
].namelen
))
3150 return *vars
[i
].value
? vars
[i
].value
: vars
[i
].value_if_empty
;
3152 report("Unknown replacement: `%s`", name
);
3157 format_argv(const char ***dst_argv
, const char *src_argv
[], bool replace
)
3159 char buf
[SIZEOF_STR
];
3162 argv_free(*dst_argv
);
3164 for (argc
= 0; src_argv
[argc
]; argc
++) {
3165 const char *arg
= src_argv
[argc
];
3169 char *next
= strstr(arg
, "%(");
3170 int len
= next
- arg
;
3173 if (!next
|| !replace
) {
3178 value
= format_arg(next
);
3185 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
3188 arg
= next
&& replace
? strchr(next
, ')') + 1 : NULL
;
3191 if (!argv_append(dst_argv
, buf
))
3195 return src_argv
[argc
] == NULL
;
3199 restore_view_position(struct view
*view
)
3201 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
3204 /* Changing the view position cancels the restoring. */
3205 /* FIXME: Changing back to the first line is not detected. */
3206 if (view
->offset
!= 0 || view
->lineno
!= 0) {
3207 view
->p_restore
= FALSE
;
3211 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
3212 view_is_displayed(view
))
3215 view
->yoffset
= view
->p_yoffset
;
3216 view
->p_restore
= FALSE
;
3222 end_update(struct view
*view
, bool force
)
3226 while (!view
->ops
->read(view
, NULL
))
3230 io_kill(view
->pipe
);
3231 io_done(view
->pipe
);
3236 setup_update(struct view
*view
, const char *vid
)
3239 string_copy_rev(view
->vid
, vid
);
3240 view
->pipe
= &view
->io
;
3241 view
->start_time
= time(NULL
);
3245 prepare_io(struct view
*view
, const char *dir
, const char *argv
[], bool replace
)
3248 return format_argv(&view
->argv
, argv
, replace
);
3252 prepare_update(struct view
*view
, const char *argv
[], const char *dir
)
3255 end_update(view
, TRUE
);
3256 return prepare_io(view
, dir
, argv
, FALSE
);
3260 start_update(struct view
*view
, const char **argv
, const char *dir
)
3263 io_done(view
->pipe
);
3264 return prepare_io(view
, dir
, argv
, FALSE
) &&
3265 io_run(&view
->io
, IO_RD
, dir
, view
->argv
);
3269 prepare_update_file(struct view
*view
, const char *name
)
3272 end_update(view
, TRUE
);
3273 argv_free(view
->argv
);
3274 return io_open(&view
->io
, "%s/%s", opt_cdup
[0] ? opt_cdup
: ".", name
);
3278 begin_update(struct view
*view
, bool refresh
)
3281 end_update(view
, TRUE
);
3284 if (view
->ops
->prepare
) {
3285 if (!view
->ops
->prepare(view
))
3287 } else if (!prepare_io(view
, NULL
, view
->ops
->argv
, TRUE
)) {
3291 /* Put the current ref_* value to the view title ref
3292 * member. This is needed by the blob view. Most other
3293 * views sets it automatically after loading because the
3294 * first line is a commit line. */
3295 string_copy_rev(view
->ref
, view
->id
);
3298 if (view
->argv
&& view
->argv
[0] &&
3299 !io_run(&view
->io
, IO_RD
, view
->dir
, view
->argv
))
3302 setup_update(view
, view
->id
);
3308 update_view(struct view
*view
)
3310 char out_buffer
[BUFSIZ
* 2];
3312 /* Clear the view and redraw everything since the tree sorting
3313 * might have rearranged things. */
3314 bool redraw
= view
->lines
== 0;
3315 bool can_read
= TRUE
;
3320 if (!io_can_read(view
->pipe
)) {
3321 if (view
->lines
== 0 && view_is_displayed(view
)) {
3322 time_t secs
= time(NULL
) - view
->start_time
;
3324 if (secs
> 1 && secs
> view
->update_secs
) {
3325 if (view
->update_secs
== 0)
3327 update_view_title(view
);
3328 view
->update_secs
= secs
;
3334 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
3335 if (opt_iconv_in
!= ICONV_NONE
) {
3336 ICONV_CONST
char *inbuf
= line
;
3337 size_t inlen
= strlen(line
) + 1;
3339 char *outbuf
= out_buffer
;
3340 size_t outlen
= sizeof(out_buffer
);
3344 ret
= iconv(opt_iconv_in
, &inbuf
, &inlen
, &outbuf
, &outlen
);
3345 if (ret
!= (size_t) -1)
3349 if (!view
->ops
->read(view
, line
)) {
3350 report("Allocation failure");
3351 end_update(view
, TRUE
);
3357 unsigned long lines
= view
->lines
;
3360 for (digits
= 0; lines
; digits
++)
3363 /* Keep the displayed view in sync with line number scaling. */
3364 if (digits
!= view
->digits
) {
3365 view
->digits
= digits
;
3366 if (opt_line_number
|| view
->type
== VIEW_BLAME
)
3371 if (io_error(view
->pipe
)) {
3372 report("Failed to read: %s", io_strerror(view
->pipe
));
3373 end_update(view
, TRUE
);
3375 } else if (io_eof(view
->pipe
)) {
3376 if (view_is_displayed(view
))
3378 end_update(view
, FALSE
);
3381 if (restore_view_position(view
))
3384 if (!view_is_displayed(view
))
3388 redraw_view_from(view
, 0);
3390 redraw_view_dirty(view
);
3392 /* Update the title _after_ the redraw so that if the redraw picks up a
3393 * commit reference in view->ref it'll be available here. */
3394 update_view_title(view
);
3398 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
3400 static struct line
*
3401 add_line_data(struct view
*view
, void *data
, enum line_type type
)
3405 if (!realloc_lines(&view
->line
, view
->lines
, 1))
3408 line
= &view
->line
[view
->lines
++];
3409 memset(line
, 0, sizeof(*line
));
3417 static struct line
*
3418 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
3420 char *data
= text
? strdup(text
) : NULL
;
3422 return data
? add_line_data(view
, data
, type
) : NULL
;
3425 static struct line
*
3426 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3428 char buf
[SIZEOF_STR
];
3431 va_start(args
, fmt
);
3432 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3436 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3444 OPEN_DEFAULT
= 0, /* Use default view switching. */
3445 OPEN_SPLIT
= 1, /* Split current view. */
3446 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3447 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3448 OPEN_PREPARED
= 32, /* Open already prepared command. */
3452 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3454 bool split
= !!(flags
& OPEN_SPLIT
);
3455 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3456 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3457 struct view
*view
= VIEW(request
);
3458 int nviews
= displayed_views();
3459 struct view
*base_view
= display
[0];
3461 if (view
== prev
&& nviews
== 1 && !reload
) {
3462 report("Already in %s view", view
->name
);
3466 if (view
->git_dir
&& !opt_git_dir
[0]) {
3467 report("The %s view is disabled in pager view", view
->name
);
3474 view
->parent
= prev
;
3475 } else if (!nomaximize
) {
3476 /* Maximize the current view. */
3477 memset(display
, 0, sizeof(display
));
3479 display
[current_view
] = view
;
3482 /* No prev signals that this is the first loaded view. */
3483 if (prev
&& view
!= prev
) {
3487 /* Resize the view when switching between split- and full-screen,
3488 * or when switching between two different full-screen views. */
3489 if (nviews
!= displayed_views() ||
3490 (nviews
== 1 && base_view
!= display
[0]))
3493 if (view
->ops
->open
) {
3495 end_update(view
, TRUE
);
3496 if (!view
->ops
->open(view
)) {
3497 report("Failed to load %s view", view
->name
);
3500 restore_view_position(view
);
3502 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3503 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3504 report("Failed to load %s view", view
->name
);
3508 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3509 /* Take the title line into account. */
3510 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3512 /* Scroll the view that was split if the current line is
3513 * outside the new limited view. */
3514 do_scroll_view(prev
, lines
);
3517 if (prev
&& view
!= prev
&& split
&& view_is_displayed(prev
)) {
3518 /* "Blur" the previous view. */
3519 update_view_title(prev
);
3522 if (view
->pipe
&& view
->lines
== 0) {
3523 /* Clear the old view and let the incremental updating refill
3526 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3528 } else if (view_is_displayed(view
)) {
3535 open_external_viewer(const char *argv
[], const char *dir
)
3537 def_prog_mode(); /* save current tty modes */
3538 endwin(); /* restore original tty modes */
3539 io_run_fg(argv
, dir
);
3540 fprintf(stderr
, "Press Enter to continue");
3543 redraw_display(TRUE
);
3547 open_mergetool(const char *file
)
3549 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3551 open_external_viewer(mergetool_argv
, opt_cdup
);
3555 open_editor(const char *file
)
3557 const char *editor_argv
[] = { "vi", file
, NULL
};
3560 editor
= getenv("GIT_EDITOR");
3561 if (!editor
&& *opt_editor
)
3562 editor
= opt_editor
;
3564 editor
= getenv("VISUAL");
3566 editor
= getenv("EDITOR");
3570 editor_argv
[0] = editor
;
3571 open_external_viewer(editor_argv
, opt_cdup
);
3575 open_run_request(enum request request
)
3577 struct run_request
*req
= get_run_request(request
);
3578 const char **argv
= NULL
;
3581 report("Unknown run request");
3585 if (format_argv(&argv
, req
->argv
, TRUE
))
3586 open_external_viewer(argv
, NULL
);
3593 * User request switch noodle
3597 view_driver(struct view
*view
, enum request request
)
3601 if (request
== REQ_NONE
)
3604 if (request
> REQ_NONE
) {
3605 open_run_request(request
);
3606 view_request(view
, REQ_REFRESH
);
3610 request
= view_request(view
, request
);
3611 if (request
== REQ_NONE
)
3617 case REQ_MOVE_PAGE_UP
:
3618 case REQ_MOVE_PAGE_DOWN
:
3619 case REQ_MOVE_FIRST_LINE
:
3620 case REQ_MOVE_LAST_LINE
:
3621 move_view(view
, request
);
3624 case REQ_SCROLL_LEFT
:
3625 case REQ_SCROLL_RIGHT
:
3626 case REQ_SCROLL_LINE_DOWN
:
3627 case REQ_SCROLL_LINE_UP
:
3628 case REQ_SCROLL_PAGE_DOWN
:
3629 case REQ_SCROLL_PAGE_UP
:
3630 scroll_view(view
, request
);
3633 case REQ_VIEW_BLAME
:
3635 report("No file chosen, press %s to open tree view",
3636 get_key(view
->keymap
, REQ_VIEW_TREE
));
3639 open_view(view
, request
, OPEN_DEFAULT
);
3644 report("No file chosen, press %s to open tree view",
3645 get_key(view
->keymap
, REQ_VIEW_TREE
));
3648 open_view(view
, request
, OPEN_DEFAULT
);
3651 case REQ_VIEW_PAGER
:
3652 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3653 report("No pager content, press %s to run command from prompt",
3654 get_key(view
->keymap
, REQ_PROMPT
));
3657 open_view(view
, request
, OPEN_DEFAULT
);
3660 case REQ_VIEW_STAGE
:
3661 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3662 report("No stage content, press %s to open the status view and choose file",
3663 get_key(view
->keymap
, REQ_VIEW_STATUS
));
3666 open_view(view
, request
, OPEN_DEFAULT
);
3669 case REQ_VIEW_STATUS
:
3670 if (opt_is_inside_work_tree
== FALSE
) {
3671 report("The status view requires a working tree");
3674 open_view(view
, request
, OPEN_DEFAULT
);
3682 case REQ_VIEW_BRANCH
:
3683 open_view(view
, request
, OPEN_DEFAULT
);
3688 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3693 view
= view
->parent
;
3694 line
= view
->lineno
;
3695 move_view(view
, request
);
3696 if (view_is_displayed(view
))
3697 update_view_title(view
);
3698 if (line
!= view
->lineno
)
3699 view_request(view
, REQ_ENTER
);
3701 move_view(view
, request
);
3707 int nviews
= displayed_views();
3708 int next_view
= (current_view
+ 1) % nviews
;
3710 if (next_view
== current_view
) {
3711 report("Only one view is displayed");
3715 current_view
= next_view
;
3716 /* Blur out the title of the previous view. */
3717 update_view_title(view
);
3722 report("Refreshing is not yet supported for the %s view", view
->name
);
3726 if (displayed_views() == 2)
3727 maximize_view(view
);
3734 case REQ_TOGGLE_LINENO
:
3735 toggle_view_option(&opt_line_number
, "line numbers");
3738 case REQ_TOGGLE_DATE
:
3742 case REQ_TOGGLE_AUTHOR
:
3746 case REQ_TOGGLE_REV_GRAPH
:
3747 toggle_view_option(&opt_rev_graph
, "revision graph display");
3750 case REQ_TOGGLE_REFS
:
3751 toggle_view_option(&opt_show_refs
, "reference display");
3754 case REQ_TOGGLE_SORT_FIELD
:
3755 case REQ_TOGGLE_SORT_ORDER
:
3756 report("Sorting is not yet supported for the %s view", view
->name
);
3760 case REQ_SEARCH_BACK
:
3761 search_view(view
, request
);
3766 find_next(view
, request
);
3769 case REQ_STOP_LOADING
:
3770 foreach_view(view
, i
) {
3772 report("Stopped loading the %s view", view
->name
),
3773 end_update(view
, TRUE
);
3777 case REQ_SHOW_VERSION
:
3778 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3781 case REQ_SCREEN_REDRAW
:
3782 redraw_display(TRUE
);
3786 report("Nothing to edit");
3790 report("Nothing to enter");
3793 case REQ_VIEW_CLOSE
:
3794 /* XXX: Mark closed views by letting view->prev point to the
3795 * view itself. Parents to closed view should never be
3797 if (view
->prev
&& view
->prev
!= view
) {
3798 maximize_view(view
->prev
);
3807 report("Unknown key, press %s for help",
3808 get_key(view
->keymap
, REQ_VIEW_HELP
));
3817 * View backend utilities
3827 const enum sort_field
*fields
;
3828 size_t size
, current
;
3832 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3833 #define get_sort_field(state) ((state).fields[(state).current])
3834 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3837 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3838 int (*compare
)(const void *, const void *))
3841 case REQ_TOGGLE_SORT_FIELD
:
3842 state
->current
= (state
->current
+ 1) % state
->size
;
3845 case REQ_TOGGLE_SORT_ORDER
:
3846 state
->reverse
= !state
->reverse
;
3849 die("Not a sort request");
3852 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3856 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3858 /* Small author cache to reduce memory consumption. It uses binary
3859 * search to lookup or find place to position new entries. No entries
3860 * are ever freed. */
3862 get_author(const char *name
)
3864 static const char **authors
;
3865 static size_t authors_size
;
3866 int from
= 0, to
= authors_size
- 1;
3868 while (from
<= to
) {
3869 size_t pos
= (to
+ from
) / 2;
3870 int cmp
= strcmp(name
, authors
[pos
]);
3873 return authors
[pos
];
3881 if (!realloc_authors(&authors
, authors_size
, 1))
3883 name
= strdup(name
);
3887 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3888 authors
[from
] = name
;
3895 parse_timesec(struct time
*time
, const char *sec
)
3897 time
->sec
= (time_t) atol(sec
);
3901 parse_timezone(struct time
*time
, const char *zone
)
3905 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3906 tz
+= ('0' - zone
[2]) * 60 * 60;
3907 tz
+= ('0' - zone
[3]) * 60 * 10;
3908 tz
+= ('0' - zone
[4]) * 60;
3917 /* Parse author lines where the name may be empty:
3918 * author <email@address.tld> 1138474660 +0100
3921 parse_author_line(char *ident
, const char **author
, struct time
*time
)
3923 char *nameend
= strchr(ident
, '<');
3924 char *emailend
= strchr(ident
, '>');
3926 if (nameend
&& emailend
)
3927 *nameend
= *emailend
= 0;
3928 ident
= chomp_string(ident
);
3931 ident
= chomp_string(nameend
+ 1);
3936 *author
= get_author(ident
);
3938 /* Parse epoch and timezone */
3939 if (emailend
&& emailend
[1] == ' ') {
3940 char *secs
= emailend
+ 2;
3941 char *zone
= strchr(secs
, ' ');
3943 parse_timesec(time
, secs
);
3945 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
3946 parse_timezone(time
, zone
+ 1);
3951 open_commit_parent_menu(char buf
[SIZEOF_STR
], int *parents
)
3953 char rev
[SIZEOF_REV
];
3954 const char *revlist_argv
[] = {
3955 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev
, NULL
3957 struct menu_item
*items
;
3958 char text
[SIZEOF_STR
];
3962 items
= calloc(*parents
+ 1, sizeof(*items
));
3966 for (i
= 0; i
< *parents
; i
++) {
3967 string_copy_rev(rev
, &buf
[SIZEOF_REV
* i
]);
3968 if (!io_run_buf(revlist_argv
, text
, sizeof(text
)) ||
3969 !(items
[i
].text
= strdup(text
))) {
3977 ok
= prompt_menu("Select parent", items
, parents
);
3979 for (i
= 0; items
[i
].text
; i
++)
3980 free((char *) items
[i
].text
);
3986 select_commit_parent(const char *id
, char rev
[SIZEOF_REV
], const char *path
)
3988 char buf
[SIZEOF_STR
* 4];
3989 const char *revlist_argv
[] = {
3990 "git", "log", "--no-color", "-1",
3991 "--pretty=format:%P", id
, "--", path
, NULL
3995 if (!io_run_buf(revlist_argv
, buf
, sizeof(buf
)) ||
3996 (parents
= strlen(buf
) / 40) < 0) {
3997 report("Failed to get parent information");
4000 } else if (parents
== 0) {
4002 report("Path '%s' does not exist in the parent", path
);
4004 report("The selected commit has no parents");
4010 else if (!open_commit_parent_menu(buf
, &parents
))
4013 string_copy_rev(rev
, &buf
[41 * parents
]);
4022 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4024 char text
[SIZEOF_STR
];
4026 if (opt_line_number
&& draw_lineno(view
, lineno
))
4029 string_expand(text
, sizeof(text
), line
->data
, opt_tab_size
);
4030 draw_text(view
, line
->type
, text
, TRUE
);
4035 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
4037 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
4038 char ref
[SIZEOF_STR
];
4040 if (!io_run_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
4043 /* This is the only fatal call, since it can "corrupt" the buffer. */
4044 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
4051 add_pager_refs(struct view
*view
, struct line
*line
)
4053 char buf
[SIZEOF_STR
];
4054 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
4055 struct ref_list
*list
;
4056 size_t bufpos
= 0, i
;
4057 const char *sep
= "Refs: ";
4058 bool is_tag
= FALSE
;
4060 assert(line
->type
== LINE_COMMIT
);
4062 list
= get_ref_list(commit_id
);
4064 if (view
->type
== VIEW_DIFF
)
4065 goto try_add_describe_ref
;
4069 for (i
= 0; i
< list
->size
; i
++) {
4070 struct ref
*ref
= list
->refs
[i
];
4071 const char *fmt
= ref
->tag
? "%s[%s]" :
4072 ref
->remote
? "%s<%s>" : "%s%s";
4074 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
4081 if (!is_tag
&& view
->type
== VIEW_DIFF
) {
4082 try_add_describe_ref
:
4083 /* Add <tag>-g<commit_id> "fake" reference. */
4084 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
4091 add_line_text(view
, buf
, LINE_PP_REFS
);
4095 pager_read(struct view
*view
, char *data
)
4102 line
= add_line_text(view
, data
, get_line_type(data
));
4106 if (line
->type
== LINE_COMMIT
&&
4107 (view
->type
== VIEW_DIFF
||
4108 view
->type
== VIEW_LOG
))
4109 add_pager_refs(view
, line
);
4115 pager_request(struct view
*view
, enum request request
, struct line
*line
)
4119 if (request
!= REQ_ENTER
)
4122 if (line
->type
== LINE_COMMIT
&&
4123 (view
->type
== VIEW_LOG
||
4124 view
->type
== VIEW_PAGER
)) {
4125 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
4129 /* Always scroll the view even if it was split. That way
4130 * you can use Enter to scroll through the log view and
4131 * split open each commit diff. */
4132 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
4134 /* FIXME: A minor workaround. Scrolling the view will call report("")
4135 * but if we are scrolling a non-current view this won't properly
4136 * update the view title. */
4138 update_view_title(view
);
4144 pager_grep(struct view
*view
, struct line
*line
)
4146 const char *text
[] = { line
->data
, NULL
};
4148 return grep_text(view
, text
);
4152 pager_select(struct view
*view
, struct line
*line
)
4154 if (line
->type
== LINE_COMMIT
) {
4155 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
4157 if (view
->type
!= VIEW_PAGER
)
4158 string_copy_rev(view
->ref
, text
);
4159 string_copy_rev(ref_commit
, text
);
4163 static struct view_ops pager_ops
= {
4174 static const char *log_argv
[SIZEOF_ARG
] = {
4175 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4179 log_request(struct view
*view
, enum request request
, struct line
*line
)
4184 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
4187 return pager_request(view
, request
, line
);
4191 static struct view_ops log_ops
= {
4202 static const char *diff_argv
[SIZEOF_ARG
] = {
4203 "git", "show", "--pretty=fuller", "--no-color", "--root",
4204 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4207 static struct view_ops diff_ops
= {
4222 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
4225 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
4229 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
4230 help_keymap_hidden
[keymap
] ? '+' : '-',
4231 enum_name(keymap_table
[keymap
]));
4233 line
->other
= keymap
;
4235 return help_keymap_hidden
[keymap
];
4239 help_open_keymap(struct view
*view
, enum keymap keymap
)
4241 const char *group
= NULL
;
4242 char buf
[SIZEOF_STR
];
4244 bool add_title
= TRUE
;
4247 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
4248 const char *key
= NULL
;
4250 if (req_info
[i
].request
== REQ_NONE
)
4253 if (!req_info
[i
].request
) {
4254 group
= req_info
[i
].help
;
4258 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4262 if (add_title
&& help_open_keymap_title(view
, keymap
))
4267 add_line_text(view
, group
, LINE_HELP_GROUP
);
4271 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4272 enum_name(req_info
[i
]), req_info
[i
].help
);
4275 group
= "External commands:";
4277 for (i
= 0; i
< run_requests
; i
++) {
4278 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4282 if (!req
|| req
->keymap
!= keymap
)
4285 key
= get_key_name(req
->key
);
4287 key
= "(no key defined)";
4289 if (add_title
&& help_open_keymap_title(view
, keymap
))
4292 add_line_text(view
, group
, LINE_HELP_GROUP
);
4296 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4297 if (!string_format_from(buf
, &bufpos
, "%s%s",
4298 argc
? " " : "", req
->argv
[argc
]))
4301 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4306 help_open(struct view
*view
)
4311 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4312 add_line_text(view
, "", LINE_DEFAULT
);
4314 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4315 help_open_keymap(view
, keymap
);
4321 help_request(struct view
*view
, enum request request
, struct line
*line
)
4325 if (line
->type
== LINE_HELP_KEYMAP
) {
4326 help_keymap_hidden
[line
->other
] =
4327 !help_keymap_hidden
[line
->other
];
4328 view
->p_restore
= TRUE
;
4329 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4334 return pager_request(view
, request
, line
);
4338 static struct view_ops help_ops
= {
4354 struct tree_stack_entry
{
4355 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4356 unsigned long lineno
; /* Line number to restore */
4357 char *name
; /* Position of name in opt_path */
4360 /* The top of the path stack. */
4361 static struct tree_stack_entry
*tree_stack
= NULL
;
4362 unsigned long tree_lineno
= 0;
4365 pop_tree_stack_entry(void)
4367 struct tree_stack_entry
*entry
= tree_stack
;
4369 tree_lineno
= entry
->lineno
;
4371 tree_stack
= entry
->prev
;
4376 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4378 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4379 size_t pathlen
= strlen(opt_path
);
4384 entry
->prev
= tree_stack
;
4385 entry
->name
= opt_path
+ pathlen
;
4388 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4389 pop_tree_stack_entry();
4393 /* Move the current line to the first tree entry. */
4395 entry
->lineno
= lineno
;
4398 /* Parse output from git-ls-tree(1):
4400 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4403 #define SIZEOF_TREE_ATTR \
4404 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4406 #define SIZEOF_TREE_MODE \
4407 STRING_SIZE("100644 ")
4409 #define TREE_ID_OFFSET \
4410 STRING_SIZE("100644 blob ")
4413 char id
[SIZEOF_REV
];
4415 struct time time
; /* Date from the author ident. */
4416 const char *author
; /* Author of the commit. */
4421 tree_path(const struct line
*line
)
4423 return ((struct tree_entry
*) line
->data
)->name
;
4427 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4429 if (line1
->type
!= line2
->type
)
4430 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4431 return strcmp(tree_path(line1
), tree_path(line2
));
4434 static const enum sort_field tree_sort_fields
[] = {
4435 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4437 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4440 tree_compare(const void *l1
, const void *l2
)
4442 const struct line
*line1
= (const struct line
*) l1
;
4443 const struct line
*line2
= (const struct line
*) l2
;
4444 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4445 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4447 if (line1
->type
== LINE_TREE_HEAD
)
4449 if (line2
->type
== LINE_TREE_HEAD
)
4452 switch (get_sort_field(tree_sort_state
)) {
4454 return sort_order(tree_sort_state
, timecmp(&entry1
->time
, &entry2
->time
));
4456 case ORDERBY_AUTHOR
:
4457 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4461 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4466 static struct line
*
4467 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4468 const char *mode
, const char *id
)
4470 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4471 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4473 if (!entry
|| !line
) {
4478 strncpy(entry
->name
, path
, strlen(path
));
4480 entry
->mode
= strtoul(mode
, NULL
, 8);
4482 string_copy_rev(entry
->id
, id
);
4488 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4490 static const char *author_name
;
4491 static struct time author_time
;
4493 if (!text
&& *read_date
) {
4498 char *path
= *opt_path
? opt_path
: ".";
4499 /* Find next entry to process */
4500 const char *log_file
[] = {
4501 "git", "log", "--no-color", "--pretty=raw",
4502 "--cc", "--raw", view
->id
, "--", path
, NULL
4506 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4507 report("Tree is empty");
4511 if (!start_update(view
, log_file
, opt_cdup
)) {
4512 report("Failed to load tree data");
4519 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4520 parse_author_line(text
+ STRING_SIZE("author "),
4521 &author_name
, &author_time
);
4523 } else if (*text
== ':') {
4525 size_t annotated
= 1;
4528 pos
= strchr(text
, '\t');
4532 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4533 text
+= strlen(opt_path
);
4534 pos
= strchr(text
, '/');
4538 for (i
= 1; i
< view
->lines
; i
++) {
4539 struct line
*line
= &view
->line
[i
];
4540 struct tree_entry
*entry
= line
->data
;
4542 annotated
+= !!entry
->author
;
4543 if (entry
->author
|| strcmp(entry
->name
, text
))
4546 entry
->author
= author_name
;
4547 entry
->time
= author_time
;
4552 if (annotated
== view
->lines
)
4553 io_kill(view
->pipe
);
4559 tree_read(struct view
*view
, char *text
)
4561 static bool read_date
= FALSE
;
4562 struct tree_entry
*data
;
4563 struct line
*entry
, *line
;
4564 enum line_type type
;
4565 size_t textlen
= text
? strlen(text
) : 0;
4566 char *path
= text
+ SIZEOF_TREE_ATTR
;
4568 if (read_date
|| !text
)
4569 return tree_read_date(view
, text
, &read_date
);
4571 if (textlen
<= SIZEOF_TREE_ATTR
)
4573 if (view
->lines
== 0 &&
4574 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4577 /* Strip the path part ... */
4579 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4580 size_t striplen
= strlen(opt_path
);
4582 if (pathlen
> striplen
)
4583 memmove(path
, path
+ striplen
,
4584 pathlen
- striplen
+ 1);
4586 /* Insert "link" to parent directory. */
4587 if (view
->lines
== 1 &&
4588 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4592 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4593 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4598 /* Skip "Directory ..." and ".." line. */
4599 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4600 if (tree_compare_entry(line
, entry
) <= 0)
4603 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4607 for (; line
<= entry
; line
++)
4608 line
->dirty
= line
->cleareol
= 1;
4612 if (tree_lineno
> view
->lineno
) {
4613 view
->lineno
= tree_lineno
;
4621 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4623 struct tree_entry
*entry
= line
->data
;
4625 if (line
->type
== LINE_TREE_HEAD
) {
4626 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4629 if (draw_mode(view
, entry
->mode
))
4632 if (opt_author
&& draw_author(view
, entry
->author
))
4635 if (opt_date
&& draw_date(view
, &entry
->time
))
4638 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4644 open_blob_editor(const char *id
)
4646 const char *blob_argv
[] = { "git", "cat-file", "blob", id
, NULL
};
4647 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4648 int fd
= mkstemp(file
);
4651 report("Failed to create temporary file");
4652 else if (!io_run_append(blob_argv
, fd
))
4653 report("Failed to save blob data to file");
4661 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4663 enum open_flags flags
;
4664 struct tree_entry
*entry
= line
->data
;
4667 case REQ_VIEW_BLAME
:
4668 if (line
->type
!= LINE_TREE_FILE
) {
4669 report("Blame only supported for files");
4673 string_copy(opt_ref
, view
->vid
);
4677 if (line
->type
!= LINE_TREE_FILE
) {
4678 report("Edit only supported for files");
4679 } else if (!is_head_commit(view
->vid
)) {
4680 open_blob_editor(entry
->id
);
4682 open_editor(opt_file
);
4686 case REQ_TOGGLE_SORT_FIELD
:
4687 case REQ_TOGGLE_SORT_ORDER
:
4688 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4693 /* quit view if at top of tree */
4694 return REQ_VIEW_CLOSE
;
4697 line
= &view
->line
[1];
4707 /* Cleanup the stack if the tree view is at a different tree. */
4708 while (!*opt_path
&& tree_stack
)
4709 pop_tree_stack_entry();
4711 switch (line
->type
) {
4713 /* Depending on whether it is a subdirectory or parent link
4714 * mangle the path buffer. */
4715 if (line
== &view
->line
[1] && *opt_path
) {
4716 pop_tree_stack_entry();
4719 const char *basename
= tree_path(line
);
4721 push_tree_stack_entry(basename
, view
->lineno
);
4724 /* Trees and subtrees share the same ID, so they are not not
4725 * unique like blobs. */
4726 flags
= OPEN_RELOAD
;
4727 request
= REQ_VIEW_TREE
;
4730 case LINE_TREE_FILE
:
4731 flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
4732 request
= REQ_VIEW_BLOB
;
4739 open_view(view
, request
, flags
);
4740 if (request
== REQ_VIEW_TREE
)
4741 view
->lineno
= tree_lineno
;
4747 tree_grep(struct view
*view
, struct line
*line
)
4749 struct tree_entry
*entry
= line
->data
;
4750 const char *text
[] = {
4752 opt_author
? entry
->author
: "",
4753 mkdate(&entry
->time
, opt_date
),
4757 return grep_text(view
, text
);
4761 tree_select(struct view
*view
, struct line
*line
)
4763 struct tree_entry
*entry
= line
->data
;
4765 if (line
->type
== LINE_TREE_FILE
) {
4766 string_copy_rev(ref_blob
, entry
->id
);
4767 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4769 } else if (line
->type
!= LINE_TREE_DIR
) {
4773 string_copy_rev(view
->ref
, entry
->id
);
4777 tree_prepare(struct view
*view
)
4779 if (view
->lines
== 0 && opt_prefix
[0]) {
4780 char *pos
= opt_prefix
;
4782 while (pos
&& *pos
) {
4783 char *end
= strchr(pos
, '/');
4787 push_tree_stack_entry(pos
, 0);
4795 } else if (strcmp(view
->vid
, view
->id
)) {
4799 return prepare_io(view
, opt_cdup
, view
->ops
->argv
, TRUE
);
4802 static const char *tree_argv
[SIZEOF_ARG
] = {
4803 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4806 static struct view_ops tree_ops
= {
4819 blob_read(struct view
*view
, char *line
)
4823 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4827 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4831 open_blob_editor(view
->vid
);
4834 return pager_request(view
, request
, line
);
4838 static const char *blob_argv
[SIZEOF_ARG
] = {
4839 "git", "cat-file", "blob", "%(blob)", NULL
4842 static struct view_ops blob_ops
= {
4856 * Loading the blame view is a two phase job:
4858 * 1. File content is read either using opt_file from the
4859 * filesystem or using git-cat-file.
4860 * 2. Then blame information is incrementally added by
4861 * reading output from git-blame.
4864 struct blame_commit
{
4865 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4866 char title
[128]; /* First line of the commit message. */
4867 const char *author
; /* Author of the commit. */
4868 struct time time
; /* Date from the author ident. */
4869 char filename
[128]; /* Name of file. */
4870 bool has_previous
; /* Was a "previous" line detected. */
4874 struct blame_commit
*commit
;
4875 unsigned long lineno
;
4880 blame_open(struct view
*view
)
4882 char path
[SIZEOF_STR
];
4884 if (!view
->prev
&& *opt_prefix
) {
4885 string_copy(path
, opt_file
);
4886 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4890 if (*opt_ref
|| !io_open(&view
->io
, "%s%s", opt_cdup
, opt_file
)) {
4891 const char *blame_cat_file_argv
[] = {
4892 "git", "cat-file", "blob", path
, NULL
4895 if (!string_format(path
, "%s:%s", opt_ref
, opt_file
) ||
4896 !start_update(view
, blame_cat_file_argv
, opt_cdup
))
4900 setup_update(view
, opt_file
);
4901 string_format(view
->ref
, "%s ...", opt_file
);
4906 static struct blame_commit
*
4907 get_blame_commit(struct view
*view
, const char *id
)
4911 for (i
= 0; i
< view
->lines
; i
++) {
4912 struct blame
*blame
= view
->line
[i
].data
;
4917 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4918 return blame
->commit
;
4922 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4925 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4931 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4933 const char *pos
= *posref
;
4936 pos
= strchr(pos
+ 1, ' ');
4937 if (!pos
|| !isdigit(pos
[1]))
4939 *number
= atoi(pos
+ 1);
4940 if (*number
< min
|| *number
> max
)
4947 static struct blame_commit
*
4948 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4950 struct blame_commit
*commit
;
4951 struct blame
*blame
;
4952 const char *pos
= text
+ SIZEOF_REV
- 2;
4953 size_t orig_lineno
= 0;
4957 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4960 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4961 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4962 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4965 commit
= get_blame_commit(view
, text
);
4971 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4974 blame
->commit
= commit
;
4975 blame
->lineno
= orig_lineno
+ group
- 1;
4983 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4986 const char *blame_argv
[] = {
4987 "git", "blame", "--incremental",
4988 *opt_ref
? opt_ref
: "--incremental", "--", opt_file
, NULL
4991 if (view
->lines
== 0 && !view
->prev
)
4992 die("No blame exist for %s", view
->vid
);
4994 if (view
->lines
== 0 || !start_update(view
, blame_argv
, opt_cdup
)) {
4995 report("Failed to load blame data");
5003 size_t linelen
= strlen(line
);
5004 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
5009 blame
->commit
= NULL
;
5010 strncpy(blame
->text
, line
, linelen
);
5011 blame
->text
[linelen
] = 0;
5012 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
5017 match_blame_header(const char *name
, char **line
)
5019 size_t namelen
= strlen(name
);
5020 bool matched
= !strncmp(name
, *line
, namelen
);
5029 blame_read(struct view
*view
, char *line
)
5031 static struct blame_commit
*commit
= NULL
;
5032 static int blamed
= 0;
5033 static bool read_file
= TRUE
;
5036 return blame_read_file(view
, line
, &read_file
);
5043 string_format(view
->ref
, "%s", view
->vid
);
5044 if (view_is_displayed(view
)) {
5045 update_view_title(view
);
5046 redraw_view_from(view
, 0);
5052 commit
= parse_blame_commit(view
, line
, &blamed
);
5053 string_format(view
->ref
, "%s %2d%%", view
->vid
,
5054 view
->lines
? blamed
* 100 / view
->lines
: 0);
5056 } else if (match_blame_header("author ", &line
)) {
5057 commit
->author
= get_author(line
);
5059 } else if (match_blame_header("author-time ", &line
)) {
5060 parse_timesec(&commit
->time
, line
);
5062 } else if (match_blame_header("author-tz ", &line
)) {
5063 parse_timezone(&commit
->time
, line
);
5065 } else if (match_blame_header("summary ", &line
)) {
5066 string_ncopy(commit
->title
, line
, strlen(line
));
5068 } else if (match_blame_header("previous ", &line
)) {
5069 commit
->has_previous
= TRUE
;
5071 } else if (match_blame_header("filename ", &line
)) {
5072 string_ncopy(commit
->filename
, line
, strlen(line
));
5080 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5082 struct blame
*blame
= line
->data
;
5083 struct time
*time
= NULL
;
5084 const char *id
= NULL
, *author
= NULL
;
5085 char text
[SIZEOF_STR
];
5087 if (blame
->commit
&& *blame
->commit
->filename
) {
5088 id
= blame
->commit
->id
;
5089 author
= blame
->commit
->author
;
5090 time
= &blame
->commit
->time
;
5093 if (opt_date
&& draw_date(view
, time
))
5096 if (opt_author
&& draw_author(view
, author
))
5099 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
5102 if (draw_lineno(view
, lineno
))
5105 string_expand(text
, sizeof(text
), blame
->text
, opt_tab_size
);
5106 draw_text(view
, LINE_DEFAULT
, text
, TRUE
);
5111 check_blame_commit(struct blame
*blame
, bool check_null_id
)
5114 report("Commit data not loaded yet");
5115 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5116 report("No commit exist for the selected line");
5123 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
5125 const char *diff_tree_argv
[] = {
5126 "git", "diff-tree", "-U0", blame
->commit
->id
,
5127 "--", blame
->commit
->filename
, NULL
5130 int parent_lineno
= -1;
5131 int blamed_lineno
= -1;
5134 if (!io_run(&io
, IO_RD
, NULL
, diff_tree_argv
))
5137 while ((line
= io_get(&io
, '\n', TRUE
))) {
5139 char *pos
= strchr(line
, '+');
5141 parent_lineno
= atoi(line
+ 4);
5143 blamed_lineno
= atoi(pos
+ 1);
5145 } else if (*line
== '+' && parent_lineno
!= -1) {
5146 if (blame
->lineno
== blamed_lineno
- 1 &&
5147 !strcmp(blame
->text
, line
+ 1)) {
5148 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
5159 blame_request(struct view
*view
, enum request request
, struct line
*line
)
5161 enum open_flags flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
5162 struct blame
*blame
= line
->data
;
5165 case REQ_VIEW_BLAME
:
5166 if (check_blame_commit(blame
, TRUE
)) {
5167 string_copy(opt_ref
, blame
->commit
->id
);
5168 string_copy(opt_file
, blame
->commit
->filename
);
5170 view
->lineno
= blame
->lineno
;
5171 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5176 if (check_blame_commit(blame
, TRUE
) &&
5177 select_commit_parent(blame
->commit
->id
, opt_ref
,
5178 blame
->commit
->filename
)) {
5179 string_copy(opt_file
, blame
->commit
->filename
);
5180 setup_blame_parent_line(view
, blame
);
5181 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5186 if (!check_blame_commit(blame
, FALSE
))
5189 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
5190 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
5193 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
5194 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
5195 const char *diff_index_argv
[] = {
5196 "git", "diff-index", "--root", "--patch-with-stat",
5197 "-C", "-M", "HEAD", "--", view
->vid
, NULL
5200 if (!blame
->commit
->has_previous
) {
5201 diff_index_argv
[1] = "diff";
5202 diff_index_argv
[2] = "--no-color";
5203 diff_index_argv
[6] = "--";
5204 diff_index_argv
[7] = "/dev/null";
5207 if (!prepare_update(diff
, diff_index_argv
, NULL
)) {
5208 report("Failed to allocate diff command");
5211 flags
|= OPEN_PREPARED
;
5214 open_view(view
, REQ_VIEW_DIFF
, flags
);
5215 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5216 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
5227 blame_grep(struct view
*view
, struct line
*line
)
5229 struct blame
*blame
= line
->data
;
5230 struct blame_commit
*commit
= blame
->commit
;
5231 const char *text
[] = {
5233 commit
? commit
->title
: "",
5234 commit
? commit
->id
: "",
5235 commit
&& opt_author
? commit
->author
: "",
5236 commit
? mkdate(&commit
->time
, opt_date
) : "",
5240 return grep_text(view
, text
);
5244 blame_select(struct view
*view
, struct line
*line
)
5246 struct blame
*blame
= line
->data
;
5247 struct blame_commit
*commit
= blame
->commit
;
5252 if (!strcmp(commit
->id
, NULL_ID
))
5253 string_ncopy(ref_commit
, "HEAD", 4);
5255 string_copy_rev(ref_commit
, commit
->id
);
5258 static struct view_ops blame_ops
= {
5274 const char *author
; /* Author of the last commit. */
5275 struct time time
; /* Date of the last activity. */
5276 const struct ref
*ref
; /* Name and commit ID information. */
5279 static const struct ref branch_all
;
5281 static const enum sort_field branch_sort_fields
[] = {
5282 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5284 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5287 branch_compare(const void *l1
, const void *l2
)
5289 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5290 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5292 switch (get_sort_field(branch_sort_state
)) {
5294 return sort_order(branch_sort_state
, timecmp(&branch1
->time
, &branch2
->time
));
5296 case ORDERBY_AUTHOR
:
5297 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5301 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5306 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5308 struct branch
*branch
= line
->data
;
5309 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5311 if (opt_date
&& draw_date(view
, &branch
->time
))
5314 if (opt_author
&& draw_author(view
, branch
->author
))
5317 draw_text(view
, type
, branch
->ref
== &branch_all
? "All branches" : branch
->ref
->name
, TRUE
);
5322 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5324 struct branch
*branch
= line
->data
;
5329 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5332 case REQ_TOGGLE_SORT_FIELD
:
5333 case REQ_TOGGLE_SORT_ORDER
:
5334 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5338 if (branch
->ref
== &branch_all
) {
5339 const char *all_branches_argv
[] = {
5340 "git", "log", "--no-color", "--pretty=raw", "--parents",
5341 "--topo-order", "--all", NULL
5343 struct view
*main_view
= VIEW(REQ_VIEW_MAIN
);
5345 if (!prepare_update(main_view
, all_branches_argv
, NULL
)) {
5346 report("Failed to load view of all branches");
5349 open_view(view
, REQ_VIEW_MAIN
, OPEN_PREPARED
| OPEN_SPLIT
);
5351 open_view(view
, REQ_VIEW_MAIN
, OPEN_SPLIT
);
5361 branch_read(struct view
*view
, char *line
)
5363 static char id
[SIZEOF_REV
];
5364 struct branch
*reference
;
5370 switch (get_line_type(line
)) {
5372 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5376 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5377 struct branch
*branch
= view
->line
[i
].data
;
5379 if (strcmp(branch
->ref
->id
, id
))
5382 view
->line
[i
].dirty
= TRUE
;
5384 branch
->author
= reference
->author
;
5385 branch
->time
= reference
->time
;
5389 parse_author_line(line
+ STRING_SIZE("author "),
5390 &branch
->author
, &branch
->time
);
5402 branch_open_visitor(void *data
, const struct ref
*ref
)
5404 struct view
*view
= data
;
5405 struct branch
*branch
;
5407 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5410 branch
= calloc(1, sizeof(*branch
));
5415 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5419 branch_open(struct view
*view
)
5421 const char *branch_log
[] = {
5422 "git", "log", "--no-color", "--pretty=raw",
5423 "--simplify-by-decoration", "--all", NULL
5426 if (!start_update(view
, branch_log
, NULL
)) {
5427 report("Failed to load branch data");
5431 setup_update(view
, view
->id
);
5432 branch_open_visitor(view
, &branch_all
);
5433 foreach_ref(branch_open_visitor
, view
);
5434 view
->p_restore
= TRUE
;
5440 branch_grep(struct view
*view
, struct line
*line
)
5442 struct branch
*branch
= line
->data
;
5443 const char *text
[] = {
5449 return grep_text(view
, text
);
5453 branch_select(struct view
*view
, struct line
*line
)
5455 struct branch
*branch
= line
->data
;
5457 string_copy_rev(view
->ref
, branch
->ref
->id
);
5458 string_copy_rev(ref_commit
, branch
->ref
->id
);
5459 string_copy_rev(ref_head
, branch
->ref
->id
);
5460 string_copy_rev(ref_branch
, branch
->ref
->name
);
5463 static struct view_ops branch_ops
= {
5482 char rev
[SIZEOF_REV
];
5483 char name
[SIZEOF_STR
];
5487 char rev
[SIZEOF_REV
];
5488 char name
[SIZEOF_STR
];
5492 static char status_onbranch
[SIZEOF_STR
];
5493 static struct status stage_status
;
5494 static enum line_type stage_line_type
;
5495 static size_t stage_chunks
;
5496 static int *stage_chunk
;
5498 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5500 /* This should work even for the "On branch" line. */
5502 status_has_none(struct view
*view
, struct line
*line
)
5504 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5507 /* Get fields from the diff line:
5508 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5511 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5513 const char *old_mode
= buf
+ 1;
5514 const char *new_mode
= buf
+ 8;
5515 const char *old_rev
= buf
+ 15;
5516 const char *new_rev
= buf
+ 56;
5517 const char *status
= buf
+ 97;
5520 old_mode
[-1] != ':' ||
5521 new_mode
[-1] != ' ' ||
5522 old_rev
[-1] != ' ' ||
5523 new_rev
[-1] != ' ' ||
5527 file
->status
= *status
;
5529 string_copy_rev(file
->old
.rev
, old_rev
);
5530 string_copy_rev(file
->new.rev
, new_rev
);
5532 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5533 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5535 file
->old
.name
[0] = file
->new.name
[0] = 0;
5541 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5543 struct status
*unmerged
= NULL
;
5547 if (!io_run(&io
, IO_RD
, opt_cdup
, argv
))
5550 add_line_data(view
, NULL
, type
);
5552 while ((buf
= io_get(&io
, 0, TRUE
))) {
5553 struct status
*file
= unmerged
;
5556 file
= calloc(1, sizeof(*file
));
5557 if (!file
|| !add_line_data(view
, file
, type
))
5561 /* Parse diff info part. */
5563 file
->status
= status
;
5565 string_copy(file
->old
.rev
, NULL_ID
);
5567 } else if (!file
->status
|| file
== unmerged
) {
5568 if (!status_get_diff(file
, buf
, strlen(buf
)))
5571 buf
= io_get(&io
, 0, TRUE
);
5575 /* Collapse all modified entries that follow an
5576 * associated unmerged entry. */
5577 if (unmerged
== file
) {
5578 unmerged
->status
= 'U';
5580 } else if (file
->status
== 'U') {
5585 /* Grab the old name for rename/copy. */
5586 if (!*file
->old
.name
&&
5587 (file
->status
== 'R' || file
->status
== 'C')) {
5588 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5590 buf
= io_get(&io
, 0, TRUE
);
5595 /* git-ls-files just delivers a NUL separated list of
5596 * file names similar to the second half of the
5597 * git-diff-* output. */
5598 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5599 if (!*file
->old
.name
)
5600 string_copy(file
->old
.name
, file
->new.name
);
5604 if (io_error(&io
)) {
5610 if (!view
->line
[view
->lines
- 1].data
)
5611 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5617 /* Don't show unmerged entries in the staged section. */
5618 static const char *status_diff_index_argv
[] = {
5619 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5620 "--cached", "-M", "HEAD", NULL
5623 static const char *status_diff_files_argv
[] = {
5624 "git", "diff-files", "-z", NULL
5627 static const char *status_list_other_argv
[] = {
5628 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix
, NULL
5631 static const char *status_list_no_head_argv
[] = {
5632 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5635 static const char *update_index_argv
[] = {
5636 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5639 /* Restore the previous line number to stay in the context or select a
5640 * line with something that can be updated. */
5642 status_restore(struct view
*view
)
5644 if (view
->p_lineno
>= view
->lines
)
5645 view
->p_lineno
= view
->lines
- 1;
5646 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5648 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5651 /* If the above fails, always skip the "On branch" line. */
5652 if (view
->p_lineno
< view
->lines
)
5653 view
->lineno
= view
->p_lineno
;
5657 if (view
->lineno
< view
->offset
)
5658 view
->offset
= view
->lineno
;
5659 else if (view
->offset
+ view
->height
<= view
->lineno
)
5660 view
->offset
= view
->lineno
- view
->height
+ 1;
5662 view
->p_restore
= FALSE
;
5666 status_update_onbranch(void)
5668 static const char *paths
[][2] = {
5669 { "rebase-apply/rebasing", "Rebasing" },
5670 { "rebase-apply/applying", "Applying mailbox" },
5671 { "rebase-apply/", "Rebasing mailbox" },
5672 { "rebase-merge/interactive", "Interactive rebase" },
5673 { "rebase-merge/", "Rebase merge" },
5674 { "MERGE_HEAD", "Merging" },
5675 { "BISECT_LOG", "Bisecting" },
5676 { "HEAD", "On branch" },
5678 char buf
[SIZEOF_STR
];
5682 if (is_initial_commit()) {
5683 string_copy(status_onbranch
, "Initial commit");
5687 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5688 char *head
= opt_head
;
5690 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5691 lstat(buf
, &stat
) < 0)
5697 if (io_open(&io
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5698 io_read_buf(&io
, buf
, sizeof(buf
))) {
5700 if (!prefixcmp(head
, "refs/heads/"))
5701 head
+= STRING_SIZE("refs/heads/");
5705 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5706 string_copy(status_onbranch
, opt_head
);
5710 string_copy(status_onbranch
, "Not currently on any branch");
5713 /* First parse staged info using git-diff-index(1), then parse unstaged
5714 * info using git-diff-files(1), and finally untracked files using
5715 * git-ls-files(1). */
5717 status_open(struct view
*view
)
5721 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5722 status_update_onbranch();
5724 io_run_bg(update_index_argv
);
5726 if (is_initial_commit()) {
5727 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5729 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5733 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5734 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5737 /* Restore the exact position or use the specialized restore
5739 if (!view
->p_restore
)
5740 status_restore(view
);
5745 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5747 struct status
*status
= line
->data
;
5748 enum line_type type
;
5752 switch (line
->type
) {
5753 case LINE_STAT_STAGED
:
5754 type
= LINE_STAT_SECTION
;
5755 text
= "Changes to be committed:";
5758 case LINE_STAT_UNSTAGED
:
5759 type
= LINE_STAT_SECTION
;
5760 text
= "Changed but not updated:";
5763 case LINE_STAT_UNTRACKED
:
5764 type
= LINE_STAT_SECTION
;
5765 text
= "Untracked files:";
5768 case LINE_STAT_NONE
:
5769 type
= LINE_DEFAULT
;
5770 text
= " (no files)";
5773 case LINE_STAT_HEAD
:
5774 type
= LINE_STAT_HEAD
;
5775 text
= status_onbranch
;
5782 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5784 buf
[0] = status
->status
;
5785 if (draw_text(view
, line
->type
, buf
, TRUE
))
5787 type
= LINE_DEFAULT
;
5788 text
= status
->new.name
;
5791 draw_text(view
, type
, text
, TRUE
);
5796 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5798 if (displayed_views() == 2 || display
[current_view
] != view
)
5799 maximize_view(view
);
5800 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5805 status_enter(struct view
*view
, struct line
*line
)
5807 struct status
*status
= line
->data
;
5808 const char *oldpath
= status
? status
->old
.name
: NULL
;
5809 /* Diffs for unmerged entries are empty when passing the new
5810 * path, so leave it empty. */
5811 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5813 enum open_flags split
;
5814 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5816 if (line
->type
== LINE_STAT_NONE
||
5817 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5818 report("No file to diff");
5822 switch (line
->type
) {
5823 case LINE_STAT_STAGED
:
5824 if (is_initial_commit()) {
5825 const char *no_head_diff_argv
[] = {
5826 "git", "diff", "--no-color", "--patch-with-stat",
5827 "--", "/dev/null", newpath
, NULL
5830 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
))
5831 return status_load_error(view
, stage
, newpath
);
5833 const char *index_show_argv
[] = {
5834 "git", "diff-index", "--root", "--patch-with-stat",
5835 "-C", "-M", "--cached", "HEAD", "--",
5836 oldpath
, newpath
, NULL
5839 if (!prepare_update(stage
, index_show_argv
, opt_cdup
))
5840 return status_load_error(view
, stage
, newpath
);
5844 info
= "Staged changes to %s";
5846 info
= "Staged changes";
5849 case LINE_STAT_UNSTAGED
:
5851 const char *files_show_argv
[] = {
5852 "git", "diff-files", "--root", "--patch-with-stat",
5853 "-C", "-M", "--", oldpath
, newpath
, NULL
5856 if (!prepare_update(stage
, files_show_argv
, opt_cdup
))
5857 return status_load_error(view
, stage
, newpath
);
5859 info
= "Unstaged changes to %s";
5861 info
= "Unstaged changes";
5864 case LINE_STAT_UNTRACKED
:
5866 report("No file to show");
5870 if (!suffixcmp(status
->new.name
, -1, "/")) {
5871 report("Cannot display a directory");
5875 if (!prepare_update_file(stage
, newpath
))
5876 return status_load_error(view
, stage
, newpath
);
5877 info
= "Untracked file %s";
5880 case LINE_STAT_HEAD
:
5884 die("line type %d not handled in switch", line
->type
);
5887 split
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
5888 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5889 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5891 stage_status
= *status
;
5893 memset(&stage_status
, 0, sizeof(stage_status
));
5896 stage_line_type
= line
->type
;
5898 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5905 status_exists(struct status
*status
, enum line_type type
)
5907 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5908 unsigned long lineno
;
5910 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5911 struct line
*line
= &view
->line
[lineno
];
5912 struct status
*pos
= line
->data
;
5914 if (line
->type
!= type
)
5916 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5917 select_view_line(view
, lineno
);
5920 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5921 select_view_line(view
, lineno
);
5931 status_update_prepare(struct io
*io
, enum line_type type
)
5933 const char *staged_argv
[] = {
5934 "git", "update-index", "-z", "--index-info", NULL
5936 const char *others_argv
[] = {
5937 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5941 case LINE_STAT_STAGED
:
5942 return io_run(io
, IO_WR
, opt_cdup
, staged_argv
);
5944 case LINE_STAT_UNSTAGED
:
5945 case LINE_STAT_UNTRACKED
:
5946 return io_run(io
, IO_WR
, opt_cdup
, others_argv
);
5949 die("line type %d not handled in switch", type
);
5955 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5957 char buf
[SIZEOF_STR
];
5961 case LINE_STAT_STAGED
:
5962 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5965 status
->old
.name
, 0))
5969 case LINE_STAT_UNSTAGED
:
5970 case LINE_STAT_UNTRACKED
:
5971 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
5976 die("line type %d not handled in switch", type
);
5979 return io_write(io
, buf
, bufsize
);
5983 status_update_file(struct status
*status
, enum line_type type
)
5988 if (!status_update_prepare(&io
, type
))
5991 result
= status_update_write(&io
, status
, type
);
5992 return io_done(&io
) && result
;
5996 status_update_files(struct view
*view
, struct line
*line
)
5998 char buf
[sizeof(view
->ref
)];
6001 struct line
*pos
= view
->line
+ view
->lines
;
6004 int cursor_y
= -1, cursor_x
= -1;
6006 if (!status_update_prepare(&io
, line
->type
))
6009 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
6012 string_copy(buf
, view
->ref
);
6013 getsyx(cursor_y
, cursor_x
);
6014 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
6015 int almost_done
= file
* 100 / files
;
6017 if (almost_done
> done
) {
6019 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
6021 update_view_title(view
);
6022 setsyx(cursor_y
, cursor_x
);
6025 result
= status_update_write(&io
, line
->data
, line
->type
);
6027 string_copy(view
->ref
, buf
);
6029 return io_done(&io
) && result
;
6033 status_update(struct view
*view
)
6035 struct line
*line
= &view
->line
[view
->lineno
];
6037 assert(view
->lines
);
6040 /* This should work even for the "On branch" line. */
6041 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
6042 report("Nothing to update");
6046 if (!status_update_files(view
, line
+ 1)) {
6047 report("Failed to update file status");
6051 } else if (!status_update_file(line
->data
, line
->type
)) {
6052 report("Failed to update file status");
6060 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
6062 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
6063 if (type
== LINE_STAT_STAGED
) {
6064 report("Cannot revert changes to staged files");
6065 } else if (type
== LINE_STAT_UNTRACKED
) {
6066 report("Cannot revert changes to untracked files");
6067 } else if (has_none
) {
6068 report("Nothing to revert");
6070 report("Cannot revert changes to multiple files");
6073 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6074 char mode
[10] = "100644";
6075 const char *reset_argv
[] = {
6076 "git", "update-index", "--cacheinfo", mode
,
6077 status
->old
.rev
, status
->old
.name
, NULL
6079 const char *checkout_argv
[] = {
6080 "git", "checkout", "--", status
->old
.name
, NULL
6083 if (status
->status
== 'U') {
6084 string_format(mode
, "%5o", status
->old
.mode
);
6086 if (status
->old
.mode
== 0 && status
->new.mode
== 0) {
6087 reset_argv
[2] = "--force-remove";
6088 reset_argv
[3] = status
->old
.name
;
6089 reset_argv
[4] = NULL
;
6092 if (!io_run_fg(reset_argv
, opt_cdup
))
6094 if (status
->old
.mode
== 0 && status
->new.mode
== 0)
6098 return io_run_fg(checkout_argv
, opt_cdup
);
6105 status_request(struct view
*view
, enum request request
, struct line
*line
)
6107 struct status
*status
= line
->data
;
6110 case REQ_STATUS_UPDATE
:
6111 if (!status_update(view
))
6115 case REQ_STATUS_REVERT
:
6116 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
6120 case REQ_STATUS_MERGE
:
6121 if (!status
|| status
->status
!= 'U') {
6122 report("Merging only possible for files with unmerged status ('U').");
6125 open_mergetool(status
->new.name
);
6131 if (status
->status
== 'D') {
6132 report("File has been deleted.");
6136 open_editor(status
->new.name
);
6139 case REQ_VIEW_BLAME
:
6145 /* After returning the status view has been split to
6146 * show the stage view. No further reloading is
6148 return status_enter(view
, line
);
6151 /* Simply reload the view. */
6158 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
6164 status_select(struct view
*view
, struct line
*line
)
6166 struct status
*status
= line
->data
;
6167 char file
[SIZEOF_STR
] = "all files";
6171 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
6174 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
6177 switch (line
->type
) {
6178 case LINE_STAT_STAGED
:
6179 text
= "Press %s to unstage %s for commit";
6182 case LINE_STAT_UNSTAGED
:
6183 text
= "Press %s to stage %s for commit";
6186 case LINE_STAT_UNTRACKED
:
6187 text
= "Press %s to stage %s for addition";
6190 case LINE_STAT_HEAD
:
6191 case LINE_STAT_NONE
:
6192 text
= "Nothing to update";
6196 die("line type %d not handled in switch", line
->type
);
6199 if (status
&& status
->status
== 'U') {
6200 text
= "Press %s to resolve conflict in %s";
6201 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
6204 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
6207 string_format(view
->ref
, text
, key
, file
);
6209 string_copy(opt_file
, status
->new.name
);
6213 status_grep(struct view
*view
, struct line
*line
)
6215 struct status
*status
= line
->data
;
6218 const char buf
[2] = { status
->status
, 0 };
6219 const char *text
[] = { status
->new.name
, buf
, NULL
};
6221 return grep_text(view
, text
);
6227 static struct view_ops status_ops
= {
6240 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
6242 while (line
< end
) {
6243 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
6244 !io_write(io
, "\n", 1))
6247 if (line
->type
== LINE_DIFF_CHUNK
||
6248 line
->type
== LINE_DIFF_HEADER
)
6255 static struct line
*
6256 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
6258 for (; view
->line
< line
; line
--)
6259 if (line
->type
== type
)
6266 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
6268 const char *apply_argv
[SIZEOF_ARG
] = {
6269 "git", "apply", "--whitespace=nowarn", NULL
6271 struct line
*diff_hdr
;
6275 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6280 apply_argv
[argc
++] = "--cached";
6281 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6282 apply_argv
[argc
++] = "-R";
6283 apply_argv
[argc
++] = "-";
6284 apply_argv
[argc
++] = NULL
;
6285 if (!io_run(&io
, IO_WR
, opt_cdup
, apply_argv
))
6288 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6289 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6293 io_run_bg(update_index_argv
);
6295 return chunk
? TRUE
: FALSE
;
6299 stage_update(struct view
*view
, struct line
*line
)
6301 struct line
*chunk
= NULL
;
6303 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6304 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6307 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6308 report("Failed to apply chunk");
6312 } else if (!stage_status
.status
) {
6313 view
= VIEW(REQ_VIEW_STATUS
);
6315 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6316 if (line
->type
== stage_line_type
)
6319 if (!status_update_files(view
, line
+ 1)) {
6320 report("Failed to update files");
6324 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6325 report("Failed to update file");
6333 stage_revert(struct view
*view
, struct line
*line
)
6335 struct line
*chunk
= NULL
;
6337 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6338 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6341 if (!prompt_yesno("Are you sure you want to revert changes?"))
6344 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6345 report("Failed to revert chunk");
6351 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6352 stage_line_type
, FALSE
);
6358 stage_next(struct view
*view
, struct line
*line
)
6362 if (!stage_chunks
) {
6363 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6364 if (line
->type
!= LINE_DIFF_CHUNK
)
6367 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6368 report("Allocation failure");
6372 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6376 for (i
= 0; i
< stage_chunks
; i
++) {
6377 if (stage_chunk
[i
] > view
->lineno
) {
6378 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6379 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6384 report("No next chunk found");
6388 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6391 case REQ_STATUS_UPDATE
:
6392 if (!stage_update(view
, line
))
6396 case REQ_STATUS_REVERT
:
6397 if (!stage_revert(view
, line
))
6401 case REQ_STAGE_NEXT
:
6402 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6403 report("File is untracked; press %s to add",
6404 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6407 stage_next(view
, line
);
6411 if (!stage_status
.new.name
[0])
6413 if (stage_status
.status
== 'D') {
6414 report("File has been deleted.");
6418 open_editor(stage_status
.new.name
);
6422 /* Reload everything ... */
6425 case REQ_VIEW_BLAME
:
6426 if (stage_status
.new.name
[0]) {
6427 string_copy(opt_file
, stage_status
.new.name
);
6433 return pager_request(view
, request
, line
);
6439 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6440 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6442 /* Check whether the staged entry still exists, and close the
6443 * stage view if it doesn't. */
6444 if (!status_exists(&stage_status
, stage_line_type
)) {
6445 status_restore(VIEW(REQ_VIEW_STATUS
));
6446 return REQ_VIEW_CLOSE
;
6449 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6450 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6451 report("Cannot display a directory");
6455 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6456 report("Failed to open file: %s", strerror(errno
));
6460 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6465 static struct view_ops stage_ops
= {
6482 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6483 char title
[128]; /* First line of the commit message. */
6484 const char *author
; /* Author of the commit. */
6485 struct time time
; /* Date from the author ident. */
6486 struct ref_list
*refs
; /* Repository references. */
6487 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6488 size_t graph_size
; /* The width of the graph array. */
6489 bool has_parents
; /* Rewritten --parents seen. */
6492 /* Size of rev graph with no "padding" columns */
6493 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6496 struct rev_graph
*prev
, *next
, *parents
;
6497 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6499 struct commit
*commit
;
6501 unsigned int boundary
:1;
6504 /* Parents of the commit being visualized. */
6505 static struct rev_graph graph_parents
[4];
6507 /* The current stack of revisions on the graph. */
6508 static struct rev_graph graph_stacks
[4] = {
6509 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6510 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6511 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6512 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6516 graph_parent_is_merge(struct rev_graph
*graph
)
6518 return graph
->parents
->size
> 1;
6522 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6524 struct commit
*commit
= graph
->commit
;
6526 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6527 commit
->graph
[commit
->graph_size
++] = symbol
;
6531 clear_rev_graph(struct rev_graph
*graph
)
6533 graph
->boundary
= 0;
6534 graph
->size
= graph
->pos
= 0;
6535 graph
->commit
= NULL
;
6536 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6540 done_rev_graph(struct rev_graph
*graph
)
6542 if (graph_parent_is_merge(graph
) &&
6543 graph
->pos
< graph
->size
- 1 &&
6544 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6545 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6547 graph
->commit
->graph_size
= i
* 2;
6548 while (i
< graph
->next
->size
- 1) {
6549 append_to_rev_graph(graph
, ' ');
6550 append_to_rev_graph(graph
, '\\');
6555 clear_rev_graph(graph
);
6559 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6563 /* "Collapse" duplicate parents lines.
6565 * FIXME: This needs to also update update the drawn graph but
6566 * for now it just serves as a method for pruning graph lines. */
6567 for (i
= 0; i
< graph
->size
; i
++)
6568 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6571 if (graph
->size
< SIZEOF_REVITEMS
) {
6572 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6577 get_rev_graph_symbol(struct rev_graph
*graph
)
6581 if (graph
->boundary
)
6582 symbol
= REVGRAPH_BOUND
;
6583 else if (graph
->parents
->size
== 0)
6584 symbol
= REVGRAPH_INIT
;
6585 else if (graph_parent_is_merge(graph
))
6586 symbol
= REVGRAPH_MERGE
;
6587 else if (graph
->pos
>= graph
->size
)
6588 symbol
= REVGRAPH_BRANCH
;
6590 symbol
= REVGRAPH_COMMIT
;
6596 draw_rev_graph(struct rev_graph
*graph
)
6599 chtype separator
, line
;
6601 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6602 static struct rev_filler fillers
[] = {
6608 chtype symbol
= get_rev_graph_symbol(graph
);
6609 struct rev_filler
*filler
;
6612 fillers
[DEFAULT
].line
= opt_line_graphics
? ACS_VLINE
: '|';
6613 filler
= &fillers
[DEFAULT
];
6615 for (i
= 0; i
< graph
->pos
; i
++) {
6616 append_to_rev_graph(graph
, filler
->line
);
6617 if (graph_parent_is_merge(graph
->prev
) &&
6618 graph
->prev
->pos
== i
)
6619 filler
= &fillers
[RSHARP
];
6621 append_to_rev_graph(graph
, filler
->separator
);
6624 /* Place the symbol for this revision. */
6625 append_to_rev_graph(graph
, symbol
);
6627 if (graph
->prev
->size
> graph
->size
)
6628 filler
= &fillers
[RDIAG
];
6630 filler
= &fillers
[DEFAULT
];
6634 for (; i
< graph
->size
; i
++) {
6635 append_to_rev_graph(graph
, filler
->separator
);
6636 append_to_rev_graph(graph
, filler
->line
);
6637 if (graph_parent_is_merge(graph
->prev
) &&
6638 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6639 filler
= &fillers
[RSHARP
];
6640 if (graph
->prev
->size
> graph
->size
)
6641 filler
= &fillers
[LDIAG
];
6644 if (graph
->prev
->size
> graph
->size
) {
6645 append_to_rev_graph(graph
, filler
->separator
);
6646 if (filler
->line
!= ' ')
6647 append_to_rev_graph(graph
, filler
->line
);
6651 /* Prepare the next rev graph */
6653 prepare_rev_graph(struct rev_graph
*graph
)
6657 /* First, traverse all lines of revisions up to the active one. */
6658 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6659 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6662 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6665 /* Interleave the new revision parent(s). */
6666 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6667 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6669 /* Lastly, put any remaining revisions. */
6670 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6671 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6675 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6677 /* If this is the finalizing update ... */
6679 prepare_rev_graph(graph
);
6681 /* Graph visualization needs a one rev look-ahead,
6682 * so the first update doesn't visualize anything. */
6683 if (!graph
->prev
->commit
)
6686 if (view
->lines
> 2)
6687 view
->line
[view
->lines
- 3].dirty
= 1;
6688 if (view
->lines
> 1)
6689 view
->line
[view
->lines
- 2].dirty
= 1;
6690 draw_rev_graph(graph
->prev
);
6691 done_rev_graph(graph
->prev
->prev
);
6699 static const char *main_argv
[SIZEOF_ARG
] = {
6700 "git", "log", "--no-color", "--pretty=raw", "--parents",
6701 "--topo-order", "%(head)", NULL
6705 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6707 struct commit
*commit
= line
->data
;
6709 if (!commit
->author
)
6712 if (opt_date
&& draw_date(view
, &commit
->time
))
6715 if (opt_author
&& draw_author(view
, commit
->author
))
6718 if (opt_rev_graph
&& commit
->graph_size
&&
6719 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6722 if (opt_show_refs
&& commit
->refs
) {
6725 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6726 struct ref
*ref
= commit
->refs
->refs
[i
];
6727 enum line_type type
;
6730 type
= LINE_MAIN_HEAD
;
6732 type
= LINE_MAIN_LOCAL_TAG
;
6734 type
= LINE_MAIN_TAG
;
6735 else if (ref
->tracked
)
6736 type
= LINE_MAIN_TRACKED
;
6737 else if (ref
->remote
)
6738 type
= LINE_MAIN_REMOTE
;
6740 type
= LINE_MAIN_REF
;
6742 if (draw_text(view
, type
, "[", TRUE
) ||
6743 draw_text(view
, type
, ref
->name
, TRUE
) ||
6744 draw_text(view
, type
, "]", TRUE
))
6747 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6752 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6756 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6758 main_read(struct view
*view
, char *line
)
6760 static struct rev_graph
*graph
= graph_stacks
;
6761 enum line_type type
;
6762 struct commit
*commit
;
6767 if (!view
->lines
&& !view
->prev
)
6768 die("No revisions match the given arguments.");
6769 if (view
->lines
> 0) {
6770 commit
= view
->line
[view
->lines
- 1].data
;
6771 view
->line
[view
->lines
- 1].dirty
= 1;
6772 if (!commit
->author
) {
6775 graph
->commit
= NULL
;
6778 update_rev_graph(view
, graph
);
6780 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6781 clear_rev_graph(&graph_stacks
[i
]);
6785 type
= get_line_type(line
);
6786 if (type
== LINE_COMMIT
) {
6787 commit
= calloc(1, sizeof(struct commit
));
6791 line
+= STRING_SIZE("commit ");
6793 graph
->boundary
= 1;
6797 string_copy_rev(commit
->id
, line
);
6798 commit
->refs
= get_ref_list(commit
->id
);
6799 graph
->commit
= commit
;
6800 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6802 while ((line
= strchr(line
, ' '))) {
6804 push_rev_graph(graph
->parents
, line
);
6805 commit
->has_parents
= TRUE
;
6812 commit
= view
->line
[view
->lines
- 1].data
;
6816 if (commit
->has_parents
)
6818 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6822 parse_author_line(line
+ STRING_SIZE("author "),
6823 &commit
->author
, &commit
->time
);
6824 update_rev_graph(view
, graph
);
6825 graph
= graph
->next
;
6829 /* Fill in the commit title if it has not already been set. */
6830 if (commit
->title
[0])
6833 /* Require titles to start with a non-space character at the
6834 * offset used by git log. */
6835 if (strncmp(line
, " ", 4))
6838 /* Well, if the title starts with a whitespace character,
6839 * try to be forgiving. Otherwise we end up with no title. */
6840 while (isspace(*line
))
6844 /* FIXME: More graceful handling of titles; append "..." to
6845 * shortened titles, etc. */
6847 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6848 view
->line
[view
->lines
- 1].dirty
= 1;
6855 main_request(struct view
*view
, enum request request
, struct line
*line
)
6857 enum open_flags flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
6861 open_view(view
, REQ_VIEW_DIFF
, flags
);
6865 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6875 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6880 if (!opt_show_refs
|| !list
)
6883 for (i
= 0; i
< list
->size
; i
++) {
6884 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6892 main_grep(struct view
*view
, struct line
*line
)
6894 struct commit
*commit
= line
->data
;
6895 const char *text
[] = {
6897 opt_author
? commit
->author
: "",
6898 mkdate(&commit
->time
, opt_date
),
6902 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6906 main_select(struct view
*view
, struct line
*line
)
6908 struct commit
*commit
= line
->data
;
6910 string_copy_rev(view
->ref
, commit
->id
);
6911 string_copy_rev(ref_commit
, view
->ref
);
6914 static struct view_ops main_ops
= {
6930 /* Whether or not the curses interface has been initialized. */
6931 static bool cursed
= FALSE
;
6933 /* Terminal hacks and workarounds. */
6934 static bool use_scroll_redrawwin
;
6935 static bool use_scroll_status_wclear
;
6937 /* The status window is used for polling keystrokes. */
6938 static WINDOW
*status_win
;
6940 /* Reading from the prompt? */
6941 static bool input_mode
= FALSE
;
6943 static bool status_empty
= FALSE
;
6945 /* Update status and title window. */
6947 report(const char *msg
, ...)
6949 struct view
*view
= display
[current_view
];
6955 char buf
[SIZEOF_STR
];
6958 va_start(args
, msg
);
6959 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6960 buf
[sizeof(buf
) - 1] = 0;
6961 buf
[sizeof(buf
) - 2] = '.';
6962 buf
[sizeof(buf
) - 3] = '.';
6963 buf
[sizeof(buf
) - 4] = '.';
6969 if (!status_empty
|| *msg
) {
6972 va_start(args
, msg
);
6974 wmove(status_win
, 0, 0);
6975 if (view
->has_scrolled
&& use_scroll_status_wclear
)
6978 vwprintw(status_win
, msg
, args
);
6979 status_empty
= FALSE
;
6981 status_empty
= TRUE
;
6983 wclrtoeol(status_win
);
6984 wnoutrefresh(status_win
);
6989 update_view_title(view
);
6998 /* Initialize the curses library */
6999 if (isatty(STDIN_FILENO
)) {
7000 cursed
= !!initscr();
7003 /* Leave stdin and stdout alone when acting as a pager. */
7004 opt_tty
= fopen("/dev/tty", "r+");
7006 die("Failed to open /dev/tty");
7007 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
7011 die("Failed to initialize curses");
7013 nonl(); /* Disable conversion and detect newlines from input. */
7014 cbreak(); /* Take input chars one at a time, no wait for \n */
7015 noecho(); /* Don't echo input */
7016 leaveok(stdscr
, FALSE
);
7021 getmaxyx(stdscr
, y
, x
);
7022 status_win
= newwin(1, 0, y
- 1, 0);
7024 die("Failed to create status window");
7026 /* Enable keyboard mapping */
7027 keypad(status_win
, TRUE
);
7028 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
7030 TABSIZE
= opt_tab_size
;
7032 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
7033 if (term
&& !strcmp(term
, "gnome-terminal")) {
7034 /* In the gnome-terminal-emulator, the message from
7035 * scrolling up one line when impossible followed by
7036 * scrolling down one line causes corruption of the
7037 * status line. This is fixed by calling wclear. */
7038 use_scroll_status_wclear
= TRUE
;
7039 use_scroll_redrawwin
= FALSE
;
7041 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
7042 /* No problems with full optimizations in xrvt-(unicode)
7044 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
7047 /* When scrolling in (u)xterm the last line in the
7048 * scrolling direction will update slowly. */
7049 use_scroll_redrawwin
= TRUE
;
7050 use_scroll_status_wclear
= FALSE
;
7055 get_input(int prompt_position
)
7058 int i
, key
, cursor_y
, cursor_x
;
7059 bool loading
= FALSE
;
7061 if (prompt_position
)
7065 foreach_view (view
, i
) {
7067 if (view_is_displayed(view
) && view
->has_scrolled
&&
7068 use_scroll_redrawwin
)
7069 redrawwin(view
->win
);
7070 view
->has_scrolled
= FALSE
;
7075 /* Update the cursor position. */
7076 if (prompt_position
) {
7077 getbegyx(status_win
, cursor_y
, cursor_x
);
7078 cursor_x
= prompt_position
;
7080 view
= display
[current_view
];
7081 getbegyx(view
->win
, cursor_y
, cursor_x
);
7082 cursor_x
= view
->width
- 1;
7083 cursor_y
+= view
->lineno
- view
->offset
;
7085 setsyx(cursor_y
, cursor_x
);
7087 /* Refresh, accept single keystroke of input */
7089 nodelay(status_win
, loading
);
7090 key
= wgetch(status_win
);
7092 /* wgetch() with nodelay() enabled returns ERR when
7093 * there's no input. */
7096 } else if (key
== KEY_RESIZE
) {
7099 getmaxyx(stdscr
, height
, width
);
7101 wresize(status_win
, 1, width
);
7102 mvwin(status_win
, height
- 1, 0);
7103 wnoutrefresh(status_win
);
7105 redraw_display(TRUE
);
7115 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7117 enum input_status status
= INPUT_OK
;
7118 static char buf
[SIZEOF_STR
];
7123 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7126 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7127 wclrtoeol(status_win
);
7129 key
= get_input(pos
+ 1);
7134 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7141 status
= INPUT_CANCEL
;
7145 status
= INPUT_CANCEL
;
7149 if (pos
>= sizeof(buf
)) {
7150 report("Input string too long");
7154 status
= handler(data
, buf
, key
);
7155 if (status
== INPUT_OK
)
7156 buf
[pos
++] = (char) key
;
7160 /* Clear the status window */
7161 status_empty
= FALSE
;
7164 if (status
== INPUT_CANCEL
)
7172 static enum input_status
7173 prompt_yesno_handler(void *data
, char *buf
, int c
)
7175 if (c
== 'y' || c
== 'Y')
7177 if (c
== 'n' || c
== 'N')
7178 return INPUT_CANCEL
;
7183 prompt_yesno(const char *prompt
)
7185 char prompt2
[SIZEOF_STR
];
7187 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7190 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7193 static enum input_status
7194 read_prompt_handler(void *data
, char *buf
, int c
)
7196 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7200 read_prompt(const char *prompt
)
7202 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7205 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7207 enum input_status status
= INPUT_OK
;
7210 while (items
[size
].text
)
7213 while (status
== INPUT_OK
) {
7214 const struct menu_item
*item
= &items
[*selected
];
7218 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7219 prompt
, *selected
+ 1, size
);
7221 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7222 wprintw(status_win
, "%s", item
->text
);
7223 wclrtoeol(status_win
);
7225 key
= get_input(COLS
- 1);
7230 status
= INPUT_STOP
;
7235 *selected
= *selected
- 1;
7237 *selected
= size
- 1;
7242 *selected
= (*selected
+ 1) % size
;
7246 status
= INPUT_CANCEL
;
7250 for (i
= 0; items
[i
].text
; i
++)
7251 if (items
[i
].hotkey
== key
) {
7253 status
= INPUT_STOP
;
7259 /* Clear the status window */
7260 status_empty
= FALSE
;
7263 return status
!= INPUT_CANCEL
;
7267 * Repository properties
7270 static struct ref
**refs
= NULL
;
7271 static size_t refs_size
= 0;
7272 static struct ref
*refs_head
= NULL
;
7274 static struct ref_list
**ref_lists
= NULL
;
7275 static size_t ref_lists_size
= 0;
7277 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7278 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7279 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7282 compare_refs(const void *ref1_
, const void *ref2_
)
7284 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7285 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7287 if (ref1
->tag
!= ref2
->tag
)
7288 return ref2
->tag
- ref1
->tag
;
7289 if (ref1
->ltag
!= ref2
->ltag
)
7290 return ref2
->ltag
- ref2
->ltag
;
7291 if (ref1
->head
!= ref2
->head
)
7292 return ref2
->head
- ref1
->head
;
7293 if (ref1
->tracked
!= ref2
->tracked
)
7294 return ref2
->tracked
- ref1
->tracked
;
7295 if (ref1
->remote
!= ref2
->remote
)
7296 return ref2
->remote
- ref1
->remote
;
7297 return strcmp(ref1
->name
, ref2
->name
);
7301 foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
)
7305 for (i
= 0; i
< refs_size
; i
++)
7306 if (!visitor(data
, refs
[i
]))
7316 static struct ref_list
*
7317 get_ref_list(const char *id
)
7319 struct ref_list
*list
;
7322 for (i
= 0; i
< ref_lists_size
; i
++)
7323 if (!strcmp(id
, ref_lists
[i
]->id
))
7324 return ref_lists
[i
];
7326 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7328 list
= calloc(1, sizeof(*list
));
7332 for (i
= 0; i
< refs_size
; i
++) {
7333 if (!strcmp(id
, refs
[i
]->id
) &&
7334 realloc_refs_list(&list
->refs
, list
->size
, 1))
7335 list
->refs
[list
->size
++] = refs
[i
];
7343 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7344 ref_lists
[ref_lists_size
++] = list
;
7349 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
7351 struct ref
*ref
= NULL
;
7354 bool remote
= FALSE
;
7355 bool tracked
= FALSE
;
7357 int from
= 0, to
= refs_size
- 1;
7359 if (!prefixcmp(name
, "refs/tags/")) {
7360 if (!suffixcmp(name
, namelen
, "^{}")) {
7368 namelen
-= STRING_SIZE("refs/tags/");
7369 name
+= STRING_SIZE("refs/tags/");
7371 } else if (!prefixcmp(name
, "refs/remotes/")) {
7373 namelen
-= STRING_SIZE("refs/remotes/");
7374 name
+= STRING_SIZE("refs/remotes/");
7375 tracked
= !strcmp(opt_remote
, name
);
7377 } else if (!prefixcmp(name
, "refs/heads/")) {
7378 namelen
-= STRING_SIZE("refs/heads/");
7379 name
+= STRING_SIZE("refs/heads/");
7380 if (!strncmp(opt_head
, name
, namelen
))
7383 } else if (!strcmp(name
, "HEAD")) {
7386 namelen
= strlen(opt_head
);
7391 /* If we are reloading or it's an annotated tag, replace the
7392 * previous SHA1 with the resolved commit id; relies on the fact
7393 * git-ls-remote lists the commit id of an annotated tag right
7394 * before the commit id it points to. */
7395 while (from
<= to
) {
7396 size_t pos
= (to
+ from
) / 2;
7397 int cmp
= strcmp(name
, refs
[pos
]->name
);
7411 if (!realloc_refs(&refs
, refs_size
, 1))
7413 ref
= calloc(1, sizeof(*ref
) + namelen
);
7416 memmove(refs
+ from
+ 1, refs
+ from
,
7417 (refs_size
- from
) * sizeof(*refs
));
7419 strncpy(ref
->name
, name
, namelen
);
7426 ref
->remote
= remote
;
7427 ref
->tracked
= tracked
;
7428 string_copy_rev(ref
->id
, id
);
7438 const char *head_argv
[] = {
7439 "git", "symbolic-ref", "HEAD", NULL
7441 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7442 "git", "ls-remote", opt_git_dir
, NULL
7444 static bool init
= FALSE
;
7448 if (!argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE"))
7449 die("TIG_LS_REMOTE contains too many arguments");
7456 if (io_run_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7457 !prefixcmp(opt_head
, "refs/heads/")) {
7458 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7460 memmove(opt_head
, offset
, strlen(offset
) + 1);
7464 for (i
= 0; i
< refs_size
; i
++)
7467 if (io_run_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7470 /* Update the ref lists to reflect changes. */
7471 for (i
= 0; i
< ref_lists_size
; i
++) {
7472 struct ref_list
*list
= ref_lists
[i
];
7475 for (old
= new = 0; old
< list
->size
; old
++)
7476 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7477 list
->refs
[new++] = list
->refs
[old
];
7485 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7487 if (!strcmp(name
, ".remote")) {
7488 string_ncopy(opt_remote
, value
, valuelen
);
7490 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7491 size_t from
= strlen(opt_remote
);
7493 if (!prefixcmp(value
, "refs/heads/"))
7494 value
+= STRING_SIZE("refs/heads/");
7496 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7502 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7504 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7505 int argc
= 1 + (cmd
== option_set_command
);
7508 if (!argv_from_string(argv
, &argc
, value
))
7509 config_msg
= "Too many option arguments";
7511 error
= cmd(argc
, argv
);
7514 warn("Option 'tig.%s': %s", name
, config_msg
);
7518 set_environment_variable(const char *name
, const char *value
)
7520 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7521 char *env
= malloc(len
);
7524 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7532 set_work_tree(const char *value
)
7534 char cwd
[SIZEOF_STR
];
7536 if (!getcwd(cwd
, sizeof(cwd
)))
7537 die("Failed to get cwd path: %s", strerror(errno
));
7538 if (chdir(opt_git_dir
) < 0)
7539 die("Failed to chdir(%s): %s", strerror(errno
));
7540 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7541 die("Failed to get git path: %s", strerror(errno
));
7543 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7544 if (chdir(value
) < 0)
7545 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7546 if (!getcwd(cwd
, sizeof(cwd
)))
7547 die("Failed to get cwd path: %s", strerror(errno
));
7548 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7549 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7550 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7551 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7552 opt_is_inside_work_tree
= TRUE
;
7556 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7558 if (!strcmp(name
, "i18n.commitencoding"))
7559 string_ncopy(opt_encoding
, value
, valuelen
);
7561 else if (!strcmp(name
, "core.editor"))
7562 string_ncopy(opt_editor
, value
, valuelen
);
7564 else if (!strcmp(name
, "core.worktree"))
7565 set_work_tree(value
);
7567 else if (!prefixcmp(name
, "tig.color."))
7568 set_repo_config_option(name
+ 10, value
, option_color_command
);
7570 else if (!prefixcmp(name
, "tig.bind."))
7571 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7573 else if (!prefixcmp(name
, "tig."))
7574 set_repo_config_option(name
+ 4, value
, option_set_command
);
7576 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7577 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7578 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7584 load_git_config(void)
7586 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7588 return io_run_load(config_list_argv
, "=", read_repo_config_option
);
7592 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7594 if (!opt_git_dir
[0]) {
7595 string_ncopy(opt_git_dir
, name
, namelen
);
7597 } else if (opt_is_inside_work_tree
== -1) {
7598 /* This can be 3 different values depending on the
7599 * version of git being used. If git-rev-parse does not
7600 * understand --is-inside-work-tree it will simply echo
7601 * the option else either "true" or "false" is printed.
7602 * Default to true for the unknown case. */
7603 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7605 } else if (*name
== '.') {
7606 string_ncopy(opt_cdup
, name
, namelen
);
7609 string_ncopy(opt_prefix
, name
, namelen
);
7616 load_repo_info(void)
7618 const char *rev_parse_argv
[] = {
7619 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7620 "--show-cdup", "--show-prefix", NULL
7623 return io_run_load(rev_parse_argv
, "=", read_repo_info
);
7631 static const char usage
[] =
7632 "tig " TIG_VERSION
" (" __DATE__
")\n"
7634 "Usage: tig [options] [revs] [--] [paths]\n"
7635 " or: tig show [options] [revs] [--] [paths]\n"
7636 " or: tig blame [rev] path\n"
7638 " or: tig < [git command output]\n"
7641 " -v, --version Show version and exit\n"
7642 " -h, --help Show help message and exit";
7644 static void __NORETURN
7647 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7653 static void __NORETURN
7654 die(const char *err
, ...)
7660 va_start(args
, err
);
7661 fputs("tig: ", stderr
);
7662 vfprintf(stderr
, err
, args
);
7663 fputs("\n", stderr
);
7670 warn(const char *msg
, ...)
7674 va_start(args
, msg
);
7675 fputs("tig warning: ", stderr
);
7676 vfprintf(stderr
, msg
, args
);
7677 fputs("\n", stderr
);
7682 parse_options(int argc
, const char *argv
[])
7684 enum request request
= REQ_VIEW_MAIN
;
7685 const char *subcommand
;
7686 bool seen_dashdash
= FALSE
;
7687 /* XXX: This is vulnerable to the user overriding options
7688 * required for the main view parser. */
7689 const char *custom_argv
[SIZEOF_ARG
] = {
7690 "git", "log", "--no-color", "--pretty=raw", "--parents",
7691 "--topo-order", NULL
7695 if (!isatty(STDIN_FILENO
)) {
7696 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7697 return REQ_VIEW_PAGER
;
7703 subcommand
= argv
[1];
7704 if (!strcmp(subcommand
, "status")) {
7706 warn("ignoring arguments after `%s'", subcommand
);
7707 return REQ_VIEW_STATUS
;
7709 } else if (!strcmp(subcommand
, "blame")) {
7710 if (argc
<= 2 || argc
> 4)
7711 die("invalid number of options to blame\n\n%s", usage
);
7715 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7719 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7720 return REQ_VIEW_BLAME
;
7722 } else if (!strcmp(subcommand
, "show")) {
7723 request
= REQ_VIEW_DIFF
;
7730 custom_argv
[1] = subcommand
;
7734 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7735 const char *opt
= argv
[i
];
7737 if (seen_dashdash
|| !strcmp(opt
, "--")) {
7738 seen_dashdash
= TRUE
;
7740 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7741 printf("tig version %s\n", TIG_VERSION
);
7744 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7745 printf("%s\n", usage
);
7749 custom_argv
[j
++] = opt
;
7750 if (j
>= ARRAY_SIZE(custom_argv
))
7751 die("command too long");
7754 if (!prepare_update(VIEW(request
), custom_argv
, NULL
))
7755 die("Failed to format arguments");
7761 main(int argc
, const char *argv
[])
7763 const char *codeset
= "UTF-8";
7764 enum request request
= parse_options(argc
, argv
);
7768 signal(SIGINT
, quit
);
7769 signal(SIGPIPE
, SIG_IGN
);
7771 if (setlocale(LC_ALL
, "")) {
7772 codeset
= nl_langinfo(CODESET
);
7775 if (load_repo_info() == ERR
)
7776 die("Failed to load repo info.");
7778 if (load_options() == ERR
)
7779 die("Failed to load user config.");
7781 if (load_git_config() == ERR
)
7782 die("Failed to load repo config.");
7784 /* Require a git repository unless when running in pager mode. */
7785 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7786 die("Not a git repository");
7788 if (*opt_encoding
&& strcmp(codeset
, "UTF-8")) {
7789 opt_iconv_in
= iconv_open("UTF-8", opt_encoding
);
7790 if (opt_iconv_in
== ICONV_NONE
)
7791 die("Failed to initialize character set conversion");
7794 if (codeset
&& strcmp(codeset
, "UTF-8")) {
7795 opt_iconv_out
= iconv_open(codeset
, "UTF-8");
7796 if (opt_iconv_out
== ICONV_NONE
)
7797 die("Failed to initialize character set conversion");
7800 if (load_refs() == ERR
)
7801 die("Failed to load refs.");
7803 foreach_view (view
, i
)
7804 if (!argv_from_env(view
->ops
->argv
, view
->cmd_env
))
7805 die("Too many arguments in the `%s` environment variable",
7810 if (request
!= REQ_NONE
)
7811 open_view(NULL
, request
, OPEN_PREPARED
);
7812 request
= request
== REQ_NONE
? REQ_VIEW_MAIN
: REQ_NONE
;
7814 while (view_driver(display
[current_view
], request
)) {
7815 int key
= get_input(0);
7817 view
= display
[current_view
];
7818 request
= get_keybinding(view
->keymap
, key
);
7820 /* Some low-level request handling. This keeps access to
7821 * status_win restricted. */
7824 report("Unknown key, press %s for help",
7825 get_key(view
->keymap
, REQ_VIEW_HELP
));
7829 char *cmd
= read_prompt(":");
7831 if (cmd
&& isdigit(*cmd
)) {
7832 int lineno
= view
->lineno
+ 1;
7834 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7835 select_view_line(view
, lineno
- 1);
7838 report("Unable to parse '%s' as a line number", cmd
);
7842 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7843 const char *argv
[SIZEOF_ARG
] = { "git" };
7846 /* When running random commands, initially show the
7847 * command in the title. However, it maybe later be
7848 * overwritten if a commit line is selected. */
7849 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7851 if (!argv_from_string(argv
, &argc
, cmd
)) {
7852 report("Too many arguments");
7853 } else if (!prepare_update(next
, argv
, NULL
)) {
7854 report("Failed to format command");
7856 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7864 case REQ_SEARCH_BACK
:
7866 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7867 char *search
= read_prompt(prompt
);
7870 string_ncopy(opt_search
, search
, strlen(search
));
7871 else if (*opt_search
)
7872 request
= request
== REQ_SEARCH
?