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);
3985 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
3987 char text
[SIZEOF_STR
];
3989 if (opt_line_number
&& draw_lineno(view
, lineno
))
3992 string_expand(text
, sizeof(text
), line
->data
, opt_tab_size
);
3993 draw_text(view
, line
->type
, text
, TRUE
);
3998 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
4000 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
4001 char ref
[SIZEOF_STR
];
4003 if (!io_run_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
4006 /* This is the only fatal call, since it can "corrupt" the buffer. */
4007 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
4014 add_pager_refs(struct view
*view
, struct line
*line
)
4016 char buf
[SIZEOF_STR
];
4017 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
4018 struct ref_list
*list
;
4019 size_t bufpos
= 0, i
;
4020 const char *sep
= "Refs: ";
4021 bool is_tag
= FALSE
;
4023 assert(line
->type
== LINE_COMMIT
);
4025 list
= get_ref_list(commit_id
);
4027 if (view
->type
== VIEW_DIFF
)
4028 goto try_add_describe_ref
;
4032 for (i
= 0; i
< list
->size
; i
++) {
4033 struct ref
*ref
= list
->refs
[i
];
4034 const char *fmt
= ref
->tag
? "%s[%s]" :
4035 ref
->remote
? "%s<%s>" : "%s%s";
4037 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
4044 if (!is_tag
&& view
->type
== VIEW_DIFF
) {
4045 try_add_describe_ref
:
4046 /* Add <tag>-g<commit_id> "fake" reference. */
4047 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
4054 add_line_text(view
, buf
, LINE_PP_REFS
);
4058 pager_read(struct view
*view
, char *data
)
4065 line
= add_line_text(view
, data
, get_line_type(data
));
4069 if (line
->type
== LINE_COMMIT
&&
4070 (view
->type
== VIEW_DIFF
||
4071 view
->type
== VIEW_LOG
))
4072 add_pager_refs(view
, line
);
4078 pager_request(struct view
*view
, enum request request
, struct line
*line
)
4082 if (request
!= REQ_ENTER
)
4085 if (line
->type
== LINE_COMMIT
&&
4086 (view
->type
== VIEW_LOG
||
4087 view
->type
== VIEW_PAGER
)) {
4088 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
4092 /* Always scroll the view even if it was split. That way
4093 * you can use Enter to scroll through the log view and
4094 * split open each commit diff. */
4095 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
4097 /* FIXME: A minor workaround. Scrolling the view will call report("")
4098 * but if we are scrolling a non-current view this won't properly
4099 * update the view title. */
4101 update_view_title(view
);
4107 pager_grep(struct view
*view
, struct line
*line
)
4109 const char *text
[] = { line
->data
, NULL
};
4111 return grep_text(view
, text
);
4115 pager_select(struct view
*view
, struct line
*line
)
4117 if (line
->type
== LINE_COMMIT
) {
4118 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
4120 if (view
->type
!= VIEW_PAGER
)
4121 string_copy_rev(view
->ref
, text
);
4122 string_copy_rev(ref_commit
, text
);
4126 static struct view_ops pager_ops
= {
4137 static const char *log_argv
[SIZEOF_ARG
] = {
4138 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4142 log_request(struct view
*view
, enum request request
, struct line
*line
)
4147 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
4150 return pager_request(view
, request
, line
);
4154 static struct view_ops log_ops
= {
4165 static const char *diff_argv
[SIZEOF_ARG
] = {
4166 "git", "show", "--pretty=fuller", "--no-color", "--root",
4167 "--patch-with-stat", "--find-copies-harder", "-C",
4168 "%(diff-args)", "%(commit)", "--", "%(file-args)", NULL
4171 static struct view_ops diff_ops
= {
4186 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
4189 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
4193 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
4194 help_keymap_hidden
[keymap
] ? '+' : '-',
4195 enum_name(keymap_table
[keymap
]));
4197 line
->other
= keymap
;
4199 return help_keymap_hidden
[keymap
];
4203 help_open_keymap(struct view
*view
, enum keymap keymap
)
4205 const char *group
= NULL
;
4206 char buf
[SIZEOF_STR
];
4208 bool add_title
= TRUE
;
4211 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
4212 const char *key
= NULL
;
4214 if (req_info
[i
].request
== REQ_NONE
)
4217 if (!req_info
[i
].request
) {
4218 group
= req_info
[i
].help
;
4222 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4226 if (add_title
&& help_open_keymap_title(view
, keymap
))
4231 add_line_text(view
, group
, LINE_HELP_GROUP
);
4235 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4236 enum_name(req_info
[i
]), req_info
[i
].help
);
4239 group
= "External commands:";
4241 for (i
= 0; i
< run_requests
; i
++) {
4242 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4246 if (!req
|| req
->keymap
!= keymap
)
4249 key
= get_key_name(req
->key
);
4251 key
= "(no key defined)";
4253 if (add_title
&& help_open_keymap_title(view
, keymap
))
4256 add_line_text(view
, group
, LINE_HELP_GROUP
);
4260 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4261 if (!string_format_from(buf
, &bufpos
, "%s%s",
4262 argc
? " " : "", req
->argv
[argc
]))
4265 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4270 help_open(struct view
*view
)
4275 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4276 add_line_text(view
, "", LINE_DEFAULT
);
4278 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4279 help_open_keymap(view
, keymap
);
4285 help_request(struct view
*view
, enum request request
, struct line
*line
)
4289 if (line
->type
== LINE_HELP_KEYMAP
) {
4290 help_keymap_hidden
[line
->other
] =
4291 !help_keymap_hidden
[line
->other
];
4292 view
->p_restore
= TRUE
;
4293 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4298 return pager_request(view
, request
, line
);
4302 static struct view_ops help_ops
= {
4318 struct tree_stack_entry
{
4319 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4320 unsigned long lineno
; /* Line number to restore */
4321 char *name
; /* Position of name in opt_path */
4324 /* The top of the path stack. */
4325 static struct tree_stack_entry
*tree_stack
= NULL
;
4326 unsigned long tree_lineno
= 0;
4329 pop_tree_stack_entry(void)
4331 struct tree_stack_entry
*entry
= tree_stack
;
4333 tree_lineno
= entry
->lineno
;
4335 tree_stack
= entry
->prev
;
4340 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4342 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4343 size_t pathlen
= strlen(opt_path
);
4348 entry
->prev
= tree_stack
;
4349 entry
->name
= opt_path
+ pathlen
;
4352 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4353 pop_tree_stack_entry();
4357 /* Move the current line to the first tree entry. */
4359 entry
->lineno
= lineno
;
4362 /* Parse output from git-ls-tree(1):
4364 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4367 #define SIZEOF_TREE_ATTR \
4368 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4370 #define SIZEOF_TREE_MODE \
4371 STRING_SIZE("100644 ")
4373 #define TREE_ID_OFFSET \
4374 STRING_SIZE("100644 blob ")
4377 char id
[SIZEOF_REV
];
4379 struct time time
; /* Date from the author ident. */
4380 const char *author
; /* Author of the commit. */
4385 tree_path(const struct line
*line
)
4387 return ((struct tree_entry
*) line
->data
)->name
;
4391 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4393 if (line1
->type
!= line2
->type
)
4394 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4395 return strcmp(tree_path(line1
), tree_path(line2
));
4398 static const enum sort_field tree_sort_fields
[] = {
4399 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4401 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4404 tree_compare(const void *l1
, const void *l2
)
4406 const struct line
*line1
= (const struct line
*) l1
;
4407 const struct line
*line2
= (const struct line
*) l2
;
4408 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4409 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4411 if (line1
->type
== LINE_TREE_HEAD
)
4413 if (line2
->type
== LINE_TREE_HEAD
)
4416 switch (get_sort_field(tree_sort_state
)) {
4418 return sort_order(tree_sort_state
, timecmp(&entry1
->time
, &entry2
->time
));
4420 case ORDERBY_AUTHOR
:
4421 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4425 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4430 static struct line
*
4431 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4432 const char *mode
, const char *id
)
4434 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4435 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4437 if (!entry
|| !line
) {
4442 strncpy(entry
->name
, path
, strlen(path
));
4444 entry
->mode
= strtoul(mode
, NULL
, 8);
4446 string_copy_rev(entry
->id
, id
);
4452 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4454 static const char *author_name
;
4455 static struct time author_time
;
4457 if (!text
&& *read_date
) {
4462 char *path
= *opt_path
? opt_path
: ".";
4463 /* Find next entry to process */
4464 const char *log_file
[] = {
4465 "git", "log", "--no-color", "--pretty=raw",
4466 "--cc", "--raw", view
->id
, "--", path
, NULL
4470 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4471 report("Tree is empty");
4475 if (!start_update(view
, log_file
, opt_cdup
)) {
4476 report("Failed to load tree data");
4483 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4484 parse_author_line(text
+ STRING_SIZE("author "),
4485 &author_name
, &author_time
);
4487 } else if (*text
== ':') {
4489 size_t annotated
= 1;
4492 pos
= strchr(text
, '\t');
4496 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4497 text
+= strlen(opt_path
);
4498 pos
= strchr(text
, '/');
4502 for (i
= 1; i
< view
->lines
; i
++) {
4503 struct line
*line
= &view
->line
[i
];
4504 struct tree_entry
*entry
= line
->data
;
4506 annotated
+= !!entry
->author
;
4507 if (entry
->author
|| strcmp(entry
->name
, text
))
4510 entry
->author
= author_name
;
4511 entry
->time
= author_time
;
4516 if (annotated
== view
->lines
)
4517 io_kill(view
->pipe
);
4523 tree_read(struct view
*view
, char *text
)
4525 static bool read_date
= FALSE
;
4526 struct tree_entry
*data
;
4527 struct line
*entry
, *line
;
4528 enum line_type type
;
4529 size_t textlen
= text
? strlen(text
) : 0;
4530 char *path
= text
+ SIZEOF_TREE_ATTR
;
4532 if (read_date
|| !text
)
4533 return tree_read_date(view
, text
, &read_date
);
4535 if (textlen
<= SIZEOF_TREE_ATTR
)
4537 if (view
->lines
== 0 &&
4538 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4541 /* Strip the path part ... */
4543 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4544 size_t striplen
= strlen(opt_path
);
4546 if (pathlen
> striplen
)
4547 memmove(path
, path
+ striplen
,
4548 pathlen
- striplen
+ 1);
4550 /* Insert "link" to parent directory. */
4551 if (view
->lines
== 1 &&
4552 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4556 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4557 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4562 /* Skip "Directory ..." and ".." line. */
4563 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4564 if (tree_compare_entry(line
, entry
) <= 0)
4567 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4571 for (; line
<= entry
; line
++)
4572 line
->dirty
= line
->cleareol
= 1;
4576 if (tree_lineno
> view
->lineno
) {
4577 view
->lineno
= tree_lineno
;
4585 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4587 struct tree_entry
*entry
= line
->data
;
4589 if (line
->type
== LINE_TREE_HEAD
) {
4590 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4593 if (draw_mode(view
, entry
->mode
))
4596 if (opt_author
&& draw_author(view
, entry
->author
))
4599 if (opt_date
&& draw_date(view
, &entry
->time
))
4602 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4608 open_blob_editor(const char *id
)
4610 const char *blob_argv
[] = { "git", "cat-file", "blob", id
, NULL
};
4611 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4612 int fd
= mkstemp(file
);
4615 report("Failed to create temporary file");
4616 else if (!io_run_append(blob_argv
, fd
))
4617 report("Failed to save blob data to file");
4625 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4627 enum open_flags flags
;
4628 struct tree_entry
*entry
= line
->data
;
4631 case REQ_VIEW_BLAME
:
4632 if (line
->type
!= LINE_TREE_FILE
) {
4633 report("Blame only supported for files");
4637 string_copy(opt_ref
, view
->vid
);
4641 if (line
->type
!= LINE_TREE_FILE
) {
4642 report("Edit only supported for files");
4643 } else if (!is_head_commit(view
->vid
)) {
4644 open_blob_editor(entry
->id
);
4646 open_editor(opt_file
);
4650 case REQ_TOGGLE_SORT_FIELD
:
4651 case REQ_TOGGLE_SORT_ORDER
:
4652 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4657 /* quit view if at top of tree */
4658 return REQ_VIEW_CLOSE
;
4661 line
= &view
->line
[1];
4671 /* Cleanup the stack if the tree view is at a different tree. */
4672 while (!*opt_path
&& tree_stack
)
4673 pop_tree_stack_entry();
4675 switch (line
->type
) {
4677 /* Depending on whether it is a subdirectory or parent link
4678 * mangle the path buffer. */
4679 if (line
== &view
->line
[1] && *opt_path
) {
4680 pop_tree_stack_entry();
4683 const char *basename
= tree_path(line
);
4685 push_tree_stack_entry(basename
, view
->lineno
);
4688 /* Trees and subtrees share the same ID, so they are not not
4689 * unique like blobs. */
4690 flags
= OPEN_RELOAD
;
4691 request
= REQ_VIEW_TREE
;
4694 case LINE_TREE_FILE
:
4695 flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
4696 request
= REQ_VIEW_BLOB
;
4703 open_view(view
, request
, flags
);
4704 if (request
== REQ_VIEW_TREE
)
4705 view
->lineno
= tree_lineno
;
4711 tree_grep(struct view
*view
, struct line
*line
)
4713 struct tree_entry
*entry
= line
->data
;
4714 const char *text
[] = {
4716 opt_author
? entry
->author
: "",
4717 mkdate(&entry
->time
, opt_date
),
4721 return grep_text(view
, text
);
4725 tree_select(struct view
*view
, struct line
*line
)
4727 struct tree_entry
*entry
= line
->data
;
4729 if (line
->type
== LINE_TREE_FILE
) {
4730 string_copy_rev(ref_blob
, entry
->id
);
4731 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4733 } else if (line
->type
!= LINE_TREE_DIR
) {
4737 string_copy_rev(view
->ref
, entry
->id
);
4741 tree_prepare(struct view
*view
)
4743 if (view
->lines
== 0 && opt_prefix
[0]) {
4744 char *pos
= opt_prefix
;
4746 while (pos
&& *pos
) {
4747 char *end
= strchr(pos
, '/');
4751 push_tree_stack_entry(pos
, 0);
4759 } else if (strcmp(view
->vid
, view
->id
)) {
4763 return prepare_io(view
, opt_cdup
, view
->ops
->argv
, TRUE
);
4766 static const char *tree_argv
[SIZEOF_ARG
] = {
4767 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4770 static struct view_ops tree_ops
= {
4783 blob_read(struct view
*view
, char *line
)
4787 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4791 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4795 open_blob_editor(view
->vid
);
4798 return pager_request(view
, request
, line
);
4802 static const char *blob_argv
[SIZEOF_ARG
] = {
4803 "git", "cat-file", "blob", "%(blob)", NULL
4806 static struct view_ops blob_ops
= {
4820 * Loading the blame view is a two phase job:
4822 * 1. File content is read either using opt_file from the
4823 * filesystem or using git-cat-file.
4824 * 2. Then blame information is incrementally added by
4825 * reading output from git-blame.
4828 struct blame_commit
{
4829 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4830 char title
[128]; /* First line of the commit message. */
4831 const char *author
; /* Author of the commit. */
4832 struct time time
; /* Date from the author ident. */
4833 char filename
[128]; /* Name of file. */
4834 char parent_id
[SIZEOF_REV
]; /* Parent/previous SHA1 ID. */
4835 char parent_filename
[128]; /* Parent/previous name of file. */
4839 struct blame_commit
*commit
;
4840 unsigned long lineno
;
4845 blame_open(struct view
*view
)
4847 char path
[SIZEOF_STR
];
4850 if (!view
->prev
&& *opt_prefix
) {
4851 string_copy(path
, opt_file
);
4852 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4856 if (*opt_ref
|| !io_open(&view
->io
, "%s%s", opt_cdup
, opt_file
)) {
4857 const char *blame_cat_file_argv
[] = {
4858 "git", "cat-file", "blob", path
, NULL
4861 if (!string_format(path
, "%s:%s", opt_ref
, opt_file
) ||
4862 !start_update(view
, blame_cat_file_argv
, opt_cdup
))
4866 /* First pass: remove multiple references to the same commit. */
4867 for (i
= 0; i
< view
->lines
; i
++) {
4868 struct blame
*blame
= view
->line
[i
].data
;
4870 if (blame
->commit
&& blame
->commit
->id
[0])
4871 blame
->commit
->id
[0] = 0;
4873 blame
->commit
= NULL
;
4876 /* Second pass: free existing references. */
4877 for (i
= 0; i
< view
->lines
; i
++) {
4878 struct blame
*blame
= view
->line
[i
].data
;
4881 free(blame
->commit
);
4884 setup_update(view
, opt_file
);
4885 string_format(view
->ref
, "%s ...", opt_file
);
4890 static struct blame_commit
*
4891 get_blame_commit(struct view
*view
, const char *id
)
4895 for (i
= 0; i
< view
->lines
; i
++) {
4896 struct blame
*blame
= view
->line
[i
].data
;
4901 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4902 return blame
->commit
;
4906 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4909 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4915 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4917 const char *pos
= *posref
;
4920 pos
= strchr(pos
+ 1, ' ');
4921 if (!pos
|| !isdigit(pos
[1]))
4923 *number
= atoi(pos
+ 1);
4924 if (*number
< min
|| *number
> max
)
4931 static struct blame_commit
*
4932 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4934 struct blame_commit
*commit
;
4935 struct blame
*blame
;
4936 const char *pos
= text
+ SIZEOF_REV
- 2;
4937 size_t orig_lineno
= 0;
4941 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4944 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4945 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4946 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4949 commit
= get_blame_commit(view
, text
);
4955 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4958 blame
->commit
= commit
;
4959 blame
->lineno
= orig_lineno
+ group
- 1;
4967 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4970 const char *blame_argv
[] = {
4971 "git", "blame", "--incremental",
4972 *opt_ref
? opt_ref
: "--incremental", "--", opt_file
, NULL
4975 if (view
->lines
== 0 && !view
->prev
)
4976 die("No blame exist for %s", view
->vid
);
4978 if (view
->lines
== 0 || !start_update(view
, blame_argv
, opt_cdup
)) {
4979 report("Failed to load blame data");
4987 size_t linelen
= strlen(line
);
4988 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
4993 blame
->commit
= NULL
;
4994 strncpy(blame
->text
, line
, linelen
);
4995 blame
->text
[linelen
] = 0;
4996 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
5001 match_blame_header(const char *name
, char **line
)
5003 size_t namelen
= strlen(name
);
5004 bool matched
= !strncmp(name
, *line
, namelen
);
5013 blame_read(struct view
*view
, char *line
)
5015 static struct blame_commit
*commit
= NULL
;
5016 static int blamed
= 0;
5017 static bool read_file
= TRUE
;
5020 return blame_read_file(view
, line
, &read_file
);
5027 string_format(view
->ref
, "%s", view
->vid
);
5028 if (view_is_displayed(view
)) {
5029 update_view_title(view
);
5030 redraw_view_from(view
, 0);
5036 commit
= parse_blame_commit(view
, line
, &blamed
);
5037 string_format(view
->ref
, "%s %2d%%", view
->vid
,
5038 view
->lines
? blamed
* 100 / view
->lines
: 0);
5040 } else if (match_blame_header("author ", &line
)) {
5041 commit
->author
= get_author(line
);
5043 } else if (match_blame_header("author-time ", &line
)) {
5044 parse_timesec(&commit
->time
, line
);
5046 } else if (match_blame_header("author-tz ", &line
)) {
5047 parse_timezone(&commit
->time
, line
);
5049 } else if (match_blame_header("summary ", &line
)) {
5050 string_ncopy(commit
->title
, line
, strlen(line
));
5052 } else if (match_blame_header("previous ", &line
)) {
5053 if (strlen(line
) <= SIZEOF_REV
)
5055 string_copy_rev(commit
->parent_id
, line
);
5057 string_ncopy(commit
->parent_filename
, line
, strlen(line
));
5059 } else if (match_blame_header("filename ", &line
)) {
5060 string_ncopy(commit
->filename
, line
, strlen(line
));
5068 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5070 struct blame
*blame
= line
->data
;
5071 struct time
*time
= NULL
;
5072 const char *id
= NULL
, *author
= NULL
;
5073 char text
[SIZEOF_STR
];
5075 if (blame
->commit
&& *blame
->commit
->filename
) {
5076 id
= blame
->commit
->id
;
5077 author
= blame
->commit
->author
;
5078 time
= &blame
->commit
->time
;
5081 if (opt_date
&& draw_date(view
, time
))
5084 if (opt_author
&& draw_author(view
, author
))
5087 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
5090 if (draw_lineno(view
, lineno
))
5093 string_expand(text
, sizeof(text
), blame
->text
, opt_tab_size
);
5094 draw_text(view
, LINE_DEFAULT
, text
, TRUE
);
5099 check_blame_commit(struct blame
*blame
, bool check_null_id
)
5102 report("Commit data not loaded yet");
5103 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5104 report("No commit exist for the selected line");
5111 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
5113 const char *diff_tree_argv
[] = {
5114 "git", "diff-tree", "-U0", blame
->commit
->id
,
5115 "--", blame
->commit
->filename
, NULL
5118 int parent_lineno
= -1;
5119 int blamed_lineno
= -1;
5122 if (!io_run(&io
, IO_RD
, NULL
, diff_tree_argv
))
5125 while ((line
= io_get(&io
, '\n', TRUE
))) {
5127 char *pos
= strchr(line
, '+');
5129 parent_lineno
= atoi(line
+ 4);
5131 blamed_lineno
= atoi(pos
+ 1);
5133 } else if (*line
== '+' && parent_lineno
!= -1) {
5134 if (blame
->lineno
== blamed_lineno
- 1 &&
5135 !strcmp(blame
->text
, line
+ 1)) {
5136 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
5147 blame_request(struct view
*view
, enum request request
, struct line
*line
)
5149 enum open_flags flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
5150 struct blame
*blame
= line
->data
;
5153 case REQ_VIEW_BLAME
:
5154 if (check_blame_commit(blame
, TRUE
)) {
5155 string_copy(opt_ref
, blame
->commit
->id
);
5156 string_copy(opt_file
, blame
->commit
->filename
);
5158 view
->lineno
= blame
->lineno
;
5159 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5164 if (!check_blame_commit(blame
, TRUE
))
5166 if (!*blame
->commit
->parent_id
) {
5167 report("The selected commit has no parents");
5169 string_copy_rev(opt_ref
, blame
->commit
->parent_id
);
5170 string_copy(opt_file
, blame
->commit
->parent_filename
);
5171 setup_blame_parent_line(view
, blame
);
5172 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5177 if (!check_blame_commit(blame
, FALSE
))
5180 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
5181 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
5184 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
5185 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
5186 const char *diff_index_argv
[] = {
5187 "git", "diff-index", "--root", "--patch-with-stat",
5188 "-C", "-M", "HEAD", "--", view
->vid
, NULL
5191 if (!*blame
->commit
->parent_id
) {
5192 diff_index_argv
[1] = "diff";
5193 diff_index_argv
[2] = "--no-color";
5194 diff_index_argv
[6] = "--";
5195 diff_index_argv
[7] = "/dev/null";
5198 if (!prepare_update(diff
, diff_index_argv
, NULL
)) {
5199 report("Failed to allocate diff command");
5202 flags
|= OPEN_PREPARED
;
5205 open_view(view
, REQ_VIEW_DIFF
, flags
);
5206 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5207 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
5218 blame_grep(struct view
*view
, struct line
*line
)
5220 struct blame
*blame
= line
->data
;
5221 struct blame_commit
*commit
= blame
->commit
;
5222 const char *text
[] = {
5224 commit
? commit
->title
: "",
5225 commit
? commit
->id
: "",
5226 commit
&& opt_author
? commit
->author
: "",
5227 commit
? mkdate(&commit
->time
, opt_date
) : "",
5231 return grep_text(view
, text
);
5235 blame_select(struct view
*view
, struct line
*line
)
5237 struct blame
*blame
= line
->data
;
5238 struct blame_commit
*commit
= blame
->commit
;
5243 if (!strcmp(commit
->id
, NULL_ID
))
5244 string_ncopy(ref_commit
, "HEAD", 4);
5246 string_copy_rev(ref_commit
, commit
->id
);
5249 static struct view_ops blame_ops
= {
5265 const char *author
; /* Author of the last commit. */
5266 struct time time
; /* Date of the last activity. */
5267 const struct ref
*ref
; /* Name and commit ID information. */
5270 static const struct ref branch_all
;
5272 static const enum sort_field branch_sort_fields
[] = {
5273 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5275 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5278 branch_compare(const void *l1
, const void *l2
)
5280 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5281 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5283 switch (get_sort_field(branch_sort_state
)) {
5285 return sort_order(branch_sort_state
, timecmp(&branch1
->time
, &branch2
->time
));
5287 case ORDERBY_AUTHOR
:
5288 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5292 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5297 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5299 struct branch
*branch
= line
->data
;
5300 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5302 if (opt_date
&& draw_date(view
, &branch
->time
))
5305 if (opt_author
&& draw_author(view
, branch
->author
))
5308 draw_text(view
, type
, branch
->ref
== &branch_all
? "All branches" : branch
->ref
->name
, TRUE
);
5313 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5315 struct branch
*branch
= line
->data
;
5320 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5323 case REQ_TOGGLE_SORT_FIELD
:
5324 case REQ_TOGGLE_SORT_ORDER
:
5325 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5330 const struct ref
*ref
= branch
->ref
;
5331 const char *all_branches_argv
[] = {
5332 "git", "log", "--no-color", "--pretty=raw", "--parents",
5334 ref
== &branch_all
? "--all" : ref
->name
, NULL
5336 struct view
*main_view
= VIEW(REQ_VIEW_MAIN
);
5338 if (!prepare_update(main_view
, all_branches_argv
, NULL
))
5339 report("Failed to load view of all branches");
5341 open_view(view
, REQ_VIEW_MAIN
, OPEN_PREPARED
| OPEN_SPLIT
);
5350 branch_read(struct view
*view
, char *line
)
5352 static char id
[SIZEOF_REV
];
5353 struct branch
*reference
;
5359 switch (get_line_type(line
)) {
5361 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5365 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5366 struct branch
*branch
= view
->line
[i
].data
;
5368 if (strcmp(branch
->ref
->id
, id
))
5371 view
->line
[i
].dirty
= TRUE
;
5373 branch
->author
= reference
->author
;
5374 branch
->time
= reference
->time
;
5378 parse_author_line(line
+ STRING_SIZE("author "),
5379 &branch
->author
, &branch
->time
);
5391 branch_open_visitor(void *data
, const struct ref
*ref
)
5393 struct view
*view
= data
;
5394 struct branch
*branch
;
5396 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5399 branch
= calloc(1, sizeof(*branch
));
5404 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5408 branch_open(struct view
*view
)
5410 const char *branch_log
[] = {
5411 "git", "log", "--no-color", "--pretty=raw",
5412 "--simplify-by-decoration", "--all", NULL
5415 if (!start_update(view
, branch_log
, NULL
)) {
5416 report("Failed to load branch data");
5420 setup_update(view
, view
->id
);
5421 branch_open_visitor(view
, &branch_all
);
5422 foreach_ref(branch_open_visitor
, view
);
5423 view
->p_restore
= TRUE
;
5429 branch_grep(struct view
*view
, struct line
*line
)
5431 struct branch
*branch
= line
->data
;
5432 const char *text
[] = {
5438 return grep_text(view
, text
);
5442 branch_select(struct view
*view
, struct line
*line
)
5444 struct branch
*branch
= line
->data
;
5446 string_copy_rev(view
->ref
, branch
->ref
->id
);
5447 string_copy_rev(ref_commit
, branch
->ref
->id
);
5448 string_copy_rev(ref_head
, branch
->ref
->id
);
5449 string_copy_rev(ref_branch
, branch
->ref
->name
);
5452 static struct view_ops branch_ops
= {
5471 char rev
[SIZEOF_REV
];
5472 char name
[SIZEOF_STR
];
5476 char rev
[SIZEOF_REV
];
5477 char name
[SIZEOF_STR
];
5481 static char status_onbranch
[SIZEOF_STR
];
5482 static struct status stage_status
;
5483 static enum line_type stage_line_type
;
5484 static size_t stage_chunks
;
5485 static int *stage_chunk
;
5487 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5489 /* This should work even for the "On branch" line. */
5491 status_has_none(struct view
*view
, struct line
*line
)
5493 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5496 /* Get fields from the diff line:
5497 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5500 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5502 const char *old_mode
= buf
+ 1;
5503 const char *new_mode
= buf
+ 8;
5504 const char *old_rev
= buf
+ 15;
5505 const char *new_rev
= buf
+ 56;
5506 const char *status
= buf
+ 97;
5509 old_mode
[-1] != ':' ||
5510 new_mode
[-1] != ' ' ||
5511 old_rev
[-1] != ' ' ||
5512 new_rev
[-1] != ' ' ||
5516 file
->status
= *status
;
5518 string_copy_rev(file
->old
.rev
, old_rev
);
5519 string_copy_rev(file
->new.rev
, new_rev
);
5521 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5522 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5524 file
->old
.name
[0] = file
->new.name
[0] = 0;
5530 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5532 struct status
*unmerged
= NULL
;
5536 if (!io_run(&io
, IO_RD
, opt_cdup
, argv
))
5539 add_line_data(view
, NULL
, type
);
5541 while ((buf
= io_get(&io
, 0, TRUE
))) {
5542 struct status
*file
= unmerged
;
5545 file
= calloc(1, sizeof(*file
));
5546 if (!file
|| !add_line_data(view
, file
, type
))
5550 /* Parse diff info part. */
5552 file
->status
= status
;
5554 string_copy(file
->old
.rev
, NULL_ID
);
5556 } else if (!file
->status
|| file
== unmerged
) {
5557 if (!status_get_diff(file
, buf
, strlen(buf
)))
5560 buf
= io_get(&io
, 0, TRUE
);
5564 /* Collapse all modified entries that follow an
5565 * associated unmerged entry. */
5566 if (unmerged
== file
) {
5567 unmerged
->status
= 'U';
5569 } else if (file
->status
== 'U') {
5574 /* Grab the old name for rename/copy. */
5575 if (!*file
->old
.name
&&
5576 (file
->status
== 'R' || file
->status
== 'C')) {
5577 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5579 buf
= io_get(&io
, 0, TRUE
);
5584 /* git-ls-files just delivers a NUL separated list of
5585 * file names similar to the second half of the
5586 * git-diff-* output. */
5587 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5588 if (!*file
->old
.name
)
5589 string_copy(file
->old
.name
, file
->new.name
);
5593 if (io_error(&io
)) {
5599 if (!view
->line
[view
->lines
- 1].data
)
5600 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5606 /* Don't show unmerged entries in the staged section. */
5607 static const char *status_diff_index_argv
[] = {
5608 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5609 "--cached", "-M", "HEAD", NULL
5612 static const char *status_diff_files_argv
[] = {
5613 "git", "diff-files", "-z", NULL
5616 static const char *status_list_other_argv
[] = {
5617 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix
, NULL
5620 static const char *status_list_no_head_argv
[] = {
5621 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5624 static const char *update_index_argv
[] = {
5625 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5628 /* Restore the previous line number to stay in the context or select a
5629 * line with something that can be updated. */
5631 status_restore(struct view
*view
)
5633 if (view
->p_lineno
>= view
->lines
)
5634 view
->p_lineno
= view
->lines
- 1;
5635 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5637 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5640 /* If the above fails, always skip the "On branch" line. */
5641 if (view
->p_lineno
< view
->lines
)
5642 view
->lineno
= view
->p_lineno
;
5646 if (view
->lineno
< view
->offset
)
5647 view
->offset
= view
->lineno
;
5648 else if (view
->offset
+ view
->height
<= view
->lineno
)
5649 view
->offset
= view
->lineno
- view
->height
+ 1;
5651 view
->p_restore
= FALSE
;
5655 status_update_onbranch(void)
5657 static const char *paths
[][2] = {
5658 { "rebase-apply/rebasing", "Rebasing" },
5659 { "rebase-apply/applying", "Applying mailbox" },
5660 { "rebase-apply/", "Rebasing mailbox" },
5661 { "rebase-merge/interactive", "Interactive rebase" },
5662 { "rebase-merge/", "Rebase merge" },
5663 { "MERGE_HEAD", "Merging" },
5664 { "BISECT_LOG", "Bisecting" },
5665 { "HEAD", "On branch" },
5667 char buf
[SIZEOF_STR
];
5671 if (is_initial_commit()) {
5672 string_copy(status_onbranch
, "Initial commit");
5676 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5677 char *head
= opt_head
;
5679 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5680 lstat(buf
, &stat
) < 0)
5686 if (io_open(&io
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5687 io_read_buf(&io
, buf
, sizeof(buf
))) {
5689 if (!prefixcmp(head
, "refs/heads/"))
5690 head
+= STRING_SIZE("refs/heads/");
5694 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5695 string_copy(status_onbranch
, opt_head
);
5699 string_copy(status_onbranch
, "Not currently on any branch");
5702 /* First parse staged info using git-diff-index(1), then parse unstaged
5703 * info using git-diff-files(1), and finally untracked files using
5704 * git-ls-files(1). */
5706 status_open(struct view
*view
)
5710 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5711 status_update_onbranch();
5713 io_run_bg(update_index_argv
);
5715 if (is_initial_commit()) {
5716 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5718 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5722 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5723 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5726 /* Restore the exact position or use the specialized restore
5728 if (!view
->p_restore
)
5729 status_restore(view
);
5734 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5736 struct status
*status
= line
->data
;
5737 enum line_type type
;
5741 switch (line
->type
) {
5742 case LINE_STAT_STAGED
:
5743 type
= LINE_STAT_SECTION
;
5744 text
= "Changes to be committed:";
5747 case LINE_STAT_UNSTAGED
:
5748 type
= LINE_STAT_SECTION
;
5749 text
= "Changed but not updated:";
5752 case LINE_STAT_UNTRACKED
:
5753 type
= LINE_STAT_SECTION
;
5754 text
= "Untracked files:";
5757 case LINE_STAT_NONE
:
5758 type
= LINE_DEFAULT
;
5759 text
= " (no files)";
5762 case LINE_STAT_HEAD
:
5763 type
= LINE_STAT_HEAD
;
5764 text
= status_onbranch
;
5771 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5773 buf
[0] = status
->status
;
5774 if (draw_text(view
, line
->type
, buf
, TRUE
))
5776 type
= LINE_DEFAULT
;
5777 text
= status
->new.name
;
5780 draw_text(view
, type
, text
, TRUE
);
5785 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5787 if (displayed_views() == 2 || display
[current_view
] != view
)
5788 maximize_view(view
);
5789 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5794 status_enter(struct view
*view
, struct line
*line
)
5796 struct status
*status
= line
->data
;
5797 const char *oldpath
= status
? status
->old
.name
: NULL
;
5798 /* Diffs for unmerged entries are empty when passing the new
5799 * path, so leave it empty. */
5800 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5802 enum open_flags split
;
5803 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5805 if (line
->type
== LINE_STAT_NONE
||
5806 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5807 report("No file to diff");
5811 switch (line
->type
) {
5812 case LINE_STAT_STAGED
:
5813 if (is_initial_commit()) {
5814 const char *no_head_diff_argv
[] = {
5815 "git", "diff", "--no-color", "--patch-with-stat",
5816 "--", "/dev/null", newpath
, NULL
5819 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
))
5820 return status_load_error(view
, stage
, newpath
);
5822 const char *index_show_argv
[] = {
5823 "git", "diff-index", "--root", "--patch-with-stat",
5824 "-C", "-M", "--cached", "HEAD", "--",
5825 oldpath
, newpath
, NULL
5828 if (!prepare_update(stage
, index_show_argv
, opt_cdup
))
5829 return status_load_error(view
, stage
, newpath
);
5833 info
= "Staged changes to %s";
5835 info
= "Staged changes";
5838 case LINE_STAT_UNSTAGED
:
5840 const char *files_show_argv
[] = {
5841 "git", "diff-files", "--root", "--patch-with-stat",
5842 "-C", "-M", "--", oldpath
, newpath
, NULL
5845 if (!prepare_update(stage
, files_show_argv
, opt_cdup
))
5846 return status_load_error(view
, stage
, newpath
);
5848 info
= "Unstaged changes to %s";
5850 info
= "Unstaged changes";
5853 case LINE_STAT_UNTRACKED
:
5855 report("No file to show");
5859 if (!suffixcmp(status
->new.name
, -1, "/")) {
5860 report("Cannot display a directory");
5864 if (!prepare_update_file(stage
, newpath
))
5865 return status_load_error(view
, stage
, newpath
);
5866 info
= "Untracked file %s";
5869 case LINE_STAT_HEAD
:
5873 die("line type %d not handled in switch", line
->type
);
5876 split
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
5877 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5878 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5880 stage_status
= *status
;
5882 memset(&stage_status
, 0, sizeof(stage_status
));
5885 stage_line_type
= line
->type
;
5887 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5894 status_exists(struct status
*status
, enum line_type type
)
5896 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5897 unsigned long lineno
;
5899 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5900 struct line
*line
= &view
->line
[lineno
];
5901 struct status
*pos
= line
->data
;
5903 if (line
->type
!= type
)
5905 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5906 select_view_line(view
, lineno
);
5909 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5910 select_view_line(view
, lineno
);
5920 status_update_prepare(struct io
*io
, enum line_type type
)
5922 const char *staged_argv
[] = {
5923 "git", "update-index", "-z", "--index-info", NULL
5925 const char *others_argv
[] = {
5926 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5930 case LINE_STAT_STAGED
:
5931 return io_run(io
, IO_WR
, opt_cdup
, staged_argv
);
5933 case LINE_STAT_UNSTAGED
:
5934 case LINE_STAT_UNTRACKED
:
5935 return io_run(io
, IO_WR
, opt_cdup
, others_argv
);
5938 die("line type %d not handled in switch", type
);
5944 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5946 char buf
[SIZEOF_STR
];
5950 case LINE_STAT_STAGED
:
5951 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5954 status
->old
.name
, 0))
5958 case LINE_STAT_UNSTAGED
:
5959 case LINE_STAT_UNTRACKED
:
5960 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
5965 die("line type %d not handled in switch", type
);
5968 return io_write(io
, buf
, bufsize
);
5972 status_update_file(struct status
*status
, enum line_type type
)
5977 if (!status_update_prepare(&io
, type
))
5980 result
= status_update_write(&io
, status
, type
);
5981 return io_done(&io
) && result
;
5985 status_update_files(struct view
*view
, struct line
*line
)
5987 char buf
[sizeof(view
->ref
)];
5990 struct line
*pos
= view
->line
+ view
->lines
;
5993 int cursor_y
= -1, cursor_x
= -1;
5995 if (!status_update_prepare(&io
, line
->type
))
5998 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
6001 string_copy(buf
, view
->ref
);
6002 getsyx(cursor_y
, cursor_x
);
6003 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
6004 int almost_done
= file
* 100 / files
;
6006 if (almost_done
> done
) {
6008 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
6010 update_view_title(view
);
6011 setsyx(cursor_y
, cursor_x
);
6014 result
= status_update_write(&io
, line
->data
, line
->type
);
6016 string_copy(view
->ref
, buf
);
6018 return io_done(&io
) && result
;
6022 status_update(struct view
*view
)
6024 struct line
*line
= &view
->line
[view
->lineno
];
6026 assert(view
->lines
);
6029 /* This should work even for the "On branch" line. */
6030 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
6031 report("Nothing to update");
6035 if (!status_update_files(view
, line
+ 1)) {
6036 report("Failed to update file status");
6040 } else if (!status_update_file(line
->data
, line
->type
)) {
6041 report("Failed to update file status");
6049 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
6051 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
6052 if (type
== LINE_STAT_STAGED
) {
6053 report("Cannot revert changes to staged files");
6054 } else if (type
== LINE_STAT_UNTRACKED
) {
6055 report("Cannot revert changes to untracked files");
6056 } else if (has_none
) {
6057 report("Nothing to revert");
6059 report("Cannot revert changes to multiple files");
6062 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6063 char mode
[10] = "100644";
6064 const char *reset_argv
[] = {
6065 "git", "update-index", "--cacheinfo", mode
,
6066 status
->old
.rev
, status
->old
.name
, NULL
6068 const char *checkout_argv
[] = {
6069 "git", "checkout", "--", status
->old
.name
, NULL
6072 if (status
->status
== 'U') {
6073 string_format(mode
, "%5o", status
->old
.mode
);
6075 if (status
->old
.mode
== 0 && status
->new.mode
== 0) {
6076 reset_argv
[2] = "--force-remove";
6077 reset_argv
[3] = status
->old
.name
;
6078 reset_argv
[4] = NULL
;
6081 if (!io_run_fg(reset_argv
, opt_cdup
))
6083 if (status
->old
.mode
== 0 && status
->new.mode
== 0)
6087 return io_run_fg(checkout_argv
, opt_cdup
);
6094 status_request(struct view
*view
, enum request request
, struct line
*line
)
6096 struct status
*status
= line
->data
;
6099 case REQ_STATUS_UPDATE
:
6100 if (!status_update(view
))
6104 case REQ_STATUS_REVERT
:
6105 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
6109 case REQ_STATUS_MERGE
:
6110 if (!status
|| status
->status
!= 'U') {
6111 report("Merging only possible for files with unmerged status ('U').");
6114 open_mergetool(status
->new.name
);
6120 if (status
->status
== 'D') {
6121 report("File has been deleted.");
6125 open_editor(status
->new.name
);
6128 case REQ_VIEW_BLAME
:
6134 /* After returning the status view has been split to
6135 * show the stage view. No further reloading is
6137 return status_enter(view
, line
);
6140 /* Simply reload the view. */
6147 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
6153 status_select(struct view
*view
, struct line
*line
)
6155 struct status
*status
= line
->data
;
6156 char file
[SIZEOF_STR
] = "all files";
6160 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
6163 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
6166 switch (line
->type
) {
6167 case LINE_STAT_STAGED
:
6168 text
= "Press %s to unstage %s for commit";
6171 case LINE_STAT_UNSTAGED
:
6172 text
= "Press %s to stage %s for commit";
6175 case LINE_STAT_UNTRACKED
:
6176 text
= "Press %s to stage %s for addition";
6179 case LINE_STAT_HEAD
:
6180 case LINE_STAT_NONE
:
6181 text
= "Nothing to update";
6185 die("line type %d not handled in switch", line
->type
);
6188 if (status
&& status
->status
== 'U') {
6189 text
= "Press %s to resolve conflict in %s";
6190 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
6193 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
6196 string_format(view
->ref
, text
, key
, file
);
6198 string_copy(opt_file
, status
->new.name
);
6202 status_grep(struct view
*view
, struct line
*line
)
6204 struct status
*status
= line
->data
;
6207 const char buf
[2] = { status
->status
, 0 };
6208 const char *text
[] = { status
->new.name
, buf
, NULL
};
6210 return grep_text(view
, text
);
6216 static struct view_ops status_ops
= {
6229 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
6231 while (line
< end
) {
6232 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
6233 !io_write(io
, "\n", 1))
6236 if (line
->type
== LINE_DIFF_CHUNK
||
6237 line
->type
== LINE_DIFF_HEADER
)
6244 static struct line
*
6245 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
6247 for (; view
->line
< line
; line
--)
6248 if (line
->type
== type
)
6255 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
6257 const char *apply_argv
[SIZEOF_ARG
] = {
6258 "git", "apply", "--whitespace=nowarn", NULL
6260 struct line
*diff_hdr
;
6264 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6269 apply_argv
[argc
++] = "--cached";
6270 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6271 apply_argv
[argc
++] = "-R";
6272 apply_argv
[argc
++] = "-";
6273 apply_argv
[argc
++] = NULL
;
6274 if (!io_run(&io
, IO_WR
, opt_cdup
, apply_argv
))
6277 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6278 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6282 io_run_bg(update_index_argv
);
6284 return chunk
? TRUE
: FALSE
;
6288 stage_update(struct view
*view
, struct line
*line
)
6290 struct line
*chunk
= NULL
;
6292 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6293 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6296 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6297 report("Failed to apply chunk");
6301 } else if (!stage_status
.status
) {
6302 view
= VIEW(REQ_VIEW_STATUS
);
6304 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6305 if (line
->type
== stage_line_type
)
6308 if (!status_update_files(view
, line
+ 1)) {
6309 report("Failed to update files");
6313 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6314 report("Failed to update file");
6322 stage_revert(struct view
*view
, struct line
*line
)
6324 struct line
*chunk
= NULL
;
6326 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6327 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6330 if (!prompt_yesno("Are you sure you want to revert changes?"))
6333 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6334 report("Failed to revert chunk");
6340 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6341 stage_line_type
, FALSE
);
6347 stage_next(struct view
*view
, struct line
*line
)
6351 if (!stage_chunks
) {
6352 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6353 if (line
->type
!= LINE_DIFF_CHUNK
)
6356 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6357 report("Allocation failure");
6361 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6365 for (i
= 0; i
< stage_chunks
; i
++) {
6366 if (stage_chunk
[i
] > view
->lineno
) {
6367 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6368 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6373 report("No next chunk found");
6377 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6380 case REQ_STATUS_UPDATE
:
6381 if (!stage_update(view
, line
))
6385 case REQ_STATUS_REVERT
:
6386 if (!stage_revert(view
, line
))
6390 case REQ_STAGE_NEXT
:
6391 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6392 report("File is untracked; press %s to add",
6393 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6396 stage_next(view
, line
);
6400 if (!stage_status
.new.name
[0])
6402 if (stage_status
.status
== 'D') {
6403 report("File has been deleted.");
6407 open_editor(stage_status
.new.name
);
6411 /* Reload everything ... */
6414 case REQ_VIEW_BLAME
:
6415 if (stage_status
.new.name
[0]) {
6416 string_copy(opt_file
, stage_status
.new.name
);
6422 return pager_request(view
, request
, line
);
6428 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6429 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6431 /* Check whether the staged entry still exists, and close the
6432 * stage view if it doesn't. */
6433 if (!status_exists(&stage_status
, stage_line_type
)) {
6434 status_restore(VIEW(REQ_VIEW_STATUS
));
6435 return REQ_VIEW_CLOSE
;
6438 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6439 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6440 report("Cannot display a directory");
6444 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6445 report("Failed to open file: %s", strerror(errno
));
6449 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6454 static struct view_ops stage_ops
= {
6471 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6472 char title
[128]; /* First line of the commit message. */
6473 const char *author
; /* Author of the commit. */
6474 struct time time
; /* Date from the author ident. */
6475 struct ref_list
*refs
; /* Repository references. */
6476 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6477 size_t graph_size
; /* The width of the graph array. */
6478 bool has_parents
; /* Rewritten --parents seen. */
6481 /* Size of rev graph with no "padding" columns */
6482 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6485 struct rev_graph
*prev
, *next
, *parents
;
6486 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6488 struct commit
*commit
;
6490 unsigned int boundary
:1;
6493 /* Parents of the commit being visualized. */
6494 static struct rev_graph graph_parents
[4];
6496 /* The current stack of revisions on the graph. */
6497 static struct rev_graph graph_stacks
[4] = {
6498 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6499 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6500 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6501 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6505 graph_parent_is_merge(struct rev_graph
*graph
)
6507 return graph
->parents
->size
> 1;
6511 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6513 struct commit
*commit
= graph
->commit
;
6515 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6516 commit
->graph
[commit
->graph_size
++] = symbol
;
6520 clear_rev_graph(struct rev_graph
*graph
)
6522 graph
->boundary
= 0;
6523 graph
->size
= graph
->pos
= 0;
6524 graph
->commit
= NULL
;
6525 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6529 done_rev_graph(struct rev_graph
*graph
)
6531 if (graph_parent_is_merge(graph
) &&
6532 graph
->pos
< graph
->size
- 1 &&
6533 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6534 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6536 graph
->commit
->graph_size
= i
* 2;
6537 while (i
< graph
->next
->size
- 1) {
6538 append_to_rev_graph(graph
, ' ');
6539 append_to_rev_graph(graph
, '\\');
6544 clear_rev_graph(graph
);
6548 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6552 /* "Collapse" duplicate parents lines.
6554 * FIXME: This needs to also update update the drawn graph but
6555 * for now it just serves as a method for pruning graph lines. */
6556 for (i
= 0; i
< graph
->size
; i
++)
6557 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6560 if (graph
->size
< SIZEOF_REVITEMS
) {
6561 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6566 get_rev_graph_symbol(struct rev_graph
*graph
)
6570 if (graph
->boundary
)
6571 symbol
= REVGRAPH_BOUND
;
6572 else if (graph
->parents
->size
== 0)
6573 symbol
= REVGRAPH_INIT
;
6574 else if (graph_parent_is_merge(graph
))
6575 symbol
= REVGRAPH_MERGE
;
6576 else if (graph
->pos
>= graph
->size
)
6577 symbol
= REVGRAPH_BRANCH
;
6579 symbol
= REVGRAPH_COMMIT
;
6585 draw_rev_graph(struct rev_graph
*graph
)
6588 chtype separator
, line
;
6590 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6591 static struct rev_filler fillers
[] = {
6597 chtype symbol
= get_rev_graph_symbol(graph
);
6598 struct rev_filler
*filler
;
6601 fillers
[DEFAULT
].line
= opt_line_graphics
? ACS_VLINE
: '|';
6602 filler
= &fillers
[DEFAULT
];
6604 for (i
= 0; i
< graph
->pos
; i
++) {
6605 append_to_rev_graph(graph
, filler
->line
);
6606 if (graph_parent_is_merge(graph
->prev
) &&
6607 graph
->prev
->pos
== i
)
6608 filler
= &fillers
[RSHARP
];
6610 append_to_rev_graph(graph
, filler
->separator
);
6613 /* Place the symbol for this revision. */
6614 append_to_rev_graph(graph
, symbol
);
6616 if (graph
->prev
->size
> graph
->size
)
6617 filler
= &fillers
[RDIAG
];
6619 filler
= &fillers
[DEFAULT
];
6623 for (; i
< graph
->size
; i
++) {
6624 append_to_rev_graph(graph
, filler
->separator
);
6625 append_to_rev_graph(graph
, filler
->line
);
6626 if (graph_parent_is_merge(graph
->prev
) &&
6627 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6628 filler
= &fillers
[RSHARP
];
6629 if (graph
->prev
->size
> graph
->size
)
6630 filler
= &fillers
[LDIAG
];
6633 if (graph
->prev
->size
> graph
->size
) {
6634 append_to_rev_graph(graph
, filler
->separator
);
6635 if (filler
->line
!= ' ')
6636 append_to_rev_graph(graph
, filler
->line
);
6640 /* Prepare the next rev graph */
6642 prepare_rev_graph(struct rev_graph
*graph
)
6646 /* First, traverse all lines of revisions up to the active one. */
6647 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6648 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6651 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6654 /* Interleave the new revision parent(s). */
6655 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6656 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6658 /* Lastly, put any remaining revisions. */
6659 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6660 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6664 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6666 /* If this is the finalizing update ... */
6668 prepare_rev_graph(graph
);
6670 /* Graph visualization needs a one rev look-ahead,
6671 * so the first update doesn't visualize anything. */
6672 if (!graph
->prev
->commit
)
6675 if (view
->lines
> 2)
6676 view
->line
[view
->lines
- 3].dirty
= 1;
6677 if (view
->lines
> 1)
6678 view
->line
[view
->lines
- 2].dirty
= 1;
6679 draw_rev_graph(graph
->prev
);
6680 done_rev_graph(graph
->prev
->prev
);
6688 static const char *main_argv
[SIZEOF_ARG
] = {
6689 "git", "log", "--no-color", "--pretty=raw", "--parents",
6690 "--topo-order", "%(diff-args)", "%(rev-args)",
6691 "--", "%(file-args)", NULL
6695 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6697 struct commit
*commit
= line
->data
;
6699 if (!commit
->author
)
6702 if (opt_date
&& draw_date(view
, &commit
->time
))
6705 if (opt_author
&& draw_author(view
, commit
->author
))
6708 if (opt_rev_graph
&& commit
->graph_size
&&
6709 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6712 if (opt_show_refs
&& commit
->refs
) {
6715 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6716 struct ref
*ref
= commit
->refs
->refs
[i
];
6717 enum line_type type
;
6720 type
= LINE_MAIN_HEAD
;
6722 type
= LINE_MAIN_LOCAL_TAG
;
6724 type
= LINE_MAIN_TAG
;
6725 else if (ref
->tracked
)
6726 type
= LINE_MAIN_TRACKED
;
6727 else if (ref
->remote
)
6728 type
= LINE_MAIN_REMOTE
;
6730 type
= LINE_MAIN_REF
;
6732 if (draw_text(view
, type
, "[", TRUE
) ||
6733 draw_text(view
, type
, ref
->name
, TRUE
) ||
6734 draw_text(view
, type
, "]", TRUE
))
6737 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6742 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6746 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6748 main_read(struct view
*view
, char *line
)
6750 static struct rev_graph
*graph
= graph_stacks
;
6751 enum line_type type
;
6752 struct commit
*commit
;
6757 if (!view
->lines
&& !view
->prev
)
6758 die("No revisions match the given arguments.");
6759 if (view
->lines
> 0) {
6760 commit
= view
->line
[view
->lines
- 1].data
;
6761 view
->line
[view
->lines
- 1].dirty
= 1;
6762 if (!commit
->author
) {
6765 graph
->commit
= NULL
;
6768 update_rev_graph(view
, graph
);
6770 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6771 clear_rev_graph(&graph_stacks
[i
]);
6775 type
= get_line_type(line
);
6776 if (type
== LINE_COMMIT
) {
6777 commit
= calloc(1, sizeof(struct commit
));
6781 line
+= STRING_SIZE("commit ");
6783 graph
->boundary
= 1;
6787 string_copy_rev(commit
->id
, line
);
6788 commit
->refs
= get_ref_list(commit
->id
);
6789 graph
->commit
= commit
;
6790 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6792 while ((line
= strchr(line
, ' '))) {
6794 push_rev_graph(graph
->parents
, line
);
6795 commit
->has_parents
= TRUE
;
6802 commit
= view
->line
[view
->lines
- 1].data
;
6806 if (commit
->has_parents
)
6808 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6812 parse_author_line(line
+ STRING_SIZE("author "),
6813 &commit
->author
, &commit
->time
);
6814 update_rev_graph(view
, graph
);
6815 graph
= graph
->next
;
6819 /* Fill in the commit title if it has not already been set. */
6820 if (commit
->title
[0])
6823 /* Require titles to start with a non-space character at the
6824 * offset used by git log. */
6825 if (strncmp(line
, " ", 4))
6828 /* Well, if the title starts with a whitespace character,
6829 * try to be forgiving. Otherwise we end up with no title. */
6830 while (isspace(*line
))
6834 /* FIXME: More graceful handling of titles; append "..." to
6835 * shortened titles, etc. */
6837 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6838 view
->line
[view
->lines
- 1].dirty
= 1;
6845 main_request(struct view
*view
, enum request request
, struct line
*line
)
6847 enum open_flags flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
6851 open_view(view
, REQ_VIEW_DIFF
, flags
);
6855 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6865 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6870 if (!opt_show_refs
|| !list
)
6873 for (i
= 0; i
< list
->size
; i
++) {
6874 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6882 main_grep(struct view
*view
, struct line
*line
)
6884 struct commit
*commit
= line
->data
;
6885 const char *text
[] = {
6887 opt_author
? commit
->author
: "",
6888 mkdate(&commit
->time
, opt_date
),
6892 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6896 main_select(struct view
*view
, struct line
*line
)
6898 struct commit
*commit
= line
->data
;
6900 string_copy_rev(view
->ref
, commit
->id
);
6901 string_copy_rev(ref_commit
, view
->ref
);
6904 static struct view_ops main_ops
= {
6920 /* Whether or not the curses interface has been initialized. */
6921 static bool cursed
= FALSE
;
6923 /* Terminal hacks and workarounds. */
6924 static bool use_scroll_redrawwin
;
6925 static bool use_scroll_status_wclear
;
6927 /* The status window is used for polling keystrokes. */
6928 static WINDOW
*status_win
;
6930 /* Reading from the prompt? */
6931 static bool input_mode
= FALSE
;
6933 static bool status_empty
= FALSE
;
6935 /* Update status and title window. */
6937 report(const char *msg
, ...)
6939 struct view
*view
= display
[current_view
];
6945 char buf
[SIZEOF_STR
];
6948 va_start(args
, msg
);
6949 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6950 buf
[sizeof(buf
) - 1] = 0;
6951 buf
[sizeof(buf
) - 2] = '.';
6952 buf
[sizeof(buf
) - 3] = '.';
6953 buf
[sizeof(buf
) - 4] = '.';
6959 if (!status_empty
|| *msg
) {
6962 va_start(args
, msg
);
6964 wmove(status_win
, 0, 0);
6965 if (view
->has_scrolled
&& use_scroll_status_wclear
)
6968 vwprintw(status_win
, msg
, args
);
6969 status_empty
= FALSE
;
6971 status_empty
= TRUE
;
6973 wclrtoeol(status_win
);
6974 wnoutrefresh(status_win
);
6979 update_view_title(view
);
6988 /* Initialize the curses library */
6989 if (isatty(STDIN_FILENO
)) {
6990 cursed
= !!initscr();
6993 /* Leave stdin and stdout alone when acting as a pager. */
6994 opt_tty
= fopen("/dev/tty", "r+");
6996 die("Failed to open /dev/tty");
6997 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
7001 die("Failed to initialize curses");
7003 nonl(); /* Disable conversion and detect newlines from input. */
7004 cbreak(); /* Take input chars one at a time, no wait for \n */
7005 noecho(); /* Don't echo input */
7006 leaveok(stdscr
, FALSE
);
7011 getmaxyx(stdscr
, y
, x
);
7012 status_win
= newwin(1, 0, y
- 1, 0);
7014 die("Failed to create status window");
7016 /* Enable keyboard mapping */
7017 keypad(status_win
, TRUE
);
7018 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
7020 TABSIZE
= opt_tab_size
;
7022 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
7023 if (term
&& !strcmp(term
, "gnome-terminal")) {
7024 /* In the gnome-terminal-emulator, the message from
7025 * scrolling up one line when impossible followed by
7026 * scrolling down one line causes corruption of the
7027 * status line. This is fixed by calling wclear. */
7028 use_scroll_status_wclear
= TRUE
;
7029 use_scroll_redrawwin
= FALSE
;
7031 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
7032 /* No problems with full optimizations in xrvt-(unicode)
7034 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
7037 /* When scrolling in (u)xterm the last line in the
7038 * scrolling direction will update slowly. */
7039 use_scroll_redrawwin
= TRUE
;
7040 use_scroll_status_wclear
= FALSE
;
7045 get_input(int prompt_position
)
7048 int i
, key
, cursor_y
, cursor_x
;
7050 if (prompt_position
)
7054 bool loading
= FALSE
;
7056 foreach_view (view
, i
) {
7058 if (view_is_displayed(view
) && view
->has_scrolled
&&
7059 use_scroll_redrawwin
)
7060 redrawwin(view
->win
);
7061 view
->has_scrolled
= FALSE
;
7066 /* Update the cursor position. */
7067 if (prompt_position
) {
7068 getbegyx(status_win
, cursor_y
, cursor_x
);
7069 cursor_x
= prompt_position
;
7071 view
= display
[current_view
];
7072 getbegyx(view
->win
, cursor_y
, cursor_x
);
7073 cursor_x
= view
->width
- 1;
7074 cursor_y
+= view
->lineno
- view
->offset
;
7076 setsyx(cursor_y
, cursor_x
);
7078 /* Refresh, accept single keystroke of input */
7080 nodelay(status_win
, loading
);
7081 key
= wgetch(status_win
);
7083 /* wgetch() with nodelay() enabled returns ERR when
7084 * there's no input. */
7087 } else if (key
== KEY_RESIZE
) {
7090 getmaxyx(stdscr
, height
, width
);
7092 wresize(status_win
, 1, width
);
7093 mvwin(status_win
, height
- 1, 0);
7094 wnoutrefresh(status_win
);
7096 redraw_display(TRUE
);
7106 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7108 enum input_status status
= INPUT_OK
;
7109 static char buf
[SIZEOF_STR
];
7114 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7117 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7118 wclrtoeol(status_win
);
7120 key
= get_input(pos
+ 1);
7125 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7132 status
= INPUT_CANCEL
;
7136 status
= INPUT_CANCEL
;
7140 if (pos
>= sizeof(buf
)) {
7141 report("Input string too long");
7145 status
= handler(data
, buf
, key
);
7146 if (status
== INPUT_OK
)
7147 buf
[pos
++] = (char) key
;
7151 /* Clear the status window */
7152 status_empty
= FALSE
;
7155 if (status
== INPUT_CANCEL
)
7163 static enum input_status
7164 prompt_yesno_handler(void *data
, char *buf
, int c
)
7166 if (c
== 'y' || c
== 'Y')
7168 if (c
== 'n' || c
== 'N')
7169 return INPUT_CANCEL
;
7174 prompt_yesno(const char *prompt
)
7176 char prompt2
[SIZEOF_STR
];
7178 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7181 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7184 static enum input_status
7185 read_prompt_handler(void *data
, char *buf
, int c
)
7187 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7191 read_prompt(const char *prompt
)
7193 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7196 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7198 enum input_status status
= INPUT_OK
;
7201 while (items
[size
].text
)
7204 while (status
== INPUT_OK
) {
7205 const struct menu_item
*item
= &items
[*selected
];
7209 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7210 prompt
, *selected
+ 1, size
);
7212 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7213 wprintw(status_win
, "%s", item
->text
);
7214 wclrtoeol(status_win
);
7216 key
= get_input(COLS
- 1);
7221 status
= INPUT_STOP
;
7226 *selected
= *selected
- 1;
7228 *selected
= size
- 1;
7233 *selected
= (*selected
+ 1) % size
;
7237 status
= INPUT_CANCEL
;
7241 for (i
= 0; items
[i
].text
; i
++)
7242 if (items
[i
].hotkey
== key
) {
7244 status
= INPUT_STOP
;
7250 /* Clear the status window */
7251 status_empty
= FALSE
;
7254 return status
!= INPUT_CANCEL
;
7258 * Repository properties
7261 static struct ref
**refs
= NULL
;
7262 static size_t refs_size
= 0;
7263 static struct ref
*refs_head
= NULL
;
7265 static struct ref_list
**ref_lists
= NULL
;
7266 static size_t ref_lists_size
= 0;
7268 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7269 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7270 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7273 compare_refs(const void *ref1_
, const void *ref2_
)
7275 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7276 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7278 if (ref1
->tag
!= ref2
->tag
)
7279 return ref2
->tag
- ref1
->tag
;
7280 if (ref1
->ltag
!= ref2
->ltag
)
7281 return ref2
->ltag
- ref2
->ltag
;
7282 if (ref1
->head
!= ref2
->head
)
7283 return ref2
->head
- ref1
->head
;
7284 if (ref1
->tracked
!= ref2
->tracked
)
7285 return ref2
->tracked
- ref1
->tracked
;
7286 if (ref1
->remote
!= ref2
->remote
)
7287 return ref2
->remote
- ref1
->remote
;
7288 return strcmp(ref1
->name
, ref2
->name
);
7292 foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
)
7296 for (i
= 0; i
< refs_size
; i
++)
7297 if (!visitor(data
, refs
[i
]))
7307 static struct ref_list
*
7308 get_ref_list(const char *id
)
7310 struct ref_list
*list
;
7313 for (i
= 0; i
< ref_lists_size
; i
++)
7314 if (!strcmp(id
, ref_lists
[i
]->id
))
7315 return ref_lists
[i
];
7317 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7319 list
= calloc(1, sizeof(*list
));
7323 for (i
= 0; i
< refs_size
; i
++) {
7324 if (!strcmp(id
, refs
[i
]->id
) &&
7325 realloc_refs_list(&list
->refs
, list
->size
, 1))
7326 list
->refs
[list
->size
++] = refs
[i
];
7334 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7335 ref_lists
[ref_lists_size
++] = list
;
7340 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
7342 struct ref
*ref
= NULL
;
7345 bool remote
= FALSE
;
7346 bool tracked
= FALSE
;
7348 int from
= 0, to
= refs_size
- 1;
7350 if (!prefixcmp(name
, "refs/tags/")) {
7351 if (!suffixcmp(name
, namelen
, "^{}")) {
7359 namelen
-= STRING_SIZE("refs/tags/");
7360 name
+= STRING_SIZE("refs/tags/");
7362 } else if (!prefixcmp(name
, "refs/remotes/")) {
7364 namelen
-= STRING_SIZE("refs/remotes/");
7365 name
+= STRING_SIZE("refs/remotes/");
7366 tracked
= !strcmp(opt_remote
, name
);
7368 } else if (!prefixcmp(name
, "refs/heads/")) {
7369 namelen
-= STRING_SIZE("refs/heads/");
7370 name
+= STRING_SIZE("refs/heads/");
7371 if (!strncmp(opt_head
, name
, namelen
))
7374 } else if (!strcmp(name
, "HEAD")) {
7377 namelen
= strlen(opt_head
);
7382 /* If we are reloading or it's an annotated tag, replace the
7383 * previous SHA1 with the resolved commit id; relies on the fact
7384 * git-ls-remote lists the commit id of an annotated tag right
7385 * before the commit id it points to. */
7386 while (from
<= to
) {
7387 size_t pos
= (to
+ from
) / 2;
7388 int cmp
= strcmp(name
, refs
[pos
]->name
);
7402 if (!realloc_refs(&refs
, refs_size
, 1))
7404 ref
= calloc(1, sizeof(*ref
) + namelen
);
7407 memmove(refs
+ from
+ 1, refs
+ from
,
7408 (refs_size
- from
) * sizeof(*refs
));
7410 strncpy(ref
->name
, name
, namelen
);
7417 ref
->remote
= remote
;
7418 ref
->tracked
= tracked
;
7419 string_copy_rev(ref
->id
, id
);
7429 const char *head_argv
[] = {
7430 "git", "symbolic-ref", "HEAD", NULL
7432 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7433 "git", "ls-remote", opt_git_dir
, NULL
7435 static bool init
= FALSE
;
7439 if (!argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE"))
7440 die("TIG_LS_REMOTE contains too many arguments");
7447 if (io_run_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7448 !prefixcmp(opt_head
, "refs/heads/")) {
7449 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7451 memmove(opt_head
, offset
, strlen(offset
) + 1);
7455 for (i
= 0; i
< refs_size
; i
++)
7458 if (io_run_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7461 /* Update the ref lists to reflect changes. */
7462 for (i
= 0; i
< ref_lists_size
; i
++) {
7463 struct ref_list
*list
= ref_lists
[i
];
7466 for (old
= new = 0; old
< list
->size
; old
++)
7467 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7468 list
->refs
[new++] = list
->refs
[old
];
7476 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7478 if (!strcmp(name
, ".remote")) {
7479 string_ncopy(opt_remote
, value
, valuelen
);
7481 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7482 size_t from
= strlen(opt_remote
);
7484 if (!prefixcmp(value
, "refs/heads/"))
7485 value
+= STRING_SIZE("refs/heads/");
7487 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7493 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7495 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7496 int argc
= 1 + (cmd
== option_set_command
);
7499 if (!argv_from_string(argv
, &argc
, value
))
7500 config_msg
= "Too many option arguments";
7502 error
= cmd(argc
, argv
);
7505 warn("Option 'tig.%s': %s", name
, config_msg
);
7509 set_environment_variable(const char *name
, const char *value
)
7511 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7512 char *env
= malloc(len
);
7515 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7523 set_work_tree(const char *value
)
7525 char cwd
[SIZEOF_STR
];
7527 if (!getcwd(cwd
, sizeof(cwd
)))
7528 die("Failed to get cwd path: %s", strerror(errno
));
7529 if (chdir(opt_git_dir
) < 0)
7530 die("Failed to chdir(%s): %s", strerror(errno
));
7531 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7532 die("Failed to get git path: %s", strerror(errno
));
7534 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7535 if (chdir(value
) < 0)
7536 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7537 if (!getcwd(cwd
, sizeof(cwd
)))
7538 die("Failed to get cwd path: %s", strerror(errno
));
7539 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7540 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7541 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7542 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7543 opt_is_inside_work_tree
= TRUE
;
7547 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7549 if (!strcmp(name
, "i18n.commitencoding"))
7550 string_ncopy(opt_encoding
, value
, valuelen
);
7552 else if (!strcmp(name
, "core.editor"))
7553 string_ncopy(opt_editor
, value
, valuelen
);
7555 else if (!strcmp(name
, "core.worktree"))
7556 set_work_tree(value
);
7558 else if (!prefixcmp(name
, "tig.color."))
7559 set_repo_config_option(name
+ 10, value
, option_color_command
);
7561 else if (!prefixcmp(name
, "tig.bind."))
7562 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7564 else if (!prefixcmp(name
, "tig."))
7565 set_repo_config_option(name
+ 4, value
, option_set_command
);
7567 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7568 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7569 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7575 load_git_config(void)
7577 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7579 return io_run_load(config_list_argv
, "=", read_repo_config_option
);
7583 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7585 if (!opt_git_dir
[0]) {
7586 string_ncopy(opt_git_dir
, name
, namelen
);
7588 } else if (opt_is_inside_work_tree
== -1) {
7589 /* This can be 3 different values depending on the
7590 * version of git being used. If git-rev-parse does not
7591 * understand --is-inside-work-tree it will simply echo
7592 * the option else either "true" or "false" is printed.
7593 * Default to true for the unknown case. */
7594 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7596 } else if (*name
== '.') {
7597 string_ncopy(opt_cdup
, name
, namelen
);
7600 string_ncopy(opt_prefix
, name
, namelen
);
7607 load_repo_info(void)
7609 const char *rev_parse_argv
[] = {
7610 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7611 "--show-cdup", "--show-prefix", NULL
7614 return io_run_load(rev_parse_argv
, "=", read_repo_info
);
7622 static const char usage
[] =
7623 "tig " TIG_VERSION
" (" __DATE__
")\n"
7625 "Usage: tig [options] [revs] [--] [paths]\n"
7626 " or: tig show [options] [revs] [--] [paths]\n"
7627 " or: tig blame [rev] path\n"
7629 " or: tig < [git command output]\n"
7632 " -v, --version Show version and exit\n"
7633 " -h, --help Show help message and exit";
7635 static void __NORETURN
7638 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7644 static void __NORETURN
7645 die(const char *err
, ...)
7651 va_start(args
, err
);
7652 fputs("tig: ", stderr
);
7653 vfprintf(stderr
, err
, args
);
7654 fputs("\n", stderr
);
7661 warn(const char *msg
, ...)
7665 va_start(args
, msg
);
7666 fputs("tig warning: ", stderr
);
7667 vfprintf(stderr
, msg
, args
);
7668 fputs("\n", stderr
);
7672 static const char ***filter_args
;
7675 read_filter_args(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7677 return argv_append(filter_args
, name
) ? OK
: ERR
;
7681 filter_rev_parse(const char ***args
, const char *arg1
, const char *arg2
, const char *argv
[])
7683 const char *rev_parse_argv
[SIZEOF_ARG
] = { "git", "rev-parse", arg1
, arg2
};
7684 const char **all_argv
= NULL
;
7687 if (!argv_append_array(&all_argv
, rev_parse_argv
) ||
7688 !argv_append_array(&all_argv
, argv
) ||
7689 !io_run_load(all_argv
, "\n", read_filter_args
) == ERR
)
7690 die("Failed to split arguments");
7691 argv_free(all_argv
);
7696 filter_options(const char *argv
[])
7698 filter_rev_parse(&opt_file_args
, "--no-revs", "--no-flags", argv
);
7699 filter_rev_parse(&opt_diff_args
, "--no-revs", "--flags", argv
);
7700 filter_rev_parse(&opt_rev_args
, "--symbolic", "--revs-only", argv
);
7704 parse_options(int argc
, const char *argv
[])
7706 enum request request
= REQ_VIEW_MAIN
;
7707 const char *subcommand
;
7708 bool seen_dashdash
= FALSE
;
7709 const char **filter_argv
= NULL
;
7712 if (!isatty(STDIN_FILENO
)) {
7713 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7714 return REQ_VIEW_PAGER
;
7718 return REQ_VIEW_MAIN
;
7720 subcommand
= argv
[1];
7721 if (!strcmp(subcommand
, "status")) {
7723 warn("ignoring arguments after `%s'", subcommand
);
7724 return REQ_VIEW_STATUS
;
7726 } else if (!strcmp(subcommand
, "blame")) {
7727 if (argc
<= 2 || argc
> 4)
7728 die("invalid number of options to blame\n\n%s", usage
);
7732 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7736 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7737 return REQ_VIEW_BLAME
;
7739 } else if (!strcmp(subcommand
, "show")) {
7740 request
= REQ_VIEW_DIFF
;
7746 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7747 const char *opt
= argv
[i
];
7749 if (seen_dashdash
) {
7750 argv_append(&opt_file_args
, opt
);
7753 } else if (!strcmp(opt
, "--")) {
7754 seen_dashdash
= TRUE
;
7757 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7758 printf("tig version %s\n", TIG_VERSION
);
7761 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7762 printf("%s\n", usage
);
7765 } else if (!strcmp(opt
, "--all")) {
7766 argv_append(&opt_rev_args
, opt
);
7770 if (!argv_append(&filter_argv
, opt
))
7771 die("command too long");
7775 filter_options(filter_argv
);
7781 main(int argc
, const char *argv
[])
7783 const char *codeset
= "UTF-8";
7784 enum request request
= parse_options(argc
, argv
);
7788 signal(SIGINT
, quit
);
7789 signal(SIGPIPE
, SIG_IGN
);
7791 if (setlocale(LC_ALL
, "")) {
7792 codeset
= nl_langinfo(CODESET
);
7795 if (load_repo_info() == ERR
)
7796 die("Failed to load repo info.");
7798 if (load_options() == ERR
)
7799 die("Failed to load user config.");
7801 if (load_git_config() == ERR
)
7802 die("Failed to load repo config.");
7804 /* Require a git repository unless when running in pager mode. */
7805 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7806 die("Not a git repository");
7808 if (*opt_encoding
&& strcmp(codeset
, "UTF-8")) {
7809 opt_iconv_in
= iconv_open("UTF-8", opt_encoding
);
7810 if (opt_iconv_in
== ICONV_NONE
)
7811 die("Failed to initialize character set conversion");
7814 if (codeset
&& strcmp(codeset
, "UTF-8")) {
7815 opt_iconv_out
= iconv_open(codeset
, "UTF-8");
7816 if (opt_iconv_out
== ICONV_NONE
)
7817 die("Failed to initialize character set conversion");
7820 if (load_refs() == ERR
)
7821 die("Failed to load refs.");
7823 foreach_view (view
, i
) {
7824 if (getenv(view
->cmd_env
))
7825 warn("Use of the %s environment variable is deprecated,"
7826 " use options or TIG_DIFF_ARGS instead",
7828 if (!argv_from_env(view
->ops
->argv
, view
->cmd_env
))
7829 die("Too many arguments in the `%s` environment variable",
7835 while (view_driver(display
[current_view
], request
)) {
7836 int key
= get_input(0);
7838 view
= display
[current_view
];
7839 request
= get_keybinding(view
->keymap
, key
);
7841 /* Some low-level request handling. This keeps access to
7842 * status_win restricted. */
7845 report("Unknown key, press %s for help",
7846 get_key(view
->keymap
, REQ_VIEW_HELP
));
7850 char *cmd
= read_prompt(":");
7852 if (cmd
&& isdigit(*cmd
)) {
7853 int lineno
= view
->lineno
+ 1;
7855 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7856 select_view_line(view
, lineno
- 1);
7859 report("Unable to parse '%s' as a line number", cmd
);
7863 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7864 const char *argv
[SIZEOF_ARG
] = { "git" };
7867 /* When running random commands, initially show the
7868 * command in the title. However, it maybe later be
7869 * overwritten if a commit line is selected. */
7870 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7872 if (!argv_from_string(argv
, &argc
, cmd
)) {
7873 report("Too many arguments");
7874 } else if (!prepare_update(next
, argv
, NULL
)) {
7875 report("Failed to format command");
7877 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7885 case REQ_SEARCH_BACK
:
7887 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7888 char *search
= read_prompt(prompt
);
7891 string_ncopy(opt_search
, search
, strlen(search
));
7892 else if (*opt_search
)
7893 request
= request
== REQ_SEARCH
?