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
, ...);
71 static size_t utf8_length(const char **start
, size_t skip
, int *width
, size_t max_width
, int *trimmed
, bool reserve
, int tab_size
);
72 static inline unsigned char utf8_char_length(const char *string
, const char *end
);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define MAX(x, y) ((x) > (y) ? (x) : (y))
78 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x) (sizeof(x) - 1)
81 #define SIZEOF_STR 1024 /* Default string size. */
82 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG 32 /* Default argument array size. */
88 #define REVGRAPH_INIT 'I'
89 #define REVGRAPH_MERGE 'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND '^'
94 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT (-1)
99 #define ICONV_NONE ((iconv_t) -1)
101 #define ICONV_CONST /* nothing */
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT "%Y-%m-%d %H:%M"
106 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
110 #define AUTHOR_COLS 19
112 #define MIN_VIEW_HEIGHT 4
114 #define NULL_ID "0000000000000000000000000000000000000000"
116 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
118 /* Some ASCII-shorthands fitted into the ncurses namespace. */
120 #define KEY_RETURN '\r'
125 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
126 unsigned int head
:1; /* Is it the current HEAD? */
127 unsigned int tag
:1; /* Is it a tag? */
128 unsigned int ltag
:1; /* If so, is the tag local? */
129 unsigned int remote
:1; /* Is it a remote ref? */
130 unsigned int tracked
:1; /* Is it the remote for the current HEAD? */
131 char name
[1]; /* Ref name; tag or head names are shortened. */
135 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
136 size_t size
; /* Number of refs. */
137 struct ref
**refs
; /* References for this ID. */
140 static struct ref
*get_ref_head();
141 static struct ref_list
*get_ref_list(const char *id
);
142 static void foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
);
143 static int load_refs(void);
146 FORMAT_ALL
, /* Perform replacement in all arguments. */
147 FORMAT_DASH
, /* Perform replacement up until "--". */
148 FORMAT_NONE
/* No replacement should be performed. */
151 static bool format_argv(const char *dst
[], const char *src
[], enum format_flags flags
);
160 typedef enum input_status (*input_handler
)(void *data
, char *buf
, int c
);
162 static char *prompt_input(const char *prompt
, input_handler handler
, void *data
);
163 static bool prompt_yesno(const char *prompt
);
171 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
);
174 * Allocation helpers ... Entering macro hell to never be seen again.
177 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
179 name(type **mem, size_t size, size_t increase) \
181 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
182 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
185 if (mem == NULL || num_chunks != num_chunks_new) { \
186 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
199 string_ncopy_do(char *dst
, size_t dstlen
, const char *src
, size_t srclen
)
201 if (srclen
> dstlen
- 1)
204 strncpy(dst
, src
, srclen
);
208 /* Shorthands for safely copying into a fixed buffer. */
210 #define string_copy(dst, src) \
211 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
213 #define string_ncopy(dst, src, srclen) \
214 string_ncopy_do(dst, sizeof(dst), src, srclen)
216 #define string_copy_rev(dst, src) \
217 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
219 #define string_add(dst, from, src) \
220 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
223 string_expand(char *dst
, size_t dstlen
, const char *src
, int tabsize
)
227 for (size
= pos
= 0; size
< dstlen
- 1 && src
[pos
]; pos
++) {
228 if (src
[pos
] == '\t') {
229 size_t expanded
= tabsize
- (size
% tabsize
);
231 if (expanded
+ size
>= dstlen
- 1)
232 expanded
= dstlen
- size
- 1;
233 memcpy(dst
+ size
, " ", expanded
);
236 dst
[size
++] = src
[pos
];
244 chomp_string(char *name
)
248 while (isspace(*name
))
251 namelen
= strlen(name
) - 1;
252 while (namelen
> 0 && isspace(name
[namelen
]))
259 string_nformat(char *buf
, size_t bufsize
, size_t *bufpos
, const char *fmt
, ...)
262 size_t pos
= bufpos
? *bufpos
: 0;
265 pos
+= vsnprintf(buf
+ pos
, bufsize
- pos
, fmt
, args
);
271 return pos
>= bufsize
? FALSE
: TRUE
;
274 #define string_format(buf, fmt, args...) \
275 string_nformat(buf, sizeof(buf), NULL, fmt, args)
277 #define string_format_from(buf, from, fmt, args...) \
278 string_nformat(buf, sizeof(buf), from, fmt, args)
281 string_enum_compare(const char *str1
, const char *str2
, int len
)
285 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
287 /* Diff-Header == DIFF_HEADER */
288 for (i
= 0; i
< len
; i
++) {
289 if (toupper(str1
[i
]) == toupper(str2
[i
]))
292 if (string_enum_sep(str1
[i
]) &&
293 string_enum_sep(str2
[i
]))
296 return str1
[i
] - str2
[i
];
302 #define enum_equals(entry, str, len) \
303 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
311 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
314 enum_map_name(const char *name
, size_t namelen
)
316 static char buf
[SIZEOF_STR
];
319 for (bufpos
= 0; bufpos
<= namelen
; bufpos
++) {
320 buf
[bufpos
] = tolower(name
[bufpos
]);
321 if (buf
[bufpos
] == '_')
329 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
332 map_enum_do(const struct enum_map
*map
, size_t map_size
, int *value
, const char *name
)
334 size_t namelen
= strlen(name
);
337 for (i
= 0; i
< map_size
; i
++)
338 if (enum_equals(map
[i
], name
, namelen
)) {
339 *value
= map
[i
].value
;
346 #define map_enum(attr, map, name) \
347 map_enum_do(map, ARRAY_SIZE(map), attr, name)
349 #define prefixcmp(str1, str2) \
350 strncmp(str1, str2, STRING_SIZE(str2))
353 suffixcmp(const char *str
, int slen
, const char *suffix
)
355 size_t len
= slen
>= 0 ? slen
: strlen(str
);
356 size_t suffixlen
= strlen(suffix
);
358 return suffixlen
< len
? strcmp(str
+ len
- suffixlen
, suffix
) : -1;
369 #define DATE_(name) DATE_##name
374 static const struct enum_map date_map
[] = {
375 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
385 static inline int timecmp(const struct time
*t1
, const struct time
*t2
)
387 return t1
->sec
- t2
->sec
;
391 string_date(const struct time
*time
, enum date date
)
393 static char buf
[DATE_COLS
+ 1];
394 static const struct enum_map reldate
[] = {
395 { "second", 1, 60 * 2 },
396 { "minute", 60, 60 * 60 * 2 },
397 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
398 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
399 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
400 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
404 if (date
== DATE_RELATIVE
) {
406 time_t date
= time
->sec
+ time
->tz
;
410 gettimeofday(&now
, NULL
);
411 seconds
= now
.tv_sec
< date
? date
- now
.tv_sec
: now
.tv_sec
- date
;
412 for (i
= 0; i
< ARRAY_SIZE(reldate
); i
++) {
413 if (seconds
>= reldate
[i
].value
)
416 seconds
/= reldate
[i
].namelen
;
417 if (!string_format(buf
, "%ld %s%s %s",
418 seconds
, reldate
[i
].name
,
419 seconds
> 1 ? "s" : "",
420 now
.tv_sec
>= date
? "ago" : "ahead"))
426 gmtime_r(&time
->sec
, &tm
);
427 return strftime(buf
, sizeof(buf
), DATE_FORMAT
, &tm
) ? buf
: NULL
;
431 #define AUTHOR_VALUES \
437 #define AUTHOR_(name) AUTHOR_##name
440 AUTHOR_DEFAULT
= AUTHOR_FULL
443 static const struct enum_map author_map
[] = {
444 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
450 get_author_initials(const char *author
)
452 static char initials
[AUTHOR_COLS
* 6 + 1];
454 const char *end
= strchr(author
, '\0');
456 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
458 memset(initials
, 0, sizeof(initials
));
459 while (author
< end
) {
463 while (is_initial_sep(*author
))
466 bytes
= utf8_char_length(author
, end
);
467 if (bytes
< sizeof(initials
) - 1 - pos
) {
469 initials
[pos
++] = *author
++;
473 for (i
= pos
; author
< end
&& !is_initial_sep(*author
); author
++) {
474 if (i
< sizeof(initials
) - 1)
475 initials
[i
++] = *author
;
486 argv_from_string(const char *argv
[SIZEOF_ARG
], int *argc
, char *cmd
)
490 while (*cmd
&& *argc
< SIZEOF_ARG
&& (valuelen
= strcspn(cmd
, " \t"))) {
491 bool advance
= cmd
[valuelen
] != 0;
494 argv
[(*argc
)++] = chomp_string(cmd
);
495 cmd
= chomp_string(cmd
+ valuelen
+ advance
);
498 if (*argc
< SIZEOF_ARG
)
500 return *argc
< SIZEOF_ARG
;
504 argv_from_env(const char **argv
, const char *name
)
506 char *env
= argv
? getenv(name
) : NULL
;
511 if (env
&& !argv_from_string(argv
, &argc
, env
))
512 die("Too many arguments in the `%s` environment variable", name
);
517 * Executing external commands.
521 IO_FD
, /* File descriptor based IO. */
522 IO_BG
, /* Execute command in the background. */
523 IO_FG
, /* Execute command with same std{in,out,err}. */
524 IO_RD
, /* Read only fork+exec IO. */
525 IO_WR
, /* Write only fork+exec IO. */
526 IO_AP
, /* Append fork+exec output to file. */
530 enum io_type type
; /* The requested type of pipe. */
531 const char *dir
; /* Directory from which to execute. */
532 pid_t pid
; /* Pipe for reading or writing. */
533 int pipe
; /* Pipe end for reading or writing. */
534 int error
; /* Error status. */
535 const char *argv
[SIZEOF_ARG
]; /* Shell command arguments. */
536 char *buf
; /* Read buffer. */
537 size_t bufalloc
; /* Allocated buffer size. */
538 size_t bufsize
; /* Buffer content size. */
539 char *bufpos
; /* Current buffer position. */
540 unsigned int eof
:1; /* Has end of file been reached. */
544 reset_io(struct io
*io
)
548 io
->buf
= io
->bufpos
= NULL
;
549 io
->bufalloc
= io
->bufsize
= 0;
555 init_io(struct io
*io
, const char *dir
, enum io_type type
)
563 init_io_rd(struct io
*io
, const char *argv
[], const char *dir
,
564 enum format_flags flags
)
566 init_io(io
, dir
, IO_RD
);
567 return format_argv(io
->argv
, argv
, flags
);
571 io_open(struct io
*io
, const char *fmt
, ...)
573 char name
[SIZEOF_STR
] = "";
577 init_io(io
, NULL
, IO_FD
);
580 fits
= vsnprintf(name
, sizeof(name
), fmt
, args
) < sizeof(name
);
584 io
->error
= ENAMETOOLONG
;
587 io
->pipe
= *name
? open(name
, O_RDONLY
) : STDIN_FILENO
;
590 return io
->pipe
!= -1;
594 kill_io(struct io
*io
)
596 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
600 done_io(struct io
*io
)
611 pid_t waiting
= waitpid(pid
, &status
, 0);
616 report("waitpid failed (%s)", strerror(errno
));
620 return waiting
== pid
&&
621 !WIFSIGNALED(status
) &&
623 !WEXITSTATUS(status
);
630 start_io(struct io
*io
)
632 int pipefds
[2] = { -1, -1 };
634 if (io
->type
== IO_FD
)
637 if ((io
->type
== IO_RD
|| io
->type
== IO_WR
) &&
640 else if (io
->type
== IO_AP
)
641 pipefds
[1] = io
->pipe
;
643 if ((io
->pid
= fork())) {
644 if (pipefds
[!(io
->type
== IO_WR
)] != -1)
645 close(pipefds
[!(io
->type
== IO_WR
)]);
647 io
->pipe
= pipefds
[!!(io
->type
== IO_WR
)];
652 if (io
->type
!= IO_FG
) {
653 int devnull
= open("/dev/null", O_RDWR
);
654 int readfd
= io
->type
== IO_WR
? pipefds
[0] : devnull
;
655 int writefd
= (io
->type
== IO_RD
|| io
->type
== IO_AP
)
656 ? pipefds
[1] : devnull
;
658 dup2(readfd
, STDIN_FILENO
);
659 dup2(writefd
, STDOUT_FILENO
);
660 dup2(devnull
, STDERR_FILENO
);
663 if (pipefds
[0] != -1)
665 if (pipefds
[1] != -1)
669 if (io
->dir
&& *io
->dir
&& chdir(io
->dir
) == -1)
670 die("Failed to change directory: %s", strerror(errno
));
672 execvp(io
->argv
[0], (char *const*) io
->argv
);
673 die("Failed to execute program: %s", strerror(errno
));
676 if (pipefds
[!!(io
->type
== IO_WR
)] != -1)
677 close(pipefds
[!!(io
->type
== IO_WR
)]);
682 run_io(struct io
*io
, const char **argv
, const char *dir
, enum io_type type
)
684 init_io(io
, dir
, type
);
685 if (!format_argv(io
->argv
, argv
, FORMAT_NONE
))
691 run_io_do(struct io
*io
)
693 return start_io(io
) && done_io(io
);
697 run_io_bg(const char **argv
)
701 init_io(&io
, NULL
, IO_BG
);
702 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
704 return run_io_do(&io
);
708 run_io_fg(const char **argv
, const char *dir
)
712 init_io(&io
, dir
, IO_FG
);
713 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
715 return run_io_do(&io
);
719 run_io_append(const char **argv
, enum format_flags flags
, int fd
)
723 init_io(&io
, NULL
, IO_AP
);
725 if (format_argv(io
.argv
, argv
, flags
))
726 return run_io_do(&io
);
732 run_io_rd(struct io
*io
, const char **argv
, const char *dir
, enum format_flags flags
)
734 return init_io_rd(io
, argv
, dir
, flags
) && start_io(io
);
738 io_eof(struct io
*io
)
744 io_error(struct io
*io
)
750 io_strerror(struct io
*io
)
752 return strerror(io
->error
);
756 io_can_read(struct io
*io
)
758 struct timeval tv
= { 0, 500 };
762 FD_SET(io
->pipe
, &fds
);
764 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
768 io_read(struct io
*io
, void *buf
, size_t bufsize
)
771 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
773 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
775 else if (readsize
== -1)
777 else if (readsize
== 0)
783 DEFINE_ALLOCATOR(realloc_io_buf
, char, BUFSIZ
)
786 io_get(struct io
*io
, int c
, bool can_read
)
792 if (io
->bufsize
> 0) {
793 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
795 char *line
= io
->bufpos
;
798 io
->bufpos
= eol
+ 1;
799 io
->bufsize
-= io
->bufpos
- line
;
806 io
->bufpos
[io
->bufsize
] = 0;
816 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
817 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
819 if (io
->bufalloc
== io
->bufsize
) {
820 if (!realloc_io_buf(&io
->buf
, io
->bufalloc
, BUFSIZ
))
822 io
->bufalloc
+= BUFSIZ
;
825 io
->bufpos
= io
->buf
;
826 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
829 io
->bufsize
+= readsize
;
834 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
838 while (!io_error(io
) && written
< bufsize
) {
841 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
842 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
850 return written
== bufsize
;
854 io_read_buf(struct io
*io
, char buf
[], size_t bufsize
)
856 char *result
= io_get(io
, '\n', TRUE
);
859 result
= chomp_string(result
);
860 string_ncopy_do(buf
, bufsize
, result
, strlen(result
));
863 return done_io(io
) && result
;
867 run_io_buf(const char **argv
, char buf
[], size_t bufsize
)
871 return run_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
872 && io_read_buf(&io
, buf
, bufsize
);
876 io_load(struct io
*io
, const char *separators
,
877 int (*read_property
)(char *, size_t, char *, size_t))
885 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
890 name
= chomp_string(name
);
891 namelen
= strcspn(name
, separators
);
895 value
= chomp_string(name
+ namelen
+ 1);
896 valuelen
= strlen(value
);
903 state
= read_property(name
, namelen
, value
, valuelen
);
906 if (state
!= ERR
&& io_error(io
))
914 run_io_load(const char **argv
, const char *separators
,
915 int (*read_property
)(char *, size_t, char *, size_t))
919 return init_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
920 ? io_load(&io
, separators
, read_property
) : ERR
;
929 /* XXX: Keep the view request first and in sync with views[]. */ \
930 REQ_GROUP("View switching") \
931 REQ_(VIEW_MAIN, "Show main view"), \
932 REQ_(VIEW_DIFF, "Show diff view"), \
933 REQ_(VIEW_LOG, "Show log view"), \
934 REQ_(VIEW_TREE, "Show tree view"), \
935 REQ_(VIEW_BLOB, "Show blob view"), \
936 REQ_(VIEW_BLAME, "Show blame view"), \
937 REQ_(VIEW_BRANCH, "Show branch view"), \
938 REQ_(VIEW_HELP, "Show help page"), \
939 REQ_(VIEW_PAGER, "Show pager view"), \
940 REQ_(VIEW_STATUS, "Show status view"), \
941 REQ_(VIEW_STAGE, "Show stage view"), \
943 REQ_GROUP("View manipulation") \
944 REQ_(ENTER, "Enter current line and scroll"), \
945 REQ_(NEXT, "Move to next"), \
946 REQ_(PREVIOUS, "Move to previous"), \
947 REQ_(PARENT, "Move to parent"), \
948 REQ_(VIEW_NEXT, "Move focus to next view"), \
949 REQ_(REFRESH, "Reload and refresh"), \
950 REQ_(MAXIMIZE, "Maximize the current view"), \
951 REQ_(VIEW_CLOSE, "Close the current view"), \
952 REQ_(QUIT, "Close all views and quit"), \
954 REQ_GROUP("View specific requests") \
955 REQ_(STATUS_UPDATE, "Update file status"), \
956 REQ_(STATUS_REVERT, "Revert file changes"), \
957 REQ_(STATUS_MERGE, "Merge file using external tool"), \
958 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
960 REQ_GROUP("Cursor navigation") \
961 REQ_(MOVE_UP, "Move cursor one line up"), \
962 REQ_(MOVE_DOWN, "Move cursor one line down"), \
963 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
964 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
965 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
966 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
968 REQ_GROUP("Scrolling") \
969 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
970 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
971 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
972 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
973 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
974 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
976 REQ_GROUP("Searching") \
977 REQ_(SEARCH, "Search the view"), \
978 REQ_(SEARCH_BACK, "Search backwards in the view"), \
979 REQ_(FIND_NEXT, "Find next search match"), \
980 REQ_(FIND_PREV, "Find previous search match"), \
982 REQ_GROUP("Option manipulation") \
983 REQ_(OPTIONS, "Open option menu"), \
984 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
985 REQ_(TOGGLE_DATE, "Toggle date display"), \
986 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
987 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
988 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
989 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
990 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
991 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
994 REQ_(PROMPT, "Bring up the prompt"), \
995 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
996 REQ_(SHOW_VERSION, "Show version information"), \
997 REQ_(STOP_LOADING, "Stop all loading views"), \
998 REQ_(EDIT, "Open in editor"), \
999 REQ_(NONE, "Do nothing")
1002 /* User action requests. */
1004 #define REQ_GROUP(help)
1005 #define REQ_(req, help) REQ_##req
1007 /* Offset all requests to avoid conflicts with ncurses getch values. */
1008 REQ_OFFSET
= KEY_MAX
+ 1,
1015 struct request_info
{
1016 enum request request
;
1022 static const struct request_info req_info
[] = {
1023 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1024 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1031 get_request(const char *name
)
1033 int namelen
= strlen(name
);
1036 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
1037 if (enum_equals(req_info
[i
], name
, namelen
))
1038 return req_info
[i
].request
;
1048 /* Option and state variables. */
1049 static enum date opt_date
= DATE_DEFAULT
;
1050 static enum author opt_author
= AUTHOR_DEFAULT
;
1051 static bool opt_line_number
= FALSE
;
1052 static bool opt_line_graphics
= TRUE
;
1053 static bool opt_rev_graph
= FALSE
;
1054 static bool opt_show_refs
= TRUE
;
1055 static int opt_num_interval
= 5;
1056 static double opt_hscroll
= 0.50;
1057 static double opt_scale_split_view
= 2.0 / 3.0;
1058 static int opt_tab_size
= 8;
1059 static int opt_author_cols
= AUTHOR_COLS
;
1060 static char opt_path
[SIZEOF_STR
] = "";
1061 static char opt_file
[SIZEOF_STR
] = "";
1062 static char opt_ref
[SIZEOF_REF
] = "";
1063 static char opt_head
[SIZEOF_REF
] = "";
1064 static char opt_remote
[SIZEOF_REF
] = "";
1065 static char opt_encoding
[20] = "UTF-8";
1066 static iconv_t opt_iconv_in
= ICONV_NONE
;
1067 static iconv_t opt_iconv_out
= ICONV_NONE
;
1068 static char opt_search
[SIZEOF_STR
] = "";
1069 static char opt_cdup
[SIZEOF_STR
] = "";
1070 static char opt_prefix
[SIZEOF_STR
] = "";
1071 static char opt_git_dir
[SIZEOF_STR
] = "";
1072 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
1073 static char opt_editor
[SIZEOF_STR
] = "";
1074 static FILE *opt_tty
= NULL
;
1076 #define is_initial_commit() (!get_ref_head())
1077 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1078 #define mkdate(time) string_date(time, opt_date)
1082 * Line-oriented content detection.
1086 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1087 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1088 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1089 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1090 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1091 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1092 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1093 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1094 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1095 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1096 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1097 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1098 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1099 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1100 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1101 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1102 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1103 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1104 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1105 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1106 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1107 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1108 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1109 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1110 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1111 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1112 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1113 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1114 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1115 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1116 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1117 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1118 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1119 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1120 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1121 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1122 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1123 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1124 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1125 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1126 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1127 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1128 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1129 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1130 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1131 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1132 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1133 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1134 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1135 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1136 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1137 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1138 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1139 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1140 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1141 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1142 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1145 #define LINE(type, line, fg, bg, attr) \
1153 const char *name
; /* Option name. */
1154 int namelen
; /* Size of option name. */
1155 const char *line
; /* The start of line to match. */
1156 int linelen
; /* Size of string to match. */
1157 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1160 static struct line_info line_info
[] = {
1161 #define LINE(type, line, fg, bg, attr) \
1162 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1167 static enum line_type
1168 get_line_type(const char *line
)
1170 int linelen
= strlen(line
);
1171 enum line_type type
;
1173 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1174 /* Case insensitive search matches Signed-off-by lines better. */
1175 if (linelen
>= line_info
[type
].linelen
&&
1176 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1179 return LINE_DEFAULT
;
1183 get_line_attr(enum line_type type
)
1185 assert(type
< ARRAY_SIZE(line_info
));
1186 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1189 static struct line_info
*
1190 get_line_info(const char *name
)
1192 size_t namelen
= strlen(name
);
1193 enum line_type type
;
1195 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1196 if (enum_equals(line_info
[type
], name
, namelen
))
1197 return &line_info
[type
];
1205 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1206 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1207 enum line_type type
;
1211 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1212 default_bg
= COLOR_BLACK
;
1213 default_fg
= COLOR_WHITE
;
1216 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1217 struct line_info
*info
= &line_info
[type
];
1218 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1219 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1221 init_pair(type
, fg
, bg
);
1226 enum line_type type
;
1229 unsigned int selected
:1;
1230 unsigned int dirty
:1;
1231 unsigned int cleareol
:1;
1232 unsigned int other
:16;
1234 void *data
; /* User data */
1244 enum request request
;
1247 static const struct keybinding default_keybindings
[] = {
1248 /* View switching */
1249 { 'm', REQ_VIEW_MAIN
},
1250 { 'd', REQ_VIEW_DIFF
},
1251 { 'l', REQ_VIEW_LOG
},
1252 { 't', REQ_VIEW_TREE
},
1253 { 'f', REQ_VIEW_BLOB
},
1254 { 'B', REQ_VIEW_BLAME
},
1255 { 'H', REQ_VIEW_BRANCH
},
1256 { 'p', REQ_VIEW_PAGER
},
1257 { 'h', REQ_VIEW_HELP
},
1258 { 'S', REQ_VIEW_STATUS
},
1259 { 'c', REQ_VIEW_STAGE
},
1261 /* View manipulation */
1262 { 'q', REQ_VIEW_CLOSE
},
1263 { KEY_TAB
, REQ_VIEW_NEXT
},
1264 { KEY_RETURN
, REQ_ENTER
},
1265 { KEY_UP
, REQ_PREVIOUS
},
1266 { KEY_DOWN
, REQ_NEXT
},
1267 { 'R', REQ_REFRESH
},
1268 { KEY_F(5), REQ_REFRESH
},
1269 { 'O', REQ_MAXIMIZE
},
1271 /* Cursor navigation */
1272 { 'k', REQ_MOVE_UP
},
1273 { 'j', REQ_MOVE_DOWN
},
1274 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1275 { KEY_END
, REQ_MOVE_LAST_LINE
},
1276 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1277 { ' ', REQ_MOVE_PAGE_DOWN
},
1278 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1279 { 'b', REQ_MOVE_PAGE_UP
},
1280 { '-', REQ_MOVE_PAGE_UP
},
1283 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1284 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1285 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1286 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1287 { 'w', REQ_SCROLL_PAGE_UP
},
1288 { 's', REQ_SCROLL_PAGE_DOWN
},
1291 { '/', REQ_SEARCH
},
1292 { '?', REQ_SEARCH_BACK
},
1293 { 'n', REQ_FIND_NEXT
},
1294 { 'N', REQ_FIND_PREV
},
1298 { 'z', REQ_STOP_LOADING
},
1299 { 'v', REQ_SHOW_VERSION
},
1300 { 'r', REQ_SCREEN_REDRAW
},
1301 { 'o', REQ_OPTIONS
},
1302 { '.', REQ_TOGGLE_LINENO
},
1303 { 'D', REQ_TOGGLE_DATE
},
1304 { 'A', REQ_TOGGLE_AUTHOR
},
1305 { 'g', REQ_TOGGLE_REV_GRAPH
},
1306 { 'F', REQ_TOGGLE_REFS
},
1307 { 'I', REQ_TOGGLE_SORT_ORDER
},
1308 { 'i', REQ_TOGGLE_SORT_FIELD
},
1309 { ':', REQ_PROMPT
},
1310 { 'u', REQ_STATUS_UPDATE
},
1311 { '!', REQ_STATUS_REVERT
},
1312 { 'M', REQ_STATUS_MERGE
},
1313 { '@', REQ_STAGE_NEXT
},
1314 { ',', REQ_PARENT
},
1318 #define KEYMAP_INFO \
1333 #define KEYMAP_(name) KEYMAP_##name
1338 static const struct enum_map keymap_table
[] = {
1339 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1344 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1346 struct keybinding_table
{
1347 struct keybinding
*data
;
1351 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1354 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1356 struct keybinding_table
*table
= &keybindings
[keymap
];
1358 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1360 die("Failed to allocate keybinding");
1361 table
->data
[table
->size
].alias
= key
;
1362 table
->data
[table
->size
++].request
= request
;
1365 /* Looks for a key binding first in the given map, then in the generic map, and
1366 * lastly in the default keybindings. */
1368 get_keybinding(enum keymap keymap
, int key
)
1372 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1373 if (keybindings
[keymap
].data
[i
].alias
== key
)
1374 return keybindings
[keymap
].data
[i
].request
;
1376 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1377 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1378 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1380 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1381 if (default_keybindings
[i
].alias
== key
)
1382 return default_keybindings
[i
].request
;
1384 return (enum request
) key
;
1393 static const struct key key_table
[] = {
1394 { "Enter", KEY_RETURN
},
1396 { "Backspace", KEY_BACKSPACE
},
1398 { "Escape", KEY_ESC
},
1399 { "Left", KEY_LEFT
},
1400 { "Right", KEY_RIGHT
},
1402 { "Down", KEY_DOWN
},
1403 { "Insert", KEY_IC
},
1404 { "Delete", KEY_DC
},
1406 { "Home", KEY_HOME
},
1408 { "PageUp", KEY_PPAGE
},
1409 { "PageDown", KEY_NPAGE
},
1419 { "F10", KEY_F(10) },
1420 { "F11", KEY_F(11) },
1421 { "F12", KEY_F(12) },
1425 get_key_value(const char *name
)
1429 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1430 if (!strcasecmp(key_table
[i
].name
, name
))
1431 return key_table
[i
].value
;
1433 if (strlen(name
) == 1 && isprint(*name
))
1440 get_key_name(int key_value
)
1442 static char key_char
[] = "'X'";
1443 const char *seq
= NULL
;
1446 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1447 if (key_table
[key
].value
== key_value
)
1448 seq
= key_table
[key
].name
;
1452 isprint(key_value
)) {
1453 key_char
[1] = (char) key_value
;
1457 return seq
? seq
: "(no key)";
1461 append_key(char *buf
, size_t *pos
, const struct keybinding
*keybinding
)
1463 const char *sep
= *pos
> 0 ? ", " : "";
1464 const char *keyname
= get_key_name(keybinding
->alias
);
1466 return string_nformat(buf
, BUFSIZ
, pos
, "%s%s", sep
, keyname
);
1470 append_keymap_request_keys(char *buf
, size_t *pos
, enum request request
,
1471 enum keymap keymap
, bool all
)
1475 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1476 if (keybindings
[keymap
].data
[i
].request
== request
) {
1477 if (!append_key(buf
, pos
, &keybindings
[keymap
].data
[i
]))
1487 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1490 get_keys(enum keymap keymap
, enum request request
, bool all
)
1492 static char buf
[BUFSIZ
];
1498 if (!append_keymap_request_keys(buf
, &pos
, request
, keymap
, all
))
1499 return "Too many keybindings!";
1500 if (pos
> 0 && !all
)
1503 if (keymap
!= KEYMAP_GENERIC
) {
1504 /* Only the generic keymap includes the default keybindings when
1505 * listing all keys. */
1509 if (!append_keymap_request_keys(buf
, &pos
, request
, KEYMAP_GENERIC
, all
))
1510 return "Too many keybindings!";
1515 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1516 if (default_keybindings
[i
].request
== request
) {
1517 if (!append_key(buf
, &pos
, &default_keybindings
[i
]))
1518 return "Too many keybindings!";
1527 struct run_request
{
1530 const char *argv
[SIZEOF_ARG
];
1533 static struct run_request
*run_request
;
1534 static size_t run_requests
;
1536 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1539 add_run_request(enum keymap keymap
, int key
, int argc
, const char **argv
)
1541 struct run_request
*req
;
1543 if (argc
>= ARRAY_SIZE(req
->argv
) - 1)
1546 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1549 req
= &run_request
[run_requests
];
1550 req
->keymap
= keymap
;
1552 req
->argv
[0] = NULL
;
1554 if (!format_argv(req
->argv
, argv
, FORMAT_NONE
))
1557 return REQ_NONE
+ ++run_requests
;
1560 static struct run_request
*
1561 get_run_request(enum request request
)
1563 if (request
<= REQ_NONE
)
1565 return &run_request
[request
- REQ_NONE
- 1];
1569 add_builtin_run_requests(void)
1571 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1572 const char *commit
[] = { "git", "commit", NULL
};
1573 const char *gc
[] = { "git", "gc", NULL
};
1580 { KEYMAP_MAIN
, 'C', ARRAY_SIZE(cherry_pick
) - 1, cherry_pick
},
1581 { KEYMAP_STATUS
, 'C', ARRAY_SIZE(commit
) - 1, commit
},
1582 { KEYMAP_GENERIC
, 'G', ARRAY_SIZE(gc
) - 1, gc
},
1586 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1589 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argc
, reqs
[i
].argv
);
1590 if (req
!= REQ_NONE
)
1591 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1596 * User config file handling.
1599 static int config_lineno
;
1600 static bool config_errors
;
1601 static const char *config_msg
;
1603 static const struct enum_map color_map
[] = {
1604 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1616 static const struct enum_map attr_map
[] = {
1617 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1624 ATTR_MAP(UNDERLINE
),
1627 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1629 static int parse_step(double *opt
, const char *arg
)
1632 if (!strchr(arg
, '%'))
1635 /* "Shift down" so 100% and 1 does not conflict. */
1636 *opt
= (*opt
- 1) / 100;
1639 config_msg
= "Step value larger than 100%";
1644 config_msg
= "Invalid step value";
1651 parse_int(int *opt
, const char *arg
, int min
, int max
)
1653 int value
= atoi(arg
);
1655 if (min
<= value
&& value
<= max
) {
1660 config_msg
= "Integer value out of bound";
1665 set_color(int *color
, const char *name
)
1667 if (map_enum(color
, color_map
, name
))
1669 if (!prefixcmp(name
, "color"))
1670 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1674 /* Wants: object fgcolor bgcolor [attribute] */
1676 option_color_command(int argc
, const char *argv
[])
1678 struct line_info
*info
;
1681 config_msg
= "Wrong number of arguments given to color command";
1685 info
= get_line_info(argv
[0]);
1687 static const struct enum_map obsolete
[] = {
1688 ENUM_MAP("main-delim", LINE_DELIMITER
),
1689 ENUM_MAP("main-date", LINE_DATE
),
1690 ENUM_MAP("main-author", LINE_AUTHOR
),
1694 if (!map_enum(&index
, obsolete
, argv
[0])) {
1695 config_msg
= "Unknown color name";
1698 info
= &line_info
[index
];
1701 if (!set_color(&info
->fg
, argv
[1]) ||
1702 !set_color(&info
->bg
, argv
[2])) {
1703 config_msg
= "Unknown color";
1708 while (argc
-- > 3) {
1711 if (!set_attribute(&attr
, argv
[argc
])) {
1712 config_msg
= "Unknown attribute";
1721 static int parse_bool(bool *opt
, const char *arg
)
1723 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1728 static int parse_enum_do(unsigned int *opt
, const char *arg
,
1729 const struct enum_map
*map
, size_t map_size
)
1733 assert(map_size
> 1);
1735 if (map_enum_do(map
, map_size
, (int *) opt
, arg
))
1738 if (parse_bool(&is_true
, arg
) != OK
)
1741 *opt
= is_true
? map
[1].value
: map
[0].value
;
1745 #define parse_enum(opt, arg, map) \
1746 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1749 parse_string(char *opt
, const char *arg
, size_t optsize
)
1751 int arglen
= strlen(arg
);
1756 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1757 config_msg
= "Unmatched quotation";
1760 arg
+= 1; arglen
-= 2;
1762 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1767 /* Wants: name = value */
1769 option_set_command(int argc
, const char *argv
[])
1772 config_msg
= "Wrong number of arguments given to set command";
1776 if (strcmp(argv
[1], "=")) {
1777 config_msg
= "No value assigned";
1781 if (!strcmp(argv
[0], "show-author"))
1782 return parse_enum(&opt_author
, argv
[2], author_map
);
1784 if (!strcmp(argv
[0], "show-date"))
1785 return parse_enum(&opt_date
, argv
[2], date_map
);
1787 if (!strcmp(argv
[0], "show-rev-graph"))
1788 return parse_bool(&opt_rev_graph
, argv
[2]);
1790 if (!strcmp(argv
[0], "show-refs"))
1791 return parse_bool(&opt_show_refs
, argv
[2]);
1793 if (!strcmp(argv
[0], "show-line-numbers"))
1794 return parse_bool(&opt_line_number
, argv
[2]);
1796 if (!strcmp(argv
[0], "line-graphics"))
1797 return parse_bool(&opt_line_graphics
, argv
[2]);
1799 if (!strcmp(argv
[0], "line-number-interval"))
1800 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
1802 if (!strcmp(argv
[0], "author-width"))
1803 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
1805 if (!strcmp(argv
[0], "horizontal-scroll"))
1806 return parse_step(&opt_hscroll
, argv
[2]);
1808 if (!strcmp(argv
[0], "split-view-height"))
1809 return parse_step(&opt_scale_split_view
, argv
[2]);
1811 if (!strcmp(argv
[0], "tab-size"))
1812 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
1814 if (!strcmp(argv
[0], "commit-encoding"))
1815 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
1817 config_msg
= "Unknown variable name";
1821 /* Wants: mode request key */
1823 option_bind_command(int argc
, const char *argv
[])
1825 enum request request
;
1830 config_msg
= "Wrong number of arguments given to bind command";
1834 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
1835 config_msg
= "Unknown key map";
1839 key
= get_key_value(argv
[1]);
1841 config_msg
= "Unknown key";
1845 request
= get_request(argv
[2]);
1846 if (request
== REQ_NONE
) {
1847 static const struct enum_map obsolete
[] = {
1848 ENUM_MAP("cherry-pick", REQ_NONE
),
1849 ENUM_MAP("screen-resize", REQ_NONE
),
1850 ENUM_MAP("tree-parent", REQ_PARENT
),
1854 if (map_enum(&alias
, obsolete
, argv
[2])) {
1855 if (alias
!= REQ_NONE
)
1856 add_keybinding(keymap
, alias
, key
);
1857 config_msg
= "Obsolete request name";
1861 if (request
== REQ_NONE
&& *argv
[2]++ == '!')
1862 request
= add_run_request(keymap
, key
, argc
- 2, argv
+ 2);
1863 if (request
== REQ_NONE
) {
1864 config_msg
= "Unknown request name";
1868 add_keybinding(keymap
, request
, key
);
1874 set_option(const char *opt
, char *value
)
1876 const char *argv
[SIZEOF_ARG
];
1879 if (!argv_from_string(argv
, &argc
, value
)) {
1880 config_msg
= "Too many option arguments";
1884 if (!strcmp(opt
, "color"))
1885 return option_color_command(argc
, argv
);
1887 if (!strcmp(opt
, "set"))
1888 return option_set_command(argc
, argv
);
1890 if (!strcmp(opt
, "bind"))
1891 return option_bind_command(argc
, argv
);
1893 config_msg
= "Unknown option command";
1898 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
1903 config_msg
= "Internal error";
1905 /* Check for comment markers, since read_properties() will
1906 * only ensure opt and value are split at first " \t". */
1907 optlen
= strcspn(opt
, "#");
1911 if (opt
[optlen
] != 0) {
1912 config_msg
= "No option value";
1916 /* Look for comment endings in the value. */
1917 size_t len
= strcspn(value
, "#");
1919 if (len
< valuelen
) {
1921 value
[valuelen
] = 0;
1924 status
= set_option(opt
, value
);
1927 if (status
== ERR
) {
1928 warn("Error on line %d, near '%.*s': %s",
1929 config_lineno
, (int) optlen
, opt
, config_msg
);
1930 config_errors
= TRUE
;
1933 /* Always keep going if errors are encountered. */
1938 load_option_file(const char *path
)
1942 /* It's OK that the file doesn't exist. */
1943 if (!io_open(&io
, "%s", path
))
1947 config_errors
= FALSE
;
1949 if (io_load(&io
, " \t", read_option
) == ERR
||
1950 config_errors
== TRUE
)
1951 warn("Errors while loading %s.", path
);
1957 const char *home
= getenv("HOME");
1958 const char *tigrc_user
= getenv("TIGRC_USER");
1959 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
1960 char buf
[SIZEOF_STR
];
1962 add_builtin_run_requests();
1965 tigrc_system
= SYSCONFDIR
"/tigrc";
1966 load_option_file(tigrc_system
);
1969 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
1973 load_option_file(tigrc_user
);
1986 /* The display array of active views and the index of the current view. */
1987 static struct view
*display
[2];
1988 static unsigned int current_view
;
1990 #define foreach_displayed_view(view, i) \
1991 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1993 #define displayed_views() (display[1] != NULL ? 2 : 1)
1995 /* Current head and commit ID */
1996 static char ref_blob
[SIZEOF_REF
] = "";
1997 static char ref_commit
[SIZEOF_REF
] = "HEAD";
1998 static char ref_head
[SIZEOF_REF
] = "HEAD";
2001 const char *name
; /* View name */
2002 const char *cmd_env
; /* Command line set via environment */
2003 const char *id
; /* Points to either of ref_{head,commit,blob} */
2005 struct view_ops
*ops
; /* View operations */
2007 enum keymap keymap
; /* What keymap does this view have */
2008 bool git_dir
; /* Whether the view requires a git directory. */
2010 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
2011 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
2013 int height
, width
; /* The width and height of the main window */
2014 WINDOW
*win
; /* The main window */
2015 WINDOW
*title
; /* The title window living below the main window */
2018 unsigned long offset
; /* Offset of the window top */
2019 unsigned long yoffset
; /* Offset from the window side. */
2020 unsigned long lineno
; /* Current line number */
2021 unsigned long p_offset
; /* Previous offset of the window top */
2022 unsigned long p_yoffset
;/* Previous offset from the window side */
2023 unsigned long p_lineno
; /* Previous current line number */
2024 bool p_restore
; /* Should the previous position be restored. */
2027 char grep
[SIZEOF_STR
]; /* Search string */
2028 regex_t
*regex
; /* Pre-compiled regexp */
2030 /* If non-NULL, points to the view that opened this view. If this view
2031 * is closed tig will switch back to the parent view. */
2032 struct view
*parent
;
2035 size_t lines
; /* Total number of lines */
2036 struct line
*line
; /* Line index */
2037 unsigned int digits
; /* Number of digits in the lines member. */
2040 struct line
*curline
; /* Line currently being drawn. */
2041 enum line_type curtype
; /* Attribute currently used for drawing. */
2042 unsigned long col
; /* Column when drawing. */
2043 bool has_scrolled
; /* View was scrolled. */
2053 /* What type of content being displayed. Used in the title bar. */
2055 /* Default command arguments. */
2057 /* Open and reads in all view content. */
2058 bool (*open
)(struct view
*view
);
2059 /* Read one line; updates view->line. */
2060 bool (*read
)(struct view
*view
, char *data
);
2061 /* Draw one line; @lineno must be < view->height. */
2062 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
2063 /* Depending on view handle a special requests. */
2064 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
2065 /* Search for regexp in a line. */
2066 bool (*grep
)(struct view
*view
, struct line
*line
);
2068 void (*select
)(struct view
*view
, struct line
*line
);
2069 /* Prepare view for loading */
2070 bool (*prepare
)(struct view
*view
);
2073 static struct view_ops blame_ops
;
2074 static struct view_ops blob_ops
;
2075 static struct view_ops diff_ops
;
2076 static struct view_ops help_ops
;
2077 static struct view_ops log_ops
;
2078 static struct view_ops main_ops
;
2079 static struct view_ops pager_ops
;
2080 static struct view_ops stage_ops
;
2081 static struct view_ops status_ops
;
2082 static struct view_ops tree_ops
;
2083 static struct view_ops branch_ops
;
2085 #define VIEW_STR(name, env, ref, ops, map, git) \
2086 { name, #env, ref, ops, map, git }
2088 #define VIEW_(id, name, ops, git, ref) \
2089 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2092 static struct view views
[] = {
2093 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
2094 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
2095 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
2096 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
2097 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
2098 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
2099 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
2100 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
2101 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
2102 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
2103 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
2106 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2107 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2109 #define foreach_view(view, i) \
2110 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2112 #define view_is_displayed(view) \
2113 (view == display[0] || view == display[1])
2117 set_view_attr(struct view
*view
, enum line_type type
)
2119 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
2120 (void) wattrset(view
->win
, get_line_attr(type
));
2121 wchgat(view
->win
, -1, 0, type
, NULL
);
2122 view
->curtype
= type
;
2127 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
2128 int max_len
, bool use_tilde
)
2130 static char out_buffer
[BUFSIZ
* 2];
2133 int trimmed
= FALSE
;
2134 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2139 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
, opt_tab_size
);
2141 set_view_attr(view
, type
);
2143 if (opt_iconv_out
!= ICONV_NONE
) {
2144 ICONV_CONST
char *inbuf
= (ICONV_CONST
char *) string
;
2145 size_t inlen
= len
+ 1;
2147 char *outbuf
= out_buffer
;
2148 size_t outlen
= sizeof(out_buffer
);
2152 ret
= iconv(opt_iconv_out
, &inbuf
, &inlen
, &outbuf
, &outlen
);
2153 if (ret
!= (size_t) -1) {
2154 string
= out_buffer
;
2155 len
= sizeof(out_buffer
) - outlen
;
2159 waddnstr(view
->win
, string
, len
);
2161 if (trimmed
&& use_tilde
) {
2162 set_view_attr(view
, LINE_DELIMITER
);
2163 waddch(view
->win
, '~');
2171 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
2173 static char space
[] = " ";
2176 spaces
= MIN(max
, spaces
);
2178 while (spaces
> 0) {
2179 int len
= MIN(spaces
, sizeof(space
) - 1);
2181 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
2189 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
2191 view
->col
+= draw_chars(view
, type
, string
, view
->width
+ view
->yoffset
- view
->col
, trim
);
2192 return view
->width
+ view
->yoffset
<= view
->col
;
2196 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
2198 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2199 int max
= view
->width
+ view
->yoffset
- view
->col
;
2205 set_view_attr(view
, type
);
2206 /* Using waddch() instead of waddnstr() ensures that
2207 * they'll be rendered correctly for the cursor line. */
2208 for (i
= skip
; i
< size
; i
++)
2209 waddch(view
->win
, graphic
[i
]);
2212 if (size
< max
&& skip
<= size
)
2213 waddch(view
->win
, ' ');
2216 return view
->width
+ view
->yoffset
<= view
->col
;
2220 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2222 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2226 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2228 col
= draw_space(view
, type
, max
- 1, max
- 1);
2231 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2232 return view
->width
+ view
->yoffset
<= view
->col
;
2236 draw_date(struct view
*view
, struct time
*time
)
2238 const char *date
= time
&& time
->sec
? mkdate(time
) : "";
2239 int cols
= opt_date
== DATE_SHORT
? DATE_SHORT_COLS
: DATE_COLS
;
2241 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2245 draw_author(struct view
*view
, const char *author
)
2247 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5;
2248 bool abbreviate
= opt_author
== AUTHOR_ABBREVIATED
|| !trim
;
2250 if (abbreviate
&& author
)
2251 author
= get_author_initials(author
);
2253 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2257 draw_mode(struct view
*view
, mode_t mode
)
2263 else if (S_ISLNK(mode
))
2265 else if (S_ISGITLINK(mode
))
2267 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2269 else if (S_ISREG(mode
))
2274 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2278 draw_lineno(struct view
*view
, unsigned int lineno
)
2281 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2282 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2284 chtype separator
= opt_line_graphics
? ACS_VLINE
: '|';
2286 lineno
+= view
->offset
+ 1;
2287 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2288 static char fmt
[] = "%1ld";
2290 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2291 if (string_format(number
, fmt
, lineno
))
2295 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2297 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2298 return draw_graphic(view
, LINE_DEFAULT
, &separator
, 1);
2302 draw_view_line(struct view
*view
, unsigned int lineno
)
2305 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2307 assert(view_is_displayed(view
));
2309 if (view
->offset
+ lineno
>= view
->lines
)
2312 line
= &view
->line
[view
->offset
+ lineno
];
2314 wmove(view
->win
, lineno
, 0);
2316 wclrtoeol(view
->win
);
2318 view
->curline
= line
;
2319 view
->curtype
= LINE_NONE
;
2320 line
->selected
= FALSE
;
2321 line
->dirty
= line
->cleareol
= 0;
2324 set_view_attr(view
, LINE_CURSOR
);
2325 line
->selected
= TRUE
;
2326 view
->ops
->select(view
, line
);
2329 return view
->ops
->draw(view
, line
, lineno
);
2333 redraw_view_dirty(struct view
*view
)
2338 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2339 if (view
->offset
+ lineno
>= view
->lines
)
2341 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2344 if (!draw_view_line(view
, lineno
))
2350 wnoutrefresh(view
->win
);
2354 redraw_view_from(struct view
*view
, int lineno
)
2356 assert(0 <= lineno
&& lineno
< view
->height
);
2358 for (; lineno
< view
->height
; lineno
++) {
2359 if (!draw_view_line(view
, lineno
))
2363 wnoutrefresh(view
->win
);
2367 redraw_view(struct view
*view
)
2370 redraw_view_from(view
, 0);
2375 update_view_title(struct view
*view
)
2377 char buf
[SIZEOF_STR
];
2378 char state
[SIZEOF_STR
];
2379 size_t bufpos
= 0, statelen
= 0;
2381 assert(view_is_displayed(view
));
2383 if (view
!= VIEW(REQ_VIEW_STATUS
) && view
->lines
) {
2384 unsigned int view_lines
= view
->offset
+ view
->height
;
2385 unsigned int lines
= view
->lines
2386 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2389 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2398 time_t secs
= time(NULL
) - view
->start_time
;
2400 /* Three git seconds are a long time ... */
2402 string_format_from(state
, &statelen
, " loading %lds", secs
);
2405 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2406 if (*view
->ref
&& bufpos
< view
->width
) {
2407 size_t refsize
= strlen(view
->ref
);
2408 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2410 if (minsize
< view
->width
)
2411 refsize
= view
->width
- minsize
+ 7;
2412 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2415 if (statelen
&& bufpos
< view
->width
) {
2416 string_format_from(buf
, &bufpos
, "%s", state
);
2419 if (view
== display
[current_view
])
2420 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2422 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2424 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2425 wclrtoeol(view
->title
);
2426 wnoutrefresh(view
->title
);
2430 apply_step(double step
, int value
)
2434 value
*= step
+ 0.01;
2435 return value
? value
: 1;
2439 resize_display(void)
2442 struct view
*base
= display
[0];
2443 struct view
*view
= display
[1] ? display
[1] : display
[0];
2445 /* Setup window dimensions */
2447 getmaxyx(stdscr
, base
->height
, base
->width
);
2449 /* Make room for the status window. */
2453 /* Horizontal split. */
2454 view
->width
= base
->width
;
2455 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2456 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2457 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2458 base
->height
-= view
->height
;
2460 /* Make room for the title bar. */
2464 /* Make room for the title bar. */
2469 foreach_displayed_view (view
, i
) {
2471 view
->win
= newwin(view
->height
, 0, offset
, 0);
2473 die("Failed to create %s view", view
->name
);
2475 scrollok(view
->win
, FALSE
);
2477 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2479 die("Failed to create title window");
2482 wresize(view
->win
, view
->height
, view
->width
);
2483 mvwin(view
->win
, offset
, 0);
2484 mvwin(view
->title
, offset
+ view
->height
, 0);
2487 offset
+= view
->height
+ 1;
2492 redraw_display(bool clear
)
2497 foreach_displayed_view (view
, i
) {
2501 update_view_title(view
);
2506 toggle_enum_option_do(unsigned int *opt
, const char *help
,
2507 const struct enum_map
*map
, size_t size
)
2509 *opt
= (*opt
+ 1) % size
;
2510 redraw_display(FALSE
);
2511 report("Displaying %s %s", enum_name(map
[*opt
]), help
);
2514 #define toggle_enum_option(opt, help, map) \
2515 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2517 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2518 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2521 toggle_view_option(bool *option
, const char *help
)
2524 redraw_display(FALSE
);
2525 report("%sabling %s", *option
? "En" : "Dis", help
);
2529 open_option_menu(void)
2531 const struct menu_item menu
[] = {
2532 { '.', "line numbers", &opt_line_number
},
2533 { 'D', "date display", &opt_date
},
2534 { 'A', "author display", &opt_author
},
2535 { 'g', "revision graph display", &opt_rev_graph
},
2536 { 'F', "reference display", &opt_show_refs
},
2541 if (prompt_menu("Toggle option", menu
, &selected
)) {
2542 if (menu
[selected
].data
== &opt_date
)
2544 else if (menu
[selected
].data
== &opt_author
)
2547 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2552 maximize_view(struct view
*view
)
2554 memset(display
, 0, sizeof(display
));
2556 display
[current_view
] = view
;
2558 redraw_display(FALSE
);
2568 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2570 if (lineno
>= view
->lines
)
2571 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2573 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2574 unsigned long half
= view
->height
/ 2;
2577 offset
= lineno
- half
;
2582 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2583 view
->offset
= offset
;
2584 view
->lineno
= lineno
;
2591 /* Scrolling backend */
2593 do_scroll_view(struct view
*view
, int lines
)
2595 bool redraw_current_line
= FALSE
;
2597 /* The rendering expects the new offset. */
2598 view
->offset
+= lines
;
2600 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2603 /* Move current line into the view. */
2604 if (view
->lineno
< view
->offset
) {
2605 view
->lineno
= view
->offset
;
2606 redraw_current_line
= TRUE
;
2607 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2608 view
->lineno
= view
->offset
+ view
->height
- 1;
2609 redraw_current_line
= TRUE
;
2612 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2614 /* Redraw the whole screen if scrolling is pointless. */
2615 if (view
->height
< ABS(lines
)) {
2619 int line
= lines
> 0 ? view
->height
- lines
: 0;
2620 int end
= line
+ ABS(lines
);
2622 scrollok(view
->win
, TRUE
);
2623 wscrl(view
->win
, lines
);
2624 scrollok(view
->win
, FALSE
);
2626 while (line
< end
&& draw_view_line(view
, line
))
2629 if (redraw_current_line
)
2630 draw_view_line(view
, view
->lineno
- view
->offset
);
2631 wnoutrefresh(view
->win
);
2634 view
->has_scrolled
= TRUE
;
2638 /* Scroll frontend */
2640 scroll_view(struct view
*view
, enum request request
)
2644 assert(view_is_displayed(view
));
2647 case REQ_SCROLL_LEFT
:
2648 if (view
->yoffset
== 0) {
2649 report("Cannot scroll beyond the first column");
2652 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2655 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2656 redraw_view_from(view
, 0);
2659 case REQ_SCROLL_RIGHT
:
2660 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2664 case REQ_SCROLL_PAGE_DOWN
:
2665 lines
= view
->height
;
2666 case REQ_SCROLL_LINE_DOWN
:
2667 if (view
->offset
+ lines
> view
->lines
)
2668 lines
= view
->lines
- view
->offset
;
2670 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2671 report("Cannot scroll beyond the last line");
2676 case REQ_SCROLL_PAGE_UP
:
2677 lines
= view
->height
;
2678 case REQ_SCROLL_LINE_UP
:
2679 if (lines
> view
->offset
)
2680 lines
= view
->offset
;
2683 report("Cannot scroll beyond the first line");
2691 die("request %d not handled in switch", request
);
2694 do_scroll_view(view
, lines
);
2699 move_view(struct view
*view
, enum request request
)
2701 int scroll_steps
= 0;
2705 case REQ_MOVE_FIRST_LINE
:
2706 steps
= -view
->lineno
;
2709 case REQ_MOVE_LAST_LINE
:
2710 steps
= view
->lines
- view
->lineno
- 1;
2713 case REQ_MOVE_PAGE_UP
:
2714 steps
= view
->height
> view
->lineno
2715 ? -view
->lineno
: -view
->height
;
2718 case REQ_MOVE_PAGE_DOWN
:
2719 steps
= view
->lineno
+ view
->height
>= view
->lines
2720 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2732 die("request %d not handled in switch", request
);
2735 if (steps
<= 0 && view
->lineno
== 0) {
2736 report("Cannot move beyond the first line");
2739 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2740 report("Cannot move beyond the last line");
2744 /* Move the current line */
2745 view
->lineno
+= steps
;
2746 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2748 /* Check whether the view needs to be scrolled */
2749 if (view
->lineno
< view
->offset
||
2750 view
->lineno
>= view
->offset
+ view
->height
) {
2751 scroll_steps
= steps
;
2752 if (steps
< 0 && -steps
> view
->offset
) {
2753 scroll_steps
= -view
->offset
;
2755 } else if (steps
> 0) {
2756 if (view
->lineno
== view
->lines
- 1 &&
2757 view
->lines
> view
->height
) {
2758 scroll_steps
= view
->lines
- view
->offset
- 1;
2759 if (scroll_steps
>= view
->height
)
2760 scroll_steps
-= view
->height
- 1;
2765 if (!view_is_displayed(view
)) {
2766 view
->offset
+= scroll_steps
;
2767 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2768 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2772 /* Repaint the old "current" line if we be scrolling */
2773 if (ABS(steps
) < view
->height
)
2774 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
2777 do_scroll_view(view
, scroll_steps
);
2781 /* Draw the current line */
2782 draw_view_line(view
, view
->lineno
- view
->offset
);
2784 wnoutrefresh(view
->win
);
2793 static void search_view(struct view
*view
, enum request request
);
2796 grep_text(struct view
*view
, const char *text
[])
2801 for (i
= 0; text
[i
]; i
++)
2803 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
2809 select_view_line(struct view
*view
, unsigned long lineno
)
2811 unsigned long old_lineno
= view
->lineno
;
2812 unsigned long old_offset
= view
->offset
;
2814 if (goto_view_line(view
, view
->offset
, lineno
)) {
2815 if (view_is_displayed(view
)) {
2816 if (old_offset
!= view
->offset
) {
2819 draw_view_line(view
, old_lineno
- view
->offset
);
2820 draw_view_line(view
, view
->lineno
- view
->offset
);
2821 wnoutrefresh(view
->win
);
2824 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2830 find_next(struct view
*view
, enum request request
)
2832 unsigned long lineno
= view
->lineno
;
2837 report("No previous search");
2839 search_view(view
, request
);
2849 case REQ_SEARCH_BACK
:
2858 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
2859 lineno
+= direction
;
2861 /* Note, lineno is unsigned long so will wrap around in which case it
2862 * will become bigger than view->lines. */
2863 for (; lineno
< view
->lines
; lineno
+= direction
) {
2864 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
2865 select_view_line(view
, lineno
);
2866 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
2871 report("No match found for '%s'", view
->grep
);
2875 search_view(struct view
*view
, enum request request
)
2880 regfree(view
->regex
);
2883 view
->regex
= calloc(1, sizeof(*view
->regex
));
2888 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
2889 if (regex_err
!= 0) {
2890 char buf
[SIZEOF_STR
] = "unknown error";
2892 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
2893 report("Search failed: %s", buf
);
2897 string_copy(view
->grep
, opt_search
);
2899 find_next(view
, request
);
2903 * Incremental updating
2907 reset_view(struct view
*view
)
2911 for (i
= 0; i
< view
->lines
; i
++)
2912 free(view
->line
[i
].data
);
2915 view
->p_offset
= view
->offset
;
2916 view
->p_yoffset
= view
->yoffset
;
2917 view
->p_lineno
= view
->lineno
;
2925 view
->update_secs
= 0;
2929 free_argv(const char *argv
[])
2933 for (argc
= 0; argv
[argc
]; argc
++)
2934 free((void *) argv
[argc
]);
2938 format_arg(const char *name
)
2944 const char *value_if_empty
;
2946 #define FORMAT_VAR(name, value, value_if_empty) \
2947 { name, STRING_SIZE(name), value, value_if_empty }
2948 FORMAT_VAR("%(directory)", opt_path
, ""),
2949 FORMAT_VAR("%(file)", opt_file
, ""),
2950 FORMAT_VAR("%(ref)", opt_ref
, "HEAD"),
2951 FORMAT_VAR("%(head)", ref_head
, ""),
2952 FORMAT_VAR("%(commit)", ref_commit
, ""),
2953 FORMAT_VAR("%(blob)", ref_blob
, ""),
2957 for (i
= 0; i
< ARRAY_SIZE(vars
); i
++)
2958 if (!strncmp(name
, vars
[i
].name
, vars
[i
].namelen
))
2959 return *vars
[i
].value
? vars
[i
].value
: vars
[i
].value_if_empty
;
2964 format_argv(const char *dst_argv
[], const char *src_argv
[], enum format_flags flags
)
2966 char buf
[SIZEOF_STR
];
2968 bool noreplace
= flags
== FORMAT_NONE
;
2970 free_argv(dst_argv
);
2972 for (argc
= 0; src_argv
[argc
]; argc
++) {
2973 const char *arg
= src_argv
[argc
];
2977 char *next
= strstr(arg
, "%(");
2978 int len
= next
- arg
;
2981 if (!next
|| noreplace
) {
2982 if (flags
== FORMAT_DASH
&& !strcmp(arg
, "--"))
2988 value
= format_arg(next
);
2991 report("Unknown replacement: `%s`", next
);
2996 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
2999 arg
= next
&& !noreplace
? strchr(next
, ')') + 1 : NULL
;
3002 dst_argv
[argc
] = strdup(buf
);
3003 if (!dst_argv
[argc
])
3007 dst_argv
[argc
] = NULL
;
3009 return src_argv
[argc
] == NULL
;
3013 restore_view_position(struct view
*view
)
3015 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
3018 /* Changing the view position cancels the restoring. */
3019 /* FIXME: Changing back to the first line is not detected. */
3020 if (view
->offset
!= 0 || view
->lineno
!= 0) {
3021 view
->p_restore
= FALSE
;
3025 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
3026 view_is_displayed(view
))
3029 view
->yoffset
= view
->p_yoffset
;
3030 view
->p_restore
= FALSE
;
3036 end_update(struct view
*view
, bool force
)
3040 while (!view
->ops
->read(view
, NULL
))
3044 kill_io(view
->pipe
);
3045 done_io(view
->pipe
);
3050 setup_update(struct view
*view
, const char *vid
)
3053 string_copy_rev(view
->vid
, vid
);
3054 view
->pipe
= &view
->io
;
3055 view
->start_time
= time(NULL
);
3059 prepare_update(struct view
*view
, const char *argv
[], const char *dir
,
3060 enum format_flags flags
)
3063 end_update(view
, TRUE
);
3064 return init_io_rd(&view
->io
, argv
, dir
, flags
);
3068 prepare_update_file(struct view
*view
, const char *name
)
3071 end_update(view
, TRUE
);
3072 return io_open(&view
->io
, "%s/%s", opt_cdup
[0] ? opt_cdup
: ".", name
);
3076 begin_update(struct view
*view
, bool refresh
)
3079 end_update(view
, TRUE
);
3082 if (view
->ops
->prepare
) {
3083 if (!view
->ops
->prepare(view
))
3085 } else if (!init_io_rd(&view
->io
, view
->ops
->argv
, NULL
, FORMAT_ALL
)) {
3089 /* Put the current ref_* value to the view title ref
3090 * member. This is needed by the blob view. Most other
3091 * views sets it automatically after loading because the
3092 * first line is a commit line. */
3093 string_copy_rev(view
->ref
, view
->id
);
3096 if (!start_io(&view
->io
))
3099 setup_update(view
, view
->id
);
3105 update_view(struct view
*view
)
3107 char out_buffer
[BUFSIZ
* 2];
3109 /* Clear the view and redraw everything since the tree sorting
3110 * might have rearranged things. */
3111 bool redraw
= view
->lines
== 0;
3112 bool can_read
= TRUE
;
3117 if (!io_can_read(view
->pipe
)) {
3118 if (view
->lines
== 0 && view_is_displayed(view
)) {
3119 time_t secs
= time(NULL
) - view
->start_time
;
3121 if (secs
> 1 && secs
> view
->update_secs
) {
3122 if (view
->update_secs
== 0)
3124 update_view_title(view
);
3125 view
->update_secs
= secs
;
3131 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
3132 if (opt_iconv_in
!= ICONV_NONE
) {
3133 ICONV_CONST
char *inbuf
= line
;
3134 size_t inlen
= strlen(line
) + 1;
3136 char *outbuf
= out_buffer
;
3137 size_t outlen
= sizeof(out_buffer
);
3141 ret
= iconv(opt_iconv_in
, &inbuf
, &inlen
, &outbuf
, &outlen
);
3142 if (ret
!= (size_t) -1)
3146 if (!view
->ops
->read(view
, line
)) {
3147 report("Allocation failure");
3148 end_update(view
, TRUE
);
3154 unsigned long lines
= view
->lines
;
3157 for (digits
= 0; lines
; digits
++)
3160 /* Keep the displayed view in sync with line number scaling. */
3161 if (digits
!= view
->digits
) {
3162 view
->digits
= digits
;
3163 if (opt_line_number
|| view
== VIEW(REQ_VIEW_BLAME
))
3168 if (io_error(view
->pipe
)) {
3169 report("Failed to read: %s", io_strerror(view
->pipe
));
3170 end_update(view
, TRUE
);
3172 } else if (io_eof(view
->pipe
)) {
3174 end_update(view
, FALSE
);
3177 if (restore_view_position(view
))
3180 if (!view_is_displayed(view
))
3184 redraw_view_from(view
, 0);
3186 redraw_view_dirty(view
);
3188 /* Update the title _after_ the redraw so that if the redraw picks up a
3189 * commit reference in view->ref it'll be available here. */
3190 update_view_title(view
);
3194 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
3196 static struct line
*
3197 add_line_data(struct view
*view
, void *data
, enum line_type type
)
3201 if (!realloc_lines(&view
->line
, view
->lines
, 1))
3204 line
= &view
->line
[view
->lines
++];
3205 memset(line
, 0, sizeof(*line
));
3213 static struct line
*
3214 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
3216 char *data
= text
? strdup(text
) : NULL
;
3218 return data
? add_line_data(view
, data
, type
) : NULL
;
3221 static struct line
*
3222 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3224 char buf
[SIZEOF_STR
];
3227 va_start(args
, fmt
);
3228 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3232 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3240 OPEN_DEFAULT
= 0, /* Use default view switching. */
3241 OPEN_SPLIT
= 1, /* Split current view. */
3242 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3243 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3244 OPEN_PREPARED
= 32, /* Open already prepared command. */
3248 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3250 bool split
= !!(flags
& OPEN_SPLIT
);
3251 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3252 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3253 struct view
*view
= VIEW(request
);
3254 int nviews
= displayed_views();
3255 struct view
*base_view
= display
[0];
3257 if (view
== prev
&& nviews
== 1 && !reload
) {
3258 report("Already in %s view", view
->name
);
3262 if (view
->git_dir
&& !opt_git_dir
[0]) {
3263 report("The %s view is disabled in pager view", view
->name
);
3270 } else if (!nomaximize
) {
3271 /* Maximize the current view. */
3272 memset(display
, 0, sizeof(display
));
3274 display
[current_view
] = view
;
3277 /* No parent signals that this is the first loaded view. */
3278 if (prev
&& view
!= prev
) {
3279 view
->parent
= prev
;
3282 /* Resize the view when switching between split- and full-screen,
3283 * or when switching between two different full-screen views. */
3284 if (nviews
!= displayed_views() ||
3285 (nviews
== 1 && base_view
!= display
[0]))
3288 if (view
->ops
->open
) {
3290 end_update(view
, TRUE
);
3291 if (!view
->ops
->open(view
)) {
3292 report("Failed to load %s view", view
->name
);
3295 restore_view_position(view
);
3297 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3298 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3299 report("Failed to load %s view", view
->name
);
3303 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3304 /* Take the title line into account. */
3305 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3307 /* Scroll the view that was split if the current line is
3308 * outside the new limited view. */
3309 do_scroll_view(prev
, lines
);
3312 if (prev
&& view
!= prev
&& split
&& view_is_displayed(prev
)) {
3313 /* "Blur" the previous view. */
3314 update_view_title(prev
);
3317 if (view
->pipe
&& view
->lines
== 0) {
3318 /* Clear the old view and let the incremental updating refill
3321 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3323 } else if (view_is_displayed(view
)) {
3330 open_external_viewer(const char *argv
[], const char *dir
)
3332 def_prog_mode(); /* save current tty modes */
3333 endwin(); /* restore original tty modes */
3334 run_io_fg(argv
, dir
);
3335 fprintf(stderr
, "Press Enter to continue");
3338 redraw_display(TRUE
);
3342 open_mergetool(const char *file
)
3344 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3346 open_external_viewer(mergetool_argv
, opt_cdup
);
3350 open_editor(const char *file
)
3352 const char *editor_argv
[] = { "vi", file
, NULL
};
3355 editor
= getenv("GIT_EDITOR");
3356 if (!editor
&& *opt_editor
)
3357 editor
= opt_editor
;
3359 editor
= getenv("VISUAL");
3361 editor
= getenv("EDITOR");
3365 editor_argv
[0] = editor
;
3366 open_external_viewer(editor_argv
, opt_cdup
);
3370 open_run_request(enum request request
)
3372 struct run_request
*req
= get_run_request(request
);
3373 const char *argv
[ARRAY_SIZE(req
->argv
)] = { NULL
};
3376 report("Unknown run request");
3380 if (format_argv(argv
, req
->argv
, FORMAT_ALL
))
3381 open_external_viewer(argv
, NULL
);
3386 * User request switch noodle
3390 view_driver(struct view
*view
, enum request request
)
3394 if (request
== REQ_NONE
)
3397 if (request
> REQ_NONE
) {
3398 open_run_request(request
);
3399 /* FIXME: When all views can refresh always do this. */
3400 if (view
== VIEW(REQ_VIEW_STATUS
) ||
3401 view
== VIEW(REQ_VIEW_MAIN
) ||
3402 view
== VIEW(REQ_VIEW_LOG
) ||
3403 view
== VIEW(REQ_VIEW_BRANCH
) ||
3404 view
== VIEW(REQ_VIEW_STAGE
))
3405 request
= REQ_REFRESH
;
3410 if (view
&& view
->lines
) {
3411 request
= view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
3412 if (request
== REQ_NONE
)
3419 case REQ_MOVE_PAGE_UP
:
3420 case REQ_MOVE_PAGE_DOWN
:
3421 case REQ_MOVE_FIRST_LINE
:
3422 case REQ_MOVE_LAST_LINE
:
3423 move_view(view
, request
);
3426 case REQ_SCROLL_LEFT
:
3427 case REQ_SCROLL_RIGHT
:
3428 case REQ_SCROLL_LINE_DOWN
:
3429 case REQ_SCROLL_LINE_UP
:
3430 case REQ_SCROLL_PAGE_DOWN
:
3431 case REQ_SCROLL_PAGE_UP
:
3432 scroll_view(view
, request
);
3435 case REQ_VIEW_BLAME
:
3437 report("No file chosen, press %s to open tree view",
3438 get_key(view
->keymap
, REQ_VIEW_TREE
));
3441 open_view(view
, request
, OPEN_DEFAULT
);
3446 report("No file chosen, press %s to open tree view",
3447 get_key(view
->keymap
, REQ_VIEW_TREE
));
3450 open_view(view
, request
, OPEN_DEFAULT
);
3453 case REQ_VIEW_PAGER
:
3454 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3455 report("No pager content, press %s to run command from prompt",
3456 get_key(view
->keymap
, REQ_PROMPT
));
3459 open_view(view
, request
, OPEN_DEFAULT
);
3462 case REQ_VIEW_STAGE
:
3463 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3464 report("No stage content, press %s to open the status view and choose file",
3465 get_key(view
->keymap
, REQ_VIEW_STATUS
));
3468 open_view(view
, request
, OPEN_DEFAULT
);
3471 case REQ_VIEW_STATUS
:
3472 if (opt_is_inside_work_tree
== FALSE
) {
3473 report("The status view requires a working tree");
3476 open_view(view
, request
, OPEN_DEFAULT
);
3484 case REQ_VIEW_BRANCH
:
3485 open_view(view
, request
, OPEN_DEFAULT
);
3490 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3492 if ((view
== VIEW(REQ_VIEW_DIFF
) &&
3493 view
->parent
== VIEW(REQ_VIEW_MAIN
)) ||
3494 (view
== VIEW(REQ_VIEW_DIFF
) &&
3495 view
->parent
== VIEW(REQ_VIEW_BLAME
)) ||
3496 (view
== VIEW(REQ_VIEW_STAGE
) &&
3497 view
->parent
== VIEW(REQ_VIEW_STATUS
)) ||
3498 (view
== VIEW(REQ_VIEW_BLOB
) &&
3499 view
->parent
== VIEW(REQ_VIEW_TREE
)) ||
3500 (view
== VIEW(REQ_VIEW_MAIN
) &&
3501 view
->parent
== VIEW(REQ_VIEW_BRANCH
))) {
3504 view
= view
->parent
;
3505 line
= view
->lineno
;
3506 move_view(view
, request
);
3507 if (view_is_displayed(view
))
3508 update_view_title(view
);
3509 if (line
!= view
->lineno
)
3510 view
->ops
->request(view
, REQ_ENTER
,
3511 &view
->line
[view
->lineno
]);
3514 move_view(view
, request
);
3520 int nviews
= displayed_views();
3521 int next_view
= (current_view
+ 1) % nviews
;
3523 if (next_view
== current_view
) {
3524 report("Only one view is displayed");
3528 current_view
= next_view
;
3529 /* Blur out the title of the previous view. */
3530 update_view_title(view
);
3535 report("Refreshing is not yet supported for the %s view", view
->name
);
3539 if (displayed_views() == 2)
3540 maximize_view(view
);
3547 case REQ_TOGGLE_LINENO
:
3548 toggle_view_option(&opt_line_number
, "line numbers");
3551 case REQ_TOGGLE_DATE
:
3555 case REQ_TOGGLE_AUTHOR
:
3559 case REQ_TOGGLE_REV_GRAPH
:
3560 toggle_view_option(&opt_rev_graph
, "revision graph display");
3563 case REQ_TOGGLE_REFS
:
3564 toggle_view_option(&opt_show_refs
, "reference display");
3567 case REQ_TOGGLE_SORT_FIELD
:
3568 case REQ_TOGGLE_SORT_ORDER
:
3569 report("Sorting is not yet supported for the %s view", view
->name
);
3573 case REQ_SEARCH_BACK
:
3574 search_view(view
, request
);
3579 find_next(view
, request
);
3582 case REQ_STOP_LOADING
:
3583 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
3586 report("Stopped loading the %s view", view
->name
),
3587 end_update(view
, TRUE
);
3591 case REQ_SHOW_VERSION
:
3592 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3595 case REQ_SCREEN_REDRAW
:
3596 redraw_display(TRUE
);
3600 report("Nothing to edit");
3604 report("Nothing to enter");
3607 case REQ_VIEW_CLOSE
:
3608 /* XXX: Mark closed views by letting view->parent point to the
3609 * view itself. Parents to closed view should never be
3612 view
->parent
->parent
!= view
->parent
) {
3613 maximize_view(view
->parent
);
3614 view
->parent
= view
;
3622 report("Unknown key, press %s for help",
3623 get_key(view
->keymap
, REQ_VIEW_HELP
));
3632 * View backend utilities
3642 const enum sort_field
*fields
;
3643 size_t size
, current
;
3647 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3648 #define get_sort_field(state) ((state).fields[(state).current])
3649 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3652 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3653 int (*compare
)(const void *, const void *))
3656 case REQ_TOGGLE_SORT_FIELD
:
3657 state
->current
= (state
->current
+ 1) % state
->size
;
3660 case REQ_TOGGLE_SORT_ORDER
:
3661 state
->reverse
= !state
->reverse
;
3664 die("Not a sort request");
3667 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3671 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3673 /* Small author cache to reduce memory consumption. It uses binary
3674 * search to lookup or find place to position new entries. No entries
3675 * are ever freed. */
3677 get_author(const char *name
)
3679 static const char **authors
;
3680 static size_t authors_size
;
3681 int from
= 0, to
= authors_size
- 1;
3683 while (from
<= to
) {
3684 size_t pos
= (to
+ from
) / 2;
3685 int cmp
= strcmp(name
, authors
[pos
]);
3688 return authors
[pos
];
3696 if (!realloc_authors(&authors
, authors_size
, 1))
3698 name
= strdup(name
);
3702 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3703 authors
[from
] = name
;
3710 parse_timesec(struct time
*time
, const char *sec
)
3712 time
->sec
= (time_t) atol(sec
);
3716 parse_timezone(struct time
*time
, const char *zone
)
3720 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3721 tz
+= ('0' - zone
[2]) * 60 * 60;
3722 tz
+= ('0' - zone
[3]) * 60;
3723 tz
+= ('0' - zone
[4]);
3732 /* Parse author lines where the name may be empty:
3733 * author <email@address.tld> 1138474660 +0100
3736 parse_author_line(char *ident
, const char **author
, struct time
*time
)
3738 char *nameend
= strchr(ident
, '<');
3739 char *emailend
= strchr(ident
, '>');
3741 if (nameend
&& emailend
)
3742 *nameend
= *emailend
= 0;
3743 ident
= chomp_string(ident
);
3746 ident
= chomp_string(nameend
+ 1);
3751 *author
= get_author(ident
);
3753 /* Parse epoch and timezone */
3754 if (emailend
&& emailend
[1] == ' ') {
3755 char *secs
= emailend
+ 2;
3756 char *zone
= strchr(secs
, ' ');
3758 parse_timesec(time
, secs
);
3760 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
3761 parse_timezone(time
, zone
+ 1);
3766 open_commit_parent_menu(char buf
[SIZEOF_STR
], int *parents
)
3768 char rev
[SIZEOF_REV
];
3769 const char *revlist_argv
[] = {
3770 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev
, NULL
3772 struct menu_item
*items
;
3773 char text
[SIZEOF_STR
];
3777 items
= calloc(*parents
+ 1, sizeof(*items
));
3781 for (i
= 0; i
< *parents
; i
++) {
3782 string_copy_rev(rev
, &buf
[SIZEOF_REV
* i
]);
3783 if (!run_io_buf(revlist_argv
, text
, sizeof(text
)) ||
3784 !(items
[i
].text
= strdup(text
))) {
3792 ok
= prompt_menu("Select parent", items
, parents
);
3794 for (i
= 0; items
[i
].text
; i
++)
3795 free((char *) items
[i
].text
);
3801 select_commit_parent(const char *id
, char rev
[SIZEOF_REV
], const char *path
)
3803 char buf
[SIZEOF_STR
* 4];
3804 const char *revlist_argv
[] = {
3805 "git", "log", "--no-color", "-1",
3806 "--pretty=format:%P", id
, "--", path
, NULL
3810 if (!run_io_buf(revlist_argv
, buf
, sizeof(buf
)) ||
3811 (parents
= strlen(buf
) / 40) < 0) {
3812 report("Failed to get parent information");
3815 } else if (parents
== 0) {
3817 report("Path '%s' does not exist in the parent", path
);
3819 report("The selected commit has no parents");
3823 if (parents
> 1 && !open_commit_parent_menu(buf
, &parents
))
3826 string_copy_rev(rev
, &buf
[41 * parents
]);
3835 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
3837 char text
[SIZEOF_STR
];
3839 if (opt_line_number
&& draw_lineno(view
, lineno
))
3842 string_expand(text
, sizeof(text
), line
->data
, opt_tab_size
);
3843 draw_text(view
, line
->type
, text
, TRUE
);
3848 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
3850 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
3851 char ref
[SIZEOF_STR
];
3853 if (!run_io_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
3856 /* This is the only fatal call, since it can "corrupt" the buffer. */
3857 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
3864 add_pager_refs(struct view
*view
, struct line
*line
)
3866 char buf
[SIZEOF_STR
];
3867 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
3868 struct ref_list
*list
;
3869 size_t bufpos
= 0, i
;
3870 const char *sep
= "Refs: ";
3871 bool is_tag
= FALSE
;
3873 assert(line
->type
== LINE_COMMIT
);
3875 list
= get_ref_list(commit_id
);
3877 if (view
== VIEW(REQ_VIEW_DIFF
))
3878 goto try_add_describe_ref
;
3882 for (i
= 0; i
< list
->size
; i
++) {
3883 struct ref
*ref
= list
->refs
[i
];
3884 const char *fmt
= ref
->tag
? "%s[%s]" :
3885 ref
->remote
? "%s<%s>" : "%s%s";
3887 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
3894 if (!is_tag
&& view
== VIEW(REQ_VIEW_DIFF
)) {
3895 try_add_describe_ref
:
3896 /* Add <tag>-g<commit_id> "fake" reference. */
3897 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
3904 add_line_text(view
, buf
, LINE_PP_REFS
);
3908 pager_read(struct view
*view
, char *data
)
3915 line
= add_line_text(view
, data
, get_line_type(data
));
3919 if (line
->type
== LINE_COMMIT
&&
3920 (view
== VIEW(REQ_VIEW_DIFF
) ||
3921 view
== VIEW(REQ_VIEW_LOG
)))
3922 add_pager_refs(view
, line
);
3928 pager_request(struct view
*view
, enum request request
, struct line
*line
)
3932 if (request
!= REQ_ENTER
)
3935 if (line
->type
== LINE_COMMIT
&&
3936 (view
== VIEW(REQ_VIEW_LOG
) ||
3937 view
== VIEW(REQ_VIEW_PAGER
))) {
3938 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
3942 /* Always scroll the view even if it was split. That way
3943 * you can use Enter to scroll through the log view and
3944 * split open each commit diff. */
3945 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
3947 /* FIXME: A minor workaround. Scrolling the view will call report("")
3948 * but if we are scrolling a non-current view this won't properly
3949 * update the view title. */
3951 update_view_title(view
);
3957 pager_grep(struct view
*view
, struct line
*line
)
3959 const char *text
[] = { line
->data
, NULL
};
3961 return grep_text(view
, text
);
3965 pager_select(struct view
*view
, struct line
*line
)
3967 if (line
->type
== LINE_COMMIT
) {
3968 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
3970 if (view
!= VIEW(REQ_VIEW_PAGER
))
3971 string_copy_rev(view
->ref
, text
);
3972 string_copy_rev(ref_commit
, text
);
3976 static struct view_ops pager_ops
= {
3987 static const char *log_argv
[SIZEOF_ARG
] = {
3988 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3992 log_request(struct view
*view
, enum request request
, struct line
*line
)
3997 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
4000 return pager_request(view
, request
, line
);
4004 static struct view_ops log_ops
= {
4015 static const char *diff_argv
[SIZEOF_ARG
] = {
4016 "git", "show", "--pretty=fuller", "--no-color", "--root",
4017 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4020 static struct view_ops diff_ops
= {
4035 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
4038 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
4042 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
4043 help_keymap_hidden
[keymap
] ? '+' : '-',
4044 enum_name(keymap_table
[keymap
]));
4046 line
->other
= keymap
;
4048 return help_keymap_hidden
[keymap
];
4052 help_open_keymap(struct view
*view
, enum keymap keymap
)
4054 const char *group
= NULL
;
4055 char buf
[SIZEOF_STR
];
4057 bool add_title
= TRUE
;
4060 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
4061 const char *key
= NULL
;
4063 if (req_info
[i
].request
== REQ_NONE
)
4066 if (!req_info
[i
].request
) {
4067 group
= req_info
[i
].help
;
4071 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4075 if (add_title
&& help_open_keymap_title(view
, keymap
))
4080 add_line_text(view
, group
, LINE_HELP_GROUP
);
4084 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4085 enum_name(req_info
[i
]), req_info
[i
].help
);
4088 group
= "External commands:";
4090 for (i
= 0; i
< run_requests
; i
++) {
4091 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4095 if (!req
|| req
->keymap
!= keymap
)
4098 key
= get_key_name(req
->key
);
4100 key
= "(no key defined)";
4102 if (add_title
&& help_open_keymap_title(view
, keymap
))
4105 add_line_text(view
, group
, LINE_HELP_GROUP
);
4109 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4110 if (!string_format_from(buf
, &bufpos
, "%s%s",
4111 argc
? " " : "", req
->argv
[argc
]))
4114 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4119 help_open(struct view
*view
)
4124 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4125 add_line_text(view
, "", LINE_DEFAULT
);
4127 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4128 help_open_keymap(view
, keymap
);
4134 help_request(struct view
*view
, enum request request
, struct line
*line
)
4138 if (line
->type
== LINE_HELP_KEYMAP
) {
4139 help_keymap_hidden
[line
->other
] =
4140 !help_keymap_hidden
[line
->other
];
4141 view
->p_restore
= TRUE
;
4142 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4147 return pager_request(view
, request
, line
);
4151 static struct view_ops help_ops
= {
4167 struct tree_stack_entry
{
4168 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4169 unsigned long lineno
; /* Line number to restore */
4170 char *name
; /* Position of name in opt_path */
4173 /* The top of the path stack. */
4174 static struct tree_stack_entry
*tree_stack
= NULL
;
4175 unsigned long tree_lineno
= 0;
4178 pop_tree_stack_entry(void)
4180 struct tree_stack_entry
*entry
= tree_stack
;
4182 tree_lineno
= entry
->lineno
;
4184 tree_stack
= entry
->prev
;
4189 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4191 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4192 size_t pathlen
= strlen(opt_path
);
4197 entry
->prev
= tree_stack
;
4198 entry
->name
= opt_path
+ pathlen
;
4201 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4202 pop_tree_stack_entry();
4206 /* Move the current line to the first tree entry. */
4208 entry
->lineno
= lineno
;
4211 /* Parse output from git-ls-tree(1):
4213 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4216 #define SIZEOF_TREE_ATTR \
4217 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4219 #define SIZEOF_TREE_MODE \
4220 STRING_SIZE("100644 ")
4222 #define TREE_ID_OFFSET \
4223 STRING_SIZE("100644 blob ")
4226 char id
[SIZEOF_REV
];
4228 struct time time
; /* Date from the author ident. */
4229 const char *author
; /* Author of the commit. */
4234 tree_path(const struct line
*line
)
4236 return ((struct tree_entry
*) line
->data
)->name
;
4240 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4242 if (line1
->type
!= line2
->type
)
4243 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4244 return strcmp(tree_path(line1
), tree_path(line2
));
4247 static const enum sort_field tree_sort_fields
[] = {
4248 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4250 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4253 tree_compare(const void *l1
, const void *l2
)
4255 const struct line
*line1
= (const struct line
*) l1
;
4256 const struct line
*line2
= (const struct line
*) l2
;
4257 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4258 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4260 if (line1
->type
== LINE_TREE_HEAD
)
4262 if (line2
->type
== LINE_TREE_HEAD
)
4265 switch (get_sort_field(tree_sort_state
)) {
4267 return sort_order(tree_sort_state
, timecmp(&entry1
->time
, &entry2
->time
));
4269 case ORDERBY_AUTHOR
:
4270 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4274 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4279 static struct line
*
4280 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4281 const char *mode
, const char *id
)
4283 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4284 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4286 if (!entry
|| !line
) {
4291 strncpy(entry
->name
, path
, strlen(path
));
4293 entry
->mode
= strtoul(mode
, NULL
, 8);
4295 string_copy_rev(entry
->id
, id
);
4301 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4303 static const char *author_name
;
4304 static struct time author_time
;
4306 if (!text
&& *read_date
) {
4311 char *path
= *opt_path
? opt_path
: ".";
4312 /* Find next entry to process */
4313 const char *log_file
[] = {
4314 "git", "log", "--no-color", "--pretty=raw",
4315 "--cc", "--raw", view
->id
, "--", path
, NULL
4320 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4321 report("Tree is empty");
4325 if (!run_io_rd(&io
, log_file
, opt_cdup
, FORMAT_NONE
)) {
4326 report("Failed to load tree data");
4330 done_io(view
->pipe
);
4335 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4336 parse_author_line(text
+ STRING_SIZE("author "),
4337 &author_name
, &author_time
);
4339 } else if (*text
== ':') {
4341 size_t annotated
= 1;
4344 pos
= strchr(text
, '\t');
4348 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4349 text
+= strlen(opt_path
);
4350 pos
= strchr(text
, '/');
4354 for (i
= 1; i
< view
->lines
; i
++) {
4355 struct line
*line
= &view
->line
[i
];
4356 struct tree_entry
*entry
= line
->data
;
4358 annotated
+= !!entry
->author
;
4359 if (entry
->author
|| strcmp(entry
->name
, text
))
4362 entry
->author
= author_name
;
4363 entry
->time
= author_time
;
4368 if (annotated
== view
->lines
)
4369 kill_io(view
->pipe
);
4375 tree_read(struct view
*view
, char *text
)
4377 static bool read_date
= FALSE
;
4378 struct tree_entry
*data
;
4379 struct line
*entry
, *line
;
4380 enum line_type type
;
4381 size_t textlen
= text
? strlen(text
) : 0;
4382 char *path
= text
+ SIZEOF_TREE_ATTR
;
4384 if (read_date
|| !text
)
4385 return tree_read_date(view
, text
, &read_date
);
4387 if (textlen
<= SIZEOF_TREE_ATTR
)
4389 if (view
->lines
== 0 &&
4390 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4393 /* Strip the path part ... */
4395 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4396 size_t striplen
= strlen(opt_path
);
4398 if (pathlen
> striplen
)
4399 memmove(path
, path
+ striplen
,
4400 pathlen
- striplen
+ 1);
4402 /* Insert "link" to parent directory. */
4403 if (view
->lines
== 1 &&
4404 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4408 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4409 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4414 /* Skip "Directory ..." and ".." line. */
4415 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4416 if (tree_compare_entry(line
, entry
) <= 0)
4419 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4423 for (; line
<= entry
; line
++)
4424 line
->dirty
= line
->cleareol
= 1;
4428 if (tree_lineno
> view
->lineno
) {
4429 view
->lineno
= tree_lineno
;
4437 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4439 struct tree_entry
*entry
= line
->data
;
4441 if (line
->type
== LINE_TREE_HEAD
) {
4442 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4445 if (draw_mode(view
, entry
->mode
))
4448 if (opt_author
&& draw_author(view
, entry
->author
))
4451 if (opt_date
&& draw_date(view
, &entry
->time
))
4454 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4462 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4463 int fd
= mkstemp(file
);
4466 report("Failed to create temporary file");
4467 else if (!run_io_append(blob_ops
.argv
, FORMAT_ALL
, fd
))
4468 report("Failed to save blob data to file");
4476 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4478 enum open_flags flags
;
4481 case REQ_VIEW_BLAME
:
4482 if (line
->type
!= LINE_TREE_FILE
) {
4483 report("Blame only supported for files");
4487 string_copy(opt_ref
, view
->vid
);
4491 if (line
->type
!= LINE_TREE_FILE
) {
4492 report("Edit only supported for files");
4493 } else if (!is_head_commit(view
->vid
)) {
4496 open_editor(opt_file
);
4500 case REQ_TOGGLE_SORT_FIELD
:
4501 case REQ_TOGGLE_SORT_ORDER
:
4502 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4507 /* quit view if at top of tree */
4508 return REQ_VIEW_CLOSE
;
4511 line
= &view
->line
[1];
4521 /* Cleanup the stack if the tree view is at a different tree. */
4522 while (!*opt_path
&& tree_stack
)
4523 pop_tree_stack_entry();
4525 switch (line
->type
) {
4527 /* Depending on whether it is a subdirectory or parent link
4528 * mangle the path buffer. */
4529 if (line
== &view
->line
[1] && *opt_path
) {
4530 pop_tree_stack_entry();
4533 const char *basename
= tree_path(line
);
4535 push_tree_stack_entry(basename
, view
->lineno
);
4538 /* Trees and subtrees share the same ID, so they are not not
4539 * unique like blobs. */
4540 flags
= OPEN_RELOAD
;
4541 request
= REQ_VIEW_TREE
;
4544 case LINE_TREE_FILE
:
4545 flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4546 request
= REQ_VIEW_BLOB
;
4553 open_view(view
, request
, flags
);
4554 if (request
== REQ_VIEW_TREE
)
4555 view
->lineno
= tree_lineno
;
4561 tree_grep(struct view
*view
, struct line
*line
)
4563 struct tree_entry
*entry
= line
->data
;
4564 const char *text
[] = {
4566 opt_author
? entry
->author
: "",
4567 opt_date
? mkdate(&entry
->time
) : "",
4571 return grep_text(view
, text
);
4575 tree_select(struct view
*view
, struct line
*line
)
4577 struct tree_entry
*entry
= line
->data
;
4579 if (line
->type
== LINE_TREE_FILE
) {
4580 string_copy_rev(ref_blob
, entry
->id
);
4581 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4583 } else if (line
->type
!= LINE_TREE_DIR
) {
4587 string_copy_rev(view
->ref
, entry
->id
);
4591 tree_prepare(struct view
*view
)
4593 if (view
->lines
== 0 && opt_prefix
[0]) {
4594 char *pos
= opt_prefix
;
4596 while (pos
&& *pos
) {
4597 char *end
= strchr(pos
, '/');
4601 push_tree_stack_entry(pos
, 0);
4609 } else if (strcmp(view
->vid
, view
->id
)) {
4613 return init_io_rd(&view
->io
, view
->ops
->argv
, opt_cdup
, FORMAT_ALL
);
4616 static const char *tree_argv
[SIZEOF_ARG
] = {
4617 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4620 static struct view_ops tree_ops
= {
4633 blob_read(struct view
*view
, char *line
)
4637 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4641 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4648 return pager_request(view
, request
, line
);
4652 static const char *blob_argv
[SIZEOF_ARG
] = {
4653 "git", "cat-file", "blob", "%(blob)", NULL
4656 static struct view_ops blob_ops
= {
4670 * Loading the blame view is a two phase job:
4672 * 1. File content is read either using opt_file from the
4673 * filesystem or using git-cat-file.
4674 * 2. Then blame information is incrementally added by
4675 * reading output from git-blame.
4678 static const char *blame_head_argv
[] = {
4679 "git", "blame", "--incremental", "--", "%(file)", NULL
4682 static const char *blame_ref_argv
[] = {
4683 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4686 static const char *blame_cat_file_argv
[] = {
4687 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4690 struct blame_commit
{
4691 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4692 char title
[128]; /* First line of the commit message. */
4693 const char *author
; /* Author of the commit. */
4694 struct time time
; /* Date from the author ident. */
4695 char filename
[128]; /* Name of file. */
4696 bool has_previous
; /* Was a "previous" line detected. */
4700 struct blame_commit
*commit
;
4701 unsigned long lineno
;
4706 blame_open(struct view
*view
)
4708 char path
[SIZEOF_STR
];
4710 if (!view
->parent
&& *opt_prefix
) {
4711 string_copy(path
, opt_file
);
4712 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4716 if (*opt_ref
|| !io_open(&view
->io
, "%s%s", opt_cdup
, opt_file
)) {
4717 if (!run_io_rd(&view
->io
, blame_cat_file_argv
, opt_cdup
, FORMAT_ALL
))
4721 setup_update(view
, opt_file
);
4722 string_format(view
->ref
, "%s ...", opt_file
);
4727 static struct blame_commit
*
4728 get_blame_commit(struct view
*view
, const char *id
)
4732 for (i
= 0; i
< view
->lines
; i
++) {
4733 struct blame
*blame
= view
->line
[i
].data
;
4738 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4739 return blame
->commit
;
4743 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4746 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4752 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4754 const char *pos
= *posref
;
4757 pos
= strchr(pos
+ 1, ' ');
4758 if (!pos
|| !isdigit(pos
[1]))
4760 *number
= atoi(pos
+ 1);
4761 if (*number
< min
|| *number
> max
)
4768 static struct blame_commit
*
4769 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4771 struct blame_commit
*commit
;
4772 struct blame
*blame
;
4773 const char *pos
= text
+ SIZEOF_REV
- 2;
4774 size_t orig_lineno
= 0;
4778 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4781 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4782 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4783 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4786 commit
= get_blame_commit(view
, text
);
4792 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4795 blame
->commit
= commit
;
4796 blame
->lineno
= orig_lineno
+ group
- 1;
4804 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4807 const char **argv
= *opt_ref
? blame_ref_argv
: blame_head_argv
;
4810 if (view
->lines
== 0 && !view
->parent
)
4811 die("No blame exist for %s", view
->vid
);
4813 if (view
->lines
== 0 || !run_io_rd(&io
, argv
, opt_cdup
, FORMAT_ALL
)) {
4814 report("Failed to load blame data");
4818 done_io(view
->pipe
);
4824 size_t linelen
= strlen(line
);
4825 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
4830 blame
->commit
= NULL
;
4831 strncpy(blame
->text
, line
, linelen
);
4832 blame
->text
[linelen
] = 0;
4833 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
4838 match_blame_header(const char *name
, char **line
)
4840 size_t namelen
= strlen(name
);
4841 bool matched
= !strncmp(name
, *line
, namelen
);
4850 blame_read(struct view
*view
, char *line
)
4852 static struct blame_commit
*commit
= NULL
;
4853 static int blamed
= 0;
4854 static bool read_file
= TRUE
;
4857 return blame_read_file(view
, line
, &read_file
);
4864 string_format(view
->ref
, "%s", view
->vid
);
4865 if (view_is_displayed(view
)) {
4866 update_view_title(view
);
4867 redraw_view_from(view
, 0);
4873 commit
= parse_blame_commit(view
, line
, &blamed
);
4874 string_format(view
->ref
, "%s %2d%%", view
->vid
,
4875 view
->lines
? blamed
* 100 / view
->lines
: 0);
4877 } else if (match_blame_header("author ", &line
)) {
4878 commit
->author
= get_author(line
);
4880 } else if (match_blame_header("author-time ", &line
)) {
4881 parse_timesec(&commit
->time
, line
);
4883 } else if (match_blame_header("author-tz ", &line
)) {
4884 parse_timezone(&commit
->time
, line
);
4886 } else if (match_blame_header("summary ", &line
)) {
4887 string_ncopy(commit
->title
, line
, strlen(line
));
4889 } else if (match_blame_header("previous ", &line
)) {
4890 commit
->has_previous
= TRUE
;
4892 } else if (match_blame_header("filename ", &line
)) {
4893 string_ncopy(commit
->filename
, line
, strlen(line
));
4901 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4903 struct blame
*blame
= line
->data
;
4904 struct time
*time
= NULL
;
4905 const char *id
= NULL
, *author
= NULL
;
4906 char text
[SIZEOF_STR
];
4908 if (blame
->commit
&& *blame
->commit
->filename
) {
4909 id
= blame
->commit
->id
;
4910 author
= blame
->commit
->author
;
4911 time
= &blame
->commit
->time
;
4914 if (opt_date
&& draw_date(view
, time
))
4917 if (opt_author
&& draw_author(view
, author
))
4920 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
4923 if (draw_lineno(view
, lineno
))
4926 string_expand(text
, sizeof(text
), blame
->text
, opt_tab_size
);
4927 draw_text(view
, LINE_DEFAULT
, text
, TRUE
);
4932 check_blame_commit(struct blame
*blame
, bool check_null_id
)
4935 report("Commit data not loaded yet");
4936 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4937 report("No commit exist for the selected line");
4944 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
4946 const char *diff_tree_argv
[] = {
4947 "git", "diff-tree", "-U0", blame
->commit
->id
,
4948 "--", blame
->commit
->filename
, NULL
4951 int parent_lineno
= -1;
4952 int blamed_lineno
= -1;
4955 if (!run_io(&io
, diff_tree_argv
, NULL
, IO_RD
))
4958 while ((line
= io_get(&io
, '\n', TRUE
))) {
4960 char *pos
= strchr(line
, '+');
4962 parent_lineno
= atoi(line
+ 4);
4964 blamed_lineno
= atoi(pos
+ 1);
4966 } else if (*line
== '+' && parent_lineno
!= -1) {
4967 if (blame
->lineno
== blamed_lineno
- 1 &&
4968 !strcmp(blame
->text
, line
+ 1)) {
4969 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
4980 blame_request(struct view
*view
, enum request request
, struct line
*line
)
4982 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4983 struct blame
*blame
= line
->data
;
4986 case REQ_VIEW_BLAME
:
4987 if (check_blame_commit(blame
, TRUE
)) {
4988 string_copy(opt_ref
, blame
->commit
->id
);
4989 string_copy(opt_file
, blame
->commit
->filename
);
4991 view
->lineno
= blame
->lineno
;
4992 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
4997 if (check_blame_commit(blame
, TRUE
) &&
4998 select_commit_parent(blame
->commit
->id
, opt_ref
,
4999 blame
->commit
->filename
)) {
5000 string_copy(opt_file
, blame
->commit
->filename
);
5001 setup_blame_parent_line(view
, blame
);
5002 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5007 if (!check_blame_commit(blame
, FALSE
))
5010 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
5011 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
5014 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
5015 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
5016 const char *diff_index_argv
[] = {
5017 "git", "diff-index", "--root", "--patch-with-stat",
5018 "-C", "-M", "HEAD", "--", view
->vid
, NULL
5021 if (!blame
->commit
->has_previous
) {
5022 diff_index_argv
[1] = "diff";
5023 diff_index_argv
[2] = "--no-color";
5024 diff_index_argv
[6] = "--";
5025 diff_index_argv
[7] = "/dev/null";
5028 if (!prepare_update(diff
, diff_index_argv
, NULL
, FORMAT_DASH
)) {
5029 report("Failed to allocate diff command");
5032 flags
|= OPEN_PREPARED
;
5035 open_view(view
, REQ_VIEW_DIFF
, flags
);
5036 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5037 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
5048 blame_grep(struct view
*view
, struct line
*line
)
5050 struct blame
*blame
= line
->data
;
5051 struct blame_commit
*commit
= blame
->commit
;
5052 const char *text
[] = {
5054 commit
? commit
->title
: "",
5055 commit
? commit
->id
: "",
5056 commit
&& opt_author
? commit
->author
: "",
5057 commit
&& opt_date
? mkdate(&commit
->time
) : "",
5061 return grep_text(view
, text
);
5065 blame_select(struct view
*view
, struct line
*line
)
5067 struct blame
*blame
= line
->data
;
5068 struct blame_commit
*commit
= blame
->commit
;
5073 if (!strcmp(commit
->id
, NULL_ID
))
5074 string_ncopy(ref_commit
, "HEAD", 4);
5076 string_copy_rev(ref_commit
, commit
->id
);
5079 static struct view_ops blame_ops
= {
5095 const char *author
; /* Author of the last commit. */
5096 struct time time
; /* Date of the last activity. */
5097 const struct ref
*ref
; /* Name and commit ID information. */
5100 static const struct ref branch_all
;
5102 static const enum sort_field branch_sort_fields
[] = {
5103 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5105 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5108 branch_compare(const void *l1
, const void *l2
)
5110 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5111 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5113 switch (get_sort_field(branch_sort_state
)) {
5115 return sort_order(branch_sort_state
, timecmp(&branch1
->time
, &branch2
->time
));
5117 case ORDERBY_AUTHOR
:
5118 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5122 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5127 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5129 struct branch
*branch
= line
->data
;
5130 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5132 if (opt_date
&& draw_date(view
, &branch
->time
))
5135 if (opt_author
&& draw_author(view
, branch
->author
))
5138 draw_text(view
, type
, branch
->ref
== &branch_all
? "All branches" : branch
->ref
->name
, TRUE
);
5143 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5145 struct branch
*branch
= line
->data
;
5150 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5153 case REQ_TOGGLE_SORT_FIELD
:
5154 case REQ_TOGGLE_SORT_ORDER
:
5155 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5159 if (branch
->ref
== &branch_all
) {
5160 const char *all_branches_argv
[] = {
5161 "git", "log", "--no-color", "--pretty=raw", "--parents",
5162 "--topo-order", "--all", NULL
5164 struct view
*main_view
= VIEW(REQ_VIEW_MAIN
);
5166 if (!prepare_update(main_view
, all_branches_argv
, NULL
, FORMAT_NONE
)) {
5167 report("Failed to load view of all branches");
5170 open_view(view
, REQ_VIEW_MAIN
, OPEN_PREPARED
| OPEN_SPLIT
);
5172 open_view(view
, REQ_VIEW_MAIN
, OPEN_SPLIT
);
5182 branch_read(struct view
*view
, char *line
)
5184 static char id
[SIZEOF_REV
];
5185 struct branch
*reference
;
5191 switch (get_line_type(line
)) {
5193 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5197 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5198 struct branch
*branch
= view
->line
[i
].data
;
5200 if (strcmp(branch
->ref
->id
, id
))
5203 view
->line
[i
].dirty
= TRUE
;
5205 branch
->author
= reference
->author
;
5206 branch
->time
= reference
->time
;
5210 parse_author_line(line
+ STRING_SIZE("author "),
5211 &branch
->author
, &branch
->time
);
5223 branch_open_visitor(void *data
, const struct ref
*ref
)
5225 struct view
*view
= data
;
5226 struct branch
*branch
;
5228 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5231 branch
= calloc(1, sizeof(*branch
));
5236 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5240 branch_open(struct view
*view
)
5242 const char *branch_log
[] = {
5243 "git", "log", "--no-color", "--pretty=raw",
5244 "--simplify-by-decoration", "--all", NULL
5247 if (!run_io_rd(&view
->io
, branch_log
, NULL
, FORMAT_NONE
)) {
5248 report("Failed to load branch data");
5252 setup_update(view
, view
->id
);
5253 branch_open_visitor(view
, &branch_all
);
5254 foreach_ref(branch_open_visitor
, view
);
5255 view
->p_restore
= TRUE
;
5261 branch_grep(struct view
*view
, struct line
*line
)
5263 struct branch
*branch
= line
->data
;
5264 const char *text
[] = {
5270 return grep_text(view
, text
);
5274 branch_select(struct view
*view
, struct line
*line
)
5276 struct branch
*branch
= line
->data
;
5278 string_copy_rev(view
->ref
, branch
->ref
->id
);
5279 string_copy_rev(ref_commit
, branch
->ref
->id
);
5280 string_copy_rev(ref_head
, branch
->ref
->id
);
5283 static struct view_ops branch_ops
= {
5302 char rev
[SIZEOF_REV
];
5303 char name
[SIZEOF_STR
];
5307 char rev
[SIZEOF_REV
];
5308 char name
[SIZEOF_STR
];
5312 static char status_onbranch
[SIZEOF_STR
];
5313 static struct status stage_status
;
5314 static enum line_type stage_line_type
;
5315 static size_t stage_chunks
;
5316 static int *stage_chunk
;
5318 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5320 /* This should work even for the "On branch" line. */
5322 status_has_none(struct view
*view
, struct line
*line
)
5324 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5327 /* Get fields from the diff line:
5328 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5331 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5333 const char *old_mode
= buf
+ 1;
5334 const char *new_mode
= buf
+ 8;
5335 const char *old_rev
= buf
+ 15;
5336 const char *new_rev
= buf
+ 56;
5337 const char *status
= buf
+ 97;
5340 old_mode
[-1] != ':' ||
5341 new_mode
[-1] != ' ' ||
5342 old_rev
[-1] != ' ' ||
5343 new_rev
[-1] != ' ' ||
5347 file
->status
= *status
;
5349 string_copy_rev(file
->old
.rev
, old_rev
);
5350 string_copy_rev(file
->new.rev
, new_rev
);
5352 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5353 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5355 file
->old
.name
[0] = file
->new.name
[0] = 0;
5361 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5363 struct status
*unmerged
= NULL
;
5367 if (!run_io(&io
, argv
, opt_cdup
, IO_RD
))
5370 add_line_data(view
, NULL
, type
);
5372 while ((buf
= io_get(&io
, 0, TRUE
))) {
5373 struct status
*file
= unmerged
;
5376 file
= calloc(1, sizeof(*file
));
5377 if (!file
|| !add_line_data(view
, file
, type
))
5381 /* Parse diff info part. */
5383 file
->status
= status
;
5385 string_copy(file
->old
.rev
, NULL_ID
);
5387 } else if (!file
->status
|| file
== unmerged
) {
5388 if (!status_get_diff(file
, buf
, strlen(buf
)))
5391 buf
= io_get(&io
, 0, TRUE
);
5395 /* Collapse all modified entries that follow an
5396 * associated unmerged entry. */
5397 if (unmerged
== file
) {
5398 unmerged
->status
= 'U';
5400 } else if (file
->status
== 'U') {
5405 /* Grab the old name for rename/copy. */
5406 if (!*file
->old
.name
&&
5407 (file
->status
== 'R' || file
->status
== 'C')) {
5408 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5410 buf
= io_get(&io
, 0, TRUE
);
5415 /* git-ls-files just delivers a NUL separated list of
5416 * file names similar to the second half of the
5417 * git-diff-* output. */
5418 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5419 if (!*file
->old
.name
)
5420 string_copy(file
->old
.name
, file
->new.name
);
5424 if (io_error(&io
)) {
5430 if (!view
->line
[view
->lines
- 1].data
)
5431 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5437 /* Don't show unmerged entries in the staged section. */
5438 static const char *status_diff_index_argv
[] = {
5439 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5440 "--cached", "-M", "HEAD", NULL
5443 static const char *status_diff_files_argv
[] = {
5444 "git", "diff-files", "-z", NULL
5447 static const char *status_list_other_argv
[] = {
5448 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix
, NULL
5451 static const char *status_list_no_head_argv
[] = {
5452 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5455 static const char *update_index_argv
[] = {
5456 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5459 /* Restore the previous line number to stay in the context or select a
5460 * line with something that can be updated. */
5462 status_restore(struct view
*view
)
5464 if (view
->p_lineno
>= view
->lines
)
5465 view
->p_lineno
= view
->lines
- 1;
5466 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5468 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5471 /* If the above fails, always skip the "On branch" line. */
5472 if (view
->p_lineno
< view
->lines
)
5473 view
->lineno
= view
->p_lineno
;
5477 if (view
->lineno
< view
->offset
)
5478 view
->offset
= view
->lineno
;
5479 else if (view
->offset
+ view
->height
<= view
->lineno
)
5480 view
->offset
= view
->lineno
- view
->height
+ 1;
5482 view
->p_restore
= FALSE
;
5486 status_update_onbranch(void)
5488 static const char *paths
[][2] = {
5489 { "rebase-apply/rebasing", "Rebasing" },
5490 { "rebase-apply/applying", "Applying mailbox" },
5491 { "rebase-apply/", "Rebasing mailbox" },
5492 { "rebase-merge/interactive", "Interactive rebase" },
5493 { "rebase-merge/", "Rebase merge" },
5494 { "MERGE_HEAD", "Merging" },
5495 { "BISECT_LOG", "Bisecting" },
5496 { "HEAD", "On branch" },
5498 char buf
[SIZEOF_STR
];
5502 if (is_initial_commit()) {
5503 string_copy(status_onbranch
, "Initial commit");
5507 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5508 char *head
= opt_head
;
5510 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5511 lstat(buf
, &stat
) < 0)
5517 if (io_open(&io
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5518 io_read_buf(&io
, buf
, sizeof(buf
))) {
5520 if (!prefixcmp(head
, "refs/heads/"))
5521 head
+= STRING_SIZE("refs/heads/");
5525 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5526 string_copy(status_onbranch
, opt_head
);
5530 string_copy(status_onbranch
, "Not currently on any branch");
5533 /* First parse staged info using git-diff-index(1), then parse unstaged
5534 * info using git-diff-files(1), and finally untracked files using
5535 * git-ls-files(1). */
5537 status_open(struct view
*view
)
5541 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5542 status_update_onbranch();
5544 run_io_bg(update_index_argv
);
5546 if (is_initial_commit()) {
5547 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5549 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5553 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5554 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5557 /* Restore the exact position or use the specialized restore
5559 if (!view
->p_restore
)
5560 status_restore(view
);
5565 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5567 struct status
*status
= line
->data
;
5568 enum line_type type
;
5572 switch (line
->type
) {
5573 case LINE_STAT_STAGED
:
5574 type
= LINE_STAT_SECTION
;
5575 text
= "Changes to be committed:";
5578 case LINE_STAT_UNSTAGED
:
5579 type
= LINE_STAT_SECTION
;
5580 text
= "Changed but not updated:";
5583 case LINE_STAT_UNTRACKED
:
5584 type
= LINE_STAT_SECTION
;
5585 text
= "Untracked files:";
5588 case LINE_STAT_NONE
:
5589 type
= LINE_DEFAULT
;
5590 text
= " (no files)";
5593 case LINE_STAT_HEAD
:
5594 type
= LINE_STAT_HEAD
;
5595 text
= status_onbranch
;
5602 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5604 buf
[0] = status
->status
;
5605 if (draw_text(view
, line
->type
, buf
, TRUE
))
5607 type
= LINE_DEFAULT
;
5608 text
= status
->new.name
;
5611 draw_text(view
, type
, text
, TRUE
);
5616 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5618 if (displayed_views() == 2 || display
[current_view
] != view
)
5619 maximize_view(view
);
5620 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5625 status_enter(struct view
*view
, struct line
*line
)
5627 struct status
*status
= line
->data
;
5628 const char *oldpath
= status
? status
->old
.name
: NULL
;
5629 /* Diffs for unmerged entries are empty when passing the new
5630 * path, so leave it empty. */
5631 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5633 enum open_flags split
;
5634 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5636 if (line
->type
== LINE_STAT_NONE
||
5637 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5638 report("No file to diff");
5642 switch (line
->type
) {
5643 case LINE_STAT_STAGED
:
5644 if (is_initial_commit()) {
5645 const char *no_head_diff_argv
[] = {
5646 "git", "diff", "--no-color", "--patch-with-stat",
5647 "--", "/dev/null", newpath
, NULL
5650 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
, FORMAT_DASH
))
5651 return status_load_error(view
, stage
, newpath
);
5653 const char *index_show_argv
[] = {
5654 "git", "diff-index", "--root", "--patch-with-stat",
5655 "-C", "-M", "--cached", "HEAD", "--",
5656 oldpath
, newpath
, NULL
5659 if (!prepare_update(stage
, index_show_argv
, opt_cdup
, FORMAT_DASH
))
5660 return status_load_error(view
, stage
, newpath
);
5664 info
= "Staged changes to %s";
5666 info
= "Staged changes";
5669 case LINE_STAT_UNSTAGED
:
5671 const char *files_show_argv
[] = {
5672 "git", "diff-files", "--root", "--patch-with-stat",
5673 "-C", "-M", "--", oldpath
, newpath
, NULL
5676 if (!prepare_update(stage
, files_show_argv
, opt_cdup
, FORMAT_DASH
))
5677 return status_load_error(view
, stage
, newpath
);
5679 info
= "Unstaged changes to %s";
5681 info
= "Unstaged changes";
5684 case LINE_STAT_UNTRACKED
:
5686 report("No file to show");
5690 if (!suffixcmp(status
->new.name
, -1, "/")) {
5691 report("Cannot display a directory");
5695 if (!prepare_update_file(stage
, newpath
))
5696 return status_load_error(view
, stage
, newpath
);
5697 info
= "Untracked file %s";
5700 case LINE_STAT_HEAD
:
5704 die("line type %d not handled in switch", line
->type
);
5707 split
= view_is_displayed(view
) ? OPEN_SPLIT
: 0;
5708 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5709 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5711 stage_status
= *status
;
5713 memset(&stage_status
, 0, sizeof(stage_status
));
5716 stage_line_type
= line
->type
;
5718 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5725 status_exists(struct status
*status
, enum line_type type
)
5727 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5728 unsigned long lineno
;
5730 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5731 struct line
*line
= &view
->line
[lineno
];
5732 struct status
*pos
= line
->data
;
5734 if (line
->type
!= type
)
5736 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5737 select_view_line(view
, lineno
);
5740 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5741 select_view_line(view
, lineno
);
5751 status_update_prepare(struct io
*io
, enum line_type type
)
5753 const char *staged_argv
[] = {
5754 "git", "update-index", "-z", "--index-info", NULL
5756 const char *others_argv
[] = {
5757 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5761 case LINE_STAT_STAGED
:
5762 return run_io(io
, staged_argv
, opt_cdup
, IO_WR
);
5764 case LINE_STAT_UNSTAGED
:
5765 case LINE_STAT_UNTRACKED
:
5766 return run_io(io
, others_argv
, opt_cdup
, IO_WR
);
5769 die("line type %d not handled in switch", type
);
5775 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5777 char buf
[SIZEOF_STR
];
5781 case LINE_STAT_STAGED
:
5782 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5785 status
->old
.name
, 0))
5789 case LINE_STAT_UNSTAGED
:
5790 case LINE_STAT_UNTRACKED
:
5791 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
5796 die("line type %d not handled in switch", type
);
5799 return io_write(io
, buf
, bufsize
);
5803 status_update_file(struct status
*status
, enum line_type type
)
5808 if (!status_update_prepare(&io
, type
))
5811 result
= status_update_write(&io
, status
, type
);
5812 return done_io(&io
) && result
;
5816 status_update_files(struct view
*view
, struct line
*line
)
5818 char buf
[sizeof(view
->ref
)];
5821 struct line
*pos
= view
->line
+ view
->lines
;
5824 int cursor_y
= -1, cursor_x
= -1;
5826 if (!status_update_prepare(&io
, line
->type
))
5829 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
5832 string_copy(buf
, view
->ref
);
5833 getsyx(cursor_y
, cursor_x
);
5834 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
5835 int almost_done
= file
* 100 / files
;
5837 if (almost_done
> done
) {
5839 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
5841 update_view_title(view
);
5842 setsyx(cursor_y
, cursor_x
);
5845 result
= status_update_write(&io
, line
->data
, line
->type
);
5847 string_copy(view
->ref
, buf
);
5849 return done_io(&io
) && result
;
5853 status_update(struct view
*view
)
5855 struct line
*line
= &view
->line
[view
->lineno
];
5857 assert(view
->lines
);
5860 /* This should work even for the "On branch" line. */
5861 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
5862 report("Nothing to update");
5866 if (!status_update_files(view
, line
+ 1)) {
5867 report("Failed to update file status");
5871 } else if (!status_update_file(line
->data
, line
->type
)) {
5872 report("Failed to update file status");
5880 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
5882 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
5883 if (type
== LINE_STAT_STAGED
) {
5884 report("Cannot revert changes to staged files");
5885 } else if (type
== LINE_STAT_UNTRACKED
) {
5886 report("Cannot revert changes to untracked files");
5887 } else if (has_none
) {
5888 report("Nothing to revert");
5890 report("Cannot revert changes to multiple files");
5893 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5894 char mode
[10] = "100644";
5895 const char *reset_argv
[] = {
5896 "git", "update-index", "--cacheinfo", mode
,
5897 status
->old
.rev
, status
->old
.name
, NULL
5899 const char *checkout_argv
[] = {
5900 "git", "checkout", "--", status
->old
.name
, NULL
5903 if (status
->status
== 'U') {
5904 string_format(mode
, "%5o", status
->old
.mode
);
5906 if (status
->old
.mode
== 0 && status
->new.mode
== 0) {
5907 reset_argv
[2] = "--force-remove";
5908 reset_argv
[3] = status
->old
.name
;
5909 reset_argv
[4] = NULL
;
5912 if (!run_io_fg(reset_argv
, opt_cdup
))
5914 if (status
->old
.mode
== 0 && status
->new.mode
== 0)
5918 return run_io_fg(checkout_argv
, opt_cdup
);
5925 status_request(struct view
*view
, enum request request
, struct line
*line
)
5927 struct status
*status
= line
->data
;
5930 case REQ_STATUS_UPDATE
:
5931 if (!status_update(view
))
5935 case REQ_STATUS_REVERT
:
5936 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
5940 case REQ_STATUS_MERGE
:
5941 if (!status
|| status
->status
!= 'U') {
5942 report("Merging only possible for files with unmerged status ('U').");
5945 open_mergetool(status
->new.name
);
5951 if (status
->status
== 'D') {
5952 report("File has been deleted.");
5956 open_editor(status
->new.name
);
5959 case REQ_VIEW_BLAME
:
5965 /* After returning the status view has been split to
5966 * show the stage view. No further reloading is
5968 return status_enter(view
, line
);
5971 /* Simply reload the view. */
5978 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
5984 status_select(struct view
*view
, struct line
*line
)
5986 struct status
*status
= line
->data
;
5987 char file
[SIZEOF_STR
] = "all files";
5991 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
5994 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
5997 switch (line
->type
) {
5998 case LINE_STAT_STAGED
:
5999 text
= "Press %s to unstage %s for commit";
6002 case LINE_STAT_UNSTAGED
:
6003 text
= "Press %s to stage %s for commit";
6006 case LINE_STAT_UNTRACKED
:
6007 text
= "Press %s to stage %s for addition";
6010 case LINE_STAT_HEAD
:
6011 case LINE_STAT_NONE
:
6012 text
= "Nothing to update";
6016 die("line type %d not handled in switch", line
->type
);
6019 if (status
&& status
->status
== 'U') {
6020 text
= "Press %s to resolve conflict in %s";
6021 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
6024 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
6027 string_format(view
->ref
, text
, key
, file
);
6029 string_copy(opt_file
, status
->new.name
);
6033 status_grep(struct view
*view
, struct line
*line
)
6035 struct status
*status
= line
->data
;
6038 const char buf
[2] = { status
->status
, 0 };
6039 const char *text
[] = { status
->new.name
, buf
, NULL
};
6041 return grep_text(view
, text
);
6047 static struct view_ops status_ops
= {
6060 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
6062 while (line
< end
) {
6063 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
6064 !io_write(io
, "\n", 1))
6067 if (line
->type
== LINE_DIFF_CHUNK
||
6068 line
->type
== LINE_DIFF_HEADER
)
6075 static struct line
*
6076 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
6078 for (; view
->line
< line
; line
--)
6079 if (line
->type
== type
)
6086 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
6088 const char *apply_argv
[SIZEOF_ARG
] = {
6089 "git", "apply", "--whitespace=nowarn", NULL
6091 struct line
*diff_hdr
;
6095 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6100 apply_argv
[argc
++] = "--cached";
6101 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6102 apply_argv
[argc
++] = "-R";
6103 apply_argv
[argc
++] = "-";
6104 apply_argv
[argc
++] = NULL
;
6105 if (!run_io(&io
, apply_argv
, opt_cdup
, IO_WR
))
6108 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6109 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6113 run_io_bg(update_index_argv
);
6115 return chunk
? TRUE
: FALSE
;
6119 stage_update(struct view
*view
, struct line
*line
)
6121 struct line
*chunk
= NULL
;
6123 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6124 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6127 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6128 report("Failed to apply chunk");
6132 } else if (!stage_status
.status
) {
6133 view
= VIEW(REQ_VIEW_STATUS
);
6135 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6136 if (line
->type
== stage_line_type
)
6139 if (!status_update_files(view
, line
+ 1)) {
6140 report("Failed to update files");
6144 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6145 report("Failed to update file");
6153 stage_revert(struct view
*view
, struct line
*line
)
6155 struct line
*chunk
= NULL
;
6157 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6158 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6161 if (!prompt_yesno("Are you sure you want to revert changes?"))
6164 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6165 report("Failed to revert chunk");
6171 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6172 stage_line_type
, FALSE
);
6178 stage_next(struct view
*view
, struct line
*line
)
6182 if (!stage_chunks
) {
6183 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6184 if (line
->type
!= LINE_DIFF_CHUNK
)
6187 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6188 report("Allocation failure");
6192 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6196 for (i
= 0; i
< stage_chunks
; i
++) {
6197 if (stage_chunk
[i
] > view
->lineno
) {
6198 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6199 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6204 report("No next chunk found");
6208 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6211 case REQ_STATUS_UPDATE
:
6212 if (!stage_update(view
, line
))
6216 case REQ_STATUS_REVERT
:
6217 if (!stage_revert(view
, line
))
6221 case REQ_STAGE_NEXT
:
6222 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6223 report("File is untracked; press %s to add",
6224 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6227 stage_next(view
, line
);
6231 if (!stage_status
.new.name
[0])
6233 if (stage_status
.status
== 'D') {
6234 report("File has been deleted.");
6238 open_editor(stage_status
.new.name
);
6242 /* Reload everything ... */
6245 case REQ_VIEW_BLAME
:
6246 if (stage_status
.new.name
[0]) {
6247 string_copy(opt_file
, stage_status
.new.name
);
6253 return pager_request(view
, request
, line
);
6259 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6260 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6262 /* Check whether the staged entry still exists, and close the
6263 * stage view if it doesn't. */
6264 if (!status_exists(&stage_status
, stage_line_type
)) {
6265 status_restore(VIEW(REQ_VIEW_STATUS
));
6266 return REQ_VIEW_CLOSE
;
6269 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6270 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6271 report("Cannot display a directory");
6275 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6276 report("Failed to open file: %s", strerror(errno
));
6280 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6285 static struct view_ops stage_ops
= {
6302 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6303 char title
[128]; /* First line of the commit message. */
6304 const char *author
; /* Author of the commit. */
6305 struct time time
; /* Date from the author ident. */
6306 struct ref_list
*refs
; /* Repository references. */
6307 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6308 size_t graph_size
; /* The width of the graph array. */
6309 bool has_parents
; /* Rewritten --parents seen. */
6312 /* Size of rev graph with no "padding" columns */
6313 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6316 struct rev_graph
*prev
, *next
, *parents
;
6317 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6319 struct commit
*commit
;
6321 unsigned int boundary
:1;
6324 /* Parents of the commit being visualized. */
6325 static struct rev_graph graph_parents
[4];
6327 /* The current stack of revisions on the graph. */
6328 static struct rev_graph graph_stacks
[4] = {
6329 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6330 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6331 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6332 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6336 graph_parent_is_merge(struct rev_graph
*graph
)
6338 return graph
->parents
->size
> 1;
6342 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6344 struct commit
*commit
= graph
->commit
;
6346 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6347 commit
->graph
[commit
->graph_size
++] = symbol
;
6351 clear_rev_graph(struct rev_graph
*graph
)
6353 graph
->boundary
= 0;
6354 graph
->size
= graph
->pos
= 0;
6355 graph
->commit
= NULL
;
6356 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6360 done_rev_graph(struct rev_graph
*graph
)
6362 if (graph_parent_is_merge(graph
) &&
6363 graph
->pos
< graph
->size
- 1 &&
6364 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6365 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6367 graph
->commit
->graph_size
= i
* 2;
6368 while (i
< graph
->next
->size
- 1) {
6369 append_to_rev_graph(graph
, ' ');
6370 append_to_rev_graph(graph
, '\\');
6375 clear_rev_graph(graph
);
6379 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6383 /* "Collapse" duplicate parents lines.
6385 * FIXME: This needs to also update update the drawn graph but
6386 * for now it just serves as a method for pruning graph lines. */
6387 for (i
= 0; i
< graph
->size
; i
++)
6388 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6391 if (graph
->size
< SIZEOF_REVITEMS
) {
6392 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6397 get_rev_graph_symbol(struct rev_graph
*graph
)
6401 if (graph
->boundary
)
6402 symbol
= REVGRAPH_BOUND
;
6403 else if (graph
->parents
->size
== 0)
6404 symbol
= REVGRAPH_INIT
;
6405 else if (graph_parent_is_merge(graph
))
6406 symbol
= REVGRAPH_MERGE
;
6407 else if (graph
->pos
>= graph
->size
)
6408 symbol
= REVGRAPH_BRANCH
;
6410 symbol
= REVGRAPH_COMMIT
;
6416 draw_rev_graph(struct rev_graph
*graph
)
6419 chtype separator
, line
;
6421 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6422 static struct rev_filler fillers
[] = {
6428 chtype symbol
= get_rev_graph_symbol(graph
);
6429 struct rev_filler
*filler
;
6432 fillers
[DEFAULT
].line
= opt_line_graphics
? ACS_VLINE
: '|';
6433 filler
= &fillers
[DEFAULT
];
6435 for (i
= 0; i
< graph
->pos
; i
++) {
6436 append_to_rev_graph(graph
, filler
->line
);
6437 if (graph_parent_is_merge(graph
->prev
) &&
6438 graph
->prev
->pos
== i
)
6439 filler
= &fillers
[RSHARP
];
6441 append_to_rev_graph(graph
, filler
->separator
);
6444 /* Place the symbol for this revision. */
6445 append_to_rev_graph(graph
, symbol
);
6447 if (graph
->prev
->size
> graph
->size
)
6448 filler
= &fillers
[RDIAG
];
6450 filler
= &fillers
[DEFAULT
];
6454 for (; i
< graph
->size
; i
++) {
6455 append_to_rev_graph(graph
, filler
->separator
);
6456 append_to_rev_graph(graph
, filler
->line
);
6457 if (graph_parent_is_merge(graph
->prev
) &&
6458 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6459 filler
= &fillers
[RSHARP
];
6460 if (graph
->prev
->size
> graph
->size
)
6461 filler
= &fillers
[LDIAG
];
6464 if (graph
->prev
->size
> graph
->size
) {
6465 append_to_rev_graph(graph
, filler
->separator
);
6466 if (filler
->line
!= ' ')
6467 append_to_rev_graph(graph
, filler
->line
);
6471 /* Prepare the next rev graph */
6473 prepare_rev_graph(struct rev_graph
*graph
)
6477 /* First, traverse all lines of revisions up to the active one. */
6478 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6479 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6482 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6485 /* Interleave the new revision parent(s). */
6486 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6487 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6489 /* Lastly, put any remaining revisions. */
6490 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6491 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6495 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6497 /* If this is the finalizing update ... */
6499 prepare_rev_graph(graph
);
6501 /* Graph visualization needs a one rev look-ahead,
6502 * so the first update doesn't visualize anything. */
6503 if (!graph
->prev
->commit
)
6506 if (view
->lines
> 2)
6507 view
->line
[view
->lines
- 3].dirty
= 1;
6508 if (view
->lines
> 1)
6509 view
->line
[view
->lines
- 2].dirty
= 1;
6510 draw_rev_graph(graph
->prev
);
6511 done_rev_graph(graph
->prev
->prev
);
6519 static const char *main_argv
[SIZEOF_ARG
] = {
6520 "git", "log", "--no-color", "--pretty=raw", "--parents",
6521 "--topo-order", "%(head)", NULL
6525 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6527 struct commit
*commit
= line
->data
;
6529 if (!commit
->author
)
6532 if (opt_date
&& draw_date(view
, &commit
->time
))
6535 if (opt_author
&& draw_author(view
, commit
->author
))
6538 if (opt_rev_graph
&& commit
->graph_size
&&
6539 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6542 if (opt_show_refs
&& commit
->refs
) {
6545 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6546 struct ref
*ref
= commit
->refs
->refs
[i
];
6547 enum line_type type
;
6550 type
= LINE_MAIN_HEAD
;
6552 type
= LINE_MAIN_LOCAL_TAG
;
6554 type
= LINE_MAIN_TAG
;
6555 else if (ref
->tracked
)
6556 type
= LINE_MAIN_TRACKED
;
6557 else if (ref
->remote
)
6558 type
= LINE_MAIN_REMOTE
;
6560 type
= LINE_MAIN_REF
;
6562 if (draw_text(view
, type
, "[", TRUE
) ||
6563 draw_text(view
, type
, ref
->name
, TRUE
) ||
6564 draw_text(view
, type
, "]", TRUE
))
6567 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6572 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6576 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6578 main_read(struct view
*view
, char *line
)
6580 static struct rev_graph
*graph
= graph_stacks
;
6581 enum line_type type
;
6582 struct commit
*commit
;
6587 if (!view
->lines
&& !view
->parent
)
6588 die("No revisions match the given arguments.");
6589 if (view
->lines
> 0) {
6590 commit
= view
->line
[view
->lines
- 1].data
;
6591 view
->line
[view
->lines
- 1].dirty
= 1;
6592 if (!commit
->author
) {
6595 graph
->commit
= NULL
;
6598 update_rev_graph(view
, graph
);
6600 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6601 clear_rev_graph(&graph_stacks
[i
]);
6605 type
= get_line_type(line
);
6606 if (type
== LINE_COMMIT
) {
6607 commit
= calloc(1, sizeof(struct commit
));
6611 line
+= STRING_SIZE("commit ");
6613 graph
->boundary
= 1;
6617 string_copy_rev(commit
->id
, line
);
6618 commit
->refs
= get_ref_list(commit
->id
);
6619 graph
->commit
= commit
;
6620 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6622 while ((line
= strchr(line
, ' '))) {
6624 push_rev_graph(graph
->parents
, line
);
6625 commit
->has_parents
= TRUE
;
6632 commit
= view
->line
[view
->lines
- 1].data
;
6636 if (commit
->has_parents
)
6638 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6642 parse_author_line(line
+ STRING_SIZE("author "),
6643 &commit
->author
, &commit
->time
);
6644 update_rev_graph(view
, graph
);
6645 graph
= graph
->next
;
6649 /* Fill in the commit title if it has not already been set. */
6650 if (commit
->title
[0])
6653 /* Require titles to start with a non-space character at the
6654 * offset used by git log. */
6655 if (strncmp(line
, " ", 4))
6658 /* Well, if the title starts with a whitespace character,
6659 * try to be forgiving. Otherwise we end up with no title. */
6660 while (isspace(*line
))
6664 /* FIXME: More graceful handling of titles; append "..." to
6665 * shortened titles, etc. */
6667 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6668 view
->line
[view
->lines
- 1].dirty
= 1;
6675 main_request(struct view
*view
, enum request request
, struct line
*line
)
6677 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
6681 open_view(view
, REQ_VIEW_DIFF
, flags
);
6685 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6695 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6700 if (!opt_show_refs
|| !list
)
6703 for (i
= 0; i
< list
->size
; i
++) {
6704 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6712 main_grep(struct view
*view
, struct line
*line
)
6714 struct commit
*commit
= line
->data
;
6715 const char *text
[] = {
6717 opt_author
? commit
->author
: "",
6718 opt_date
? mkdate(&commit
->time
) : "",
6722 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6726 main_select(struct view
*view
, struct line
*line
)
6728 struct commit
*commit
= line
->data
;
6730 string_copy_rev(view
->ref
, commit
->id
);
6731 string_copy_rev(ref_commit
, view
->ref
);
6734 static struct view_ops main_ops
= {
6747 * Unicode / UTF-8 handling
6749 * NOTE: Much of the following code for dealing with Unicode is derived from
6750 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6751 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6755 unicode_width(unsigned long c
, int tab_size
)
6758 (c
<= 0x115f /* Hangul Jamo */
6761 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
6763 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
6764 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
6765 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
6766 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
6767 || (c
>= 0xffe0 && c
<= 0xffe6)
6768 || (c
>= 0x20000 && c
<= 0x2fffd)
6769 || (c
>= 0x30000 && c
<= 0x3fffd)))
6778 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6779 * Illegal bytes are set one. */
6780 static const unsigned char utf8_bytes
[256] = {
6781 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,
6782 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,
6783 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,
6784 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,
6785 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,
6786 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,
6787 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,
6788 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,
6791 static inline unsigned char
6792 utf8_char_length(const char *string
, const char *end
)
6794 int c
= *(unsigned char *) string
;
6796 return utf8_bytes
[c
];
6799 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6800 static inline unsigned long
6801 utf8_to_unicode(const char *string
, size_t length
)
6803 unsigned long unicode
;
6807 unicode
= string
[0];
6810 unicode
= (string
[0] & 0x1f) << 6;
6811 unicode
+= (string
[1] & 0x3f);
6814 unicode
= (string
[0] & 0x0f) << 12;
6815 unicode
+= ((string
[1] & 0x3f) << 6);
6816 unicode
+= (string
[2] & 0x3f);
6819 unicode
= (string
[0] & 0x0f) << 18;
6820 unicode
+= ((string
[1] & 0x3f) << 12);
6821 unicode
+= ((string
[2] & 0x3f) << 6);
6822 unicode
+= (string
[3] & 0x3f);
6825 unicode
= (string
[0] & 0x0f) << 24;
6826 unicode
+= ((string
[1] & 0x3f) << 18);
6827 unicode
+= ((string
[2] & 0x3f) << 12);
6828 unicode
+= ((string
[3] & 0x3f) << 6);
6829 unicode
+= (string
[4] & 0x3f);
6832 unicode
= (string
[0] & 0x01) << 30;
6833 unicode
+= ((string
[1] & 0x3f) << 24);
6834 unicode
+= ((string
[2] & 0x3f) << 18);
6835 unicode
+= ((string
[3] & 0x3f) << 12);
6836 unicode
+= ((string
[4] & 0x3f) << 6);
6837 unicode
+= (string
[5] & 0x3f);
6840 die("Invalid Unicode length");
6843 /* Invalid characters could return the special 0xfffd value but NUL
6844 * should be just as good. */
6845 return unicode
> 0xffff ? 0 : unicode
;
6848 /* Calculates how much of string can be shown within the given maximum width
6849 * and sets trimmed parameter to non-zero value if all of string could not be
6850 * shown. If the reserve flag is TRUE, it will reserve at least one
6851 * trailing character, which can be useful when drawing a delimiter.
6853 * Returns the number of bytes to output from string to satisfy max_width. */
6855 utf8_length(const char **start
, size_t skip
, int *width
, size_t max_width
, int *trimmed
, bool reserve
, int tab_size
)
6857 const char *string
= *start
;
6858 const char *end
= strchr(string
, '\0');
6859 unsigned char last_bytes
= 0;
6860 size_t last_ucwidth
= 0;
6865 while (string
< end
) {
6866 unsigned char bytes
= utf8_char_length(string
, end
);
6868 unsigned long unicode
;
6870 if (string
+ bytes
> end
)
6873 /* Change representation to figure out whether
6874 * it is a single- or double-width character. */
6876 unicode
= utf8_to_unicode(string
, bytes
);
6877 /* FIXME: Graceful handling of invalid Unicode character. */
6881 ucwidth
= unicode_width(unicode
, tab_size
);
6883 skip
-= ucwidth
<= skip
? ucwidth
: skip
;
6887 if (*width
> max_width
) {
6890 if (reserve
&& *width
== max_width
) {
6891 string
-= last_bytes
;
6892 *width
-= last_ucwidth
;
6898 last_bytes
= ucwidth
? bytes
: 0;
6899 last_ucwidth
= ucwidth
;
6902 return string
- *start
;
6910 /* Whether or not the curses interface has been initialized. */
6911 static bool cursed
= FALSE
;
6913 /* Terminal hacks and workarounds. */
6914 static bool use_scroll_redrawwin
;
6915 static bool use_scroll_status_wclear
;
6917 /* The status window is used for polling keystrokes. */
6918 static WINDOW
*status_win
;
6920 /* Reading from the prompt? */
6921 static bool input_mode
= FALSE
;
6923 static bool status_empty
= FALSE
;
6925 /* Update status and title window. */
6927 report(const char *msg
, ...)
6929 struct view
*view
= display
[current_view
];
6935 char buf
[SIZEOF_STR
];
6938 va_start(args
, msg
);
6939 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6940 buf
[sizeof(buf
) - 1] = 0;
6941 buf
[sizeof(buf
) - 2] = '.';
6942 buf
[sizeof(buf
) - 3] = '.';
6943 buf
[sizeof(buf
) - 4] = '.';
6949 if (!status_empty
|| *msg
) {
6952 va_start(args
, msg
);
6954 wmove(status_win
, 0, 0);
6955 if (view
->has_scrolled
&& use_scroll_status_wclear
)
6958 vwprintw(status_win
, msg
, args
);
6959 status_empty
= FALSE
;
6961 status_empty
= TRUE
;
6963 wclrtoeol(status_win
);
6964 wnoutrefresh(status_win
);
6969 update_view_title(view
);
6978 /* Initialize the curses library */
6979 if (isatty(STDIN_FILENO
)) {
6980 cursed
= !!initscr();
6983 /* Leave stdin and stdout alone when acting as a pager. */
6984 opt_tty
= fopen("/dev/tty", "r+");
6986 die("Failed to open /dev/tty");
6987 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
6991 die("Failed to initialize curses");
6993 nonl(); /* Disable conversion and detect newlines from input. */
6994 cbreak(); /* Take input chars one at a time, no wait for \n */
6995 noecho(); /* Don't echo input */
6996 leaveok(stdscr
, FALSE
);
7001 getmaxyx(stdscr
, y
, x
);
7002 status_win
= newwin(1, 0, y
- 1, 0);
7004 die("Failed to create status window");
7006 /* Enable keyboard mapping */
7007 keypad(status_win
, TRUE
);
7008 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
7010 TABSIZE
= opt_tab_size
;
7012 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
7013 if (term
&& !strcmp(term
, "gnome-terminal")) {
7014 /* In the gnome-terminal-emulator, the message from
7015 * scrolling up one line when impossible followed by
7016 * scrolling down one line causes corruption of the
7017 * status line. This is fixed by calling wclear. */
7018 use_scroll_status_wclear
= TRUE
;
7019 use_scroll_redrawwin
= FALSE
;
7021 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
7022 /* No problems with full optimizations in xrvt-(unicode)
7024 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
7027 /* When scrolling in (u)xterm the last line in the
7028 * scrolling direction will update slowly. */
7029 use_scroll_redrawwin
= TRUE
;
7030 use_scroll_status_wclear
= FALSE
;
7035 get_input(int prompt_position
)
7038 int i
, key
, cursor_y
, cursor_x
;
7039 bool loading
= FALSE
;
7041 if (prompt_position
)
7045 foreach_view (view
, i
) {
7047 if (view_is_displayed(view
) && view
->has_scrolled
&&
7048 use_scroll_redrawwin
)
7049 redrawwin(view
->win
);
7050 view
->has_scrolled
= FALSE
;
7055 /* Update the cursor position. */
7056 if (prompt_position
) {
7057 getbegyx(status_win
, cursor_y
, cursor_x
);
7058 cursor_x
= prompt_position
;
7060 view
= display
[current_view
];
7061 getbegyx(view
->win
, cursor_y
, cursor_x
);
7062 cursor_x
= view
->width
- 1;
7063 cursor_y
+= view
->lineno
- view
->offset
;
7065 setsyx(cursor_y
, cursor_x
);
7067 /* Refresh, accept single keystroke of input */
7069 nodelay(status_win
, loading
);
7070 key
= wgetch(status_win
);
7072 /* wgetch() with nodelay() enabled returns ERR when
7073 * there's no input. */
7076 } else if (key
== KEY_RESIZE
) {
7079 getmaxyx(stdscr
, height
, width
);
7081 wresize(status_win
, 1, width
);
7082 mvwin(status_win
, height
- 1, 0);
7083 wnoutrefresh(status_win
);
7085 redraw_display(TRUE
);
7095 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7097 enum input_status status
= INPUT_OK
;
7098 static char buf
[SIZEOF_STR
];
7103 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7106 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7107 wclrtoeol(status_win
);
7109 key
= get_input(pos
+ 1);
7114 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7121 status
= INPUT_CANCEL
;
7125 status
= INPUT_CANCEL
;
7129 if (pos
>= sizeof(buf
)) {
7130 report("Input string too long");
7134 status
= handler(data
, buf
, key
);
7135 if (status
== INPUT_OK
)
7136 buf
[pos
++] = (char) key
;
7140 /* Clear the status window */
7141 status_empty
= FALSE
;
7144 if (status
== INPUT_CANCEL
)
7152 static enum input_status
7153 prompt_yesno_handler(void *data
, char *buf
, int c
)
7155 if (c
== 'y' || c
== 'Y')
7157 if (c
== 'n' || c
== 'N')
7158 return INPUT_CANCEL
;
7163 prompt_yesno(const char *prompt
)
7165 char prompt2
[SIZEOF_STR
];
7167 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7170 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7173 static enum input_status
7174 read_prompt_handler(void *data
, char *buf
, int c
)
7176 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7180 read_prompt(const char *prompt
)
7182 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7185 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7187 enum input_status status
= INPUT_OK
;
7190 while (items
[size
].text
)
7193 while (status
== INPUT_OK
) {
7194 const struct menu_item
*item
= &items
[*selected
];
7198 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7199 prompt
, *selected
+ 1, size
);
7201 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7202 wprintw(status_win
, "%s", item
->text
);
7203 wclrtoeol(status_win
);
7205 key
= get_input(COLS
- 1);
7210 status
= INPUT_STOP
;
7215 *selected
= *selected
- 1;
7217 *selected
= size
- 1;
7222 *selected
= (*selected
+ 1) % size
;
7226 status
= INPUT_CANCEL
;
7230 for (i
= 0; items
[i
].text
; i
++)
7231 if (items
[i
].hotkey
== key
) {
7233 status
= INPUT_STOP
;
7239 /* Clear the status window */
7240 status_empty
= FALSE
;
7243 return status
!= INPUT_CANCEL
;
7247 * Repository properties
7250 static struct ref
**refs
= NULL
;
7251 static size_t refs_size
= 0;
7252 static struct ref
*refs_head
= NULL
;
7254 static struct ref_list
**ref_lists
= NULL
;
7255 static size_t ref_lists_size
= 0;
7257 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7258 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7259 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7262 compare_refs(const void *ref1_
, const void *ref2_
)
7264 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7265 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7267 if (ref1
->tag
!= ref2
->tag
)
7268 return ref2
->tag
- ref1
->tag
;
7269 if (ref1
->ltag
!= ref2
->ltag
)
7270 return ref2
->ltag
- ref2
->ltag
;
7271 if (ref1
->head
!= ref2
->head
)
7272 return ref2
->head
- ref1
->head
;
7273 if (ref1
->tracked
!= ref2
->tracked
)
7274 return ref2
->tracked
- ref1
->tracked
;
7275 if (ref1
->remote
!= ref2
->remote
)
7276 return ref2
->remote
- ref1
->remote
;
7277 return strcmp(ref1
->name
, ref2
->name
);
7281 foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
)
7285 for (i
= 0; i
< refs_size
; i
++)
7286 if (!visitor(data
, refs
[i
]))
7296 static struct ref_list
*
7297 get_ref_list(const char *id
)
7299 struct ref_list
*list
;
7302 for (i
= 0; i
< ref_lists_size
; i
++)
7303 if (!strcmp(id
, ref_lists
[i
]->id
))
7304 return ref_lists
[i
];
7306 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7308 list
= calloc(1, sizeof(*list
));
7312 for (i
= 0; i
< refs_size
; i
++) {
7313 if (!strcmp(id
, refs
[i
]->id
) &&
7314 realloc_refs_list(&list
->refs
, list
->size
, 1))
7315 list
->refs
[list
->size
++] = refs
[i
];
7323 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7324 ref_lists
[ref_lists_size
++] = list
;
7329 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
7331 struct ref
*ref
= NULL
;
7334 bool remote
= FALSE
;
7335 bool tracked
= FALSE
;
7337 int from
= 0, to
= refs_size
- 1;
7339 if (!prefixcmp(name
, "refs/tags/")) {
7340 if (!suffixcmp(name
, namelen
, "^{}")) {
7348 namelen
-= STRING_SIZE("refs/tags/");
7349 name
+= STRING_SIZE("refs/tags/");
7351 } else if (!prefixcmp(name
, "refs/remotes/")) {
7353 namelen
-= STRING_SIZE("refs/remotes/");
7354 name
+= STRING_SIZE("refs/remotes/");
7355 tracked
= !strcmp(opt_remote
, name
);
7357 } else if (!prefixcmp(name
, "refs/heads/")) {
7358 namelen
-= STRING_SIZE("refs/heads/");
7359 name
+= STRING_SIZE("refs/heads/");
7360 if (!strncmp(opt_head
, name
, namelen
))
7363 } else if (!strcmp(name
, "HEAD")) {
7366 namelen
= strlen(opt_head
);
7371 /* If we are reloading or it's an annotated tag, replace the
7372 * previous SHA1 with the resolved commit id; relies on the fact
7373 * git-ls-remote lists the commit id of an annotated tag right
7374 * before the commit id it points to. */
7375 while (from
<= to
) {
7376 size_t pos
= (to
+ from
) / 2;
7377 int cmp
= strcmp(name
, refs
[pos
]->name
);
7391 if (!realloc_refs(&refs
, refs_size
, 1))
7393 ref
= calloc(1, sizeof(*ref
) + namelen
);
7396 memmove(refs
+ from
+ 1, refs
+ from
,
7397 (refs_size
- from
) * sizeof(*refs
));
7399 strncpy(ref
->name
, name
, namelen
);
7406 ref
->remote
= remote
;
7407 ref
->tracked
= tracked
;
7408 string_copy_rev(ref
->id
, id
);
7418 const char *head_argv
[] = {
7419 "git", "symbolic-ref", "HEAD", NULL
7421 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7422 "git", "ls-remote", opt_git_dir
, NULL
7424 static bool init
= FALSE
;
7428 argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE");
7435 if (run_io_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7436 !prefixcmp(opt_head
, "refs/heads/")) {
7437 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7439 memmove(opt_head
, offset
, strlen(offset
) + 1);
7443 for (i
= 0; i
< refs_size
; i
++)
7446 if (run_io_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7449 /* Update the ref lists to reflect changes. */
7450 for (i
= 0; i
< ref_lists_size
; i
++) {
7451 struct ref_list
*list
= ref_lists
[i
];
7454 for (old
= new = 0; old
< list
->size
; old
++)
7455 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7456 list
->refs
[new++] = list
->refs
[old
];
7464 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7466 if (!strcmp(name
, ".remote")) {
7467 string_ncopy(opt_remote
, value
, valuelen
);
7469 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7470 size_t from
= strlen(opt_remote
);
7472 if (!prefixcmp(value
, "refs/heads/"))
7473 value
+= STRING_SIZE("refs/heads/");
7475 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7481 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7483 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7484 int argc
= 1 + (cmd
== option_set_command
);
7487 if (!argv_from_string(argv
, &argc
, value
))
7488 config_msg
= "Too many option arguments";
7490 error
= cmd(argc
, argv
);
7493 warn("Option 'tig.%s': %s", name
, config_msg
);
7497 set_environment_variable(const char *name
, const char *value
)
7499 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7500 char *env
= malloc(len
);
7503 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7511 set_work_tree(const char *value
)
7513 char cwd
[SIZEOF_STR
];
7515 if (!getcwd(cwd
, sizeof(cwd
)))
7516 die("Failed to get cwd path: %s", strerror(errno
));
7517 if (chdir(opt_git_dir
) < 0)
7518 die("Failed to chdir(%s): %s", strerror(errno
));
7519 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7520 die("Failed to get git path: %s", strerror(errno
));
7522 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7523 if (chdir(value
) < 0)
7524 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7525 if (!getcwd(cwd
, sizeof(cwd
)))
7526 die("Failed to get cwd path: %s", strerror(errno
));
7527 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7528 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7529 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7530 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7531 opt_is_inside_work_tree
= TRUE
;
7535 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7537 if (!strcmp(name
, "i18n.commitencoding"))
7538 string_ncopy(opt_encoding
, value
, valuelen
);
7540 else if (!strcmp(name
, "core.editor"))
7541 string_ncopy(opt_editor
, value
, valuelen
);
7543 else if (!strcmp(name
, "core.worktree"))
7544 set_work_tree(value
);
7546 else if (!prefixcmp(name
, "tig.color."))
7547 set_repo_config_option(name
+ 10, value
, option_color_command
);
7549 else if (!prefixcmp(name
, "tig.bind."))
7550 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7552 else if (!prefixcmp(name
, "tig."))
7553 set_repo_config_option(name
+ 4, value
, option_set_command
);
7555 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7556 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7557 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7563 load_git_config(void)
7565 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7567 return run_io_load(config_list_argv
, "=", read_repo_config_option
);
7571 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7573 if (!opt_git_dir
[0]) {
7574 string_ncopy(opt_git_dir
, name
, namelen
);
7576 } else if (opt_is_inside_work_tree
== -1) {
7577 /* This can be 3 different values depending on the
7578 * version of git being used. If git-rev-parse does not
7579 * understand --is-inside-work-tree it will simply echo
7580 * the option else either "true" or "false" is printed.
7581 * Default to true for the unknown case. */
7582 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7584 } else if (*name
== '.') {
7585 string_ncopy(opt_cdup
, name
, namelen
);
7588 string_ncopy(opt_prefix
, name
, namelen
);
7595 load_repo_info(void)
7597 const char *rev_parse_argv
[] = {
7598 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7599 "--show-cdup", "--show-prefix", NULL
7602 return run_io_load(rev_parse_argv
, "=", read_repo_info
);
7610 static const char usage
[] =
7611 "tig " TIG_VERSION
" (" __DATE__
")\n"
7613 "Usage: tig [options] [revs] [--] [paths]\n"
7614 " or: tig show [options] [revs] [--] [paths]\n"
7615 " or: tig blame [rev] path\n"
7617 " or: tig < [git command output]\n"
7620 " -v, --version Show version and exit\n"
7621 " -h, --help Show help message and exit";
7623 static void __NORETURN
7626 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7632 static void __NORETURN
7633 die(const char *err
, ...)
7639 va_start(args
, err
);
7640 fputs("tig: ", stderr
);
7641 vfprintf(stderr
, err
, args
);
7642 fputs("\n", stderr
);
7649 warn(const char *msg
, ...)
7653 va_start(args
, msg
);
7654 fputs("tig warning: ", stderr
);
7655 vfprintf(stderr
, msg
, args
);
7656 fputs("\n", stderr
);
7661 parse_options(int argc
, const char *argv
[])
7663 enum request request
= REQ_VIEW_MAIN
;
7664 const char *subcommand
;
7665 bool seen_dashdash
= FALSE
;
7666 /* XXX: This is vulnerable to the user overriding options
7667 * required for the main view parser. */
7668 const char *custom_argv
[SIZEOF_ARG
] = {
7669 "git", "log", "--no-color", "--pretty=raw", "--parents",
7670 "--topo-order", NULL
7674 if (!isatty(STDIN_FILENO
)) {
7675 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7676 return REQ_VIEW_PAGER
;
7682 subcommand
= argv
[1];
7683 if (!strcmp(subcommand
, "status")) {
7685 warn("ignoring arguments after `%s'", subcommand
);
7686 return REQ_VIEW_STATUS
;
7688 } else if (!strcmp(subcommand
, "blame")) {
7689 if (argc
<= 2 || argc
> 4)
7690 die("invalid number of options to blame\n\n%s", usage
);
7694 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7698 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7699 return REQ_VIEW_BLAME
;
7701 } else if (!strcmp(subcommand
, "show")) {
7702 request
= REQ_VIEW_DIFF
;
7709 custom_argv
[1] = subcommand
;
7713 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7714 const char *opt
= argv
[i
];
7716 if (seen_dashdash
|| !strcmp(opt
, "--")) {
7717 seen_dashdash
= TRUE
;
7719 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7720 printf("tig version %s\n", TIG_VERSION
);
7723 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7724 printf("%s\n", usage
);
7728 custom_argv
[j
++] = opt
;
7729 if (j
>= ARRAY_SIZE(custom_argv
))
7730 die("command too long");
7733 if (!prepare_update(VIEW(request
), custom_argv
, NULL
, FORMAT_NONE
))
7734 die("Failed to format arguments");
7740 main(int argc
, const char *argv
[])
7742 const char *codeset
= "UTF-8";
7743 enum request request
= parse_options(argc
, argv
);
7747 signal(SIGINT
, quit
);
7748 signal(SIGPIPE
, SIG_IGN
);
7750 if (setlocale(LC_ALL
, "")) {
7751 codeset
= nl_langinfo(CODESET
);
7754 if (load_repo_info() == ERR
)
7755 die("Failed to load repo info.");
7757 if (load_options() == ERR
)
7758 die("Failed to load user config.");
7760 if (load_git_config() == ERR
)
7761 die("Failed to load repo config.");
7763 /* Require a git repository unless when running in pager mode. */
7764 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7765 die("Not a git repository");
7767 if (*opt_encoding
&& strcmp(codeset
, "UTF-8")) {
7768 opt_iconv_in
= iconv_open("UTF-8", opt_encoding
);
7769 if (opt_iconv_in
== ICONV_NONE
)
7770 die("Failed to initialize character set conversion");
7773 if (codeset
&& strcmp(codeset
, "UTF-8")) {
7774 opt_iconv_out
= iconv_open(codeset
, "UTF-8");
7775 if (opt_iconv_out
== ICONV_NONE
)
7776 die("Failed to initialize character set conversion");
7779 if (load_refs() == ERR
)
7780 die("Failed to load refs.");
7782 foreach_view (view
, i
)
7783 argv_from_env(view
->ops
->argv
, view
->cmd_env
);
7787 if (request
!= REQ_NONE
)
7788 open_view(NULL
, request
, OPEN_PREPARED
);
7789 request
= request
== REQ_NONE
? REQ_VIEW_MAIN
: REQ_NONE
;
7791 while (view_driver(display
[current_view
], request
)) {
7792 int key
= get_input(0);
7794 view
= display
[current_view
];
7795 request
= get_keybinding(view
->keymap
, key
);
7797 /* Some low-level request handling. This keeps access to
7798 * status_win restricted. */
7802 char *cmd
= read_prompt(":");
7804 if (cmd
&& isdigit(*cmd
)) {
7805 int lineno
= view
->lineno
+ 1;
7807 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7808 select_view_line(view
, lineno
- 1);
7811 report("Unable to parse '%s' as a line number", cmd
);
7815 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7816 const char *argv
[SIZEOF_ARG
] = { "git" };
7819 /* When running random commands, initially show the
7820 * command in the title. However, it maybe later be
7821 * overwritten if a commit line is selected. */
7822 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7824 if (!argv_from_string(argv
, &argc
, cmd
)) {
7825 report("Too many arguments");
7826 } else if (!prepare_update(next
, argv
, NULL
, FORMAT_DASH
)) {
7827 report("Failed to format command");
7829 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7837 case REQ_SEARCH_BACK
:
7839 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7840 char *search
= read_prompt(prompt
);
7843 string_ncopy(opt_search
, search
, strlen(search
));
7844 else if (*opt_search
)
7845 request
= request
== REQ_SEARCH
?