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 mkdate(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
|| !time
|| !time
->sec
)
407 if (date
== DATE_RELATIVE
) {
409 time_t date
= time
->sec
+ time
->tz
;
413 gettimeofday(&now
, NULL
);
414 seconds
= now
.tv_sec
< date
? date
- now
.tv_sec
: now
.tv_sec
- date
;
415 for (i
= 0; i
< ARRAY_SIZE(reldate
); i
++) {
416 if (seconds
>= reldate
[i
].value
)
419 seconds
/= reldate
[i
].namelen
;
420 if (!string_format(buf
, "%ld %s%s %s",
421 seconds
, reldate
[i
].name
,
422 seconds
> 1 ? "s" : "",
423 now
.tv_sec
>= date
? "ago" : "ahead"))
429 gmtime_r(&time
->sec
, &tm
);
430 return strftime(buf
, sizeof(buf
), DATE_FORMAT
, &tm
) ? buf
: NULL
;
434 #define AUTHOR_VALUES \
440 #define AUTHOR_(name) AUTHOR_##name
443 AUTHOR_DEFAULT
= AUTHOR_FULL
446 static const struct enum_map author_map
[] = {
447 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
453 get_author_initials(const char *author
)
455 static char initials
[AUTHOR_COLS
* 6 + 1];
457 const char *end
= strchr(author
, '\0');
459 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
461 memset(initials
, 0, sizeof(initials
));
462 while (author
< end
) {
466 while (is_initial_sep(*author
))
469 bytes
= utf8_char_length(author
, end
);
470 if (bytes
< sizeof(initials
) - 1 - pos
) {
472 initials
[pos
++] = *author
++;
476 for (i
= pos
; author
< end
&& !is_initial_sep(*author
); author
++) {
477 if (i
< sizeof(initials
) - 1)
478 initials
[i
++] = *author
;
489 argv_from_string(const char *argv
[SIZEOF_ARG
], int *argc
, char *cmd
)
493 while (*cmd
&& *argc
< SIZEOF_ARG
&& (valuelen
= strcspn(cmd
, " \t"))) {
494 bool advance
= cmd
[valuelen
] != 0;
497 argv
[(*argc
)++] = chomp_string(cmd
);
498 cmd
= chomp_string(cmd
+ valuelen
+ advance
);
501 if (*argc
< SIZEOF_ARG
)
503 return *argc
< SIZEOF_ARG
;
507 argv_from_env(const char **argv
, const char *name
)
509 char *env
= argv
? getenv(name
) : NULL
;
514 if (env
&& !argv_from_string(argv
, &argc
, env
))
515 die("Too many arguments in the `%s` environment variable", name
);
520 * Executing external commands.
524 IO_FD
, /* File descriptor based IO. */
525 IO_BG
, /* Execute command in the background. */
526 IO_FG
, /* Execute command with same std{in,out,err}. */
527 IO_RD
, /* Read only fork+exec IO. */
528 IO_WR
, /* Write only fork+exec IO. */
529 IO_AP
, /* Append fork+exec output to file. */
533 enum io_type type
; /* The requested type of pipe. */
534 const char *dir
; /* Directory from which to execute. */
535 pid_t pid
; /* PID of spawned process. */
536 int pipe
; /* Pipe end for reading or writing. */
537 int error
; /* Error status. */
538 const char *argv
[SIZEOF_ARG
]; /* Shell command arguments. */
539 char *buf
; /* Read buffer. */
540 size_t bufalloc
; /* Allocated buffer size. */
541 size_t bufsize
; /* Buffer content size. */
542 char *bufpos
; /* Current buffer position. */
543 unsigned int eof
:1; /* Has end of file been reached. */
547 reset_io(struct io
*io
)
551 io
->buf
= io
->bufpos
= NULL
;
552 io
->bufalloc
= io
->bufsize
= 0;
558 init_io(struct io
*io
, const char *dir
, enum io_type type
)
566 init_io_rd(struct io
*io
, const char *argv
[], const char *dir
,
567 enum format_flags flags
)
569 init_io(io
, dir
, IO_RD
);
570 return format_argv(io
->argv
, argv
, flags
);
574 io_open(struct io
*io
, const char *fmt
, ...)
576 char name
[SIZEOF_STR
] = "";
580 init_io(io
, NULL
, IO_FD
);
583 fits
= vsnprintf(name
, sizeof(name
), fmt
, args
) < sizeof(name
);
587 io
->error
= ENAMETOOLONG
;
590 io
->pipe
= *name
? open(name
, O_RDONLY
) : STDIN_FILENO
;
593 return io
->pipe
!= -1;
597 kill_io(struct io
*io
)
599 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
603 done_io(struct io
*io
)
614 pid_t waiting
= waitpid(pid
, &status
, 0);
619 report("waitpid failed (%s)", strerror(errno
));
623 return waiting
== pid
&&
624 !WIFSIGNALED(status
) &&
626 !WEXITSTATUS(status
);
633 start_io(struct io
*io
)
635 int pipefds
[2] = { -1, -1 };
637 if (io
->type
== IO_FD
)
640 if ((io
->type
== IO_RD
|| io
->type
== IO_WR
) &&
643 else if (io
->type
== IO_AP
)
644 pipefds
[1] = io
->pipe
;
646 if ((io
->pid
= fork())) {
647 if (pipefds
[!(io
->type
== IO_WR
)] != -1)
648 close(pipefds
[!(io
->type
== IO_WR
)]);
650 io
->pipe
= pipefds
[!!(io
->type
== IO_WR
)];
655 if (io
->type
!= IO_FG
) {
656 int devnull
= open("/dev/null", O_RDWR
);
657 int readfd
= io
->type
== IO_WR
? pipefds
[0] : devnull
;
658 int writefd
= (io
->type
== IO_RD
|| io
->type
== IO_AP
)
659 ? pipefds
[1] : devnull
;
661 dup2(readfd
, STDIN_FILENO
);
662 dup2(writefd
, STDOUT_FILENO
);
663 dup2(devnull
, STDERR_FILENO
);
666 if (pipefds
[0] != -1)
668 if (pipefds
[1] != -1)
672 if (io
->dir
&& *io
->dir
&& chdir(io
->dir
) == -1)
673 die("Failed to change directory: %s", strerror(errno
));
675 execvp(io
->argv
[0], (char *const*) io
->argv
);
676 die("Failed to execute program: %s", strerror(errno
));
679 if (pipefds
[!!(io
->type
== IO_WR
)] != -1)
680 close(pipefds
[!!(io
->type
== IO_WR
)]);
685 run_io(struct io
*io
, const char **argv
, const char *dir
, enum io_type type
)
687 init_io(io
, dir
, type
);
688 if (!format_argv(io
->argv
, argv
, FORMAT_NONE
))
694 run_io_do(struct io
*io
)
696 return start_io(io
) && done_io(io
);
700 run_io_bg(const char **argv
)
704 init_io(&io
, NULL
, IO_BG
);
705 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
707 return run_io_do(&io
);
711 run_io_fg(const char **argv
, const char *dir
)
715 init_io(&io
, dir
, IO_FG
);
716 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
718 return run_io_do(&io
);
722 run_io_append(const char **argv
, enum format_flags flags
, int fd
)
726 init_io(&io
, NULL
, IO_AP
);
728 if (format_argv(io
.argv
, argv
, flags
))
729 return run_io_do(&io
);
735 run_io_rd(struct io
*io
, const char **argv
, const char *dir
, enum format_flags flags
)
737 return init_io_rd(io
, argv
, dir
, flags
) && start_io(io
);
741 io_eof(struct io
*io
)
747 io_error(struct io
*io
)
753 io_strerror(struct io
*io
)
755 return strerror(io
->error
);
759 io_can_read(struct io
*io
)
761 struct timeval tv
= { 0, 500 };
765 FD_SET(io
->pipe
, &fds
);
767 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
771 io_read(struct io
*io
, void *buf
, size_t bufsize
)
774 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
776 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
778 else if (readsize
== -1)
780 else if (readsize
== 0)
786 DEFINE_ALLOCATOR(realloc_io_buf
, char, BUFSIZ
)
789 io_get(struct io
*io
, int c
, bool can_read
)
795 if (io
->bufsize
> 0) {
796 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
798 char *line
= io
->bufpos
;
801 io
->bufpos
= eol
+ 1;
802 io
->bufsize
-= io
->bufpos
- line
;
809 io
->bufpos
[io
->bufsize
] = 0;
819 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
820 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
822 if (io
->bufalloc
== io
->bufsize
) {
823 if (!realloc_io_buf(&io
->buf
, io
->bufalloc
, BUFSIZ
))
825 io
->bufalloc
+= BUFSIZ
;
828 io
->bufpos
= io
->buf
;
829 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
832 io
->bufsize
+= readsize
;
837 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
841 while (!io_error(io
) && written
< bufsize
) {
844 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
845 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
853 return written
== bufsize
;
857 io_read_buf(struct io
*io
, char buf
[], size_t bufsize
)
859 char *result
= io_get(io
, '\n', TRUE
);
862 result
= chomp_string(result
);
863 string_ncopy_do(buf
, bufsize
, result
, strlen(result
));
866 return done_io(io
) && result
;
870 run_io_buf(const char **argv
, char buf
[], size_t bufsize
)
874 return run_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
875 && io_read_buf(&io
, buf
, bufsize
);
879 io_load(struct io
*io
, const char *separators
,
880 int (*read_property
)(char *, size_t, char *, size_t))
888 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
893 name
= chomp_string(name
);
894 namelen
= strcspn(name
, separators
);
898 value
= chomp_string(name
+ namelen
+ 1);
899 valuelen
= strlen(value
);
906 state
= read_property(name
, namelen
, value
, valuelen
);
909 if (state
!= ERR
&& io_error(io
))
917 run_io_load(const char **argv
, const char *separators
,
918 int (*read_property
)(char *, size_t, char *, size_t))
922 return init_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
923 ? io_load(&io
, separators
, read_property
) : ERR
;
932 /* XXX: Keep the view request first and in sync with views[]. */ \
933 REQ_GROUP("View switching") \
934 REQ_(VIEW_MAIN, "Show main view"), \
935 REQ_(VIEW_DIFF, "Show diff view"), \
936 REQ_(VIEW_LOG, "Show log view"), \
937 REQ_(VIEW_TREE, "Show tree view"), \
938 REQ_(VIEW_BLOB, "Show blob view"), \
939 REQ_(VIEW_BLAME, "Show blame view"), \
940 REQ_(VIEW_BRANCH, "Show branch view"), \
941 REQ_(VIEW_HELP, "Show help page"), \
942 REQ_(VIEW_PAGER, "Show pager view"), \
943 REQ_(VIEW_STATUS, "Show status view"), \
944 REQ_(VIEW_STAGE, "Show stage view"), \
946 REQ_GROUP("View manipulation") \
947 REQ_(ENTER, "Enter current line and scroll"), \
948 REQ_(NEXT, "Move to next"), \
949 REQ_(PREVIOUS, "Move to previous"), \
950 REQ_(PARENT, "Move to parent"), \
951 REQ_(VIEW_NEXT, "Move focus to next view"), \
952 REQ_(REFRESH, "Reload and refresh"), \
953 REQ_(MAXIMIZE, "Maximize the current view"), \
954 REQ_(VIEW_CLOSE, "Close the current view"), \
955 REQ_(QUIT, "Close all views and quit"), \
957 REQ_GROUP("View specific requests") \
958 REQ_(STATUS_UPDATE, "Update file status"), \
959 REQ_(STATUS_REVERT, "Revert file changes"), \
960 REQ_(STATUS_MERGE, "Merge file using external tool"), \
961 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
963 REQ_GROUP("Cursor navigation") \
964 REQ_(MOVE_UP, "Move cursor one line up"), \
965 REQ_(MOVE_DOWN, "Move cursor one line down"), \
966 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
967 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
968 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
969 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
971 REQ_GROUP("Scrolling") \
972 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
973 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
974 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
975 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
976 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
977 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
979 REQ_GROUP("Searching") \
980 REQ_(SEARCH, "Search the view"), \
981 REQ_(SEARCH_BACK, "Search backwards in the view"), \
982 REQ_(FIND_NEXT, "Find next search match"), \
983 REQ_(FIND_PREV, "Find previous search match"), \
985 REQ_GROUP("Option manipulation") \
986 REQ_(OPTIONS, "Open option menu"), \
987 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
988 REQ_(TOGGLE_DATE, "Toggle date display"), \
989 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
990 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
991 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
992 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
993 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
994 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
997 REQ_(PROMPT, "Bring up the prompt"), \
998 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
999 REQ_(SHOW_VERSION, "Show version information"), \
1000 REQ_(STOP_LOADING, "Stop all loading views"), \
1001 REQ_(EDIT, "Open in editor"), \
1002 REQ_(NONE, "Do nothing")
1005 /* User action requests. */
1007 #define REQ_GROUP(help)
1008 #define REQ_(req, help) REQ_##req
1010 /* Offset all requests to avoid conflicts with ncurses getch values. */
1011 REQ_OFFSET
= KEY_MAX
+ 1,
1018 struct request_info
{
1019 enum request request
;
1025 static const struct request_info req_info
[] = {
1026 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1027 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1034 get_request(const char *name
)
1036 int namelen
= strlen(name
);
1039 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
1040 if (enum_equals(req_info
[i
], name
, namelen
))
1041 return req_info
[i
].request
;
1051 /* Option and state variables. */
1052 static enum date opt_date
= DATE_DEFAULT
;
1053 static enum author opt_author
= AUTHOR_DEFAULT
;
1054 static bool opt_line_number
= FALSE
;
1055 static bool opt_line_graphics
= TRUE
;
1056 static bool opt_rev_graph
= FALSE
;
1057 static bool opt_show_refs
= TRUE
;
1058 static int opt_num_interval
= 5;
1059 static double opt_hscroll
= 0.50;
1060 static double opt_scale_split_view
= 2.0 / 3.0;
1061 static int opt_tab_size
= 8;
1062 static int opt_author_cols
= AUTHOR_COLS
;
1063 static char opt_path
[SIZEOF_STR
] = "";
1064 static char opt_file
[SIZEOF_STR
] = "";
1065 static char opt_ref
[SIZEOF_REF
] = "";
1066 static char opt_head
[SIZEOF_REF
] = "";
1067 static char opt_remote
[SIZEOF_REF
] = "";
1068 static char opt_encoding
[20] = "UTF-8";
1069 static iconv_t opt_iconv_in
= ICONV_NONE
;
1070 static iconv_t opt_iconv_out
= ICONV_NONE
;
1071 static char opt_search
[SIZEOF_STR
] = "";
1072 static char opt_cdup
[SIZEOF_STR
] = "";
1073 static char opt_prefix
[SIZEOF_STR
] = "";
1074 static char opt_git_dir
[SIZEOF_STR
] = "";
1075 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
1076 static char opt_editor
[SIZEOF_STR
] = "";
1077 static FILE *opt_tty
= NULL
;
1079 #define is_initial_commit() (!get_ref_head())
1080 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1084 * Line-oriented content detection.
1088 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1089 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1090 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1091 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1092 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1093 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1094 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1095 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1096 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1097 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1098 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1099 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1100 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1101 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1102 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1103 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1104 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1105 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1106 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1107 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1108 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1109 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1110 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1111 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1112 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1113 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1114 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1115 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1116 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1117 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1118 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1119 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1120 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1121 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1122 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1123 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1124 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1125 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1126 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1127 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1128 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1129 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1130 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1131 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1132 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1133 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1134 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1135 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1136 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1137 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1138 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1139 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1140 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1141 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1142 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1143 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1144 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1147 #define LINE(type, line, fg, bg, attr) \
1155 const char *name
; /* Option name. */
1156 int namelen
; /* Size of option name. */
1157 const char *line
; /* The start of line to match. */
1158 int linelen
; /* Size of string to match. */
1159 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1162 static struct line_info line_info
[] = {
1163 #define LINE(type, line, fg, bg, attr) \
1164 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1169 static enum line_type
1170 get_line_type(const char *line
)
1172 int linelen
= strlen(line
);
1173 enum line_type type
;
1175 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1176 /* Case insensitive search matches Signed-off-by lines better. */
1177 if (linelen
>= line_info
[type
].linelen
&&
1178 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1181 return LINE_DEFAULT
;
1185 get_line_attr(enum line_type type
)
1187 assert(type
< ARRAY_SIZE(line_info
));
1188 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1191 static struct line_info
*
1192 get_line_info(const char *name
)
1194 size_t namelen
= strlen(name
);
1195 enum line_type type
;
1197 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1198 if (enum_equals(line_info
[type
], name
, namelen
))
1199 return &line_info
[type
];
1207 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1208 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1209 enum line_type type
;
1213 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1214 default_bg
= COLOR_BLACK
;
1215 default_fg
= COLOR_WHITE
;
1218 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1219 struct line_info
*info
= &line_info
[type
];
1220 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1221 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1223 init_pair(type
, fg
, bg
);
1228 enum line_type type
;
1231 unsigned int selected
:1;
1232 unsigned int dirty
:1;
1233 unsigned int cleareol
:1;
1234 unsigned int other
:16;
1236 void *data
; /* User data */
1246 enum request request
;
1249 static const struct keybinding default_keybindings
[] = {
1250 /* View switching */
1251 { 'm', REQ_VIEW_MAIN
},
1252 { 'd', REQ_VIEW_DIFF
},
1253 { 'l', REQ_VIEW_LOG
},
1254 { 't', REQ_VIEW_TREE
},
1255 { 'f', REQ_VIEW_BLOB
},
1256 { 'B', REQ_VIEW_BLAME
},
1257 { 'H', REQ_VIEW_BRANCH
},
1258 { 'p', REQ_VIEW_PAGER
},
1259 { 'h', REQ_VIEW_HELP
},
1260 { 'S', REQ_VIEW_STATUS
},
1261 { 'c', REQ_VIEW_STAGE
},
1263 /* View manipulation */
1264 { 'q', REQ_VIEW_CLOSE
},
1265 { KEY_TAB
, REQ_VIEW_NEXT
},
1266 { KEY_RETURN
, REQ_ENTER
},
1267 { KEY_UP
, REQ_PREVIOUS
},
1268 { KEY_DOWN
, REQ_NEXT
},
1269 { 'R', REQ_REFRESH
},
1270 { KEY_F(5), REQ_REFRESH
},
1271 { 'O', REQ_MAXIMIZE
},
1273 /* Cursor navigation */
1274 { 'k', REQ_MOVE_UP
},
1275 { 'j', REQ_MOVE_DOWN
},
1276 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1277 { KEY_END
, REQ_MOVE_LAST_LINE
},
1278 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1279 { ' ', REQ_MOVE_PAGE_DOWN
},
1280 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1281 { 'b', REQ_MOVE_PAGE_UP
},
1282 { '-', REQ_MOVE_PAGE_UP
},
1285 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1286 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1287 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1288 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1289 { 'w', REQ_SCROLL_PAGE_UP
},
1290 { 's', REQ_SCROLL_PAGE_DOWN
},
1293 { '/', REQ_SEARCH
},
1294 { '?', REQ_SEARCH_BACK
},
1295 { 'n', REQ_FIND_NEXT
},
1296 { 'N', REQ_FIND_PREV
},
1300 { 'z', REQ_STOP_LOADING
},
1301 { 'v', REQ_SHOW_VERSION
},
1302 { 'r', REQ_SCREEN_REDRAW
},
1303 { 'o', REQ_OPTIONS
},
1304 { '.', REQ_TOGGLE_LINENO
},
1305 { 'D', REQ_TOGGLE_DATE
},
1306 { 'A', REQ_TOGGLE_AUTHOR
},
1307 { 'g', REQ_TOGGLE_REV_GRAPH
},
1308 { 'F', REQ_TOGGLE_REFS
},
1309 { 'I', REQ_TOGGLE_SORT_ORDER
},
1310 { 'i', REQ_TOGGLE_SORT_FIELD
},
1311 { ':', REQ_PROMPT
},
1312 { 'u', REQ_STATUS_UPDATE
},
1313 { '!', REQ_STATUS_REVERT
},
1314 { 'M', REQ_STATUS_MERGE
},
1315 { '@', REQ_STAGE_NEXT
},
1316 { ',', REQ_PARENT
},
1320 #define KEYMAP_INFO \
1335 #define KEYMAP_(name) KEYMAP_##name
1340 static const struct enum_map keymap_table
[] = {
1341 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1346 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1348 struct keybinding_table
{
1349 struct keybinding
*data
;
1353 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1356 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1358 struct keybinding_table
*table
= &keybindings
[keymap
];
1360 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1362 die("Failed to allocate keybinding");
1363 table
->data
[table
->size
].alias
= key
;
1364 table
->data
[table
->size
++].request
= request
;
1367 /* Looks for a key binding first in the given map, then in the generic map, and
1368 * lastly in the default keybindings. */
1370 get_keybinding(enum keymap keymap
, int key
)
1374 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1375 if (keybindings
[keymap
].data
[i
].alias
== key
)
1376 return keybindings
[keymap
].data
[i
].request
;
1378 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1379 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1380 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1382 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1383 if (default_keybindings
[i
].alias
== key
)
1384 return default_keybindings
[i
].request
;
1386 return (enum request
) key
;
1395 static const struct key key_table
[] = {
1396 { "Enter", KEY_RETURN
},
1398 { "Backspace", KEY_BACKSPACE
},
1400 { "Escape", KEY_ESC
},
1401 { "Left", KEY_LEFT
},
1402 { "Right", KEY_RIGHT
},
1404 { "Down", KEY_DOWN
},
1405 { "Insert", KEY_IC
},
1406 { "Delete", KEY_DC
},
1408 { "Home", KEY_HOME
},
1410 { "PageUp", KEY_PPAGE
},
1411 { "PageDown", KEY_NPAGE
},
1421 { "F10", KEY_F(10) },
1422 { "F11", KEY_F(11) },
1423 { "F12", KEY_F(12) },
1427 get_key_value(const char *name
)
1431 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1432 if (!strcasecmp(key_table
[i
].name
, name
))
1433 return key_table
[i
].value
;
1435 if (strlen(name
) == 1 && isprint(*name
))
1442 get_key_name(int key_value
)
1444 static char key_char
[] = "'X'";
1445 const char *seq
= NULL
;
1448 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1449 if (key_table
[key
].value
== key_value
)
1450 seq
= key_table
[key
].name
;
1454 isprint(key_value
)) {
1455 key_char
[1] = (char) key_value
;
1459 return seq
? seq
: "(no key)";
1463 append_key(char *buf
, size_t *pos
, const struct keybinding
*keybinding
)
1465 const char *sep
= *pos
> 0 ? ", " : "";
1466 const char *keyname
= get_key_name(keybinding
->alias
);
1468 return string_nformat(buf
, BUFSIZ
, pos
, "%s%s", sep
, keyname
);
1472 append_keymap_request_keys(char *buf
, size_t *pos
, enum request request
,
1473 enum keymap keymap
, bool all
)
1477 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1478 if (keybindings
[keymap
].data
[i
].request
== request
) {
1479 if (!append_key(buf
, pos
, &keybindings
[keymap
].data
[i
]))
1489 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1492 get_keys(enum keymap keymap
, enum request request
, bool all
)
1494 static char buf
[BUFSIZ
];
1500 if (!append_keymap_request_keys(buf
, &pos
, request
, keymap
, all
))
1501 return "Too many keybindings!";
1502 if (pos
> 0 && !all
)
1505 if (keymap
!= KEYMAP_GENERIC
) {
1506 /* Only the generic keymap includes the default keybindings when
1507 * listing all keys. */
1511 if (!append_keymap_request_keys(buf
, &pos
, request
, KEYMAP_GENERIC
, all
))
1512 return "Too many keybindings!";
1517 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1518 if (default_keybindings
[i
].request
== request
) {
1519 if (!append_key(buf
, &pos
, &default_keybindings
[i
]))
1520 return "Too many keybindings!";
1529 struct run_request
{
1532 const char *argv
[SIZEOF_ARG
];
1535 static struct run_request
*run_request
;
1536 static size_t run_requests
;
1538 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1541 add_run_request(enum keymap keymap
, int key
, int argc
, const char **argv
)
1543 struct run_request
*req
;
1545 if (argc
>= ARRAY_SIZE(req
->argv
) - 1)
1548 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1551 req
= &run_request
[run_requests
];
1552 req
->keymap
= keymap
;
1554 req
->argv
[0] = NULL
;
1556 if (!format_argv(req
->argv
, argv
, FORMAT_NONE
))
1559 return REQ_NONE
+ ++run_requests
;
1562 static struct run_request
*
1563 get_run_request(enum request request
)
1565 if (request
<= REQ_NONE
)
1567 return &run_request
[request
- REQ_NONE
- 1];
1571 add_builtin_run_requests(void)
1573 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1574 const char *commit
[] = { "git", "commit", NULL
};
1575 const char *gc
[] = { "git", "gc", NULL
};
1582 { KEYMAP_MAIN
, 'C', ARRAY_SIZE(cherry_pick
) - 1, cherry_pick
},
1583 { KEYMAP_STATUS
, 'C', ARRAY_SIZE(commit
) - 1, commit
},
1584 { KEYMAP_GENERIC
, 'G', ARRAY_SIZE(gc
) - 1, gc
},
1588 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1591 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argc
, reqs
[i
].argv
);
1592 if (req
!= REQ_NONE
)
1593 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1598 * User config file handling.
1601 static int config_lineno
;
1602 static bool config_errors
;
1603 static const char *config_msg
;
1605 static const struct enum_map color_map
[] = {
1606 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1618 static const struct enum_map attr_map
[] = {
1619 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1626 ATTR_MAP(UNDERLINE
),
1629 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1631 static int parse_step(double *opt
, const char *arg
)
1634 if (!strchr(arg
, '%'))
1637 /* "Shift down" so 100% and 1 does not conflict. */
1638 *opt
= (*opt
- 1) / 100;
1641 config_msg
= "Step value larger than 100%";
1646 config_msg
= "Invalid step value";
1653 parse_int(int *opt
, const char *arg
, int min
, int max
)
1655 int value
= atoi(arg
);
1657 if (min
<= value
&& value
<= max
) {
1662 config_msg
= "Integer value out of bound";
1667 set_color(int *color
, const char *name
)
1669 if (map_enum(color
, color_map
, name
))
1671 if (!prefixcmp(name
, "color"))
1672 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1676 /* Wants: object fgcolor bgcolor [attribute] */
1678 option_color_command(int argc
, const char *argv
[])
1680 struct line_info
*info
;
1683 config_msg
= "Wrong number of arguments given to color command";
1687 info
= get_line_info(argv
[0]);
1689 static const struct enum_map obsolete
[] = {
1690 ENUM_MAP("main-delim", LINE_DELIMITER
),
1691 ENUM_MAP("main-date", LINE_DATE
),
1692 ENUM_MAP("main-author", LINE_AUTHOR
),
1696 if (!map_enum(&index
, obsolete
, argv
[0])) {
1697 config_msg
= "Unknown color name";
1700 info
= &line_info
[index
];
1703 if (!set_color(&info
->fg
, argv
[1]) ||
1704 !set_color(&info
->bg
, argv
[2])) {
1705 config_msg
= "Unknown color";
1710 while (argc
-- > 3) {
1713 if (!set_attribute(&attr
, argv
[argc
])) {
1714 config_msg
= "Unknown attribute";
1723 static int parse_bool(bool *opt
, const char *arg
)
1725 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1730 static int parse_enum_do(unsigned int *opt
, const char *arg
,
1731 const struct enum_map
*map
, size_t map_size
)
1735 assert(map_size
> 1);
1737 if (map_enum_do(map
, map_size
, (int *) opt
, arg
))
1740 if (parse_bool(&is_true
, arg
) != OK
)
1743 *opt
= is_true
? map
[1].value
: map
[0].value
;
1747 #define parse_enum(opt, arg, map) \
1748 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1751 parse_string(char *opt
, const char *arg
, size_t optsize
)
1753 int arglen
= strlen(arg
);
1758 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1759 config_msg
= "Unmatched quotation";
1762 arg
+= 1; arglen
-= 2;
1764 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1769 /* Wants: name = value */
1771 option_set_command(int argc
, const char *argv
[])
1774 config_msg
= "Wrong number of arguments given to set command";
1778 if (strcmp(argv
[1], "=")) {
1779 config_msg
= "No value assigned";
1783 if (!strcmp(argv
[0], "show-author"))
1784 return parse_enum(&opt_author
, argv
[2], author_map
);
1786 if (!strcmp(argv
[0], "show-date"))
1787 return parse_enum(&opt_date
, argv
[2], date_map
);
1789 if (!strcmp(argv
[0], "show-rev-graph"))
1790 return parse_bool(&opt_rev_graph
, argv
[2]);
1792 if (!strcmp(argv
[0], "show-refs"))
1793 return parse_bool(&opt_show_refs
, argv
[2]);
1795 if (!strcmp(argv
[0], "show-line-numbers"))
1796 return parse_bool(&opt_line_number
, argv
[2]);
1798 if (!strcmp(argv
[0], "line-graphics"))
1799 return parse_bool(&opt_line_graphics
, argv
[2]);
1801 if (!strcmp(argv
[0], "line-number-interval"))
1802 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
1804 if (!strcmp(argv
[0], "author-width"))
1805 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
1807 if (!strcmp(argv
[0], "horizontal-scroll"))
1808 return parse_step(&opt_hscroll
, argv
[2]);
1810 if (!strcmp(argv
[0], "split-view-height"))
1811 return parse_step(&opt_scale_split_view
, argv
[2]);
1813 if (!strcmp(argv
[0], "tab-size"))
1814 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
1816 if (!strcmp(argv
[0], "commit-encoding"))
1817 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
1819 config_msg
= "Unknown variable name";
1823 /* Wants: mode request key */
1825 option_bind_command(int argc
, const char *argv
[])
1827 enum request request
;
1832 config_msg
= "Wrong number of arguments given to bind command";
1836 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
1837 config_msg
= "Unknown key map";
1841 key
= get_key_value(argv
[1]);
1843 config_msg
= "Unknown key";
1847 request
= get_request(argv
[2]);
1848 if (request
== REQ_NONE
) {
1849 static const struct enum_map obsolete
[] = {
1850 ENUM_MAP("cherry-pick", REQ_NONE
),
1851 ENUM_MAP("screen-resize", REQ_NONE
),
1852 ENUM_MAP("tree-parent", REQ_PARENT
),
1856 if (map_enum(&alias
, obsolete
, argv
[2])) {
1857 if (alias
!= REQ_NONE
)
1858 add_keybinding(keymap
, alias
, key
);
1859 config_msg
= "Obsolete request name";
1863 if (request
== REQ_NONE
&& *argv
[2]++ == '!')
1864 request
= add_run_request(keymap
, key
, argc
- 2, argv
+ 2);
1865 if (request
== REQ_NONE
) {
1866 config_msg
= "Unknown request name";
1870 add_keybinding(keymap
, request
, key
);
1876 set_option(const char *opt
, char *value
)
1878 const char *argv
[SIZEOF_ARG
];
1881 if (!argv_from_string(argv
, &argc
, value
)) {
1882 config_msg
= "Too many option arguments";
1886 if (!strcmp(opt
, "color"))
1887 return option_color_command(argc
, argv
);
1889 if (!strcmp(opt
, "set"))
1890 return option_set_command(argc
, argv
);
1892 if (!strcmp(opt
, "bind"))
1893 return option_bind_command(argc
, argv
);
1895 config_msg
= "Unknown option command";
1900 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
1905 config_msg
= "Internal error";
1907 /* Check for comment markers, since read_properties() will
1908 * only ensure opt and value are split at first " \t". */
1909 optlen
= strcspn(opt
, "#");
1913 if (opt
[optlen
] != 0) {
1914 config_msg
= "No option value";
1918 /* Look for comment endings in the value. */
1919 size_t len
= strcspn(value
, "#");
1921 if (len
< valuelen
) {
1923 value
[valuelen
] = 0;
1926 status
= set_option(opt
, value
);
1929 if (status
== ERR
) {
1930 warn("Error on line %d, near '%.*s': %s",
1931 config_lineno
, (int) optlen
, opt
, config_msg
);
1932 config_errors
= TRUE
;
1935 /* Always keep going if errors are encountered. */
1940 load_option_file(const char *path
)
1944 /* It's OK that the file doesn't exist. */
1945 if (!io_open(&io
, "%s", path
))
1949 config_errors
= FALSE
;
1951 if (io_load(&io
, " \t", read_option
) == ERR
||
1952 config_errors
== TRUE
)
1953 warn("Errors while loading %s.", path
);
1959 const char *home
= getenv("HOME");
1960 const char *tigrc_user
= getenv("TIGRC_USER");
1961 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
1962 char buf
[SIZEOF_STR
];
1964 add_builtin_run_requests();
1967 tigrc_system
= SYSCONFDIR
"/tigrc";
1968 load_option_file(tigrc_system
);
1971 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
1975 load_option_file(tigrc_user
);
1988 /* The display array of active views and the index of the current view. */
1989 static struct view
*display
[2];
1990 static unsigned int current_view
;
1992 #define foreach_displayed_view(view, i) \
1993 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1995 #define displayed_views() (display[1] != NULL ? 2 : 1)
1997 /* Current head and commit ID */
1998 static char ref_blob
[SIZEOF_REF
] = "";
1999 static char ref_commit
[SIZEOF_REF
] = "HEAD";
2000 static char ref_head
[SIZEOF_REF
] = "HEAD";
2003 const char *name
; /* View name */
2004 const char *cmd_env
; /* Command line set via environment */
2005 const char *id
; /* Points to either of ref_{head,commit,blob} */
2007 struct view_ops
*ops
; /* View operations */
2009 enum keymap keymap
; /* What keymap does this view have */
2010 bool git_dir
; /* Whether the view requires a git directory. */
2012 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
2013 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
2015 int height
, width
; /* The width and height of the main window */
2016 WINDOW
*win
; /* The main window */
2017 WINDOW
*title
; /* The title window living below the main window */
2020 unsigned long offset
; /* Offset of the window top */
2021 unsigned long yoffset
; /* Offset from the window side. */
2022 unsigned long lineno
; /* Current line number */
2023 unsigned long p_offset
; /* Previous offset of the window top */
2024 unsigned long p_yoffset
;/* Previous offset from the window side */
2025 unsigned long p_lineno
; /* Previous current line number */
2026 bool p_restore
; /* Should the previous position be restored. */
2029 char grep
[SIZEOF_STR
]; /* Search string */
2030 regex_t
*regex
; /* Pre-compiled regexp */
2032 /* If non-NULL, points to the view that opened this view. If this view
2033 * is closed tig will switch back to the parent view. */
2034 struct view
*parent
;
2037 size_t lines
; /* Total number of lines */
2038 struct line
*line
; /* Line index */
2039 unsigned int digits
; /* Number of digits in the lines member. */
2042 struct line
*curline
; /* Line currently being drawn. */
2043 enum line_type curtype
; /* Attribute currently used for drawing. */
2044 unsigned long col
; /* Column when drawing. */
2045 bool has_scrolled
; /* View was scrolled. */
2055 /* What type of content being displayed. Used in the title bar. */
2057 /* Default command arguments. */
2059 /* Open and reads in all view content. */
2060 bool (*open
)(struct view
*view
);
2061 /* Read one line; updates view->line. */
2062 bool (*read
)(struct view
*view
, char *data
);
2063 /* Draw one line; @lineno must be < view->height. */
2064 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
2065 /* Depending on view handle a special requests. */
2066 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
2067 /* Search for regexp in a line. */
2068 bool (*grep
)(struct view
*view
, struct line
*line
);
2070 void (*select
)(struct view
*view
, struct line
*line
);
2071 /* Prepare view for loading */
2072 bool (*prepare
)(struct view
*view
);
2075 static struct view_ops blame_ops
;
2076 static struct view_ops blob_ops
;
2077 static struct view_ops diff_ops
;
2078 static struct view_ops help_ops
;
2079 static struct view_ops log_ops
;
2080 static struct view_ops main_ops
;
2081 static struct view_ops pager_ops
;
2082 static struct view_ops stage_ops
;
2083 static struct view_ops status_ops
;
2084 static struct view_ops tree_ops
;
2085 static struct view_ops branch_ops
;
2087 #define VIEW_STR(name, env, ref, ops, map, git) \
2088 { name, #env, ref, ops, map, git }
2090 #define VIEW_(id, name, ops, git, ref) \
2091 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2094 static struct view views
[] = {
2095 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
2096 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
2097 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
2098 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
2099 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
2100 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
2101 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
2102 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
2103 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
2104 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
2105 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
2108 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2109 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2111 #define foreach_view(view, i) \
2112 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2114 #define view_is_displayed(view) \
2115 (view == display[0] || view == display[1])
2119 set_view_attr(struct view
*view
, enum line_type type
)
2121 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
2122 (void) wattrset(view
->win
, get_line_attr(type
));
2123 wchgat(view
->win
, -1, 0, type
, NULL
);
2124 view
->curtype
= type
;
2129 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
2130 int max_len
, bool use_tilde
)
2132 static char out_buffer
[BUFSIZ
* 2];
2135 int trimmed
= FALSE
;
2136 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2141 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
, opt_tab_size
);
2143 set_view_attr(view
, type
);
2145 if (opt_iconv_out
!= ICONV_NONE
) {
2146 ICONV_CONST
char *inbuf
= (ICONV_CONST
char *) string
;
2147 size_t inlen
= len
+ 1;
2149 char *outbuf
= out_buffer
;
2150 size_t outlen
= sizeof(out_buffer
);
2154 ret
= iconv(opt_iconv_out
, &inbuf
, &inlen
, &outbuf
, &outlen
);
2155 if (ret
!= (size_t) -1) {
2156 string
= out_buffer
;
2157 len
= sizeof(out_buffer
) - outlen
;
2161 waddnstr(view
->win
, string
, len
);
2163 if (trimmed
&& use_tilde
) {
2164 set_view_attr(view
, LINE_DELIMITER
);
2165 waddch(view
->win
, '~');
2173 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
2175 static char space
[] = " ";
2178 spaces
= MIN(max
, spaces
);
2180 while (spaces
> 0) {
2181 int len
= MIN(spaces
, sizeof(space
) - 1);
2183 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
2191 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
2193 view
->col
+= draw_chars(view
, type
, string
, view
->width
+ view
->yoffset
- view
->col
, trim
);
2194 return view
->width
+ view
->yoffset
<= view
->col
;
2198 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
2200 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2201 int max
= view
->width
+ view
->yoffset
- view
->col
;
2207 set_view_attr(view
, type
);
2208 /* Using waddch() instead of waddnstr() ensures that
2209 * they'll be rendered correctly for the cursor line. */
2210 for (i
= skip
; i
< size
; i
++)
2211 waddch(view
->win
, graphic
[i
]);
2214 if (size
< max
&& skip
<= size
)
2215 waddch(view
->win
, ' ');
2218 return view
->width
+ view
->yoffset
<= view
->col
;
2222 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2224 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2228 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2230 col
= draw_space(view
, type
, max
- 1, max
- 1);
2233 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2234 return view
->width
+ view
->yoffset
<= view
->col
;
2238 draw_date(struct view
*view
, struct time
*time
)
2240 const char *date
= mkdate(time
, opt_date
);
2241 int cols
= opt_date
== DATE_SHORT
? DATE_SHORT_COLS
: DATE_COLS
;
2243 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2247 draw_author(struct view
*view
, const char *author
)
2249 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5;
2250 bool abbreviate
= opt_author
== AUTHOR_ABBREVIATED
|| !trim
;
2252 if (abbreviate
&& author
)
2253 author
= get_author_initials(author
);
2255 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2259 draw_mode(struct view
*view
, mode_t mode
)
2265 else if (S_ISLNK(mode
))
2267 else if (S_ISGITLINK(mode
))
2269 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2271 else if (S_ISREG(mode
))
2276 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2280 draw_lineno(struct view
*view
, unsigned int lineno
)
2283 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2284 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2286 chtype separator
= opt_line_graphics
? ACS_VLINE
: '|';
2288 lineno
+= view
->offset
+ 1;
2289 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2290 static char fmt
[] = "%1ld";
2292 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2293 if (string_format(number
, fmt
, lineno
))
2297 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2299 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2300 return draw_graphic(view
, LINE_DEFAULT
, &separator
, 1);
2304 draw_view_line(struct view
*view
, unsigned int lineno
)
2307 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2309 assert(view_is_displayed(view
));
2311 if (view
->offset
+ lineno
>= view
->lines
)
2314 line
= &view
->line
[view
->offset
+ lineno
];
2316 wmove(view
->win
, lineno
, 0);
2318 wclrtoeol(view
->win
);
2320 view
->curline
= line
;
2321 view
->curtype
= LINE_NONE
;
2322 line
->selected
= FALSE
;
2323 line
->dirty
= line
->cleareol
= 0;
2326 set_view_attr(view
, LINE_CURSOR
);
2327 line
->selected
= TRUE
;
2328 view
->ops
->select(view
, line
);
2331 return view
->ops
->draw(view
, line
, lineno
);
2335 redraw_view_dirty(struct view
*view
)
2340 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2341 if (view
->offset
+ lineno
>= view
->lines
)
2343 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2346 if (!draw_view_line(view
, lineno
))
2352 wnoutrefresh(view
->win
);
2356 redraw_view_from(struct view
*view
, int lineno
)
2358 assert(0 <= lineno
&& lineno
< view
->height
);
2360 for (; lineno
< view
->height
; lineno
++) {
2361 if (!draw_view_line(view
, lineno
))
2365 wnoutrefresh(view
->win
);
2369 redraw_view(struct view
*view
)
2372 redraw_view_from(view
, 0);
2377 update_view_title(struct view
*view
)
2379 char buf
[SIZEOF_STR
];
2380 char state
[SIZEOF_STR
];
2381 size_t bufpos
= 0, statelen
= 0;
2383 assert(view_is_displayed(view
));
2385 if (view
!= VIEW(REQ_VIEW_STATUS
) && view
->lines
) {
2386 unsigned int view_lines
= view
->offset
+ view
->height
;
2387 unsigned int lines
= view
->lines
2388 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2391 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2400 time_t secs
= time(NULL
) - view
->start_time
;
2402 /* Three git seconds are a long time ... */
2404 string_format_from(state
, &statelen
, " loading %lds", secs
);
2407 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2408 if (*view
->ref
&& bufpos
< view
->width
) {
2409 size_t refsize
= strlen(view
->ref
);
2410 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2412 if (minsize
< view
->width
)
2413 refsize
= view
->width
- minsize
+ 7;
2414 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2417 if (statelen
&& bufpos
< view
->width
) {
2418 string_format_from(buf
, &bufpos
, "%s", state
);
2421 if (view
== display
[current_view
])
2422 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2424 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2426 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2427 wclrtoeol(view
->title
);
2428 wnoutrefresh(view
->title
);
2432 apply_step(double step
, int value
)
2436 value
*= step
+ 0.01;
2437 return value
? value
: 1;
2441 resize_display(void)
2444 struct view
*base
= display
[0];
2445 struct view
*view
= display
[1] ? display
[1] : display
[0];
2447 /* Setup window dimensions */
2449 getmaxyx(stdscr
, base
->height
, base
->width
);
2451 /* Make room for the status window. */
2455 /* Horizontal split. */
2456 view
->width
= base
->width
;
2457 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2458 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2459 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2460 base
->height
-= view
->height
;
2462 /* Make room for the title bar. */
2466 /* Make room for the title bar. */
2471 foreach_displayed_view (view
, i
) {
2473 view
->win
= newwin(view
->height
, 0, offset
, 0);
2475 die("Failed to create %s view", view
->name
);
2477 scrollok(view
->win
, FALSE
);
2479 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2481 die("Failed to create title window");
2484 wresize(view
->win
, view
->height
, view
->width
);
2485 mvwin(view
->win
, offset
, 0);
2486 mvwin(view
->title
, offset
+ view
->height
, 0);
2489 offset
+= view
->height
+ 1;
2494 redraw_display(bool clear
)
2499 foreach_displayed_view (view
, i
) {
2503 update_view_title(view
);
2508 toggle_enum_option_do(unsigned int *opt
, const char *help
,
2509 const struct enum_map
*map
, size_t size
)
2511 *opt
= (*opt
+ 1) % size
;
2512 redraw_display(FALSE
);
2513 report("Displaying %s %s", enum_name(map
[*opt
]), help
);
2516 #define toggle_enum_option(opt, help, map) \
2517 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2519 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2520 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2523 toggle_view_option(bool *option
, const char *help
)
2526 redraw_display(FALSE
);
2527 report("%sabling %s", *option
? "En" : "Dis", help
);
2531 open_option_menu(void)
2533 const struct menu_item menu
[] = {
2534 { '.', "line numbers", &opt_line_number
},
2535 { 'D', "date display", &opt_date
},
2536 { 'A', "author display", &opt_author
},
2537 { 'g', "revision graph display", &opt_rev_graph
},
2538 { 'F', "reference display", &opt_show_refs
},
2543 if (prompt_menu("Toggle option", menu
, &selected
)) {
2544 if (menu
[selected
].data
== &opt_date
)
2546 else if (menu
[selected
].data
== &opt_author
)
2549 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2554 maximize_view(struct view
*view
)
2556 memset(display
, 0, sizeof(display
));
2558 display
[current_view
] = view
;
2560 redraw_display(FALSE
);
2570 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2572 if (lineno
>= view
->lines
)
2573 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2575 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2576 unsigned long half
= view
->height
/ 2;
2579 offset
= lineno
- half
;
2584 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2585 view
->offset
= offset
;
2586 view
->lineno
= lineno
;
2593 /* Scrolling backend */
2595 do_scroll_view(struct view
*view
, int lines
)
2597 bool redraw_current_line
= FALSE
;
2599 /* The rendering expects the new offset. */
2600 view
->offset
+= lines
;
2602 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2605 /* Move current line into the view. */
2606 if (view
->lineno
< view
->offset
) {
2607 view
->lineno
= view
->offset
;
2608 redraw_current_line
= TRUE
;
2609 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2610 view
->lineno
= view
->offset
+ view
->height
- 1;
2611 redraw_current_line
= TRUE
;
2614 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2616 /* Redraw the whole screen if scrolling is pointless. */
2617 if (view
->height
< ABS(lines
)) {
2621 int line
= lines
> 0 ? view
->height
- lines
: 0;
2622 int end
= line
+ ABS(lines
);
2624 scrollok(view
->win
, TRUE
);
2625 wscrl(view
->win
, lines
);
2626 scrollok(view
->win
, FALSE
);
2628 while (line
< end
&& draw_view_line(view
, line
))
2631 if (redraw_current_line
)
2632 draw_view_line(view
, view
->lineno
- view
->offset
);
2633 wnoutrefresh(view
->win
);
2636 view
->has_scrolled
= TRUE
;
2640 /* Scroll frontend */
2642 scroll_view(struct view
*view
, enum request request
)
2646 assert(view_is_displayed(view
));
2649 case REQ_SCROLL_LEFT
:
2650 if (view
->yoffset
== 0) {
2651 report("Cannot scroll beyond the first column");
2654 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2657 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2658 redraw_view_from(view
, 0);
2661 case REQ_SCROLL_RIGHT
:
2662 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2666 case REQ_SCROLL_PAGE_DOWN
:
2667 lines
= view
->height
;
2668 case REQ_SCROLL_LINE_DOWN
:
2669 if (view
->offset
+ lines
> view
->lines
)
2670 lines
= view
->lines
- view
->offset
;
2672 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2673 report("Cannot scroll beyond the last line");
2678 case REQ_SCROLL_PAGE_UP
:
2679 lines
= view
->height
;
2680 case REQ_SCROLL_LINE_UP
:
2681 if (lines
> view
->offset
)
2682 lines
= view
->offset
;
2685 report("Cannot scroll beyond the first line");
2693 die("request %d not handled in switch", request
);
2696 do_scroll_view(view
, lines
);
2701 move_view(struct view
*view
, enum request request
)
2703 int scroll_steps
= 0;
2707 case REQ_MOVE_FIRST_LINE
:
2708 steps
= -view
->lineno
;
2711 case REQ_MOVE_LAST_LINE
:
2712 steps
= view
->lines
- view
->lineno
- 1;
2715 case REQ_MOVE_PAGE_UP
:
2716 steps
= view
->height
> view
->lineno
2717 ? -view
->lineno
: -view
->height
;
2720 case REQ_MOVE_PAGE_DOWN
:
2721 steps
= view
->lineno
+ view
->height
>= view
->lines
2722 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2734 die("request %d not handled in switch", request
);
2737 if (steps
<= 0 && view
->lineno
== 0) {
2738 report("Cannot move beyond the first line");
2741 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2742 report("Cannot move beyond the last line");
2746 /* Move the current line */
2747 view
->lineno
+= steps
;
2748 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2750 /* Check whether the view needs to be scrolled */
2751 if (view
->lineno
< view
->offset
||
2752 view
->lineno
>= view
->offset
+ view
->height
) {
2753 scroll_steps
= steps
;
2754 if (steps
< 0 && -steps
> view
->offset
) {
2755 scroll_steps
= -view
->offset
;
2757 } else if (steps
> 0) {
2758 if (view
->lineno
== view
->lines
- 1 &&
2759 view
->lines
> view
->height
) {
2760 scroll_steps
= view
->lines
- view
->offset
- 1;
2761 if (scroll_steps
>= view
->height
)
2762 scroll_steps
-= view
->height
- 1;
2767 if (!view_is_displayed(view
)) {
2768 view
->offset
+= scroll_steps
;
2769 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2770 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2774 /* Repaint the old "current" line if we be scrolling */
2775 if (ABS(steps
) < view
->height
)
2776 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
2779 do_scroll_view(view
, scroll_steps
);
2783 /* Draw the current line */
2784 draw_view_line(view
, view
->lineno
- view
->offset
);
2786 wnoutrefresh(view
->win
);
2795 static void search_view(struct view
*view
, enum request request
);
2798 grep_text(struct view
*view
, const char *text
[])
2803 for (i
= 0; text
[i
]; i
++)
2805 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
2811 select_view_line(struct view
*view
, unsigned long lineno
)
2813 unsigned long old_lineno
= view
->lineno
;
2814 unsigned long old_offset
= view
->offset
;
2816 if (goto_view_line(view
, view
->offset
, lineno
)) {
2817 if (view_is_displayed(view
)) {
2818 if (old_offset
!= view
->offset
) {
2821 draw_view_line(view
, old_lineno
- view
->offset
);
2822 draw_view_line(view
, view
->lineno
- view
->offset
);
2823 wnoutrefresh(view
->win
);
2826 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2832 find_next(struct view
*view
, enum request request
)
2834 unsigned long lineno
= view
->lineno
;
2839 report("No previous search");
2841 search_view(view
, request
);
2851 case REQ_SEARCH_BACK
:
2860 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
2861 lineno
+= direction
;
2863 /* Note, lineno is unsigned long so will wrap around in which case it
2864 * will become bigger than view->lines. */
2865 for (; lineno
< view
->lines
; lineno
+= direction
) {
2866 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
2867 select_view_line(view
, lineno
);
2868 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
2873 report("No match found for '%s'", view
->grep
);
2877 search_view(struct view
*view
, enum request request
)
2882 regfree(view
->regex
);
2885 view
->regex
= calloc(1, sizeof(*view
->regex
));
2890 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
2891 if (regex_err
!= 0) {
2892 char buf
[SIZEOF_STR
] = "unknown error";
2894 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
2895 report("Search failed: %s", buf
);
2899 string_copy(view
->grep
, opt_search
);
2901 find_next(view
, request
);
2905 * Incremental updating
2909 reset_view(struct view
*view
)
2913 for (i
= 0; i
< view
->lines
; i
++)
2914 free(view
->line
[i
].data
);
2917 view
->p_offset
= view
->offset
;
2918 view
->p_yoffset
= view
->yoffset
;
2919 view
->p_lineno
= view
->lineno
;
2927 view
->update_secs
= 0;
2931 free_argv(const char *argv
[])
2935 for (argc
= 0; argv
[argc
]; argc
++)
2936 free((void *) argv
[argc
]);
2940 format_arg(const char *name
)
2946 const char *value_if_empty
;
2948 #define FORMAT_VAR(name, value, value_if_empty) \
2949 { name, STRING_SIZE(name), value, value_if_empty }
2950 FORMAT_VAR("%(directory)", opt_path
, ""),
2951 FORMAT_VAR("%(file)", opt_file
, ""),
2952 FORMAT_VAR("%(ref)", opt_ref
, "HEAD"),
2953 FORMAT_VAR("%(head)", ref_head
, ""),
2954 FORMAT_VAR("%(commit)", ref_commit
, ""),
2955 FORMAT_VAR("%(blob)", ref_blob
, ""),
2959 for (i
= 0; i
< ARRAY_SIZE(vars
); i
++)
2960 if (!strncmp(name
, vars
[i
].name
, vars
[i
].namelen
))
2961 return *vars
[i
].value
? vars
[i
].value
: vars
[i
].value_if_empty
;
2966 format_argv(const char *dst_argv
[], const char *src_argv
[], enum format_flags flags
)
2968 char buf
[SIZEOF_STR
];
2970 bool noreplace
= flags
== FORMAT_NONE
;
2972 free_argv(dst_argv
);
2974 for (argc
= 0; src_argv
[argc
]; argc
++) {
2975 const char *arg
= src_argv
[argc
];
2979 char *next
= strstr(arg
, "%(");
2980 int len
= next
- arg
;
2983 if (!next
|| noreplace
) {
2984 if (flags
== FORMAT_DASH
&& !strcmp(arg
, "--"))
2990 value
= format_arg(next
);
2993 report("Unknown replacement: `%s`", next
);
2998 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
3001 arg
= next
&& !noreplace
? strchr(next
, ')') + 1 : NULL
;
3004 dst_argv
[argc
] = strdup(buf
);
3005 if (!dst_argv
[argc
])
3009 dst_argv
[argc
] = NULL
;
3011 return src_argv
[argc
] == NULL
;
3015 restore_view_position(struct view
*view
)
3017 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
3020 /* Changing the view position cancels the restoring. */
3021 /* FIXME: Changing back to the first line is not detected. */
3022 if (view
->offset
!= 0 || view
->lineno
!= 0) {
3023 view
->p_restore
= FALSE
;
3027 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
3028 view_is_displayed(view
))
3031 view
->yoffset
= view
->p_yoffset
;
3032 view
->p_restore
= FALSE
;
3038 end_update(struct view
*view
, bool force
)
3042 while (!view
->ops
->read(view
, NULL
))
3046 kill_io(view
->pipe
);
3047 done_io(view
->pipe
);
3052 setup_update(struct view
*view
, const char *vid
)
3055 string_copy_rev(view
->vid
, vid
);
3056 view
->pipe
= &view
->io
;
3057 view
->start_time
= time(NULL
);
3061 prepare_update(struct view
*view
, const char *argv
[], const char *dir
,
3062 enum format_flags flags
)
3065 end_update(view
, TRUE
);
3066 return init_io_rd(&view
->io
, argv
, dir
, flags
);
3070 prepare_update_file(struct view
*view
, const char *name
)
3073 end_update(view
, TRUE
);
3074 return io_open(&view
->io
, "%s/%s", opt_cdup
[0] ? opt_cdup
: ".", name
);
3078 begin_update(struct view
*view
, bool refresh
)
3081 end_update(view
, TRUE
);
3084 if (view
->ops
->prepare
) {
3085 if (!view
->ops
->prepare(view
))
3087 } else if (!init_io_rd(&view
->io
, view
->ops
->argv
, NULL
, FORMAT_ALL
)) {
3091 /* Put the current ref_* value to the view title ref
3092 * member. This is needed by the blob view. Most other
3093 * views sets it automatically after loading because the
3094 * first line is a commit line. */
3095 string_copy_rev(view
->ref
, view
->id
);
3098 if (!start_io(&view
->io
))
3101 setup_update(view
, view
->id
);
3107 update_view(struct view
*view
)
3109 char out_buffer
[BUFSIZ
* 2];
3111 /* Clear the view and redraw everything since the tree sorting
3112 * might have rearranged things. */
3113 bool redraw
= view
->lines
== 0;
3114 bool can_read
= TRUE
;
3119 if (!io_can_read(view
->pipe
)) {
3120 if (view
->lines
== 0 && view_is_displayed(view
)) {
3121 time_t secs
= time(NULL
) - view
->start_time
;
3123 if (secs
> 1 && secs
> view
->update_secs
) {
3124 if (view
->update_secs
== 0)
3126 update_view_title(view
);
3127 view
->update_secs
= secs
;
3133 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
3134 if (opt_iconv_in
!= ICONV_NONE
) {
3135 ICONV_CONST
char *inbuf
= line
;
3136 size_t inlen
= strlen(line
) + 1;
3138 char *outbuf
= out_buffer
;
3139 size_t outlen
= sizeof(out_buffer
);
3143 ret
= iconv(opt_iconv_in
, &inbuf
, &inlen
, &outbuf
, &outlen
);
3144 if (ret
!= (size_t) -1)
3148 if (!view
->ops
->read(view
, line
)) {
3149 report("Allocation failure");
3150 end_update(view
, TRUE
);
3156 unsigned long lines
= view
->lines
;
3159 for (digits
= 0; lines
; digits
++)
3162 /* Keep the displayed view in sync with line number scaling. */
3163 if (digits
!= view
->digits
) {
3164 view
->digits
= digits
;
3165 if (opt_line_number
|| view
== VIEW(REQ_VIEW_BLAME
))
3170 if (io_error(view
->pipe
)) {
3171 report("Failed to read: %s", io_strerror(view
->pipe
));
3172 end_update(view
, TRUE
);
3174 } else if (io_eof(view
->pipe
)) {
3176 end_update(view
, FALSE
);
3179 if (restore_view_position(view
))
3182 if (!view_is_displayed(view
))
3186 redraw_view_from(view
, 0);
3188 redraw_view_dirty(view
);
3190 /* Update the title _after_ the redraw so that if the redraw picks up a
3191 * commit reference in view->ref it'll be available here. */
3192 update_view_title(view
);
3196 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
3198 static struct line
*
3199 add_line_data(struct view
*view
, void *data
, enum line_type type
)
3203 if (!realloc_lines(&view
->line
, view
->lines
, 1))
3206 line
= &view
->line
[view
->lines
++];
3207 memset(line
, 0, sizeof(*line
));
3215 static struct line
*
3216 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
3218 char *data
= text
? strdup(text
) : NULL
;
3220 return data
? add_line_data(view
, data
, type
) : NULL
;
3223 static struct line
*
3224 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3226 char buf
[SIZEOF_STR
];
3229 va_start(args
, fmt
);
3230 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3234 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3242 OPEN_DEFAULT
= 0, /* Use default view switching. */
3243 OPEN_SPLIT
= 1, /* Split current view. */
3244 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3245 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3246 OPEN_PREPARED
= 32, /* Open already prepared command. */
3250 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3252 bool split
= !!(flags
& OPEN_SPLIT
);
3253 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3254 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3255 struct view
*view
= VIEW(request
);
3256 int nviews
= displayed_views();
3257 struct view
*base_view
= display
[0];
3259 if (view
== prev
&& nviews
== 1 && !reload
) {
3260 report("Already in %s view", view
->name
);
3264 if (view
->git_dir
&& !opt_git_dir
[0]) {
3265 report("The %s view is disabled in pager view", view
->name
);
3272 } else if (!nomaximize
) {
3273 /* Maximize the current view. */
3274 memset(display
, 0, sizeof(display
));
3276 display
[current_view
] = view
;
3279 /* No parent signals that this is the first loaded view. */
3280 if (prev
&& view
!= prev
) {
3281 view
->parent
= prev
;
3284 /* Resize the view when switching between split- and full-screen,
3285 * or when switching between two different full-screen views. */
3286 if (nviews
!= displayed_views() ||
3287 (nviews
== 1 && base_view
!= display
[0]))
3290 if (view
->ops
->open
) {
3292 end_update(view
, TRUE
);
3293 if (!view
->ops
->open(view
)) {
3294 report("Failed to load %s view", view
->name
);
3297 restore_view_position(view
);
3299 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3300 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3301 report("Failed to load %s view", view
->name
);
3305 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3306 /* Take the title line into account. */
3307 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3309 /* Scroll the view that was split if the current line is
3310 * outside the new limited view. */
3311 do_scroll_view(prev
, lines
);
3314 if (prev
&& view
!= prev
&& split
&& view_is_displayed(prev
)) {
3315 /* "Blur" the previous view. */
3316 update_view_title(prev
);
3319 if (view
->pipe
&& view
->lines
== 0) {
3320 /* Clear the old view and let the incremental updating refill
3323 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3325 } else if (view_is_displayed(view
)) {
3332 open_external_viewer(const char *argv
[], const char *dir
)
3334 def_prog_mode(); /* save current tty modes */
3335 endwin(); /* restore original tty modes */
3336 run_io_fg(argv
, dir
);
3337 fprintf(stderr
, "Press Enter to continue");
3340 redraw_display(TRUE
);
3344 open_mergetool(const char *file
)
3346 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3348 open_external_viewer(mergetool_argv
, opt_cdup
);
3352 open_editor(const char *file
)
3354 const char *editor_argv
[] = { "vi", file
, NULL
};
3357 editor
= getenv("GIT_EDITOR");
3358 if (!editor
&& *opt_editor
)
3359 editor
= opt_editor
;
3361 editor
= getenv("VISUAL");
3363 editor
= getenv("EDITOR");
3367 editor_argv
[0] = editor
;
3368 open_external_viewer(editor_argv
, opt_cdup
);
3372 open_run_request(enum request request
)
3374 struct run_request
*req
= get_run_request(request
);
3375 const char *argv
[ARRAY_SIZE(req
->argv
)] = { NULL
};
3378 report("Unknown run request");
3382 if (format_argv(argv
, req
->argv
, FORMAT_ALL
))
3383 open_external_viewer(argv
, NULL
);
3388 * User request switch noodle
3392 view_driver(struct view
*view
, enum request request
)
3396 if (request
== REQ_NONE
)
3399 if (request
> REQ_NONE
) {
3400 open_run_request(request
);
3401 /* FIXME: When all views can refresh always do this. */
3402 if (view
== VIEW(REQ_VIEW_STATUS
) ||
3403 view
== VIEW(REQ_VIEW_MAIN
) ||
3404 view
== VIEW(REQ_VIEW_LOG
) ||
3405 view
== VIEW(REQ_VIEW_BRANCH
) ||
3406 view
== VIEW(REQ_VIEW_STAGE
))
3407 request
= REQ_REFRESH
;
3412 if (view
&& view
->lines
) {
3413 request
= view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
3414 if (request
== REQ_NONE
)
3421 case REQ_MOVE_PAGE_UP
:
3422 case REQ_MOVE_PAGE_DOWN
:
3423 case REQ_MOVE_FIRST_LINE
:
3424 case REQ_MOVE_LAST_LINE
:
3425 move_view(view
, request
);
3428 case REQ_SCROLL_LEFT
:
3429 case REQ_SCROLL_RIGHT
:
3430 case REQ_SCROLL_LINE_DOWN
:
3431 case REQ_SCROLL_LINE_UP
:
3432 case REQ_SCROLL_PAGE_DOWN
:
3433 case REQ_SCROLL_PAGE_UP
:
3434 scroll_view(view
, request
);
3437 case REQ_VIEW_BLAME
:
3439 report("No file chosen, press %s to open tree view",
3440 get_key(view
->keymap
, REQ_VIEW_TREE
));
3443 open_view(view
, request
, OPEN_DEFAULT
);
3448 report("No file chosen, press %s to open tree view",
3449 get_key(view
->keymap
, REQ_VIEW_TREE
));
3452 open_view(view
, request
, OPEN_DEFAULT
);
3455 case REQ_VIEW_PAGER
:
3456 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3457 report("No pager content, press %s to run command from prompt",
3458 get_key(view
->keymap
, REQ_PROMPT
));
3461 open_view(view
, request
, OPEN_DEFAULT
);
3464 case REQ_VIEW_STAGE
:
3465 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3466 report("No stage content, press %s to open the status view and choose file",
3467 get_key(view
->keymap
, REQ_VIEW_STATUS
));
3470 open_view(view
, request
, OPEN_DEFAULT
);
3473 case REQ_VIEW_STATUS
:
3474 if (opt_is_inside_work_tree
== FALSE
) {
3475 report("The status view requires a working tree");
3478 open_view(view
, request
, OPEN_DEFAULT
);
3486 case REQ_VIEW_BRANCH
:
3487 open_view(view
, request
, OPEN_DEFAULT
);
3492 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3494 if ((view
== VIEW(REQ_VIEW_DIFF
) &&
3495 view
->parent
== VIEW(REQ_VIEW_MAIN
)) ||
3496 (view
== VIEW(REQ_VIEW_DIFF
) &&
3497 view
->parent
== VIEW(REQ_VIEW_BLAME
)) ||
3498 (view
== VIEW(REQ_VIEW_STAGE
) &&
3499 view
->parent
== VIEW(REQ_VIEW_STATUS
)) ||
3500 (view
== VIEW(REQ_VIEW_BLOB
) &&
3501 view
->parent
== VIEW(REQ_VIEW_TREE
)) ||
3502 (view
== VIEW(REQ_VIEW_MAIN
) &&
3503 view
->parent
== VIEW(REQ_VIEW_BRANCH
))) {
3506 view
= view
->parent
;
3507 line
= view
->lineno
;
3508 move_view(view
, request
);
3509 if (view_is_displayed(view
))
3510 update_view_title(view
);
3511 if (line
!= view
->lineno
)
3512 view
->ops
->request(view
, REQ_ENTER
,
3513 &view
->line
[view
->lineno
]);
3516 move_view(view
, request
);
3522 int nviews
= displayed_views();
3523 int next_view
= (current_view
+ 1) % nviews
;
3525 if (next_view
== current_view
) {
3526 report("Only one view is displayed");
3530 current_view
= next_view
;
3531 /* Blur out the title of the previous view. */
3532 update_view_title(view
);
3537 report("Refreshing is not yet supported for the %s view", view
->name
);
3541 if (displayed_views() == 2)
3542 maximize_view(view
);
3549 case REQ_TOGGLE_LINENO
:
3550 toggle_view_option(&opt_line_number
, "line numbers");
3553 case REQ_TOGGLE_DATE
:
3557 case REQ_TOGGLE_AUTHOR
:
3561 case REQ_TOGGLE_REV_GRAPH
:
3562 toggle_view_option(&opt_rev_graph
, "revision graph display");
3565 case REQ_TOGGLE_REFS
:
3566 toggle_view_option(&opt_show_refs
, "reference display");
3569 case REQ_TOGGLE_SORT_FIELD
:
3570 case REQ_TOGGLE_SORT_ORDER
:
3571 report("Sorting is not yet supported for the %s view", view
->name
);
3575 case REQ_SEARCH_BACK
:
3576 search_view(view
, request
);
3581 find_next(view
, request
);
3584 case REQ_STOP_LOADING
:
3585 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
3588 report("Stopped loading the %s view", view
->name
),
3589 end_update(view
, TRUE
);
3593 case REQ_SHOW_VERSION
:
3594 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3597 case REQ_SCREEN_REDRAW
:
3598 redraw_display(TRUE
);
3602 report("Nothing to edit");
3606 report("Nothing to enter");
3609 case REQ_VIEW_CLOSE
:
3610 /* XXX: Mark closed views by letting view->parent point to the
3611 * view itself. Parents to closed view should never be
3614 view
->parent
->parent
!= view
->parent
) {
3615 maximize_view(view
->parent
);
3616 view
->parent
= view
;
3624 report("Unknown key, press %s for help",
3625 get_key(view
->keymap
, REQ_VIEW_HELP
));
3634 * View backend utilities
3644 const enum sort_field
*fields
;
3645 size_t size
, current
;
3649 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3650 #define get_sort_field(state) ((state).fields[(state).current])
3651 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3654 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3655 int (*compare
)(const void *, const void *))
3658 case REQ_TOGGLE_SORT_FIELD
:
3659 state
->current
= (state
->current
+ 1) % state
->size
;
3662 case REQ_TOGGLE_SORT_ORDER
:
3663 state
->reverse
= !state
->reverse
;
3666 die("Not a sort request");
3669 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3673 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3675 /* Small author cache to reduce memory consumption. It uses binary
3676 * search to lookup or find place to position new entries. No entries
3677 * are ever freed. */
3679 get_author(const char *name
)
3681 static const char **authors
;
3682 static size_t authors_size
;
3683 int from
= 0, to
= authors_size
- 1;
3685 while (from
<= to
) {
3686 size_t pos
= (to
+ from
) / 2;
3687 int cmp
= strcmp(name
, authors
[pos
]);
3690 return authors
[pos
];
3698 if (!realloc_authors(&authors
, authors_size
, 1))
3700 name
= strdup(name
);
3704 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3705 authors
[from
] = name
;
3712 parse_timesec(struct time
*time
, const char *sec
)
3714 time
->sec
= (time_t) atol(sec
);
3718 parse_timezone(struct time
*time
, const char *zone
)
3722 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3723 tz
+= ('0' - zone
[2]) * 60 * 60;
3724 tz
+= ('0' - zone
[3]) * 60;
3725 tz
+= ('0' - zone
[4]);
3734 /* Parse author lines where the name may be empty:
3735 * author <email@address.tld> 1138474660 +0100
3738 parse_author_line(char *ident
, const char **author
, struct time
*time
)
3740 char *nameend
= strchr(ident
, '<');
3741 char *emailend
= strchr(ident
, '>');
3743 if (nameend
&& emailend
)
3744 *nameend
= *emailend
= 0;
3745 ident
= chomp_string(ident
);
3748 ident
= chomp_string(nameend
+ 1);
3753 *author
= get_author(ident
);
3755 /* Parse epoch and timezone */
3756 if (emailend
&& emailend
[1] == ' ') {
3757 char *secs
= emailend
+ 2;
3758 char *zone
= strchr(secs
, ' ');
3760 parse_timesec(time
, secs
);
3762 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
3763 parse_timezone(time
, zone
+ 1);
3768 open_commit_parent_menu(char buf
[SIZEOF_STR
], int *parents
)
3770 char rev
[SIZEOF_REV
];
3771 const char *revlist_argv
[] = {
3772 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev
, NULL
3774 struct menu_item
*items
;
3775 char text
[SIZEOF_STR
];
3779 items
= calloc(*parents
+ 1, sizeof(*items
));
3783 for (i
= 0; i
< *parents
; i
++) {
3784 string_copy_rev(rev
, &buf
[SIZEOF_REV
* i
]);
3785 if (!run_io_buf(revlist_argv
, text
, sizeof(text
)) ||
3786 !(items
[i
].text
= strdup(text
))) {
3794 ok
= prompt_menu("Select parent", items
, parents
);
3796 for (i
= 0; items
[i
].text
; i
++)
3797 free((char *) items
[i
].text
);
3803 select_commit_parent(const char *id
, char rev
[SIZEOF_REV
], const char *path
)
3805 char buf
[SIZEOF_STR
* 4];
3806 const char *revlist_argv
[] = {
3807 "git", "log", "--no-color", "-1",
3808 "--pretty=format:%P", id
, "--", path
, NULL
3812 if (!run_io_buf(revlist_argv
, buf
, sizeof(buf
)) ||
3813 (parents
= strlen(buf
) / 40) < 0) {
3814 report("Failed to get parent information");
3817 } else if (parents
== 0) {
3819 report("Path '%s' does not exist in the parent", path
);
3821 report("The selected commit has no parents");
3825 if (parents
> 1 && !open_commit_parent_menu(buf
, &parents
))
3828 string_copy_rev(rev
, &buf
[41 * parents
]);
3837 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
3839 char text
[SIZEOF_STR
];
3841 if (opt_line_number
&& draw_lineno(view
, lineno
))
3844 string_expand(text
, sizeof(text
), line
->data
, opt_tab_size
);
3845 draw_text(view
, line
->type
, text
, TRUE
);
3850 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
3852 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
3853 char ref
[SIZEOF_STR
];
3855 if (!run_io_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
3858 /* This is the only fatal call, since it can "corrupt" the buffer. */
3859 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
3866 add_pager_refs(struct view
*view
, struct line
*line
)
3868 char buf
[SIZEOF_STR
];
3869 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
3870 struct ref_list
*list
;
3871 size_t bufpos
= 0, i
;
3872 const char *sep
= "Refs: ";
3873 bool is_tag
= FALSE
;
3875 assert(line
->type
== LINE_COMMIT
);
3877 list
= get_ref_list(commit_id
);
3879 if (view
== VIEW(REQ_VIEW_DIFF
))
3880 goto try_add_describe_ref
;
3884 for (i
= 0; i
< list
->size
; i
++) {
3885 struct ref
*ref
= list
->refs
[i
];
3886 const char *fmt
= ref
->tag
? "%s[%s]" :
3887 ref
->remote
? "%s<%s>" : "%s%s";
3889 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
3896 if (!is_tag
&& view
== VIEW(REQ_VIEW_DIFF
)) {
3897 try_add_describe_ref
:
3898 /* Add <tag>-g<commit_id> "fake" reference. */
3899 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
3906 add_line_text(view
, buf
, LINE_PP_REFS
);
3910 pager_read(struct view
*view
, char *data
)
3917 line
= add_line_text(view
, data
, get_line_type(data
));
3921 if (line
->type
== LINE_COMMIT
&&
3922 (view
== VIEW(REQ_VIEW_DIFF
) ||
3923 view
== VIEW(REQ_VIEW_LOG
)))
3924 add_pager_refs(view
, line
);
3930 pager_request(struct view
*view
, enum request request
, struct line
*line
)
3934 if (request
!= REQ_ENTER
)
3937 if (line
->type
== LINE_COMMIT
&&
3938 (view
== VIEW(REQ_VIEW_LOG
) ||
3939 view
== VIEW(REQ_VIEW_PAGER
))) {
3940 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
3944 /* Always scroll the view even if it was split. That way
3945 * you can use Enter to scroll through the log view and
3946 * split open each commit diff. */
3947 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
3949 /* FIXME: A minor workaround. Scrolling the view will call report("")
3950 * but if we are scrolling a non-current view this won't properly
3951 * update the view title. */
3953 update_view_title(view
);
3959 pager_grep(struct view
*view
, struct line
*line
)
3961 const char *text
[] = { line
->data
, NULL
};
3963 return grep_text(view
, text
);
3967 pager_select(struct view
*view
, struct line
*line
)
3969 if (line
->type
== LINE_COMMIT
) {
3970 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
3972 if (view
!= VIEW(REQ_VIEW_PAGER
))
3973 string_copy_rev(view
->ref
, text
);
3974 string_copy_rev(ref_commit
, text
);
3978 static struct view_ops pager_ops
= {
3989 static const char *log_argv
[SIZEOF_ARG
] = {
3990 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3994 log_request(struct view
*view
, enum request request
, struct line
*line
)
3999 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
4002 return pager_request(view
, request
, line
);
4006 static struct view_ops log_ops
= {
4017 static const char *diff_argv
[SIZEOF_ARG
] = {
4018 "git", "show", "--pretty=fuller", "--no-color", "--root",
4019 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4022 static struct view_ops diff_ops
= {
4037 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
4040 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
4044 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
4045 help_keymap_hidden
[keymap
] ? '+' : '-',
4046 enum_name(keymap_table
[keymap
]));
4048 line
->other
= keymap
;
4050 return help_keymap_hidden
[keymap
];
4054 help_open_keymap(struct view
*view
, enum keymap keymap
)
4056 const char *group
= NULL
;
4057 char buf
[SIZEOF_STR
];
4059 bool add_title
= TRUE
;
4062 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
4063 const char *key
= NULL
;
4065 if (req_info
[i
].request
== REQ_NONE
)
4068 if (!req_info
[i
].request
) {
4069 group
= req_info
[i
].help
;
4073 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4077 if (add_title
&& help_open_keymap_title(view
, keymap
))
4082 add_line_text(view
, group
, LINE_HELP_GROUP
);
4086 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4087 enum_name(req_info
[i
]), req_info
[i
].help
);
4090 group
= "External commands:";
4092 for (i
= 0; i
< run_requests
; i
++) {
4093 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4097 if (!req
|| req
->keymap
!= keymap
)
4100 key
= get_key_name(req
->key
);
4102 key
= "(no key defined)";
4104 if (add_title
&& help_open_keymap_title(view
, keymap
))
4107 add_line_text(view
, group
, LINE_HELP_GROUP
);
4111 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4112 if (!string_format_from(buf
, &bufpos
, "%s%s",
4113 argc
? " " : "", req
->argv
[argc
]))
4116 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4121 help_open(struct view
*view
)
4126 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4127 add_line_text(view
, "", LINE_DEFAULT
);
4129 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4130 help_open_keymap(view
, keymap
);
4136 help_request(struct view
*view
, enum request request
, struct line
*line
)
4140 if (line
->type
== LINE_HELP_KEYMAP
) {
4141 help_keymap_hidden
[line
->other
] =
4142 !help_keymap_hidden
[line
->other
];
4143 view
->p_restore
= TRUE
;
4144 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4149 return pager_request(view
, request
, line
);
4153 static struct view_ops help_ops
= {
4169 struct tree_stack_entry
{
4170 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4171 unsigned long lineno
; /* Line number to restore */
4172 char *name
; /* Position of name in opt_path */
4175 /* The top of the path stack. */
4176 static struct tree_stack_entry
*tree_stack
= NULL
;
4177 unsigned long tree_lineno
= 0;
4180 pop_tree_stack_entry(void)
4182 struct tree_stack_entry
*entry
= tree_stack
;
4184 tree_lineno
= entry
->lineno
;
4186 tree_stack
= entry
->prev
;
4191 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4193 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4194 size_t pathlen
= strlen(opt_path
);
4199 entry
->prev
= tree_stack
;
4200 entry
->name
= opt_path
+ pathlen
;
4203 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4204 pop_tree_stack_entry();
4208 /* Move the current line to the first tree entry. */
4210 entry
->lineno
= lineno
;
4213 /* Parse output from git-ls-tree(1):
4215 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4218 #define SIZEOF_TREE_ATTR \
4219 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4221 #define SIZEOF_TREE_MODE \
4222 STRING_SIZE("100644 ")
4224 #define TREE_ID_OFFSET \
4225 STRING_SIZE("100644 blob ")
4228 char id
[SIZEOF_REV
];
4230 struct time time
; /* Date from the author ident. */
4231 const char *author
; /* Author of the commit. */
4236 tree_path(const struct line
*line
)
4238 return ((struct tree_entry
*) line
->data
)->name
;
4242 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4244 if (line1
->type
!= line2
->type
)
4245 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4246 return strcmp(tree_path(line1
), tree_path(line2
));
4249 static const enum sort_field tree_sort_fields
[] = {
4250 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4252 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4255 tree_compare(const void *l1
, const void *l2
)
4257 const struct line
*line1
= (const struct line
*) l1
;
4258 const struct line
*line2
= (const struct line
*) l2
;
4259 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4260 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4262 if (line1
->type
== LINE_TREE_HEAD
)
4264 if (line2
->type
== LINE_TREE_HEAD
)
4267 switch (get_sort_field(tree_sort_state
)) {
4269 return sort_order(tree_sort_state
, timecmp(&entry1
->time
, &entry2
->time
));
4271 case ORDERBY_AUTHOR
:
4272 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4276 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4281 static struct line
*
4282 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4283 const char *mode
, const char *id
)
4285 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4286 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4288 if (!entry
|| !line
) {
4293 strncpy(entry
->name
, path
, strlen(path
));
4295 entry
->mode
= strtoul(mode
, NULL
, 8);
4297 string_copy_rev(entry
->id
, id
);
4303 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4305 static const char *author_name
;
4306 static struct time author_time
;
4308 if (!text
&& *read_date
) {
4313 char *path
= *opt_path
? opt_path
: ".";
4314 /* Find next entry to process */
4315 const char *log_file
[] = {
4316 "git", "log", "--no-color", "--pretty=raw",
4317 "--cc", "--raw", view
->id
, "--", path
, NULL
4322 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4323 report("Tree is empty");
4327 if (!run_io_rd(&io
, log_file
, opt_cdup
, FORMAT_NONE
)) {
4328 report("Failed to load tree data");
4332 done_io(view
->pipe
);
4337 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4338 parse_author_line(text
+ STRING_SIZE("author "),
4339 &author_name
, &author_time
);
4341 } else if (*text
== ':') {
4343 size_t annotated
= 1;
4346 pos
= strchr(text
, '\t');
4350 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4351 text
+= strlen(opt_path
);
4352 pos
= strchr(text
, '/');
4356 for (i
= 1; i
< view
->lines
; i
++) {
4357 struct line
*line
= &view
->line
[i
];
4358 struct tree_entry
*entry
= line
->data
;
4360 annotated
+= !!entry
->author
;
4361 if (entry
->author
|| strcmp(entry
->name
, text
))
4364 entry
->author
= author_name
;
4365 entry
->time
= author_time
;
4370 if (annotated
== view
->lines
)
4371 kill_io(view
->pipe
);
4377 tree_read(struct view
*view
, char *text
)
4379 static bool read_date
= FALSE
;
4380 struct tree_entry
*data
;
4381 struct line
*entry
, *line
;
4382 enum line_type type
;
4383 size_t textlen
= text
? strlen(text
) : 0;
4384 char *path
= text
+ SIZEOF_TREE_ATTR
;
4386 if (read_date
|| !text
)
4387 return tree_read_date(view
, text
, &read_date
);
4389 if (textlen
<= SIZEOF_TREE_ATTR
)
4391 if (view
->lines
== 0 &&
4392 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4395 /* Strip the path part ... */
4397 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4398 size_t striplen
= strlen(opt_path
);
4400 if (pathlen
> striplen
)
4401 memmove(path
, path
+ striplen
,
4402 pathlen
- striplen
+ 1);
4404 /* Insert "link" to parent directory. */
4405 if (view
->lines
== 1 &&
4406 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4410 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4411 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4416 /* Skip "Directory ..." and ".." line. */
4417 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4418 if (tree_compare_entry(line
, entry
) <= 0)
4421 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4425 for (; line
<= entry
; line
++)
4426 line
->dirty
= line
->cleareol
= 1;
4430 if (tree_lineno
> view
->lineno
) {
4431 view
->lineno
= tree_lineno
;
4439 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4441 struct tree_entry
*entry
= line
->data
;
4443 if (line
->type
== LINE_TREE_HEAD
) {
4444 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4447 if (draw_mode(view
, entry
->mode
))
4450 if (opt_author
&& draw_author(view
, entry
->author
))
4453 if (opt_date
&& draw_date(view
, &entry
->time
))
4456 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4464 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4465 int fd
= mkstemp(file
);
4468 report("Failed to create temporary file");
4469 else if (!run_io_append(blob_ops
.argv
, FORMAT_ALL
, fd
))
4470 report("Failed to save blob data to file");
4478 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4480 enum open_flags flags
;
4483 case REQ_VIEW_BLAME
:
4484 if (line
->type
!= LINE_TREE_FILE
) {
4485 report("Blame only supported for files");
4489 string_copy(opt_ref
, view
->vid
);
4493 if (line
->type
!= LINE_TREE_FILE
) {
4494 report("Edit only supported for files");
4495 } else if (!is_head_commit(view
->vid
)) {
4498 open_editor(opt_file
);
4502 case REQ_TOGGLE_SORT_FIELD
:
4503 case REQ_TOGGLE_SORT_ORDER
:
4504 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4509 /* quit view if at top of tree */
4510 return REQ_VIEW_CLOSE
;
4513 line
= &view
->line
[1];
4523 /* Cleanup the stack if the tree view is at a different tree. */
4524 while (!*opt_path
&& tree_stack
)
4525 pop_tree_stack_entry();
4527 switch (line
->type
) {
4529 /* Depending on whether it is a subdirectory or parent link
4530 * mangle the path buffer. */
4531 if (line
== &view
->line
[1] && *opt_path
) {
4532 pop_tree_stack_entry();
4535 const char *basename
= tree_path(line
);
4537 push_tree_stack_entry(basename
, view
->lineno
);
4540 /* Trees and subtrees share the same ID, so they are not not
4541 * unique like blobs. */
4542 flags
= OPEN_RELOAD
;
4543 request
= REQ_VIEW_TREE
;
4546 case LINE_TREE_FILE
:
4547 flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4548 request
= REQ_VIEW_BLOB
;
4555 open_view(view
, request
, flags
);
4556 if (request
== REQ_VIEW_TREE
)
4557 view
->lineno
= tree_lineno
;
4563 tree_grep(struct view
*view
, struct line
*line
)
4565 struct tree_entry
*entry
= line
->data
;
4566 const char *text
[] = {
4568 opt_author
? entry
->author
: "",
4569 mkdate(&entry
->time
, opt_date
),
4573 return grep_text(view
, text
);
4577 tree_select(struct view
*view
, struct line
*line
)
4579 struct tree_entry
*entry
= line
->data
;
4581 if (line
->type
== LINE_TREE_FILE
) {
4582 string_copy_rev(ref_blob
, entry
->id
);
4583 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4585 } else if (line
->type
!= LINE_TREE_DIR
) {
4589 string_copy_rev(view
->ref
, entry
->id
);
4593 tree_prepare(struct view
*view
)
4595 if (view
->lines
== 0 && opt_prefix
[0]) {
4596 char *pos
= opt_prefix
;
4598 while (pos
&& *pos
) {
4599 char *end
= strchr(pos
, '/');
4603 push_tree_stack_entry(pos
, 0);
4611 } else if (strcmp(view
->vid
, view
->id
)) {
4615 return init_io_rd(&view
->io
, view
->ops
->argv
, opt_cdup
, FORMAT_ALL
);
4618 static const char *tree_argv
[SIZEOF_ARG
] = {
4619 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4622 static struct view_ops tree_ops
= {
4635 blob_read(struct view
*view
, char *line
)
4639 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4643 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4650 return pager_request(view
, request
, line
);
4654 static const char *blob_argv
[SIZEOF_ARG
] = {
4655 "git", "cat-file", "blob", "%(blob)", NULL
4658 static struct view_ops blob_ops
= {
4672 * Loading the blame view is a two phase job:
4674 * 1. File content is read either using opt_file from the
4675 * filesystem or using git-cat-file.
4676 * 2. Then blame information is incrementally added by
4677 * reading output from git-blame.
4680 static const char *blame_head_argv
[] = {
4681 "git", "blame", "--incremental", "--", "%(file)", NULL
4684 static const char *blame_ref_argv
[] = {
4685 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4688 static const char *blame_cat_file_argv
[] = {
4689 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4692 struct blame_commit
{
4693 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4694 char title
[128]; /* First line of the commit message. */
4695 const char *author
; /* Author of the commit. */
4696 struct time time
; /* Date from the author ident. */
4697 char filename
[128]; /* Name of file. */
4698 bool has_previous
; /* Was a "previous" line detected. */
4702 struct blame_commit
*commit
;
4703 unsigned long lineno
;
4708 blame_open(struct view
*view
)
4710 char path
[SIZEOF_STR
];
4712 if (!view
->parent
&& *opt_prefix
) {
4713 string_copy(path
, opt_file
);
4714 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4718 if (*opt_ref
|| !io_open(&view
->io
, "%s%s", opt_cdup
, opt_file
)) {
4719 if (!run_io_rd(&view
->io
, blame_cat_file_argv
, opt_cdup
, FORMAT_ALL
))
4723 setup_update(view
, opt_file
);
4724 string_format(view
->ref
, "%s ...", opt_file
);
4729 static struct blame_commit
*
4730 get_blame_commit(struct view
*view
, const char *id
)
4734 for (i
= 0; i
< view
->lines
; i
++) {
4735 struct blame
*blame
= view
->line
[i
].data
;
4740 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4741 return blame
->commit
;
4745 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4748 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4754 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4756 const char *pos
= *posref
;
4759 pos
= strchr(pos
+ 1, ' ');
4760 if (!pos
|| !isdigit(pos
[1]))
4762 *number
= atoi(pos
+ 1);
4763 if (*number
< min
|| *number
> max
)
4770 static struct blame_commit
*
4771 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4773 struct blame_commit
*commit
;
4774 struct blame
*blame
;
4775 const char *pos
= text
+ SIZEOF_REV
- 2;
4776 size_t orig_lineno
= 0;
4780 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4783 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4784 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4785 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4788 commit
= get_blame_commit(view
, text
);
4794 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4797 blame
->commit
= commit
;
4798 blame
->lineno
= orig_lineno
+ group
- 1;
4806 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4809 const char **argv
= *opt_ref
? blame_ref_argv
: blame_head_argv
;
4812 if (view
->lines
== 0 && !view
->parent
)
4813 die("No blame exist for %s", view
->vid
);
4815 if (view
->lines
== 0 || !run_io_rd(&io
, argv
, opt_cdup
, FORMAT_ALL
)) {
4816 report("Failed to load blame data");
4820 done_io(view
->pipe
);
4826 size_t linelen
= strlen(line
);
4827 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
4832 blame
->commit
= NULL
;
4833 strncpy(blame
->text
, line
, linelen
);
4834 blame
->text
[linelen
] = 0;
4835 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
4840 match_blame_header(const char *name
, char **line
)
4842 size_t namelen
= strlen(name
);
4843 bool matched
= !strncmp(name
, *line
, namelen
);
4852 blame_read(struct view
*view
, char *line
)
4854 static struct blame_commit
*commit
= NULL
;
4855 static int blamed
= 0;
4856 static bool read_file
= TRUE
;
4859 return blame_read_file(view
, line
, &read_file
);
4866 string_format(view
->ref
, "%s", view
->vid
);
4867 if (view_is_displayed(view
)) {
4868 update_view_title(view
);
4869 redraw_view_from(view
, 0);
4875 commit
= parse_blame_commit(view
, line
, &blamed
);
4876 string_format(view
->ref
, "%s %2d%%", view
->vid
,
4877 view
->lines
? blamed
* 100 / view
->lines
: 0);
4879 } else if (match_blame_header("author ", &line
)) {
4880 commit
->author
= get_author(line
);
4882 } else if (match_blame_header("author-time ", &line
)) {
4883 parse_timesec(&commit
->time
, line
);
4885 } else if (match_blame_header("author-tz ", &line
)) {
4886 parse_timezone(&commit
->time
, line
);
4888 } else if (match_blame_header("summary ", &line
)) {
4889 string_ncopy(commit
->title
, line
, strlen(line
));
4891 } else if (match_blame_header("previous ", &line
)) {
4892 commit
->has_previous
= TRUE
;
4894 } else if (match_blame_header("filename ", &line
)) {
4895 string_ncopy(commit
->filename
, line
, strlen(line
));
4903 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4905 struct blame
*blame
= line
->data
;
4906 struct time
*time
= NULL
;
4907 const char *id
= NULL
, *author
= NULL
;
4908 char text
[SIZEOF_STR
];
4910 if (blame
->commit
&& *blame
->commit
->filename
) {
4911 id
= blame
->commit
->id
;
4912 author
= blame
->commit
->author
;
4913 time
= &blame
->commit
->time
;
4916 if (opt_date
&& draw_date(view
, time
))
4919 if (opt_author
&& draw_author(view
, author
))
4922 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
4925 if (draw_lineno(view
, lineno
))
4928 string_expand(text
, sizeof(text
), blame
->text
, opt_tab_size
);
4929 draw_text(view
, LINE_DEFAULT
, text
, TRUE
);
4934 check_blame_commit(struct blame
*blame
, bool check_null_id
)
4937 report("Commit data not loaded yet");
4938 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4939 report("No commit exist for the selected line");
4946 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
4948 const char *diff_tree_argv
[] = {
4949 "git", "diff-tree", "-U0", blame
->commit
->id
,
4950 "--", blame
->commit
->filename
, NULL
4953 int parent_lineno
= -1;
4954 int blamed_lineno
= -1;
4957 if (!run_io(&io
, diff_tree_argv
, NULL
, IO_RD
))
4960 while ((line
= io_get(&io
, '\n', TRUE
))) {
4962 char *pos
= strchr(line
, '+');
4964 parent_lineno
= atoi(line
+ 4);
4966 blamed_lineno
= atoi(pos
+ 1);
4968 } else if (*line
== '+' && parent_lineno
!= -1) {
4969 if (blame
->lineno
== blamed_lineno
- 1 &&
4970 !strcmp(blame
->text
, line
+ 1)) {
4971 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
4982 blame_request(struct view
*view
, enum request request
, struct line
*line
)
4984 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4985 struct blame
*blame
= line
->data
;
4988 case REQ_VIEW_BLAME
:
4989 if (check_blame_commit(blame
, TRUE
)) {
4990 string_copy(opt_ref
, blame
->commit
->id
);
4991 string_copy(opt_file
, blame
->commit
->filename
);
4993 view
->lineno
= blame
->lineno
;
4994 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
4999 if (check_blame_commit(blame
, TRUE
) &&
5000 select_commit_parent(blame
->commit
->id
, opt_ref
,
5001 blame
->commit
->filename
)) {
5002 string_copy(opt_file
, blame
->commit
->filename
);
5003 setup_blame_parent_line(view
, blame
);
5004 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5009 if (!check_blame_commit(blame
, FALSE
))
5012 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
5013 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
5016 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
5017 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
5018 const char *diff_index_argv
[] = {
5019 "git", "diff-index", "--root", "--patch-with-stat",
5020 "-C", "-M", "HEAD", "--", view
->vid
, NULL
5023 if (!blame
->commit
->has_previous
) {
5024 diff_index_argv
[1] = "diff";
5025 diff_index_argv
[2] = "--no-color";
5026 diff_index_argv
[6] = "--";
5027 diff_index_argv
[7] = "/dev/null";
5030 if (!prepare_update(diff
, diff_index_argv
, NULL
, FORMAT_DASH
)) {
5031 report("Failed to allocate diff command");
5034 flags
|= OPEN_PREPARED
;
5037 open_view(view
, REQ_VIEW_DIFF
, flags
);
5038 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5039 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
5050 blame_grep(struct view
*view
, struct line
*line
)
5052 struct blame
*blame
= line
->data
;
5053 struct blame_commit
*commit
= blame
->commit
;
5054 const char *text
[] = {
5056 commit
? commit
->title
: "",
5057 commit
? commit
->id
: "",
5058 commit
&& opt_author
? commit
->author
: "",
5059 commit
? mkdate(&commit
->time
, opt_date
) : "",
5063 return grep_text(view
, text
);
5067 blame_select(struct view
*view
, struct line
*line
)
5069 struct blame
*blame
= line
->data
;
5070 struct blame_commit
*commit
= blame
->commit
;
5075 if (!strcmp(commit
->id
, NULL_ID
))
5076 string_ncopy(ref_commit
, "HEAD", 4);
5078 string_copy_rev(ref_commit
, commit
->id
);
5081 static struct view_ops blame_ops
= {
5097 const char *author
; /* Author of the last commit. */
5098 struct time time
; /* Date of the last activity. */
5099 const struct ref
*ref
; /* Name and commit ID information. */
5102 static const struct ref branch_all
;
5104 static const enum sort_field branch_sort_fields
[] = {
5105 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5107 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5110 branch_compare(const void *l1
, const void *l2
)
5112 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5113 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5115 switch (get_sort_field(branch_sort_state
)) {
5117 return sort_order(branch_sort_state
, timecmp(&branch1
->time
, &branch2
->time
));
5119 case ORDERBY_AUTHOR
:
5120 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5124 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5129 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5131 struct branch
*branch
= line
->data
;
5132 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5134 if (opt_date
&& draw_date(view
, &branch
->time
))
5137 if (opt_author
&& draw_author(view
, branch
->author
))
5140 draw_text(view
, type
, branch
->ref
== &branch_all
? "All branches" : branch
->ref
->name
, TRUE
);
5145 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5147 struct branch
*branch
= line
->data
;
5152 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5155 case REQ_TOGGLE_SORT_FIELD
:
5156 case REQ_TOGGLE_SORT_ORDER
:
5157 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5161 if (branch
->ref
== &branch_all
) {
5162 const char *all_branches_argv
[] = {
5163 "git", "log", "--no-color", "--pretty=raw", "--parents",
5164 "--topo-order", "--all", NULL
5166 struct view
*main_view
= VIEW(REQ_VIEW_MAIN
);
5168 if (!prepare_update(main_view
, all_branches_argv
, NULL
, FORMAT_NONE
)) {
5169 report("Failed to load view of all branches");
5172 open_view(view
, REQ_VIEW_MAIN
, OPEN_PREPARED
| OPEN_SPLIT
);
5174 open_view(view
, REQ_VIEW_MAIN
, OPEN_SPLIT
);
5184 branch_read(struct view
*view
, char *line
)
5186 static char id
[SIZEOF_REV
];
5187 struct branch
*reference
;
5193 switch (get_line_type(line
)) {
5195 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5199 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5200 struct branch
*branch
= view
->line
[i
].data
;
5202 if (strcmp(branch
->ref
->id
, id
))
5205 view
->line
[i
].dirty
= TRUE
;
5207 branch
->author
= reference
->author
;
5208 branch
->time
= reference
->time
;
5212 parse_author_line(line
+ STRING_SIZE("author "),
5213 &branch
->author
, &branch
->time
);
5225 branch_open_visitor(void *data
, const struct ref
*ref
)
5227 struct view
*view
= data
;
5228 struct branch
*branch
;
5230 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5233 branch
= calloc(1, sizeof(*branch
));
5238 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5242 branch_open(struct view
*view
)
5244 const char *branch_log
[] = {
5245 "git", "log", "--no-color", "--pretty=raw",
5246 "--simplify-by-decoration", "--all", NULL
5249 if (!run_io_rd(&view
->io
, branch_log
, NULL
, FORMAT_NONE
)) {
5250 report("Failed to load branch data");
5254 setup_update(view
, view
->id
);
5255 branch_open_visitor(view
, &branch_all
);
5256 foreach_ref(branch_open_visitor
, view
);
5257 view
->p_restore
= TRUE
;
5263 branch_grep(struct view
*view
, struct line
*line
)
5265 struct branch
*branch
= line
->data
;
5266 const char *text
[] = {
5272 return grep_text(view
, text
);
5276 branch_select(struct view
*view
, struct line
*line
)
5278 struct branch
*branch
= line
->data
;
5280 string_copy_rev(view
->ref
, branch
->ref
->id
);
5281 string_copy_rev(ref_commit
, branch
->ref
->id
);
5282 string_copy_rev(ref_head
, branch
->ref
->id
);
5285 static struct view_ops branch_ops
= {
5304 char rev
[SIZEOF_REV
];
5305 char name
[SIZEOF_STR
];
5309 char rev
[SIZEOF_REV
];
5310 char name
[SIZEOF_STR
];
5314 static char status_onbranch
[SIZEOF_STR
];
5315 static struct status stage_status
;
5316 static enum line_type stage_line_type
;
5317 static size_t stage_chunks
;
5318 static int *stage_chunk
;
5320 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5322 /* This should work even for the "On branch" line. */
5324 status_has_none(struct view
*view
, struct line
*line
)
5326 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5329 /* Get fields from the diff line:
5330 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5333 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5335 const char *old_mode
= buf
+ 1;
5336 const char *new_mode
= buf
+ 8;
5337 const char *old_rev
= buf
+ 15;
5338 const char *new_rev
= buf
+ 56;
5339 const char *status
= buf
+ 97;
5342 old_mode
[-1] != ':' ||
5343 new_mode
[-1] != ' ' ||
5344 old_rev
[-1] != ' ' ||
5345 new_rev
[-1] != ' ' ||
5349 file
->status
= *status
;
5351 string_copy_rev(file
->old
.rev
, old_rev
);
5352 string_copy_rev(file
->new.rev
, new_rev
);
5354 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5355 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5357 file
->old
.name
[0] = file
->new.name
[0] = 0;
5363 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5365 struct status
*unmerged
= NULL
;
5369 if (!run_io(&io
, argv
, opt_cdup
, IO_RD
))
5372 add_line_data(view
, NULL
, type
);
5374 while ((buf
= io_get(&io
, 0, TRUE
))) {
5375 struct status
*file
= unmerged
;
5378 file
= calloc(1, sizeof(*file
));
5379 if (!file
|| !add_line_data(view
, file
, type
))
5383 /* Parse diff info part. */
5385 file
->status
= status
;
5387 string_copy(file
->old
.rev
, NULL_ID
);
5389 } else if (!file
->status
|| file
== unmerged
) {
5390 if (!status_get_diff(file
, buf
, strlen(buf
)))
5393 buf
= io_get(&io
, 0, TRUE
);
5397 /* Collapse all modified entries that follow an
5398 * associated unmerged entry. */
5399 if (unmerged
== file
) {
5400 unmerged
->status
= 'U';
5402 } else if (file
->status
== 'U') {
5407 /* Grab the old name for rename/copy. */
5408 if (!*file
->old
.name
&&
5409 (file
->status
== 'R' || file
->status
== 'C')) {
5410 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5412 buf
= io_get(&io
, 0, TRUE
);
5417 /* git-ls-files just delivers a NUL separated list of
5418 * file names similar to the second half of the
5419 * git-diff-* output. */
5420 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5421 if (!*file
->old
.name
)
5422 string_copy(file
->old
.name
, file
->new.name
);
5426 if (io_error(&io
)) {
5432 if (!view
->line
[view
->lines
- 1].data
)
5433 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5439 /* Don't show unmerged entries in the staged section. */
5440 static const char *status_diff_index_argv
[] = {
5441 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5442 "--cached", "-M", "HEAD", NULL
5445 static const char *status_diff_files_argv
[] = {
5446 "git", "diff-files", "-z", NULL
5449 static const char *status_list_other_argv
[] = {
5450 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix
, NULL
5453 static const char *status_list_no_head_argv
[] = {
5454 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5457 static const char *update_index_argv
[] = {
5458 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5461 /* Restore the previous line number to stay in the context or select a
5462 * line with something that can be updated. */
5464 status_restore(struct view
*view
)
5466 if (view
->p_lineno
>= view
->lines
)
5467 view
->p_lineno
= view
->lines
- 1;
5468 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5470 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5473 /* If the above fails, always skip the "On branch" line. */
5474 if (view
->p_lineno
< view
->lines
)
5475 view
->lineno
= view
->p_lineno
;
5479 if (view
->lineno
< view
->offset
)
5480 view
->offset
= view
->lineno
;
5481 else if (view
->offset
+ view
->height
<= view
->lineno
)
5482 view
->offset
= view
->lineno
- view
->height
+ 1;
5484 view
->p_restore
= FALSE
;
5488 status_update_onbranch(void)
5490 static const char *paths
[][2] = {
5491 { "rebase-apply/rebasing", "Rebasing" },
5492 { "rebase-apply/applying", "Applying mailbox" },
5493 { "rebase-apply/", "Rebasing mailbox" },
5494 { "rebase-merge/interactive", "Interactive rebase" },
5495 { "rebase-merge/", "Rebase merge" },
5496 { "MERGE_HEAD", "Merging" },
5497 { "BISECT_LOG", "Bisecting" },
5498 { "HEAD", "On branch" },
5500 char buf
[SIZEOF_STR
];
5504 if (is_initial_commit()) {
5505 string_copy(status_onbranch
, "Initial commit");
5509 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5510 char *head
= opt_head
;
5512 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5513 lstat(buf
, &stat
) < 0)
5519 if (io_open(&io
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5520 io_read_buf(&io
, buf
, sizeof(buf
))) {
5522 if (!prefixcmp(head
, "refs/heads/"))
5523 head
+= STRING_SIZE("refs/heads/");
5527 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5528 string_copy(status_onbranch
, opt_head
);
5532 string_copy(status_onbranch
, "Not currently on any branch");
5535 /* First parse staged info using git-diff-index(1), then parse unstaged
5536 * info using git-diff-files(1), and finally untracked files using
5537 * git-ls-files(1). */
5539 status_open(struct view
*view
)
5543 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5544 status_update_onbranch();
5546 run_io_bg(update_index_argv
);
5548 if (is_initial_commit()) {
5549 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5551 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5555 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5556 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5559 /* Restore the exact position or use the specialized restore
5561 if (!view
->p_restore
)
5562 status_restore(view
);
5567 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5569 struct status
*status
= line
->data
;
5570 enum line_type type
;
5574 switch (line
->type
) {
5575 case LINE_STAT_STAGED
:
5576 type
= LINE_STAT_SECTION
;
5577 text
= "Changes to be committed:";
5580 case LINE_STAT_UNSTAGED
:
5581 type
= LINE_STAT_SECTION
;
5582 text
= "Changed but not updated:";
5585 case LINE_STAT_UNTRACKED
:
5586 type
= LINE_STAT_SECTION
;
5587 text
= "Untracked files:";
5590 case LINE_STAT_NONE
:
5591 type
= LINE_DEFAULT
;
5592 text
= " (no files)";
5595 case LINE_STAT_HEAD
:
5596 type
= LINE_STAT_HEAD
;
5597 text
= status_onbranch
;
5604 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5606 buf
[0] = status
->status
;
5607 if (draw_text(view
, line
->type
, buf
, TRUE
))
5609 type
= LINE_DEFAULT
;
5610 text
= status
->new.name
;
5613 draw_text(view
, type
, text
, TRUE
);
5618 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5620 if (displayed_views() == 2 || display
[current_view
] != view
)
5621 maximize_view(view
);
5622 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5627 status_enter(struct view
*view
, struct line
*line
)
5629 struct status
*status
= line
->data
;
5630 const char *oldpath
= status
? status
->old
.name
: NULL
;
5631 /* Diffs for unmerged entries are empty when passing the new
5632 * path, so leave it empty. */
5633 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5635 enum open_flags split
;
5636 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5638 if (line
->type
== LINE_STAT_NONE
||
5639 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5640 report("No file to diff");
5644 switch (line
->type
) {
5645 case LINE_STAT_STAGED
:
5646 if (is_initial_commit()) {
5647 const char *no_head_diff_argv
[] = {
5648 "git", "diff", "--no-color", "--patch-with-stat",
5649 "--", "/dev/null", newpath
, NULL
5652 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
, FORMAT_DASH
))
5653 return status_load_error(view
, stage
, newpath
);
5655 const char *index_show_argv
[] = {
5656 "git", "diff-index", "--root", "--patch-with-stat",
5657 "-C", "-M", "--cached", "HEAD", "--",
5658 oldpath
, newpath
, NULL
5661 if (!prepare_update(stage
, index_show_argv
, opt_cdup
, FORMAT_DASH
))
5662 return status_load_error(view
, stage
, newpath
);
5666 info
= "Staged changes to %s";
5668 info
= "Staged changes";
5671 case LINE_STAT_UNSTAGED
:
5673 const char *files_show_argv
[] = {
5674 "git", "diff-files", "--root", "--patch-with-stat",
5675 "-C", "-M", "--", oldpath
, newpath
, NULL
5678 if (!prepare_update(stage
, files_show_argv
, opt_cdup
, FORMAT_DASH
))
5679 return status_load_error(view
, stage
, newpath
);
5681 info
= "Unstaged changes to %s";
5683 info
= "Unstaged changes";
5686 case LINE_STAT_UNTRACKED
:
5688 report("No file to show");
5692 if (!suffixcmp(status
->new.name
, -1, "/")) {
5693 report("Cannot display a directory");
5697 if (!prepare_update_file(stage
, newpath
))
5698 return status_load_error(view
, stage
, newpath
);
5699 info
= "Untracked file %s";
5702 case LINE_STAT_HEAD
:
5706 die("line type %d not handled in switch", line
->type
);
5709 split
= view_is_displayed(view
) ? OPEN_SPLIT
: 0;
5710 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5711 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5713 stage_status
= *status
;
5715 memset(&stage_status
, 0, sizeof(stage_status
));
5718 stage_line_type
= line
->type
;
5720 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5727 status_exists(struct status
*status
, enum line_type type
)
5729 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5730 unsigned long lineno
;
5732 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5733 struct line
*line
= &view
->line
[lineno
];
5734 struct status
*pos
= line
->data
;
5736 if (line
->type
!= type
)
5738 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5739 select_view_line(view
, lineno
);
5742 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5743 select_view_line(view
, lineno
);
5753 status_update_prepare(struct io
*io
, enum line_type type
)
5755 const char *staged_argv
[] = {
5756 "git", "update-index", "-z", "--index-info", NULL
5758 const char *others_argv
[] = {
5759 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5763 case LINE_STAT_STAGED
:
5764 return run_io(io
, staged_argv
, opt_cdup
, IO_WR
);
5766 case LINE_STAT_UNSTAGED
:
5767 case LINE_STAT_UNTRACKED
:
5768 return run_io(io
, others_argv
, opt_cdup
, IO_WR
);
5771 die("line type %d not handled in switch", type
);
5777 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5779 char buf
[SIZEOF_STR
];
5783 case LINE_STAT_STAGED
:
5784 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5787 status
->old
.name
, 0))
5791 case LINE_STAT_UNSTAGED
:
5792 case LINE_STAT_UNTRACKED
:
5793 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
5798 die("line type %d not handled in switch", type
);
5801 return io_write(io
, buf
, bufsize
);
5805 status_update_file(struct status
*status
, enum line_type type
)
5810 if (!status_update_prepare(&io
, type
))
5813 result
= status_update_write(&io
, status
, type
);
5814 return done_io(&io
) && result
;
5818 status_update_files(struct view
*view
, struct line
*line
)
5820 char buf
[sizeof(view
->ref
)];
5823 struct line
*pos
= view
->line
+ view
->lines
;
5826 int cursor_y
= -1, cursor_x
= -1;
5828 if (!status_update_prepare(&io
, line
->type
))
5831 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
5834 string_copy(buf
, view
->ref
);
5835 getsyx(cursor_y
, cursor_x
);
5836 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
5837 int almost_done
= file
* 100 / files
;
5839 if (almost_done
> done
) {
5841 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
5843 update_view_title(view
);
5844 setsyx(cursor_y
, cursor_x
);
5847 result
= status_update_write(&io
, line
->data
, line
->type
);
5849 string_copy(view
->ref
, buf
);
5851 return done_io(&io
) && result
;
5855 status_update(struct view
*view
)
5857 struct line
*line
= &view
->line
[view
->lineno
];
5859 assert(view
->lines
);
5862 /* This should work even for the "On branch" line. */
5863 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
5864 report("Nothing to update");
5868 if (!status_update_files(view
, line
+ 1)) {
5869 report("Failed to update file status");
5873 } else if (!status_update_file(line
->data
, line
->type
)) {
5874 report("Failed to update file status");
5882 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
5884 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
5885 if (type
== LINE_STAT_STAGED
) {
5886 report("Cannot revert changes to staged files");
5887 } else if (type
== LINE_STAT_UNTRACKED
) {
5888 report("Cannot revert changes to untracked files");
5889 } else if (has_none
) {
5890 report("Nothing to revert");
5892 report("Cannot revert changes to multiple files");
5895 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5896 char mode
[10] = "100644";
5897 const char *reset_argv
[] = {
5898 "git", "update-index", "--cacheinfo", mode
,
5899 status
->old
.rev
, status
->old
.name
, NULL
5901 const char *checkout_argv
[] = {
5902 "git", "checkout", "--", status
->old
.name
, NULL
5905 if (status
->status
== 'U') {
5906 string_format(mode
, "%5o", status
->old
.mode
);
5908 if (status
->old
.mode
== 0 && status
->new.mode
== 0) {
5909 reset_argv
[2] = "--force-remove";
5910 reset_argv
[3] = status
->old
.name
;
5911 reset_argv
[4] = NULL
;
5914 if (!run_io_fg(reset_argv
, opt_cdup
))
5916 if (status
->old
.mode
== 0 && status
->new.mode
== 0)
5920 return run_io_fg(checkout_argv
, opt_cdup
);
5927 status_request(struct view
*view
, enum request request
, struct line
*line
)
5929 struct status
*status
= line
->data
;
5932 case REQ_STATUS_UPDATE
:
5933 if (!status_update(view
))
5937 case REQ_STATUS_REVERT
:
5938 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
5942 case REQ_STATUS_MERGE
:
5943 if (!status
|| status
->status
!= 'U') {
5944 report("Merging only possible for files with unmerged status ('U').");
5947 open_mergetool(status
->new.name
);
5953 if (status
->status
== 'D') {
5954 report("File has been deleted.");
5958 open_editor(status
->new.name
);
5961 case REQ_VIEW_BLAME
:
5967 /* After returning the status view has been split to
5968 * show the stage view. No further reloading is
5970 return status_enter(view
, line
);
5973 /* Simply reload the view. */
5980 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
5986 status_select(struct view
*view
, struct line
*line
)
5988 struct status
*status
= line
->data
;
5989 char file
[SIZEOF_STR
] = "all files";
5993 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
5996 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
5999 switch (line
->type
) {
6000 case LINE_STAT_STAGED
:
6001 text
= "Press %s to unstage %s for commit";
6004 case LINE_STAT_UNSTAGED
:
6005 text
= "Press %s to stage %s for commit";
6008 case LINE_STAT_UNTRACKED
:
6009 text
= "Press %s to stage %s for addition";
6012 case LINE_STAT_HEAD
:
6013 case LINE_STAT_NONE
:
6014 text
= "Nothing to update";
6018 die("line type %d not handled in switch", line
->type
);
6021 if (status
&& status
->status
== 'U') {
6022 text
= "Press %s to resolve conflict in %s";
6023 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
6026 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
6029 string_format(view
->ref
, text
, key
, file
);
6031 string_copy(opt_file
, status
->new.name
);
6035 status_grep(struct view
*view
, struct line
*line
)
6037 struct status
*status
= line
->data
;
6040 const char buf
[2] = { status
->status
, 0 };
6041 const char *text
[] = { status
->new.name
, buf
, NULL
};
6043 return grep_text(view
, text
);
6049 static struct view_ops status_ops
= {
6062 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
6064 while (line
< end
) {
6065 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
6066 !io_write(io
, "\n", 1))
6069 if (line
->type
== LINE_DIFF_CHUNK
||
6070 line
->type
== LINE_DIFF_HEADER
)
6077 static struct line
*
6078 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
6080 for (; view
->line
< line
; line
--)
6081 if (line
->type
== type
)
6088 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
6090 const char *apply_argv
[SIZEOF_ARG
] = {
6091 "git", "apply", "--whitespace=nowarn", NULL
6093 struct line
*diff_hdr
;
6097 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6102 apply_argv
[argc
++] = "--cached";
6103 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6104 apply_argv
[argc
++] = "-R";
6105 apply_argv
[argc
++] = "-";
6106 apply_argv
[argc
++] = NULL
;
6107 if (!run_io(&io
, apply_argv
, opt_cdup
, IO_WR
))
6110 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6111 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6115 run_io_bg(update_index_argv
);
6117 return chunk
? TRUE
: FALSE
;
6121 stage_update(struct view
*view
, struct line
*line
)
6123 struct line
*chunk
= NULL
;
6125 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6126 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6129 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6130 report("Failed to apply chunk");
6134 } else if (!stage_status
.status
) {
6135 view
= VIEW(REQ_VIEW_STATUS
);
6137 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6138 if (line
->type
== stage_line_type
)
6141 if (!status_update_files(view
, line
+ 1)) {
6142 report("Failed to update files");
6146 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6147 report("Failed to update file");
6155 stage_revert(struct view
*view
, struct line
*line
)
6157 struct line
*chunk
= NULL
;
6159 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6160 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6163 if (!prompt_yesno("Are you sure you want to revert changes?"))
6166 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6167 report("Failed to revert chunk");
6173 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6174 stage_line_type
, FALSE
);
6180 stage_next(struct view
*view
, struct line
*line
)
6184 if (!stage_chunks
) {
6185 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6186 if (line
->type
!= LINE_DIFF_CHUNK
)
6189 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6190 report("Allocation failure");
6194 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6198 for (i
= 0; i
< stage_chunks
; i
++) {
6199 if (stage_chunk
[i
] > view
->lineno
) {
6200 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6201 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6206 report("No next chunk found");
6210 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6213 case REQ_STATUS_UPDATE
:
6214 if (!stage_update(view
, line
))
6218 case REQ_STATUS_REVERT
:
6219 if (!stage_revert(view
, line
))
6223 case REQ_STAGE_NEXT
:
6224 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6225 report("File is untracked; press %s to add",
6226 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6229 stage_next(view
, line
);
6233 if (!stage_status
.new.name
[0])
6235 if (stage_status
.status
== 'D') {
6236 report("File has been deleted.");
6240 open_editor(stage_status
.new.name
);
6244 /* Reload everything ... */
6247 case REQ_VIEW_BLAME
:
6248 if (stage_status
.new.name
[0]) {
6249 string_copy(opt_file
, stage_status
.new.name
);
6255 return pager_request(view
, request
, line
);
6261 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6262 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6264 /* Check whether the staged entry still exists, and close the
6265 * stage view if it doesn't. */
6266 if (!status_exists(&stage_status
, stage_line_type
)) {
6267 status_restore(VIEW(REQ_VIEW_STATUS
));
6268 return REQ_VIEW_CLOSE
;
6271 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6272 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6273 report("Cannot display a directory");
6277 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6278 report("Failed to open file: %s", strerror(errno
));
6282 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6287 static struct view_ops stage_ops
= {
6304 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6305 char title
[128]; /* First line of the commit message. */
6306 const char *author
; /* Author of the commit. */
6307 struct time time
; /* Date from the author ident. */
6308 struct ref_list
*refs
; /* Repository references. */
6309 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6310 size_t graph_size
; /* The width of the graph array. */
6311 bool has_parents
; /* Rewritten --parents seen. */
6314 /* Size of rev graph with no "padding" columns */
6315 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6318 struct rev_graph
*prev
, *next
, *parents
;
6319 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6321 struct commit
*commit
;
6323 unsigned int boundary
:1;
6326 /* Parents of the commit being visualized. */
6327 static struct rev_graph graph_parents
[4];
6329 /* The current stack of revisions on the graph. */
6330 static struct rev_graph graph_stacks
[4] = {
6331 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6332 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6333 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6334 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6338 graph_parent_is_merge(struct rev_graph
*graph
)
6340 return graph
->parents
->size
> 1;
6344 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6346 struct commit
*commit
= graph
->commit
;
6348 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6349 commit
->graph
[commit
->graph_size
++] = symbol
;
6353 clear_rev_graph(struct rev_graph
*graph
)
6355 graph
->boundary
= 0;
6356 graph
->size
= graph
->pos
= 0;
6357 graph
->commit
= NULL
;
6358 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6362 done_rev_graph(struct rev_graph
*graph
)
6364 if (graph_parent_is_merge(graph
) &&
6365 graph
->pos
< graph
->size
- 1 &&
6366 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6367 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6369 graph
->commit
->graph_size
= i
* 2;
6370 while (i
< graph
->next
->size
- 1) {
6371 append_to_rev_graph(graph
, ' ');
6372 append_to_rev_graph(graph
, '\\');
6377 clear_rev_graph(graph
);
6381 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6385 /* "Collapse" duplicate parents lines.
6387 * FIXME: This needs to also update update the drawn graph but
6388 * for now it just serves as a method for pruning graph lines. */
6389 for (i
= 0; i
< graph
->size
; i
++)
6390 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6393 if (graph
->size
< SIZEOF_REVITEMS
) {
6394 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6399 get_rev_graph_symbol(struct rev_graph
*graph
)
6403 if (graph
->boundary
)
6404 symbol
= REVGRAPH_BOUND
;
6405 else if (graph
->parents
->size
== 0)
6406 symbol
= REVGRAPH_INIT
;
6407 else if (graph_parent_is_merge(graph
))
6408 symbol
= REVGRAPH_MERGE
;
6409 else if (graph
->pos
>= graph
->size
)
6410 symbol
= REVGRAPH_BRANCH
;
6412 symbol
= REVGRAPH_COMMIT
;
6418 draw_rev_graph(struct rev_graph
*graph
)
6421 chtype separator
, line
;
6423 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6424 static struct rev_filler fillers
[] = {
6430 chtype symbol
= get_rev_graph_symbol(graph
);
6431 struct rev_filler
*filler
;
6434 fillers
[DEFAULT
].line
= opt_line_graphics
? ACS_VLINE
: '|';
6435 filler
= &fillers
[DEFAULT
];
6437 for (i
= 0; i
< graph
->pos
; i
++) {
6438 append_to_rev_graph(graph
, filler
->line
);
6439 if (graph_parent_is_merge(graph
->prev
) &&
6440 graph
->prev
->pos
== i
)
6441 filler
= &fillers
[RSHARP
];
6443 append_to_rev_graph(graph
, filler
->separator
);
6446 /* Place the symbol for this revision. */
6447 append_to_rev_graph(graph
, symbol
);
6449 if (graph
->prev
->size
> graph
->size
)
6450 filler
= &fillers
[RDIAG
];
6452 filler
= &fillers
[DEFAULT
];
6456 for (; i
< graph
->size
; i
++) {
6457 append_to_rev_graph(graph
, filler
->separator
);
6458 append_to_rev_graph(graph
, filler
->line
);
6459 if (graph_parent_is_merge(graph
->prev
) &&
6460 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6461 filler
= &fillers
[RSHARP
];
6462 if (graph
->prev
->size
> graph
->size
)
6463 filler
= &fillers
[LDIAG
];
6466 if (graph
->prev
->size
> graph
->size
) {
6467 append_to_rev_graph(graph
, filler
->separator
);
6468 if (filler
->line
!= ' ')
6469 append_to_rev_graph(graph
, filler
->line
);
6473 /* Prepare the next rev graph */
6475 prepare_rev_graph(struct rev_graph
*graph
)
6479 /* First, traverse all lines of revisions up to the active one. */
6480 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6481 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6484 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6487 /* Interleave the new revision parent(s). */
6488 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6489 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6491 /* Lastly, put any remaining revisions. */
6492 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6493 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6497 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6499 /* If this is the finalizing update ... */
6501 prepare_rev_graph(graph
);
6503 /* Graph visualization needs a one rev look-ahead,
6504 * so the first update doesn't visualize anything. */
6505 if (!graph
->prev
->commit
)
6508 if (view
->lines
> 2)
6509 view
->line
[view
->lines
- 3].dirty
= 1;
6510 if (view
->lines
> 1)
6511 view
->line
[view
->lines
- 2].dirty
= 1;
6512 draw_rev_graph(graph
->prev
);
6513 done_rev_graph(graph
->prev
->prev
);
6521 static const char *main_argv
[SIZEOF_ARG
] = {
6522 "git", "log", "--no-color", "--pretty=raw", "--parents",
6523 "--topo-order", "%(head)", NULL
6527 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6529 struct commit
*commit
= line
->data
;
6531 if (!commit
->author
)
6534 if (opt_date
&& draw_date(view
, &commit
->time
))
6537 if (opt_author
&& draw_author(view
, commit
->author
))
6540 if (opt_rev_graph
&& commit
->graph_size
&&
6541 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6544 if (opt_show_refs
&& commit
->refs
) {
6547 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6548 struct ref
*ref
= commit
->refs
->refs
[i
];
6549 enum line_type type
;
6552 type
= LINE_MAIN_HEAD
;
6554 type
= LINE_MAIN_LOCAL_TAG
;
6556 type
= LINE_MAIN_TAG
;
6557 else if (ref
->tracked
)
6558 type
= LINE_MAIN_TRACKED
;
6559 else if (ref
->remote
)
6560 type
= LINE_MAIN_REMOTE
;
6562 type
= LINE_MAIN_REF
;
6564 if (draw_text(view
, type
, "[", TRUE
) ||
6565 draw_text(view
, type
, ref
->name
, TRUE
) ||
6566 draw_text(view
, type
, "]", TRUE
))
6569 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6574 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6578 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6580 main_read(struct view
*view
, char *line
)
6582 static struct rev_graph
*graph
= graph_stacks
;
6583 enum line_type type
;
6584 struct commit
*commit
;
6589 if (!view
->lines
&& !view
->parent
)
6590 die("No revisions match the given arguments.");
6591 if (view
->lines
> 0) {
6592 commit
= view
->line
[view
->lines
- 1].data
;
6593 view
->line
[view
->lines
- 1].dirty
= 1;
6594 if (!commit
->author
) {
6597 graph
->commit
= NULL
;
6600 update_rev_graph(view
, graph
);
6602 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6603 clear_rev_graph(&graph_stacks
[i
]);
6607 type
= get_line_type(line
);
6608 if (type
== LINE_COMMIT
) {
6609 commit
= calloc(1, sizeof(struct commit
));
6613 line
+= STRING_SIZE("commit ");
6615 graph
->boundary
= 1;
6619 string_copy_rev(commit
->id
, line
);
6620 commit
->refs
= get_ref_list(commit
->id
);
6621 graph
->commit
= commit
;
6622 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6624 while ((line
= strchr(line
, ' '))) {
6626 push_rev_graph(graph
->parents
, line
);
6627 commit
->has_parents
= TRUE
;
6634 commit
= view
->line
[view
->lines
- 1].data
;
6638 if (commit
->has_parents
)
6640 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6644 parse_author_line(line
+ STRING_SIZE("author "),
6645 &commit
->author
, &commit
->time
);
6646 update_rev_graph(view
, graph
);
6647 graph
= graph
->next
;
6651 /* Fill in the commit title if it has not already been set. */
6652 if (commit
->title
[0])
6655 /* Require titles to start with a non-space character at the
6656 * offset used by git log. */
6657 if (strncmp(line
, " ", 4))
6660 /* Well, if the title starts with a whitespace character,
6661 * try to be forgiving. Otherwise we end up with no title. */
6662 while (isspace(*line
))
6666 /* FIXME: More graceful handling of titles; append "..." to
6667 * shortened titles, etc. */
6669 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6670 view
->line
[view
->lines
- 1].dirty
= 1;
6677 main_request(struct view
*view
, enum request request
, struct line
*line
)
6679 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
6683 open_view(view
, REQ_VIEW_DIFF
, flags
);
6687 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6697 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6702 if (!opt_show_refs
|| !list
)
6705 for (i
= 0; i
< list
->size
; i
++) {
6706 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6714 main_grep(struct view
*view
, struct line
*line
)
6716 struct commit
*commit
= line
->data
;
6717 const char *text
[] = {
6719 opt_author
? commit
->author
: "",
6720 mkdate(&commit
->time
, opt_date
),
6724 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6728 main_select(struct view
*view
, struct line
*line
)
6730 struct commit
*commit
= line
->data
;
6732 string_copy_rev(view
->ref
, commit
->id
);
6733 string_copy_rev(ref_commit
, view
->ref
);
6736 static struct view_ops main_ops
= {
6749 * Unicode / UTF-8 handling
6751 * NOTE: Much of the following code for dealing with Unicode is derived from
6752 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6753 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6757 unicode_width(unsigned long c
, int tab_size
)
6760 (c
<= 0x115f /* Hangul Jamo */
6763 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
6765 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
6766 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
6767 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
6768 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
6769 || (c
>= 0xffe0 && c
<= 0xffe6)
6770 || (c
>= 0x20000 && c
<= 0x2fffd)
6771 || (c
>= 0x30000 && c
<= 0x3fffd)))
6780 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6781 * Illegal bytes are set one. */
6782 static const unsigned char utf8_bytes
[256] = {
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 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,
6788 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,
6789 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,
6790 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,
6793 static inline unsigned char
6794 utf8_char_length(const char *string
, const char *end
)
6796 int c
= *(unsigned char *) string
;
6798 return utf8_bytes
[c
];
6801 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6802 static inline unsigned long
6803 utf8_to_unicode(const char *string
, size_t length
)
6805 unsigned long unicode
;
6809 unicode
= string
[0];
6812 unicode
= (string
[0] & 0x1f) << 6;
6813 unicode
+= (string
[1] & 0x3f);
6816 unicode
= (string
[0] & 0x0f) << 12;
6817 unicode
+= ((string
[1] & 0x3f) << 6);
6818 unicode
+= (string
[2] & 0x3f);
6821 unicode
= (string
[0] & 0x0f) << 18;
6822 unicode
+= ((string
[1] & 0x3f) << 12);
6823 unicode
+= ((string
[2] & 0x3f) << 6);
6824 unicode
+= (string
[3] & 0x3f);
6827 unicode
= (string
[0] & 0x0f) << 24;
6828 unicode
+= ((string
[1] & 0x3f) << 18);
6829 unicode
+= ((string
[2] & 0x3f) << 12);
6830 unicode
+= ((string
[3] & 0x3f) << 6);
6831 unicode
+= (string
[4] & 0x3f);
6834 unicode
= (string
[0] & 0x01) << 30;
6835 unicode
+= ((string
[1] & 0x3f) << 24);
6836 unicode
+= ((string
[2] & 0x3f) << 18);
6837 unicode
+= ((string
[3] & 0x3f) << 12);
6838 unicode
+= ((string
[4] & 0x3f) << 6);
6839 unicode
+= (string
[5] & 0x3f);
6842 die("Invalid Unicode length");
6845 /* Invalid characters could return the special 0xfffd value but NUL
6846 * should be just as good. */
6847 return unicode
> 0xffff ? 0 : unicode
;
6850 /* Calculates how much of string can be shown within the given maximum width
6851 * and sets trimmed parameter to non-zero value if all of string could not be
6852 * shown. If the reserve flag is TRUE, it will reserve at least one
6853 * trailing character, which can be useful when drawing a delimiter.
6855 * Returns the number of bytes to output from string to satisfy max_width. */
6857 utf8_length(const char **start
, size_t skip
, int *width
, size_t max_width
, int *trimmed
, bool reserve
, int tab_size
)
6859 const char *string
= *start
;
6860 const char *end
= strchr(string
, '\0');
6861 unsigned char last_bytes
= 0;
6862 size_t last_ucwidth
= 0;
6867 while (string
< end
) {
6868 unsigned char bytes
= utf8_char_length(string
, end
);
6870 unsigned long unicode
;
6872 if (string
+ bytes
> end
)
6875 /* Change representation to figure out whether
6876 * it is a single- or double-width character. */
6878 unicode
= utf8_to_unicode(string
, bytes
);
6879 /* FIXME: Graceful handling of invalid Unicode character. */
6883 ucwidth
= unicode_width(unicode
, tab_size
);
6885 skip
-= ucwidth
<= skip
? ucwidth
: skip
;
6889 if (*width
> max_width
) {
6892 if (reserve
&& *width
== max_width
) {
6893 string
-= last_bytes
;
6894 *width
-= last_ucwidth
;
6900 last_bytes
= ucwidth
? bytes
: 0;
6901 last_ucwidth
= ucwidth
;
6904 return string
- *start
;
6912 /* Whether or not the curses interface has been initialized. */
6913 static bool cursed
= FALSE
;
6915 /* Terminal hacks and workarounds. */
6916 static bool use_scroll_redrawwin
;
6917 static bool use_scroll_status_wclear
;
6919 /* The status window is used for polling keystrokes. */
6920 static WINDOW
*status_win
;
6922 /* Reading from the prompt? */
6923 static bool input_mode
= FALSE
;
6925 static bool status_empty
= FALSE
;
6927 /* Update status and title window. */
6929 report(const char *msg
, ...)
6931 struct view
*view
= display
[current_view
];
6937 char buf
[SIZEOF_STR
];
6940 va_start(args
, msg
);
6941 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6942 buf
[sizeof(buf
) - 1] = 0;
6943 buf
[sizeof(buf
) - 2] = '.';
6944 buf
[sizeof(buf
) - 3] = '.';
6945 buf
[sizeof(buf
) - 4] = '.';
6951 if (!status_empty
|| *msg
) {
6954 va_start(args
, msg
);
6956 wmove(status_win
, 0, 0);
6957 if (view
->has_scrolled
&& use_scroll_status_wclear
)
6960 vwprintw(status_win
, msg
, args
);
6961 status_empty
= FALSE
;
6963 status_empty
= TRUE
;
6965 wclrtoeol(status_win
);
6966 wnoutrefresh(status_win
);
6971 update_view_title(view
);
6980 /* Initialize the curses library */
6981 if (isatty(STDIN_FILENO
)) {
6982 cursed
= !!initscr();
6985 /* Leave stdin and stdout alone when acting as a pager. */
6986 opt_tty
= fopen("/dev/tty", "r+");
6988 die("Failed to open /dev/tty");
6989 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
6993 die("Failed to initialize curses");
6995 nonl(); /* Disable conversion and detect newlines from input. */
6996 cbreak(); /* Take input chars one at a time, no wait for \n */
6997 noecho(); /* Don't echo input */
6998 leaveok(stdscr
, FALSE
);
7003 getmaxyx(stdscr
, y
, x
);
7004 status_win
= newwin(1, 0, y
- 1, 0);
7006 die("Failed to create status window");
7008 /* Enable keyboard mapping */
7009 keypad(status_win
, TRUE
);
7010 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
7012 TABSIZE
= opt_tab_size
;
7014 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
7015 if (term
&& !strcmp(term
, "gnome-terminal")) {
7016 /* In the gnome-terminal-emulator, the message from
7017 * scrolling up one line when impossible followed by
7018 * scrolling down one line causes corruption of the
7019 * status line. This is fixed by calling wclear. */
7020 use_scroll_status_wclear
= TRUE
;
7021 use_scroll_redrawwin
= FALSE
;
7023 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
7024 /* No problems with full optimizations in xrvt-(unicode)
7026 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
7029 /* When scrolling in (u)xterm the last line in the
7030 * scrolling direction will update slowly. */
7031 use_scroll_redrawwin
= TRUE
;
7032 use_scroll_status_wclear
= FALSE
;
7037 get_input(int prompt_position
)
7040 int i
, key
, cursor_y
, cursor_x
;
7041 bool loading
= FALSE
;
7043 if (prompt_position
)
7047 foreach_view (view
, i
) {
7049 if (view_is_displayed(view
) && view
->has_scrolled
&&
7050 use_scroll_redrawwin
)
7051 redrawwin(view
->win
);
7052 view
->has_scrolled
= FALSE
;
7057 /* Update the cursor position. */
7058 if (prompt_position
) {
7059 getbegyx(status_win
, cursor_y
, cursor_x
);
7060 cursor_x
= prompt_position
;
7062 view
= display
[current_view
];
7063 getbegyx(view
->win
, cursor_y
, cursor_x
);
7064 cursor_x
= view
->width
- 1;
7065 cursor_y
+= view
->lineno
- view
->offset
;
7067 setsyx(cursor_y
, cursor_x
);
7069 /* Refresh, accept single keystroke of input */
7071 nodelay(status_win
, loading
);
7072 key
= wgetch(status_win
);
7074 /* wgetch() with nodelay() enabled returns ERR when
7075 * there's no input. */
7078 } else if (key
== KEY_RESIZE
) {
7081 getmaxyx(stdscr
, height
, width
);
7083 wresize(status_win
, 1, width
);
7084 mvwin(status_win
, height
- 1, 0);
7085 wnoutrefresh(status_win
);
7087 redraw_display(TRUE
);
7097 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7099 enum input_status status
= INPUT_OK
;
7100 static char buf
[SIZEOF_STR
];
7105 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7108 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7109 wclrtoeol(status_win
);
7111 key
= get_input(pos
+ 1);
7116 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7123 status
= INPUT_CANCEL
;
7127 status
= INPUT_CANCEL
;
7131 if (pos
>= sizeof(buf
)) {
7132 report("Input string too long");
7136 status
= handler(data
, buf
, key
);
7137 if (status
== INPUT_OK
)
7138 buf
[pos
++] = (char) key
;
7142 /* Clear the status window */
7143 status_empty
= FALSE
;
7146 if (status
== INPUT_CANCEL
)
7154 static enum input_status
7155 prompt_yesno_handler(void *data
, char *buf
, int c
)
7157 if (c
== 'y' || c
== 'Y')
7159 if (c
== 'n' || c
== 'N')
7160 return INPUT_CANCEL
;
7165 prompt_yesno(const char *prompt
)
7167 char prompt2
[SIZEOF_STR
];
7169 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7172 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7175 static enum input_status
7176 read_prompt_handler(void *data
, char *buf
, int c
)
7178 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7182 read_prompt(const char *prompt
)
7184 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7187 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7189 enum input_status status
= INPUT_OK
;
7192 while (items
[size
].text
)
7195 while (status
== INPUT_OK
) {
7196 const struct menu_item
*item
= &items
[*selected
];
7200 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7201 prompt
, *selected
+ 1, size
);
7203 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7204 wprintw(status_win
, "%s", item
->text
);
7205 wclrtoeol(status_win
);
7207 key
= get_input(COLS
- 1);
7212 status
= INPUT_STOP
;
7217 *selected
= *selected
- 1;
7219 *selected
= size
- 1;
7224 *selected
= (*selected
+ 1) % size
;
7228 status
= INPUT_CANCEL
;
7232 for (i
= 0; items
[i
].text
; i
++)
7233 if (items
[i
].hotkey
== key
) {
7235 status
= INPUT_STOP
;
7241 /* Clear the status window */
7242 status_empty
= FALSE
;
7245 return status
!= INPUT_CANCEL
;
7249 * Repository properties
7252 static struct ref
**refs
= NULL
;
7253 static size_t refs_size
= 0;
7254 static struct ref
*refs_head
= NULL
;
7256 static struct ref_list
**ref_lists
= NULL
;
7257 static size_t ref_lists_size
= 0;
7259 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7260 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7261 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7264 compare_refs(const void *ref1_
, const void *ref2_
)
7266 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7267 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7269 if (ref1
->tag
!= ref2
->tag
)
7270 return ref2
->tag
- ref1
->tag
;
7271 if (ref1
->ltag
!= ref2
->ltag
)
7272 return ref2
->ltag
- ref2
->ltag
;
7273 if (ref1
->head
!= ref2
->head
)
7274 return ref2
->head
- ref1
->head
;
7275 if (ref1
->tracked
!= ref2
->tracked
)
7276 return ref2
->tracked
- ref1
->tracked
;
7277 if (ref1
->remote
!= ref2
->remote
)
7278 return ref2
->remote
- ref1
->remote
;
7279 return strcmp(ref1
->name
, ref2
->name
);
7283 foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
)
7287 for (i
= 0; i
< refs_size
; i
++)
7288 if (!visitor(data
, refs
[i
]))
7298 static struct ref_list
*
7299 get_ref_list(const char *id
)
7301 struct ref_list
*list
;
7304 for (i
= 0; i
< ref_lists_size
; i
++)
7305 if (!strcmp(id
, ref_lists
[i
]->id
))
7306 return ref_lists
[i
];
7308 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7310 list
= calloc(1, sizeof(*list
));
7314 for (i
= 0; i
< refs_size
; i
++) {
7315 if (!strcmp(id
, refs
[i
]->id
) &&
7316 realloc_refs_list(&list
->refs
, list
->size
, 1))
7317 list
->refs
[list
->size
++] = refs
[i
];
7325 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7326 ref_lists
[ref_lists_size
++] = list
;
7331 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
7333 struct ref
*ref
= NULL
;
7336 bool remote
= FALSE
;
7337 bool tracked
= FALSE
;
7339 int from
= 0, to
= refs_size
- 1;
7341 if (!prefixcmp(name
, "refs/tags/")) {
7342 if (!suffixcmp(name
, namelen
, "^{}")) {
7350 namelen
-= STRING_SIZE("refs/tags/");
7351 name
+= STRING_SIZE("refs/tags/");
7353 } else if (!prefixcmp(name
, "refs/remotes/")) {
7355 namelen
-= STRING_SIZE("refs/remotes/");
7356 name
+= STRING_SIZE("refs/remotes/");
7357 tracked
= !strcmp(opt_remote
, name
);
7359 } else if (!prefixcmp(name
, "refs/heads/")) {
7360 namelen
-= STRING_SIZE("refs/heads/");
7361 name
+= STRING_SIZE("refs/heads/");
7362 if (!strncmp(opt_head
, name
, namelen
))
7365 } else if (!strcmp(name
, "HEAD")) {
7368 namelen
= strlen(opt_head
);
7373 /* If we are reloading or it's an annotated tag, replace the
7374 * previous SHA1 with the resolved commit id; relies on the fact
7375 * git-ls-remote lists the commit id of an annotated tag right
7376 * before the commit id it points to. */
7377 while (from
<= to
) {
7378 size_t pos
= (to
+ from
) / 2;
7379 int cmp
= strcmp(name
, refs
[pos
]->name
);
7393 if (!realloc_refs(&refs
, refs_size
, 1))
7395 ref
= calloc(1, sizeof(*ref
) + namelen
);
7398 memmove(refs
+ from
+ 1, refs
+ from
,
7399 (refs_size
- from
) * sizeof(*refs
));
7401 strncpy(ref
->name
, name
, namelen
);
7408 ref
->remote
= remote
;
7409 ref
->tracked
= tracked
;
7410 string_copy_rev(ref
->id
, id
);
7420 const char *head_argv
[] = {
7421 "git", "symbolic-ref", "HEAD", NULL
7423 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7424 "git", "ls-remote", opt_git_dir
, NULL
7426 static bool init
= FALSE
;
7430 argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE");
7437 if (run_io_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7438 !prefixcmp(opt_head
, "refs/heads/")) {
7439 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7441 memmove(opt_head
, offset
, strlen(offset
) + 1);
7445 for (i
= 0; i
< refs_size
; i
++)
7448 if (run_io_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7451 /* Update the ref lists to reflect changes. */
7452 for (i
= 0; i
< ref_lists_size
; i
++) {
7453 struct ref_list
*list
= ref_lists
[i
];
7456 for (old
= new = 0; old
< list
->size
; old
++)
7457 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7458 list
->refs
[new++] = list
->refs
[old
];
7466 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7468 if (!strcmp(name
, ".remote")) {
7469 string_ncopy(opt_remote
, value
, valuelen
);
7471 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7472 size_t from
= strlen(opt_remote
);
7474 if (!prefixcmp(value
, "refs/heads/"))
7475 value
+= STRING_SIZE("refs/heads/");
7477 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7483 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7485 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7486 int argc
= 1 + (cmd
== option_set_command
);
7489 if (!argv_from_string(argv
, &argc
, value
))
7490 config_msg
= "Too many option arguments";
7492 error
= cmd(argc
, argv
);
7495 warn("Option 'tig.%s': %s", name
, config_msg
);
7499 set_environment_variable(const char *name
, const char *value
)
7501 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7502 char *env
= malloc(len
);
7505 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7513 set_work_tree(const char *value
)
7515 char cwd
[SIZEOF_STR
];
7517 if (!getcwd(cwd
, sizeof(cwd
)))
7518 die("Failed to get cwd path: %s", strerror(errno
));
7519 if (chdir(opt_git_dir
) < 0)
7520 die("Failed to chdir(%s): %s", strerror(errno
));
7521 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7522 die("Failed to get git path: %s", strerror(errno
));
7524 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7525 if (chdir(value
) < 0)
7526 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7527 if (!getcwd(cwd
, sizeof(cwd
)))
7528 die("Failed to get cwd path: %s", strerror(errno
));
7529 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7530 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7531 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7532 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7533 opt_is_inside_work_tree
= TRUE
;
7537 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7539 if (!strcmp(name
, "i18n.commitencoding"))
7540 string_ncopy(opt_encoding
, value
, valuelen
);
7542 else if (!strcmp(name
, "core.editor"))
7543 string_ncopy(opt_editor
, value
, valuelen
);
7545 else if (!strcmp(name
, "core.worktree"))
7546 set_work_tree(value
);
7548 else if (!prefixcmp(name
, "tig.color."))
7549 set_repo_config_option(name
+ 10, value
, option_color_command
);
7551 else if (!prefixcmp(name
, "tig.bind."))
7552 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7554 else if (!prefixcmp(name
, "tig."))
7555 set_repo_config_option(name
+ 4, value
, option_set_command
);
7557 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7558 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7559 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7565 load_git_config(void)
7567 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7569 return run_io_load(config_list_argv
, "=", read_repo_config_option
);
7573 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7575 if (!opt_git_dir
[0]) {
7576 string_ncopy(opt_git_dir
, name
, namelen
);
7578 } else if (opt_is_inside_work_tree
== -1) {
7579 /* This can be 3 different values depending on the
7580 * version of git being used. If git-rev-parse does not
7581 * understand --is-inside-work-tree it will simply echo
7582 * the option else either "true" or "false" is printed.
7583 * Default to true for the unknown case. */
7584 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7586 } else if (*name
== '.') {
7587 string_ncopy(opt_cdup
, name
, namelen
);
7590 string_ncopy(opt_prefix
, name
, namelen
);
7597 load_repo_info(void)
7599 const char *rev_parse_argv
[] = {
7600 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7601 "--show-cdup", "--show-prefix", NULL
7604 return run_io_load(rev_parse_argv
, "=", read_repo_info
);
7612 static const char usage
[] =
7613 "tig " TIG_VERSION
" (" __DATE__
")\n"
7615 "Usage: tig [options] [revs] [--] [paths]\n"
7616 " or: tig show [options] [revs] [--] [paths]\n"
7617 " or: tig blame [rev] path\n"
7619 " or: tig < [git command output]\n"
7622 " -v, --version Show version and exit\n"
7623 " -h, --help Show help message and exit";
7625 static void __NORETURN
7628 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7634 static void __NORETURN
7635 die(const char *err
, ...)
7641 va_start(args
, err
);
7642 fputs("tig: ", stderr
);
7643 vfprintf(stderr
, err
, args
);
7644 fputs("\n", stderr
);
7651 warn(const char *msg
, ...)
7655 va_start(args
, msg
);
7656 fputs("tig warning: ", stderr
);
7657 vfprintf(stderr
, msg
, args
);
7658 fputs("\n", stderr
);
7663 parse_options(int argc
, const char *argv
[])
7665 enum request request
= REQ_VIEW_MAIN
;
7666 const char *subcommand
;
7667 bool seen_dashdash
= FALSE
;
7668 /* XXX: This is vulnerable to the user overriding options
7669 * required for the main view parser. */
7670 const char *custom_argv
[SIZEOF_ARG
] = {
7671 "git", "log", "--no-color", "--pretty=raw", "--parents",
7672 "--topo-order", NULL
7676 if (!isatty(STDIN_FILENO
)) {
7677 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7678 return REQ_VIEW_PAGER
;
7684 subcommand
= argv
[1];
7685 if (!strcmp(subcommand
, "status")) {
7687 warn("ignoring arguments after `%s'", subcommand
);
7688 return REQ_VIEW_STATUS
;
7690 } else if (!strcmp(subcommand
, "blame")) {
7691 if (argc
<= 2 || argc
> 4)
7692 die("invalid number of options to blame\n\n%s", usage
);
7696 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7700 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7701 return REQ_VIEW_BLAME
;
7703 } else if (!strcmp(subcommand
, "show")) {
7704 request
= REQ_VIEW_DIFF
;
7711 custom_argv
[1] = subcommand
;
7715 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7716 const char *opt
= argv
[i
];
7718 if (seen_dashdash
|| !strcmp(opt
, "--")) {
7719 seen_dashdash
= TRUE
;
7721 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7722 printf("tig version %s\n", TIG_VERSION
);
7725 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7726 printf("%s\n", usage
);
7730 custom_argv
[j
++] = opt
;
7731 if (j
>= ARRAY_SIZE(custom_argv
))
7732 die("command too long");
7735 if (!prepare_update(VIEW(request
), custom_argv
, NULL
, FORMAT_NONE
))
7736 die("Failed to format arguments");
7742 main(int argc
, const char *argv
[])
7744 const char *codeset
= "UTF-8";
7745 enum request request
= parse_options(argc
, argv
);
7749 signal(SIGINT
, quit
);
7750 signal(SIGPIPE
, SIG_IGN
);
7752 if (setlocale(LC_ALL
, "")) {
7753 codeset
= nl_langinfo(CODESET
);
7756 if (load_repo_info() == ERR
)
7757 die("Failed to load repo info.");
7759 if (load_options() == ERR
)
7760 die("Failed to load user config.");
7762 if (load_git_config() == ERR
)
7763 die("Failed to load repo config.");
7765 /* Require a git repository unless when running in pager mode. */
7766 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7767 die("Not a git repository");
7769 if (*opt_encoding
&& strcmp(codeset
, "UTF-8")) {
7770 opt_iconv_in
= iconv_open("UTF-8", opt_encoding
);
7771 if (opt_iconv_in
== ICONV_NONE
)
7772 die("Failed to initialize character set conversion");
7775 if (codeset
&& strcmp(codeset
, "UTF-8")) {
7776 opt_iconv_out
= iconv_open(codeset
, "UTF-8");
7777 if (opt_iconv_out
== ICONV_NONE
)
7778 die("Failed to initialize character set conversion");
7781 if (load_refs() == ERR
)
7782 die("Failed to load refs.");
7784 foreach_view (view
, i
)
7785 argv_from_env(view
->ops
->argv
, view
->cmd_env
);
7789 if (request
!= REQ_NONE
)
7790 open_view(NULL
, request
, OPEN_PREPARED
);
7791 request
= request
== REQ_NONE
? REQ_VIEW_MAIN
: REQ_NONE
;
7793 while (view_driver(display
[current_view
], request
)) {
7794 int key
= get_input(0);
7796 view
= display
[current_view
];
7797 request
= get_keybinding(view
->keymap
, key
);
7799 /* Some low-level request handling. This keeps access to
7800 * status_win restricted. */
7804 char *cmd
= read_prompt(":");
7806 if (cmd
&& isdigit(*cmd
)) {
7807 int lineno
= view
->lineno
+ 1;
7809 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7810 select_view_line(view
, lineno
- 1);
7813 report("Unable to parse '%s' as a line number", cmd
);
7817 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7818 const char *argv
[SIZEOF_ARG
] = { "git" };
7821 /* When running random commands, initially show the
7822 * command in the title. However, it maybe later be
7823 * overwritten if a commit line is selected. */
7824 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7826 if (!argv_from_string(argv
, &argc
, cmd
)) {
7827 report("Too many arguments");
7828 } else if (!prepare_update(next
, argv
, NULL
, FORMAT_DASH
)) {
7829 report("Failed to format command");
7831 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7839 case REQ_SEARCH_BACK
:
7841 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7842 char *search
= read_prompt(prompt
);
7845 string_ncopy(opt_search
, search
, strlen(search
));
7846 else if (*opt_search
)
7847 request
= request
== REQ_SEARCH
?