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_append_array(const char ***dst_argv
, const char *src_argv
[])
709 for (i
= 0; src_argv
&& src_argv
[i
]; i
++)
710 if (!argv_append(dst_argv
, src_argv
[i
]))
716 argv_copy(const char ***dst
, const char *src
[])
720 for (argc
= 0; src
[argc
]; argc
++)
721 if (!argv_append(dst
, src
[argc
]))
728 * Executing external commands.
732 IO_FD
, /* File descriptor based IO. */
733 IO_BG
, /* Execute command in the background. */
734 IO_FG
, /* Execute command with same std{in,out,err}. */
735 IO_RD
, /* Read only fork+exec IO. */
736 IO_WR
, /* Write only fork+exec IO. */
737 IO_AP
, /* Append fork+exec output to file. */
741 int pipe
; /* Pipe end for reading or writing. */
742 pid_t pid
; /* PID of spawned process. */
743 int error
; /* Error status. */
744 char *buf
; /* Read buffer. */
745 size_t bufalloc
; /* Allocated buffer size. */
746 size_t bufsize
; /* Buffer content size. */
747 char *bufpos
; /* Current buffer position. */
748 unsigned int eof
:1; /* Has end of file been reached. */
752 io_init(struct io
*io
)
754 memset(io
, 0, sizeof(*io
));
759 io_open(struct io
*io
, const char *fmt
, ...)
761 char name
[SIZEOF_STR
] = "";
768 fits
= vsnprintf(name
, sizeof(name
), fmt
, args
) < sizeof(name
);
772 io
->error
= ENAMETOOLONG
;
775 io
->pipe
= *name
? open(name
, O_RDONLY
) : STDIN_FILENO
;
778 return io
->pipe
!= -1;
782 io_kill(struct io
*io
)
784 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
788 io_done(struct io
*io
)
799 pid_t waiting
= waitpid(pid
, &status
, 0);
808 return waiting
== pid
&&
809 !WIFSIGNALED(status
) &&
811 !WEXITSTATUS(status
);
818 io_run(struct io
*io
, enum io_type type
, const char *dir
, const char *argv
[], ...)
820 int pipefds
[2] = { -1, -1 };
825 if ((type
== IO_RD
|| type
== IO_WR
) && pipe(pipefds
) < 0) {
828 } else if (type
== IO_AP
) {
829 va_start(args
, argv
);
830 pipefds
[1] = va_arg(args
, int);
834 if ((io
->pid
= fork())) {
837 if (pipefds
[!(type
== IO_WR
)] != -1)
838 close(pipefds
[!(type
== IO_WR
)]);
840 io
->pipe
= pipefds
[!!(type
== IO_WR
)];
846 int devnull
= open("/dev/null", O_RDWR
);
847 int readfd
= type
== IO_WR
? pipefds
[0] : devnull
;
848 int writefd
= (type
== IO_RD
|| type
== IO_AP
)
849 ? pipefds
[1] : devnull
;
851 dup2(readfd
, STDIN_FILENO
);
852 dup2(writefd
, STDOUT_FILENO
);
853 dup2(devnull
, STDERR_FILENO
);
856 if (pipefds
[0] != -1)
858 if (pipefds
[1] != -1)
862 if (dir
&& *dir
&& chdir(dir
) == -1)
865 execvp(argv
[0], (char *const*) argv
);
869 if (pipefds
[!!(type
== IO_WR
)] != -1)
870 close(pipefds
[!!(type
== IO_WR
)]);
875 io_complete(enum io_type type
, const char **argv
, const char *dir
, int fd
)
879 return io_run(&io
, type
, dir
, argv
, fd
) && io_done(&io
);
883 io_run_bg(const char **argv
)
885 return io_complete(IO_BG
, argv
, NULL
, -1);
889 io_run_fg(const char **argv
, const char *dir
)
891 return io_complete(IO_FG
, argv
, dir
, -1);
895 io_run_append(const char **argv
, int fd
)
897 return io_complete(IO_AP
, argv
, NULL
, fd
);
901 io_eof(struct io
*io
)
907 io_error(struct io
*io
)
913 io_strerror(struct io
*io
)
915 return strerror(io
->error
);
919 io_can_read(struct io
*io
)
921 struct timeval tv
= { 0, 500 };
925 FD_SET(io
->pipe
, &fds
);
927 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
931 io_read(struct io
*io
, void *buf
, size_t bufsize
)
934 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
936 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
938 else if (readsize
== -1)
940 else if (readsize
== 0)
946 DEFINE_ALLOCATOR(io_realloc_buf
, char, BUFSIZ
)
949 io_get(struct io
*io
, int c
, bool can_read
)
955 if (io
->bufsize
> 0) {
956 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
958 char *line
= io
->bufpos
;
961 io
->bufpos
= eol
+ 1;
962 io
->bufsize
-= io
->bufpos
- line
;
969 io
->bufpos
[io
->bufsize
] = 0;
979 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
980 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
982 if (io
->bufalloc
== io
->bufsize
) {
983 if (!io_realloc_buf(&io
->buf
, io
->bufalloc
, BUFSIZ
))
985 io
->bufalloc
+= BUFSIZ
;
988 io
->bufpos
= io
->buf
;
989 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
992 io
->bufsize
+= readsize
;
997 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
1001 while (!io_error(io
) && written
< bufsize
) {
1004 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
1005 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
1007 else if (size
== -1)
1013 return written
== bufsize
;
1017 io_read_buf(struct io
*io
, char buf
[], size_t bufsize
)
1019 char *result
= io_get(io
, '\n', TRUE
);
1022 result
= chomp_string(result
);
1023 string_ncopy_do(buf
, bufsize
, result
, strlen(result
));
1026 return io_done(io
) && result
;
1030 io_run_buf(const char **argv
, char buf
[], size_t bufsize
)
1034 return io_run(&io
, IO_RD
, NULL
, argv
) && io_read_buf(&io
, buf
, bufsize
);
1038 io_load(struct io
*io
, const char *separators
,
1039 int (*read_property
)(char *, size_t, char *, size_t))
1044 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
1049 name
= chomp_string(name
);
1050 namelen
= strcspn(name
, separators
);
1052 if (name
[namelen
]) {
1054 value
= chomp_string(name
+ namelen
+ 1);
1055 valuelen
= strlen(value
);
1062 state
= read_property(name
, namelen
, value
, valuelen
);
1065 if (state
!= ERR
&& io_error(io
))
1073 io_run_load(const char **argv
, const char *separators
,
1074 int (*read_property
)(char *, size_t, char *, size_t))
1078 if (!io_run(&io
, IO_RD
, NULL
, argv
))
1080 return io_load(&io
, separators
, read_property
);
1089 /* XXX: Keep the view request first and in sync with views[]. */ \
1090 REQ_GROUP("View switching") \
1091 REQ_(VIEW_MAIN, "Show main view"), \
1092 REQ_(VIEW_DIFF, "Show diff view"), \
1093 REQ_(VIEW_LOG, "Show log view"), \
1094 REQ_(VIEW_TREE, "Show tree view"), \
1095 REQ_(VIEW_BLOB, "Show blob view"), \
1096 REQ_(VIEW_BLAME, "Show blame view"), \
1097 REQ_(VIEW_BRANCH, "Show branch view"), \
1098 REQ_(VIEW_HELP, "Show help page"), \
1099 REQ_(VIEW_PAGER, "Show pager view"), \
1100 REQ_(VIEW_STATUS, "Show status view"), \
1101 REQ_(VIEW_STAGE, "Show stage view"), \
1103 REQ_GROUP("View manipulation") \
1104 REQ_(ENTER, "Enter current line and scroll"), \
1105 REQ_(NEXT, "Move to next"), \
1106 REQ_(PREVIOUS, "Move to previous"), \
1107 REQ_(PARENT, "Move to parent"), \
1108 REQ_(VIEW_NEXT, "Move focus to next view"), \
1109 REQ_(REFRESH, "Reload and refresh"), \
1110 REQ_(MAXIMIZE, "Maximize the current view"), \
1111 REQ_(VIEW_CLOSE, "Close the current view"), \
1112 REQ_(QUIT, "Close all views and quit"), \
1114 REQ_GROUP("View specific requests") \
1115 REQ_(STATUS_UPDATE, "Update file status"), \
1116 REQ_(STATUS_REVERT, "Revert file changes"), \
1117 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1118 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1120 REQ_GROUP("Cursor navigation") \
1121 REQ_(MOVE_UP, "Move cursor one line up"), \
1122 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1123 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1124 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1125 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1126 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1128 REQ_GROUP("Scrolling") \
1129 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1130 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1131 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1132 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1133 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1134 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1136 REQ_GROUP("Searching") \
1137 REQ_(SEARCH, "Search the view"), \
1138 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1139 REQ_(FIND_NEXT, "Find next search match"), \
1140 REQ_(FIND_PREV, "Find previous search match"), \
1142 REQ_GROUP("Option manipulation") \
1143 REQ_(OPTIONS, "Open option menu"), \
1144 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1145 REQ_(TOGGLE_DATE, "Toggle date display"), \
1146 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1147 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1148 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1149 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1150 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1151 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1154 REQ_(PROMPT, "Bring up the prompt"), \
1155 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1156 REQ_(SHOW_VERSION, "Show version information"), \
1157 REQ_(STOP_LOADING, "Stop all loading views"), \
1158 REQ_(EDIT, "Open in editor"), \
1159 REQ_(NONE, "Do nothing")
1162 /* User action requests. */
1164 #define REQ_GROUP(help)
1165 #define REQ_(req, help) REQ_##req
1167 /* Offset all requests to avoid conflicts with ncurses getch values. */
1168 REQ_UNKNOWN
= KEY_MAX
+ 1,
1176 struct request_info
{
1177 enum request request
;
1183 static const struct request_info req_info
[] = {
1184 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1185 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1192 get_request(const char *name
)
1194 int namelen
= strlen(name
);
1197 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
1198 if (enum_equals(req_info
[i
], name
, namelen
))
1199 return req_info
[i
].request
;
1209 /* Option and state variables. */
1210 static enum date opt_date
= DATE_DEFAULT
;
1211 static enum author opt_author
= AUTHOR_DEFAULT
;
1212 static bool opt_line_number
= FALSE
;
1213 static bool opt_line_graphics
= TRUE
;
1214 static bool opt_rev_graph
= FALSE
;
1215 static bool opt_show_refs
= TRUE
;
1216 static int opt_num_interval
= 5;
1217 static double opt_hscroll
= 0.50;
1218 static double opt_scale_split_view
= 2.0 / 3.0;
1219 static int opt_tab_size
= 8;
1220 static int opt_author_cols
= AUTHOR_COLS
;
1221 static char opt_path
[SIZEOF_STR
] = "";
1222 static char opt_file
[SIZEOF_STR
] = "";
1223 static char opt_ref
[SIZEOF_REF
] = "";
1224 static char opt_head
[SIZEOF_REF
] = "";
1225 static char opt_remote
[SIZEOF_REF
] = "";
1226 static char opt_encoding
[20] = "UTF-8";
1227 static iconv_t opt_iconv_in
= ICONV_NONE
;
1228 static iconv_t opt_iconv_out
= ICONV_NONE
;
1229 static char opt_search
[SIZEOF_STR
] = "";
1230 static char opt_cdup
[SIZEOF_STR
] = "";
1231 static char opt_prefix
[SIZEOF_STR
] = "";
1232 static char opt_git_dir
[SIZEOF_STR
] = "";
1233 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
1234 static char opt_editor
[SIZEOF_STR
] = "";
1235 static FILE *opt_tty
= NULL
;
1236 static const char **opt_diff_args
= NULL
;
1237 static const char **opt_rev_args
= NULL
;
1238 static const char **opt_file_args
= NULL
;
1240 #define is_initial_commit() (!get_ref_head())
1241 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1245 * Line-oriented content detection.
1249 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1250 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1251 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1252 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1253 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1254 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1263 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1264 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1265 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1266 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1270 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1271 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1272 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1273 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1274 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1275 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1276 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1277 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1279 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1280 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1281 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1282 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1283 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1284 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1285 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1286 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1287 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1288 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1289 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1290 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1291 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1292 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1293 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1294 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1295 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1296 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1297 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1298 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1299 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1300 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1301 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1302 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1303 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1304 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1305 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1306 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1307 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1310 #define LINE(type, line, fg, bg, attr) \
1318 const char *name
; /* Option name. */
1319 int namelen
; /* Size of option name. */
1320 const char *line
; /* The start of line to match. */
1321 int linelen
; /* Size of string to match. */
1322 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1325 static struct line_info line_info
[] = {
1326 #define LINE(type, line, fg, bg, attr) \
1327 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1332 static enum line_type
1333 get_line_type(const char *line
)
1335 int linelen
= strlen(line
);
1336 enum line_type type
;
1338 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1339 /* Case insensitive search matches Signed-off-by lines better. */
1340 if (linelen
>= line_info
[type
].linelen
&&
1341 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1344 return LINE_DEFAULT
;
1348 get_line_attr(enum line_type type
)
1350 assert(type
< ARRAY_SIZE(line_info
));
1351 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1354 static struct line_info
*
1355 get_line_info(const char *name
)
1357 size_t namelen
= strlen(name
);
1358 enum line_type type
;
1360 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1361 if (enum_equals(line_info
[type
], name
, namelen
))
1362 return &line_info
[type
];
1370 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1371 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1372 enum line_type type
;
1376 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1377 default_bg
= COLOR_BLACK
;
1378 default_fg
= COLOR_WHITE
;
1381 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1382 struct line_info
*info
= &line_info
[type
];
1383 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1384 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1386 init_pair(type
, fg
, bg
);
1391 enum line_type type
;
1394 unsigned int selected
:1;
1395 unsigned int dirty
:1;
1396 unsigned int cleareol
:1;
1397 unsigned int other
:16;
1399 void *data
; /* User data */
1409 enum request request
;
1412 static struct keybinding default_keybindings
[] = {
1413 /* View switching */
1414 { 'm', REQ_VIEW_MAIN
},
1415 { 'd', REQ_VIEW_DIFF
},
1416 { 'l', REQ_VIEW_LOG
},
1417 { 't', REQ_VIEW_TREE
},
1418 { 'f', REQ_VIEW_BLOB
},
1419 { 'B', REQ_VIEW_BLAME
},
1420 { 'H', REQ_VIEW_BRANCH
},
1421 { 'p', REQ_VIEW_PAGER
},
1422 { 'h', REQ_VIEW_HELP
},
1423 { 'S', REQ_VIEW_STATUS
},
1424 { 'c', REQ_VIEW_STAGE
},
1426 /* View manipulation */
1427 { 'q', REQ_VIEW_CLOSE
},
1428 { KEY_TAB
, REQ_VIEW_NEXT
},
1429 { KEY_RETURN
, REQ_ENTER
},
1430 { KEY_UP
, REQ_PREVIOUS
},
1431 { KEY_DOWN
, REQ_NEXT
},
1432 { 'R', REQ_REFRESH
},
1433 { KEY_F(5), REQ_REFRESH
},
1434 { 'O', REQ_MAXIMIZE
},
1436 /* Cursor navigation */
1437 { 'k', REQ_MOVE_UP
},
1438 { 'j', REQ_MOVE_DOWN
},
1439 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1440 { KEY_END
, REQ_MOVE_LAST_LINE
},
1441 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1442 { ' ', REQ_MOVE_PAGE_DOWN
},
1443 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1444 { 'b', REQ_MOVE_PAGE_UP
},
1445 { '-', REQ_MOVE_PAGE_UP
},
1448 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1449 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1450 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1451 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1452 { 'w', REQ_SCROLL_PAGE_UP
},
1453 { 's', REQ_SCROLL_PAGE_DOWN
},
1456 { '/', REQ_SEARCH
},
1457 { '?', REQ_SEARCH_BACK
},
1458 { 'n', REQ_FIND_NEXT
},
1459 { 'N', REQ_FIND_PREV
},
1463 { 'z', REQ_STOP_LOADING
},
1464 { 'v', REQ_SHOW_VERSION
},
1465 { 'r', REQ_SCREEN_REDRAW
},
1466 { 'o', REQ_OPTIONS
},
1467 { '.', REQ_TOGGLE_LINENO
},
1468 { 'D', REQ_TOGGLE_DATE
},
1469 { 'A', REQ_TOGGLE_AUTHOR
},
1470 { 'g', REQ_TOGGLE_REV_GRAPH
},
1471 { 'F', REQ_TOGGLE_REFS
},
1472 { 'I', REQ_TOGGLE_SORT_ORDER
},
1473 { 'i', REQ_TOGGLE_SORT_FIELD
},
1474 { ':', REQ_PROMPT
},
1475 { 'u', REQ_STATUS_UPDATE
},
1476 { '!', REQ_STATUS_REVERT
},
1477 { 'M', REQ_STATUS_MERGE
},
1478 { '@', REQ_STAGE_NEXT
},
1479 { ',', REQ_PARENT
},
1483 #define KEYMAP_INFO \
1498 #define KEYMAP_(name) KEYMAP_##name
1503 static const struct enum_map keymap_table
[] = {
1504 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1509 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1511 struct keybinding_table
{
1512 struct keybinding
*data
;
1516 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1519 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1521 struct keybinding_table
*table
= &keybindings
[keymap
];
1524 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1525 if (keybindings
[keymap
].data
[i
].alias
== key
) {
1526 keybindings
[keymap
].data
[i
].request
= request
;
1531 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1533 die("Failed to allocate keybinding");
1534 table
->data
[table
->size
].alias
= key
;
1535 table
->data
[table
->size
++].request
= request
;
1537 if (request
== REQ_NONE
&& keymap
== KEYMAP_GENERIC
) {
1540 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1541 if (default_keybindings
[i
].alias
== key
)
1542 default_keybindings
[i
].request
= REQ_NONE
;
1546 /* Looks for a key binding first in the given map, then in the generic map, and
1547 * lastly in the default keybindings. */
1549 get_keybinding(enum keymap keymap
, int key
)
1553 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1554 if (keybindings
[keymap
].data
[i
].alias
== key
)
1555 return keybindings
[keymap
].data
[i
].request
;
1557 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1558 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1559 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1561 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1562 if (default_keybindings
[i
].alias
== key
)
1563 return default_keybindings
[i
].request
;
1565 return (enum request
) key
;
1574 static const struct key key_table
[] = {
1575 { "Enter", KEY_RETURN
},
1577 { "Backspace", KEY_BACKSPACE
},
1579 { "Escape", KEY_ESC
},
1580 { "Left", KEY_LEFT
},
1581 { "Right", KEY_RIGHT
},
1583 { "Down", KEY_DOWN
},
1584 { "Insert", KEY_IC
},
1585 { "Delete", KEY_DC
},
1587 { "Home", KEY_HOME
},
1589 { "PageUp", KEY_PPAGE
},
1590 { "PageDown", KEY_NPAGE
},
1600 { "F10", KEY_F(10) },
1601 { "F11", KEY_F(11) },
1602 { "F12", KEY_F(12) },
1606 get_key_value(const char *name
)
1610 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1611 if (!strcasecmp(key_table
[i
].name
, name
))
1612 return key_table
[i
].value
;
1614 if (strlen(name
) == 1 && isprint(*name
))
1621 get_key_name(int key_value
)
1623 static char key_char
[] = "'X'";
1624 const char *seq
= NULL
;
1627 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1628 if (key_table
[key
].value
== key_value
)
1629 seq
= key_table
[key
].name
;
1633 isprint(key_value
)) {
1634 key_char
[1] = (char) key_value
;
1638 return seq
? seq
: "(no key)";
1642 append_key(char *buf
, size_t *pos
, const struct keybinding
*keybinding
)
1644 const char *sep
= *pos
> 0 ? ", " : "";
1645 const char *keyname
= get_key_name(keybinding
->alias
);
1647 return string_nformat(buf
, BUFSIZ
, pos
, "%s%s", sep
, keyname
);
1651 append_keymap_request_keys(char *buf
, size_t *pos
, enum request request
,
1652 enum keymap keymap
, bool all
)
1656 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1657 if (keybindings
[keymap
].data
[i
].request
== request
) {
1658 if (!append_key(buf
, pos
, &keybindings
[keymap
].data
[i
]))
1668 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1671 get_keys(enum keymap keymap
, enum request request
, bool all
)
1673 static char buf
[BUFSIZ
];
1679 if (!append_keymap_request_keys(buf
, &pos
, request
, keymap
, all
))
1680 return "Too many keybindings!";
1681 if (pos
> 0 && !all
)
1684 if (keymap
!= KEYMAP_GENERIC
) {
1685 /* Only the generic keymap includes the default keybindings when
1686 * listing all keys. */
1690 if (!append_keymap_request_keys(buf
, &pos
, request
, KEYMAP_GENERIC
, all
))
1691 return "Too many keybindings!";
1696 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1697 if (default_keybindings
[i
].request
== request
) {
1698 if (!append_key(buf
, &pos
, &default_keybindings
[i
]))
1699 return "Too many keybindings!";
1708 struct run_request
{
1714 static struct run_request
*run_request
;
1715 static size_t run_requests
;
1717 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1720 add_run_request(enum keymap keymap
, int key
, const char **argv
)
1722 struct run_request
*req
;
1724 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1727 req
= &run_request
[run_requests
];
1728 req
->keymap
= keymap
;
1732 if (!argv_copy(&req
->argv
, argv
))
1735 return REQ_NONE
+ ++run_requests
;
1738 static struct run_request
*
1739 get_run_request(enum request request
)
1741 if (request
<= REQ_NONE
)
1743 return &run_request
[request
- REQ_NONE
- 1];
1747 add_builtin_run_requests(void)
1749 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1750 const char *checkout
[] = { "git", "checkout", "%(branch)", NULL
};
1751 const char *commit
[] = { "git", "commit", NULL
};
1752 const char *gc
[] = { "git", "gc", NULL
};
1753 struct run_request reqs
[] = {
1754 { KEYMAP_MAIN
, 'C', cherry_pick
},
1755 { KEYMAP_STATUS
, 'C', commit
},
1756 { KEYMAP_BRANCH
, 'C', checkout
},
1757 { KEYMAP_GENERIC
, 'G', gc
},
1761 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1762 enum request req
= get_keybinding(reqs
[i
].keymap
, reqs
[i
].key
);
1764 if (req
!= reqs
[i
].key
)
1766 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argv
);
1767 if (req
!= REQ_NONE
)
1768 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1773 * User config file handling.
1776 static int config_lineno
;
1777 static bool config_errors
;
1778 static const char *config_msg
;
1780 static const struct enum_map color_map
[] = {
1781 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1793 static const struct enum_map attr_map
[] = {
1794 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1801 ATTR_MAP(UNDERLINE
),
1804 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1806 static int parse_step(double *opt
, const char *arg
)
1809 if (!strchr(arg
, '%'))
1812 /* "Shift down" so 100% and 1 does not conflict. */
1813 *opt
= (*opt
- 1) / 100;
1816 config_msg
= "Step value larger than 100%";
1821 config_msg
= "Invalid step value";
1828 parse_int(int *opt
, const char *arg
, int min
, int max
)
1830 int value
= atoi(arg
);
1832 if (min
<= value
&& value
<= max
) {
1837 config_msg
= "Integer value out of bound";
1842 set_color(int *color
, const char *name
)
1844 if (map_enum(color
, color_map
, name
))
1846 if (!prefixcmp(name
, "color"))
1847 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1851 /* Wants: object fgcolor bgcolor [attribute] */
1853 option_color_command(int argc
, const char *argv
[])
1855 struct line_info
*info
;
1858 config_msg
= "Wrong number of arguments given to color command";
1862 info
= get_line_info(argv
[0]);
1864 static const struct enum_map obsolete
[] = {
1865 ENUM_MAP("main-delim", LINE_DELIMITER
),
1866 ENUM_MAP("main-date", LINE_DATE
),
1867 ENUM_MAP("main-author", LINE_AUTHOR
),
1871 if (!map_enum(&index
, obsolete
, argv
[0])) {
1872 config_msg
= "Unknown color name";
1875 info
= &line_info
[index
];
1878 if (!set_color(&info
->fg
, argv
[1]) ||
1879 !set_color(&info
->bg
, argv
[2])) {
1880 config_msg
= "Unknown color";
1885 while (argc
-- > 3) {
1888 if (!set_attribute(&attr
, argv
[argc
])) {
1889 config_msg
= "Unknown attribute";
1898 static int parse_bool(bool *opt
, const char *arg
)
1900 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1905 static int parse_enum_do(unsigned int *opt
, const char *arg
,
1906 const struct enum_map
*map
, size_t map_size
)
1910 assert(map_size
> 1);
1912 if (map_enum_do(map
, map_size
, (int *) opt
, arg
))
1915 if (parse_bool(&is_true
, arg
) != OK
)
1918 *opt
= is_true
? map
[1].value
: map
[0].value
;
1922 #define parse_enum(opt, arg, map) \
1923 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1926 parse_string(char *opt
, const char *arg
, size_t optsize
)
1928 int arglen
= strlen(arg
);
1933 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1934 config_msg
= "Unmatched quotation";
1937 arg
+= 1; arglen
-= 2;
1939 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1944 /* Wants: name = value */
1946 option_set_command(int argc
, const char *argv
[])
1949 config_msg
= "Wrong number of arguments given to set command";
1953 if (strcmp(argv
[1], "=")) {
1954 config_msg
= "No value assigned";
1958 if (!strcmp(argv
[0], "show-author"))
1959 return parse_enum(&opt_author
, argv
[2], author_map
);
1961 if (!strcmp(argv
[0], "show-date"))
1962 return parse_enum(&opt_date
, argv
[2], date_map
);
1964 if (!strcmp(argv
[0], "show-rev-graph"))
1965 return parse_bool(&opt_rev_graph
, argv
[2]);
1967 if (!strcmp(argv
[0], "show-refs"))
1968 return parse_bool(&opt_show_refs
, argv
[2]);
1970 if (!strcmp(argv
[0], "show-line-numbers"))
1971 return parse_bool(&opt_line_number
, argv
[2]);
1973 if (!strcmp(argv
[0], "line-graphics"))
1974 return parse_bool(&opt_line_graphics
, argv
[2]);
1976 if (!strcmp(argv
[0], "line-number-interval"))
1977 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
1979 if (!strcmp(argv
[0], "author-width"))
1980 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
1982 if (!strcmp(argv
[0], "horizontal-scroll"))
1983 return parse_step(&opt_hscroll
, argv
[2]);
1985 if (!strcmp(argv
[0], "split-view-height"))
1986 return parse_step(&opt_scale_split_view
, argv
[2]);
1988 if (!strcmp(argv
[0], "tab-size"))
1989 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
1991 if (!strcmp(argv
[0], "commit-encoding"))
1992 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
1994 config_msg
= "Unknown variable name";
1998 /* Wants: mode request key */
2000 option_bind_command(int argc
, const char *argv
[])
2002 enum request request
;
2007 config_msg
= "Wrong number of arguments given to bind command";
2011 if (!set_keymap(&keymap
, argv
[0])) {
2012 config_msg
= "Unknown key map";
2016 key
= get_key_value(argv
[1]);
2018 config_msg
= "Unknown key";
2022 request
= get_request(argv
[2]);
2023 if (request
== REQ_UNKNOWN
) {
2024 static const struct enum_map obsolete
[] = {
2025 ENUM_MAP("cherry-pick", REQ_NONE
),
2026 ENUM_MAP("screen-resize", REQ_NONE
),
2027 ENUM_MAP("tree-parent", REQ_PARENT
),
2031 if (map_enum(&alias
, obsolete
, argv
[2])) {
2032 if (alias
!= REQ_NONE
)
2033 add_keybinding(keymap
, alias
, key
);
2034 config_msg
= "Obsolete request name";
2038 if (request
== REQ_UNKNOWN
&& *argv
[2]++ == '!')
2039 request
= add_run_request(keymap
, key
, argv
+ 2);
2040 if (request
== REQ_UNKNOWN
) {
2041 config_msg
= "Unknown request name";
2045 add_keybinding(keymap
, request
, key
);
2051 set_option(const char *opt
, char *value
)
2053 const char *argv
[SIZEOF_ARG
];
2056 if (!argv_from_string(argv
, &argc
, value
)) {
2057 config_msg
= "Too many option arguments";
2061 if (!strcmp(opt
, "color"))
2062 return option_color_command(argc
, argv
);
2064 if (!strcmp(opt
, "set"))
2065 return option_set_command(argc
, argv
);
2067 if (!strcmp(opt
, "bind"))
2068 return option_bind_command(argc
, argv
);
2070 config_msg
= "Unknown option command";
2075 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
2080 config_msg
= "Internal error";
2082 /* Check for comment markers, since read_properties() will
2083 * only ensure opt and value are split at first " \t". */
2084 optlen
= strcspn(opt
, "#");
2088 if (opt
[optlen
] != 0) {
2089 config_msg
= "No option value";
2093 /* Look for comment endings in the value. */
2094 size_t len
= strcspn(value
, "#");
2096 if (len
< valuelen
) {
2098 value
[valuelen
] = 0;
2101 status
= set_option(opt
, value
);
2104 if (status
== ERR
) {
2105 warn("Error on line %d, near '%.*s': %s",
2106 config_lineno
, (int) optlen
, opt
, config_msg
);
2107 config_errors
= TRUE
;
2110 /* Always keep going if errors are encountered. */
2115 load_option_file(const char *path
)
2119 /* It's OK that the file doesn't exist. */
2120 if (!io_open(&io
, "%s", path
))
2124 config_errors
= FALSE
;
2126 if (io_load(&io
, " \t", read_option
) == ERR
||
2127 config_errors
== TRUE
)
2128 warn("Errors while loading %s.", path
);
2134 const char *home
= getenv("HOME");
2135 const char *tigrc_user
= getenv("TIGRC_USER");
2136 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
2137 char buf
[SIZEOF_STR
];
2140 tigrc_system
= SYSCONFDIR
"/tigrc";
2141 load_option_file(tigrc_system
);
2144 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
2148 load_option_file(tigrc_user
);
2150 /* Add _after_ loading config files to avoid adding run requests
2151 * that conflict with keybindings. */
2152 add_builtin_run_requests();
2165 /* The display array of active views and the index of the current view. */
2166 static struct view
*display
[2];
2167 static unsigned int current_view
;
2169 #define foreach_displayed_view(view, i) \
2170 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2172 #define displayed_views() (display[1] != NULL ? 2 : 1)
2174 /* Current head and commit ID */
2175 static char ref_blob
[SIZEOF_REF
] = "";
2176 static char ref_commit
[SIZEOF_REF
] = "HEAD";
2177 static char ref_head
[SIZEOF_REF
] = "HEAD";
2178 static char ref_branch
[SIZEOF_REF
] = "";
2195 enum view_type type
; /* View type */
2196 const char *name
; /* View name */
2197 const char *cmd_env
; /* Command line set via environment */
2198 const char *id
; /* Points to either of ref_{head,commit,blob} */
2200 struct view_ops
*ops
; /* View operations */
2202 enum keymap keymap
; /* What keymap does this view have */
2203 bool git_dir
; /* Whether the view requires a git directory. */
2205 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
2206 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
2208 int height
, width
; /* The width and height of the main window */
2209 WINDOW
*win
; /* The main window */
2210 WINDOW
*title
; /* The title window living below the main window */
2213 unsigned long offset
; /* Offset of the window top */
2214 unsigned long yoffset
; /* Offset from the window side. */
2215 unsigned long lineno
; /* Current line number */
2216 unsigned long p_offset
; /* Previous offset of the window top */
2217 unsigned long p_yoffset
;/* Previous offset from the window side */
2218 unsigned long p_lineno
; /* Previous current line number */
2219 bool p_restore
; /* Should the previous position be restored. */
2222 char grep
[SIZEOF_STR
]; /* Search string */
2223 regex_t
*regex
; /* Pre-compiled regexp */
2225 /* If non-NULL, points to the view that opened this view. If this view
2226 * is closed tig will switch back to the parent view. */
2227 struct view
*parent
;
2231 size_t lines
; /* Total number of lines */
2232 struct line
*line
; /* Line index */
2233 unsigned int digits
; /* Number of digits in the lines member. */
2236 struct line
*curline
; /* Line currently being drawn. */
2237 enum line_type curtype
; /* Attribute currently used for drawing. */
2238 unsigned long col
; /* Column when drawing. */
2239 bool has_scrolled
; /* View was scrolled. */
2242 const char **argv
; /* Shell command arguments. */
2243 const char *dir
; /* Directory from which to execute. */
2251 /* What type of content being displayed. Used in the title bar. */
2253 /* Default command arguments. */
2255 /* Open and reads in all view content. */
2256 bool (*open
)(struct view
*view
);
2257 /* Read one line; updates view->line. */
2258 bool (*read
)(struct view
*view
, char *data
);
2259 /* Draw one line; @lineno must be < view->height. */
2260 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
2261 /* Depending on view handle a special requests. */
2262 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
2263 /* Search for regexp in a line. */
2264 bool (*grep
)(struct view
*view
, struct line
*line
);
2266 void (*select
)(struct view
*view
, struct line
*line
);
2267 /* Prepare view for loading */
2268 bool (*prepare
)(struct view
*view
);
2271 static struct view_ops blame_ops
;
2272 static struct view_ops blob_ops
;
2273 static struct view_ops diff_ops
;
2274 static struct view_ops help_ops
;
2275 static struct view_ops log_ops
;
2276 static struct view_ops main_ops
;
2277 static struct view_ops pager_ops
;
2278 static struct view_ops stage_ops
;
2279 static struct view_ops status_ops
;
2280 static struct view_ops tree_ops
;
2281 static struct view_ops branch_ops
;
2283 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2284 { type, name, #env, ref, ops, map, git }
2286 #define VIEW_(id, name, ops, git, ref) \
2287 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2289 static struct view views
[] = {
2290 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
2291 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
2292 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
2293 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
2294 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
2295 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
2296 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
2297 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
2298 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
2299 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
2300 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
2303 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2305 #define foreach_view(view, i) \
2306 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2308 #define view_is_displayed(view) \
2309 (view == display[0] || view == display[1])
2312 view_request(struct view
*view
, enum request request
)
2314 if (!view
|| !view
->lines
)
2316 return view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
2325 set_view_attr(struct view
*view
, enum line_type type
)
2327 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
2328 (void) wattrset(view
->win
, get_line_attr(type
));
2329 wchgat(view
->win
, -1, 0, type
, NULL
);
2330 view
->curtype
= type
;
2335 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
2336 int max_len
, bool use_tilde
)
2338 static char out_buffer
[BUFSIZ
* 2];
2341 int trimmed
= FALSE
;
2342 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2347 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
, opt_tab_size
);
2349 set_view_attr(view
, type
);
2351 if (opt_iconv_out
!= ICONV_NONE
) {
2352 ICONV_CONST
char *inbuf
= (ICONV_CONST
char *) string
;
2353 size_t inlen
= len
+ 1;
2355 char *outbuf
= out_buffer
;
2356 size_t outlen
= sizeof(out_buffer
);
2360 ret
= iconv(opt_iconv_out
, &inbuf
, &inlen
, &outbuf
, &outlen
);
2361 if (ret
!= (size_t) -1) {
2362 string
= out_buffer
;
2363 len
= sizeof(out_buffer
) - outlen
;
2367 waddnstr(view
->win
, string
, len
);
2369 if (trimmed
&& use_tilde
) {
2370 set_view_attr(view
, LINE_DELIMITER
);
2371 waddch(view
->win
, '~');
2379 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
2381 static char space
[] = " ";
2384 spaces
= MIN(max
, spaces
);
2386 while (spaces
> 0) {
2387 int len
= MIN(spaces
, sizeof(space
) - 1);
2389 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
2397 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
2399 view
->col
+= draw_chars(view
, type
, string
, view
->width
+ view
->yoffset
- view
->col
, trim
);
2400 return view
->width
+ view
->yoffset
<= view
->col
;
2404 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
2406 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2407 int max
= view
->width
+ view
->yoffset
- view
->col
;
2413 set_view_attr(view
, type
);
2414 /* Using waddch() instead of waddnstr() ensures that
2415 * they'll be rendered correctly for the cursor line. */
2416 for (i
= skip
; i
< size
; i
++)
2417 waddch(view
->win
, graphic
[i
]);
2420 if (size
< max
&& skip
<= size
)
2421 waddch(view
->win
, ' ');
2424 return view
->width
+ view
->yoffset
<= view
->col
;
2428 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2430 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2434 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2436 col
= draw_space(view
, type
, max
- 1, max
- 1);
2439 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2440 return view
->width
+ view
->yoffset
<= view
->col
;
2444 draw_date(struct view
*view
, struct time
*time
)
2446 const char *date
= mkdate(time
, opt_date
);
2447 int cols
= opt_date
== DATE_SHORT
? DATE_SHORT_COLS
: DATE_COLS
;
2449 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2453 draw_author(struct view
*view
, const char *author
)
2455 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5;
2456 bool abbreviate
= opt_author
== AUTHOR_ABBREVIATED
|| !trim
;
2458 if (abbreviate
&& author
)
2459 author
= get_author_initials(author
);
2461 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2465 draw_mode(struct view
*view
, mode_t mode
)
2471 else if (S_ISLNK(mode
))
2473 else if (S_ISGITLINK(mode
))
2475 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2477 else if (S_ISREG(mode
))
2482 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2486 draw_lineno(struct view
*view
, unsigned int lineno
)
2489 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2490 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2492 chtype separator
= opt_line_graphics
? ACS_VLINE
: '|';
2494 lineno
+= view
->offset
+ 1;
2495 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2496 static char fmt
[] = "%1ld";
2498 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2499 if (string_format(number
, fmt
, lineno
))
2503 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2505 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2506 return draw_graphic(view
, LINE_DEFAULT
, &separator
, 1);
2510 draw_view_line(struct view
*view
, unsigned int lineno
)
2513 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2515 assert(view_is_displayed(view
));
2517 if (view
->offset
+ lineno
>= view
->lines
)
2520 line
= &view
->line
[view
->offset
+ lineno
];
2522 wmove(view
->win
, lineno
, 0);
2524 wclrtoeol(view
->win
);
2526 view
->curline
= line
;
2527 view
->curtype
= LINE_NONE
;
2528 line
->selected
= FALSE
;
2529 line
->dirty
= line
->cleareol
= 0;
2532 set_view_attr(view
, LINE_CURSOR
);
2533 line
->selected
= TRUE
;
2534 view
->ops
->select(view
, line
);
2537 return view
->ops
->draw(view
, line
, lineno
);
2541 redraw_view_dirty(struct view
*view
)
2546 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2547 if (view
->offset
+ lineno
>= view
->lines
)
2549 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2552 if (!draw_view_line(view
, lineno
))
2558 wnoutrefresh(view
->win
);
2562 redraw_view_from(struct view
*view
, int lineno
)
2564 assert(0 <= lineno
&& lineno
< view
->height
);
2566 for (; lineno
< view
->height
; lineno
++) {
2567 if (!draw_view_line(view
, lineno
))
2571 wnoutrefresh(view
->win
);
2575 redraw_view(struct view
*view
)
2578 redraw_view_from(view
, 0);
2583 update_view_title(struct view
*view
)
2585 char buf
[SIZEOF_STR
];
2586 char state
[SIZEOF_STR
];
2587 size_t bufpos
= 0, statelen
= 0;
2589 assert(view_is_displayed(view
));
2591 if (view
->type
!= VIEW_STATUS
&& view
->lines
) {
2592 unsigned int view_lines
= view
->offset
+ view
->height
;
2593 unsigned int lines
= view
->lines
2594 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2597 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2606 time_t secs
= time(NULL
) - view
->start_time
;
2608 /* Three git seconds are a long time ... */
2610 string_format_from(state
, &statelen
, " loading %lds", secs
);
2613 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2614 if (*view
->ref
&& bufpos
< view
->width
) {
2615 size_t refsize
= strlen(view
->ref
);
2616 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2618 if (minsize
< view
->width
)
2619 refsize
= view
->width
- minsize
+ 7;
2620 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2623 if (statelen
&& bufpos
< view
->width
) {
2624 string_format_from(buf
, &bufpos
, "%s", state
);
2627 if (view
== display
[current_view
])
2628 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2630 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2632 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2633 wclrtoeol(view
->title
);
2634 wnoutrefresh(view
->title
);
2638 apply_step(double step
, int value
)
2642 value
*= step
+ 0.01;
2643 return value
? value
: 1;
2647 resize_display(void)
2650 struct view
*base
= display
[0];
2651 struct view
*view
= display
[1] ? display
[1] : display
[0];
2653 /* Setup window dimensions */
2655 getmaxyx(stdscr
, base
->height
, base
->width
);
2657 /* Make room for the status window. */
2661 /* Horizontal split. */
2662 view
->width
= base
->width
;
2663 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2664 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2665 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2666 base
->height
-= view
->height
;
2668 /* Make room for the title bar. */
2672 /* Make room for the title bar. */
2677 foreach_displayed_view (view
, i
) {
2679 view
->win
= newwin(view
->height
, 0, offset
, 0);
2681 die("Failed to create %s view", view
->name
);
2683 scrollok(view
->win
, FALSE
);
2685 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2687 die("Failed to create title window");
2690 wresize(view
->win
, view
->height
, view
->width
);
2691 mvwin(view
->win
, offset
, 0);
2692 mvwin(view
->title
, offset
+ view
->height
, 0);
2695 offset
+= view
->height
+ 1;
2700 redraw_display(bool clear
)
2705 foreach_displayed_view (view
, i
) {
2709 update_view_title(view
);
2719 toggle_enum_option_do(unsigned int *opt
, const char *help
,
2720 const struct enum_map
*map
, size_t size
)
2722 *opt
= (*opt
+ 1) % size
;
2723 redraw_display(FALSE
);
2724 report("Displaying %s %s", enum_name(map
[*opt
]), help
);
2727 #define toggle_enum_option(opt, help, map) \
2728 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2730 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2731 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2734 toggle_view_option(bool *option
, const char *help
)
2737 redraw_display(FALSE
);
2738 report("%sabling %s", *option
? "En" : "Dis", help
);
2742 open_option_menu(void)
2744 const struct menu_item menu
[] = {
2745 { '.', "line numbers", &opt_line_number
},
2746 { 'D', "date display", &opt_date
},
2747 { 'A', "author display", &opt_author
},
2748 { 'g', "revision graph display", &opt_rev_graph
},
2749 { 'F', "reference display", &opt_show_refs
},
2754 if (prompt_menu("Toggle option", menu
, &selected
)) {
2755 if (menu
[selected
].data
== &opt_date
)
2757 else if (menu
[selected
].data
== &opt_author
)
2760 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2765 maximize_view(struct view
*view
)
2767 memset(display
, 0, sizeof(display
));
2769 display
[current_view
] = view
;
2771 redraw_display(FALSE
);
2781 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2783 if (lineno
>= view
->lines
)
2784 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2786 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2787 unsigned long half
= view
->height
/ 2;
2790 offset
= lineno
- half
;
2795 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2796 view
->offset
= offset
;
2797 view
->lineno
= lineno
;
2804 /* Scrolling backend */
2806 do_scroll_view(struct view
*view
, int lines
)
2808 bool redraw_current_line
= FALSE
;
2810 /* The rendering expects the new offset. */
2811 view
->offset
+= lines
;
2813 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2816 /* Move current line into the view. */
2817 if (view
->lineno
< view
->offset
) {
2818 view
->lineno
= view
->offset
;
2819 redraw_current_line
= TRUE
;
2820 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2821 view
->lineno
= view
->offset
+ view
->height
- 1;
2822 redraw_current_line
= TRUE
;
2825 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2827 /* Redraw the whole screen if scrolling is pointless. */
2828 if (view
->height
< ABS(lines
)) {
2832 int line
= lines
> 0 ? view
->height
- lines
: 0;
2833 int end
= line
+ ABS(lines
);
2835 scrollok(view
->win
, TRUE
);
2836 wscrl(view
->win
, lines
);
2837 scrollok(view
->win
, FALSE
);
2839 while (line
< end
&& draw_view_line(view
, line
))
2842 if (redraw_current_line
)
2843 draw_view_line(view
, view
->lineno
- view
->offset
);
2844 wnoutrefresh(view
->win
);
2847 view
->has_scrolled
= TRUE
;
2851 /* Scroll frontend */
2853 scroll_view(struct view
*view
, enum request request
)
2857 assert(view_is_displayed(view
));
2860 case REQ_SCROLL_LEFT
:
2861 if (view
->yoffset
== 0) {
2862 report("Cannot scroll beyond the first column");
2865 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2868 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2869 redraw_view_from(view
, 0);
2872 case REQ_SCROLL_RIGHT
:
2873 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2877 case REQ_SCROLL_PAGE_DOWN
:
2878 lines
= view
->height
;
2879 case REQ_SCROLL_LINE_DOWN
:
2880 if (view
->offset
+ lines
> view
->lines
)
2881 lines
= view
->lines
- view
->offset
;
2883 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2884 report("Cannot scroll beyond the last line");
2889 case REQ_SCROLL_PAGE_UP
:
2890 lines
= view
->height
;
2891 case REQ_SCROLL_LINE_UP
:
2892 if (lines
> view
->offset
)
2893 lines
= view
->offset
;
2896 report("Cannot scroll beyond the first line");
2904 die("request %d not handled in switch", request
);
2907 do_scroll_view(view
, lines
);
2912 move_view(struct view
*view
, enum request request
)
2914 int scroll_steps
= 0;
2918 case REQ_MOVE_FIRST_LINE
:
2919 steps
= -view
->lineno
;
2922 case REQ_MOVE_LAST_LINE
:
2923 steps
= view
->lines
- view
->lineno
- 1;
2926 case REQ_MOVE_PAGE_UP
:
2927 steps
= view
->height
> view
->lineno
2928 ? -view
->lineno
: -view
->height
;
2931 case REQ_MOVE_PAGE_DOWN
:
2932 steps
= view
->lineno
+ view
->height
>= view
->lines
2933 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2945 die("request %d not handled in switch", request
);
2948 if (steps
<= 0 && view
->lineno
== 0) {
2949 report("Cannot move beyond the first line");
2952 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2953 report("Cannot move beyond the last line");
2957 /* Move the current line */
2958 view
->lineno
+= steps
;
2959 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2961 /* Check whether the view needs to be scrolled */
2962 if (view
->lineno
< view
->offset
||
2963 view
->lineno
>= view
->offset
+ view
->height
) {
2964 scroll_steps
= steps
;
2965 if (steps
< 0 && -steps
> view
->offset
) {
2966 scroll_steps
= -view
->offset
;
2968 } else if (steps
> 0) {
2969 if (view
->lineno
== view
->lines
- 1 &&
2970 view
->lines
> view
->height
) {
2971 scroll_steps
= view
->lines
- view
->offset
- 1;
2972 if (scroll_steps
>= view
->height
)
2973 scroll_steps
-= view
->height
- 1;
2978 if (!view_is_displayed(view
)) {
2979 view
->offset
+= scroll_steps
;
2980 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2981 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2985 /* Repaint the old "current" line if we be scrolling */
2986 if (ABS(steps
) < view
->height
)
2987 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
2990 do_scroll_view(view
, scroll_steps
);
2994 /* Draw the current line */
2995 draw_view_line(view
, view
->lineno
- view
->offset
);
2997 wnoutrefresh(view
->win
);
3006 static void search_view(struct view
*view
, enum request request
);
3009 grep_text(struct view
*view
, const char *text
[])
3014 for (i
= 0; text
[i
]; i
++)
3016 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
3022 select_view_line(struct view
*view
, unsigned long lineno
)
3024 unsigned long old_lineno
= view
->lineno
;
3025 unsigned long old_offset
= view
->offset
;
3027 if (goto_view_line(view
, view
->offset
, lineno
)) {
3028 if (view_is_displayed(view
)) {
3029 if (old_offset
!= view
->offset
) {
3032 draw_view_line(view
, old_lineno
- view
->offset
);
3033 draw_view_line(view
, view
->lineno
- view
->offset
);
3034 wnoutrefresh(view
->win
);
3037 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
3043 find_next(struct view
*view
, enum request request
)
3045 unsigned long lineno
= view
->lineno
;
3050 report("No previous search");
3052 search_view(view
, request
);
3062 case REQ_SEARCH_BACK
:
3071 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
3072 lineno
+= direction
;
3074 /* Note, lineno is unsigned long so will wrap around in which case it
3075 * will become bigger than view->lines. */
3076 for (; lineno
< view
->lines
; lineno
+= direction
) {
3077 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
3078 select_view_line(view
, lineno
);
3079 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
3084 report("No match found for '%s'", view
->grep
);
3088 search_view(struct view
*view
, enum request request
)
3093 regfree(view
->regex
);
3096 view
->regex
= calloc(1, sizeof(*view
->regex
));
3101 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
3102 if (regex_err
!= 0) {
3103 char buf
[SIZEOF_STR
] = "unknown error";
3105 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
3106 report("Search failed: %s", buf
);
3110 string_copy(view
->grep
, opt_search
);
3112 find_next(view
, request
);
3116 * Incremental updating
3120 reset_view(struct view
*view
)
3124 for (i
= 0; i
< view
->lines
; i
++)
3125 free(view
->line
[i
].data
);
3128 view
->p_offset
= view
->offset
;
3129 view
->p_yoffset
= view
->yoffset
;
3130 view
->p_lineno
= view
->lineno
;
3138 view
->update_secs
= 0;
3142 format_arg(const char *name
)
3148 const char *value_if_empty
;
3150 #define FORMAT_VAR(name, value, value_if_empty) \
3151 { name, STRING_SIZE(name), value, value_if_empty }
3152 FORMAT_VAR("%(directory)", opt_path
, ""),
3153 FORMAT_VAR("%(file)", opt_file
, ""),
3154 FORMAT_VAR("%(ref)", opt_ref
, "HEAD"),
3155 FORMAT_VAR("%(head)", ref_head
, ""),
3156 FORMAT_VAR("%(commit)", ref_commit
, ""),
3157 FORMAT_VAR("%(blob)", ref_blob
, ""),
3158 FORMAT_VAR("%(branch)", ref_branch
, ""),
3162 for (i
= 0; i
< ARRAY_SIZE(vars
); i
++)
3163 if (!strncmp(name
, vars
[i
].name
, vars
[i
].namelen
))
3164 return *vars
[i
].value
? vars
[i
].value
: vars
[i
].value_if_empty
;
3166 report("Unknown replacement: `%s`", name
);
3171 format_argv(const char ***dst_argv
, const char *src_argv
[], bool replace
)
3173 char buf
[SIZEOF_STR
];
3176 argv_free(*dst_argv
);
3178 for (argc
= 0; src_argv
[argc
]; argc
++) {
3179 const char *arg
= src_argv
[argc
];
3182 if (!strcmp(arg
, "%(file-args)")) {
3183 if (!argv_append_array(dst_argv
, opt_file_args
))
3187 } else if (!strcmp(arg
, "%(diff-args)")) {
3188 if (!argv_append_array(dst_argv
, opt_diff_args
))
3192 } else if (!strcmp(arg
, "%(rev-args)")) {
3193 if (!argv_append_array(dst_argv
, opt_rev_args
))
3199 char *next
= strstr(arg
, "%(");
3200 int len
= next
- arg
;
3203 if (!next
|| !replace
) {
3208 value
= format_arg(next
);
3215 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
3218 arg
= next
&& replace
? strchr(next
, ')') + 1 : NULL
;
3221 if (!argv_append(dst_argv
, buf
))
3225 return src_argv
[argc
] == NULL
;
3229 restore_view_position(struct view
*view
)
3231 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
3234 /* Changing the view position cancels the restoring. */
3235 /* FIXME: Changing back to the first line is not detected. */
3236 if (view
->offset
!= 0 || view
->lineno
!= 0) {
3237 view
->p_restore
= FALSE
;
3241 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
3242 view_is_displayed(view
))
3245 view
->yoffset
= view
->p_yoffset
;
3246 view
->p_restore
= FALSE
;
3252 end_update(struct view
*view
, bool force
)
3256 while (!view
->ops
->read(view
, NULL
))
3260 io_kill(view
->pipe
);
3261 io_done(view
->pipe
);
3266 setup_update(struct view
*view
, const char *vid
)
3269 string_copy_rev(view
->vid
, vid
);
3270 view
->pipe
= &view
->io
;
3271 view
->start_time
= time(NULL
);
3275 prepare_io(struct view
*view
, const char *dir
, const char *argv
[], bool replace
)
3278 return format_argv(&view
->argv
, argv
, replace
);
3282 prepare_update(struct view
*view
, const char *argv
[], const char *dir
)
3285 end_update(view
, TRUE
);
3286 return prepare_io(view
, dir
, argv
, FALSE
);
3290 start_update(struct view
*view
, const char **argv
, const char *dir
)
3293 io_done(view
->pipe
);
3294 return prepare_io(view
, dir
, argv
, FALSE
) &&
3295 io_run(&view
->io
, IO_RD
, dir
, view
->argv
);
3299 prepare_update_file(struct view
*view
, const char *name
)
3302 end_update(view
, TRUE
);
3303 argv_free(view
->argv
);
3304 return io_open(&view
->io
, "%s/%s", opt_cdup
[0] ? opt_cdup
: ".", name
);
3308 begin_update(struct view
*view
, bool refresh
)
3311 end_update(view
, TRUE
);
3314 if (view
->ops
->prepare
) {
3315 if (!view
->ops
->prepare(view
))
3317 } else if (!prepare_io(view
, NULL
, view
->ops
->argv
, TRUE
)) {
3321 /* Put the current ref_* value to the view title ref
3322 * member. This is needed by the blob view. Most other
3323 * views sets it automatically after loading because the
3324 * first line is a commit line. */
3325 string_copy_rev(view
->ref
, view
->id
);
3328 if (view
->argv
&& view
->argv
[0] &&
3329 !io_run(&view
->io
, IO_RD
, view
->dir
, view
->argv
))
3332 setup_update(view
, view
->id
);
3338 update_view(struct view
*view
)
3340 char out_buffer
[BUFSIZ
* 2];
3342 /* Clear the view and redraw everything since the tree sorting
3343 * might have rearranged things. */
3344 bool redraw
= view
->lines
== 0;
3345 bool can_read
= TRUE
;
3350 if (!io_can_read(view
->pipe
)) {
3351 if (view
->lines
== 0 && view_is_displayed(view
)) {
3352 time_t secs
= time(NULL
) - view
->start_time
;
3354 if (secs
> 1 && secs
> view
->update_secs
) {
3355 if (view
->update_secs
== 0)
3357 update_view_title(view
);
3358 view
->update_secs
= secs
;
3364 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
3365 if (opt_iconv_in
!= ICONV_NONE
) {
3366 ICONV_CONST
char *inbuf
= line
;
3367 size_t inlen
= strlen(line
) + 1;
3369 char *outbuf
= out_buffer
;
3370 size_t outlen
= sizeof(out_buffer
);
3374 ret
= iconv(opt_iconv_in
, &inbuf
, &inlen
, &outbuf
, &outlen
);
3375 if (ret
!= (size_t) -1)
3379 if (!view
->ops
->read(view
, line
)) {
3380 report("Allocation failure");
3381 end_update(view
, TRUE
);
3387 unsigned long lines
= view
->lines
;
3390 for (digits
= 0; lines
; digits
++)
3393 /* Keep the displayed view in sync with line number scaling. */
3394 if (digits
!= view
->digits
) {
3395 view
->digits
= digits
;
3396 if (opt_line_number
|| view
->type
== VIEW_BLAME
)
3401 if (io_error(view
->pipe
)) {
3402 report("Failed to read: %s", io_strerror(view
->pipe
));
3403 end_update(view
, TRUE
);
3405 } else if (io_eof(view
->pipe
)) {
3406 if (view_is_displayed(view
))
3408 end_update(view
, FALSE
);
3411 if (restore_view_position(view
))
3414 if (!view_is_displayed(view
))
3418 redraw_view_from(view
, 0);
3420 redraw_view_dirty(view
);
3422 /* Update the title _after_ the redraw so that if the redraw picks up a
3423 * commit reference in view->ref it'll be available here. */
3424 update_view_title(view
);
3428 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
3430 static struct line
*
3431 add_line_data(struct view
*view
, void *data
, enum line_type type
)
3435 if (!realloc_lines(&view
->line
, view
->lines
, 1))
3438 line
= &view
->line
[view
->lines
++];
3439 memset(line
, 0, sizeof(*line
));
3447 static struct line
*
3448 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
3450 char *data
= text
? strdup(text
) : NULL
;
3452 return data
? add_line_data(view
, data
, type
) : NULL
;
3455 static struct line
*
3456 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3458 char buf
[SIZEOF_STR
];
3461 va_start(args
, fmt
);
3462 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3466 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3474 OPEN_DEFAULT
= 0, /* Use default view switching. */
3475 OPEN_SPLIT
= 1, /* Split current view. */
3476 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3477 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3478 OPEN_PREPARED
= 32, /* Open already prepared command. */
3482 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3484 bool split
= !!(flags
& OPEN_SPLIT
);
3485 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3486 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3487 struct view
*view
= VIEW(request
);
3488 int nviews
= displayed_views();
3489 struct view
*base_view
= display
[0];
3491 if (view
== prev
&& nviews
== 1 && !reload
) {
3492 report("Already in %s view", view
->name
);
3496 if (view
->git_dir
&& !opt_git_dir
[0]) {
3497 report("The %s view is disabled in pager view", view
->name
);
3504 view
->parent
= prev
;
3505 } else if (!nomaximize
) {
3506 /* Maximize the current view. */
3507 memset(display
, 0, sizeof(display
));
3509 display
[current_view
] = view
;
3512 /* No prev signals that this is the first loaded view. */
3513 if (prev
&& view
!= prev
) {
3517 /* Resize the view when switching between split- and full-screen,
3518 * or when switching between two different full-screen views. */
3519 if (nviews
!= displayed_views() ||
3520 (nviews
== 1 && base_view
!= display
[0]))
3523 if (view
->ops
->open
) {
3525 end_update(view
, TRUE
);
3526 if (!view
->ops
->open(view
)) {
3527 report("Failed to load %s view", view
->name
);
3530 restore_view_position(view
);
3532 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3533 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3534 report("Failed to load %s view", view
->name
);
3538 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3539 /* Take the title line into account. */
3540 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3542 /* Scroll the view that was split if the current line is
3543 * outside the new limited view. */
3544 do_scroll_view(prev
, lines
);
3547 if (prev
&& view
!= prev
&& split
&& view_is_displayed(prev
)) {
3548 /* "Blur" the previous view. */
3549 update_view_title(prev
);
3552 if (view
->pipe
&& view
->lines
== 0) {
3553 /* Clear the old view and let the incremental updating refill
3556 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3558 } else if (view_is_displayed(view
)) {
3565 open_external_viewer(const char *argv
[], const char *dir
)
3567 def_prog_mode(); /* save current tty modes */
3568 endwin(); /* restore original tty modes */
3569 io_run_fg(argv
, dir
);
3570 fprintf(stderr
, "Press Enter to continue");
3573 redraw_display(TRUE
);
3577 open_mergetool(const char *file
)
3579 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3581 open_external_viewer(mergetool_argv
, opt_cdup
);
3585 open_editor(const char *file
)
3587 const char *editor_argv
[] = { "vi", file
, NULL
};
3590 editor
= getenv("GIT_EDITOR");
3591 if (!editor
&& *opt_editor
)
3592 editor
= opt_editor
;
3594 editor
= getenv("VISUAL");
3596 editor
= getenv("EDITOR");
3600 editor_argv
[0] = editor
;
3601 open_external_viewer(editor_argv
, opt_cdup
);
3605 open_run_request(enum request request
)
3607 struct run_request
*req
= get_run_request(request
);
3608 const char **argv
= NULL
;
3611 report("Unknown run request");
3615 if (format_argv(&argv
, req
->argv
, TRUE
))
3616 open_external_viewer(argv
, NULL
);
3623 * User request switch noodle
3627 view_driver(struct view
*view
, enum request request
)
3631 if (request
== REQ_NONE
)
3634 if (request
> REQ_NONE
) {
3635 open_run_request(request
);
3636 view_request(view
, REQ_REFRESH
);
3640 request
= view_request(view
, request
);
3641 if (request
== REQ_NONE
)
3647 case REQ_MOVE_PAGE_UP
:
3648 case REQ_MOVE_PAGE_DOWN
:
3649 case REQ_MOVE_FIRST_LINE
:
3650 case REQ_MOVE_LAST_LINE
:
3651 move_view(view
, request
);
3654 case REQ_SCROLL_LEFT
:
3655 case REQ_SCROLL_RIGHT
:
3656 case REQ_SCROLL_LINE_DOWN
:
3657 case REQ_SCROLL_LINE_UP
:
3658 case REQ_SCROLL_PAGE_DOWN
:
3659 case REQ_SCROLL_PAGE_UP
:
3660 scroll_view(view
, request
);
3663 case REQ_VIEW_BLAME
:
3665 report("No file chosen, press %s to open tree view",
3666 get_key(view
->keymap
, REQ_VIEW_TREE
));
3669 open_view(view
, request
, OPEN_DEFAULT
);
3674 report("No file chosen, press %s to open tree view",
3675 get_key(view
->keymap
, REQ_VIEW_TREE
));
3678 open_view(view
, request
, OPEN_DEFAULT
);
3681 case REQ_VIEW_PAGER
:
3682 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3683 report("No pager content, press %s to run command from prompt",
3684 get_key(view
->keymap
, REQ_PROMPT
));
3687 open_view(view
, request
, OPEN_DEFAULT
);
3690 case REQ_VIEW_STAGE
:
3691 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3692 report("No stage content, press %s to open the status view and choose file",
3693 get_key(view
->keymap
, REQ_VIEW_STATUS
));
3696 open_view(view
, request
, OPEN_DEFAULT
);
3699 case REQ_VIEW_STATUS
:
3700 if (opt_is_inside_work_tree
== FALSE
) {
3701 report("The status view requires a working tree");
3704 open_view(view
, request
, OPEN_DEFAULT
);
3712 case REQ_VIEW_BRANCH
:
3713 open_view(view
, request
, OPEN_DEFAULT
);
3718 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3723 view
= view
->parent
;
3724 line
= view
->lineno
;
3725 move_view(view
, request
);
3726 if (view_is_displayed(view
))
3727 update_view_title(view
);
3728 if (line
!= view
->lineno
)
3729 view_request(view
, REQ_ENTER
);
3731 move_view(view
, request
);
3737 int nviews
= displayed_views();
3738 int next_view
= (current_view
+ 1) % nviews
;
3740 if (next_view
== current_view
) {
3741 report("Only one view is displayed");
3745 current_view
= next_view
;
3746 /* Blur out the title of the previous view. */
3747 update_view_title(view
);
3752 report("Refreshing is not yet supported for the %s view", view
->name
);
3756 if (displayed_views() == 2)
3757 maximize_view(view
);
3764 case REQ_TOGGLE_LINENO
:
3765 toggle_view_option(&opt_line_number
, "line numbers");
3768 case REQ_TOGGLE_DATE
:
3772 case REQ_TOGGLE_AUTHOR
:
3776 case REQ_TOGGLE_REV_GRAPH
:
3777 toggle_view_option(&opt_rev_graph
, "revision graph display");
3780 case REQ_TOGGLE_REFS
:
3781 toggle_view_option(&opt_show_refs
, "reference display");
3784 case REQ_TOGGLE_SORT_FIELD
:
3785 case REQ_TOGGLE_SORT_ORDER
:
3786 report("Sorting is not yet supported for the %s view", view
->name
);
3790 case REQ_SEARCH_BACK
:
3791 search_view(view
, request
);
3796 find_next(view
, request
);
3799 case REQ_STOP_LOADING
:
3800 foreach_view(view
, i
) {
3802 report("Stopped loading the %s view", view
->name
),
3803 end_update(view
, TRUE
);
3807 case REQ_SHOW_VERSION
:
3808 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3811 case REQ_SCREEN_REDRAW
:
3812 redraw_display(TRUE
);
3816 report("Nothing to edit");
3820 report("Nothing to enter");
3823 case REQ_VIEW_CLOSE
:
3824 /* XXX: Mark closed views by letting view->prev point to the
3825 * view itself. Parents to closed view should never be
3827 if (view
->prev
&& view
->prev
!= view
) {
3828 maximize_view(view
->prev
);
3837 report("Unknown key, press %s for help",
3838 get_key(view
->keymap
, REQ_VIEW_HELP
));
3847 * View backend utilities
3857 const enum sort_field
*fields
;
3858 size_t size
, current
;
3862 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3863 #define get_sort_field(state) ((state).fields[(state).current])
3864 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3867 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3868 int (*compare
)(const void *, const void *))
3871 case REQ_TOGGLE_SORT_FIELD
:
3872 state
->current
= (state
->current
+ 1) % state
->size
;
3875 case REQ_TOGGLE_SORT_ORDER
:
3876 state
->reverse
= !state
->reverse
;
3879 die("Not a sort request");
3882 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3886 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3888 /* Small author cache to reduce memory consumption. It uses binary
3889 * search to lookup or find place to position new entries. No entries
3890 * are ever freed. */
3892 get_author(const char *name
)
3894 static const char **authors
;
3895 static size_t authors_size
;
3896 int from
= 0, to
= authors_size
- 1;
3898 while (from
<= to
) {
3899 size_t pos
= (to
+ from
) / 2;
3900 int cmp
= strcmp(name
, authors
[pos
]);
3903 return authors
[pos
];
3911 if (!realloc_authors(&authors
, authors_size
, 1))
3913 name
= strdup(name
);
3917 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3918 authors
[from
] = name
;
3925 parse_timesec(struct time
*time
, const char *sec
)
3927 time
->sec
= (time_t) atol(sec
);
3931 parse_timezone(struct time
*time
, const char *zone
)
3935 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3936 tz
+= ('0' - zone
[2]) * 60 * 60;
3937 tz
+= ('0' - zone
[3]) * 60 * 10;
3938 tz
+= ('0' - zone
[4]) * 60;
3947 /* Parse author lines where the name may be empty:
3948 * author <email@address.tld> 1138474660 +0100
3951 parse_author_line(char *ident
, const char **author
, struct time
*time
)
3953 char *nameend
= strchr(ident
, '<');
3954 char *emailend
= strchr(ident
, '>');
3956 if (nameend
&& emailend
)
3957 *nameend
= *emailend
= 0;
3958 ident
= chomp_string(ident
);
3961 ident
= chomp_string(nameend
+ 1);
3966 *author
= get_author(ident
);
3968 /* Parse epoch and timezone */
3969 if (emailend
&& emailend
[1] == ' ') {
3970 char *secs
= emailend
+ 2;
3971 char *zone
= strchr(secs
, ' ');
3973 parse_timesec(time
, secs
);
3975 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
3976 parse_timezone(time
, zone
+ 1);
3981 open_commit_parent_menu(char buf
[SIZEOF_STR
], int *parents
)
3983 char rev
[SIZEOF_REV
];
3984 const char *revlist_argv
[] = {
3985 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev
, NULL
3987 struct menu_item
*items
;
3988 char text
[SIZEOF_STR
];
3992 items
= calloc(*parents
+ 1, sizeof(*items
));
3996 for (i
= 0; i
< *parents
; i
++) {
3997 string_copy_rev(rev
, &buf
[SIZEOF_REV
* i
]);
3998 if (!io_run_buf(revlist_argv
, text
, sizeof(text
)) ||
3999 !(items
[i
].text
= strdup(text
))) {
4007 ok
= prompt_menu("Select parent", items
, parents
);
4009 for (i
= 0; items
[i
].text
; i
++)
4010 free((char *) items
[i
].text
);
4016 select_commit_parent(const char *id
, char rev
[SIZEOF_REV
], const char *path
)
4018 char buf
[SIZEOF_STR
* 4];
4019 const char *revlist_argv
[] = {
4020 "git", "log", "--no-color", "-1",
4021 "--pretty=format:%P", id
, "--", path
, NULL
4025 if (!io_run_buf(revlist_argv
, buf
, sizeof(buf
)) ||
4026 (parents
= strlen(buf
) / 40) < 0) {
4027 report("Failed to get parent information");
4030 } else if (parents
== 0) {
4032 report("Path '%s' does not exist in the parent", path
);
4034 report("The selected commit has no parents");
4040 else if (!open_commit_parent_menu(buf
, &parents
))
4043 string_copy_rev(rev
, &buf
[41 * parents
]);
4052 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4054 char text
[SIZEOF_STR
];
4056 if (opt_line_number
&& draw_lineno(view
, lineno
))
4059 string_expand(text
, sizeof(text
), line
->data
, opt_tab_size
);
4060 draw_text(view
, line
->type
, text
, TRUE
);
4065 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
4067 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
4068 char ref
[SIZEOF_STR
];
4070 if (!io_run_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
4073 /* This is the only fatal call, since it can "corrupt" the buffer. */
4074 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
4081 add_pager_refs(struct view
*view
, struct line
*line
)
4083 char buf
[SIZEOF_STR
];
4084 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
4085 struct ref_list
*list
;
4086 size_t bufpos
= 0, i
;
4087 const char *sep
= "Refs: ";
4088 bool is_tag
= FALSE
;
4090 assert(line
->type
== LINE_COMMIT
);
4092 list
= get_ref_list(commit_id
);
4094 if (view
->type
== VIEW_DIFF
)
4095 goto try_add_describe_ref
;
4099 for (i
= 0; i
< list
->size
; i
++) {
4100 struct ref
*ref
= list
->refs
[i
];
4101 const char *fmt
= ref
->tag
? "%s[%s]" :
4102 ref
->remote
? "%s<%s>" : "%s%s";
4104 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
4111 if (!is_tag
&& view
->type
== VIEW_DIFF
) {
4112 try_add_describe_ref
:
4113 /* Add <tag>-g<commit_id> "fake" reference. */
4114 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
4121 add_line_text(view
, buf
, LINE_PP_REFS
);
4125 pager_read(struct view
*view
, char *data
)
4132 line
= add_line_text(view
, data
, get_line_type(data
));
4136 if (line
->type
== LINE_COMMIT
&&
4137 (view
->type
== VIEW_DIFF
||
4138 view
->type
== VIEW_LOG
))
4139 add_pager_refs(view
, line
);
4145 pager_request(struct view
*view
, enum request request
, struct line
*line
)
4149 if (request
!= REQ_ENTER
)
4152 if (line
->type
== LINE_COMMIT
&&
4153 (view
->type
== VIEW_LOG
||
4154 view
->type
== VIEW_PAGER
)) {
4155 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
4159 /* Always scroll the view even if it was split. That way
4160 * you can use Enter to scroll through the log view and
4161 * split open each commit diff. */
4162 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
4164 /* FIXME: A minor workaround. Scrolling the view will call report("")
4165 * but if we are scrolling a non-current view this won't properly
4166 * update the view title. */
4168 update_view_title(view
);
4174 pager_grep(struct view
*view
, struct line
*line
)
4176 const char *text
[] = { line
->data
, NULL
};
4178 return grep_text(view
, text
);
4182 pager_select(struct view
*view
, struct line
*line
)
4184 if (line
->type
== LINE_COMMIT
) {
4185 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
4187 if (view
->type
!= VIEW_PAGER
)
4188 string_copy_rev(view
->ref
, text
);
4189 string_copy_rev(ref_commit
, text
);
4193 static struct view_ops pager_ops
= {
4204 static const char *log_argv
[SIZEOF_ARG
] = {
4205 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4209 log_request(struct view
*view
, enum request request
, struct line
*line
)
4214 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
4217 return pager_request(view
, request
, line
);
4221 static struct view_ops log_ops
= {
4232 static const char *diff_argv
[SIZEOF_ARG
] = {
4233 "git", "show", "--pretty=fuller", "--no-color", "--root",
4234 "--patch-with-stat", "--find-copies-harder", "-C",
4235 "%(diff-args)", "%(commit)", "--", "%(file-args)", NULL
4238 static struct view_ops diff_ops
= {
4253 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
4256 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
4260 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
4261 help_keymap_hidden
[keymap
] ? '+' : '-',
4262 enum_name(keymap_table
[keymap
]));
4264 line
->other
= keymap
;
4266 return help_keymap_hidden
[keymap
];
4270 help_open_keymap(struct view
*view
, enum keymap keymap
)
4272 const char *group
= NULL
;
4273 char buf
[SIZEOF_STR
];
4275 bool add_title
= TRUE
;
4278 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
4279 const char *key
= NULL
;
4281 if (req_info
[i
].request
== REQ_NONE
)
4284 if (!req_info
[i
].request
) {
4285 group
= req_info
[i
].help
;
4289 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4293 if (add_title
&& help_open_keymap_title(view
, keymap
))
4298 add_line_text(view
, group
, LINE_HELP_GROUP
);
4302 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4303 enum_name(req_info
[i
]), req_info
[i
].help
);
4306 group
= "External commands:";
4308 for (i
= 0; i
< run_requests
; i
++) {
4309 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4313 if (!req
|| req
->keymap
!= keymap
)
4316 key
= get_key_name(req
->key
);
4318 key
= "(no key defined)";
4320 if (add_title
&& help_open_keymap_title(view
, keymap
))
4323 add_line_text(view
, group
, LINE_HELP_GROUP
);
4327 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4328 if (!string_format_from(buf
, &bufpos
, "%s%s",
4329 argc
? " " : "", req
->argv
[argc
]))
4332 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4337 help_open(struct view
*view
)
4342 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4343 add_line_text(view
, "", LINE_DEFAULT
);
4345 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4346 help_open_keymap(view
, keymap
);
4352 help_request(struct view
*view
, enum request request
, struct line
*line
)
4356 if (line
->type
== LINE_HELP_KEYMAP
) {
4357 help_keymap_hidden
[line
->other
] =
4358 !help_keymap_hidden
[line
->other
];
4359 view
->p_restore
= TRUE
;
4360 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4365 return pager_request(view
, request
, line
);
4369 static struct view_ops help_ops
= {
4385 struct tree_stack_entry
{
4386 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4387 unsigned long lineno
; /* Line number to restore */
4388 char *name
; /* Position of name in opt_path */
4391 /* The top of the path stack. */
4392 static struct tree_stack_entry
*tree_stack
= NULL
;
4393 unsigned long tree_lineno
= 0;
4396 pop_tree_stack_entry(void)
4398 struct tree_stack_entry
*entry
= tree_stack
;
4400 tree_lineno
= entry
->lineno
;
4402 tree_stack
= entry
->prev
;
4407 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4409 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4410 size_t pathlen
= strlen(opt_path
);
4415 entry
->prev
= tree_stack
;
4416 entry
->name
= opt_path
+ pathlen
;
4419 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4420 pop_tree_stack_entry();
4424 /* Move the current line to the first tree entry. */
4426 entry
->lineno
= lineno
;
4429 /* Parse output from git-ls-tree(1):
4431 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4434 #define SIZEOF_TREE_ATTR \
4435 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4437 #define SIZEOF_TREE_MODE \
4438 STRING_SIZE("100644 ")
4440 #define TREE_ID_OFFSET \
4441 STRING_SIZE("100644 blob ")
4444 char id
[SIZEOF_REV
];
4446 struct time time
; /* Date from the author ident. */
4447 const char *author
; /* Author of the commit. */
4452 tree_path(const struct line
*line
)
4454 return ((struct tree_entry
*) line
->data
)->name
;
4458 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4460 if (line1
->type
!= line2
->type
)
4461 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4462 return strcmp(tree_path(line1
), tree_path(line2
));
4465 static const enum sort_field tree_sort_fields
[] = {
4466 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4468 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4471 tree_compare(const void *l1
, const void *l2
)
4473 const struct line
*line1
= (const struct line
*) l1
;
4474 const struct line
*line2
= (const struct line
*) l2
;
4475 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4476 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4478 if (line1
->type
== LINE_TREE_HEAD
)
4480 if (line2
->type
== LINE_TREE_HEAD
)
4483 switch (get_sort_field(tree_sort_state
)) {
4485 return sort_order(tree_sort_state
, timecmp(&entry1
->time
, &entry2
->time
));
4487 case ORDERBY_AUTHOR
:
4488 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4492 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4497 static struct line
*
4498 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4499 const char *mode
, const char *id
)
4501 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4502 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4504 if (!entry
|| !line
) {
4509 strncpy(entry
->name
, path
, strlen(path
));
4511 entry
->mode
= strtoul(mode
, NULL
, 8);
4513 string_copy_rev(entry
->id
, id
);
4519 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4521 static const char *author_name
;
4522 static struct time author_time
;
4524 if (!text
&& *read_date
) {
4529 char *path
= *opt_path
? opt_path
: ".";
4530 /* Find next entry to process */
4531 const char *log_file
[] = {
4532 "git", "log", "--no-color", "--pretty=raw",
4533 "--cc", "--raw", view
->id
, "--", path
, NULL
4537 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4538 report("Tree is empty");
4542 if (!start_update(view
, log_file
, opt_cdup
)) {
4543 report("Failed to load tree data");
4550 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4551 parse_author_line(text
+ STRING_SIZE("author "),
4552 &author_name
, &author_time
);
4554 } else if (*text
== ':') {
4556 size_t annotated
= 1;
4559 pos
= strchr(text
, '\t');
4563 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4564 text
+= strlen(opt_path
);
4565 pos
= strchr(text
, '/');
4569 for (i
= 1; i
< view
->lines
; i
++) {
4570 struct line
*line
= &view
->line
[i
];
4571 struct tree_entry
*entry
= line
->data
;
4573 annotated
+= !!entry
->author
;
4574 if (entry
->author
|| strcmp(entry
->name
, text
))
4577 entry
->author
= author_name
;
4578 entry
->time
= author_time
;
4583 if (annotated
== view
->lines
)
4584 io_kill(view
->pipe
);
4590 tree_read(struct view
*view
, char *text
)
4592 static bool read_date
= FALSE
;
4593 struct tree_entry
*data
;
4594 struct line
*entry
, *line
;
4595 enum line_type type
;
4596 size_t textlen
= text
? strlen(text
) : 0;
4597 char *path
= text
+ SIZEOF_TREE_ATTR
;
4599 if (read_date
|| !text
)
4600 return tree_read_date(view
, text
, &read_date
);
4602 if (textlen
<= SIZEOF_TREE_ATTR
)
4604 if (view
->lines
== 0 &&
4605 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4608 /* Strip the path part ... */
4610 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4611 size_t striplen
= strlen(opt_path
);
4613 if (pathlen
> striplen
)
4614 memmove(path
, path
+ striplen
,
4615 pathlen
- striplen
+ 1);
4617 /* Insert "link" to parent directory. */
4618 if (view
->lines
== 1 &&
4619 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4623 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4624 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4629 /* Skip "Directory ..." and ".." line. */
4630 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4631 if (tree_compare_entry(line
, entry
) <= 0)
4634 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4638 for (; line
<= entry
; line
++)
4639 line
->dirty
= line
->cleareol
= 1;
4643 if (tree_lineno
> view
->lineno
) {
4644 view
->lineno
= tree_lineno
;
4652 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4654 struct tree_entry
*entry
= line
->data
;
4656 if (line
->type
== LINE_TREE_HEAD
) {
4657 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4660 if (draw_mode(view
, entry
->mode
))
4663 if (opt_author
&& draw_author(view
, entry
->author
))
4666 if (opt_date
&& draw_date(view
, &entry
->time
))
4669 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4675 open_blob_editor(const char *id
)
4677 const char *blob_argv
[] = { "git", "cat-file", "blob", id
, NULL
};
4678 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4679 int fd
= mkstemp(file
);
4682 report("Failed to create temporary file");
4683 else if (!io_run_append(blob_argv
, fd
))
4684 report("Failed to save blob data to file");
4692 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4694 enum open_flags flags
;
4695 struct tree_entry
*entry
= line
->data
;
4698 case REQ_VIEW_BLAME
:
4699 if (line
->type
!= LINE_TREE_FILE
) {
4700 report("Blame only supported for files");
4704 string_copy(opt_ref
, view
->vid
);
4708 if (line
->type
!= LINE_TREE_FILE
) {
4709 report("Edit only supported for files");
4710 } else if (!is_head_commit(view
->vid
)) {
4711 open_blob_editor(entry
->id
);
4713 open_editor(opt_file
);
4717 case REQ_TOGGLE_SORT_FIELD
:
4718 case REQ_TOGGLE_SORT_ORDER
:
4719 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4724 /* quit view if at top of tree */
4725 return REQ_VIEW_CLOSE
;
4728 line
= &view
->line
[1];
4738 /* Cleanup the stack if the tree view is at a different tree. */
4739 while (!*opt_path
&& tree_stack
)
4740 pop_tree_stack_entry();
4742 switch (line
->type
) {
4744 /* Depending on whether it is a subdirectory or parent link
4745 * mangle the path buffer. */
4746 if (line
== &view
->line
[1] && *opt_path
) {
4747 pop_tree_stack_entry();
4750 const char *basename
= tree_path(line
);
4752 push_tree_stack_entry(basename
, view
->lineno
);
4755 /* Trees and subtrees share the same ID, so they are not not
4756 * unique like blobs. */
4757 flags
= OPEN_RELOAD
;
4758 request
= REQ_VIEW_TREE
;
4761 case LINE_TREE_FILE
:
4762 flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
4763 request
= REQ_VIEW_BLOB
;
4770 open_view(view
, request
, flags
);
4771 if (request
== REQ_VIEW_TREE
)
4772 view
->lineno
= tree_lineno
;
4778 tree_grep(struct view
*view
, struct line
*line
)
4780 struct tree_entry
*entry
= line
->data
;
4781 const char *text
[] = {
4783 opt_author
? entry
->author
: "",
4784 mkdate(&entry
->time
, opt_date
),
4788 return grep_text(view
, text
);
4792 tree_select(struct view
*view
, struct line
*line
)
4794 struct tree_entry
*entry
= line
->data
;
4796 if (line
->type
== LINE_TREE_FILE
) {
4797 string_copy_rev(ref_blob
, entry
->id
);
4798 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4800 } else if (line
->type
!= LINE_TREE_DIR
) {
4804 string_copy_rev(view
->ref
, entry
->id
);
4808 tree_prepare(struct view
*view
)
4810 if (view
->lines
== 0 && opt_prefix
[0]) {
4811 char *pos
= opt_prefix
;
4813 while (pos
&& *pos
) {
4814 char *end
= strchr(pos
, '/');
4818 push_tree_stack_entry(pos
, 0);
4826 } else if (strcmp(view
->vid
, view
->id
)) {
4830 return prepare_io(view
, opt_cdup
, view
->ops
->argv
, TRUE
);
4833 static const char *tree_argv
[SIZEOF_ARG
] = {
4834 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4837 static struct view_ops tree_ops
= {
4850 blob_read(struct view
*view
, char *line
)
4854 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4858 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4862 open_blob_editor(view
->vid
);
4865 return pager_request(view
, request
, line
);
4869 static const char *blob_argv
[SIZEOF_ARG
] = {
4870 "git", "cat-file", "blob", "%(blob)", NULL
4873 static struct view_ops blob_ops
= {
4887 * Loading the blame view is a two phase job:
4889 * 1. File content is read either using opt_file from the
4890 * filesystem or using git-cat-file.
4891 * 2. Then blame information is incrementally added by
4892 * reading output from git-blame.
4895 struct blame_commit
{
4896 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4897 char title
[128]; /* First line of the commit message. */
4898 const char *author
; /* Author of the commit. */
4899 struct time time
; /* Date from the author ident. */
4900 char filename
[128]; /* Name of file. */
4901 bool has_previous
; /* Was a "previous" line detected. */
4905 struct blame_commit
*commit
;
4906 unsigned long lineno
;
4911 blame_open(struct view
*view
)
4913 char path
[SIZEOF_STR
];
4915 if (!view
->prev
&& *opt_prefix
) {
4916 string_copy(path
, opt_file
);
4917 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4921 if (*opt_ref
|| !io_open(&view
->io
, "%s%s", opt_cdup
, opt_file
)) {
4922 const char *blame_cat_file_argv
[] = {
4923 "git", "cat-file", "blob", path
, NULL
4926 if (!string_format(path
, "%s:%s", opt_ref
, opt_file
) ||
4927 !start_update(view
, blame_cat_file_argv
, opt_cdup
))
4931 setup_update(view
, opt_file
);
4932 string_format(view
->ref
, "%s ...", opt_file
);
4937 static struct blame_commit
*
4938 get_blame_commit(struct view
*view
, const char *id
)
4942 for (i
= 0; i
< view
->lines
; i
++) {
4943 struct blame
*blame
= view
->line
[i
].data
;
4948 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4949 return blame
->commit
;
4953 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4956 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4962 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4964 const char *pos
= *posref
;
4967 pos
= strchr(pos
+ 1, ' ');
4968 if (!pos
|| !isdigit(pos
[1]))
4970 *number
= atoi(pos
+ 1);
4971 if (*number
< min
|| *number
> max
)
4978 static struct blame_commit
*
4979 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4981 struct blame_commit
*commit
;
4982 struct blame
*blame
;
4983 const char *pos
= text
+ SIZEOF_REV
- 2;
4984 size_t orig_lineno
= 0;
4988 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4991 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4992 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4993 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4996 commit
= get_blame_commit(view
, text
);
5002 struct line
*line
= &view
->line
[lineno
+ group
- 1];
5005 blame
->commit
= commit
;
5006 blame
->lineno
= orig_lineno
+ group
- 1;
5014 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
5017 const char *blame_argv
[] = {
5018 "git", "blame", "--incremental",
5019 *opt_ref
? opt_ref
: "--incremental", "--", opt_file
, NULL
5022 if (view
->lines
== 0 && !view
->prev
)
5023 die("No blame exist for %s", view
->vid
);
5025 if (view
->lines
== 0 || !start_update(view
, blame_argv
, opt_cdup
)) {
5026 report("Failed to load blame data");
5034 size_t linelen
= strlen(line
);
5035 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
5040 blame
->commit
= NULL
;
5041 strncpy(blame
->text
, line
, linelen
);
5042 blame
->text
[linelen
] = 0;
5043 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
5048 match_blame_header(const char *name
, char **line
)
5050 size_t namelen
= strlen(name
);
5051 bool matched
= !strncmp(name
, *line
, namelen
);
5060 blame_read(struct view
*view
, char *line
)
5062 static struct blame_commit
*commit
= NULL
;
5063 static int blamed
= 0;
5064 static bool read_file
= TRUE
;
5067 return blame_read_file(view
, line
, &read_file
);
5074 string_format(view
->ref
, "%s", view
->vid
);
5075 if (view_is_displayed(view
)) {
5076 update_view_title(view
);
5077 redraw_view_from(view
, 0);
5083 commit
= parse_blame_commit(view
, line
, &blamed
);
5084 string_format(view
->ref
, "%s %2d%%", view
->vid
,
5085 view
->lines
? blamed
* 100 / view
->lines
: 0);
5087 } else if (match_blame_header("author ", &line
)) {
5088 commit
->author
= get_author(line
);
5090 } else if (match_blame_header("author-time ", &line
)) {
5091 parse_timesec(&commit
->time
, line
);
5093 } else if (match_blame_header("author-tz ", &line
)) {
5094 parse_timezone(&commit
->time
, line
);
5096 } else if (match_blame_header("summary ", &line
)) {
5097 string_ncopy(commit
->title
, line
, strlen(line
));
5099 } else if (match_blame_header("previous ", &line
)) {
5100 commit
->has_previous
= TRUE
;
5102 } else if (match_blame_header("filename ", &line
)) {
5103 string_ncopy(commit
->filename
, line
, strlen(line
));
5111 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5113 struct blame
*blame
= line
->data
;
5114 struct time
*time
= NULL
;
5115 const char *id
= NULL
, *author
= NULL
;
5116 char text
[SIZEOF_STR
];
5118 if (blame
->commit
&& *blame
->commit
->filename
) {
5119 id
= blame
->commit
->id
;
5120 author
= blame
->commit
->author
;
5121 time
= &blame
->commit
->time
;
5124 if (opt_date
&& draw_date(view
, time
))
5127 if (opt_author
&& draw_author(view
, author
))
5130 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
5133 if (draw_lineno(view
, lineno
))
5136 string_expand(text
, sizeof(text
), blame
->text
, opt_tab_size
);
5137 draw_text(view
, LINE_DEFAULT
, text
, TRUE
);
5142 check_blame_commit(struct blame
*blame
, bool check_null_id
)
5145 report("Commit data not loaded yet");
5146 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5147 report("No commit exist for the selected line");
5154 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
5156 const char *diff_tree_argv
[] = {
5157 "git", "diff-tree", "-U0", blame
->commit
->id
,
5158 "--", blame
->commit
->filename
, NULL
5161 int parent_lineno
= -1;
5162 int blamed_lineno
= -1;
5165 if (!io_run(&io
, IO_RD
, NULL
, diff_tree_argv
))
5168 while ((line
= io_get(&io
, '\n', TRUE
))) {
5170 char *pos
= strchr(line
, '+');
5172 parent_lineno
= atoi(line
+ 4);
5174 blamed_lineno
= atoi(pos
+ 1);
5176 } else if (*line
== '+' && parent_lineno
!= -1) {
5177 if (blame
->lineno
== blamed_lineno
- 1 &&
5178 !strcmp(blame
->text
, line
+ 1)) {
5179 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
5190 blame_request(struct view
*view
, enum request request
, struct line
*line
)
5192 enum open_flags flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
5193 struct blame
*blame
= line
->data
;
5196 case REQ_VIEW_BLAME
:
5197 if (check_blame_commit(blame
, TRUE
)) {
5198 string_copy(opt_ref
, blame
->commit
->id
);
5199 string_copy(opt_file
, blame
->commit
->filename
);
5201 view
->lineno
= blame
->lineno
;
5202 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5207 if (check_blame_commit(blame
, TRUE
) &&
5208 select_commit_parent(blame
->commit
->id
, opt_ref
,
5209 blame
->commit
->filename
)) {
5210 string_copy(opt_file
, blame
->commit
->filename
);
5211 setup_blame_parent_line(view
, blame
);
5212 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5217 if (!check_blame_commit(blame
, FALSE
))
5220 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
5221 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
5224 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
5225 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
5226 const char *diff_index_argv
[] = {
5227 "git", "diff-index", "--root", "--patch-with-stat",
5228 "-C", "-M", "HEAD", "--", view
->vid
, NULL
5231 if (!blame
->commit
->has_previous
) {
5232 diff_index_argv
[1] = "diff";
5233 diff_index_argv
[2] = "--no-color";
5234 diff_index_argv
[6] = "--";
5235 diff_index_argv
[7] = "/dev/null";
5238 if (!prepare_update(diff
, diff_index_argv
, NULL
)) {
5239 report("Failed to allocate diff command");
5242 flags
|= OPEN_PREPARED
;
5245 open_view(view
, REQ_VIEW_DIFF
, flags
);
5246 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5247 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
5258 blame_grep(struct view
*view
, struct line
*line
)
5260 struct blame
*blame
= line
->data
;
5261 struct blame_commit
*commit
= blame
->commit
;
5262 const char *text
[] = {
5264 commit
? commit
->title
: "",
5265 commit
? commit
->id
: "",
5266 commit
&& opt_author
? commit
->author
: "",
5267 commit
? mkdate(&commit
->time
, opt_date
) : "",
5271 return grep_text(view
, text
);
5275 blame_select(struct view
*view
, struct line
*line
)
5277 struct blame
*blame
= line
->data
;
5278 struct blame_commit
*commit
= blame
->commit
;
5283 if (!strcmp(commit
->id
, NULL_ID
))
5284 string_ncopy(ref_commit
, "HEAD", 4);
5286 string_copy_rev(ref_commit
, commit
->id
);
5289 static struct view_ops blame_ops
= {
5305 const char *author
; /* Author of the last commit. */
5306 struct time time
; /* Date of the last activity. */
5307 const struct ref
*ref
; /* Name and commit ID information. */
5310 static const struct ref branch_all
;
5312 static const enum sort_field branch_sort_fields
[] = {
5313 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5315 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5318 branch_compare(const void *l1
, const void *l2
)
5320 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5321 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5323 switch (get_sort_field(branch_sort_state
)) {
5325 return sort_order(branch_sort_state
, timecmp(&branch1
->time
, &branch2
->time
));
5327 case ORDERBY_AUTHOR
:
5328 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5332 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5337 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5339 struct branch
*branch
= line
->data
;
5340 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5342 if (opt_date
&& draw_date(view
, &branch
->time
))
5345 if (opt_author
&& draw_author(view
, branch
->author
))
5348 draw_text(view
, type
, branch
->ref
== &branch_all
? "All branches" : branch
->ref
->name
, TRUE
);
5353 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5355 struct branch
*branch
= line
->data
;
5360 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5363 case REQ_TOGGLE_SORT_FIELD
:
5364 case REQ_TOGGLE_SORT_ORDER
:
5365 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5370 const struct ref
*ref
= branch
->ref
;
5371 const char *all_branches_argv
[] = {
5372 "git", "log", "--no-color", "--pretty=raw", "--parents",
5374 ref
== &branch_all
? "--all" : ref
->name
, NULL
5376 struct view
*main_view
= VIEW(REQ_VIEW_MAIN
);
5378 if (!prepare_update(main_view
, all_branches_argv
, NULL
))
5379 report("Failed to load view of all branches");
5381 open_view(view
, REQ_VIEW_MAIN
, OPEN_PREPARED
| OPEN_SPLIT
);
5390 branch_read(struct view
*view
, char *line
)
5392 static char id
[SIZEOF_REV
];
5393 struct branch
*reference
;
5399 switch (get_line_type(line
)) {
5401 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5405 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5406 struct branch
*branch
= view
->line
[i
].data
;
5408 if (strcmp(branch
->ref
->id
, id
))
5411 view
->line
[i
].dirty
= TRUE
;
5413 branch
->author
= reference
->author
;
5414 branch
->time
= reference
->time
;
5418 parse_author_line(line
+ STRING_SIZE("author "),
5419 &branch
->author
, &branch
->time
);
5431 branch_open_visitor(void *data
, const struct ref
*ref
)
5433 struct view
*view
= data
;
5434 struct branch
*branch
;
5436 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5439 branch
= calloc(1, sizeof(*branch
));
5444 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5448 branch_open(struct view
*view
)
5450 const char *branch_log
[] = {
5451 "git", "log", "--no-color", "--pretty=raw",
5452 "--simplify-by-decoration", "--all", NULL
5455 if (!start_update(view
, branch_log
, NULL
)) {
5456 report("Failed to load branch data");
5460 setup_update(view
, view
->id
);
5461 branch_open_visitor(view
, &branch_all
);
5462 foreach_ref(branch_open_visitor
, view
);
5463 view
->p_restore
= TRUE
;
5469 branch_grep(struct view
*view
, struct line
*line
)
5471 struct branch
*branch
= line
->data
;
5472 const char *text
[] = {
5478 return grep_text(view
, text
);
5482 branch_select(struct view
*view
, struct line
*line
)
5484 struct branch
*branch
= line
->data
;
5486 string_copy_rev(view
->ref
, branch
->ref
->id
);
5487 string_copy_rev(ref_commit
, branch
->ref
->id
);
5488 string_copy_rev(ref_head
, branch
->ref
->id
);
5489 string_copy_rev(ref_branch
, branch
->ref
->name
);
5492 static struct view_ops branch_ops
= {
5511 char rev
[SIZEOF_REV
];
5512 char name
[SIZEOF_STR
];
5516 char rev
[SIZEOF_REV
];
5517 char name
[SIZEOF_STR
];
5521 static char status_onbranch
[SIZEOF_STR
];
5522 static struct status stage_status
;
5523 static enum line_type stage_line_type
;
5524 static size_t stage_chunks
;
5525 static int *stage_chunk
;
5527 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5529 /* This should work even for the "On branch" line. */
5531 status_has_none(struct view
*view
, struct line
*line
)
5533 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5536 /* Get fields from the diff line:
5537 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5540 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5542 const char *old_mode
= buf
+ 1;
5543 const char *new_mode
= buf
+ 8;
5544 const char *old_rev
= buf
+ 15;
5545 const char *new_rev
= buf
+ 56;
5546 const char *status
= buf
+ 97;
5549 old_mode
[-1] != ':' ||
5550 new_mode
[-1] != ' ' ||
5551 old_rev
[-1] != ' ' ||
5552 new_rev
[-1] != ' ' ||
5556 file
->status
= *status
;
5558 string_copy_rev(file
->old
.rev
, old_rev
);
5559 string_copy_rev(file
->new.rev
, new_rev
);
5561 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5562 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5564 file
->old
.name
[0] = file
->new.name
[0] = 0;
5570 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5572 struct status
*unmerged
= NULL
;
5576 if (!io_run(&io
, IO_RD
, opt_cdup
, argv
))
5579 add_line_data(view
, NULL
, type
);
5581 while ((buf
= io_get(&io
, 0, TRUE
))) {
5582 struct status
*file
= unmerged
;
5585 file
= calloc(1, sizeof(*file
));
5586 if (!file
|| !add_line_data(view
, file
, type
))
5590 /* Parse diff info part. */
5592 file
->status
= status
;
5594 string_copy(file
->old
.rev
, NULL_ID
);
5596 } else if (!file
->status
|| file
== unmerged
) {
5597 if (!status_get_diff(file
, buf
, strlen(buf
)))
5600 buf
= io_get(&io
, 0, TRUE
);
5604 /* Collapse all modified entries that follow an
5605 * associated unmerged entry. */
5606 if (unmerged
== file
) {
5607 unmerged
->status
= 'U';
5609 } else if (file
->status
== 'U') {
5614 /* Grab the old name for rename/copy. */
5615 if (!*file
->old
.name
&&
5616 (file
->status
== 'R' || file
->status
== 'C')) {
5617 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5619 buf
= io_get(&io
, 0, TRUE
);
5624 /* git-ls-files just delivers a NUL separated list of
5625 * file names similar to the second half of the
5626 * git-diff-* output. */
5627 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5628 if (!*file
->old
.name
)
5629 string_copy(file
->old
.name
, file
->new.name
);
5633 if (io_error(&io
)) {
5639 if (!view
->line
[view
->lines
- 1].data
)
5640 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5646 /* Don't show unmerged entries in the staged section. */
5647 static const char *status_diff_index_argv
[] = {
5648 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5649 "--cached", "-M", "HEAD", NULL
5652 static const char *status_diff_files_argv
[] = {
5653 "git", "diff-files", "-z", NULL
5656 static const char *status_list_other_argv
[] = {
5657 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix
, NULL
5660 static const char *status_list_no_head_argv
[] = {
5661 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5664 static const char *update_index_argv
[] = {
5665 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5668 /* Restore the previous line number to stay in the context or select a
5669 * line with something that can be updated. */
5671 status_restore(struct view
*view
)
5673 if (view
->p_lineno
>= view
->lines
)
5674 view
->p_lineno
= view
->lines
- 1;
5675 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5677 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5680 /* If the above fails, always skip the "On branch" line. */
5681 if (view
->p_lineno
< view
->lines
)
5682 view
->lineno
= view
->p_lineno
;
5686 if (view
->lineno
< view
->offset
)
5687 view
->offset
= view
->lineno
;
5688 else if (view
->offset
+ view
->height
<= view
->lineno
)
5689 view
->offset
= view
->lineno
- view
->height
+ 1;
5691 view
->p_restore
= FALSE
;
5695 status_update_onbranch(void)
5697 static const char *paths
[][2] = {
5698 { "rebase-apply/rebasing", "Rebasing" },
5699 { "rebase-apply/applying", "Applying mailbox" },
5700 { "rebase-apply/", "Rebasing mailbox" },
5701 { "rebase-merge/interactive", "Interactive rebase" },
5702 { "rebase-merge/", "Rebase merge" },
5703 { "MERGE_HEAD", "Merging" },
5704 { "BISECT_LOG", "Bisecting" },
5705 { "HEAD", "On branch" },
5707 char buf
[SIZEOF_STR
];
5711 if (is_initial_commit()) {
5712 string_copy(status_onbranch
, "Initial commit");
5716 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5717 char *head
= opt_head
;
5719 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5720 lstat(buf
, &stat
) < 0)
5726 if (io_open(&io
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5727 io_read_buf(&io
, buf
, sizeof(buf
))) {
5729 if (!prefixcmp(head
, "refs/heads/"))
5730 head
+= STRING_SIZE("refs/heads/");
5734 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5735 string_copy(status_onbranch
, opt_head
);
5739 string_copy(status_onbranch
, "Not currently on any branch");
5742 /* First parse staged info using git-diff-index(1), then parse unstaged
5743 * info using git-diff-files(1), and finally untracked files using
5744 * git-ls-files(1). */
5746 status_open(struct view
*view
)
5750 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5751 status_update_onbranch();
5753 io_run_bg(update_index_argv
);
5755 if (is_initial_commit()) {
5756 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5758 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5762 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5763 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5766 /* Restore the exact position or use the specialized restore
5768 if (!view
->p_restore
)
5769 status_restore(view
);
5774 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5776 struct status
*status
= line
->data
;
5777 enum line_type type
;
5781 switch (line
->type
) {
5782 case LINE_STAT_STAGED
:
5783 type
= LINE_STAT_SECTION
;
5784 text
= "Changes to be committed:";
5787 case LINE_STAT_UNSTAGED
:
5788 type
= LINE_STAT_SECTION
;
5789 text
= "Changed but not updated:";
5792 case LINE_STAT_UNTRACKED
:
5793 type
= LINE_STAT_SECTION
;
5794 text
= "Untracked files:";
5797 case LINE_STAT_NONE
:
5798 type
= LINE_DEFAULT
;
5799 text
= " (no files)";
5802 case LINE_STAT_HEAD
:
5803 type
= LINE_STAT_HEAD
;
5804 text
= status_onbranch
;
5811 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5813 buf
[0] = status
->status
;
5814 if (draw_text(view
, line
->type
, buf
, TRUE
))
5816 type
= LINE_DEFAULT
;
5817 text
= status
->new.name
;
5820 draw_text(view
, type
, text
, TRUE
);
5825 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5827 if (displayed_views() == 2 || display
[current_view
] != view
)
5828 maximize_view(view
);
5829 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5834 status_enter(struct view
*view
, struct line
*line
)
5836 struct status
*status
= line
->data
;
5837 const char *oldpath
= status
? status
->old
.name
: NULL
;
5838 /* Diffs for unmerged entries are empty when passing the new
5839 * path, so leave it empty. */
5840 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5842 enum open_flags split
;
5843 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5845 if (line
->type
== LINE_STAT_NONE
||
5846 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5847 report("No file to diff");
5851 switch (line
->type
) {
5852 case LINE_STAT_STAGED
:
5853 if (is_initial_commit()) {
5854 const char *no_head_diff_argv
[] = {
5855 "git", "diff", "--no-color", "--patch-with-stat",
5856 "--", "/dev/null", newpath
, NULL
5859 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
))
5860 return status_load_error(view
, stage
, newpath
);
5862 const char *index_show_argv
[] = {
5863 "git", "diff-index", "--root", "--patch-with-stat",
5864 "-C", "-M", "--cached", "HEAD", "--",
5865 oldpath
, newpath
, NULL
5868 if (!prepare_update(stage
, index_show_argv
, opt_cdup
))
5869 return status_load_error(view
, stage
, newpath
);
5873 info
= "Staged changes to %s";
5875 info
= "Staged changes";
5878 case LINE_STAT_UNSTAGED
:
5880 const char *files_show_argv
[] = {
5881 "git", "diff-files", "--root", "--patch-with-stat",
5882 "-C", "-M", "--", oldpath
, newpath
, NULL
5885 if (!prepare_update(stage
, files_show_argv
, opt_cdup
))
5886 return status_load_error(view
, stage
, newpath
);
5888 info
= "Unstaged changes to %s";
5890 info
= "Unstaged changes";
5893 case LINE_STAT_UNTRACKED
:
5895 report("No file to show");
5899 if (!suffixcmp(status
->new.name
, -1, "/")) {
5900 report("Cannot display a directory");
5904 if (!prepare_update_file(stage
, newpath
))
5905 return status_load_error(view
, stage
, newpath
);
5906 info
= "Untracked file %s";
5909 case LINE_STAT_HEAD
:
5913 die("line type %d not handled in switch", line
->type
);
5916 split
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
5917 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5918 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5920 stage_status
= *status
;
5922 memset(&stage_status
, 0, sizeof(stage_status
));
5925 stage_line_type
= line
->type
;
5927 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5934 status_exists(struct status
*status
, enum line_type type
)
5936 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5937 unsigned long lineno
;
5939 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5940 struct line
*line
= &view
->line
[lineno
];
5941 struct status
*pos
= line
->data
;
5943 if (line
->type
!= type
)
5945 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5946 select_view_line(view
, lineno
);
5949 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5950 select_view_line(view
, lineno
);
5960 status_update_prepare(struct io
*io
, enum line_type type
)
5962 const char *staged_argv
[] = {
5963 "git", "update-index", "-z", "--index-info", NULL
5965 const char *others_argv
[] = {
5966 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5970 case LINE_STAT_STAGED
:
5971 return io_run(io
, IO_WR
, opt_cdup
, staged_argv
);
5973 case LINE_STAT_UNSTAGED
:
5974 case LINE_STAT_UNTRACKED
:
5975 return io_run(io
, IO_WR
, opt_cdup
, others_argv
);
5978 die("line type %d not handled in switch", type
);
5984 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5986 char buf
[SIZEOF_STR
];
5990 case LINE_STAT_STAGED
:
5991 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5994 status
->old
.name
, 0))
5998 case LINE_STAT_UNSTAGED
:
5999 case LINE_STAT_UNTRACKED
:
6000 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
6005 die("line type %d not handled in switch", type
);
6008 return io_write(io
, buf
, bufsize
);
6012 status_update_file(struct status
*status
, enum line_type type
)
6017 if (!status_update_prepare(&io
, type
))
6020 result
= status_update_write(&io
, status
, type
);
6021 return io_done(&io
) && result
;
6025 status_update_files(struct view
*view
, struct line
*line
)
6027 char buf
[sizeof(view
->ref
)];
6030 struct line
*pos
= view
->line
+ view
->lines
;
6033 int cursor_y
= -1, cursor_x
= -1;
6035 if (!status_update_prepare(&io
, line
->type
))
6038 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
6041 string_copy(buf
, view
->ref
);
6042 getsyx(cursor_y
, cursor_x
);
6043 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
6044 int almost_done
= file
* 100 / files
;
6046 if (almost_done
> done
) {
6048 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
6050 update_view_title(view
);
6051 setsyx(cursor_y
, cursor_x
);
6054 result
= status_update_write(&io
, line
->data
, line
->type
);
6056 string_copy(view
->ref
, buf
);
6058 return io_done(&io
) && result
;
6062 status_update(struct view
*view
)
6064 struct line
*line
= &view
->line
[view
->lineno
];
6066 assert(view
->lines
);
6069 /* This should work even for the "On branch" line. */
6070 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
6071 report("Nothing to update");
6075 if (!status_update_files(view
, line
+ 1)) {
6076 report("Failed to update file status");
6080 } else if (!status_update_file(line
->data
, line
->type
)) {
6081 report("Failed to update file status");
6089 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
6091 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
6092 if (type
== LINE_STAT_STAGED
) {
6093 report("Cannot revert changes to staged files");
6094 } else if (type
== LINE_STAT_UNTRACKED
) {
6095 report("Cannot revert changes to untracked files");
6096 } else if (has_none
) {
6097 report("Nothing to revert");
6099 report("Cannot revert changes to multiple files");
6102 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6103 char mode
[10] = "100644";
6104 const char *reset_argv
[] = {
6105 "git", "update-index", "--cacheinfo", mode
,
6106 status
->old
.rev
, status
->old
.name
, NULL
6108 const char *checkout_argv
[] = {
6109 "git", "checkout", "--", status
->old
.name
, NULL
6112 if (status
->status
== 'U') {
6113 string_format(mode
, "%5o", status
->old
.mode
);
6115 if (status
->old
.mode
== 0 && status
->new.mode
== 0) {
6116 reset_argv
[2] = "--force-remove";
6117 reset_argv
[3] = status
->old
.name
;
6118 reset_argv
[4] = NULL
;
6121 if (!io_run_fg(reset_argv
, opt_cdup
))
6123 if (status
->old
.mode
== 0 && status
->new.mode
== 0)
6127 return io_run_fg(checkout_argv
, opt_cdup
);
6134 status_request(struct view
*view
, enum request request
, struct line
*line
)
6136 struct status
*status
= line
->data
;
6139 case REQ_STATUS_UPDATE
:
6140 if (!status_update(view
))
6144 case REQ_STATUS_REVERT
:
6145 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
6149 case REQ_STATUS_MERGE
:
6150 if (!status
|| status
->status
!= 'U') {
6151 report("Merging only possible for files with unmerged status ('U').");
6154 open_mergetool(status
->new.name
);
6160 if (status
->status
== 'D') {
6161 report("File has been deleted.");
6165 open_editor(status
->new.name
);
6168 case REQ_VIEW_BLAME
:
6174 /* After returning the status view has been split to
6175 * show the stage view. No further reloading is
6177 return status_enter(view
, line
);
6180 /* Simply reload the view. */
6187 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
6193 status_select(struct view
*view
, struct line
*line
)
6195 struct status
*status
= line
->data
;
6196 char file
[SIZEOF_STR
] = "all files";
6200 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
6203 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
6206 switch (line
->type
) {
6207 case LINE_STAT_STAGED
:
6208 text
= "Press %s to unstage %s for commit";
6211 case LINE_STAT_UNSTAGED
:
6212 text
= "Press %s to stage %s for commit";
6215 case LINE_STAT_UNTRACKED
:
6216 text
= "Press %s to stage %s for addition";
6219 case LINE_STAT_HEAD
:
6220 case LINE_STAT_NONE
:
6221 text
= "Nothing to update";
6225 die("line type %d not handled in switch", line
->type
);
6228 if (status
&& status
->status
== 'U') {
6229 text
= "Press %s to resolve conflict in %s";
6230 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
6233 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
6236 string_format(view
->ref
, text
, key
, file
);
6238 string_copy(opt_file
, status
->new.name
);
6242 status_grep(struct view
*view
, struct line
*line
)
6244 struct status
*status
= line
->data
;
6247 const char buf
[2] = { status
->status
, 0 };
6248 const char *text
[] = { status
->new.name
, buf
, NULL
};
6250 return grep_text(view
, text
);
6256 static struct view_ops status_ops
= {
6269 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
6271 while (line
< end
) {
6272 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
6273 !io_write(io
, "\n", 1))
6276 if (line
->type
== LINE_DIFF_CHUNK
||
6277 line
->type
== LINE_DIFF_HEADER
)
6284 static struct line
*
6285 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
6287 for (; view
->line
< line
; line
--)
6288 if (line
->type
== type
)
6295 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
6297 const char *apply_argv
[SIZEOF_ARG
] = {
6298 "git", "apply", "--whitespace=nowarn", NULL
6300 struct line
*diff_hdr
;
6304 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6309 apply_argv
[argc
++] = "--cached";
6310 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6311 apply_argv
[argc
++] = "-R";
6312 apply_argv
[argc
++] = "-";
6313 apply_argv
[argc
++] = NULL
;
6314 if (!io_run(&io
, IO_WR
, opt_cdup
, apply_argv
))
6317 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6318 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6322 io_run_bg(update_index_argv
);
6324 return chunk
? TRUE
: FALSE
;
6328 stage_update(struct view
*view
, struct line
*line
)
6330 struct line
*chunk
= NULL
;
6332 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6333 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6336 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6337 report("Failed to apply chunk");
6341 } else if (!stage_status
.status
) {
6342 view
= VIEW(REQ_VIEW_STATUS
);
6344 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6345 if (line
->type
== stage_line_type
)
6348 if (!status_update_files(view
, line
+ 1)) {
6349 report("Failed to update files");
6353 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6354 report("Failed to update file");
6362 stage_revert(struct view
*view
, struct line
*line
)
6364 struct line
*chunk
= NULL
;
6366 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6367 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6370 if (!prompt_yesno("Are you sure you want to revert changes?"))
6373 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6374 report("Failed to revert chunk");
6380 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6381 stage_line_type
, FALSE
);
6387 stage_next(struct view
*view
, struct line
*line
)
6391 if (!stage_chunks
) {
6392 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6393 if (line
->type
!= LINE_DIFF_CHUNK
)
6396 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6397 report("Allocation failure");
6401 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6405 for (i
= 0; i
< stage_chunks
; i
++) {
6406 if (stage_chunk
[i
] > view
->lineno
) {
6407 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6408 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6413 report("No next chunk found");
6417 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6420 case REQ_STATUS_UPDATE
:
6421 if (!stage_update(view
, line
))
6425 case REQ_STATUS_REVERT
:
6426 if (!stage_revert(view
, line
))
6430 case REQ_STAGE_NEXT
:
6431 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6432 report("File is untracked; press %s to add",
6433 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6436 stage_next(view
, line
);
6440 if (!stage_status
.new.name
[0])
6442 if (stage_status
.status
== 'D') {
6443 report("File has been deleted.");
6447 open_editor(stage_status
.new.name
);
6451 /* Reload everything ... */
6454 case REQ_VIEW_BLAME
:
6455 if (stage_status
.new.name
[0]) {
6456 string_copy(opt_file
, stage_status
.new.name
);
6462 return pager_request(view
, request
, line
);
6468 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6469 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6471 /* Check whether the staged entry still exists, and close the
6472 * stage view if it doesn't. */
6473 if (!status_exists(&stage_status
, stage_line_type
)) {
6474 status_restore(VIEW(REQ_VIEW_STATUS
));
6475 return REQ_VIEW_CLOSE
;
6478 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6479 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6480 report("Cannot display a directory");
6484 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6485 report("Failed to open file: %s", strerror(errno
));
6489 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6494 static struct view_ops stage_ops
= {
6511 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6512 char title
[128]; /* First line of the commit message. */
6513 const char *author
; /* Author of the commit. */
6514 struct time time
; /* Date from the author ident. */
6515 struct ref_list
*refs
; /* Repository references. */
6516 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6517 size_t graph_size
; /* The width of the graph array. */
6518 bool has_parents
; /* Rewritten --parents seen. */
6521 /* Size of rev graph with no "padding" columns */
6522 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6525 struct rev_graph
*prev
, *next
, *parents
;
6526 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6528 struct commit
*commit
;
6530 unsigned int boundary
:1;
6533 /* Parents of the commit being visualized. */
6534 static struct rev_graph graph_parents
[4];
6536 /* The current stack of revisions on the graph. */
6537 static struct rev_graph graph_stacks
[4] = {
6538 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6539 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6540 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6541 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6545 graph_parent_is_merge(struct rev_graph
*graph
)
6547 return graph
->parents
->size
> 1;
6551 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6553 struct commit
*commit
= graph
->commit
;
6555 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6556 commit
->graph
[commit
->graph_size
++] = symbol
;
6560 clear_rev_graph(struct rev_graph
*graph
)
6562 graph
->boundary
= 0;
6563 graph
->size
= graph
->pos
= 0;
6564 graph
->commit
= NULL
;
6565 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6569 done_rev_graph(struct rev_graph
*graph
)
6571 if (graph_parent_is_merge(graph
) &&
6572 graph
->pos
< graph
->size
- 1 &&
6573 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6574 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6576 graph
->commit
->graph_size
= i
* 2;
6577 while (i
< graph
->next
->size
- 1) {
6578 append_to_rev_graph(graph
, ' ');
6579 append_to_rev_graph(graph
, '\\');
6584 clear_rev_graph(graph
);
6588 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6592 /* "Collapse" duplicate parents lines.
6594 * FIXME: This needs to also update update the drawn graph but
6595 * for now it just serves as a method for pruning graph lines. */
6596 for (i
= 0; i
< graph
->size
; i
++)
6597 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6600 if (graph
->size
< SIZEOF_REVITEMS
) {
6601 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6606 get_rev_graph_symbol(struct rev_graph
*graph
)
6610 if (graph
->boundary
)
6611 symbol
= REVGRAPH_BOUND
;
6612 else if (graph
->parents
->size
== 0)
6613 symbol
= REVGRAPH_INIT
;
6614 else if (graph_parent_is_merge(graph
))
6615 symbol
= REVGRAPH_MERGE
;
6616 else if (graph
->pos
>= graph
->size
)
6617 symbol
= REVGRAPH_BRANCH
;
6619 symbol
= REVGRAPH_COMMIT
;
6625 draw_rev_graph(struct rev_graph
*graph
)
6628 chtype separator
, line
;
6630 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6631 static struct rev_filler fillers
[] = {
6637 chtype symbol
= get_rev_graph_symbol(graph
);
6638 struct rev_filler
*filler
;
6641 fillers
[DEFAULT
].line
= opt_line_graphics
? ACS_VLINE
: '|';
6642 filler
= &fillers
[DEFAULT
];
6644 for (i
= 0; i
< graph
->pos
; i
++) {
6645 append_to_rev_graph(graph
, filler
->line
);
6646 if (graph_parent_is_merge(graph
->prev
) &&
6647 graph
->prev
->pos
== i
)
6648 filler
= &fillers
[RSHARP
];
6650 append_to_rev_graph(graph
, filler
->separator
);
6653 /* Place the symbol for this revision. */
6654 append_to_rev_graph(graph
, symbol
);
6656 if (graph
->prev
->size
> graph
->size
)
6657 filler
= &fillers
[RDIAG
];
6659 filler
= &fillers
[DEFAULT
];
6663 for (; i
< graph
->size
; i
++) {
6664 append_to_rev_graph(graph
, filler
->separator
);
6665 append_to_rev_graph(graph
, filler
->line
);
6666 if (graph_parent_is_merge(graph
->prev
) &&
6667 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6668 filler
= &fillers
[RSHARP
];
6669 if (graph
->prev
->size
> graph
->size
)
6670 filler
= &fillers
[LDIAG
];
6673 if (graph
->prev
->size
> graph
->size
) {
6674 append_to_rev_graph(graph
, filler
->separator
);
6675 if (filler
->line
!= ' ')
6676 append_to_rev_graph(graph
, filler
->line
);
6680 /* Prepare the next rev graph */
6682 prepare_rev_graph(struct rev_graph
*graph
)
6686 /* First, traverse all lines of revisions up to the active one. */
6687 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6688 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6691 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6694 /* Interleave the new revision parent(s). */
6695 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6696 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6698 /* Lastly, put any remaining revisions. */
6699 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6700 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6704 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6706 /* If this is the finalizing update ... */
6708 prepare_rev_graph(graph
);
6710 /* Graph visualization needs a one rev look-ahead,
6711 * so the first update doesn't visualize anything. */
6712 if (!graph
->prev
->commit
)
6715 if (view
->lines
> 2)
6716 view
->line
[view
->lines
- 3].dirty
= 1;
6717 if (view
->lines
> 1)
6718 view
->line
[view
->lines
- 2].dirty
= 1;
6719 draw_rev_graph(graph
->prev
);
6720 done_rev_graph(graph
->prev
->prev
);
6728 static const char *main_argv
[SIZEOF_ARG
] = {
6729 "git", "log", "--no-color", "--pretty=raw", "--parents",
6730 "--topo-order", "%(diff-args)", "%(rev-args)",
6731 "--", "%(file-args)", NULL
6735 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6737 struct commit
*commit
= line
->data
;
6739 if (!commit
->author
)
6742 if (opt_date
&& draw_date(view
, &commit
->time
))
6745 if (opt_author
&& draw_author(view
, commit
->author
))
6748 if (opt_rev_graph
&& commit
->graph_size
&&
6749 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6752 if (opt_show_refs
&& commit
->refs
) {
6755 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6756 struct ref
*ref
= commit
->refs
->refs
[i
];
6757 enum line_type type
;
6760 type
= LINE_MAIN_HEAD
;
6762 type
= LINE_MAIN_LOCAL_TAG
;
6764 type
= LINE_MAIN_TAG
;
6765 else if (ref
->tracked
)
6766 type
= LINE_MAIN_TRACKED
;
6767 else if (ref
->remote
)
6768 type
= LINE_MAIN_REMOTE
;
6770 type
= LINE_MAIN_REF
;
6772 if (draw_text(view
, type
, "[", TRUE
) ||
6773 draw_text(view
, type
, ref
->name
, TRUE
) ||
6774 draw_text(view
, type
, "]", TRUE
))
6777 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6782 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6786 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6788 main_read(struct view
*view
, char *line
)
6790 static struct rev_graph
*graph
= graph_stacks
;
6791 enum line_type type
;
6792 struct commit
*commit
;
6797 if (!view
->lines
&& !view
->prev
)
6798 die("No revisions match the given arguments.");
6799 if (view
->lines
> 0) {
6800 commit
= view
->line
[view
->lines
- 1].data
;
6801 view
->line
[view
->lines
- 1].dirty
= 1;
6802 if (!commit
->author
) {
6805 graph
->commit
= NULL
;
6808 update_rev_graph(view
, graph
);
6810 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6811 clear_rev_graph(&graph_stacks
[i
]);
6815 type
= get_line_type(line
);
6816 if (type
== LINE_COMMIT
) {
6817 commit
= calloc(1, sizeof(struct commit
));
6821 line
+= STRING_SIZE("commit ");
6823 graph
->boundary
= 1;
6827 string_copy_rev(commit
->id
, line
);
6828 commit
->refs
= get_ref_list(commit
->id
);
6829 graph
->commit
= commit
;
6830 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6832 while ((line
= strchr(line
, ' '))) {
6834 push_rev_graph(graph
->parents
, line
);
6835 commit
->has_parents
= TRUE
;
6842 commit
= view
->line
[view
->lines
- 1].data
;
6846 if (commit
->has_parents
)
6848 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6852 parse_author_line(line
+ STRING_SIZE("author "),
6853 &commit
->author
, &commit
->time
);
6854 update_rev_graph(view
, graph
);
6855 graph
= graph
->next
;
6859 /* Fill in the commit title if it has not already been set. */
6860 if (commit
->title
[0])
6863 /* Require titles to start with a non-space character at the
6864 * offset used by git log. */
6865 if (strncmp(line
, " ", 4))
6868 /* Well, if the title starts with a whitespace character,
6869 * try to be forgiving. Otherwise we end up with no title. */
6870 while (isspace(*line
))
6874 /* FIXME: More graceful handling of titles; append "..." to
6875 * shortened titles, etc. */
6877 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6878 view
->line
[view
->lines
- 1].dirty
= 1;
6885 main_request(struct view
*view
, enum request request
, struct line
*line
)
6887 enum open_flags flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
6891 open_view(view
, REQ_VIEW_DIFF
, flags
);
6895 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6905 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6910 if (!opt_show_refs
|| !list
)
6913 for (i
= 0; i
< list
->size
; i
++) {
6914 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6922 main_grep(struct view
*view
, struct line
*line
)
6924 struct commit
*commit
= line
->data
;
6925 const char *text
[] = {
6927 opt_author
? commit
->author
: "",
6928 mkdate(&commit
->time
, opt_date
),
6932 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6936 main_select(struct view
*view
, struct line
*line
)
6938 struct commit
*commit
= line
->data
;
6940 string_copy_rev(view
->ref
, commit
->id
);
6941 string_copy_rev(ref_commit
, view
->ref
);
6944 static struct view_ops main_ops
= {
6960 /* Whether or not the curses interface has been initialized. */
6961 static bool cursed
= FALSE
;
6963 /* Terminal hacks and workarounds. */
6964 static bool use_scroll_redrawwin
;
6965 static bool use_scroll_status_wclear
;
6967 /* The status window is used for polling keystrokes. */
6968 static WINDOW
*status_win
;
6970 /* Reading from the prompt? */
6971 static bool input_mode
= FALSE
;
6973 static bool status_empty
= FALSE
;
6975 /* Update status and title window. */
6977 report(const char *msg
, ...)
6979 struct view
*view
= display
[current_view
];
6985 char buf
[SIZEOF_STR
];
6988 va_start(args
, msg
);
6989 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6990 buf
[sizeof(buf
) - 1] = 0;
6991 buf
[sizeof(buf
) - 2] = '.';
6992 buf
[sizeof(buf
) - 3] = '.';
6993 buf
[sizeof(buf
) - 4] = '.';
6999 if (!status_empty
|| *msg
) {
7002 va_start(args
, msg
);
7004 wmove(status_win
, 0, 0);
7005 if (view
->has_scrolled
&& use_scroll_status_wclear
)
7008 vwprintw(status_win
, msg
, args
);
7009 status_empty
= FALSE
;
7011 status_empty
= TRUE
;
7013 wclrtoeol(status_win
);
7014 wnoutrefresh(status_win
);
7019 update_view_title(view
);
7028 /* Initialize the curses library */
7029 if (isatty(STDIN_FILENO
)) {
7030 cursed
= !!initscr();
7033 /* Leave stdin and stdout alone when acting as a pager. */
7034 opt_tty
= fopen("/dev/tty", "r+");
7036 die("Failed to open /dev/tty");
7037 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
7041 die("Failed to initialize curses");
7043 nonl(); /* Disable conversion and detect newlines from input. */
7044 cbreak(); /* Take input chars one at a time, no wait for \n */
7045 noecho(); /* Don't echo input */
7046 leaveok(stdscr
, FALSE
);
7051 getmaxyx(stdscr
, y
, x
);
7052 status_win
= newwin(1, 0, y
- 1, 0);
7054 die("Failed to create status window");
7056 /* Enable keyboard mapping */
7057 keypad(status_win
, TRUE
);
7058 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
7060 TABSIZE
= opt_tab_size
;
7062 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
7063 if (term
&& !strcmp(term
, "gnome-terminal")) {
7064 /* In the gnome-terminal-emulator, the message from
7065 * scrolling up one line when impossible followed by
7066 * scrolling down one line causes corruption of the
7067 * status line. This is fixed by calling wclear. */
7068 use_scroll_status_wclear
= TRUE
;
7069 use_scroll_redrawwin
= FALSE
;
7071 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
7072 /* No problems with full optimizations in xrvt-(unicode)
7074 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
7077 /* When scrolling in (u)xterm the last line in the
7078 * scrolling direction will update slowly. */
7079 use_scroll_redrawwin
= TRUE
;
7080 use_scroll_status_wclear
= FALSE
;
7085 get_input(int prompt_position
)
7088 int i
, key
, cursor_y
, cursor_x
;
7090 if (prompt_position
)
7094 bool loading
= FALSE
;
7096 foreach_view (view
, i
) {
7098 if (view_is_displayed(view
) && view
->has_scrolled
&&
7099 use_scroll_redrawwin
)
7100 redrawwin(view
->win
);
7101 view
->has_scrolled
= FALSE
;
7106 /* Update the cursor position. */
7107 if (prompt_position
) {
7108 getbegyx(status_win
, cursor_y
, cursor_x
);
7109 cursor_x
= prompt_position
;
7111 view
= display
[current_view
];
7112 getbegyx(view
->win
, cursor_y
, cursor_x
);
7113 cursor_x
= view
->width
- 1;
7114 cursor_y
+= view
->lineno
- view
->offset
;
7116 setsyx(cursor_y
, cursor_x
);
7118 /* Refresh, accept single keystroke of input */
7120 nodelay(status_win
, loading
);
7121 key
= wgetch(status_win
);
7123 /* wgetch() with nodelay() enabled returns ERR when
7124 * there's no input. */
7127 } else if (key
== KEY_RESIZE
) {
7130 getmaxyx(stdscr
, height
, width
);
7132 wresize(status_win
, 1, width
);
7133 mvwin(status_win
, height
- 1, 0);
7134 wnoutrefresh(status_win
);
7136 redraw_display(TRUE
);
7146 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7148 enum input_status status
= INPUT_OK
;
7149 static char buf
[SIZEOF_STR
];
7154 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7157 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7158 wclrtoeol(status_win
);
7160 key
= get_input(pos
+ 1);
7165 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7172 status
= INPUT_CANCEL
;
7176 status
= INPUT_CANCEL
;
7180 if (pos
>= sizeof(buf
)) {
7181 report("Input string too long");
7185 status
= handler(data
, buf
, key
);
7186 if (status
== INPUT_OK
)
7187 buf
[pos
++] = (char) key
;
7191 /* Clear the status window */
7192 status_empty
= FALSE
;
7195 if (status
== INPUT_CANCEL
)
7203 static enum input_status
7204 prompt_yesno_handler(void *data
, char *buf
, int c
)
7206 if (c
== 'y' || c
== 'Y')
7208 if (c
== 'n' || c
== 'N')
7209 return INPUT_CANCEL
;
7214 prompt_yesno(const char *prompt
)
7216 char prompt2
[SIZEOF_STR
];
7218 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7221 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7224 static enum input_status
7225 read_prompt_handler(void *data
, char *buf
, int c
)
7227 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7231 read_prompt(const char *prompt
)
7233 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7236 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7238 enum input_status status
= INPUT_OK
;
7241 while (items
[size
].text
)
7244 while (status
== INPUT_OK
) {
7245 const struct menu_item
*item
= &items
[*selected
];
7249 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7250 prompt
, *selected
+ 1, size
);
7252 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7253 wprintw(status_win
, "%s", item
->text
);
7254 wclrtoeol(status_win
);
7256 key
= get_input(COLS
- 1);
7261 status
= INPUT_STOP
;
7266 *selected
= *selected
- 1;
7268 *selected
= size
- 1;
7273 *selected
= (*selected
+ 1) % size
;
7277 status
= INPUT_CANCEL
;
7281 for (i
= 0; items
[i
].text
; i
++)
7282 if (items
[i
].hotkey
== key
) {
7284 status
= INPUT_STOP
;
7290 /* Clear the status window */
7291 status_empty
= FALSE
;
7294 return status
!= INPUT_CANCEL
;
7298 * Repository properties
7301 static struct ref
**refs
= NULL
;
7302 static size_t refs_size
= 0;
7303 static struct ref
*refs_head
= NULL
;
7305 static struct ref_list
**ref_lists
= NULL
;
7306 static size_t ref_lists_size
= 0;
7308 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7309 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7310 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7313 compare_refs(const void *ref1_
, const void *ref2_
)
7315 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7316 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7318 if (ref1
->tag
!= ref2
->tag
)
7319 return ref2
->tag
- ref1
->tag
;
7320 if (ref1
->ltag
!= ref2
->ltag
)
7321 return ref2
->ltag
- ref2
->ltag
;
7322 if (ref1
->head
!= ref2
->head
)
7323 return ref2
->head
- ref1
->head
;
7324 if (ref1
->tracked
!= ref2
->tracked
)
7325 return ref2
->tracked
- ref1
->tracked
;
7326 if (ref1
->remote
!= ref2
->remote
)
7327 return ref2
->remote
- ref1
->remote
;
7328 return strcmp(ref1
->name
, ref2
->name
);
7332 foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
)
7336 for (i
= 0; i
< refs_size
; i
++)
7337 if (!visitor(data
, refs
[i
]))
7347 static struct ref_list
*
7348 get_ref_list(const char *id
)
7350 struct ref_list
*list
;
7353 for (i
= 0; i
< ref_lists_size
; i
++)
7354 if (!strcmp(id
, ref_lists
[i
]->id
))
7355 return ref_lists
[i
];
7357 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7359 list
= calloc(1, sizeof(*list
));
7363 for (i
= 0; i
< refs_size
; i
++) {
7364 if (!strcmp(id
, refs
[i
]->id
) &&
7365 realloc_refs_list(&list
->refs
, list
->size
, 1))
7366 list
->refs
[list
->size
++] = refs
[i
];
7374 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7375 ref_lists
[ref_lists_size
++] = list
;
7380 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
7382 struct ref
*ref
= NULL
;
7385 bool remote
= FALSE
;
7386 bool tracked
= FALSE
;
7388 int from
= 0, to
= refs_size
- 1;
7390 if (!prefixcmp(name
, "refs/tags/")) {
7391 if (!suffixcmp(name
, namelen
, "^{}")) {
7399 namelen
-= STRING_SIZE("refs/tags/");
7400 name
+= STRING_SIZE("refs/tags/");
7402 } else if (!prefixcmp(name
, "refs/remotes/")) {
7404 namelen
-= STRING_SIZE("refs/remotes/");
7405 name
+= STRING_SIZE("refs/remotes/");
7406 tracked
= !strcmp(opt_remote
, name
);
7408 } else if (!prefixcmp(name
, "refs/heads/")) {
7409 namelen
-= STRING_SIZE("refs/heads/");
7410 name
+= STRING_SIZE("refs/heads/");
7411 if (!strncmp(opt_head
, name
, namelen
))
7414 } else if (!strcmp(name
, "HEAD")) {
7417 namelen
= strlen(opt_head
);
7422 /* If we are reloading or it's an annotated tag, replace the
7423 * previous SHA1 with the resolved commit id; relies on the fact
7424 * git-ls-remote lists the commit id of an annotated tag right
7425 * before the commit id it points to. */
7426 while (from
<= to
) {
7427 size_t pos
= (to
+ from
) / 2;
7428 int cmp
= strcmp(name
, refs
[pos
]->name
);
7442 if (!realloc_refs(&refs
, refs_size
, 1))
7444 ref
= calloc(1, sizeof(*ref
) + namelen
);
7447 memmove(refs
+ from
+ 1, refs
+ from
,
7448 (refs_size
- from
) * sizeof(*refs
));
7450 strncpy(ref
->name
, name
, namelen
);
7457 ref
->remote
= remote
;
7458 ref
->tracked
= tracked
;
7459 string_copy_rev(ref
->id
, id
);
7469 const char *head_argv
[] = {
7470 "git", "symbolic-ref", "HEAD", NULL
7472 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7473 "git", "ls-remote", opt_git_dir
, NULL
7475 static bool init
= FALSE
;
7479 if (!argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE"))
7480 die("TIG_LS_REMOTE contains too many arguments");
7487 if (io_run_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7488 !prefixcmp(opt_head
, "refs/heads/")) {
7489 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7491 memmove(opt_head
, offset
, strlen(offset
) + 1);
7495 for (i
= 0; i
< refs_size
; i
++)
7498 if (io_run_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7501 /* Update the ref lists to reflect changes. */
7502 for (i
= 0; i
< ref_lists_size
; i
++) {
7503 struct ref_list
*list
= ref_lists
[i
];
7506 for (old
= new = 0; old
< list
->size
; old
++)
7507 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7508 list
->refs
[new++] = list
->refs
[old
];
7516 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7518 if (!strcmp(name
, ".remote")) {
7519 string_ncopy(opt_remote
, value
, valuelen
);
7521 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7522 size_t from
= strlen(opt_remote
);
7524 if (!prefixcmp(value
, "refs/heads/"))
7525 value
+= STRING_SIZE("refs/heads/");
7527 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7533 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7535 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7536 int argc
= 1 + (cmd
== option_set_command
);
7539 if (!argv_from_string(argv
, &argc
, value
))
7540 config_msg
= "Too many option arguments";
7542 error
= cmd(argc
, argv
);
7545 warn("Option 'tig.%s': %s", name
, config_msg
);
7549 set_environment_variable(const char *name
, const char *value
)
7551 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7552 char *env
= malloc(len
);
7555 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7563 set_work_tree(const char *value
)
7565 char cwd
[SIZEOF_STR
];
7567 if (!getcwd(cwd
, sizeof(cwd
)))
7568 die("Failed to get cwd path: %s", strerror(errno
));
7569 if (chdir(opt_git_dir
) < 0)
7570 die("Failed to chdir(%s): %s", strerror(errno
));
7571 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7572 die("Failed to get git path: %s", strerror(errno
));
7574 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7575 if (chdir(value
) < 0)
7576 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7577 if (!getcwd(cwd
, sizeof(cwd
)))
7578 die("Failed to get cwd path: %s", strerror(errno
));
7579 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7580 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7581 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7582 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7583 opt_is_inside_work_tree
= TRUE
;
7587 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7589 if (!strcmp(name
, "i18n.commitencoding"))
7590 string_ncopy(opt_encoding
, value
, valuelen
);
7592 else if (!strcmp(name
, "core.editor"))
7593 string_ncopy(opt_editor
, value
, valuelen
);
7595 else if (!strcmp(name
, "core.worktree"))
7596 set_work_tree(value
);
7598 else if (!prefixcmp(name
, "tig.color."))
7599 set_repo_config_option(name
+ 10, value
, option_color_command
);
7601 else if (!prefixcmp(name
, "tig.bind."))
7602 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7604 else if (!prefixcmp(name
, "tig."))
7605 set_repo_config_option(name
+ 4, value
, option_set_command
);
7607 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7608 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7609 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7615 load_git_config(void)
7617 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7619 return io_run_load(config_list_argv
, "=", read_repo_config_option
);
7623 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7625 if (!opt_git_dir
[0]) {
7626 string_ncopy(opt_git_dir
, name
, namelen
);
7628 } else if (opt_is_inside_work_tree
== -1) {
7629 /* This can be 3 different values depending on the
7630 * version of git being used. If git-rev-parse does not
7631 * understand --is-inside-work-tree it will simply echo
7632 * the option else either "true" or "false" is printed.
7633 * Default to true for the unknown case. */
7634 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7636 } else if (*name
== '.') {
7637 string_ncopy(opt_cdup
, name
, namelen
);
7640 string_ncopy(opt_prefix
, name
, namelen
);
7647 load_repo_info(void)
7649 const char *rev_parse_argv
[] = {
7650 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7651 "--show-cdup", "--show-prefix", NULL
7654 return io_run_load(rev_parse_argv
, "=", read_repo_info
);
7662 static const char usage
[] =
7663 "tig " TIG_VERSION
" (" __DATE__
")\n"
7665 "Usage: tig [options] [revs] [--] [paths]\n"
7666 " or: tig show [options] [revs] [--] [paths]\n"
7667 " or: tig blame [rev] path\n"
7669 " or: tig < [git command output]\n"
7672 " -v, --version Show version and exit\n"
7673 " -h, --help Show help message and exit";
7675 static void __NORETURN
7678 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7684 static void __NORETURN
7685 die(const char *err
, ...)
7691 va_start(args
, err
);
7692 fputs("tig: ", stderr
);
7693 vfprintf(stderr
, err
, args
);
7694 fputs("\n", stderr
);
7701 warn(const char *msg
, ...)
7705 va_start(args
, msg
);
7706 fputs("tig warning: ", stderr
);
7707 vfprintf(stderr
, msg
, args
);
7708 fputs("\n", stderr
);
7712 static const char ***filter_args
;
7715 read_filter_args(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7717 return argv_append(filter_args
, name
) ? OK
: ERR
;
7721 filter_rev_parse(const char ***args
, const char *arg1
, const char *arg2
, const char *argv
[])
7723 const char *rev_parse_argv
[SIZEOF_ARG
] = { "git", "rev-parse", arg1
, arg2
};
7724 const char **all_argv
= NULL
;
7727 if (!argv_append_array(&all_argv
, rev_parse_argv
) ||
7728 !argv_append_array(&all_argv
, argv
) ||
7729 !io_run_load(all_argv
, "\n", read_filter_args
) == ERR
)
7730 die("Failed to split arguments");
7731 argv_free(all_argv
);
7736 filter_options(const char *argv
[])
7738 filter_rev_parse(&opt_file_args
, "--no-revs", "--no-flags", argv
);
7739 filter_rev_parse(&opt_diff_args
, "--no-revs", "--flags", argv
);
7740 filter_rev_parse(&opt_rev_args
, "--symbolic", "--revs-only", argv
);
7744 parse_options(int argc
, const char *argv
[])
7746 enum request request
= REQ_VIEW_MAIN
;
7747 const char *subcommand
;
7748 bool seen_dashdash
= FALSE
;
7749 const char **filter_argv
= NULL
;
7752 if (!isatty(STDIN_FILENO
)) {
7753 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7754 return REQ_VIEW_PAGER
;
7758 return REQ_VIEW_MAIN
;
7760 subcommand
= argv
[1];
7761 if (!strcmp(subcommand
, "status")) {
7763 warn("ignoring arguments after `%s'", subcommand
);
7764 return REQ_VIEW_STATUS
;
7766 } else if (!strcmp(subcommand
, "blame")) {
7767 if (argc
<= 2 || argc
> 4)
7768 die("invalid number of options to blame\n\n%s", usage
);
7772 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7776 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7777 return REQ_VIEW_BLAME
;
7779 } else if (!strcmp(subcommand
, "show")) {
7780 request
= REQ_VIEW_DIFF
;
7786 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7787 const char *opt
= argv
[i
];
7789 if (seen_dashdash
) {
7790 argv_append(&opt_file_args
, opt
);
7793 } else if (!strcmp(opt
, "--")) {
7794 seen_dashdash
= TRUE
;
7797 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7798 printf("tig version %s\n", TIG_VERSION
);
7801 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7802 printf("%s\n", usage
);
7805 } else if (!strcmp(opt
, "--all")) {
7806 argv_append(&opt_rev_args
, opt
);
7810 if (!argv_append(&filter_argv
, opt
))
7811 die("command too long");
7815 filter_options(filter_argv
);
7821 main(int argc
, const char *argv
[])
7823 const char *codeset
= "UTF-8";
7824 enum request request
= parse_options(argc
, argv
);
7828 signal(SIGINT
, quit
);
7829 signal(SIGPIPE
, SIG_IGN
);
7831 if (setlocale(LC_ALL
, "")) {
7832 codeset
= nl_langinfo(CODESET
);
7835 if (load_repo_info() == ERR
)
7836 die("Failed to load repo info.");
7838 if (load_options() == ERR
)
7839 die("Failed to load user config.");
7841 if (load_git_config() == ERR
)
7842 die("Failed to load repo config.");
7844 /* Require a git repository unless when running in pager mode. */
7845 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7846 die("Not a git repository");
7848 if (*opt_encoding
&& strcmp(codeset
, "UTF-8")) {
7849 opt_iconv_in
= iconv_open("UTF-8", opt_encoding
);
7850 if (opt_iconv_in
== ICONV_NONE
)
7851 die("Failed to initialize character set conversion");
7854 if (codeset
&& strcmp(codeset
, "UTF-8")) {
7855 opt_iconv_out
= iconv_open(codeset
, "UTF-8");
7856 if (opt_iconv_out
== ICONV_NONE
)
7857 die("Failed to initialize character set conversion");
7860 if (load_refs() == ERR
)
7861 die("Failed to load refs.");
7863 foreach_view (view
, i
) {
7864 if (getenv(view
->cmd_env
))
7865 warn("Use of the %s environment variable is deprecated,"
7866 " use options or TIG_DIFF_ARGS instead",
7868 if (!argv_from_env(view
->ops
->argv
, view
->cmd_env
))
7869 die("Too many arguments in the `%s` environment variable",
7875 while (view_driver(display
[current_view
], request
)) {
7876 int key
= get_input(0);
7878 view
= display
[current_view
];
7879 request
= get_keybinding(view
->keymap
, key
);
7881 /* Some low-level request handling. This keeps access to
7882 * status_win restricted. */
7885 report("Unknown key, press %s for help",
7886 get_key(view
->keymap
, REQ_VIEW_HELP
));
7890 char *cmd
= read_prompt(":");
7892 if (cmd
&& isdigit(*cmd
)) {
7893 int lineno
= view
->lineno
+ 1;
7895 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7896 select_view_line(view
, lineno
- 1);
7899 report("Unable to parse '%s' as a line number", cmd
);
7903 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7904 const char *argv
[SIZEOF_ARG
] = { "git" };
7907 /* When running random commands, initially show the
7908 * command in the title. However, it maybe later be
7909 * overwritten if a commit line is selected. */
7910 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7912 if (!argv_from_string(argv
, &argc
, cmd
)) {
7913 report("Too many arguments");
7914 } else if (!prepare_update(next
, argv
, NULL
)) {
7915 report("Failed to format command");
7917 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7925 case REQ_SEARCH_BACK
:
7927 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7928 char *search
= read_prompt(prompt
);
7931 string_ncopy(opt_search
, search
, strlen(search
));
7932 else if (*opt_search
)
7933 request
= request
== REQ_SEARCH
?