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 io_reset(struct io
*io
)
551 io
->buf
= io
->bufpos
= NULL
;
552 io
->bufalloc
= io
->bufsize
= 0;
558 io_init(struct io
*io
, const char *dir
, enum io_type type
)
566 io_format(struct io
*io
, const char *dir
, enum io_type type
,
567 const char *argv
[], enum format_flags flags
)
569 io_init(io
, dir
, type
);
570 return format_argv(io
->argv
, argv
, flags
);
574 io_open(struct io
*io
, const char *fmt
, ...)
576 char name
[SIZEOF_STR
] = "";
580 io_init(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 io_kill(struct io
*io
)
599 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
603 io_done(struct io
*io
)
614 pid_t waiting
= waitpid(pid
, &status
, 0);
623 return waiting
== pid
&&
624 !WIFSIGNALED(status
) &&
626 !WEXITSTATUS(status
);
633 io_start(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
) && pipe(pipefds
) < 0) {
643 } else if (io
->type
== IO_AP
) {
644 pipefds
[1] = io
->pipe
;
647 if ((io
->pid
= fork())) {
650 if (pipefds
[!(io
->type
== IO_WR
)] != -1)
651 close(pipefds
[!(io
->type
== IO_WR
)]);
653 io
->pipe
= pipefds
[!!(io
->type
== IO_WR
)];
658 if (io
->type
!= IO_FG
) {
659 int devnull
= open("/dev/null", O_RDWR
);
660 int readfd
= io
->type
== IO_WR
? pipefds
[0] : devnull
;
661 int writefd
= (io
->type
== IO_RD
|| io
->type
== IO_AP
)
662 ? pipefds
[1] : devnull
;
664 dup2(readfd
, STDIN_FILENO
);
665 dup2(writefd
, STDOUT_FILENO
);
666 dup2(devnull
, STDERR_FILENO
);
669 if (pipefds
[0] != -1)
671 if (pipefds
[1] != -1)
675 if (io
->dir
&& *io
->dir
&& chdir(io
->dir
) == -1)
678 execvp(io
->argv
[0], (char *const*) io
->argv
);
682 if (pipefds
[!!(io
->type
== IO_WR
)] != -1)
683 close(pipefds
[!!(io
->type
== IO_WR
)]);
688 io_run(struct io
*io
, const char **argv
, const char *dir
, enum io_type type
)
690 io_init(io
, dir
, type
);
691 if (!format_argv(io
->argv
, argv
, FORMAT_NONE
))
697 io_complete(struct io
*io
)
699 return io_start(io
) && io_done(io
);
703 io_run_bg(const char **argv
)
707 if (!io_format(&io
, NULL
, IO_BG
, argv
, FORMAT_NONE
))
709 return io_complete(&io
);
713 io_run_fg(const char **argv
, const char *dir
)
717 if (!io_format(&io
, dir
, IO_FG
, argv
, FORMAT_NONE
))
719 return io_complete(&io
);
723 io_run_append(const char **argv
, enum format_flags flags
, int fd
)
727 if (!io_format(&io
, NULL
, IO_AP
, argv
, flags
)) {
733 return io_complete(&io
);
737 io_run_rd(struct io
*io
, const char **argv
, const char *dir
, enum format_flags flags
)
739 return io_format(io
, dir
, IO_RD
, argv
, flags
) && io_start(io
);
743 io_eof(struct io
*io
)
749 io_error(struct io
*io
)
755 io_strerror(struct io
*io
)
757 return strerror(io
->error
);
761 io_can_read(struct io
*io
)
763 struct timeval tv
= { 0, 500 };
767 FD_SET(io
->pipe
, &fds
);
769 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
773 io_read(struct io
*io
, void *buf
, size_t bufsize
)
776 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
778 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
780 else if (readsize
== -1)
782 else if (readsize
== 0)
788 DEFINE_ALLOCATOR(io_realloc_buf
, char, BUFSIZ
)
791 io_get(struct io
*io
, int c
, bool can_read
)
797 if (io
->bufsize
> 0) {
798 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
800 char *line
= io
->bufpos
;
803 io
->bufpos
= eol
+ 1;
804 io
->bufsize
-= io
->bufpos
- line
;
811 io
->bufpos
[io
->bufsize
] = 0;
821 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
822 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
824 if (io
->bufalloc
== io
->bufsize
) {
825 if (!io_realloc_buf(&io
->buf
, io
->bufalloc
, BUFSIZ
))
827 io
->bufalloc
+= BUFSIZ
;
830 io
->bufpos
= io
->buf
;
831 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
834 io
->bufsize
+= readsize
;
839 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
843 while (!io_error(io
) && written
< bufsize
) {
846 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
847 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
855 return written
== bufsize
;
859 io_read_buf(struct io
*io
, char buf
[], size_t bufsize
)
861 char *result
= io_get(io
, '\n', TRUE
);
864 result
= chomp_string(result
);
865 string_ncopy_do(buf
, bufsize
, result
, strlen(result
));
868 return io_done(io
) && result
;
872 io_run_buf(const char **argv
, char buf
[], size_t bufsize
)
876 return io_run_rd(&io
, argv
, NULL
, FORMAT_NONE
)
877 && io_read_buf(&io
, buf
, bufsize
);
881 io_load(struct io
*io
, const char *separators
,
882 int (*read_property
)(char *, size_t, char *, size_t))
890 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
895 name
= chomp_string(name
);
896 namelen
= strcspn(name
, separators
);
900 value
= chomp_string(name
+ namelen
+ 1);
901 valuelen
= strlen(value
);
908 state
= read_property(name
, namelen
, value
, valuelen
);
911 if (state
!= ERR
&& io_error(io
))
919 io_run_load(const char **argv
, const char *separators
,
920 int (*read_property
)(char *, size_t, char *, size_t))
924 return io_format(&io
, NULL
, IO_RD
, argv
, FORMAT_NONE
)
925 ? io_load(&io
, separators
, read_property
) : ERR
;
934 /* XXX: Keep the view request first and in sync with views[]. */ \
935 REQ_GROUP("View switching") \
936 REQ_(VIEW_MAIN, "Show main view"), \
937 REQ_(VIEW_DIFF, "Show diff view"), \
938 REQ_(VIEW_LOG, "Show log view"), \
939 REQ_(VIEW_TREE, "Show tree view"), \
940 REQ_(VIEW_BLOB, "Show blob view"), \
941 REQ_(VIEW_BLAME, "Show blame view"), \
942 REQ_(VIEW_BRANCH, "Show branch view"), \
943 REQ_(VIEW_HELP, "Show help page"), \
944 REQ_(VIEW_PAGER, "Show pager view"), \
945 REQ_(VIEW_STATUS, "Show status view"), \
946 REQ_(VIEW_STAGE, "Show stage view"), \
948 REQ_GROUP("View manipulation") \
949 REQ_(ENTER, "Enter current line and scroll"), \
950 REQ_(NEXT, "Move to next"), \
951 REQ_(PREVIOUS, "Move to previous"), \
952 REQ_(PARENT, "Move to parent"), \
953 REQ_(VIEW_NEXT, "Move focus to next view"), \
954 REQ_(REFRESH, "Reload and refresh"), \
955 REQ_(MAXIMIZE, "Maximize the current view"), \
956 REQ_(VIEW_CLOSE, "Close the current view"), \
957 REQ_(QUIT, "Close all views and quit"), \
959 REQ_GROUP("View specific requests") \
960 REQ_(STATUS_UPDATE, "Update file status"), \
961 REQ_(STATUS_REVERT, "Revert file changes"), \
962 REQ_(STATUS_MERGE, "Merge file using external tool"), \
963 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
965 REQ_GROUP("Cursor navigation") \
966 REQ_(MOVE_UP, "Move cursor one line up"), \
967 REQ_(MOVE_DOWN, "Move cursor one line down"), \
968 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
969 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
970 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
971 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
973 REQ_GROUP("Scrolling") \
974 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
975 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
976 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
977 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
978 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
979 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
981 REQ_GROUP("Searching") \
982 REQ_(SEARCH, "Search the view"), \
983 REQ_(SEARCH_BACK, "Search backwards in the view"), \
984 REQ_(FIND_NEXT, "Find next search match"), \
985 REQ_(FIND_PREV, "Find previous search match"), \
987 REQ_GROUP("Option manipulation") \
988 REQ_(OPTIONS, "Open option menu"), \
989 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
990 REQ_(TOGGLE_DATE, "Toggle date display"), \
991 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
992 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
993 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
994 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
995 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
996 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
999 REQ_(PROMPT, "Bring up the prompt"), \
1000 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1001 REQ_(SHOW_VERSION, "Show version information"), \
1002 REQ_(STOP_LOADING, "Stop all loading views"), \
1003 REQ_(EDIT, "Open in editor"), \
1004 REQ_(NONE, "Do nothing")
1007 /* User action requests. */
1009 #define REQ_GROUP(help)
1010 #define REQ_(req, help) REQ_##req
1012 /* Offset all requests to avoid conflicts with ncurses getch values. */
1013 REQ_OFFSET
= KEY_MAX
+ 1,
1020 struct request_info
{
1021 enum request request
;
1027 static const struct request_info req_info
[] = {
1028 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1029 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1036 get_request(const char *name
)
1038 int namelen
= strlen(name
);
1041 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
1042 if (enum_equals(req_info
[i
], name
, namelen
))
1043 return req_info
[i
].request
;
1053 /* Option and state variables. */
1054 static enum date opt_date
= DATE_DEFAULT
;
1055 static enum author opt_author
= AUTHOR_DEFAULT
;
1056 static bool opt_line_number
= FALSE
;
1057 static bool opt_line_graphics
= TRUE
;
1058 static bool opt_rev_graph
= FALSE
;
1059 static bool opt_show_refs
= TRUE
;
1060 static int opt_num_interval
= 5;
1061 static double opt_hscroll
= 0.50;
1062 static double opt_scale_split_view
= 2.0 / 3.0;
1063 static int opt_tab_size
= 8;
1064 static int opt_author_cols
= AUTHOR_COLS
;
1065 static char opt_path
[SIZEOF_STR
] = "";
1066 static char opt_file
[SIZEOF_STR
] = "";
1067 static char opt_ref
[SIZEOF_REF
] = "";
1068 static char opt_head
[SIZEOF_REF
] = "";
1069 static char opt_remote
[SIZEOF_REF
] = "";
1070 static char opt_encoding
[20] = "UTF-8";
1071 static iconv_t opt_iconv_in
= ICONV_NONE
;
1072 static iconv_t opt_iconv_out
= ICONV_NONE
;
1073 static char opt_search
[SIZEOF_STR
] = "";
1074 static char opt_cdup
[SIZEOF_STR
] = "";
1075 static char opt_prefix
[SIZEOF_STR
] = "";
1076 static char opt_git_dir
[SIZEOF_STR
] = "";
1077 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
1078 static char opt_editor
[SIZEOF_STR
] = "";
1079 static FILE *opt_tty
= NULL
;
1081 #define is_initial_commit() (!get_ref_head())
1082 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1086 * Line-oriented content detection.
1090 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1091 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1092 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1093 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1094 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1095 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1096 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1097 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1098 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1099 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1100 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1101 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1102 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1103 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1104 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1105 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1106 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1107 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1108 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1109 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1110 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1111 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1112 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1113 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1114 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1115 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1116 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1117 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1118 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1119 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1120 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1121 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1122 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1123 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1124 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1125 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1126 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1127 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1128 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1129 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1130 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1131 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1132 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1133 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1134 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1135 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1136 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1137 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1138 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1139 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1140 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1141 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1142 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1143 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1144 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1145 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1146 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1149 #define LINE(type, line, fg, bg, attr) \
1157 const char *name
; /* Option name. */
1158 int namelen
; /* Size of option name. */
1159 const char *line
; /* The start of line to match. */
1160 int linelen
; /* Size of string to match. */
1161 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1164 static struct line_info line_info
[] = {
1165 #define LINE(type, line, fg, bg, attr) \
1166 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1171 static enum line_type
1172 get_line_type(const char *line
)
1174 int linelen
= strlen(line
);
1175 enum line_type type
;
1177 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1178 /* Case insensitive search matches Signed-off-by lines better. */
1179 if (linelen
>= line_info
[type
].linelen
&&
1180 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1183 return LINE_DEFAULT
;
1187 get_line_attr(enum line_type type
)
1189 assert(type
< ARRAY_SIZE(line_info
));
1190 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1193 static struct line_info
*
1194 get_line_info(const char *name
)
1196 size_t namelen
= strlen(name
);
1197 enum line_type type
;
1199 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1200 if (enum_equals(line_info
[type
], name
, namelen
))
1201 return &line_info
[type
];
1209 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1210 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1211 enum line_type type
;
1215 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1216 default_bg
= COLOR_BLACK
;
1217 default_fg
= COLOR_WHITE
;
1220 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1221 struct line_info
*info
= &line_info
[type
];
1222 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1223 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1225 init_pair(type
, fg
, bg
);
1230 enum line_type type
;
1233 unsigned int selected
:1;
1234 unsigned int dirty
:1;
1235 unsigned int cleareol
:1;
1236 unsigned int other
:16;
1238 void *data
; /* User data */
1248 enum request request
;
1251 static const struct keybinding default_keybindings
[] = {
1252 /* View switching */
1253 { 'm', REQ_VIEW_MAIN
},
1254 { 'd', REQ_VIEW_DIFF
},
1255 { 'l', REQ_VIEW_LOG
},
1256 { 't', REQ_VIEW_TREE
},
1257 { 'f', REQ_VIEW_BLOB
},
1258 { 'B', REQ_VIEW_BLAME
},
1259 { 'H', REQ_VIEW_BRANCH
},
1260 { 'p', REQ_VIEW_PAGER
},
1261 { 'h', REQ_VIEW_HELP
},
1262 { 'S', REQ_VIEW_STATUS
},
1263 { 'c', REQ_VIEW_STAGE
},
1265 /* View manipulation */
1266 { 'q', REQ_VIEW_CLOSE
},
1267 { KEY_TAB
, REQ_VIEW_NEXT
},
1268 { KEY_RETURN
, REQ_ENTER
},
1269 { KEY_UP
, REQ_PREVIOUS
},
1270 { KEY_DOWN
, REQ_NEXT
},
1271 { 'R', REQ_REFRESH
},
1272 { KEY_F(5), REQ_REFRESH
},
1273 { 'O', REQ_MAXIMIZE
},
1275 /* Cursor navigation */
1276 { 'k', REQ_MOVE_UP
},
1277 { 'j', REQ_MOVE_DOWN
},
1278 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1279 { KEY_END
, REQ_MOVE_LAST_LINE
},
1280 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1281 { ' ', REQ_MOVE_PAGE_DOWN
},
1282 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1283 { 'b', REQ_MOVE_PAGE_UP
},
1284 { '-', REQ_MOVE_PAGE_UP
},
1287 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1288 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1289 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1290 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1291 { 'w', REQ_SCROLL_PAGE_UP
},
1292 { 's', REQ_SCROLL_PAGE_DOWN
},
1295 { '/', REQ_SEARCH
},
1296 { '?', REQ_SEARCH_BACK
},
1297 { 'n', REQ_FIND_NEXT
},
1298 { 'N', REQ_FIND_PREV
},
1302 { 'z', REQ_STOP_LOADING
},
1303 { 'v', REQ_SHOW_VERSION
},
1304 { 'r', REQ_SCREEN_REDRAW
},
1305 { 'o', REQ_OPTIONS
},
1306 { '.', REQ_TOGGLE_LINENO
},
1307 { 'D', REQ_TOGGLE_DATE
},
1308 { 'A', REQ_TOGGLE_AUTHOR
},
1309 { 'g', REQ_TOGGLE_REV_GRAPH
},
1310 { 'F', REQ_TOGGLE_REFS
},
1311 { 'I', REQ_TOGGLE_SORT_ORDER
},
1312 { 'i', REQ_TOGGLE_SORT_FIELD
},
1313 { ':', REQ_PROMPT
},
1314 { 'u', REQ_STATUS_UPDATE
},
1315 { '!', REQ_STATUS_REVERT
},
1316 { 'M', REQ_STATUS_MERGE
},
1317 { '@', REQ_STAGE_NEXT
},
1318 { ',', REQ_PARENT
},
1322 #define KEYMAP_INFO \
1337 #define KEYMAP_(name) KEYMAP_##name
1342 static const struct enum_map keymap_table
[] = {
1343 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1348 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1350 struct keybinding_table
{
1351 struct keybinding
*data
;
1355 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1358 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1360 struct keybinding_table
*table
= &keybindings
[keymap
];
1362 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1364 die("Failed to allocate keybinding");
1365 table
->data
[table
->size
].alias
= key
;
1366 table
->data
[table
->size
++].request
= request
;
1369 /* Looks for a key binding first in the given map, then in the generic map, and
1370 * lastly in the default keybindings. */
1372 get_keybinding(enum keymap keymap
, int key
)
1376 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1377 if (keybindings
[keymap
].data
[i
].alias
== key
)
1378 return keybindings
[keymap
].data
[i
].request
;
1380 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1381 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1382 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1384 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1385 if (default_keybindings
[i
].alias
== key
)
1386 return default_keybindings
[i
].request
;
1388 return (enum request
) key
;
1397 static const struct key key_table
[] = {
1398 { "Enter", KEY_RETURN
},
1400 { "Backspace", KEY_BACKSPACE
},
1402 { "Escape", KEY_ESC
},
1403 { "Left", KEY_LEFT
},
1404 { "Right", KEY_RIGHT
},
1406 { "Down", KEY_DOWN
},
1407 { "Insert", KEY_IC
},
1408 { "Delete", KEY_DC
},
1410 { "Home", KEY_HOME
},
1412 { "PageUp", KEY_PPAGE
},
1413 { "PageDown", KEY_NPAGE
},
1423 { "F10", KEY_F(10) },
1424 { "F11", KEY_F(11) },
1425 { "F12", KEY_F(12) },
1429 get_key_value(const char *name
)
1433 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1434 if (!strcasecmp(key_table
[i
].name
, name
))
1435 return key_table
[i
].value
;
1437 if (strlen(name
) == 1 && isprint(*name
))
1444 get_key_name(int key_value
)
1446 static char key_char
[] = "'X'";
1447 const char *seq
= NULL
;
1450 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1451 if (key_table
[key
].value
== key_value
)
1452 seq
= key_table
[key
].name
;
1456 isprint(key_value
)) {
1457 key_char
[1] = (char) key_value
;
1461 return seq
? seq
: "(no key)";
1465 append_key(char *buf
, size_t *pos
, const struct keybinding
*keybinding
)
1467 const char *sep
= *pos
> 0 ? ", " : "";
1468 const char *keyname
= get_key_name(keybinding
->alias
);
1470 return string_nformat(buf
, BUFSIZ
, pos
, "%s%s", sep
, keyname
);
1474 append_keymap_request_keys(char *buf
, size_t *pos
, enum request request
,
1475 enum keymap keymap
, bool all
)
1479 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1480 if (keybindings
[keymap
].data
[i
].request
== request
) {
1481 if (!append_key(buf
, pos
, &keybindings
[keymap
].data
[i
]))
1491 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1494 get_keys(enum keymap keymap
, enum request request
, bool all
)
1496 static char buf
[BUFSIZ
];
1502 if (!append_keymap_request_keys(buf
, &pos
, request
, keymap
, all
))
1503 return "Too many keybindings!";
1504 if (pos
> 0 && !all
)
1507 if (keymap
!= KEYMAP_GENERIC
) {
1508 /* Only the generic keymap includes the default keybindings when
1509 * listing all keys. */
1513 if (!append_keymap_request_keys(buf
, &pos
, request
, KEYMAP_GENERIC
, all
))
1514 return "Too many keybindings!";
1519 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1520 if (default_keybindings
[i
].request
== request
) {
1521 if (!append_key(buf
, &pos
, &default_keybindings
[i
]))
1522 return "Too many keybindings!";
1531 struct run_request
{
1534 const char *argv
[SIZEOF_ARG
];
1537 static struct run_request
*run_request
;
1538 static size_t run_requests
;
1540 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1543 add_run_request(enum keymap keymap
, int key
, int argc
, const char **argv
)
1545 struct run_request
*req
;
1547 if (argc
>= ARRAY_SIZE(req
->argv
) - 1)
1550 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1553 req
= &run_request
[run_requests
];
1554 req
->keymap
= keymap
;
1556 req
->argv
[0] = NULL
;
1558 if (!format_argv(req
->argv
, argv
, FORMAT_NONE
))
1561 return REQ_NONE
+ ++run_requests
;
1564 static struct run_request
*
1565 get_run_request(enum request request
)
1567 if (request
<= REQ_NONE
)
1569 return &run_request
[request
- REQ_NONE
- 1];
1573 add_builtin_run_requests(void)
1575 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1576 const char *commit
[] = { "git", "commit", NULL
};
1577 const char *gc
[] = { "git", "gc", NULL
};
1584 { KEYMAP_MAIN
, 'C', ARRAY_SIZE(cherry_pick
) - 1, cherry_pick
},
1585 { KEYMAP_STATUS
, 'C', ARRAY_SIZE(commit
) - 1, commit
},
1586 { KEYMAP_GENERIC
, 'G', ARRAY_SIZE(gc
) - 1, gc
},
1590 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1593 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argc
, reqs
[i
].argv
);
1594 if (req
!= REQ_NONE
)
1595 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1600 * User config file handling.
1603 static int config_lineno
;
1604 static bool config_errors
;
1605 static const char *config_msg
;
1607 static const struct enum_map color_map
[] = {
1608 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1620 static const struct enum_map attr_map
[] = {
1621 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1628 ATTR_MAP(UNDERLINE
),
1631 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1633 static int parse_step(double *opt
, const char *arg
)
1636 if (!strchr(arg
, '%'))
1639 /* "Shift down" so 100% and 1 does not conflict. */
1640 *opt
= (*opt
- 1) / 100;
1643 config_msg
= "Step value larger than 100%";
1648 config_msg
= "Invalid step value";
1655 parse_int(int *opt
, const char *arg
, int min
, int max
)
1657 int value
= atoi(arg
);
1659 if (min
<= value
&& value
<= max
) {
1664 config_msg
= "Integer value out of bound";
1669 set_color(int *color
, const char *name
)
1671 if (map_enum(color
, color_map
, name
))
1673 if (!prefixcmp(name
, "color"))
1674 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1678 /* Wants: object fgcolor bgcolor [attribute] */
1680 option_color_command(int argc
, const char *argv
[])
1682 struct line_info
*info
;
1685 config_msg
= "Wrong number of arguments given to color command";
1689 info
= get_line_info(argv
[0]);
1691 static const struct enum_map obsolete
[] = {
1692 ENUM_MAP("main-delim", LINE_DELIMITER
),
1693 ENUM_MAP("main-date", LINE_DATE
),
1694 ENUM_MAP("main-author", LINE_AUTHOR
),
1698 if (!map_enum(&index
, obsolete
, argv
[0])) {
1699 config_msg
= "Unknown color name";
1702 info
= &line_info
[index
];
1705 if (!set_color(&info
->fg
, argv
[1]) ||
1706 !set_color(&info
->bg
, argv
[2])) {
1707 config_msg
= "Unknown color";
1712 while (argc
-- > 3) {
1715 if (!set_attribute(&attr
, argv
[argc
])) {
1716 config_msg
= "Unknown attribute";
1725 static int parse_bool(bool *opt
, const char *arg
)
1727 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1732 static int parse_enum_do(unsigned int *opt
, const char *arg
,
1733 const struct enum_map
*map
, size_t map_size
)
1737 assert(map_size
> 1);
1739 if (map_enum_do(map
, map_size
, (int *) opt
, arg
))
1742 if (parse_bool(&is_true
, arg
) != OK
)
1745 *opt
= is_true
? map
[1].value
: map
[0].value
;
1749 #define parse_enum(opt, arg, map) \
1750 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1753 parse_string(char *opt
, const char *arg
, size_t optsize
)
1755 int arglen
= strlen(arg
);
1760 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1761 config_msg
= "Unmatched quotation";
1764 arg
+= 1; arglen
-= 2;
1766 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1771 /* Wants: name = value */
1773 option_set_command(int argc
, const char *argv
[])
1776 config_msg
= "Wrong number of arguments given to set command";
1780 if (strcmp(argv
[1], "=")) {
1781 config_msg
= "No value assigned";
1785 if (!strcmp(argv
[0], "show-author"))
1786 return parse_enum(&opt_author
, argv
[2], author_map
);
1788 if (!strcmp(argv
[0], "show-date"))
1789 return parse_enum(&opt_date
, argv
[2], date_map
);
1791 if (!strcmp(argv
[0], "show-rev-graph"))
1792 return parse_bool(&opt_rev_graph
, argv
[2]);
1794 if (!strcmp(argv
[0], "show-refs"))
1795 return parse_bool(&opt_show_refs
, argv
[2]);
1797 if (!strcmp(argv
[0], "show-line-numbers"))
1798 return parse_bool(&opt_line_number
, argv
[2]);
1800 if (!strcmp(argv
[0], "line-graphics"))
1801 return parse_bool(&opt_line_graphics
, argv
[2]);
1803 if (!strcmp(argv
[0], "line-number-interval"))
1804 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
1806 if (!strcmp(argv
[0], "author-width"))
1807 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
1809 if (!strcmp(argv
[0], "horizontal-scroll"))
1810 return parse_step(&opt_hscroll
, argv
[2]);
1812 if (!strcmp(argv
[0], "split-view-height"))
1813 return parse_step(&opt_scale_split_view
, argv
[2]);
1815 if (!strcmp(argv
[0], "tab-size"))
1816 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
1818 if (!strcmp(argv
[0], "commit-encoding"))
1819 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
1821 config_msg
= "Unknown variable name";
1825 /* Wants: mode request key */
1827 option_bind_command(int argc
, const char *argv
[])
1829 enum request request
;
1834 config_msg
= "Wrong number of arguments given to bind command";
1838 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
1839 config_msg
= "Unknown key map";
1843 key
= get_key_value(argv
[1]);
1845 config_msg
= "Unknown key";
1849 request
= get_request(argv
[2]);
1850 if (request
== REQ_NONE
) {
1851 static const struct enum_map obsolete
[] = {
1852 ENUM_MAP("cherry-pick", REQ_NONE
),
1853 ENUM_MAP("screen-resize", REQ_NONE
),
1854 ENUM_MAP("tree-parent", REQ_PARENT
),
1858 if (map_enum(&alias
, obsolete
, argv
[2])) {
1859 if (alias
!= REQ_NONE
)
1860 add_keybinding(keymap
, alias
, key
);
1861 config_msg
= "Obsolete request name";
1865 if (request
== REQ_NONE
&& *argv
[2]++ == '!')
1866 request
= add_run_request(keymap
, key
, argc
- 2, argv
+ 2);
1867 if (request
== REQ_NONE
) {
1868 config_msg
= "Unknown request name";
1872 add_keybinding(keymap
, request
, key
);
1878 set_option(const char *opt
, char *value
)
1880 const char *argv
[SIZEOF_ARG
];
1883 if (!argv_from_string(argv
, &argc
, value
)) {
1884 config_msg
= "Too many option arguments";
1888 if (!strcmp(opt
, "color"))
1889 return option_color_command(argc
, argv
);
1891 if (!strcmp(opt
, "set"))
1892 return option_set_command(argc
, argv
);
1894 if (!strcmp(opt
, "bind"))
1895 return option_bind_command(argc
, argv
);
1897 config_msg
= "Unknown option command";
1902 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
1907 config_msg
= "Internal error";
1909 /* Check for comment markers, since read_properties() will
1910 * only ensure opt and value are split at first " \t". */
1911 optlen
= strcspn(opt
, "#");
1915 if (opt
[optlen
] != 0) {
1916 config_msg
= "No option value";
1920 /* Look for comment endings in the value. */
1921 size_t len
= strcspn(value
, "#");
1923 if (len
< valuelen
) {
1925 value
[valuelen
] = 0;
1928 status
= set_option(opt
, value
);
1931 if (status
== ERR
) {
1932 warn("Error on line %d, near '%.*s': %s",
1933 config_lineno
, (int) optlen
, opt
, config_msg
);
1934 config_errors
= TRUE
;
1937 /* Always keep going if errors are encountered. */
1942 load_option_file(const char *path
)
1946 /* It's OK that the file doesn't exist. */
1947 if (!io_open(&io
, "%s", path
))
1951 config_errors
= FALSE
;
1953 if (io_load(&io
, " \t", read_option
) == ERR
||
1954 config_errors
== TRUE
)
1955 warn("Errors while loading %s.", path
);
1961 const char *home
= getenv("HOME");
1962 const char *tigrc_user
= getenv("TIGRC_USER");
1963 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
1964 char buf
[SIZEOF_STR
];
1966 add_builtin_run_requests();
1969 tigrc_system
= SYSCONFDIR
"/tigrc";
1970 load_option_file(tigrc_system
);
1973 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
1977 load_option_file(tigrc_user
);
1990 /* The display array of active views and the index of the current view. */
1991 static struct view
*display
[2];
1992 static unsigned int current_view
;
1994 #define foreach_displayed_view(view, i) \
1995 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1997 #define displayed_views() (display[1] != NULL ? 2 : 1)
1999 /* Current head and commit ID */
2000 static char ref_blob
[SIZEOF_REF
] = "";
2001 static char ref_commit
[SIZEOF_REF
] = "HEAD";
2002 static char ref_head
[SIZEOF_REF
] = "HEAD";
2005 const char *name
; /* View name */
2006 const char *cmd_env
; /* Command line set via environment */
2007 const char *id
; /* Points to either of ref_{head,commit,blob} */
2009 struct view_ops
*ops
; /* View operations */
2011 enum keymap keymap
; /* What keymap does this view have */
2012 bool git_dir
; /* Whether the view requires a git directory. */
2014 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
2015 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
2017 int height
, width
; /* The width and height of the main window */
2018 WINDOW
*win
; /* The main window */
2019 WINDOW
*title
; /* The title window living below the main window */
2022 unsigned long offset
; /* Offset of the window top */
2023 unsigned long yoffset
; /* Offset from the window side. */
2024 unsigned long lineno
; /* Current line number */
2025 unsigned long p_offset
; /* Previous offset of the window top */
2026 unsigned long p_yoffset
;/* Previous offset from the window side */
2027 unsigned long p_lineno
; /* Previous current line number */
2028 bool p_restore
; /* Should the previous position be restored. */
2031 char grep
[SIZEOF_STR
]; /* Search string */
2032 regex_t
*regex
; /* Pre-compiled regexp */
2034 /* If non-NULL, points to the view that opened this view. If this view
2035 * is closed tig will switch back to the parent view. */
2036 struct view
*parent
;
2039 size_t lines
; /* Total number of lines */
2040 struct line
*line
; /* Line index */
2041 unsigned int digits
; /* Number of digits in the lines member. */
2044 struct line
*curline
; /* Line currently being drawn. */
2045 enum line_type curtype
; /* Attribute currently used for drawing. */
2046 unsigned long col
; /* Column when drawing. */
2047 bool has_scrolled
; /* View was scrolled. */
2057 /* What type of content being displayed. Used in the title bar. */
2059 /* Default command arguments. */
2061 /* Open and reads in all view content. */
2062 bool (*open
)(struct view
*view
);
2063 /* Read one line; updates view->line. */
2064 bool (*read
)(struct view
*view
, char *data
);
2065 /* Draw one line; @lineno must be < view->height. */
2066 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
2067 /* Depending on view handle a special requests. */
2068 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
2069 /* Search for regexp in a line. */
2070 bool (*grep
)(struct view
*view
, struct line
*line
);
2072 void (*select
)(struct view
*view
, struct line
*line
);
2073 /* Prepare view for loading */
2074 bool (*prepare
)(struct view
*view
);
2077 static struct view_ops blame_ops
;
2078 static struct view_ops blob_ops
;
2079 static struct view_ops diff_ops
;
2080 static struct view_ops help_ops
;
2081 static struct view_ops log_ops
;
2082 static struct view_ops main_ops
;
2083 static struct view_ops pager_ops
;
2084 static struct view_ops stage_ops
;
2085 static struct view_ops status_ops
;
2086 static struct view_ops tree_ops
;
2087 static struct view_ops branch_ops
;
2089 #define VIEW_STR(name, env, ref, ops, map, git) \
2090 { name, #env, ref, ops, map, git }
2092 #define VIEW_(id, name, ops, git, ref) \
2093 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2096 static struct view views
[] = {
2097 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
2098 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
2099 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
2100 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
2101 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
2102 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
2103 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
2104 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
2105 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
2106 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
2107 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
2110 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2111 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2113 #define foreach_view(view, i) \
2114 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2116 #define view_is_displayed(view) \
2117 (view == display[0] || view == display[1])
2121 set_view_attr(struct view
*view
, enum line_type type
)
2123 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
2124 (void) wattrset(view
->win
, get_line_attr(type
));
2125 wchgat(view
->win
, -1, 0, type
, NULL
);
2126 view
->curtype
= type
;
2131 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
2132 int max_len
, bool use_tilde
)
2134 static char out_buffer
[BUFSIZ
* 2];
2137 int trimmed
= FALSE
;
2138 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2143 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
, opt_tab_size
);
2145 set_view_attr(view
, type
);
2147 if (opt_iconv_out
!= ICONV_NONE
) {
2148 ICONV_CONST
char *inbuf
= (ICONV_CONST
char *) string
;
2149 size_t inlen
= len
+ 1;
2151 char *outbuf
= out_buffer
;
2152 size_t outlen
= sizeof(out_buffer
);
2156 ret
= iconv(opt_iconv_out
, &inbuf
, &inlen
, &outbuf
, &outlen
);
2157 if (ret
!= (size_t) -1) {
2158 string
= out_buffer
;
2159 len
= sizeof(out_buffer
) - outlen
;
2163 waddnstr(view
->win
, string
, len
);
2165 if (trimmed
&& use_tilde
) {
2166 set_view_attr(view
, LINE_DELIMITER
);
2167 waddch(view
->win
, '~');
2175 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
2177 static char space
[] = " ";
2180 spaces
= MIN(max
, spaces
);
2182 while (spaces
> 0) {
2183 int len
= MIN(spaces
, sizeof(space
) - 1);
2185 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
2193 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
2195 view
->col
+= draw_chars(view
, type
, string
, view
->width
+ view
->yoffset
- view
->col
, trim
);
2196 return view
->width
+ view
->yoffset
<= view
->col
;
2200 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
2202 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2203 int max
= view
->width
+ view
->yoffset
- view
->col
;
2209 set_view_attr(view
, type
);
2210 /* Using waddch() instead of waddnstr() ensures that
2211 * they'll be rendered correctly for the cursor line. */
2212 for (i
= skip
; i
< size
; i
++)
2213 waddch(view
->win
, graphic
[i
]);
2216 if (size
< max
&& skip
<= size
)
2217 waddch(view
->win
, ' ');
2220 return view
->width
+ view
->yoffset
<= view
->col
;
2224 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2226 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2230 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2232 col
= draw_space(view
, type
, max
- 1, max
- 1);
2235 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2236 return view
->width
+ view
->yoffset
<= view
->col
;
2240 draw_date(struct view
*view
, struct time
*time
)
2242 const char *date
= mkdate(time
, opt_date
);
2243 int cols
= opt_date
== DATE_SHORT
? DATE_SHORT_COLS
: DATE_COLS
;
2245 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2249 draw_author(struct view
*view
, const char *author
)
2251 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5;
2252 bool abbreviate
= opt_author
== AUTHOR_ABBREVIATED
|| !trim
;
2254 if (abbreviate
&& author
)
2255 author
= get_author_initials(author
);
2257 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2261 draw_mode(struct view
*view
, mode_t mode
)
2267 else if (S_ISLNK(mode
))
2269 else if (S_ISGITLINK(mode
))
2271 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2273 else if (S_ISREG(mode
))
2278 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2282 draw_lineno(struct view
*view
, unsigned int lineno
)
2285 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2286 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2288 chtype separator
= opt_line_graphics
? ACS_VLINE
: '|';
2290 lineno
+= view
->offset
+ 1;
2291 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2292 static char fmt
[] = "%1ld";
2294 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2295 if (string_format(number
, fmt
, lineno
))
2299 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2301 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2302 return draw_graphic(view
, LINE_DEFAULT
, &separator
, 1);
2306 draw_view_line(struct view
*view
, unsigned int lineno
)
2309 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2311 assert(view_is_displayed(view
));
2313 if (view
->offset
+ lineno
>= view
->lines
)
2316 line
= &view
->line
[view
->offset
+ lineno
];
2318 wmove(view
->win
, lineno
, 0);
2320 wclrtoeol(view
->win
);
2322 view
->curline
= line
;
2323 view
->curtype
= LINE_NONE
;
2324 line
->selected
= FALSE
;
2325 line
->dirty
= line
->cleareol
= 0;
2328 set_view_attr(view
, LINE_CURSOR
);
2329 line
->selected
= TRUE
;
2330 view
->ops
->select(view
, line
);
2333 return view
->ops
->draw(view
, line
, lineno
);
2337 redraw_view_dirty(struct view
*view
)
2342 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2343 if (view
->offset
+ lineno
>= view
->lines
)
2345 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2348 if (!draw_view_line(view
, lineno
))
2354 wnoutrefresh(view
->win
);
2358 redraw_view_from(struct view
*view
, int lineno
)
2360 assert(0 <= lineno
&& lineno
< view
->height
);
2362 for (; lineno
< view
->height
; lineno
++) {
2363 if (!draw_view_line(view
, lineno
))
2367 wnoutrefresh(view
->win
);
2371 redraw_view(struct view
*view
)
2374 redraw_view_from(view
, 0);
2379 update_view_title(struct view
*view
)
2381 char buf
[SIZEOF_STR
];
2382 char state
[SIZEOF_STR
];
2383 size_t bufpos
= 0, statelen
= 0;
2385 assert(view_is_displayed(view
));
2387 if (view
!= VIEW(REQ_VIEW_STATUS
) && view
->lines
) {
2388 unsigned int view_lines
= view
->offset
+ view
->height
;
2389 unsigned int lines
= view
->lines
2390 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2393 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2402 time_t secs
= time(NULL
) - view
->start_time
;
2404 /* Three git seconds are a long time ... */
2406 string_format_from(state
, &statelen
, " loading %lds", secs
);
2409 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2410 if (*view
->ref
&& bufpos
< view
->width
) {
2411 size_t refsize
= strlen(view
->ref
);
2412 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2414 if (minsize
< view
->width
)
2415 refsize
= view
->width
- minsize
+ 7;
2416 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2419 if (statelen
&& bufpos
< view
->width
) {
2420 string_format_from(buf
, &bufpos
, "%s", state
);
2423 if (view
== display
[current_view
])
2424 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2426 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2428 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2429 wclrtoeol(view
->title
);
2430 wnoutrefresh(view
->title
);
2434 apply_step(double step
, int value
)
2438 value
*= step
+ 0.01;
2439 return value
? value
: 1;
2443 resize_display(void)
2446 struct view
*base
= display
[0];
2447 struct view
*view
= display
[1] ? display
[1] : display
[0];
2449 /* Setup window dimensions */
2451 getmaxyx(stdscr
, base
->height
, base
->width
);
2453 /* Make room for the status window. */
2457 /* Horizontal split. */
2458 view
->width
= base
->width
;
2459 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2460 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2461 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2462 base
->height
-= view
->height
;
2464 /* Make room for the title bar. */
2468 /* Make room for the title bar. */
2473 foreach_displayed_view (view
, i
) {
2475 view
->win
= newwin(view
->height
, 0, offset
, 0);
2477 die("Failed to create %s view", view
->name
);
2479 scrollok(view
->win
, FALSE
);
2481 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2483 die("Failed to create title window");
2486 wresize(view
->win
, view
->height
, view
->width
);
2487 mvwin(view
->win
, offset
, 0);
2488 mvwin(view
->title
, offset
+ view
->height
, 0);
2491 offset
+= view
->height
+ 1;
2496 redraw_display(bool clear
)
2501 foreach_displayed_view (view
, i
) {
2505 update_view_title(view
);
2510 toggle_enum_option_do(unsigned int *opt
, const char *help
,
2511 const struct enum_map
*map
, size_t size
)
2513 *opt
= (*opt
+ 1) % size
;
2514 redraw_display(FALSE
);
2515 report("Displaying %s %s", enum_name(map
[*opt
]), help
);
2518 #define toggle_enum_option(opt, help, map) \
2519 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2521 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2522 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2525 toggle_view_option(bool *option
, const char *help
)
2528 redraw_display(FALSE
);
2529 report("%sabling %s", *option
? "En" : "Dis", help
);
2533 open_option_menu(void)
2535 const struct menu_item menu
[] = {
2536 { '.', "line numbers", &opt_line_number
},
2537 { 'D', "date display", &opt_date
},
2538 { 'A', "author display", &opt_author
},
2539 { 'g', "revision graph display", &opt_rev_graph
},
2540 { 'F', "reference display", &opt_show_refs
},
2545 if (prompt_menu("Toggle option", menu
, &selected
)) {
2546 if (menu
[selected
].data
== &opt_date
)
2548 else if (menu
[selected
].data
== &opt_author
)
2551 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2556 maximize_view(struct view
*view
)
2558 memset(display
, 0, sizeof(display
));
2560 display
[current_view
] = view
;
2562 redraw_display(FALSE
);
2572 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2574 if (lineno
>= view
->lines
)
2575 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2577 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2578 unsigned long half
= view
->height
/ 2;
2581 offset
= lineno
- half
;
2586 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2587 view
->offset
= offset
;
2588 view
->lineno
= lineno
;
2595 /* Scrolling backend */
2597 do_scroll_view(struct view
*view
, int lines
)
2599 bool redraw_current_line
= FALSE
;
2601 /* The rendering expects the new offset. */
2602 view
->offset
+= lines
;
2604 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2607 /* Move current line into the view. */
2608 if (view
->lineno
< view
->offset
) {
2609 view
->lineno
= view
->offset
;
2610 redraw_current_line
= TRUE
;
2611 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2612 view
->lineno
= view
->offset
+ view
->height
- 1;
2613 redraw_current_line
= TRUE
;
2616 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2618 /* Redraw the whole screen if scrolling is pointless. */
2619 if (view
->height
< ABS(lines
)) {
2623 int line
= lines
> 0 ? view
->height
- lines
: 0;
2624 int end
= line
+ ABS(lines
);
2626 scrollok(view
->win
, TRUE
);
2627 wscrl(view
->win
, lines
);
2628 scrollok(view
->win
, FALSE
);
2630 while (line
< end
&& draw_view_line(view
, line
))
2633 if (redraw_current_line
)
2634 draw_view_line(view
, view
->lineno
- view
->offset
);
2635 wnoutrefresh(view
->win
);
2638 view
->has_scrolled
= TRUE
;
2642 /* Scroll frontend */
2644 scroll_view(struct view
*view
, enum request request
)
2648 assert(view_is_displayed(view
));
2651 case REQ_SCROLL_LEFT
:
2652 if (view
->yoffset
== 0) {
2653 report("Cannot scroll beyond the first column");
2656 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2659 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2660 redraw_view_from(view
, 0);
2663 case REQ_SCROLL_RIGHT
:
2664 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2668 case REQ_SCROLL_PAGE_DOWN
:
2669 lines
= view
->height
;
2670 case REQ_SCROLL_LINE_DOWN
:
2671 if (view
->offset
+ lines
> view
->lines
)
2672 lines
= view
->lines
- view
->offset
;
2674 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2675 report("Cannot scroll beyond the last line");
2680 case REQ_SCROLL_PAGE_UP
:
2681 lines
= view
->height
;
2682 case REQ_SCROLL_LINE_UP
:
2683 if (lines
> view
->offset
)
2684 lines
= view
->offset
;
2687 report("Cannot scroll beyond the first line");
2695 die("request %d not handled in switch", request
);
2698 do_scroll_view(view
, lines
);
2703 move_view(struct view
*view
, enum request request
)
2705 int scroll_steps
= 0;
2709 case REQ_MOVE_FIRST_LINE
:
2710 steps
= -view
->lineno
;
2713 case REQ_MOVE_LAST_LINE
:
2714 steps
= view
->lines
- view
->lineno
- 1;
2717 case REQ_MOVE_PAGE_UP
:
2718 steps
= view
->height
> view
->lineno
2719 ? -view
->lineno
: -view
->height
;
2722 case REQ_MOVE_PAGE_DOWN
:
2723 steps
= view
->lineno
+ view
->height
>= view
->lines
2724 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2736 die("request %d not handled in switch", request
);
2739 if (steps
<= 0 && view
->lineno
== 0) {
2740 report("Cannot move beyond the first line");
2743 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2744 report("Cannot move beyond the last line");
2748 /* Move the current line */
2749 view
->lineno
+= steps
;
2750 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2752 /* Check whether the view needs to be scrolled */
2753 if (view
->lineno
< view
->offset
||
2754 view
->lineno
>= view
->offset
+ view
->height
) {
2755 scroll_steps
= steps
;
2756 if (steps
< 0 && -steps
> view
->offset
) {
2757 scroll_steps
= -view
->offset
;
2759 } else if (steps
> 0) {
2760 if (view
->lineno
== view
->lines
- 1 &&
2761 view
->lines
> view
->height
) {
2762 scroll_steps
= view
->lines
- view
->offset
- 1;
2763 if (scroll_steps
>= view
->height
)
2764 scroll_steps
-= view
->height
- 1;
2769 if (!view_is_displayed(view
)) {
2770 view
->offset
+= scroll_steps
;
2771 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2772 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2776 /* Repaint the old "current" line if we be scrolling */
2777 if (ABS(steps
) < view
->height
)
2778 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
2781 do_scroll_view(view
, scroll_steps
);
2785 /* Draw the current line */
2786 draw_view_line(view
, view
->lineno
- view
->offset
);
2788 wnoutrefresh(view
->win
);
2797 static void search_view(struct view
*view
, enum request request
);
2800 grep_text(struct view
*view
, const char *text
[])
2805 for (i
= 0; text
[i
]; i
++)
2807 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
2813 select_view_line(struct view
*view
, unsigned long lineno
)
2815 unsigned long old_lineno
= view
->lineno
;
2816 unsigned long old_offset
= view
->offset
;
2818 if (goto_view_line(view
, view
->offset
, lineno
)) {
2819 if (view_is_displayed(view
)) {
2820 if (old_offset
!= view
->offset
) {
2823 draw_view_line(view
, old_lineno
- view
->offset
);
2824 draw_view_line(view
, view
->lineno
- view
->offset
);
2825 wnoutrefresh(view
->win
);
2828 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2834 find_next(struct view
*view
, enum request request
)
2836 unsigned long lineno
= view
->lineno
;
2841 report("No previous search");
2843 search_view(view
, request
);
2853 case REQ_SEARCH_BACK
:
2862 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
2863 lineno
+= direction
;
2865 /* Note, lineno is unsigned long so will wrap around in which case it
2866 * will become bigger than view->lines. */
2867 for (; lineno
< view
->lines
; lineno
+= direction
) {
2868 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
2869 select_view_line(view
, lineno
);
2870 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
2875 report("No match found for '%s'", view
->grep
);
2879 search_view(struct view
*view
, enum request request
)
2884 regfree(view
->regex
);
2887 view
->regex
= calloc(1, sizeof(*view
->regex
));
2892 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
2893 if (regex_err
!= 0) {
2894 char buf
[SIZEOF_STR
] = "unknown error";
2896 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
2897 report("Search failed: %s", buf
);
2901 string_copy(view
->grep
, opt_search
);
2903 find_next(view
, request
);
2907 * Incremental updating
2911 reset_view(struct view
*view
)
2915 for (i
= 0; i
< view
->lines
; i
++)
2916 free(view
->line
[i
].data
);
2919 view
->p_offset
= view
->offset
;
2920 view
->p_yoffset
= view
->yoffset
;
2921 view
->p_lineno
= view
->lineno
;
2929 view
->update_secs
= 0;
2933 free_argv(const char *argv
[])
2937 for (argc
= 0; argv
[argc
]; argc
++)
2938 free((void *) argv
[argc
]);
2942 format_arg(const char *name
)
2948 const char *value_if_empty
;
2950 #define FORMAT_VAR(name, value, value_if_empty) \
2951 { name, STRING_SIZE(name), value, value_if_empty }
2952 FORMAT_VAR("%(directory)", opt_path
, ""),
2953 FORMAT_VAR("%(file)", opt_file
, ""),
2954 FORMAT_VAR("%(ref)", opt_ref
, "HEAD"),
2955 FORMAT_VAR("%(head)", ref_head
, ""),
2956 FORMAT_VAR("%(commit)", ref_commit
, ""),
2957 FORMAT_VAR("%(blob)", ref_blob
, ""),
2961 for (i
= 0; i
< ARRAY_SIZE(vars
); i
++)
2962 if (!strncmp(name
, vars
[i
].name
, vars
[i
].namelen
))
2963 return *vars
[i
].value
? vars
[i
].value
: vars
[i
].value_if_empty
;
2969 format_argv(const char *dst_argv
[], const char *src_argv
[], enum format_flags flags
)
2971 char buf
[SIZEOF_STR
];
2973 bool noreplace
= flags
== FORMAT_NONE
;
2975 free_argv(dst_argv
);
2977 for (argc
= 0; src_argv
[argc
]; argc
++) {
2978 const char *arg
= src_argv
[argc
];
2982 char *next
= strstr(arg
, "%(");
2983 int len
= next
- arg
;
2986 if (!next
|| noreplace
) {
2987 if (flags
== FORMAT_DASH
&& !strcmp(arg
, "--"))
2993 value
= format_arg(next
);
2996 report("Unknown replacement: `%s`", next
);
3001 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
3004 arg
= next
&& !noreplace
? strchr(next
, ')') + 1 : NULL
;
3007 dst_argv
[argc
] = strdup(buf
);
3008 if (!dst_argv
[argc
])
3012 dst_argv
[argc
] = NULL
;
3014 return src_argv
[argc
] == NULL
;
3018 restore_view_position(struct view
*view
)
3020 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
3023 /* Changing the view position cancels the restoring. */
3024 /* FIXME: Changing back to the first line is not detected. */
3025 if (view
->offset
!= 0 || view
->lineno
!= 0) {
3026 view
->p_restore
= FALSE
;
3030 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
3031 view_is_displayed(view
))
3034 view
->yoffset
= view
->p_yoffset
;
3035 view
->p_restore
= FALSE
;
3041 end_update(struct view
*view
, bool force
)
3045 while (!view
->ops
->read(view
, NULL
))
3049 io_kill(view
->pipe
);
3050 io_done(view
->pipe
);
3055 setup_update(struct view
*view
, const char *vid
)
3058 string_copy_rev(view
->vid
, vid
);
3059 view
->pipe
= &view
->io
;
3060 view
->start_time
= time(NULL
);
3064 prepare_update(struct view
*view
, const char *argv
[], const char *dir
,
3065 enum format_flags flags
)
3068 end_update(view
, TRUE
);
3069 return io_format(&view
->io
, dir
, IO_RD
, argv
, flags
);
3073 prepare_update_file(struct view
*view
, const char *name
)
3076 end_update(view
, TRUE
);
3077 return io_open(&view
->io
, "%s/%s", opt_cdup
[0] ? opt_cdup
: ".", name
);
3081 begin_update(struct view
*view
, bool refresh
)
3084 end_update(view
, TRUE
);
3087 if (view
->ops
->prepare
) {
3088 if (!view
->ops
->prepare(view
))
3090 } else if (!io_format(&view
->io
, NULL
, IO_RD
, view
->ops
->argv
, FORMAT_ALL
)) {
3094 /* Put the current ref_* value to the view title ref
3095 * member. This is needed by the blob view. Most other
3096 * views sets it automatically after loading because the
3097 * first line is a commit line. */
3098 string_copy_rev(view
->ref
, view
->id
);
3101 if (!io_start(&view
->io
))
3104 setup_update(view
, view
->id
);
3110 update_view(struct view
*view
)
3112 char out_buffer
[BUFSIZ
* 2];
3114 /* Clear the view and redraw everything since the tree sorting
3115 * might have rearranged things. */
3116 bool redraw
= view
->lines
== 0;
3117 bool can_read
= TRUE
;
3122 if (!io_can_read(view
->pipe
)) {
3123 if (view
->lines
== 0 && view_is_displayed(view
)) {
3124 time_t secs
= time(NULL
) - view
->start_time
;
3126 if (secs
> 1 && secs
> view
->update_secs
) {
3127 if (view
->update_secs
== 0)
3129 update_view_title(view
);
3130 view
->update_secs
= secs
;
3136 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
3137 if (opt_iconv_in
!= ICONV_NONE
) {
3138 ICONV_CONST
char *inbuf
= line
;
3139 size_t inlen
= strlen(line
) + 1;
3141 char *outbuf
= out_buffer
;
3142 size_t outlen
= sizeof(out_buffer
);
3146 ret
= iconv(opt_iconv_in
, &inbuf
, &inlen
, &outbuf
, &outlen
);
3147 if (ret
!= (size_t) -1)
3151 if (!view
->ops
->read(view
, line
)) {
3152 report("Allocation failure");
3153 end_update(view
, TRUE
);
3159 unsigned long lines
= view
->lines
;
3162 for (digits
= 0; lines
; digits
++)
3165 /* Keep the displayed view in sync with line number scaling. */
3166 if (digits
!= view
->digits
) {
3167 view
->digits
= digits
;
3168 if (opt_line_number
|| view
== VIEW(REQ_VIEW_BLAME
))
3173 if (io_error(view
->pipe
)) {
3174 report("Failed to read: %s", io_strerror(view
->pipe
));
3175 end_update(view
, TRUE
);
3177 } else if (io_eof(view
->pipe
)) {
3179 end_update(view
, FALSE
);
3182 if (restore_view_position(view
))
3185 if (!view_is_displayed(view
))
3189 redraw_view_from(view
, 0);
3191 redraw_view_dirty(view
);
3193 /* Update the title _after_ the redraw so that if the redraw picks up a
3194 * commit reference in view->ref it'll be available here. */
3195 update_view_title(view
);
3199 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
3201 static struct line
*
3202 add_line_data(struct view
*view
, void *data
, enum line_type type
)
3206 if (!realloc_lines(&view
->line
, view
->lines
, 1))
3209 line
= &view
->line
[view
->lines
++];
3210 memset(line
, 0, sizeof(*line
));
3218 static struct line
*
3219 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
3221 char *data
= text
? strdup(text
) : NULL
;
3223 return data
? add_line_data(view
, data
, type
) : NULL
;
3226 static struct line
*
3227 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3229 char buf
[SIZEOF_STR
];
3232 va_start(args
, fmt
);
3233 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3237 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3245 OPEN_DEFAULT
= 0, /* Use default view switching. */
3246 OPEN_SPLIT
= 1, /* Split current view. */
3247 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3248 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3249 OPEN_PREPARED
= 32, /* Open already prepared command. */
3253 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3255 bool split
= !!(flags
& OPEN_SPLIT
);
3256 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3257 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3258 struct view
*view
= VIEW(request
);
3259 int nviews
= displayed_views();
3260 struct view
*base_view
= display
[0];
3262 if (view
== prev
&& nviews
== 1 && !reload
) {
3263 report("Already in %s view", view
->name
);
3267 if (view
->git_dir
&& !opt_git_dir
[0]) {
3268 report("The %s view is disabled in pager view", view
->name
);
3275 } else if (!nomaximize
) {
3276 /* Maximize the current view. */
3277 memset(display
, 0, sizeof(display
));
3279 display
[current_view
] = view
;
3282 /* No parent signals that this is the first loaded view. */
3283 if (prev
&& view
!= prev
) {
3284 view
->parent
= prev
;
3287 /* Resize the view when switching between split- and full-screen,
3288 * or when switching between two different full-screen views. */
3289 if (nviews
!= displayed_views() ||
3290 (nviews
== 1 && base_view
!= display
[0]))
3293 if (view
->ops
->open
) {
3295 end_update(view
, TRUE
);
3296 if (!view
->ops
->open(view
)) {
3297 report("Failed to load %s view", view
->name
);
3300 restore_view_position(view
);
3302 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3303 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3304 report("Failed to load %s view", view
->name
);
3308 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3309 /* Take the title line into account. */
3310 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3312 /* Scroll the view that was split if the current line is
3313 * outside the new limited view. */
3314 do_scroll_view(prev
, lines
);
3317 if (prev
&& view
!= prev
&& split
&& view_is_displayed(prev
)) {
3318 /* "Blur" the previous view. */
3319 update_view_title(prev
);
3322 if (view
->pipe
&& view
->lines
== 0) {
3323 /* Clear the old view and let the incremental updating refill
3326 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3328 } else if (view_is_displayed(view
)) {
3335 open_external_viewer(const char *argv
[], const char *dir
)
3337 def_prog_mode(); /* save current tty modes */
3338 endwin(); /* restore original tty modes */
3339 io_run_fg(argv
, dir
);
3340 fprintf(stderr
, "Press Enter to continue");
3343 redraw_display(TRUE
);
3347 open_mergetool(const char *file
)
3349 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3351 open_external_viewer(mergetool_argv
, opt_cdup
);
3355 open_editor(const char *file
)
3357 const char *editor_argv
[] = { "vi", file
, NULL
};
3360 editor
= getenv("GIT_EDITOR");
3361 if (!editor
&& *opt_editor
)
3362 editor
= opt_editor
;
3364 editor
= getenv("VISUAL");
3366 editor
= getenv("EDITOR");
3370 editor_argv
[0] = editor
;
3371 open_external_viewer(editor_argv
, opt_cdup
);
3375 open_run_request(enum request request
)
3377 struct run_request
*req
= get_run_request(request
);
3378 const char *argv
[ARRAY_SIZE(req
->argv
)] = { NULL
};
3381 report("Unknown run request");
3385 if (format_argv(argv
, req
->argv
, FORMAT_ALL
))
3386 open_external_viewer(argv
, NULL
);
3391 * User request switch noodle
3395 view_driver(struct view
*view
, enum request request
)
3399 if (request
== REQ_NONE
)
3402 if (request
> REQ_NONE
) {
3403 open_run_request(request
);
3404 /* FIXME: When all views can refresh always do this. */
3405 if (view
== VIEW(REQ_VIEW_STATUS
) ||
3406 view
== VIEW(REQ_VIEW_MAIN
) ||
3407 view
== VIEW(REQ_VIEW_LOG
) ||
3408 view
== VIEW(REQ_VIEW_BRANCH
) ||
3409 view
== VIEW(REQ_VIEW_STAGE
))
3410 request
= REQ_REFRESH
;
3415 if (view
&& view
->lines
) {
3416 request
= view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
3417 if (request
== REQ_NONE
)
3424 case REQ_MOVE_PAGE_UP
:
3425 case REQ_MOVE_PAGE_DOWN
:
3426 case REQ_MOVE_FIRST_LINE
:
3427 case REQ_MOVE_LAST_LINE
:
3428 move_view(view
, request
);
3431 case REQ_SCROLL_LEFT
:
3432 case REQ_SCROLL_RIGHT
:
3433 case REQ_SCROLL_LINE_DOWN
:
3434 case REQ_SCROLL_LINE_UP
:
3435 case REQ_SCROLL_PAGE_DOWN
:
3436 case REQ_SCROLL_PAGE_UP
:
3437 scroll_view(view
, request
);
3440 case REQ_VIEW_BLAME
:
3442 report("No file chosen, press %s to open tree view",
3443 get_key(view
->keymap
, REQ_VIEW_TREE
));
3446 open_view(view
, request
, OPEN_DEFAULT
);
3451 report("No file chosen, press %s to open tree view",
3452 get_key(view
->keymap
, REQ_VIEW_TREE
));
3455 open_view(view
, request
, OPEN_DEFAULT
);
3458 case REQ_VIEW_PAGER
:
3459 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3460 report("No pager content, press %s to run command from prompt",
3461 get_key(view
->keymap
, REQ_PROMPT
));
3464 open_view(view
, request
, OPEN_DEFAULT
);
3467 case REQ_VIEW_STAGE
:
3468 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3469 report("No stage content, press %s to open the status view and choose file",
3470 get_key(view
->keymap
, REQ_VIEW_STATUS
));
3473 open_view(view
, request
, OPEN_DEFAULT
);
3476 case REQ_VIEW_STATUS
:
3477 if (opt_is_inside_work_tree
== FALSE
) {
3478 report("The status view requires a working tree");
3481 open_view(view
, request
, OPEN_DEFAULT
);
3489 case REQ_VIEW_BRANCH
:
3490 open_view(view
, request
, OPEN_DEFAULT
);
3495 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3497 if ((view
== VIEW(REQ_VIEW_DIFF
) &&
3498 view
->parent
== VIEW(REQ_VIEW_MAIN
)) ||
3499 (view
== VIEW(REQ_VIEW_DIFF
) &&
3500 view
->parent
== VIEW(REQ_VIEW_BLAME
)) ||
3501 (view
== VIEW(REQ_VIEW_STAGE
) &&
3502 view
->parent
== VIEW(REQ_VIEW_STATUS
)) ||
3503 (view
== VIEW(REQ_VIEW_BLOB
) &&
3504 view
->parent
== VIEW(REQ_VIEW_TREE
)) ||
3505 (view
== VIEW(REQ_VIEW_MAIN
) &&
3506 view
->parent
== VIEW(REQ_VIEW_BRANCH
))) {
3509 view
= view
->parent
;
3510 line
= view
->lineno
;
3511 move_view(view
, request
);
3512 if (view_is_displayed(view
))
3513 update_view_title(view
);
3514 if (line
!= view
->lineno
)
3515 view
->ops
->request(view
, REQ_ENTER
,
3516 &view
->line
[view
->lineno
]);
3519 move_view(view
, request
);
3525 int nviews
= displayed_views();
3526 int next_view
= (current_view
+ 1) % nviews
;
3528 if (next_view
== current_view
) {
3529 report("Only one view is displayed");
3533 current_view
= next_view
;
3534 /* Blur out the title of the previous view. */
3535 update_view_title(view
);
3540 report("Refreshing is not yet supported for the %s view", view
->name
);
3544 if (displayed_views() == 2)
3545 maximize_view(view
);
3552 case REQ_TOGGLE_LINENO
:
3553 toggle_view_option(&opt_line_number
, "line numbers");
3556 case REQ_TOGGLE_DATE
:
3560 case REQ_TOGGLE_AUTHOR
:
3564 case REQ_TOGGLE_REV_GRAPH
:
3565 toggle_view_option(&opt_rev_graph
, "revision graph display");
3568 case REQ_TOGGLE_REFS
:
3569 toggle_view_option(&opt_show_refs
, "reference display");
3572 case REQ_TOGGLE_SORT_FIELD
:
3573 case REQ_TOGGLE_SORT_ORDER
:
3574 report("Sorting is not yet supported for the %s view", view
->name
);
3578 case REQ_SEARCH_BACK
:
3579 search_view(view
, request
);
3584 find_next(view
, request
);
3587 case REQ_STOP_LOADING
:
3588 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
3591 report("Stopped loading the %s view", view
->name
),
3592 end_update(view
, TRUE
);
3596 case REQ_SHOW_VERSION
:
3597 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3600 case REQ_SCREEN_REDRAW
:
3601 redraw_display(TRUE
);
3605 report("Nothing to edit");
3609 report("Nothing to enter");
3612 case REQ_VIEW_CLOSE
:
3613 /* XXX: Mark closed views by letting view->parent point to the
3614 * view itself. Parents to closed view should never be
3617 view
->parent
->parent
!= view
->parent
) {
3618 maximize_view(view
->parent
);
3619 view
->parent
= view
;
3627 report("Unknown key, press %s for help",
3628 get_key(view
->keymap
, REQ_VIEW_HELP
));
3637 * View backend utilities
3647 const enum sort_field
*fields
;
3648 size_t size
, current
;
3652 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3653 #define get_sort_field(state) ((state).fields[(state).current])
3654 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3657 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3658 int (*compare
)(const void *, const void *))
3661 case REQ_TOGGLE_SORT_FIELD
:
3662 state
->current
= (state
->current
+ 1) % state
->size
;
3665 case REQ_TOGGLE_SORT_ORDER
:
3666 state
->reverse
= !state
->reverse
;
3669 die("Not a sort request");
3672 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3676 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3678 /* Small author cache to reduce memory consumption. It uses binary
3679 * search to lookup or find place to position new entries. No entries
3680 * are ever freed. */
3682 get_author(const char *name
)
3684 static const char **authors
;
3685 static size_t authors_size
;
3686 int from
= 0, to
= authors_size
- 1;
3688 while (from
<= to
) {
3689 size_t pos
= (to
+ from
) / 2;
3690 int cmp
= strcmp(name
, authors
[pos
]);
3693 return authors
[pos
];
3701 if (!realloc_authors(&authors
, authors_size
, 1))
3703 name
= strdup(name
);
3707 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3708 authors
[from
] = name
;
3715 parse_timesec(struct time
*time
, const char *sec
)
3717 time
->sec
= (time_t) atol(sec
);
3721 parse_timezone(struct time
*time
, const char *zone
)
3725 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3726 tz
+= ('0' - zone
[2]) * 60 * 60;
3727 tz
+= ('0' - zone
[3]) * 60;
3728 tz
+= ('0' - zone
[4]);
3737 /* Parse author lines where the name may be empty:
3738 * author <email@address.tld> 1138474660 +0100
3741 parse_author_line(char *ident
, const char **author
, struct time
*time
)
3743 char *nameend
= strchr(ident
, '<');
3744 char *emailend
= strchr(ident
, '>');
3746 if (nameend
&& emailend
)
3747 *nameend
= *emailend
= 0;
3748 ident
= chomp_string(ident
);
3751 ident
= chomp_string(nameend
+ 1);
3756 *author
= get_author(ident
);
3758 /* Parse epoch and timezone */
3759 if (emailend
&& emailend
[1] == ' ') {
3760 char *secs
= emailend
+ 2;
3761 char *zone
= strchr(secs
, ' ');
3763 parse_timesec(time
, secs
);
3765 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
3766 parse_timezone(time
, zone
+ 1);
3771 open_commit_parent_menu(char buf
[SIZEOF_STR
], int *parents
)
3773 char rev
[SIZEOF_REV
];
3774 const char *revlist_argv
[] = {
3775 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev
, NULL
3777 struct menu_item
*items
;
3778 char text
[SIZEOF_STR
];
3782 items
= calloc(*parents
+ 1, sizeof(*items
));
3786 for (i
= 0; i
< *parents
; i
++) {
3787 string_copy_rev(rev
, &buf
[SIZEOF_REV
* i
]);
3788 if (!io_run_buf(revlist_argv
, text
, sizeof(text
)) ||
3789 !(items
[i
].text
= strdup(text
))) {
3797 ok
= prompt_menu("Select parent", items
, parents
);
3799 for (i
= 0; items
[i
].text
; i
++)
3800 free((char *) items
[i
].text
);
3806 select_commit_parent(const char *id
, char rev
[SIZEOF_REV
], const char *path
)
3808 char buf
[SIZEOF_STR
* 4];
3809 const char *revlist_argv
[] = {
3810 "git", "log", "--no-color", "-1",
3811 "--pretty=format:%P", id
, "--", path
, NULL
3815 if (!io_run_buf(revlist_argv
, buf
, sizeof(buf
)) ||
3816 (parents
= strlen(buf
) / 40) < 0) {
3817 report("Failed to get parent information");
3820 } else if (parents
== 0) {
3822 report("Path '%s' does not exist in the parent", path
);
3824 report("The selected commit has no parents");
3828 if (parents
> 1 && !open_commit_parent_menu(buf
, &parents
))
3831 string_copy_rev(rev
, &buf
[41 * parents
]);
3840 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
3842 char text
[SIZEOF_STR
];
3844 if (opt_line_number
&& draw_lineno(view
, lineno
))
3847 string_expand(text
, sizeof(text
), line
->data
, opt_tab_size
);
3848 draw_text(view
, line
->type
, text
, TRUE
);
3853 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
3855 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
3856 char ref
[SIZEOF_STR
];
3858 if (!io_run_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
3861 /* This is the only fatal call, since it can "corrupt" the buffer. */
3862 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
3869 add_pager_refs(struct view
*view
, struct line
*line
)
3871 char buf
[SIZEOF_STR
];
3872 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
3873 struct ref_list
*list
;
3874 size_t bufpos
= 0, i
;
3875 const char *sep
= "Refs: ";
3876 bool is_tag
= FALSE
;
3878 assert(line
->type
== LINE_COMMIT
);
3880 list
= get_ref_list(commit_id
);
3882 if (view
== VIEW(REQ_VIEW_DIFF
))
3883 goto try_add_describe_ref
;
3887 for (i
= 0; i
< list
->size
; i
++) {
3888 struct ref
*ref
= list
->refs
[i
];
3889 const char *fmt
= ref
->tag
? "%s[%s]" :
3890 ref
->remote
? "%s<%s>" : "%s%s";
3892 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
3899 if (!is_tag
&& view
== VIEW(REQ_VIEW_DIFF
)) {
3900 try_add_describe_ref
:
3901 /* Add <tag>-g<commit_id> "fake" reference. */
3902 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
3909 add_line_text(view
, buf
, LINE_PP_REFS
);
3913 pager_read(struct view
*view
, char *data
)
3920 line
= add_line_text(view
, data
, get_line_type(data
));
3924 if (line
->type
== LINE_COMMIT
&&
3925 (view
== VIEW(REQ_VIEW_DIFF
) ||
3926 view
== VIEW(REQ_VIEW_LOG
)))
3927 add_pager_refs(view
, line
);
3933 pager_request(struct view
*view
, enum request request
, struct line
*line
)
3937 if (request
!= REQ_ENTER
)
3940 if (line
->type
== LINE_COMMIT
&&
3941 (view
== VIEW(REQ_VIEW_LOG
) ||
3942 view
== VIEW(REQ_VIEW_PAGER
))) {
3943 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
3947 /* Always scroll the view even if it was split. That way
3948 * you can use Enter to scroll through the log view and
3949 * split open each commit diff. */
3950 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
3952 /* FIXME: A minor workaround. Scrolling the view will call report("")
3953 * but if we are scrolling a non-current view this won't properly
3954 * update the view title. */
3956 update_view_title(view
);
3962 pager_grep(struct view
*view
, struct line
*line
)
3964 const char *text
[] = { line
->data
, NULL
};
3966 return grep_text(view
, text
);
3970 pager_select(struct view
*view
, struct line
*line
)
3972 if (line
->type
== LINE_COMMIT
) {
3973 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
3975 if (view
!= VIEW(REQ_VIEW_PAGER
))
3976 string_copy_rev(view
->ref
, text
);
3977 string_copy_rev(ref_commit
, text
);
3981 static struct view_ops pager_ops
= {
3992 static const char *log_argv
[SIZEOF_ARG
] = {
3993 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3997 log_request(struct view
*view
, enum request request
, struct line
*line
)
4002 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
4005 return pager_request(view
, request
, line
);
4009 static struct view_ops log_ops
= {
4020 static const char *diff_argv
[SIZEOF_ARG
] = {
4021 "git", "show", "--pretty=fuller", "--no-color", "--root",
4022 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4025 static struct view_ops diff_ops
= {
4040 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
4043 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
4047 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
4048 help_keymap_hidden
[keymap
] ? '+' : '-',
4049 enum_name(keymap_table
[keymap
]));
4051 line
->other
= keymap
;
4053 return help_keymap_hidden
[keymap
];
4057 help_open_keymap(struct view
*view
, enum keymap keymap
)
4059 const char *group
= NULL
;
4060 char buf
[SIZEOF_STR
];
4062 bool add_title
= TRUE
;
4065 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
4066 const char *key
= NULL
;
4068 if (req_info
[i
].request
== REQ_NONE
)
4071 if (!req_info
[i
].request
) {
4072 group
= req_info
[i
].help
;
4076 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4080 if (add_title
&& help_open_keymap_title(view
, keymap
))
4085 add_line_text(view
, group
, LINE_HELP_GROUP
);
4089 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4090 enum_name(req_info
[i
]), req_info
[i
].help
);
4093 group
= "External commands:";
4095 for (i
= 0; i
< run_requests
; i
++) {
4096 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4100 if (!req
|| req
->keymap
!= keymap
)
4103 key
= get_key_name(req
->key
);
4105 key
= "(no key defined)";
4107 if (add_title
&& help_open_keymap_title(view
, keymap
))
4110 add_line_text(view
, group
, LINE_HELP_GROUP
);
4114 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4115 if (!string_format_from(buf
, &bufpos
, "%s%s",
4116 argc
? " " : "", req
->argv
[argc
]))
4119 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4124 help_open(struct view
*view
)
4129 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4130 add_line_text(view
, "", LINE_DEFAULT
);
4132 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4133 help_open_keymap(view
, keymap
);
4139 help_request(struct view
*view
, enum request request
, struct line
*line
)
4143 if (line
->type
== LINE_HELP_KEYMAP
) {
4144 help_keymap_hidden
[line
->other
] =
4145 !help_keymap_hidden
[line
->other
];
4146 view
->p_restore
= TRUE
;
4147 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4152 return pager_request(view
, request
, line
);
4156 static struct view_ops help_ops
= {
4172 struct tree_stack_entry
{
4173 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4174 unsigned long lineno
; /* Line number to restore */
4175 char *name
; /* Position of name in opt_path */
4178 /* The top of the path stack. */
4179 static struct tree_stack_entry
*tree_stack
= NULL
;
4180 unsigned long tree_lineno
= 0;
4183 pop_tree_stack_entry(void)
4185 struct tree_stack_entry
*entry
= tree_stack
;
4187 tree_lineno
= entry
->lineno
;
4189 tree_stack
= entry
->prev
;
4194 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4196 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4197 size_t pathlen
= strlen(opt_path
);
4202 entry
->prev
= tree_stack
;
4203 entry
->name
= opt_path
+ pathlen
;
4206 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4207 pop_tree_stack_entry();
4211 /* Move the current line to the first tree entry. */
4213 entry
->lineno
= lineno
;
4216 /* Parse output from git-ls-tree(1):
4218 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4221 #define SIZEOF_TREE_ATTR \
4222 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4224 #define SIZEOF_TREE_MODE \
4225 STRING_SIZE("100644 ")
4227 #define TREE_ID_OFFSET \
4228 STRING_SIZE("100644 blob ")
4231 char id
[SIZEOF_REV
];
4233 struct time time
; /* Date from the author ident. */
4234 const char *author
; /* Author of the commit. */
4239 tree_path(const struct line
*line
)
4241 return ((struct tree_entry
*) line
->data
)->name
;
4245 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4247 if (line1
->type
!= line2
->type
)
4248 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4249 return strcmp(tree_path(line1
), tree_path(line2
));
4252 static const enum sort_field tree_sort_fields
[] = {
4253 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4255 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4258 tree_compare(const void *l1
, const void *l2
)
4260 const struct line
*line1
= (const struct line
*) l1
;
4261 const struct line
*line2
= (const struct line
*) l2
;
4262 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4263 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4265 if (line1
->type
== LINE_TREE_HEAD
)
4267 if (line2
->type
== LINE_TREE_HEAD
)
4270 switch (get_sort_field(tree_sort_state
)) {
4272 return sort_order(tree_sort_state
, timecmp(&entry1
->time
, &entry2
->time
));
4274 case ORDERBY_AUTHOR
:
4275 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4279 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4284 static struct line
*
4285 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4286 const char *mode
, const char *id
)
4288 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4289 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4291 if (!entry
|| !line
) {
4296 strncpy(entry
->name
, path
, strlen(path
));
4298 entry
->mode
= strtoul(mode
, NULL
, 8);
4300 string_copy_rev(entry
->id
, id
);
4306 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4308 static const char *author_name
;
4309 static struct time author_time
;
4311 if (!text
&& *read_date
) {
4316 char *path
= *opt_path
? opt_path
: ".";
4317 /* Find next entry to process */
4318 const char *log_file
[] = {
4319 "git", "log", "--no-color", "--pretty=raw",
4320 "--cc", "--raw", view
->id
, "--", path
, NULL
4325 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4326 report("Tree is empty");
4330 if (!io_run_rd(&io
, log_file
, opt_cdup
, FORMAT_NONE
)) {
4331 report("Failed to load tree data");
4335 io_done(view
->pipe
);
4340 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4341 parse_author_line(text
+ STRING_SIZE("author "),
4342 &author_name
, &author_time
);
4344 } else if (*text
== ':') {
4346 size_t annotated
= 1;
4349 pos
= strchr(text
, '\t');
4353 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4354 text
+= strlen(opt_path
);
4355 pos
= strchr(text
, '/');
4359 for (i
= 1; i
< view
->lines
; i
++) {
4360 struct line
*line
= &view
->line
[i
];
4361 struct tree_entry
*entry
= line
->data
;
4363 annotated
+= !!entry
->author
;
4364 if (entry
->author
|| strcmp(entry
->name
, text
))
4367 entry
->author
= author_name
;
4368 entry
->time
= author_time
;
4373 if (annotated
== view
->lines
)
4374 io_kill(view
->pipe
);
4380 tree_read(struct view
*view
, char *text
)
4382 static bool read_date
= FALSE
;
4383 struct tree_entry
*data
;
4384 struct line
*entry
, *line
;
4385 enum line_type type
;
4386 size_t textlen
= text
? strlen(text
) : 0;
4387 char *path
= text
+ SIZEOF_TREE_ATTR
;
4389 if (read_date
|| !text
)
4390 return tree_read_date(view
, text
, &read_date
);
4392 if (textlen
<= SIZEOF_TREE_ATTR
)
4394 if (view
->lines
== 0 &&
4395 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4398 /* Strip the path part ... */
4400 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4401 size_t striplen
= strlen(opt_path
);
4403 if (pathlen
> striplen
)
4404 memmove(path
, path
+ striplen
,
4405 pathlen
- striplen
+ 1);
4407 /* Insert "link" to parent directory. */
4408 if (view
->lines
== 1 &&
4409 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4413 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4414 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4419 /* Skip "Directory ..." and ".." line. */
4420 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4421 if (tree_compare_entry(line
, entry
) <= 0)
4424 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4428 for (; line
<= entry
; line
++)
4429 line
->dirty
= line
->cleareol
= 1;
4433 if (tree_lineno
> view
->lineno
) {
4434 view
->lineno
= tree_lineno
;
4442 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4444 struct tree_entry
*entry
= line
->data
;
4446 if (line
->type
== LINE_TREE_HEAD
) {
4447 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4450 if (draw_mode(view
, entry
->mode
))
4453 if (opt_author
&& draw_author(view
, entry
->author
))
4456 if (opt_date
&& draw_date(view
, &entry
->time
))
4459 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4467 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4468 int fd
= mkstemp(file
);
4471 report("Failed to create temporary file");
4472 else if (!io_run_append(blob_ops
.argv
, FORMAT_ALL
, fd
))
4473 report("Failed to save blob data to file");
4481 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4483 enum open_flags flags
;
4486 case REQ_VIEW_BLAME
:
4487 if (line
->type
!= LINE_TREE_FILE
) {
4488 report("Blame only supported for files");
4492 string_copy(opt_ref
, view
->vid
);
4496 if (line
->type
!= LINE_TREE_FILE
) {
4497 report("Edit only supported for files");
4498 } else if (!is_head_commit(view
->vid
)) {
4501 open_editor(opt_file
);
4505 case REQ_TOGGLE_SORT_FIELD
:
4506 case REQ_TOGGLE_SORT_ORDER
:
4507 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4512 /* quit view if at top of tree */
4513 return REQ_VIEW_CLOSE
;
4516 line
= &view
->line
[1];
4526 /* Cleanup the stack if the tree view is at a different tree. */
4527 while (!*opt_path
&& tree_stack
)
4528 pop_tree_stack_entry();
4530 switch (line
->type
) {
4532 /* Depending on whether it is a subdirectory or parent link
4533 * mangle the path buffer. */
4534 if (line
== &view
->line
[1] && *opt_path
) {
4535 pop_tree_stack_entry();
4538 const char *basename
= tree_path(line
);
4540 push_tree_stack_entry(basename
, view
->lineno
);
4543 /* Trees and subtrees share the same ID, so they are not not
4544 * unique like blobs. */
4545 flags
= OPEN_RELOAD
;
4546 request
= REQ_VIEW_TREE
;
4549 case LINE_TREE_FILE
:
4550 flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4551 request
= REQ_VIEW_BLOB
;
4558 open_view(view
, request
, flags
);
4559 if (request
== REQ_VIEW_TREE
)
4560 view
->lineno
= tree_lineno
;
4566 tree_grep(struct view
*view
, struct line
*line
)
4568 struct tree_entry
*entry
= line
->data
;
4569 const char *text
[] = {
4571 opt_author
? entry
->author
: "",
4572 mkdate(&entry
->time
, opt_date
),
4576 return grep_text(view
, text
);
4580 tree_select(struct view
*view
, struct line
*line
)
4582 struct tree_entry
*entry
= line
->data
;
4584 if (line
->type
== LINE_TREE_FILE
) {
4585 string_copy_rev(ref_blob
, entry
->id
);
4586 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4588 } else if (line
->type
!= LINE_TREE_DIR
) {
4592 string_copy_rev(view
->ref
, entry
->id
);
4596 tree_prepare(struct view
*view
)
4598 if (view
->lines
== 0 && opt_prefix
[0]) {
4599 char *pos
= opt_prefix
;
4601 while (pos
&& *pos
) {
4602 char *end
= strchr(pos
, '/');
4606 push_tree_stack_entry(pos
, 0);
4614 } else if (strcmp(view
->vid
, view
->id
)) {
4618 return io_format(&view
->io
, opt_cdup
, IO_RD
, view
->ops
->argv
, FORMAT_ALL
);
4621 static const char *tree_argv
[SIZEOF_ARG
] = {
4622 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4625 static struct view_ops tree_ops
= {
4638 blob_read(struct view
*view
, char *line
)
4642 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4646 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4653 return pager_request(view
, request
, line
);
4657 static const char *blob_argv
[SIZEOF_ARG
] = {
4658 "git", "cat-file", "blob", "%(blob)", NULL
4661 static struct view_ops blob_ops
= {
4675 * Loading the blame view is a two phase job:
4677 * 1. File content is read either using opt_file from the
4678 * filesystem or using git-cat-file.
4679 * 2. Then blame information is incrementally added by
4680 * reading output from git-blame.
4683 static const char *blame_head_argv
[] = {
4684 "git", "blame", "--incremental", "--", "%(file)", NULL
4687 static const char *blame_ref_argv
[] = {
4688 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4691 static const char *blame_cat_file_argv
[] = {
4692 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4695 struct blame_commit
{
4696 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4697 char title
[128]; /* First line of the commit message. */
4698 const char *author
; /* Author of the commit. */
4699 struct time time
; /* Date from the author ident. */
4700 char filename
[128]; /* Name of file. */
4701 bool has_previous
; /* Was a "previous" line detected. */
4705 struct blame_commit
*commit
;
4706 unsigned long lineno
;
4711 blame_open(struct view
*view
)
4713 char path
[SIZEOF_STR
];
4715 if (!view
->parent
&& *opt_prefix
) {
4716 string_copy(path
, opt_file
);
4717 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4721 if (*opt_ref
|| !io_open(&view
->io
, "%s%s", opt_cdup
, opt_file
)) {
4722 if (!io_run_rd(&view
->io
, blame_cat_file_argv
, opt_cdup
, FORMAT_ALL
))
4726 setup_update(view
, opt_file
);
4727 string_format(view
->ref
, "%s ...", opt_file
);
4732 static struct blame_commit
*
4733 get_blame_commit(struct view
*view
, const char *id
)
4737 for (i
= 0; i
< view
->lines
; i
++) {
4738 struct blame
*blame
= view
->line
[i
].data
;
4743 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4744 return blame
->commit
;
4748 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4751 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4757 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4759 const char *pos
= *posref
;
4762 pos
= strchr(pos
+ 1, ' ');
4763 if (!pos
|| !isdigit(pos
[1]))
4765 *number
= atoi(pos
+ 1);
4766 if (*number
< min
|| *number
> max
)
4773 static struct blame_commit
*
4774 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4776 struct blame_commit
*commit
;
4777 struct blame
*blame
;
4778 const char *pos
= text
+ SIZEOF_REV
- 2;
4779 size_t orig_lineno
= 0;
4783 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4786 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4787 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4788 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4791 commit
= get_blame_commit(view
, text
);
4797 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4800 blame
->commit
= commit
;
4801 blame
->lineno
= orig_lineno
+ group
- 1;
4809 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4812 const char **argv
= *opt_ref
? blame_ref_argv
: blame_head_argv
;
4815 if (view
->lines
== 0 && !view
->parent
)
4816 die("No blame exist for %s", view
->vid
);
4818 if (view
->lines
== 0 || !io_run_rd(&io
, argv
, opt_cdup
, FORMAT_ALL
)) {
4819 report("Failed to load blame data");
4823 io_done(view
->pipe
);
4829 size_t linelen
= strlen(line
);
4830 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
4835 blame
->commit
= NULL
;
4836 strncpy(blame
->text
, line
, linelen
);
4837 blame
->text
[linelen
] = 0;
4838 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
4843 match_blame_header(const char *name
, char **line
)
4845 size_t namelen
= strlen(name
);
4846 bool matched
= !strncmp(name
, *line
, namelen
);
4855 blame_read(struct view
*view
, char *line
)
4857 static struct blame_commit
*commit
= NULL
;
4858 static int blamed
= 0;
4859 static bool read_file
= TRUE
;
4862 return blame_read_file(view
, line
, &read_file
);
4869 string_format(view
->ref
, "%s", view
->vid
);
4870 if (view_is_displayed(view
)) {
4871 update_view_title(view
);
4872 redraw_view_from(view
, 0);
4878 commit
= parse_blame_commit(view
, line
, &blamed
);
4879 string_format(view
->ref
, "%s %2d%%", view
->vid
,
4880 view
->lines
? blamed
* 100 / view
->lines
: 0);
4882 } else if (match_blame_header("author ", &line
)) {
4883 commit
->author
= get_author(line
);
4885 } else if (match_blame_header("author-time ", &line
)) {
4886 parse_timesec(&commit
->time
, line
);
4888 } else if (match_blame_header("author-tz ", &line
)) {
4889 parse_timezone(&commit
->time
, line
);
4891 } else if (match_blame_header("summary ", &line
)) {
4892 string_ncopy(commit
->title
, line
, strlen(line
));
4894 } else if (match_blame_header("previous ", &line
)) {
4895 commit
->has_previous
= TRUE
;
4897 } else if (match_blame_header("filename ", &line
)) {
4898 string_ncopy(commit
->filename
, line
, strlen(line
));
4906 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4908 struct blame
*blame
= line
->data
;
4909 struct time
*time
= NULL
;
4910 const char *id
= NULL
, *author
= NULL
;
4911 char text
[SIZEOF_STR
];
4913 if (blame
->commit
&& *blame
->commit
->filename
) {
4914 id
= blame
->commit
->id
;
4915 author
= blame
->commit
->author
;
4916 time
= &blame
->commit
->time
;
4919 if (opt_date
&& draw_date(view
, time
))
4922 if (opt_author
&& draw_author(view
, author
))
4925 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
4928 if (draw_lineno(view
, lineno
))
4931 string_expand(text
, sizeof(text
), blame
->text
, opt_tab_size
);
4932 draw_text(view
, LINE_DEFAULT
, text
, TRUE
);
4937 check_blame_commit(struct blame
*blame
, bool check_null_id
)
4940 report("Commit data not loaded yet");
4941 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4942 report("No commit exist for the selected line");
4949 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
4951 const char *diff_tree_argv
[] = {
4952 "git", "diff-tree", "-U0", blame
->commit
->id
,
4953 "--", blame
->commit
->filename
, NULL
4956 int parent_lineno
= -1;
4957 int blamed_lineno
= -1;
4960 if (!io_run(&io
, diff_tree_argv
, NULL
, IO_RD
))
4963 while ((line
= io_get(&io
, '\n', TRUE
))) {
4965 char *pos
= strchr(line
, '+');
4967 parent_lineno
= atoi(line
+ 4);
4969 blamed_lineno
= atoi(pos
+ 1);
4971 } else if (*line
== '+' && parent_lineno
!= -1) {
4972 if (blame
->lineno
== blamed_lineno
- 1 &&
4973 !strcmp(blame
->text
, line
+ 1)) {
4974 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
4985 blame_request(struct view
*view
, enum request request
, struct line
*line
)
4987 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4988 struct blame
*blame
= line
->data
;
4991 case REQ_VIEW_BLAME
:
4992 if (check_blame_commit(blame
, TRUE
)) {
4993 string_copy(opt_ref
, blame
->commit
->id
);
4994 string_copy(opt_file
, blame
->commit
->filename
);
4996 view
->lineno
= blame
->lineno
;
4997 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5002 if (check_blame_commit(blame
, TRUE
) &&
5003 select_commit_parent(blame
->commit
->id
, opt_ref
,
5004 blame
->commit
->filename
)) {
5005 string_copy(opt_file
, blame
->commit
->filename
);
5006 setup_blame_parent_line(view
, blame
);
5007 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
5012 if (!check_blame_commit(blame
, FALSE
))
5015 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
5016 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
5019 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
5020 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
5021 const char *diff_index_argv
[] = {
5022 "git", "diff-index", "--root", "--patch-with-stat",
5023 "-C", "-M", "HEAD", "--", view
->vid
, NULL
5026 if (!blame
->commit
->has_previous
) {
5027 diff_index_argv
[1] = "diff";
5028 diff_index_argv
[2] = "--no-color";
5029 diff_index_argv
[6] = "--";
5030 diff_index_argv
[7] = "/dev/null";
5033 if (!prepare_update(diff
, diff_index_argv
, NULL
, FORMAT_DASH
)) {
5034 report("Failed to allocate diff command");
5037 flags
|= OPEN_PREPARED
;
5040 open_view(view
, REQ_VIEW_DIFF
, flags
);
5041 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
5042 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
5053 blame_grep(struct view
*view
, struct line
*line
)
5055 struct blame
*blame
= line
->data
;
5056 struct blame_commit
*commit
= blame
->commit
;
5057 const char *text
[] = {
5059 commit
? commit
->title
: "",
5060 commit
? commit
->id
: "",
5061 commit
&& opt_author
? commit
->author
: "",
5062 commit
? mkdate(&commit
->time
, opt_date
) : "",
5066 return grep_text(view
, text
);
5070 blame_select(struct view
*view
, struct line
*line
)
5072 struct blame
*blame
= line
->data
;
5073 struct blame_commit
*commit
= blame
->commit
;
5078 if (!strcmp(commit
->id
, NULL_ID
))
5079 string_ncopy(ref_commit
, "HEAD", 4);
5081 string_copy_rev(ref_commit
, commit
->id
);
5084 static struct view_ops blame_ops
= {
5100 const char *author
; /* Author of the last commit. */
5101 struct time time
; /* Date of the last activity. */
5102 const struct ref
*ref
; /* Name and commit ID information. */
5105 static const struct ref branch_all
;
5107 static const enum sort_field branch_sort_fields
[] = {
5108 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5110 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5113 branch_compare(const void *l1
, const void *l2
)
5115 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5116 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5118 switch (get_sort_field(branch_sort_state
)) {
5120 return sort_order(branch_sort_state
, timecmp(&branch1
->time
, &branch2
->time
));
5122 case ORDERBY_AUTHOR
:
5123 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5127 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5132 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5134 struct branch
*branch
= line
->data
;
5135 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5137 if (opt_date
&& draw_date(view
, &branch
->time
))
5140 if (opt_author
&& draw_author(view
, branch
->author
))
5143 draw_text(view
, type
, branch
->ref
== &branch_all
? "All branches" : branch
->ref
->name
, TRUE
);
5148 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5150 struct branch
*branch
= line
->data
;
5155 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5158 case REQ_TOGGLE_SORT_FIELD
:
5159 case REQ_TOGGLE_SORT_ORDER
:
5160 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5164 if (branch
->ref
== &branch_all
) {
5165 const char *all_branches_argv
[] = {
5166 "git", "log", "--no-color", "--pretty=raw", "--parents",
5167 "--topo-order", "--all", NULL
5169 struct view
*main_view
= VIEW(REQ_VIEW_MAIN
);
5171 if (!prepare_update(main_view
, all_branches_argv
, NULL
, FORMAT_NONE
)) {
5172 report("Failed to load view of all branches");
5175 open_view(view
, REQ_VIEW_MAIN
, OPEN_PREPARED
| OPEN_SPLIT
);
5177 open_view(view
, REQ_VIEW_MAIN
, OPEN_SPLIT
);
5187 branch_read(struct view
*view
, char *line
)
5189 static char id
[SIZEOF_REV
];
5190 struct branch
*reference
;
5196 switch (get_line_type(line
)) {
5198 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5202 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5203 struct branch
*branch
= view
->line
[i
].data
;
5205 if (strcmp(branch
->ref
->id
, id
))
5208 view
->line
[i
].dirty
= TRUE
;
5210 branch
->author
= reference
->author
;
5211 branch
->time
= reference
->time
;
5215 parse_author_line(line
+ STRING_SIZE("author "),
5216 &branch
->author
, &branch
->time
);
5228 branch_open_visitor(void *data
, const struct ref
*ref
)
5230 struct view
*view
= data
;
5231 struct branch
*branch
;
5233 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5236 branch
= calloc(1, sizeof(*branch
));
5241 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5245 branch_open(struct view
*view
)
5247 const char *branch_log
[] = {
5248 "git", "log", "--no-color", "--pretty=raw",
5249 "--simplify-by-decoration", "--all", NULL
5252 if (!io_run_rd(&view
->io
, branch_log
, NULL
, FORMAT_NONE
)) {
5253 report("Failed to load branch data");
5257 setup_update(view
, view
->id
);
5258 branch_open_visitor(view
, &branch_all
);
5259 foreach_ref(branch_open_visitor
, view
);
5260 view
->p_restore
= TRUE
;
5266 branch_grep(struct view
*view
, struct line
*line
)
5268 struct branch
*branch
= line
->data
;
5269 const char *text
[] = {
5275 return grep_text(view
, text
);
5279 branch_select(struct view
*view
, struct line
*line
)
5281 struct branch
*branch
= line
->data
;
5283 string_copy_rev(view
->ref
, branch
->ref
->id
);
5284 string_copy_rev(ref_commit
, branch
->ref
->id
);
5285 string_copy_rev(ref_head
, branch
->ref
->id
);
5288 static struct view_ops branch_ops
= {
5307 char rev
[SIZEOF_REV
];
5308 char name
[SIZEOF_STR
];
5312 char rev
[SIZEOF_REV
];
5313 char name
[SIZEOF_STR
];
5317 static char status_onbranch
[SIZEOF_STR
];
5318 static struct status stage_status
;
5319 static enum line_type stage_line_type
;
5320 static size_t stage_chunks
;
5321 static int *stage_chunk
;
5323 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5325 /* This should work even for the "On branch" line. */
5327 status_has_none(struct view
*view
, struct line
*line
)
5329 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5332 /* Get fields from the diff line:
5333 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5336 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5338 const char *old_mode
= buf
+ 1;
5339 const char *new_mode
= buf
+ 8;
5340 const char *old_rev
= buf
+ 15;
5341 const char *new_rev
= buf
+ 56;
5342 const char *status
= buf
+ 97;
5345 old_mode
[-1] != ':' ||
5346 new_mode
[-1] != ' ' ||
5347 old_rev
[-1] != ' ' ||
5348 new_rev
[-1] != ' ' ||
5352 file
->status
= *status
;
5354 string_copy_rev(file
->old
.rev
, old_rev
);
5355 string_copy_rev(file
->new.rev
, new_rev
);
5357 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5358 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5360 file
->old
.name
[0] = file
->new.name
[0] = 0;
5366 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5368 struct status
*unmerged
= NULL
;
5372 if (!io_run(&io
, argv
, opt_cdup
, IO_RD
))
5375 add_line_data(view
, NULL
, type
);
5377 while ((buf
= io_get(&io
, 0, TRUE
))) {
5378 struct status
*file
= unmerged
;
5381 file
= calloc(1, sizeof(*file
));
5382 if (!file
|| !add_line_data(view
, file
, type
))
5386 /* Parse diff info part. */
5388 file
->status
= status
;
5390 string_copy(file
->old
.rev
, NULL_ID
);
5392 } else if (!file
->status
|| file
== unmerged
) {
5393 if (!status_get_diff(file
, buf
, strlen(buf
)))
5396 buf
= io_get(&io
, 0, TRUE
);
5400 /* Collapse all modified entries that follow an
5401 * associated unmerged entry. */
5402 if (unmerged
== file
) {
5403 unmerged
->status
= 'U';
5405 } else if (file
->status
== 'U') {
5410 /* Grab the old name for rename/copy. */
5411 if (!*file
->old
.name
&&
5412 (file
->status
== 'R' || file
->status
== 'C')) {
5413 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5415 buf
= io_get(&io
, 0, TRUE
);
5420 /* git-ls-files just delivers a NUL separated list of
5421 * file names similar to the second half of the
5422 * git-diff-* output. */
5423 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5424 if (!*file
->old
.name
)
5425 string_copy(file
->old
.name
, file
->new.name
);
5429 if (io_error(&io
)) {
5435 if (!view
->line
[view
->lines
- 1].data
)
5436 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5442 /* Don't show unmerged entries in the staged section. */
5443 static const char *status_diff_index_argv
[] = {
5444 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5445 "--cached", "-M", "HEAD", NULL
5448 static const char *status_diff_files_argv
[] = {
5449 "git", "diff-files", "-z", NULL
5452 static const char *status_list_other_argv
[] = {
5453 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix
, NULL
5456 static const char *status_list_no_head_argv
[] = {
5457 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5460 static const char *update_index_argv
[] = {
5461 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5464 /* Restore the previous line number to stay in the context or select a
5465 * line with something that can be updated. */
5467 status_restore(struct view
*view
)
5469 if (view
->p_lineno
>= view
->lines
)
5470 view
->p_lineno
= view
->lines
- 1;
5471 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5473 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5476 /* If the above fails, always skip the "On branch" line. */
5477 if (view
->p_lineno
< view
->lines
)
5478 view
->lineno
= view
->p_lineno
;
5482 if (view
->lineno
< view
->offset
)
5483 view
->offset
= view
->lineno
;
5484 else if (view
->offset
+ view
->height
<= view
->lineno
)
5485 view
->offset
= view
->lineno
- view
->height
+ 1;
5487 view
->p_restore
= FALSE
;
5491 status_update_onbranch(void)
5493 static const char *paths
[][2] = {
5494 { "rebase-apply/rebasing", "Rebasing" },
5495 { "rebase-apply/applying", "Applying mailbox" },
5496 { "rebase-apply/", "Rebasing mailbox" },
5497 { "rebase-merge/interactive", "Interactive rebase" },
5498 { "rebase-merge/", "Rebase merge" },
5499 { "MERGE_HEAD", "Merging" },
5500 { "BISECT_LOG", "Bisecting" },
5501 { "HEAD", "On branch" },
5503 char buf
[SIZEOF_STR
];
5507 if (is_initial_commit()) {
5508 string_copy(status_onbranch
, "Initial commit");
5512 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5513 char *head
= opt_head
;
5515 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5516 lstat(buf
, &stat
) < 0)
5522 if (io_open(&io
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5523 io_read_buf(&io
, buf
, sizeof(buf
))) {
5525 if (!prefixcmp(head
, "refs/heads/"))
5526 head
+= STRING_SIZE("refs/heads/");
5530 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5531 string_copy(status_onbranch
, opt_head
);
5535 string_copy(status_onbranch
, "Not currently on any branch");
5538 /* First parse staged info using git-diff-index(1), then parse unstaged
5539 * info using git-diff-files(1), and finally untracked files using
5540 * git-ls-files(1). */
5542 status_open(struct view
*view
)
5546 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5547 status_update_onbranch();
5549 io_run_bg(update_index_argv
);
5551 if (is_initial_commit()) {
5552 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5554 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5558 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5559 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5562 /* Restore the exact position or use the specialized restore
5564 if (!view
->p_restore
)
5565 status_restore(view
);
5570 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5572 struct status
*status
= line
->data
;
5573 enum line_type type
;
5577 switch (line
->type
) {
5578 case LINE_STAT_STAGED
:
5579 type
= LINE_STAT_SECTION
;
5580 text
= "Changes to be committed:";
5583 case LINE_STAT_UNSTAGED
:
5584 type
= LINE_STAT_SECTION
;
5585 text
= "Changed but not updated:";
5588 case LINE_STAT_UNTRACKED
:
5589 type
= LINE_STAT_SECTION
;
5590 text
= "Untracked files:";
5593 case LINE_STAT_NONE
:
5594 type
= LINE_DEFAULT
;
5595 text
= " (no files)";
5598 case LINE_STAT_HEAD
:
5599 type
= LINE_STAT_HEAD
;
5600 text
= status_onbranch
;
5607 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5609 buf
[0] = status
->status
;
5610 if (draw_text(view
, line
->type
, buf
, TRUE
))
5612 type
= LINE_DEFAULT
;
5613 text
= status
->new.name
;
5616 draw_text(view
, type
, text
, TRUE
);
5621 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5623 if (displayed_views() == 2 || display
[current_view
] != view
)
5624 maximize_view(view
);
5625 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5630 status_enter(struct view
*view
, struct line
*line
)
5632 struct status
*status
= line
->data
;
5633 const char *oldpath
= status
? status
->old
.name
: NULL
;
5634 /* Diffs for unmerged entries are empty when passing the new
5635 * path, so leave it empty. */
5636 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5638 enum open_flags split
;
5639 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5641 if (line
->type
== LINE_STAT_NONE
||
5642 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5643 report("No file to diff");
5647 switch (line
->type
) {
5648 case LINE_STAT_STAGED
:
5649 if (is_initial_commit()) {
5650 const char *no_head_diff_argv
[] = {
5651 "git", "diff", "--no-color", "--patch-with-stat",
5652 "--", "/dev/null", newpath
, NULL
5655 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
, FORMAT_DASH
))
5656 return status_load_error(view
, stage
, newpath
);
5658 const char *index_show_argv
[] = {
5659 "git", "diff-index", "--root", "--patch-with-stat",
5660 "-C", "-M", "--cached", "HEAD", "--",
5661 oldpath
, newpath
, NULL
5664 if (!prepare_update(stage
, index_show_argv
, opt_cdup
, FORMAT_DASH
))
5665 return status_load_error(view
, stage
, newpath
);
5669 info
= "Staged changes to %s";
5671 info
= "Staged changes";
5674 case LINE_STAT_UNSTAGED
:
5676 const char *files_show_argv
[] = {
5677 "git", "diff-files", "--root", "--patch-with-stat",
5678 "-C", "-M", "--", oldpath
, newpath
, NULL
5681 if (!prepare_update(stage
, files_show_argv
, opt_cdup
, FORMAT_DASH
))
5682 return status_load_error(view
, stage
, newpath
);
5684 info
= "Unstaged changes to %s";
5686 info
= "Unstaged changes";
5689 case LINE_STAT_UNTRACKED
:
5691 report("No file to show");
5695 if (!suffixcmp(status
->new.name
, -1, "/")) {
5696 report("Cannot display a directory");
5700 if (!prepare_update_file(stage
, newpath
))
5701 return status_load_error(view
, stage
, newpath
);
5702 info
= "Untracked file %s";
5705 case LINE_STAT_HEAD
:
5709 die("line type %d not handled in switch", line
->type
);
5712 split
= view_is_displayed(view
) ? OPEN_SPLIT
: 0;
5713 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5714 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5716 stage_status
= *status
;
5718 memset(&stage_status
, 0, sizeof(stage_status
));
5721 stage_line_type
= line
->type
;
5723 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5730 status_exists(struct status
*status
, enum line_type type
)
5732 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5733 unsigned long lineno
;
5735 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5736 struct line
*line
= &view
->line
[lineno
];
5737 struct status
*pos
= line
->data
;
5739 if (line
->type
!= type
)
5741 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5742 select_view_line(view
, lineno
);
5745 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5746 select_view_line(view
, lineno
);
5756 status_update_prepare(struct io
*io
, enum line_type type
)
5758 const char *staged_argv
[] = {
5759 "git", "update-index", "-z", "--index-info", NULL
5761 const char *others_argv
[] = {
5762 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5766 case LINE_STAT_STAGED
:
5767 return io_run(io
, staged_argv
, opt_cdup
, IO_WR
);
5769 case LINE_STAT_UNSTAGED
:
5770 case LINE_STAT_UNTRACKED
:
5771 return io_run(io
, others_argv
, opt_cdup
, IO_WR
);
5774 die("line type %d not handled in switch", type
);
5780 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5782 char buf
[SIZEOF_STR
];
5786 case LINE_STAT_STAGED
:
5787 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5790 status
->old
.name
, 0))
5794 case LINE_STAT_UNSTAGED
:
5795 case LINE_STAT_UNTRACKED
:
5796 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
5801 die("line type %d not handled in switch", type
);
5804 return io_write(io
, buf
, bufsize
);
5808 status_update_file(struct status
*status
, enum line_type type
)
5813 if (!status_update_prepare(&io
, type
))
5816 result
= status_update_write(&io
, status
, type
);
5817 return io_done(&io
) && result
;
5821 status_update_files(struct view
*view
, struct line
*line
)
5823 char buf
[sizeof(view
->ref
)];
5826 struct line
*pos
= view
->line
+ view
->lines
;
5829 int cursor_y
= -1, cursor_x
= -1;
5831 if (!status_update_prepare(&io
, line
->type
))
5834 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
5837 string_copy(buf
, view
->ref
);
5838 getsyx(cursor_y
, cursor_x
);
5839 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
5840 int almost_done
= file
* 100 / files
;
5842 if (almost_done
> done
) {
5844 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
5846 update_view_title(view
);
5847 setsyx(cursor_y
, cursor_x
);
5850 result
= status_update_write(&io
, line
->data
, line
->type
);
5852 string_copy(view
->ref
, buf
);
5854 return io_done(&io
) && result
;
5858 status_update(struct view
*view
)
5860 struct line
*line
= &view
->line
[view
->lineno
];
5862 assert(view
->lines
);
5865 /* This should work even for the "On branch" line. */
5866 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
5867 report("Nothing to update");
5871 if (!status_update_files(view
, line
+ 1)) {
5872 report("Failed to update file status");
5876 } else if (!status_update_file(line
->data
, line
->type
)) {
5877 report("Failed to update file status");
5885 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
5887 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
5888 if (type
== LINE_STAT_STAGED
) {
5889 report("Cannot revert changes to staged files");
5890 } else if (type
== LINE_STAT_UNTRACKED
) {
5891 report("Cannot revert changes to untracked files");
5892 } else if (has_none
) {
5893 report("Nothing to revert");
5895 report("Cannot revert changes to multiple files");
5898 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5899 char mode
[10] = "100644";
5900 const char *reset_argv
[] = {
5901 "git", "update-index", "--cacheinfo", mode
,
5902 status
->old
.rev
, status
->old
.name
, NULL
5904 const char *checkout_argv
[] = {
5905 "git", "checkout", "--", status
->old
.name
, NULL
5908 if (status
->status
== 'U') {
5909 string_format(mode
, "%5o", status
->old
.mode
);
5911 if (status
->old
.mode
== 0 && status
->new.mode
== 0) {
5912 reset_argv
[2] = "--force-remove";
5913 reset_argv
[3] = status
->old
.name
;
5914 reset_argv
[4] = NULL
;
5917 if (!io_run_fg(reset_argv
, opt_cdup
))
5919 if (status
->old
.mode
== 0 && status
->new.mode
== 0)
5923 return io_run_fg(checkout_argv
, opt_cdup
);
5930 status_request(struct view
*view
, enum request request
, struct line
*line
)
5932 struct status
*status
= line
->data
;
5935 case REQ_STATUS_UPDATE
:
5936 if (!status_update(view
))
5940 case REQ_STATUS_REVERT
:
5941 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
5945 case REQ_STATUS_MERGE
:
5946 if (!status
|| status
->status
!= 'U') {
5947 report("Merging only possible for files with unmerged status ('U').");
5950 open_mergetool(status
->new.name
);
5956 if (status
->status
== 'D') {
5957 report("File has been deleted.");
5961 open_editor(status
->new.name
);
5964 case REQ_VIEW_BLAME
:
5970 /* After returning the status view has been split to
5971 * show the stage view. No further reloading is
5973 return status_enter(view
, line
);
5976 /* Simply reload the view. */
5983 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
5989 status_select(struct view
*view
, struct line
*line
)
5991 struct status
*status
= line
->data
;
5992 char file
[SIZEOF_STR
] = "all files";
5996 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
5999 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
6002 switch (line
->type
) {
6003 case LINE_STAT_STAGED
:
6004 text
= "Press %s to unstage %s for commit";
6007 case LINE_STAT_UNSTAGED
:
6008 text
= "Press %s to stage %s for commit";
6011 case LINE_STAT_UNTRACKED
:
6012 text
= "Press %s to stage %s for addition";
6015 case LINE_STAT_HEAD
:
6016 case LINE_STAT_NONE
:
6017 text
= "Nothing to update";
6021 die("line type %d not handled in switch", line
->type
);
6024 if (status
&& status
->status
== 'U') {
6025 text
= "Press %s to resolve conflict in %s";
6026 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
6029 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
6032 string_format(view
->ref
, text
, key
, file
);
6034 string_copy(opt_file
, status
->new.name
);
6038 status_grep(struct view
*view
, struct line
*line
)
6040 struct status
*status
= line
->data
;
6043 const char buf
[2] = { status
->status
, 0 };
6044 const char *text
[] = { status
->new.name
, buf
, NULL
};
6046 return grep_text(view
, text
);
6052 static struct view_ops status_ops
= {
6065 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
6067 while (line
< end
) {
6068 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
6069 !io_write(io
, "\n", 1))
6072 if (line
->type
== LINE_DIFF_CHUNK
||
6073 line
->type
== LINE_DIFF_HEADER
)
6080 static struct line
*
6081 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
6083 for (; view
->line
< line
; line
--)
6084 if (line
->type
== type
)
6091 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
6093 const char *apply_argv
[SIZEOF_ARG
] = {
6094 "git", "apply", "--whitespace=nowarn", NULL
6096 struct line
*diff_hdr
;
6100 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6105 apply_argv
[argc
++] = "--cached";
6106 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6107 apply_argv
[argc
++] = "-R";
6108 apply_argv
[argc
++] = "-";
6109 apply_argv
[argc
++] = NULL
;
6110 if (!io_run(&io
, apply_argv
, opt_cdup
, IO_WR
))
6113 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6114 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6118 io_run_bg(update_index_argv
);
6120 return chunk
? TRUE
: FALSE
;
6124 stage_update(struct view
*view
, struct line
*line
)
6126 struct line
*chunk
= NULL
;
6128 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6129 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6132 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6133 report("Failed to apply chunk");
6137 } else if (!stage_status
.status
) {
6138 view
= VIEW(REQ_VIEW_STATUS
);
6140 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6141 if (line
->type
== stage_line_type
)
6144 if (!status_update_files(view
, line
+ 1)) {
6145 report("Failed to update files");
6149 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6150 report("Failed to update file");
6158 stage_revert(struct view
*view
, struct line
*line
)
6160 struct line
*chunk
= NULL
;
6162 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6163 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6166 if (!prompt_yesno("Are you sure you want to revert changes?"))
6169 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6170 report("Failed to revert chunk");
6176 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6177 stage_line_type
, FALSE
);
6183 stage_next(struct view
*view
, struct line
*line
)
6187 if (!stage_chunks
) {
6188 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6189 if (line
->type
!= LINE_DIFF_CHUNK
)
6192 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6193 report("Allocation failure");
6197 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6201 for (i
= 0; i
< stage_chunks
; i
++) {
6202 if (stage_chunk
[i
] > view
->lineno
) {
6203 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6204 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6209 report("No next chunk found");
6213 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6216 case REQ_STATUS_UPDATE
:
6217 if (!stage_update(view
, line
))
6221 case REQ_STATUS_REVERT
:
6222 if (!stage_revert(view
, line
))
6226 case REQ_STAGE_NEXT
:
6227 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6228 report("File is untracked; press %s to add",
6229 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6232 stage_next(view
, line
);
6236 if (!stage_status
.new.name
[0])
6238 if (stage_status
.status
== 'D') {
6239 report("File has been deleted.");
6243 open_editor(stage_status
.new.name
);
6247 /* Reload everything ... */
6250 case REQ_VIEW_BLAME
:
6251 if (stage_status
.new.name
[0]) {
6252 string_copy(opt_file
, stage_status
.new.name
);
6258 return pager_request(view
, request
, line
);
6264 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6265 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6267 /* Check whether the staged entry still exists, and close the
6268 * stage view if it doesn't. */
6269 if (!status_exists(&stage_status
, stage_line_type
)) {
6270 status_restore(VIEW(REQ_VIEW_STATUS
));
6271 return REQ_VIEW_CLOSE
;
6274 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6275 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6276 report("Cannot display a directory");
6280 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6281 report("Failed to open file: %s", strerror(errno
));
6285 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6290 static struct view_ops stage_ops
= {
6307 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6308 char title
[128]; /* First line of the commit message. */
6309 const char *author
; /* Author of the commit. */
6310 struct time time
; /* Date from the author ident. */
6311 struct ref_list
*refs
; /* Repository references. */
6312 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6313 size_t graph_size
; /* The width of the graph array. */
6314 bool has_parents
; /* Rewritten --parents seen. */
6317 /* Size of rev graph with no "padding" columns */
6318 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6321 struct rev_graph
*prev
, *next
, *parents
;
6322 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6324 struct commit
*commit
;
6326 unsigned int boundary
:1;
6329 /* Parents of the commit being visualized. */
6330 static struct rev_graph graph_parents
[4];
6332 /* The current stack of revisions on the graph. */
6333 static struct rev_graph graph_stacks
[4] = {
6334 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6335 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6336 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6337 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6341 graph_parent_is_merge(struct rev_graph
*graph
)
6343 return graph
->parents
->size
> 1;
6347 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6349 struct commit
*commit
= graph
->commit
;
6351 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6352 commit
->graph
[commit
->graph_size
++] = symbol
;
6356 clear_rev_graph(struct rev_graph
*graph
)
6358 graph
->boundary
= 0;
6359 graph
->size
= graph
->pos
= 0;
6360 graph
->commit
= NULL
;
6361 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6365 done_rev_graph(struct rev_graph
*graph
)
6367 if (graph_parent_is_merge(graph
) &&
6368 graph
->pos
< graph
->size
- 1 &&
6369 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6370 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6372 graph
->commit
->graph_size
= i
* 2;
6373 while (i
< graph
->next
->size
- 1) {
6374 append_to_rev_graph(graph
, ' ');
6375 append_to_rev_graph(graph
, '\\');
6380 clear_rev_graph(graph
);
6384 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6388 /* "Collapse" duplicate parents lines.
6390 * FIXME: This needs to also update update the drawn graph but
6391 * for now it just serves as a method for pruning graph lines. */
6392 for (i
= 0; i
< graph
->size
; i
++)
6393 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6396 if (graph
->size
< SIZEOF_REVITEMS
) {
6397 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6402 get_rev_graph_symbol(struct rev_graph
*graph
)
6406 if (graph
->boundary
)
6407 symbol
= REVGRAPH_BOUND
;
6408 else if (graph
->parents
->size
== 0)
6409 symbol
= REVGRAPH_INIT
;
6410 else if (graph_parent_is_merge(graph
))
6411 symbol
= REVGRAPH_MERGE
;
6412 else if (graph
->pos
>= graph
->size
)
6413 symbol
= REVGRAPH_BRANCH
;
6415 symbol
= REVGRAPH_COMMIT
;
6421 draw_rev_graph(struct rev_graph
*graph
)
6424 chtype separator
, line
;
6426 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6427 static struct rev_filler fillers
[] = {
6433 chtype symbol
= get_rev_graph_symbol(graph
);
6434 struct rev_filler
*filler
;
6437 fillers
[DEFAULT
].line
= opt_line_graphics
? ACS_VLINE
: '|';
6438 filler
= &fillers
[DEFAULT
];
6440 for (i
= 0; i
< graph
->pos
; i
++) {
6441 append_to_rev_graph(graph
, filler
->line
);
6442 if (graph_parent_is_merge(graph
->prev
) &&
6443 graph
->prev
->pos
== i
)
6444 filler
= &fillers
[RSHARP
];
6446 append_to_rev_graph(graph
, filler
->separator
);
6449 /* Place the symbol for this revision. */
6450 append_to_rev_graph(graph
, symbol
);
6452 if (graph
->prev
->size
> graph
->size
)
6453 filler
= &fillers
[RDIAG
];
6455 filler
= &fillers
[DEFAULT
];
6459 for (; i
< graph
->size
; i
++) {
6460 append_to_rev_graph(graph
, filler
->separator
);
6461 append_to_rev_graph(graph
, filler
->line
);
6462 if (graph_parent_is_merge(graph
->prev
) &&
6463 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6464 filler
= &fillers
[RSHARP
];
6465 if (graph
->prev
->size
> graph
->size
)
6466 filler
= &fillers
[LDIAG
];
6469 if (graph
->prev
->size
> graph
->size
) {
6470 append_to_rev_graph(graph
, filler
->separator
);
6471 if (filler
->line
!= ' ')
6472 append_to_rev_graph(graph
, filler
->line
);
6476 /* Prepare the next rev graph */
6478 prepare_rev_graph(struct rev_graph
*graph
)
6482 /* First, traverse all lines of revisions up to the active one. */
6483 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6484 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6487 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6490 /* Interleave the new revision parent(s). */
6491 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6492 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6494 /* Lastly, put any remaining revisions. */
6495 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6496 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6500 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6502 /* If this is the finalizing update ... */
6504 prepare_rev_graph(graph
);
6506 /* Graph visualization needs a one rev look-ahead,
6507 * so the first update doesn't visualize anything. */
6508 if (!graph
->prev
->commit
)
6511 if (view
->lines
> 2)
6512 view
->line
[view
->lines
- 3].dirty
= 1;
6513 if (view
->lines
> 1)
6514 view
->line
[view
->lines
- 2].dirty
= 1;
6515 draw_rev_graph(graph
->prev
);
6516 done_rev_graph(graph
->prev
->prev
);
6524 static const char *main_argv
[SIZEOF_ARG
] = {
6525 "git", "log", "--no-color", "--pretty=raw", "--parents",
6526 "--topo-order", "%(head)", NULL
6530 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6532 struct commit
*commit
= line
->data
;
6534 if (!commit
->author
)
6537 if (opt_date
&& draw_date(view
, &commit
->time
))
6540 if (opt_author
&& draw_author(view
, commit
->author
))
6543 if (opt_rev_graph
&& commit
->graph_size
&&
6544 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6547 if (opt_show_refs
&& commit
->refs
) {
6550 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6551 struct ref
*ref
= commit
->refs
->refs
[i
];
6552 enum line_type type
;
6555 type
= LINE_MAIN_HEAD
;
6557 type
= LINE_MAIN_LOCAL_TAG
;
6559 type
= LINE_MAIN_TAG
;
6560 else if (ref
->tracked
)
6561 type
= LINE_MAIN_TRACKED
;
6562 else if (ref
->remote
)
6563 type
= LINE_MAIN_REMOTE
;
6565 type
= LINE_MAIN_REF
;
6567 if (draw_text(view
, type
, "[", TRUE
) ||
6568 draw_text(view
, type
, ref
->name
, TRUE
) ||
6569 draw_text(view
, type
, "]", TRUE
))
6572 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6577 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6581 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6583 main_read(struct view
*view
, char *line
)
6585 static struct rev_graph
*graph
= graph_stacks
;
6586 enum line_type type
;
6587 struct commit
*commit
;
6592 if (!view
->lines
&& !view
->parent
)
6593 die("No revisions match the given arguments.");
6594 if (view
->lines
> 0) {
6595 commit
= view
->line
[view
->lines
- 1].data
;
6596 view
->line
[view
->lines
- 1].dirty
= 1;
6597 if (!commit
->author
) {
6600 graph
->commit
= NULL
;
6603 update_rev_graph(view
, graph
);
6605 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6606 clear_rev_graph(&graph_stacks
[i
]);
6610 type
= get_line_type(line
);
6611 if (type
== LINE_COMMIT
) {
6612 commit
= calloc(1, sizeof(struct commit
));
6616 line
+= STRING_SIZE("commit ");
6618 graph
->boundary
= 1;
6622 string_copy_rev(commit
->id
, line
);
6623 commit
->refs
= get_ref_list(commit
->id
);
6624 graph
->commit
= commit
;
6625 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6627 while ((line
= strchr(line
, ' '))) {
6629 push_rev_graph(graph
->parents
, line
);
6630 commit
->has_parents
= TRUE
;
6637 commit
= view
->line
[view
->lines
- 1].data
;
6641 if (commit
->has_parents
)
6643 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6647 parse_author_line(line
+ STRING_SIZE("author "),
6648 &commit
->author
, &commit
->time
);
6649 update_rev_graph(view
, graph
);
6650 graph
= graph
->next
;
6654 /* Fill in the commit title if it has not already been set. */
6655 if (commit
->title
[0])
6658 /* Require titles to start with a non-space character at the
6659 * offset used by git log. */
6660 if (strncmp(line
, " ", 4))
6663 /* Well, if the title starts with a whitespace character,
6664 * try to be forgiving. Otherwise we end up with no title. */
6665 while (isspace(*line
))
6669 /* FIXME: More graceful handling of titles; append "..." to
6670 * shortened titles, etc. */
6672 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6673 view
->line
[view
->lines
- 1].dirty
= 1;
6680 main_request(struct view
*view
, enum request request
, struct line
*line
)
6682 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
6686 open_view(view
, REQ_VIEW_DIFF
, flags
);
6690 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6700 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6705 if (!opt_show_refs
|| !list
)
6708 for (i
= 0; i
< list
->size
; i
++) {
6709 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6717 main_grep(struct view
*view
, struct line
*line
)
6719 struct commit
*commit
= line
->data
;
6720 const char *text
[] = {
6722 opt_author
? commit
->author
: "",
6723 mkdate(&commit
->time
, opt_date
),
6727 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6731 main_select(struct view
*view
, struct line
*line
)
6733 struct commit
*commit
= line
->data
;
6735 string_copy_rev(view
->ref
, commit
->id
);
6736 string_copy_rev(ref_commit
, view
->ref
);
6739 static struct view_ops main_ops
= {
6752 * Unicode / UTF-8 handling
6754 * NOTE: Much of the following code for dealing with Unicode is derived from
6755 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6756 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6760 unicode_width(unsigned long c
, int tab_size
)
6763 (c
<= 0x115f /* Hangul Jamo */
6766 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
6768 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
6769 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
6770 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
6771 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
6772 || (c
>= 0xffe0 && c
<= 0xffe6)
6773 || (c
>= 0x20000 && c
<= 0x2fffd)
6774 || (c
>= 0x30000 && c
<= 0x3fffd)))
6783 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6784 * Illegal bytes are set one. */
6785 static const unsigned char utf8_bytes
[256] = {
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 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,
6790 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,
6791 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,
6792 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,
6793 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,
6796 static inline unsigned char
6797 utf8_char_length(const char *string
, const char *end
)
6799 int c
= *(unsigned char *) string
;
6801 return utf8_bytes
[c
];
6804 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6805 static inline unsigned long
6806 utf8_to_unicode(const char *string
, size_t length
)
6808 unsigned long unicode
;
6812 unicode
= string
[0];
6815 unicode
= (string
[0] & 0x1f) << 6;
6816 unicode
+= (string
[1] & 0x3f);
6819 unicode
= (string
[0] & 0x0f) << 12;
6820 unicode
+= ((string
[1] & 0x3f) << 6);
6821 unicode
+= (string
[2] & 0x3f);
6824 unicode
= (string
[0] & 0x0f) << 18;
6825 unicode
+= ((string
[1] & 0x3f) << 12);
6826 unicode
+= ((string
[2] & 0x3f) << 6);
6827 unicode
+= (string
[3] & 0x3f);
6830 unicode
= (string
[0] & 0x0f) << 24;
6831 unicode
+= ((string
[1] & 0x3f) << 18);
6832 unicode
+= ((string
[2] & 0x3f) << 12);
6833 unicode
+= ((string
[3] & 0x3f) << 6);
6834 unicode
+= (string
[4] & 0x3f);
6837 unicode
= (string
[0] & 0x01) << 30;
6838 unicode
+= ((string
[1] & 0x3f) << 24);
6839 unicode
+= ((string
[2] & 0x3f) << 18);
6840 unicode
+= ((string
[3] & 0x3f) << 12);
6841 unicode
+= ((string
[4] & 0x3f) << 6);
6842 unicode
+= (string
[5] & 0x3f);
6845 die("Invalid Unicode length");
6848 /* Invalid characters could return the special 0xfffd value but NUL
6849 * should be just as good. */
6850 return unicode
> 0xffff ? 0 : unicode
;
6853 /* Calculates how much of string can be shown within the given maximum width
6854 * and sets trimmed parameter to non-zero value if all of string could not be
6855 * shown. If the reserve flag is TRUE, it will reserve at least one
6856 * trailing character, which can be useful when drawing a delimiter.
6858 * Returns the number of bytes to output from string to satisfy max_width. */
6860 utf8_length(const char **start
, size_t skip
, int *width
, size_t max_width
, int *trimmed
, bool reserve
, int tab_size
)
6862 const char *string
= *start
;
6863 const char *end
= strchr(string
, '\0');
6864 unsigned char last_bytes
= 0;
6865 size_t last_ucwidth
= 0;
6870 while (string
< end
) {
6871 unsigned char bytes
= utf8_char_length(string
, end
);
6873 unsigned long unicode
;
6875 if (string
+ bytes
> end
)
6878 /* Change representation to figure out whether
6879 * it is a single- or double-width character. */
6881 unicode
= utf8_to_unicode(string
, bytes
);
6882 /* FIXME: Graceful handling of invalid Unicode character. */
6886 ucwidth
= unicode_width(unicode
, tab_size
);
6888 skip
-= ucwidth
<= skip
? ucwidth
: skip
;
6892 if (*width
> max_width
) {
6895 if (reserve
&& *width
== max_width
) {
6896 string
-= last_bytes
;
6897 *width
-= last_ucwidth
;
6903 last_bytes
= ucwidth
? bytes
: 0;
6904 last_ucwidth
= ucwidth
;
6907 return string
- *start
;
6915 /* Whether or not the curses interface has been initialized. */
6916 static bool cursed
= FALSE
;
6918 /* Terminal hacks and workarounds. */
6919 static bool use_scroll_redrawwin
;
6920 static bool use_scroll_status_wclear
;
6922 /* The status window is used for polling keystrokes. */
6923 static WINDOW
*status_win
;
6925 /* Reading from the prompt? */
6926 static bool input_mode
= FALSE
;
6928 static bool status_empty
= FALSE
;
6930 /* Update status and title window. */
6932 report(const char *msg
, ...)
6934 struct view
*view
= display
[current_view
];
6940 char buf
[SIZEOF_STR
];
6943 va_start(args
, msg
);
6944 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6945 buf
[sizeof(buf
) - 1] = 0;
6946 buf
[sizeof(buf
) - 2] = '.';
6947 buf
[sizeof(buf
) - 3] = '.';
6948 buf
[sizeof(buf
) - 4] = '.';
6954 if (!status_empty
|| *msg
) {
6957 va_start(args
, msg
);
6959 wmove(status_win
, 0, 0);
6960 if (view
->has_scrolled
&& use_scroll_status_wclear
)
6963 vwprintw(status_win
, msg
, args
);
6964 status_empty
= FALSE
;
6966 status_empty
= TRUE
;
6968 wclrtoeol(status_win
);
6969 wnoutrefresh(status_win
);
6974 update_view_title(view
);
6983 /* Initialize the curses library */
6984 if (isatty(STDIN_FILENO
)) {
6985 cursed
= !!initscr();
6988 /* Leave stdin and stdout alone when acting as a pager. */
6989 opt_tty
= fopen("/dev/tty", "r+");
6991 die("Failed to open /dev/tty");
6992 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
6996 die("Failed to initialize curses");
6998 nonl(); /* Disable conversion and detect newlines from input. */
6999 cbreak(); /* Take input chars one at a time, no wait for \n */
7000 noecho(); /* Don't echo input */
7001 leaveok(stdscr
, FALSE
);
7006 getmaxyx(stdscr
, y
, x
);
7007 status_win
= newwin(1, 0, y
- 1, 0);
7009 die("Failed to create status window");
7011 /* Enable keyboard mapping */
7012 keypad(status_win
, TRUE
);
7013 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
7015 TABSIZE
= opt_tab_size
;
7017 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
7018 if (term
&& !strcmp(term
, "gnome-terminal")) {
7019 /* In the gnome-terminal-emulator, the message from
7020 * scrolling up one line when impossible followed by
7021 * scrolling down one line causes corruption of the
7022 * status line. This is fixed by calling wclear. */
7023 use_scroll_status_wclear
= TRUE
;
7024 use_scroll_redrawwin
= FALSE
;
7026 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
7027 /* No problems with full optimizations in xrvt-(unicode)
7029 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
7032 /* When scrolling in (u)xterm the last line in the
7033 * scrolling direction will update slowly. */
7034 use_scroll_redrawwin
= TRUE
;
7035 use_scroll_status_wclear
= FALSE
;
7040 get_input(int prompt_position
)
7043 int i
, key
, cursor_y
, cursor_x
;
7044 bool loading
= FALSE
;
7046 if (prompt_position
)
7050 foreach_view (view
, i
) {
7052 if (view_is_displayed(view
) && view
->has_scrolled
&&
7053 use_scroll_redrawwin
)
7054 redrawwin(view
->win
);
7055 view
->has_scrolled
= FALSE
;
7060 /* Update the cursor position. */
7061 if (prompt_position
) {
7062 getbegyx(status_win
, cursor_y
, cursor_x
);
7063 cursor_x
= prompt_position
;
7065 view
= display
[current_view
];
7066 getbegyx(view
->win
, cursor_y
, cursor_x
);
7067 cursor_x
= view
->width
- 1;
7068 cursor_y
+= view
->lineno
- view
->offset
;
7070 setsyx(cursor_y
, cursor_x
);
7072 /* Refresh, accept single keystroke of input */
7074 nodelay(status_win
, loading
);
7075 key
= wgetch(status_win
);
7077 /* wgetch() with nodelay() enabled returns ERR when
7078 * there's no input. */
7081 } else if (key
== KEY_RESIZE
) {
7084 getmaxyx(stdscr
, height
, width
);
7086 wresize(status_win
, 1, width
);
7087 mvwin(status_win
, height
- 1, 0);
7088 wnoutrefresh(status_win
);
7090 redraw_display(TRUE
);
7100 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7102 enum input_status status
= INPUT_OK
;
7103 static char buf
[SIZEOF_STR
];
7108 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7111 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7112 wclrtoeol(status_win
);
7114 key
= get_input(pos
+ 1);
7119 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7126 status
= INPUT_CANCEL
;
7130 status
= INPUT_CANCEL
;
7134 if (pos
>= sizeof(buf
)) {
7135 report("Input string too long");
7139 status
= handler(data
, buf
, key
);
7140 if (status
== INPUT_OK
)
7141 buf
[pos
++] = (char) key
;
7145 /* Clear the status window */
7146 status_empty
= FALSE
;
7149 if (status
== INPUT_CANCEL
)
7157 static enum input_status
7158 prompt_yesno_handler(void *data
, char *buf
, int c
)
7160 if (c
== 'y' || c
== 'Y')
7162 if (c
== 'n' || c
== 'N')
7163 return INPUT_CANCEL
;
7168 prompt_yesno(const char *prompt
)
7170 char prompt2
[SIZEOF_STR
];
7172 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7175 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7178 static enum input_status
7179 read_prompt_handler(void *data
, char *buf
, int c
)
7181 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7185 read_prompt(const char *prompt
)
7187 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7190 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7192 enum input_status status
= INPUT_OK
;
7195 while (items
[size
].text
)
7198 while (status
== INPUT_OK
) {
7199 const struct menu_item
*item
= &items
[*selected
];
7203 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7204 prompt
, *selected
+ 1, size
);
7206 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7207 wprintw(status_win
, "%s", item
->text
);
7208 wclrtoeol(status_win
);
7210 key
= get_input(COLS
- 1);
7215 status
= INPUT_STOP
;
7220 *selected
= *selected
- 1;
7222 *selected
= size
- 1;
7227 *selected
= (*selected
+ 1) % size
;
7231 status
= INPUT_CANCEL
;
7235 for (i
= 0; items
[i
].text
; i
++)
7236 if (items
[i
].hotkey
== key
) {
7238 status
= INPUT_STOP
;
7244 /* Clear the status window */
7245 status_empty
= FALSE
;
7248 return status
!= INPUT_CANCEL
;
7252 * Repository properties
7255 static struct ref
**refs
= NULL
;
7256 static size_t refs_size
= 0;
7257 static struct ref
*refs_head
= NULL
;
7259 static struct ref_list
**ref_lists
= NULL
;
7260 static size_t ref_lists_size
= 0;
7262 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7263 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7264 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7267 compare_refs(const void *ref1_
, const void *ref2_
)
7269 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7270 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7272 if (ref1
->tag
!= ref2
->tag
)
7273 return ref2
->tag
- ref1
->tag
;
7274 if (ref1
->ltag
!= ref2
->ltag
)
7275 return ref2
->ltag
- ref2
->ltag
;
7276 if (ref1
->head
!= ref2
->head
)
7277 return ref2
->head
- ref1
->head
;
7278 if (ref1
->tracked
!= ref2
->tracked
)
7279 return ref2
->tracked
- ref1
->tracked
;
7280 if (ref1
->remote
!= ref2
->remote
)
7281 return ref2
->remote
- ref1
->remote
;
7282 return strcmp(ref1
->name
, ref2
->name
);
7286 foreach_ref(bool (*visitor
)(void *data
, const struct ref
*ref
), void *data
)
7290 for (i
= 0; i
< refs_size
; i
++)
7291 if (!visitor(data
, refs
[i
]))
7301 static struct ref_list
*
7302 get_ref_list(const char *id
)
7304 struct ref_list
*list
;
7307 for (i
= 0; i
< ref_lists_size
; i
++)
7308 if (!strcmp(id
, ref_lists
[i
]->id
))
7309 return ref_lists
[i
];
7311 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7313 list
= calloc(1, sizeof(*list
));
7317 for (i
= 0; i
< refs_size
; i
++) {
7318 if (!strcmp(id
, refs
[i
]->id
) &&
7319 realloc_refs_list(&list
->refs
, list
->size
, 1))
7320 list
->refs
[list
->size
++] = refs
[i
];
7328 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7329 ref_lists
[ref_lists_size
++] = list
;
7334 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
7336 struct ref
*ref
= NULL
;
7339 bool remote
= FALSE
;
7340 bool tracked
= FALSE
;
7342 int from
= 0, to
= refs_size
- 1;
7344 if (!prefixcmp(name
, "refs/tags/")) {
7345 if (!suffixcmp(name
, namelen
, "^{}")) {
7353 namelen
-= STRING_SIZE("refs/tags/");
7354 name
+= STRING_SIZE("refs/tags/");
7356 } else if (!prefixcmp(name
, "refs/remotes/")) {
7358 namelen
-= STRING_SIZE("refs/remotes/");
7359 name
+= STRING_SIZE("refs/remotes/");
7360 tracked
= !strcmp(opt_remote
, name
);
7362 } else if (!prefixcmp(name
, "refs/heads/")) {
7363 namelen
-= STRING_SIZE("refs/heads/");
7364 name
+= STRING_SIZE("refs/heads/");
7365 if (!strncmp(opt_head
, name
, namelen
))
7368 } else if (!strcmp(name
, "HEAD")) {
7371 namelen
= strlen(opt_head
);
7376 /* If we are reloading or it's an annotated tag, replace the
7377 * previous SHA1 with the resolved commit id; relies on the fact
7378 * git-ls-remote lists the commit id of an annotated tag right
7379 * before the commit id it points to. */
7380 while (from
<= to
) {
7381 size_t pos
= (to
+ from
) / 2;
7382 int cmp
= strcmp(name
, refs
[pos
]->name
);
7396 if (!realloc_refs(&refs
, refs_size
, 1))
7398 ref
= calloc(1, sizeof(*ref
) + namelen
);
7401 memmove(refs
+ from
+ 1, refs
+ from
,
7402 (refs_size
- from
) * sizeof(*refs
));
7404 strncpy(ref
->name
, name
, namelen
);
7411 ref
->remote
= remote
;
7412 ref
->tracked
= tracked
;
7413 string_copy_rev(ref
->id
, id
);
7423 const char *head_argv
[] = {
7424 "git", "symbolic-ref", "HEAD", NULL
7426 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7427 "git", "ls-remote", opt_git_dir
, NULL
7429 static bool init
= FALSE
;
7433 argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE");
7440 if (io_run_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7441 !prefixcmp(opt_head
, "refs/heads/")) {
7442 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7444 memmove(opt_head
, offset
, strlen(offset
) + 1);
7448 for (i
= 0; i
< refs_size
; i
++)
7451 if (io_run_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7454 /* Update the ref lists to reflect changes. */
7455 for (i
= 0; i
< ref_lists_size
; i
++) {
7456 struct ref_list
*list
= ref_lists
[i
];
7459 for (old
= new = 0; old
< list
->size
; old
++)
7460 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7461 list
->refs
[new++] = list
->refs
[old
];
7469 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7471 if (!strcmp(name
, ".remote")) {
7472 string_ncopy(opt_remote
, value
, valuelen
);
7474 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7475 size_t from
= strlen(opt_remote
);
7477 if (!prefixcmp(value
, "refs/heads/"))
7478 value
+= STRING_SIZE("refs/heads/");
7480 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7486 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7488 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7489 int argc
= 1 + (cmd
== option_set_command
);
7492 if (!argv_from_string(argv
, &argc
, value
))
7493 config_msg
= "Too many option arguments";
7495 error
= cmd(argc
, argv
);
7498 warn("Option 'tig.%s': %s", name
, config_msg
);
7502 set_environment_variable(const char *name
, const char *value
)
7504 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7505 char *env
= malloc(len
);
7508 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7516 set_work_tree(const char *value
)
7518 char cwd
[SIZEOF_STR
];
7520 if (!getcwd(cwd
, sizeof(cwd
)))
7521 die("Failed to get cwd path: %s", strerror(errno
));
7522 if (chdir(opt_git_dir
) < 0)
7523 die("Failed to chdir(%s): %s", strerror(errno
));
7524 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7525 die("Failed to get git path: %s", strerror(errno
));
7527 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7528 if (chdir(value
) < 0)
7529 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7530 if (!getcwd(cwd
, sizeof(cwd
)))
7531 die("Failed to get cwd path: %s", strerror(errno
));
7532 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7533 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7534 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7535 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7536 opt_is_inside_work_tree
= TRUE
;
7540 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7542 if (!strcmp(name
, "i18n.commitencoding"))
7543 string_ncopy(opt_encoding
, value
, valuelen
);
7545 else if (!strcmp(name
, "core.editor"))
7546 string_ncopy(opt_editor
, value
, valuelen
);
7548 else if (!strcmp(name
, "core.worktree"))
7549 set_work_tree(value
);
7551 else if (!prefixcmp(name
, "tig.color."))
7552 set_repo_config_option(name
+ 10, value
, option_color_command
);
7554 else if (!prefixcmp(name
, "tig.bind."))
7555 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7557 else if (!prefixcmp(name
, "tig."))
7558 set_repo_config_option(name
+ 4, value
, option_set_command
);
7560 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7561 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7562 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7568 load_git_config(void)
7570 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7572 return io_run_load(config_list_argv
, "=", read_repo_config_option
);
7576 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7578 if (!opt_git_dir
[0]) {
7579 string_ncopy(opt_git_dir
, name
, namelen
);
7581 } else if (opt_is_inside_work_tree
== -1) {
7582 /* This can be 3 different values depending on the
7583 * version of git being used. If git-rev-parse does not
7584 * understand --is-inside-work-tree it will simply echo
7585 * the option else either "true" or "false" is printed.
7586 * Default to true for the unknown case. */
7587 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7589 } else if (*name
== '.') {
7590 string_ncopy(opt_cdup
, name
, namelen
);
7593 string_ncopy(opt_prefix
, name
, namelen
);
7600 load_repo_info(void)
7602 const char *rev_parse_argv
[] = {
7603 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7604 "--show-cdup", "--show-prefix", NULL
7607 return io_run_load(rev_parse_argv
, "=", read_repo_info
);
7615 static const char usage
[] =
7616 "tig " TIG_VERSION
" (" __DATE__
")\n"
7618 "Usage: tig [options] [revs] [--] [paths]\n"
7619 " or: tig show [options] [revs] [--] [paths]\n"
7620 " or: tig blame [rev] path\n"
7622 " or: tig < [git command output]\n"
7625 " -v, --version Show version and exit\n"
7626 " -h, --help Show help message and exit";
7628 static void __NORETURN
7631 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7637 static void __NORETURN
7638 die(const char *err
, ...)
7644 va_start(args
, err
);
7645 fputs("tig: ", stderr
);
7646 vfprintf(stderr
, err
, args
);
7647 fputs("\n", stderr
);
7654 warn(const char *msg
, ...)
7658 va_start(args
, msg
);
7659 fputs("tig warning: ", stderr
);
7660 vfprintf(stderr
, msg
, args
);
7661 fputs("\n", stderr
);
7666 parse_options(int argc
, const char *argv
[])
7668 enum request request
= REQ_VIEW_MAIN
;
7669 const char *subcommand
;
7670 bool seen_dashdash
= FALSE
;
7671 /* XXX: This is vulnerable to the user overriding options
7672 * required for the main view parser. */
7673 const char *custom_argv
[SIZEOF_ARG
] = {
7674 "git", "log", "--no-color", "--pretty=raw", "--parents",
7675 "--topo-order", NULL
7679 if (!isatty(STDIN_FILENO
)) {
7680 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7681 return REQ_VIEW_PAGER
;
7687 subcommand
= argv
[1];
7688 if (!strcmp(subcommand
, "status")) {
7690 warn("ignoring arguments after `%s'", subcommand
);
7691 return REQ_VIEW_STATUS
;
7693 } else if (!strcmp(subcommand
, "blame")) {
7694 if (argc
<= 2 || argc
> 4)
7695 die("invalid number of options to blame\n\n%s", usage
);
7699 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7703 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7704 return REQ_VIEW_BLAME
;
7706 } else if (!strcmp(subcommand
, "show")) {
7707 request
= REQ_VIEW_DIFF
;
7714 custom_argv
[1] = subcommand
;
7718 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7719 const char *opt
= argv
[i
];
7721 if (seen_dashdash
|| !strcmp(opt
, "--")) {
7722 seen_dashdash
= TRUE
;
7724 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7725 printf("tig version %s\n", TIG_VERSION
);
7728 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7729 printf("%s\n", usage
);
7733 custom_argv
[j
++] = opt
;
7734 if (j
>= ARRAY_SIZE(custom_argv
))
7735 die("command too long");
7738 if (!prepare_update(VIEW(request
), custom_argv
, NULL
, FORMAT_NONE
))
7739 die("Failed to format arguments");
7745 main(int argc
, const char *argv
[])
7747 const char *codeset
= "UTF-8";
7748 enum request request
= parse_options(argc
, argv
);
7752 signal(SIGINT
, quit
);
7753 signal(SIGPIPE
, SIG_IGN
);
7755 if (setlocale(LC_ALL
, "")) {
7756 codeset
= nl_langinfo(CODESET
);
7759 if (load_repo_info() == ERR
)
7760 die("Failed to load repo info.");
7762 if (load_options() == ERR
)
7763 die("Failed to load user config.");
7765 if (load_git_config() == ERR
)
7766 die("Failed to load repo config.");
7768 /* Require a git repository unless when running in pager mode. */
7769 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7770 die("Not a git repository");
7772 if (*opt_encoding
&& strcmp(codeset
, "UTF-8")) {
7773 opt_iconv_in
= iconv_open("UTF-8", opt_encoding
);
7774 if (opt_iconv_in
== ICONV_NONE
)
7775 die("Failed to initialize character set conversion");
7778 if (codeset
&& strcmp(codeset
, "UTF-8")) {
7779 opt_iconv_out
= iconv_open(codeset
, "UTF-8");
7780 if (opt_iconv_out
== ICONV_NONE
)
7781 die("Failed to initialize character set conversion");
7784 if (load_refs() == ERR
)
7785 die("Failed to load refs.");
7787 foreach_view (view
, i
)
7788 argv_from_env(view
->ops
->argv
, view
->cmd_env
);
7792 if (request
!= REQ_NONE
)
7793 open_view(NULL
, request
, OPEN_PREPARED
);
7794 request
= request
== REQ_NONE
? REQ_VIEW_MAIN
: REQ_NONE
;
7796 while (view_driver(display
[current_view
], request
)) {
7797 int key
= get_input(0);
7799 view
= display
[current_view
];
7800 request
= get_keybinding(view
->keymap
, key
);
7802 /* Some low-level request handling. This keeps access to
7803 * status_win restricted. */
7807 char *cmd
= read_prompt(":");
7809 if (cmd
&& isdigit(*cmd
)) {
7810 int lineno
= view
->lineno
+ 1;
7812 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7813 select_view_line(view
, lineno
- 1);
7816 report("Unable to parse '%s' as a line number", cmd
);
7820 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7821 const char *argv
[SIZEOF_ARG
] = { "git" };
7824 /* When running random commands, initially show the
7825 * command in the title. However, it maybe later be
7826 * overwritten if a commit line is selected. */
7827 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7829 if (!argv_from_string(argv
, &argc
, cmd
)) {
7830 report("Too many arguments");
7831 } else if (!prepare_update(next
, argv
, NULL
, FORMAT_DASH
)) {
7832 report("Failed to format command");
7834 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7842 case REQ_SEARCH_BACK
:
7844 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7845 char *search
= read_prompt(prompt
);
7848 string_ncopy(opt_search
, search
, strlen(search
));
7849 else if (*opt_search
)
7850 request
= request
== REQ_SEARCH
?