1 /* Copyright (c) 2006-2009 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>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
62 #define __NORETURN __attribute__((__noreturn__))
67 static void __NORETURN
die(const char *err
, ...);
68 static void warn(const char *msg
, ...);
69 static void report(const char *msg
, ...);
70 static void set_nonblocking_input(bool loading
);
71 static size_t utf8_length(const char **string
, size_t col
, int *width
, size_t max_width
, int *trimmed
, bool reserve
);
73 #define ABS(x) ((x) >= 0 ? (x) : -(x))
74 #define MIN(x, y) ((x) < (y) ? (x) : (y))
75 #define MAX(x, y) ((x) > (y) ? (x) : (y))
77 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x) (sizeof(x) - 1)
80 #define SIZEOF_STR 1024 /* Default string size. */
81 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG 32 /* Default argument array size. */
87 #define REVGRAPH_INIT 'I'
88 #define REVGRAPH_MERGE 'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND '^'
93 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT (-1)
98 #define ICONV_NONE ((iconv_t) -1)
100 #define ICONV_CONST /* nothing */
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT "%Y-%m-%d %H:%M"
105 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
106 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
117 #define GIT_CONFIG "config"
120 /* Some ASCII-shorthands fitted into the ncurses namespace. */
122 #define KEY_RETURN '\r'
127 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
128 unsigned int head
:1; /* Is it the current HEAD? */
129 unsigned int tag
:1; /* Is it a tag? */
130 unsigned int ltag
:1; /* If so, is the tag local? */
131 unsigned int remote
:1; /* Is it a remote ref? */
132 unsigned int tracked
:1; /* Is it the remote for the current HEAD? */
133 char name
[1]; /* Ref name; tag or head names are shortened. */
137 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
138 size_t size
; /* Number of refs. */
139 struct ref
**refs
; /* References for this ID. */
142 static struct ref_list
*get_ref_list(const char *id
);
143 static void foreach_ref(bool (*visitor
)(void *data
, struct ref
*ref
), void *data
);
144 static int load_refs(void);
147 FORMAT_ALL
, /* Perform replacement in all arguments. */
148 FORMAT_DASH
, /* Perform replacement up until "--". */
149 FORMAT_NONE
/* No replacement should be performed. */
152 static bool format_argv(const char *dst
[], const char *src
[], enum format_flags flags
);
161 typedef enum input_status (*input_handler
)(void *data
, char *buf
, int c
);
163 static char *prompt_input(const char *prompt
, input_handler handler
, void *data
);
164 static bool prompt_yesno(const char *prompt
);
172 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
);
175 * Allocation helpers ... Entering macro hell to never be seen again.
178 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
180 name(type **mem, size_t size, size_t increase) \
182 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
183 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
186 if (mem == NULL || num_chunks != num_chunks_new) { \
187 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
200 string_ncopy_do(char *dst
, size_t dstlen
, const char *src
, size_t srclen
)
202 if (srclen
> dstlen
- 1)
205 strncpy(dst
, src
, srclen
);
209 /* Shorthands for safely copying into a fixed buffer. */
211 #define string_copy(dst, src) \
212 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
214 #define string_ncopy(dst, src, srclen) \
215 string_ncopy_do(dst, sizeof(dst), src, srclen)
217 #define string_copy_rev(dst, src) \
218 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
220 #define string_add(dst, from, src) \
221 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
224 string_expand(char *dst
, size_t dstlen
, const char *src
, int tabsize
)
228 for (size
= pos
= 0; size
< dstlen
- 1 && src
[pos
]; pos
++) {
229 if (src
[pos
] == '\t') {
230 size_t expanded
= tabsize
- (size
% tabsize
);
232 if (expanded
+ size
>= dstlen
- 1)
233 expanded
= dstlen
- size
- 1;
234 memcpy(dst
+ size
, " ", expanded
);
237 dst
[size
++] = src
[pos
];
245 chomp_string(char *name
)
249 while (isspace(*name
))
252 namelen
= strlen(name
) - 1;
253 while (namelen
> 0 && isspace(name
[namelen
]))
260 string_nformat(char *buf
, size_t bufsize
, size_t *bufpos
, const char *fmt
, ...)
263 size_t pos
= bufpos
? *bufpos
: 0;
266 pos
+= vsnprintf(buf
+ pos
, bufsize
- pos
, fmt
, args
);
272 return pos
>= bufsize
? FALSE
: TRUE
;
275 #define string_format(buf, fmt, args...) \
276 string_nformat(buf, sizeof(buf), NULL, fmt, args)
278 #define string_format_from(buf, from, fmt, args...) \
279 string_nformat(buf, sizeof(buf), from, fmt, args)
282 string_enum_compare(const char *str1
, const char *str2
, int len
)
286 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
288 /* Diff-Header == DIFF_HEADER */
289 for (i
= 0; i
< len
; i
++) {
290 if (toupper(str1
[i
]) == toupper(str2
[i
]))
293 if (string_enum_sep(str1
[i
]) &&
294 string_enum_sep(str2
[i
]))
297 return str1
[i
] - str2
[i
];
309 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
312 map_enum_do(const struct enum_map
*map
, size_t map_size
, int *value
, const char *name
)
314 size_t namelen
= strlen(name
);
317 for (i
= 0; i
< map_size
; i
++)
318 if (namelen
== map
[i
].namelen
&&
319 !string_enum_compare(name
, map
[i
].name
, namelen
)) {
320 *value
= map
[i
].value
;
327 #define map_enum(attr, map, name) \
328 map_enum_do(map, ARRAY_SIZE(map), attr, name)
330 #define prefixcmp(str1, str2) \
331 strncmp(str1, str2, STRING_SIZE(str2))
334 suffixcmp(const char *str
, int slen
, const char *suffix
)
336 size_t len
= slen
>= 0 ? slen
: strlen(str
);
337 size_t suffixlen
= strlen(suffix
);
339 return suffixlen
< len
? strcmp(str
+ len
- suffixlen
, suffix
) : -1;
344 mkdate(const time_t *time
)
346 static char buf
[DATE_COLS
+ 1];
350 return strftime(buf
, sizeof(buf
), DATE_FORMAT
, &tm
) ? buf
: NULL
;
355 argv_from_string(const char *argv
[SIZEOF_ARG
], int *argc
, char *cmd
)
359 while (*cmd
&& *argc
< SIZEOF_ARG
&& (valuelen
= strcspn(cmd
, " \t"))) {
360 bool advance
= cmd
[valuelen
] != 0;
363 argv
[(*argc
)++] = chomp_string(cmd
);
364 cmd
= chomp_string(cmd
+ valuelen
+ advance
);
367 if (*argc
< SIZEOF_ARG
)
369 return *argc
< SIZEOF_ARG
;
373 argv_from_env(const char **argv
, const char *name
)
375 char *env
= argv
? getenv(name
) : NULL
;
380 if (env
&& !argv_from_string(argv
, &argc
, env
))
381 die("Too many arguments in the `%s` environment variable", name
);
386 * Executing external commands.
390 IO_FD
, /* File descriptor based IO. */
391 IO_BG
, /* Execute command in the background. */
392 IO_FG
, /* Execute command with same std{in,out,err}. */
393 IO_RD
, /* Read only fork+exec IO. */
394 IO_WR
, /* Write only fork+exec IO. */
395 IO_AP
, /* Append fork+exec output to file. */
399 enum io_type type
; /* The requested type of pipe. */
400 const char *dir
; /* Directory from which to execute. */
401 pid_t pid
; /* Pipe for reading or writing. */
402 int pipe
; /* Pipe end for reading or writing. */
403 int error
; /* Error status. */
404 const char *argv
[SIZEOF_ARG
]; /* Shell command arguments. */
405 char *buf
; /* Read buffer. */
406 size_t bufalloc
; /* Allocated buffer size. */
407 size_t bufsize
; /* Buffer content size. */
408 char *bufpos
; /* Current buffer position. */
409 unsigned int eof
:1; /* Has end of file been reached. */
413 reset_io(struct io
*io
)
417 io
->buf
= io
->bufpos
= NULL
;
418 io
->bufalloc
= io
->bufsize
= 0;
424 init_io(struct io
*io
, const char *dir
, enum io_type type
)
432 init_io_rd(struct io
*io
, const char *argv
[], const char *dir
,
433 enum format_flags flags
)
435 init_io(io
, dir
, IO_RD
);
436 return format_argv(io
->argv
, argv
, flags
);
440 io_open(struct io
*io
, const char *name
)
442 init_io(io
, NULL
, IO_FD
);
443 io
->pipe
= *name
? open(name
, O_RDONLY
) : STDIN_FILENO
;
446 return io
->pipe
!= -1;
450 kill_io(struct io
*io
)
452 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
456 done_io(struct io
*io
)
467 pid_t waiting
= waitpid(pid
, &status
, 0);
472 report("waitpid failed (%s)", strerror(errno
));
476 return waiting
== pid
&&
477 !WIFSIGNALED(status
) &&
479 !WEXITSTATUS(status
);
486 start_io(struct io
*io
)
488 int pipefds
[2] = { -1, -1 };
490 if (io
->type
== IO_FD
)
493 if ((io
->type
== IO_RD
|| io
->type
== IO_WR
) &&
496 else if (io
->type
== IO_AP
)
497 pipefds
[1] = io
->pipe
;
499 if ((io
->pid
= fork())) {
500 if (pipefds
[!(io
->type
== IO_WR
)] != -1)
501 close(pipefds
[!(io
->type
== IO_WR
)]);
503 io
->pipe
= pipefds
[!!(io
->type
== IO_WR
)];
508 if (io
->type
!= IO_FG
) {
509 int devnull
= open("/dev/null", O_RDWR
);
510 int readfd
= io
->type
== IO_WR
? pipefds
[0] : devnull
;
511 int writefd
= (io
->type
== IO_RD
|| io
->type
== IO_AP
)
512 ? pipefds
[1] : devnull
;
514 dup2(readfd
, STDIN_FILENO
);
515 dup2(writefd
, STDOUT_FILENO
);
516 dup2(devnull
, STDERR_FILENO
);
519 if (pipefds
[0] != -1)
521 if (pipefds
[1] != -1)
525 if (io
->dir
&& *io
->dir
&& chdir(io
->dir
) == -1)
526 die("Failed to change directory: %s", strerror(errno
));
528 execvp(io
->argv
[0], (char *const*) io
->argv
);
529 die("Failed to execute program: %s", strerror(errno
));
532 if (pipefds
[!!(io
->type
== IO_WR
)] != -1)
533 close(pipefds
[!!(io
->type
== IO_WR
)]);
538 run_io(struct io
*io
, const char **argv
, const char *dir
, enum io_type type
)
540 init_io(io
, dir
, type
);
541 if (!format_argv(io
->argv
, argv
, FORMAT_NONE
))
547 run_io_do(struct io
*io
)
549 return start_io(io
) && done_io(io
);
553 run_io_bg(const char **argv
)
557 init_io(&io
, NULL
, IO_BG
);
558 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
560 return run_io_do(&io
);
564 run_io_fg(const char **argv
, const char *dir
)
568 init_io(&io
, dir
, IO_FG
);
569 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
571 return run_io_do(&io
);
575 run_io_append(const char **argv
, enum format_flags flags
, int fd
)
579 init_io(&io
, NULL
, IO_AP
);
581 if (format_argv(io
.argv
, argv
, flags
))
582 return run_io_do(&io
);
588 run_io_rd(struct io
*io
, const char **argv
, enum format_flags flags
)
590 return init_io_rd(io
, argv
, NULL
, flags
) && start_io(io
);
594 io_eof(struct io
*io
)
600 io_error(struct io
*io
)
606 io_strerror(struct io
*io
)
608 return strerror(io
->error
);
612 io_can_read(struct io
*io
)
614 struct timeval tv
= { 0, 500 };
618 FD_SET(io
->pipe
, &fds
);
620 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
624 io_read(struct io
*io
, void *buf
, size_t bufsize
)
627 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
629 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
631 else if (readsize
== -1)
633 else if (readsize
== 0)
639 DEFINE_ALLOCATOR(realloc_io_buf
, char, BUFSIZ
)
642 io_get(struct io
*io
, int c
, bool can_read
)
648 if (io
->bufsize
> 0) {
649 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
651 char *line
= io
->bufpos
;
654 io
->bufpos
= eol
+ 1;
655 io
->bufsize
-= io
->bufpos
- line
;
662 io
->bufpos
[io
->bufsize
] = 0;
672 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
673 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
675 if (io
->bufalloc
== io
->bufsize
) {
676 if (!realloc_io_buf(&io
->buf
, io
->bufalloc
, BUFSIZ
))
678 io
->bufalloc
+= BUFSIZ
;
681 io
->bufpos
= io
->buf
;
682 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
685 io
->bufsize
+= readsize
;
690 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
694 while (!io_error(io
) && written
< bufsize
) {
697 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
698 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
706 return written
== bufsize
;
710 io_read_buf(struct io
*io
, char buf
[], size_t bufsize
)
712 char *result
= io_get(io
, '\n', TRUE
);
715 result
= chomp_string(result
);
716 string_ncopy_do(buf
, bufsize
, result
, strlen(result
));
719 return done_io(io
) && result
;
723 run_io_buf(const char **argv
, char buf
[], size_t bufsize
)
727 return run_io_rd(&io
, argv
, FORMAT_NONE
) && io_read_buf(&io
, buf
, bufsize
);
731 io_load(struct io
*io
, const char *separators
,
732 int (*read_property
)(char *, size_t, char *, size_t))
740 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
745 name
= chomp_string(name
);
746 namelen
= strcspn(name
, separators
);
750 value
= chomp_string(name
+ namelen
+ 1);
751 valuelen
= strlen(value
);
758 state
= read_property(name
, namelen
, value
, valuelen
);
761 if (state
!= ERR
&& io_error(io
))
769 run_io_load(const char **argv
, const char *separators
,
770 int (*read_property
)(char *, size_t, char *, size_t))
774 return init_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
775 ? io_load(&io
, separators
, read_property
) : ERR
;
784 /* XXX: Keep the view request first and in sync with views[]. */ \
785 REQ_GROUP("View switching") \
786 REQ_(VIEW_MAIN, "Show main view"), \
787 REQ_(VIEW_DIFF, "Show diff view"), \
788 REQ_(VIEW_LOG, "Show log view"), \
789 REQ_(VIEW_TREE, "Show tree view"), \
790 REQ_(VIEW_BLOB, "Show blob view"), \
791 REQ_(VIEW_BLAME, "Show blame view"), \
792 REQ_(VIEW_BRANCH, "Show branch view"), \
793 REQ_(VIEW_HELP, "Show help page"), \
794 REQ_(VIEW_PAGER, "Show pager view"), \
795 REQ_(VIEW_STATUS, "Show status view"), \
796 REQ_(VIEW_STAGE, "Show stage view"), \
798 REQ_GROUP("View manipulation") \
799 REQ_(ENTER, "Enter current line and scroll"), \
800 REQ_(NEXT, "Move to next"), \
801 REQ_(PREVIOUS, "Move to previous"), \
802 REQ_(PARENT, "Move to parent"), \
803 REQ_(VIEW_NEXT, "Move focus to next view"), \
804 REQ_(REFRESH, "Reload and refresh"), \
805 REQ_(MAXIMIZE, "Maximize the current view"), \
806 REQ_(VIEW_CLOSE, "Close the current view"), \
807 REQ_(QUIT, "Close all views and quit"), \
809 REQ_GROUP("View specific requests") \
810 REQ_(STATUS_UPDATE, "Update file status"), \
811 REQ_(STATUS_REVERT, "Revert file changes"), \
812 REQ_(STATUS_MERGE, "Merge file using external tool"), \
813 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
815 REQ_GROUP("Cursor navigation") \
816 REQ_(MOVE_UP, "Move cursor one line up"), \
817 REQ_(MOVE_DOWN, "Move cursor one line down"), \
818 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
819 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
820 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
821 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
823 REQ_GROUP("Scrolling") \
824 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
825 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
826 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
827 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
828 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
829 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
831 REQ_GROUP("Searching") \
832 REQ_(SEARCH, "Search the view"), \
833 REQ_(SEARCH_BACK, "Search backwards in the view"), \
834 REQ_(FIND_NEXT, "Find next search match"), \
835 REQ_(FIND_PREV, "Find previous search match"), \
837 REQ_GROUP("Option manipulation") \
838 REQ_(OPTIONS, "Open option menu"), \
839 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
840 REQ_(TOGGLE_DATE, "Toggle date display"), \
841 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
842 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
843 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
844 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
845 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
846 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
849 REQ_(PROMPT, "Bring up the prompt"), \
850 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
851 REQ_(SHOW_VERSION, "Show version information"), \
852 REQ_(STOP_LOADING, "Stop all loading views"), \
853 REQ_(EDIT, "Open in editor"), \
854 REQ_(NONE, "Do nothing")
857 /* User action requests. */
859 #define REQ_GROUP(help)
860 #define REQ_(req, help) REQ_##req
862 /* Offset all requests to avoid conflicts with ncurses getch values. */
863 REQ_OFFSET
= KEY_MAX
+ 1,
870 struct request_info
{
871 enum request request
;
877 static const struct request_info req_info
[] = {
878 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
879 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
886 get_request(const char *name
)
888 int namelen
= strlen(name
);
891 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
892 if (req_info
[i
].namelen
== namelen
&&
893 !string_enum_compare(req_info
[i
].name
, name
, namelen
))
894 return req_info
[i
].request
;
904 /* Option and state variables. */
905 static bool opt_date
= TRUE
;
906 static bool opt_date_short
= FALSE
;
907 static bool opt_author
= TRUE
;
908 static bool opt_line_number
= FALSE
;
909 static bool opt_line_graphics
= TRUE
;
910 static bool opt_rev_graph
= FALSE
;
911 static bool opt_show_refs
= TRUE
;
912 static int opt_num_interval
= 5;
913 static double opt_hscroll
= 0.50;
914 static double opt_scale_split_view
= 2.0 / 3.0;
915 static int opt_tab_size
= 8;
916 static int opt_author_cols
= 19;
917 static char opt_path
[SIZEOF_STR
] = "";
918 static char opt_file
[SIZEOF_STR
] = "";
919 static char opt_ref
[SIZEOF_REF
] = "";
920 static char opt_head
[SIZEOF_REF
] = "";
921 static char opt_head_rev
[SIZEOF_REV
] = "";
922 static char opt_remote
[SIZEOF_REF
] = "";
923 static char opt_encoding
[20] = "UTF-8";
924 static bool opt_utf8
= TRUE
;
925 static char opt_codeset
[20] = "UTF-8";
926 static iconv_t opt_iconv
= ICONV_NONE
;
927 static char opt_search
[SIZEOF_STR
] = "";
928 static char opt_cdup
[SIZEOF_STR
] = "";
929 static char opt_prefix
[SIZEOF_STR
] = "";
930 static char opt_git_dir
[SIZEOF_STR
] = "";
931 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
932 static char opt_editor
[SIZEOF_STR
] = "";
933 static FILE *opt_tty
= NULL
;
935 #define is_initial_commit() (!*opt_head_rev)
936 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
940 * Line-oriented content detection.
944 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
945 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
946 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
947 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
948 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
949 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
950 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
951 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
952 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
953 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
954 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
955 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
956 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
957 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
958 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
959 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
960 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
961 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
962 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
963 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
964 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
965 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
966 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
967 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
968 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
969 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
970 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
971 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
972 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
973 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
974 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
975 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
976 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
977 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
978 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
979 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
980 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
981 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
982 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
983 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
984 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
985 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
986 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
987 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
988 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
989 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
990 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
991 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
992 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
993 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
994 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
995 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
996 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
997 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
998 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1001 #define LINE(type, line, fg, bg, attr) \
1009 const char *name
; /* Option name. */
1010 int namelen
; /* Size of option name. */
1011 const char *line
; /* The start of line to match. */
1012 int linelen
; /* Size of string to match. */
1013 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1016 static struct line_info line_info
[] = {
1017 #define LINE(type, line, fg, bg, attr) \
1018 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1023 static enum line_type
1024 get_line_type(const char *line
)
1026 int linelen
= strlen(line
);
1027 enum line_type type
;
1029 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1030 /* Case insensitive search matches Signed-off-by lines better. */
1031 if (linelen
>= line_info
[type
].linelen
&&
1032 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1035 return LINE_DEFAULT
;
1039 get_line_attr(enum line_type type
)
1041 assert(type
< ARRAY_SIZE(line_info
));
1042 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1045 static struct line_info
*
1046 get_line_info(const char *name
)
1048 size_t namelen
= strlen(name
);
1049 enum line_type type
;
1051 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1052 if (namelen
== line_info
[type
].namelen
&&
1053 !string_enum_compare(line_info
[type
].name
, name
, namelen
))
1054 return &line_info
[type
];
1062 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1063 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1064 enum line_type type
;
1068 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1069 default_bg
= COLOR_BLACK
;
1070 default_fg
= COLOR_WHITE
;
1073 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1074 struct line_info
*info
= &line_info
[type
];
1075 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1076 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1078 init_pair(type
, fg
, bg
);
1083 enum line_type type
;
1086 unsigned int selected
:1;
1087 unsigned int dirty
:1;
1088 unsigned int cleareol
:1;
1090 void *data
; /* User data */
1100 enum request request
;
1103 static const struct keybinding default_keybindings
[] = {
1104 /* View switching */
1105 { 'm', REQ_VIEW_MAIN
},
1106 { 'd', REQ_VIEW_DIFF
},
1107 { 'l', REQ_VIEW_LOG
},
1108 { 't', REQ_VIEW_TREE
},
1109 { 'f', REQ_VIEW_BLOB
},
1110 { 'B', REQ_VIEW_BLAME
},
1111 { 'H', REQ_VIEW_BRANCH
},
1112 { 'p', REQ_VIEW_PAGER
},
1113 { 'h', REQ_VIEW_HELP
},
1114 { 'S', REQ_VIEW_STATUS
},
1115 { 'c', REQ_VIEW_STAGE
},
1117 /* View manipulation */
1118 { 'q', REQ_VIEW_CLOSE
},
1119 { KEY_TAB
, REQ_VIEW_NEXT
},
1120 { KEY_RETURN
, REQ_ENTER
},
1121 { KEY_UP
, REQ_PREVIOUS
},
1122 { KEY_DOWN
, REQ_NEXT
},
1123 { 'R', REQ_REFRESH
},
1124 { KEY_F(5), REQ_REFRESH
},
1125 { 'O', REQ_MAXIMIZE
},
1127 /* Cursor navigation */
1128 { 'k', REQ_MOVE_UP
},
1129 { 'j', REQ_MOVE_DOWN
},
1130 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1131 { KEY_END
, REQ_MOVE_LAST_LINE
},
1132 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1133 { ' ', REQ_MOVE_PAGE_DOWN
},
1134 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1135 { 'b', REQ_MOVE_PAGE_UP
},
1136 { '-', REQ_MOVE_PAGE_UP
},
1139 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1140 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1141 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1142 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1143 { 'w', REQ_SCROLL_PAGE_UP
},
1144 { 's', REQ_SCROLL_PAGE_DOWN
},
1147 { '/', REQ_SEARCH
},
1148 { '?', REQ_SEARCH_BACK
},
1149 { 'n', REQ_FIND_NEXT
},
1150 { 'N', REQ_FIND_PREV
},
1154 { 'z', REQ_STOP_LOADING
},
1155 { 'v', REQ_SHOW_VERSION
},
1156 { 'r', REQ_SCREEN_REDRAW
},
1157 { 'o', REQ_OPTIONS
},
1158 { '.', REQ_TOGGLE_LINENO
},
1159 { 'D', REQ_TOGGLE_DATE
},
1160 { 'T', REQ_TOGGLE_DATE_SHORT
},
1161 { 'A', REQ_TOGGLE_AUTHOR
},
1162 { 'g', REQ_TOGGLE_REV_GRAPH
},
1163 { 'F', REQ_TOGGLE_REFS
},
1164 { 'I', REQ_TOGGLE_SORT_ORDER
},
1165 { 'i', REQ_TOGGLE_SORT_FIELD
},
1166 { ':', REQ_PROMPT
},
1167 { 'u', REQ_STATUS_UPDATE
},
1168 { '!', REQ_STATUS_REVERT
},
1169 { 'M', REQ_STATUS_MERGE
},
1170 { '@', REQ_STAGE_NEXT
},
1171 { ',', REQ_PARENT
},
1175 #define KEYMAP_INFO \
1190 #define KEYMAP_(name) KEYMAP_##name
1195 static const struct enum_map keymap_table
[] = {
1196 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1201 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1203 struct keybinding_table
{
1204 struct keybinding
*data
;
1208 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1211 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1213 struct keybinding_table
*table
= &keybindings
[keymap
];
1215 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1217 die("Failed to allocate keybinding");
1218 table
->data
[table
->size
].alias
= key
;
1219 table
->data
[table
->size
++].request
= request
;
1222 /* Looks for a key binding first in the given map, then in the generic map, and
1223 * lastly in the default keybindings. */
1225 get_keybinding(enum keymap keymap
, int key
)
1229 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1230 if (keybindings
[keymap
].data
[i
].alias
== key
)
1231 return keybindings
[keymap
].data
[i
].request
;
1233 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1234 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1235 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1237 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1238 if (default_keybindings
[i
].alias
== key
)
1239 return default_keybindings
[i
].request
;
1241 return (enum request
) key
;
1250 static const struct key key_table
[] = {
1251 { "Enter", KEY_RETURN
},
1253 { "Backspace", KEY_BACKSPACE
},
1255 { "Escape", KEY_ESC
},
1256 { "Left", KEY_LEFT
},
1257 { "Right", KEY_RIGHT
},
1259 { "Down", KEY_DOWN
},
1260 { "Insert", KEY_IC
},
1261 { "Delete", KEY_DC
},
1263 { "Home", KEY_HOME
},
1265 { "PageUp", KEY_PPAGE
},
1266 { "PageDown", KEY_NPAGE
},
1276 { "F10", KEY_F(10) },
1277 { "F11", KEY_F(11) },
1278 { "F12", KEY_F(12) },
1282 get_key_value(const char *name
)
1286 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1287 if (!strcasecmp(key_table
[i
].name
, name
))
1288 return key_table
[i
].value
;
1290 if (strlen(name
) == 1 && isprint(*name
))
1297 get_key_name(int key_value
)
1299 static char key_char
[] = "'X'";
1300 const char *seq
= NULL
;
1303 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1304 if (key_table
[key
].value
== key_value
)
1305 seq
= key_table
[key
].name
;
1309 isprint(key_value
)) {
1310 key_char
[1] = (char) key_value
;
1314 return seq
? seq
: "(no key)";
1318 get_key(enum request request
)
1320 static char buf
[BUFSIZ
];
1327 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1328 const struct keybinding
*keybinding
= &default_keybindings
[i
];
1330 if (keybinding
->request
!= request
)
1333 if (!string_format_from(buf
, &pos
, "%s%s", sep
,
1334 get_key_name(keybinding
->alias
)))
1335 return "Too many keybindings!";
1342 struct run_request
{
1345 const char *argv
[SIZEOF_ARG
];
1348 static struct run_request
*run_request
;
1349 static size_t run_requests
;
1351 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1354 add_run_request(enum keymap keymap
, int key
, int argc
, const char **argv
)
1356 struct run_request
*req
;
1358 if (argc
>= ARRAY_SIZE(req
->argv
) - 1)
1361 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1364 req
= &run_request
[run_requests
];
1365 req
->keymap
= keymap
;
1367 req
->argv
[0] = NULL
;
1369 if (!format_argv(req
->argv
, argv
, FORMAT_NONE
))
1372 return REQ_NONE
+ ++run_requests
;
1375 static struct run_request
*
1376 get_run_request(enum request request
)
1378 if (request
<= REQ_NONE
)
1380 return &run_request
[request
- REQ_NONE
- 1];
1384 add_builtin_run_requests(void)
1386 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1387 const char *commit
[] = { "git", "commit", NULL
};
1388 const char *gc
[] = { "git", "gc", NULL
};
1395 { KEYMAP_MAIN
, 'C', ARRAY_SIZE(cherry_pick
) - 1, cherry_pick
},
1396 { KEYMAP_STATUS
, 'C', ARRAY_SIZE(commit
) - 1, commit
},
1397 { KEYMAP_GENERIC
, 'G', ARRAY_SIZE(gc
) - 1, gc
},
1401 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1404 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argc
, reqs
[i
].argv
);
1405 if (req
!= REQ_NONE
)
1406 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1411 * User config file handling.
1414 static int config_lineno
;
1415 static bool config_errors
;
1416 static const char *config_msg
;
1418 static const struct enum_map color_map
[] = {
1419 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1431 static const struct enum_map attr_map
[] = {
1432 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1439 ATTR_MAP(UNDERLINE
),
1442 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1444 static int parse_step(double *opt
, const char *arg
)
1447 if (!strchr(arg
, '%'))
1450 /* "Shift down" so 100% and 1 does not conflict. */
1451 *opt
= (*opt
- 1) / 100;
1454 config_msg
= "Step value larger than 100%";
1459 config_msg
= "Invalid step value";
1466 parse_int(int *opt
, const char *arg
, int min
, int max
)
1468 int value
= atoi(arg
);
1470 if (min
<= value
&& value
<= max
) {
1475 config_msg
= "Integer value out of bound";
1480 set_color(int *color
, const char *name
)
1482 if (map_enum(color
, color_map
, name
))
1484 if (!prefixcmp(name
, "color"))
1485 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1489 /* Wants: object fgcolor bgcolor [attribute] */
1491 option_color_command(int argc
, const char *argv
[])
1493 struct line_info
*info
;
1496 config_msg
= "Wrong number of arguments given to color command";
1500 info
= get_line_info(argv
[0]);
1502 static const struct enum_map obsolete
[] = {
1503 ENUM_MAP("main-delim", LINE_DELIMITER
),
1504 ENUM_MAP("main-date", LINE_DATE
),
1505 ENUM_MAP("main-author", LINE_AUTHOR
),
1509 if (!map_enum(&index
, obsolete
, argv
[0])) {
1510 config_msg
= "Unknown color name";
1513 info
= &line_info
[index
];
1516 if (!set_color(&info
->fg
, argv
[1]) ||
1517 !set_color(&info
->bg
, argv
[2])) {
1518 config_msg
= "Unknown color";
1523 while (argc
-- > 3) {
1526 if (!set_attribute(&attr
, argv
[argc
])) {
1527 config_msg
= "Unknown attribute";
1536 static int parse_bool(bool *opt
, const char *arg
)
1538 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1544 parse_string(char *opt
, const char *arg
, size_t optsize
)
1546 int arglen
= strlen(arg
);
1551 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1552 config_msg
= "Unmatched quotation";
1555 arg
+= 1; arglen
-= 2;
1557 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1562 /* Wants: name = value */
1564 option_set_command(int argc
, const char *argv
[])
1567 config_msg
= "Wrong number of arguments given to set command";
1571 if (strcmp(argv
[1], "=")) {
1572 config_msg
= "No value assigned";
1576 if (!strcmp(argv
[0], "show-author"))
1577 return parse_bool(&opt_author
, argv
[2]);
1579 if (!strcmp(argv
[0], "show-date"))
1580 return parse_bool(&opt_date
, argv
[2]);
1582 if (!strcmp(argv
[0], "date-short"))
1583 return parse_bool(&opt_date_short
, argv
[2]);
1585 if (!strcmp(argv
[0], "show-rev-graph"))
1586 return parse_bool(&opt_rev_graph
, argv
[2]);
1588 if (!strcmp(argv
[0], "show-refs"))
1589 return parse_bool(&opt_show_refs
, argv
[2]);
1591 if (!strcmp(argv
[0], "show-line-numbers"))
1592 return parse_bool(&opt_line_number
, argv
[2]);
1594 if (!strcmp(argv
[0], "line-graphics"))
1595 return parse_bool(&opt_line_graphics
, argv
[2]);
1597 if (!strcmp(argv
[0], "line-number-interval"))
1598 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
1600 if (!strcmp(argv
[0], "author-width"))
1601 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
1603 if (!strcmp(argv
[0], "horizontal-scroll"))
1604 return parse_step(&opt_hscroll
, argv
[2]);
1606 if (!strcmp(argv
[0], "split-view-height"))
1607 return parse_step(&opt_scale_split_view
, argv
[2]);
1609 if (!strcmp(argv
[0], "tab-size"))
1610 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
1612 if (!strcmp(argv
[0], "commit-encoding"))
1613 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
1615 config_msg
= "Unknown variable name";
1619 /* Wants: mode request key */
1621 option_bind_command(int argc
, const char *argv
[])
1623 enum request request
;
1628 config_msg
= "Wrong number of arguments given to bind command";
1632 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
1633 config_msg
= "Unknown key map";
1637 key
= get_key_value(argv
[1]);
1639 config_msg
= "Unknown key";
1643 request
= get_request(argv
[2]);
1644 if (request
== REQ_NONE
) {
1645 static const struct enum_map obsolete
[] = {
1646 ENUM_MAP("cherry-pick", REQ_NONE
),
1647 ENUM_MAP("screen-resize", REQ_NONE
),
1648 ENUM_MAP("tree-parent", REQ_PARENT
),
1652 if (map_enum(&alias
, obsolete
, argv
[2])) {
1653 if (alias
!= REQ_NONE
)
1654 add_keybinding(keymap
, alias
, key
);
1655 config_msg
= "Obsolete request name";
1659 if (request
== REQ_NONE
&& *argv
[2]++ == '!')
1660 request
= add_run_request(keymap
, key
, argc
- 2, argv
+ 2);
1661 if (request
== REQ_NONE
) {
1662 config_msg
= "Unknown request name";
1666 add_keybinding(keymap
, request
, key
);
1672 set_option(const char *opt
, char *value
)
1674 const char *argv
[SIZEOF_ARG
];
1677 if (!argv_from_string(argv
, &argc
, value
)) {
1678 config_msg
= "Too many option arguments";
1682 if (!strcmp(opt
, "color"))
1683 return option_color_command(argc
, argv
);
1685 if (!strcmp(opt
, "set"))
1686 return option_set_command(argc
, argv
);
1688 if (!strcmp(opt
, "bind"))
1689 return option_bind_command(argc
, argv
);
1691 config_msg
= "Unknown option command";
1696 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
1701 config_msg
= "Internal error";
1703 /* Check for comment markers, since read_properties() will
1704 * only ensure opt and value are split at first " \t". */
1705 optlen
= strcspn(opt
, "#");
1709 if (opt
[optlen
] != 0) {
1710 config_msg
= "No option value";
1714 /* Look for comment endings in the value. */
1715 size_t len
= strcspn(value
, "#");
1717 if (len
< valuelen
) {
1719 value
[valuelen
] = 0;
1722 status
= set_option(opt
, value
);
1725 if (status
== ERR
) {
1726 warn("Error on line %d, near '%.*s': %s",
1727 config_lineno
, (int) optlen
, opt
, config_msg
);
1728 config_errors
= TRUE
;
1731 /* Always keep going if errors are encountered. */
1736 load_option_file(const char *path
)
1740 /* It's OK that the file doesn't exist. */
1741 if (!io_open(&io
, path
))
1745 config_errors
= FALSE
;
1747 if (io_load(&io
, " \t", read_option
) == ERR
||
1748 config_errors
== TRUE
)
1749 warn("Errors while loading %s.", path
);
1755 const char *home
= getenv("HOME");
1756 const char *tigrc_user
= getenv("TIGRC_USER");
1757 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
1758 char buf
[SIZEOF_STR
];
1760 add_builtin_run_requests();
1763 tigrc_system
= SYSCONFDIR
"/tigrc";
1764 load_option_file(tigrc_system
);
1767 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
1771 load_option_file(tigrc_user
);
1784 /* The display array of active views and the index of the current view. */
1785 static struct view
*display
[2];
1786 static unsigned int current_view
;
1788 #define foreach_displayed_view(view, i) \
1789 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1791 #define displayed_views() (display[1] != NULL ? 2 : 1)
1793 /* Current head and commit ID */
1794 static char ref_blob
[SIZEOF_REF
] = "";
1795 static char ref_commit
[SIZEOF_REF
] = "HEAD";
1796 static char ref_head
[SIZEOF_REF
] = "HEAD";
1799 const char *name
; /* View name */
1800 const char *cmd_env
; /* Command line set via environment */
1801 const char *id
; /* Points to either of ref_{head,commit,blob} */
1803 struct view_ops
*ops
; /* View operations */
1805 enum keymap keymap
; /* What keymap does this view have */
1806 bool git_dir
; /* Whether the view requires a git directory. */
1808 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
1809 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
1811 int height
, width
; /* The width and height of the main window */
1812 WINDOW
*win
; /* The main window */
1813 WINDOW
*title
; /* The title window living below the main window */
1816 unsigned long offset
; /* Offset of the window top */
1817 unsigned long yoffset
; /* Offset from the window side. */
1818 unsigned long lineno
; /* Current line number */
1819 unsigned long p_offset
; /* Previous offset of the window top */
1820 unsigned long p_yoffset
;/* Previous offset from the window side */
1821 unsigned long p_lineno
; /* Previous current line number */
1822 bool p_restore
; /* Should the previous position be restored. */
1825 char grep
[SIZEOF_STR
]; /* Search string */
1826 regex_t
*regex
; /* Pre-compiled regexp */
1828 /* If non-NULL, points to the view that opened this view. If this view
1829 * is closed tig will switch back to the parent view. */
1830 struct view
*parent
;
1833 size_t lines
; /* Total number of lines */
1834 struct line
*line
; /* Line index */
1835 unsigned int digits
; /* Number of digits in the lines member. */
1838 struct line
*curline
; /* Line currently being drawn. */
1839 enum line_type curtype
; /* Attribute currently used for drawing. */
1840 unsigned long col
; /* Column when drawing. */
1841 bool has_scrolled
; /* View was scrolled. */
1851 /* What type of content being displayed. Used in the title bar. */
1853 /* Default command arguments. */
1855 /* Open and reads in all view content. */
1856 bool (*open
)(struct view
*view
);
1857 /* Read one line; updates view->line. */
1858 bool (*read
)(struct view
*view
, char *data
);
1859 /* Draw one line; @lineno must be < view->height. */
1860 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
1861 /* Depending on view handle a special requests. */
1862 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
1863 /* Search for regexp in a line. */
1864 bool (*grep
)(struct view
*view
, struct line
*line
);
1866 void (*select
)(struct view
*view
, struct line
*line
);
1869 static struct view_ops blame_ops
;
1870 static struct view_ops blob_ops
;
1871 static struct view_ops diff_ops
;
1872 static struct view_ops help_ops
;
1873 static struct view_ops log_ops
;
1874 static struct view_ops main_ops
;
1875 static struct view_ops pager_ops
;
1876 static struct view_ops stage_ops
;
1877 static struct view_ops status_ops
;
1878 static struct view_ops tree_ops
;
1879 static struct view_ops branch_ops
;
1881 #define VIEW_STR(name, env, ref, ops, map, git) \
1882 { name, #env, ref, ops, map, git }
1884 #define VIEW_(id, name, ops, git, ref) \
1885 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1888 static struct view views
[] = {
1889 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
1890 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
1891 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
1892 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
1893 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
1894 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
1895 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
1896 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
1897 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
1898 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
1899 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
1902 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1903 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1905 #define foreach_view(view, i) \
1906 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1908 #define view_is_displayed(view) \
1909 (view == display[0] || view == display[1])
1916 static chtype line_graphics
[] = {
1917 /* LINE_GRAPHIC_VLINE: */ '|'
1921 set_view_attr(struct view
*view
, enum line_type type
)
1923 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
1924 wattrset(view
->win
, get_line_attr(type
));
1925 wchgat(view
->win
, -1, 0, type
, NULL
);
1926 view
->curtype
= type
;
1931 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
1932 int max_len
, bool use_tilde
)
1936 int trimmed
= FALSE
;
1937 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
1943 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
);
1945 col
= len
= strlen(string
);
1946 if (len
> max_len
) {
1950 col
= len
= max_len
;
1955 set_view_attr(view
, type
);
1957 waddnstr(view
->win
, string
, len
);
1958 if (trimmed
&& use_tilde
) {
1959 set_view_attr(view
, LINE_DELIMITER
);
1960 waddch(view
->win
, '~');
1968 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
1970 static char space
[] = " ";
1973 spaces
= MIN(max
, spaces
);
1975 while (spaces
> 0) {
1976 int len
= MIN(spaces
, sizeof(space
) - 1);
1978 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
1986 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
1988 view
->col
+= draw_chars(view
, type
, string
, view
->width
+ view
->yoffset
- view
->col
, trim
);
1989 return view
->width
+ view
->yoffset
<= view
->col
;
1993 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
1995 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
1996 int max
= view
->width
+ view
->yoffset
- view
->col
;
2002 set_view_attr(view
, type
);
2003 /* Using waddch() instead of waddnstr() ensures that
2004 * they'll be rendered correctly for the cursor line. */
2005 for (i
= skip
; i
< size
; i
++)
2006 waddch(view
->win
, graphic
[i
]);
2009 if (size
< max
&& skip
<= size
)
2010 waddch(view
->win
, ' ');
2013 return view
->width
+ view
->yoffset
<= view
->col
;
2017 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2019 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2023 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2025 col
= draw_space(view
, type
, max
- 1, max
- 1);
2028 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2029 return view
->width
+ view
->yoffset
<= view
->col
;
2033 draw_date(struct view
*view
, time_t *time
)
2035 const char *date
= mkdate(time
);
2036 int cols
= opt_date_short
? DATE_SHORT_COLS
: DATE_COLS
;
2038 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2042 draw_author(struct view
*view
, const char *author
)
2044 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5 || !author
;
2047 static char initials
[10];
2050 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2052 memset(initials
, 0, sizeof(initials
));
2053 for (pos
= 0; *author
&& pos
< opt_author_cols
- 1; author
++, pos
++) {
2054 while (is_initial_sep(*author
))
2056 strncpy(&initials
[pos
], author
, sizeof(initials
) - 1 - pos
);
2057 while (*author
&& !is_initial_sep(author
[1]))
2064 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2068 draw_mode(struct view
*view
, mode_t mode
)
2074 else if (S_ISLNK(mode
))
2076 else if (S_ISGITLINK(mode
))
2078 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2080 else if (S_ISREG(mode
))
2085 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2089 draw_lineno(struct view
*view
, unsigned int lineno
)
2092 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2093 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2096 lineno
+= view
->offset
+ 1;
2097 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2098 static char fmt
[] = "%1ld";
2100 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2101 if (string_format(number
, fmt
, lineno
))
2105 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2107 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2108 return draw_graphic(view
, LINE_DEFAULT
, &line_graphics
[LINE_GRAPHIC_VLINE
], 1);
2112 draw_view_line(struct view
*view
, unsigned int lineno
)
2115 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2117 assert(view_is_displayed(view
));
2119 if (view
->offset
+ lineno
>= view
->lines
)
2122 line
= &view
->line
[view
->offset
+ lineno
];
2124 wmove(view
->win
, lineno
, 0);
2126 wclrtoeol(view
->win
);
2128 view
->curline
= line
;
2129 view
->curtype
= LINE_NONE
;
2130 line
->selected
= FALSE
;
2131 line
->dirty
= line
->cleareol
= 0;
2134 set_view_attr(view
, LINE_CURSOR
);
2135 line
->selected
= TRUE
;
2136 view
->ops
->select(view
, line
);
2139 return view
->ops
->draw(view
, line
, lineno
);
2143 redraw_view_dirty(struct view
*view
)
2148 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2149 if (view
->offset
+ lineno
>= view
->lines
)
2151 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2154 if (!draw_view_line(view
, lineno
))
2160 wnoutrefresh(view
->win
);
2164 redraw_view_from(struct view
*view
, int lineno
)
2166 assert(0 <= lineno
&& lineno
< view
->height
);
2168 for (; lineno
< view
->height
; lineno
++) {
2169 if (!draw_view_line(view
, lineno
))
2173 wnoutrefresh(view
->win
);
2177 redraw_view(struct view
*view
)
2180 redraw_view_from(view
, 0);
2185 update_view_title(struct view
*view
)
2187 char buf
[SIZEOF_STR
];
2188 char state
[SIZEOF_STR
];
2189 size_t bufpos
= 0, statelen
= 0;
2191 assert(view_is_displayed(view
));
2193 if (view
!= VIEW(REQ_VIEW_STATUS
) && view
->lines
) {
2194 unsigned int view_lines
= view
->offset
+ view
->height
;
2195 unsigned int lines
= view
->lines
2196 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2199 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2208 time_t secs
= time(NULL
) - view
->start_time
;
2210 /* Three git seconds are a long time ... */
2212 string_format_from(state
, &statelen
, " loading %lds", secs
);
2215 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2216 if (*view
->ref
&& bufpos
< view
->width
) {
2217 size_t refsize
= strlen(view
->ref
);
2218 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2220 if (minsize
< view
->width
)
2221 refsize
= view
->width
- minsize
+ 7;
2222 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2225 if (statelen
&& bufpos
< view
->width
) {
2226 string_format_from(buf
, &bufpos
, "%s", state
);
2229 if (view
== display
[current_view
])
2230 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2232 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2234 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2235 wclrtoeol(view
->title
);
2236 wnoutrefresh(view
->title
);
2240 apply_step(double step
, int value
)
2244 value
*= step
+ 0.01;
2245 return value
? value
: 1;
2249 resize_display(void)
2252 struct view
*base
= display
[0];
2253 struct view
*view
= display
[1] ? display
[1] : display
[0];
2255 /* Setup window dimensions */
2257 getmaxyx(stdscr
, base
->height
, base
->width
);
2259 /* Make room for the status window. */
2263 /* Horizontal split. */
2264 view
->width
= base
->width
;
2265 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2266 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2267 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2268 base
->height
-= view
->height
;
2270 /* Make room for the title bar. */
2274 /* Make room for the title bar. */
2279 foreach_displayed_view (view
, i
) {
2281 view
->win
= newwin(view
->height
, 0, offset
, 0);
2283 die("Failed to create %s view", view
->name
);
2285 scrollok(view
->win
, FALSE
);
2287 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2289 die("Failed to create title window");
2292 wresize(view
->win
, view
->height
, view
->width
);
2293 mvwin(view
->win
, offset
, 0);
2294 mvwin(view
->title
, offset
+ view
->height
, 0);
2297 offset
+= view
->height
+ 1;
2302 redraw_display(bool clear
)
2307 foreach_displayed_view (view
, i
) {
2311 update_view_title(view
);
2316 toggle_view_option(bool *option
, const char *help
)
2319 redraw_display(FALSE
);
2320 report("%sabling %s", *option
? "En" : "Dis", help
);
2324 open_option_menu(void)
2326 const struct menu_item menu
[] = {
2327 { '.', "line numbers", &opt_line_number
},
2328 { 'D', "date display", &opt_date
},
2329 { 'A', "author display", &opt_author
},
2330 { 'g', "revision graph display", &opt_rev_graph
},
2331 { 'F', "reference display", &opt_show_refs
},
2336 if (prompt_menu("Toggle option", menu
, &selected
))
2337 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2341 maximize_view(struct view
*view
)
2343 memset(display
, 0, sizeof(display
));
2345 display
[current_view
] = view
;
2347 redraw_display(FALSE
);
2357 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2359 if (lineno
>= view
->lines
)
2360 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2362 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2363 unsigned long half
= view
->height
/ 2;
2366 offset
= lineno
- half
;
2371 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2372 view
->offset
= offset
;
2373 view
->lineno
= lineno
;
2380 /* Scrolling backend */
2382 do_scroll_view(struct view
*view
, int lines
)
2384 bool redraw_current_line
= FALSE
;
2386 /* The rendering expects the new offset. */
2387 view
->offset
+= lines
;
2389 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2392 /* Move current line into the view. */
2393 if (view
->lineno
< view
->offset
) {
2394 view
->lineno
= view
->offset
;
2395 redraw_current_line
= TRUE
;
2396 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2397 view
->lineno
= view
->offset
+ view
->height
- 1;
2398 redraw_current_line
= TRUE
;
2401 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2403 /* Redraw the whole screen if scrolling is pointless. */
2404 if (view
->height
< ABS(lines
)) {
2408 int line
= lines
> 0 ? view
->height
- lines
: 0;
2409 int end
= line
+ ABS(lines
);
2411 scrollok(view
->win
, TRUE
);
2412 wscrl(view
->win
, lines
);
2413 scrollok(view
->win
, FALSE
);
2415 while (line
< end
&& draw_view_line(view
, line
))
2418 if (redraw_current_line
)
2419 draw_view_line(view
, view
->lineno
- view
->offset
);
2420 wnoutrefresh(view
->win
);
2423 view
->has_scrolled
= TRUE
;
2427 /* Scroll frontend */
2429 scroll_view(struct view
*view
, enum request request
)
2433 assert(view_is_displayed(view
));
2436 case REQ_SCROLL_LEFT
:
2437 if (view
->yoffset
== 0) {
2438 report("Cannot scroll beyond the first column");
2441 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2444 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2445 redraw_view_from(view
, 0);
2448 case REQ_SCROLL_RIGHT
:
2449 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2453 case REQ_SCROLL_PAGE_DOWN
:
2454 lines
= view
->height
;
2455 case REQ_SCROLL_LINE_DOWN
:
2456 if (view
->offset
+ lines
> view
->lines
)
2457 lines
= view
->lines
- view
->offset
;
2459 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2460 report("Cannot scroll beyond the last line");
2465 case REQ_SCROLL_PAGE_UP
:
2466 lines
= view
->height
;
2467 case REQ_SCROLL_LINE_UP
:
2468 if (lines
> view
->offset
)
2469 lines
= view
->offset
;
2472 report("Cannot scroll beyond the first line");
2480 die("request %d not handled in switch", request
);
2483 do_scroll_view(view
, lines
);
2488 move_view(struct view
*view
, enum request request
)
2490 int scroll_steps
= 0;
2494 case REQ_MOVE_FIRST_LINE
:
2495 steps
= -view
->lineno
;
2498 case REQ_MOVE_LAST_LINE
:
2499 steps
= view
->lines
- view
->lineno
- 1;
2502 case REQ_MOVE_PAGE_UP
:
2503 steps
= view
->height
> view
->lineno
2504 ? -view
->lineno
: -view
->height
;
2507 case REQ_MOVE_PAGE_DOWN
:
2508 steps
= view
->lineno
+ view
->height
>= view
->lines
2509 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2521 die("request %d not handled in switch", request
);
2524 if (steps
<= 0 && view
->lineno
== 0) {
2525 report("Cannot move beyond the first line");
2528 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2529 report("Cannot move beyond the last line");
2533 /* Move the current line */
2534 view
->lineno
+= steps
;
2535 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2537 /* Check whether the view needs to be scrolled */
2538 if (view
->lineno
< view
->offset
||
2539 view
->lineno
>= view
->offset
+ view
->height
) {
2540 scroll_steps
= steps
;
2541 if (steps
< 0 && -steps
> view
->offset
) {
2542 scroll_steps
= -view
->offset
;
2544 } else if (steps
> 0) {
2545 if (view
->lineno
== view
->lines
- 1 &&
2546 view
->lines
> view
->height
) {
2547 scroll_steps
= view
->lines
- view
->offset
- 1;
2548 if (scroll_steps
>= view
->height
)
2549 scroll_steps
-= view
->height
- 1;
2554 if (!view_is_displayed(view
)) {
2555 view
->offset
+= scroll_steps
;
2556 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2557 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2561 /* Repaint the old "current" line if we be scrolling */
2562 if (ABS(steps
) < view
->height
)
2563 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
2566 do_scroll_view(view
, scroll_steps
);
2570 /* Draw the current line */
2571 draw_view_line(view
, view
->lineno
- view
->offset
);
2573 wnoutrefresh(view
->win
);
2582 static void search_view(struct view
*view
, enum request request
);
2585 grep_text(struct view
*view
, const char *text
[])
2590 for (i
= 0; text
[i
]; i
++)
2592 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
2598 select_view_line(struct view
*view
, unsigned long lineno
)
2600 unsigned long old_lineno
= view
->lineno
;
2601 unsigned long old_offset
= view
->offset
;
2603 if (goto_view_line(view
, view
->offset
, lineno
)) {
2604 if (view_is_displayed(view
)) {
2605 if (old_offset
!= view
->offset
) {
2608 draw_view_line(view
, old_lineno
- view
->offset
);
2609 draw_view_line(view
, view
->lineno
- view
->offset
);
2610 wnoutrefresh(view
->win
);
2613 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2619 find_next(struct view
*view
, enum request request
)
2621 unsigned long lineno
= view
->lineno
;
2626 report("No previous search");
2628 search_view(view
, request
);
2638 case REQ_SEARCH_BACK
:
2647 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
2648 lineno
+= direction
;
2650 /* Note, lineno is unsigned long so will wrap around in which case it
2651 * will become bigger than view->lines. */
2652 for (; lineno
< view
->lines
; lineno
+= direction
) {
2653 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
2654 select_view_line(view
, lineno
);
2655 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
2660 report("No match found for '%s'", view
->grep
);
2664 search_view(struct view
*view
, enum request request
)
2669 regfree(view
->regex
);
2672 view
->regex
= calloc(1, sizeof(*view
->regex
));
2677 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
2678 if (regex_err
!= 0) {
2679 char buf
[SIZEOF_STR
] = "unknown error";
2681 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
2682 report("Search failed: %s", buf
);
2686 string_copy(view
->grep
, opt_search
);
2688 find_next(view
, request
);
2692 * Incremental updating
2696 reset_view(struct view
*view
)
2700 for (i
= 0; i
< view
->lines
; i
++)
2701 free(view
->line
[i
].data
);
2704 view
->p_offset
= view
->offset
;
2705 view
->p_yoffset
= view
->yoffset
;
2706 view
->p_lineno
= view
->lineno
;
2714 view
->update_secs
= 0;
2718 free_argv(const char *argv
[])
2722 for (argc
= 0; argv
[argc
]; argc
++)
2723 free((void *) argv
[argc
]);
2727 format_argv(const char *dst_argv
[], const char *src_argv
[], enum format_flags flags
)
2729 char buf
[SIZEOF_STR
];
2731 bool noreplace
= flags
== FORMAT_NONE
;
2733 free_argv(dst_argv
);
2735 for (argc
= 0; src_argv
[argc
]; argc
++) {
2736 const char *arg
= src_argv
[argc
];
2740 char *next
= strstr(arg
, "%(");
2741 int len
= next
- arg
;
2744 if (!next
|| noreplace
) {
2745 if (flags
== FORMAT_DASH
&& !strcmp(arg
, "--"))
2750 } else if (!prefixcmp(next
, "%(directory)")) {
2753 } else if (!prefixcmp(next
, "%(file)")) {
2756 } else if (!prefixcmp(next
, "%(ref)")) {
2757 value
= *opt_ref
? opt_ref
: "HEAD";
2759 } else if (!prefixcmp(next
, "%(head)")) {
2762 } else if (!prefixcmp(next
, "%(commit)")) {
2765 } else if (!prefixcmp(next
, "%(blob)")) {
2769 report("Unknown replacement: `%s`", next
);
2773 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
2776 arg
= next
&& !noreplace
? strchr(next
, ')') + 1 : NULL
;
2779 dst_argv
[argc
] = strdup(buf
);
2780 if (!dst_argv
[argc
])
2784 dst_argv
[argc
] = NULL
;
2786 return src_argv
[argc
] == NULL
;
2790 restore_view_position(struct view
*view
)
2792 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
2795 /* Changing the view position cancels the restoring. */
2796 /* FIXME: Changing back to the first line is not detected. */
2797 if (view
->offset
!= 0 || view
->lineno
!= 0) {
2798 view
->p_restore
= FALSE
;
2802 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
2803 view_is_displayed(view
))
2806 view
->yoffset
= view
->p_yoffset
;
2807 view
->p_restore
= FALSE
;
2813 end_update(struct view
*view
, bool force
)
2817 while (!view
->ops
->read(view
, NULL
))
2820 set_nonblocking_input(FALSE
);
2822 kill_io(view
->pipe
);
2823 done_io(view
->pipe
);
2828 setup_update(struct view
*view
, const char *vid
)
2830 set_nonblocking_input(TRUE
);
2832 string_copy_rev(view
->vid
, vid
);
2833 view
->pipe
= &view
->io
;
2834 view
->start_time
= time(NULL
);
2838 prepare_update(struct view
*view
, const char *argv
[], const char *dir
,
2839 enum format_flags flags
)
2842 end_update(view
, TRUE
);
2843 return init_io_rd(&view
->io
, argv
, dir
, flags
);
2847 prepare_update_file(struct view
*view
, const char *name
)
2850 end_update(view
, TRUE
);
2851 return io_open(&view
->io
, name
);
2855 begin_update(struct view
*view
, bool refresh
)
2858 end_update(view
, TRUE
);
2861 if (!start_io(&view
->io
))
2865 if (view
== VIEW(REQ_VIEW_TREE
) && strcmp(view
->vid
, view
->id
))
2868 if (!run_io_rd(&view
->io
, view
->ops
->argv
, FORMAT_ALL
))
2871 /* Put the current ref_* value to the view title ref
2872 * member. This is needed by the blob view. Most other
2873 * views sets it automatically after loading because the
2874 * first line is a commit line. */
2875 string_copy_rev(view
->ref
, view
->id
);
2878 setup_update(view
, view
->id
);
2884 update_view(struct view
*view
)
2886 char out_buffer
[BUFSIZ
* 2];
2888 /* Clear the view and redraw everything since the tree sorting
2889 * might have rearranged things. */
2890 bool redraw
= view
->lines
== 0;
2891 bool can_read
= TRUE
;
2896 if (!io_can_read(view
->pipe
)) {
2897 if (view
->lines
== 0 && view_is_displayed(view
)) {
2898 time_t secs
= time(NULL
) - view
->start_time
;
2900 if (secs
> 1 && secs
> view
->update_secs
) {
2901 if (view
->update_secs
== 0)
2903 update_view_title(view
);
2904 view
->update_secs
= secs
;
2910 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
2911 if (opt_iconv
!= ICONV_NONE
) {
2912 ICONV_CONST
char *inbuf
= line
;
2913 size_t inlen
= strlen(line
) + 1;
2915 char *outbuf
= out_buffer
;
2916 size_t outlen
= sizeof(out_buffer
);
2920 ret
= iconv(opt_iconv
, &inbuf
, &inlen
, &outbuf
, &outlen
);
2921 if (ret
!= (size_t) -1)
2925 if (!view
->ops
->read(view
, line
)) {
2926 report("Allocation failure");
2927 end_update(view
, TRUE
);
2933 unsigned long lines
= view
->lines
;
2936 for (digits
= 0; lines
; digits
++)
2939 /* Keep the displayed view in sync with line number scaling. */
2940 if (digits
!= view
->digits
) {
2941 view
->digits
= digits
;
2942 if (opt_line_number
|| view
== VIEW(REQ_VIEW_BLAME
))
2947 if (io_error(view
->pipe
)) {
2948 report("Failed to read: %s", io_strerror(view
->pipe
));
2949 end_update(view
, TRUE
);
2951 } else if (io_eof(view
->pipe
)) {
2953 end_update(view
, FALSE
);
2956 if (restore_view_position(view
))
2959 if (!view_is_displayed(view
))
2963 redraw_view_from(view
, 0);
2965 redraw_view_dirty(view
);
2967 /* Update the title _after_ the redraw so that if the redraw picks up a
2968 * commit reference in view->ref it'll be available here. */
2969 update_view_title(view
);
2973 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
2975 static struct line
*
2976 add_line_data(struct view
*view
, void *data
, enum line_type type
)
2980 if (!realloc_lines(&view
->line
, view
->lines
, 1))
2983 line
= &view
->line
[view
->lines
++];
2984 memset(line
, 0, sizeof(*line
));
2992 static struct line
*
2993 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
2995 char *data
= text
? strdup(text
) : NULL
;
2997 return data
? add_line_data(view
, data
, type
) : NULL
;
3000 static struct line
*
3001 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3003 char buf
[SIZEOF_STR
];
3006 va_start(args
, fmt
);
3007 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3011 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3019 OPEN_DEFAULT
= 0, /* Use default view switching. */
3020 OPEN_SPLIT
= 1, /* Split current view. */
3021 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3022 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3023 OPEN_PREPARED
= 32, /* Open already prepared command. */
3027 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3029 bool split
= !!(flags
& OPEN_SPLIT
);
3030 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3031 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3032 struct view
*view
= VIEW(request
);
3033 int nviews
= displayed_views();
3034 struct view
*base_view
= display
[0];
3036 if (view
== prev
&& nviews
== 1 && !reload
) {
3037 report("Already in %s view", view
->name
);
3041 if (view
->git_dir
&& !opt_git_dir
[0]) {
3042 report("The %s view is disabled in pager view", view
->name
);
3049 } else if (!nomaximize
) {
3050 /* Maximize the current view. */
3051 memset(display
, 0, sizeof(display
));
3053 display
[current_view
] = view
;
3056 /* Resize the view when switching between split- and full-screen,
3057 * or when switching between two different full-screen views. */
3058 if (nviews
!= displayed_views() ||
3059 (nviews
== 1 && base_view
!= display
[0]))
3062 if (view
->ops
->open
) {
3064 end_update(view
, TRUE
);
3065 if (!view
->ops
->open(view
)) {
3066 report("Failed to load %s view", view
->name
);
3069 restore_view_position(view
);
3071 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3072 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3073 report("Failed to load %s view", view
->name
);
3077 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3078 /* Take the title line into account. */
3079 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3081 /* Scroll the view that was split if the current line is
3082 * outside the new limited view. */
3083 do_scroll_view(prev
, lines
);
3086 if (prev
&& view
!= prev
) {
3088 /* "Blur" the previous view. */
3089 update_view_title(prev
);
3092 view
->parent
= prev
;
3095 if (view
->pipe
&& view
->lines
== 0) {
3096 /* Clear the old view and let the incremental updating refill
3099 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3101 } else if (view_is_displayed(view
)) {
3108 open_external_viewer(const char *argv
[], const char *dir
)
3110 def_prog_mode(); /* save current tty modes */
3111 endwin(); /* restore original tty modes */
3112 run_io_fg(argv
, dir
);
3113 fprintf(stderr
, "Press Enter to continue");
3116 redraw_display(TRUE
);
3120 open_mergetool(const char *file
)
3122 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3124 open_external_viewer(mergetool_argv
, opt_cdup
);
3128 open_editor(bool from_root
, const char *file
)
3130 const char *editor_argv
[] = { "vi", file
, NULL
};
3133 editor
= getenv("GIT_EDITOR");
3134 if (!editor
&& *opt_editor
)
3135 editor
= opt_editor
;
3137 editor
= getenv("VISUAL");
3139 editor
= getenv("EDITOR");
3143 editor_argv
[0] = editor
;
3144 open_external_viewer(editor_argv
, from_root
? opt_cdup
: NULL
);
3148 open_run_request(enum request request
)
3150 struct run_request
*req
= get_run_request(request
);
3151 const char *argv
[ARRAY_SIZE(req
->argv
)] = { NULL
};
3154 report("Unknown run request");
3158 if (format_argv(argv
, req
->argv
, FORMAT_ALL
))
3159 open_external_viewer(argv
, NULL
);
3164 * User request switch noodle
3168 view_driver(struct view
*view
, enum request request
)
3172 if (request
== REQ_NONE
)
3175 if (request
> REQ_NONE
) {
3176 open_run_request(request
);
3177 /* FIXME: When all views can refresh always do this. */
3178 if (view
== VIEW(REQ_VIEW_STATUS
) ||
3179 view
== VIEW(REQ_VIEW_MAIN
) ||
3180 view
== VIEW(REQ_VIEW_LOG
) ||
3181 view
== VIEW(REQ_VIEW_BRANCH
) ||
3182 view
== VIEW(REQ_VIEW_STAGE
))
3183 request
= REQ_REFRESH
;
3188 if (view
&& view
->lines
) {
3189 request
= view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
3190 if (request
== REQ_NONE
)
3197 case REQ_MOVE_PAGE_UP
:
3198 case REQ_MOVE_PAGE_DOWN
:
3199 case REQ_MOVE_FIRST_LINE
:
3200 case REQ_MOVE_LAST_LINE
:
3201 move_view(view
, request
);
3204 case REQ_SCROLL_LEFT
:
3205 case REQ_SCROLL_RIGHT
:
3206 case REQ_SCROLL_LINE_DOWN
:
3207 case REQ_SCROLL_LINE_UP
:
3208 case REQ_SCROLL_PAGE_DOWN
:
3209 case REQ_SCROLL_PAGE_UP
:
3210 scroll_view(view
, request
);
3213 case REQ_VIEW_BLAME
:
3215 report("No file chosen, press %s to open tree view",
3216 get_key(REQ_VIEW_TREE
));
3219 open_view(view
, request
, OPEN_DEFAULT
);
3224 report("No file chosen, press %s to open tree view",
3225 get_key(REQ_VIEW_TREE
));
3228 open_view(view
, request
, OPEN_DEFAULT
);
3231 case REQ_VIEW_PAGER
:
3232 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3233 report("No pager content, press %s to run command from prompt",
3234 get_key(REQ_PROMPT
));
3237 open_view(view
, request
, OPEN_DEFAULT
);
3240 case REQ_VIEW_STAGE
:
3241 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3242 report("No stage content, press %s to open the status view and choose file",
3243 get_key(REQ_VIEW_STATUS
));
3246 open_view(view
, request
, OPEN_DEFAULT
);
3249 case REQ_VIEW_STATUS
:
3250 if (opt_is_inside_work_tree
== FALSE
) {
3251 report("The status view requires a working tree");
3254 open_view(view
, request
, OPEN_DEFAULT
);
3262 case REQ_VIEW_BRANCH
:
3263 open_view(view
, request
, OPEN_DEFAULT
);
3268 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3270 if ((view
== VIEW(REQ_VIEW_DIFF
) &&
3271 view
->parent
== VIEW(REQ_VIEW_MAIN
)) ||
3272 (view
== VIEW(REQ_VIEW_DIFF
) &&
3273 view
->parent
== VIEW(REQ_VIEW_BLAME
)) ||
3274 (view
== VIEW(REQ_VIEW_STAGE
) &&
3275 view
->parent
== VIEW(REQ_VIEW_STATUS
)) ||
3276 (view
== VIEW(REQ_VIEW_BLOB
) &&
3277 view
->parent
== VIEW(REQ_VIEW_TREE
)) ||
3278 (view
== VIEW(REQ_VIEW_MAIN
) &&
3279 view
->parent
== VIEW(REQ_VIEW_BRANCH
))) {
3282 view
= view
->parent
;
3283 line
= view
->lineno
;
3284 move_view(view
, request
);
3285 if (view_is_displayed(view
))
3286 update_view_title(view
);
3287 if (line
!= view
->lineno
)
3288 view
->ops
->request(view
, REQ_ENTER
,
3289 &view
->line
[view
->lineno
]);
3292 move_view(view
, request
);
3298 int nviews
= displayed_views();
3299 int next_view
= (current_view
+ 1) % nviews
;
3301 if (next_view
== current_view
) {
3302 report("Only one view is displayed");
3306 current_view
= next_view
;
3307 /* Blur out the title of the previous view. */
3308 update_view_title(view
);
3313 report("Refreshing is not yet supported for the %s view", view
->name
);
3317 if (displayed_views() == 2)
3318 maximize_view(view
);
3325 case REQ_TOGGLE_LINENO
:
3326 toggle_view_option(&opt_line_number
, "line numbers");
3329 case REQ_TOGGLE_DATE
:
3330 toggle_view_option(&opt_date
, "date display");
3333 case REQ_TOGGLE_DATE_SHORT
:
3334 toggle_view_option(&opt_date_short
, "date shortening");
3337 case REQ_TOGGLE_AUTHOR
:
3338 toggle_view_option(&opt_author
, "author display");
3341 case REQ_TOGGLE_REV_GRAPH
:
3342 toggle_view_option(&opt_rev_graph
, "revision graph display");
3345 case REQ_TOGGLE_REFS
:
3346 toggle_view_option(&opt_show_refs
, "reference display");
3349 case REQ_TOGGLE_SORT_FIELD
:
3350 case REQ_TOGGLE_SORT_ORDER
:
3351 report("Sorting is not yet supported for the %s view", view
->name
);
3355 case REQ_SEARCH_BACK
:
3356 search_view(view
, request
);
3361 find_next(view
, request
);
3364 case REQ_STOP_LOADING
:
3365 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
3368 report("Stopped loading the %s view", view
->name
),
3369 end_update(view
, TRUE
);
3373 case REQ_SHOW_VERSION
:
3374 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3377 case REQ_SCREEN_REDRAW
:
3378 redraw_display(TRUE
);
3382 report("Nothing to edit");
3386 report("Nothing to enter");
3389 case REQ_VIEW_CLOSE
:
3390 /* XXX: Mark closed views by letting view->parent point to the
3391 * view itself. Parents to closed view should never be
3394 view
->parent
->parent
!= view
->parent
) {
3395 maximize_view(view
->parent
);
3396 view
->parent
= view
;
3404 report("Unknown key, press 'h' for help");
3413 * View backend utilities
3423 const enum sort_field
*fields
;
3424 size_t size
, current
;
3428 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3429 #define get_sort_field(state) ((state).fields[(state).current])
3430 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3433 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3434 int (*compare
)(const void *, const void *))
3437 case REQ_TOGGLE_SORT_FIELD
:
3438 state
->current
= (state
->current
+ 1) % state
->size
;
3441 case REQ_TOGGLE_SORT_ORDER
:
3442 state
->reverse
= !state
->reverse
;
3445 die("Not a sort request");
3448 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3452 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3454 /* Small author cache to reduce memory consumption. It uses binary
3455 * search to lookup or find place to position new entries. No entries
3456 * are ever freed. */
3458 get_author(const char *name
)
3460 static const char **authors
;
3461 static size_t authors_size
;
3462 int from
= 0, to
= authors_size
- 1;
3464 while (from
<= to
) {
3465 size_t pos
= (to
+ from
) / 2;
3466 int cmp
= strcmp(name
, authors
[pos
]);
3469 return authors
[pos
];
3477 if (!realloc_authors(&authors
, authors_size
, 1))
3479 name
= strdup(name
);
3483 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3484 authors
[from
] = name
;
3491 parse_timezone(time_t *time
, const char *zone
)
3495 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3496 tz
+= ('0' - zone
[2]) * 60 * 60;
3497 tz
+= ('0' - zone
[3]) * 60;
3498 tz
+= ('0' - zone
[4]);
3506 /* Parse author lines where the name may be empty:
3507 * author <email@address.tld> 1138474660 +0100
3510 parse_author_line(char *ident
, const char **author
, time_t *time
)
3512 char *nameend
= strchr(ident
, '<');
3513 char *emailend
= strchr(ident
, '>');
3515 if (nameend
&& emailend
)
3516 *nameend
= *emailend
= 0;
3517 ident
= chomp_string(ident
);
3520 ident
= chomp_string(nameend
+ 1);
3525 *author
= get_author(ident
);
3527 /* Parse epoch and timezone */
3528 if (emailend
&& emailend
[1] == ' ') {
3529 char *secs
= emailend
+ 2;
3530 char *zone
= strchr(secs
, ' ');
3532 *time
= (time_t) atol(secs
);
3534 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
3535 parse_timezone(time
, zone
+ 1);
3540 open_commit_parent_menu(char buf
[SIZEOF_STR
], int *parents
)
3542 char rev
[SIZEOF_REV
];
3543 const char *revlist_argv
[] = {
3544 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev
, NULL
3546 struct menu_item
*items
;
3547 char text
[SIZEOF_STR
];
3551 items
= calloc(*parents
+ 1, sizeof(*items
));
3555 for (i
= 0; i
< *parents
; i
++) {
3556 string_copy_rev(rev
, &buf
[SIZEOF_REV
* i
]);
3557 if (!run_io_buf(revlist_argv
, text
, sizeof(text
)) ||
3558 !(items
[i
].text
= strdup(text
))) {
3566 ok
= prompt_menu("Select parent", items
, parents
);
3568 for (i
= 0; items
[i
].text
; i
++)
3569 free((char *) items
[i
].text
);
3575 select_commit_parent(const char *id
, char rev
[SIZEOF_REV
], const char *path
)
3577 char buf
[SIZEOF_STR
* 4];
3578 const char *revlist_argv
[] = {
3579 "git", "log", "--no-color", "-1",
3580 "--pretty=format:%P", id
, "--", path
, NULL
3584 if (!run_io_buf(revlist_argv
, buf
, sizeof(buf
)) ||
3585 (parents
= strlen(buf
) / 40) < 0) {
3586 report("Failed to get parent information");
3589 } else if (parents
== 0) {
3591 report("Path '%s' does not exist in the parent", path
);
3593 report("The selected commit has no parents");
3597 if (parents
> 1 && !open_commit_parent_menu(buf
, &parents
))
3600 string_copy_rev(rev
, &buf
[41 * parents
]);
3609 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
3611 char text
[SIZEOF_STR
];
3613 if (opt_line_number
&& draw_lineno(view
, lineno
))
3616 string_expand(text
, sizeof(text
), line
->data
, opt_tab_size
);
3617 draw_text(view
, line
->type
, text
, TRUE
);
3622 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
3624 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
3625 char ref
[SIZEOF_STR
];
3627 if (!run_io_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
3630 /* This is the only fatal call, since it can "corrupt" the buffer. */
3631 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
3638 add_pager_refs(struct view
*view
, struct line
*line
)
3640 char buf
[SIZEOF_STR
];
3641 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
3642 struct ref_list
*list
;
3643 size_t bufpos
= 0, i
;
3644 const char *sep
= "Refs: ";
3645 bool is_tag
= FALSE
;
3647 assert(line
->type
== LINE_COMMIT
);
3649 list
= get_ref_list(commit_id
);
3651 if (view
== VIEW(REQ_VIEW_DIFF
))
3652 goto try_add_describe_ref
;
3656 for (i
= 0; i
< list
->size
; i
++) {
3657 struct ref
*ref
= list
->refs
[i
];
3658 const char *fmt
= ref
->tag
? "%s[%s]" :
3659 ref
->remote
? "%s<%s>" : "%s%s";
3661 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
3668 if (!is_tag
&& view
== VIEW(REQ_VIEW_DIFF
)) {
3669 try_add_describe_ref
:
3670 /* Add <tag>-g<commit_id> "fake" reference. */
3671 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
3678 add_line_text(view
, buf
, LINE_PP_REFS
);
3682 pager_read(struct view
*view
, char *data
)
3689 line
= add_line_text(view
, data
, get_line_type(data
));
3693 if (line
->type
== LINE_COMMIT
&&
3694 (view
== VIEW(REQ_VIEW_DIFF
) ||
3695 view
== VIEW(REQ_VIEW_LOG
)))
3696 add_pager_refs(view
, line
);
3702 pager_request(struct view
*view
, enum request request
, struct line
*line
)
3706 if (request
!= REQ_ENTER
)
3709 if (line
->type
== LINE_COMMIT
&&
3710 (view
== VIEW(REQ_VIEW_LOG
) ||
3711 view
== VIEW(REQ_VIEW_PAGER
))) {
3712 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
3716 /* Always scroll the view even if it was split. That way
3717 * you can use Enter to scroll through the log view and
3718 * split open each commit diff. */
3719 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
3721 /* FIXME: A minor workaround. Scrolling the view will call report("")
3722 * but if we are scrolling a non-current view this won't properly
3723 * update the view title. */
3725 update_view_title(view
);
3731 pager_grep(struct view
*view
, struct line
*line
)
3733 const char *text
[] = { line
->data
, NULL
};
3735 return grep_text(view
, text
);
3739 pager_select(struct view
*view
, struct line
*line
)
3741 if (line
->type
== LINE_COMMIT
) {
3742 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
3744 if (view
!= VIEW(REQ_VIEW_PAGER
))
3745 string_copy_rev(view
->ref
, text
);
3746 string_copy_rev(ref_commit
, text
);
3750 static struct view_ops pager_ops
= {
3761 static const char *log_argv
[SIZEOF_ARG
] = {
3762 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3766 log_request(struct view
*view
, enum request request
, struct line
*line
)
3771 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
3774 return pager_request(view
, request
, line
);
3778 static struct view_ops log_ops
= {
3789 static const char *diff_argv
[SIZEOF_ARG
] = {
3790 "git", "show", "--pretty=fuller", "--no-color", "--root",
3791 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3794 static struct view_ops diff_ops
= {
3810 help_open(struct view
*view
)
3812 char buf
[SIZEOF_STR
];
3816 if (view
->lines
> 0)
3819 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
3821 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
3824 if (req_info
[i
].request
== REQ_NONE
)
3827 if (!req_info
[i
].request
) {
3828 add_line_text(view
, "", LINE_DEFAULT
);
3829 add_line_text(view
, req_info
[i
].help
, LINE_DEFAULT
);
3833 key
= get_key(req_info
[i
].request
);
3835 key
= "(no key defined)";
3837 for (bufpos
= 0; bufpos
<= req_info
[i
].namelen
; bufpos
++) {
3838 buf
[bufpos
] = tolower(req_info
[i
].name
[bufpos
]);
3839 if (buf
[bufpos
] == '_')
3843 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s",
3844 key
, buf
, req_info
[i
].help
);
3848 add_line_text(view
, "", LINE_DEFAULT
);
3849 add_line_text(view
, "External commands:", LINE_DEFAULT
);
3852 for (i
= 0; i
< run_requests
; i
++) {
3853 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
3860 key
= get_key_name(req
->key
);
3862 key
= "(no key defined)";
3864 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
3865 if (!string_format_from(buf
, &bufpos
, "%s%s",
3866 argc
? " " : "", req
->argv
[argc
]))
3869 add_line_format(view
, LINE_DEFAULT
, " %-10s %-14s `%s`",
3870 keymap_table
[req
->keymap
].name
, key
, buf
);
3876 static struct view_ops help_ops
= {
3892 struct tree_stack_entry
{
3893 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
3894 unsigned long lineno
; /* Line number to restore */
3895 char *name
; /* Position of name in opt_path */
3898 /* The top of the path stack. */
3899 static struct tree_stack_entry
*tree_stack
= NULL
;
3900 unsigned long tree_lineno
= 0;
3903 pop_tree_stack_entry(void)
3905 struct tree_stack_entry
*entry
= tree_stack
;
3907 tree_lineno
= entry
->lineno
;
3909 tree_stack
= entry
->prev
;
3914 push_tree_stack_entry(const char *name
, unsigned long lineno
)
3916 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
3917 size_t pathlen
= strlen(opt_path
);
3922 entry
->prev
= tree_stack
;
3923 entry
->name
= opt_path
+ pathlen
;
3926 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
3927 pop_tree_stack_entry();
3931 /* Move the current line to the first tree entry. */
3933 entry
->lineno
= lineno
;
3936 /* Parse output from git-ls-tree(1):
3938 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3941 #define SIZEOF_TREE_ATTR \
3942 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3944 #define SIZEOF_TREE_MODE \
3945 STRING_SIZE("100644 ")
3947 #define TREE_ID_OFFSET \
3948 STRING_SIZE("100644 blob ")
3951 char id
[SIZEOF_REV
];
3953 time_t time
; /* Date from the author ident. */
3954 const char *author
; /* Author of the commit. */
3959 tree_path(const struct line
*line
)
3961 return ((struct tree_entry
*) line
->data
)->name
;
3965 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
3967 if (line1
->type
!= line2
->type
)
3968 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
3969 return strcmp(tree_path(line1
), tree_path(line2
));
3972 static const enum sort_field tree_sort_fields
[] = {
3973 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
3975 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
3978 tree_compare(const void *l1
, const void *l2
)
3980 const struct line
*line1
= (const struct line
*) l1
;
3981 const struct line
*line2
= (const struct line
*) l2
;
3982 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
3983 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
3985 if (line1
->type
== LINE_TREE_HEAD
)
3987 if (line2
->type
== LINE_TREE_HEAD
)
3990 switch (get_sort_field(tree_sort_state
)) {
3992 return sort_order(tree_sort_state
, entry1
->time
- entry2
->time
);
3994 case ORDERBY_AUTHOR
:
3995 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
3999 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4004 static struct line
*
4005 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4006 const char *mode
, const char *id
)
4008 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4009 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4011 if (!entry
|| !line
) {
4016 strncpy(entry
->name
, path
, strlen(path
));
4018 entry
->mode
= strtoul(mode
, NULL
, 8);
4020 string_copy_rev(entry
->id
, id
);
4026 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4028 static const char *author_name
;
4029 static time_t author_time
;
4031 if (!text
&& *read_date
) {
4036 char *path
= *opt_path
? opt_path
: ".";
4037 /* Find next entry to process */
4038 const char *log_file
[] = {
4039 "git", "log", "--no-color", "--pretty=raw",
4040 "--cc", "--raw", view
->id
, "--", path
, NULL
4045 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4046 report("Tree is empty");
4050 if (!run_io_rd(&io
, log_file
, FORMAT_NONE
)) {
4051 report("Failed to load tree data");
4055 done_io(view
->pipe
);
4060 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4061 parse_author_line(text
+ STRING_SIZE("author "),
4062 &author_name
, &author_time
);
4064 } else if (*text
== ':') {
4066 size_t annotated
= 1;
4069 pos
= strchr(text
, '\t');
4073 if (*opt_prefix
&& !strncmp(text
, opt_prefix
, strlen(opt_prefix
)))
4074 text
+= strlen(opt_prefix
);
4075 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4076 text
+= strlen(opt_path
);
4077 pos
= strchr(text
, '/');
4081 for (i
= 1; i
< view
->lines
; i
++) {
4082 struct line
*line
= &view
->line
[i
];
4083 struct tree_entry
*entry
= line
->data
;
4085 annotated
+= !!entry
->author
;
4086 if (entry
->author
|| strcmp(entry
->name
, text
))
4089 entry
->author
= author_name
;
4090 entry
->time
= author_time
;
4095 if (annotated
== view
->lines
)
4096 kill_io(view
->pipe
);
4102 tree_read(struct view
*view
, char *text
)
4104 static bool read_date
= FALSE
;
4105 struct tree_entry
*data
;
4106 struct line
*entry
, *line
;
4107 enum line_type type
;
4108 size_t textlen
= text
? strlen(text
) : 0;
4109 char *path
= text
+ SIZEOF_TREE_ATTR
;
4111 if (read_date
|| !text
)
4112 return tree_read_date(view
, text
, &read_date
);
4114 if (textlen
<= SIZEOF_TREE_ATTR
)
4116 if (view
->lines
== 0 &&
4117 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4120 /* Strip the path part ... */
4122 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4123 size_t striplen
= strlen(opt_path
);
4125 if (pathlen
> striplen
)
4126 memmove(path
, path
+ striplen
,
4127 pathlen
- striplen
+ 1);
4129 /* Insert "link" to parent directory. */
4130 if (view
->lines
== 1 &&
4131 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4135 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4136 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4141 /* Skip "Directory ..." and ".." line. */
4142 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4143 if (tree_compare_entry(line
, entry
) <= 0)
4146 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4150 for (; line
<= entry
; line
++)
4151 line
->dirty
= line
->cleareol
= 1;
4155 if (tree_lineno
> view
->lineno
) {
4156 view
->lineno
= tree_lineno
;
4164 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4166 struct tree_entry
*entry
= line
->data
;
4168 if (line
->type
== LINE_TREE_HEAD
) {
4169 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4172 if (draw_mode(view
, entry
->mode
))
4175 if (opt_author
&& draw_author(view
, entry
->author
))
4178 if (opt_date
&& draw_date(view
, entry
->author
? &entry
->time
: NULL
))
4181 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4189 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4190 int fd
= mkstemp(file
);
4193 report("Failed to create temporary file");
4194 else if (!run_io_append(blob_ops
.argv
, FORMAT_ALL
, fd
))
4195 report("Failed to save blob data to file");
4197 open_editor(FALSE
, file
);
4203 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4205 enum open_flags flags
;
4208 case REQ_VIEW_BLAME
:
4209 if (line
->type
!= LINE_TREE_FILE
) {
4210 report("Blame only supported for files");
4214 string_copy(opt_ref
, view
->vid
);
4218 if (line
->type
!= LINE_TREE_FILE
) {
4219 report("Edit only supported for files");
4220 } else if (!is_head_commit(view
->vid
)) {
4223 open_editor(TRUE
, opt_file
);
4227 case REQ_TOGGLE_SORT_FIELD
:
4228 case REQ_TOGGLE_SORT_ORDER
:
4229 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4234 /* quit view if at top of tree */
4235 return REQ_VIEW_CLOSE
;
4238 line
= &view
->line
[1];
4248 /* Cleanup the stack if the tree view is at a different tree. */
4249 while (!*opt_path
&& tree_stack
)
4250 pop_tree_stack_entry();
4252 switch (line
->type
) {
4254 /* Depending on whether it is a subdirectory or parent link
4255 * mangle the path buffer. */
4256 if (line
== &view
->line
[1] && *opt_path
) {
4257 pop_tree_stack_entry();
4260 const char *basename
= tree_path(line
);
4262 push_tree_stack_entry(basename
, view
->lineno
);
4265 /* Trees and subtrees share the same ID, so they are not not
4266 * unique like blobs. */
4267 flags
= OPEN_RELOAD
;
4268 request
= REQ_VIEW_TREE
;
4271 case LINE_TREE_FILE
:
4272 flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4273 request
= REQ_VIEW_BLOB
;
4280 open_view(view
, request
, flags
);
4281 if (request
== REQ_VIEW_TREE
)
4282 view
->lineno
= tree_lineno
;
4288 tree_grep(struct view
*view
, struct line
*line
)
4290 struct tree_entry
*entry
= line
->data
;
4291 const char *text
[] = {
4293 opt_author
? entry
->author
: "",
4294 opt_date
? mkdate(&entry
->time
) : "",
4298 return grep_text(view
, text
);
4302 tree_select(struct view
*view
, struct line
*line
)
4304 struct tree_entry
*entry
= line
->data
;
4306 if (line
->type
== LINE_TREE_FILE
) {
4307 string_copy_rev(ref_blob
, entry
->id
);
4308 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4310 } else if (line
->type
!= LINE_TREE_DIR
) {
4314 string_copy_rev(view
->ref
, entry
->id
);
4317 static const char *tree_argv
[SIZEOF_ARG
] = {
4318 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4321 static struct view_ops tree_ops
= {
4333 blob_read(struct view
*view
, char *line
)
4337 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4341 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4348 return pager_request(view
, request
, line
);
4352 static const char *blob_argv
[SIZEOF_ARG
] = {
4353 "git", "cat-file", "blob", "%(blob)", NULL
4356 static struct view_ops blob_ops
= {
4370 * Loading the blame view is a two phase job:
4372 * 1. File content is read either using opt_file from the
4373 * filesystem or using git-cat-file.
4374 * 2. Then blame information is incrementally added by
4375 * reading output from git-blame.
4378 static const char *blame_head_argv
[] = {
4379 "git", "blame", "--incremental", "--", "%(file)", NULL
4382 static const char *blame_ref_argv
[] = {
4383 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4386 static const char *blame_cat_file_argv
[] = {
4387 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4390 struct blame_commit
{
4391 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4392 char title
[128]; /* First line of the commit message. */
4393 const char *author
; /* Author of the commit. */
4394 time_t time
; /* Date from the author ident. */
4395 char filename
[128]; /* Name of file. */
4396 bool has_previous
; /* Was a "previous" line detected. */
4400 struct blame_commit
*commit
;
4401 unsigned long lineno
;
4406 blame_open(struct view
*view
)
4408 if (*opt_ref
|| !io_open(&view
->io
, opt_file
)) {
4409 if (!run_io_rd(&view
->io
, blame_cat_file_argv
, FORMAT_ALL
))
4413 setup_update(view
, opt_file
);
4414 string_format(view
->ref
, "%s ...", opt_file
);
4419 static struct blame_commit
*
4420 get_blame_commit(struct view
*view
, const char *id
)
4424 for (i
= 0; i
< view
->lines
; i
++) {
4425 struct blame
*blame
= view
->line
[i
].data
;
4430 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4431 return blame
->commit
;
4435 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4438 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4444 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4446 const char *pos
= *posref
;
4449 pos
= strchr(pos
+ 1, ' ');
4450 if (!pos
|| !isdigit(pos
[1]))
4452 *number
= atoi(pos
+ 1);
4453 if (*number
< min
|| *number
> max
)
4460 static struct blame_commit
*
4461 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4463 struct blame_commit
*commit
;
4464 struct blame
*blame
;
4465 const char *pos
= text
+ SIZEOF_REV
- 2;
4466 size_t orig_lineno
= 0;
4470 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4473 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4474 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4475 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4478 commit
= get_blame_commit(view
, text
);
4484 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4487 blame
->commit
= commit
;
4488 blame
->lineno
= orig_lineno
+ group
- 1;
4496 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4499 const char **argv
= *opt_ref
? blame_ref_argv
: blame_head_argv
;
4502 if (view
->lines
== 0 && !view
->parent
)
4503 die("No blame exist for %s", view
->vid
);
4505 if (view
->lines
== 0 || !run_io_rd(&io
, argv
, FORMAT_ALL
)) {
4506 report("Failed to load blame data");
4510 done_io(view
->pipe
);
4516 size_t linelen
= strlen(line
);
4517 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
4522 blame
->commit
= NULL
;
4523 strncpy(blame
->text
, line
, linelen
);
4524 blame
->text
[linelen
] = 0;
4525 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
4530 match_blame_header(const char *name
, char **line
)
4532 size_t namelen
= strlen(name
);
4533 bool matched
= !strncmp(name
, *line
, namelen
);
4542 blame_read(struct view
*view
, char *line
)
4544 static struct blame_commit
*commit
= NULL
;
4545 static int blamed
= 0;
4546 static bool read_file
= TRUE
;
4549 return blame_read_file(view
, line
, &read_file
);
4556 string_format(view
->ref
, "%s", view
->vid
);
4557 if (view_is_displayed(view
)) {
4558 update_view_title(view
);
4559 redraw_view_from(view
, 0);
4565 commit
= parse_blame_commit(view
, line
, &blamed
);
4566 string_format(view
->ref
, "%s %2d%%", view
->vid
,
4567 view
->lines
? blamed
* 100 / view
->lines
: 0);
4569 } else if (match_blame_header("author ", &line
)) {
4570 commit
->author
= get_author(line
);
4572 } else if (match_blame_header("author-time ", &line
)) {
4573 commit
->time
= (time_t) atol(line
);
4575 } else if (match_blame_header("author-tz ", &line
)) {
4576 parse_timezone(&commit
->time
, line
);
4578 } else if (match_blame_header("summary ", &line
)) {
4579 string_ncopy(commit
->title
, line
, strlen(line
));
4581 } else if (match_blame_header("previous ", &line
)) {
4582 commit
->has_previous
= TRUE
;
4584 } else if (match_blame_header("filename ", &line
)) {
4585 string_ncopy(commit
->filename
, line
, strlen(line
));
4593 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4595 struct blame
*blame
= line
->data
;
4596 time_t *time
= NULL
;
4597 const char *id
= NULL
, *author
= NULL
;
4598 char text
[SIZEOF_STR
];
4600 if (blame
->commit
&& *blame
->commit
->filename
) {
4601 id
= blame
->commit
->id
;
4602 author
= blame
->commit
->author
;
4603 time
= &blame
->commit
->time
;
4606 if (opt_date
&& draw_date(view
, time
))
4609 if (opt_author
&& draw_author(view
, author
))
4612 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
4615 if (draw_lineno(view
, lineno
))
4618 string_expand(text
, sizeof(text
), blame
->text
, opt_tab_size
);
4619 draw_text(view
, LINE_DEFAULT
, text
, TRUE
);
4624 check_blame_commit(struct blame
*blame
, bool check_null_id
)
4627 report("Commit data not loaded yet");
4628 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4629 report("No commit exist for the selected line");
4636 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
4638 const char *diff_tree_argv
[] = {
4639 "git", "diff-tree", "-U0", blame
->commit
->id
,
4640 "--", blame
->commit
->filename
, NULL
4643 int parent_lineno
= -1;
4644 int blamed_lineno
= -1;
4647 if (!run_io(&io
, diff_tree_argv
, NULL
, IO_RD
))
4650 while ((line
= io_get(&io
, '\n', TRUE
))) {
4652 char *pos
= strchr(line
, '+');
4654 parent_lineno
= atoi(line
+ 4);
4656 blamed_lineno
= atoi(pos
+ 1);
4658 } else if (*line
== '+' && parent_lineno
!= -1) {
4659 if (blame
->lineno
== blamed_lineno
- 1 &&
4660 !strcmp(blame
->text
, line
+ 1)) {
4661 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
4672 blame_request(struct view
*view
, enum request request
, struct line
*line
)
4674 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4675 struct blame
*blame
= line
->data
;
4678 case REQ_VIEW_BLAME
:
4679 if (check_blame_commit(blame
, TRUE
)) {
4680 string_copy(opt_ref
, blame
->commit
->id
);
4681 string_copy(opt_file
, blame
->commit
->filename
);
4683 view
->lineno
= blame
->lineno
;
4684 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
4689 if (check_blame_commit(blame
, TRUE
) &&
4690 select_commit_parent(blame
->commit
->id
, opt_ref
,
4691 blame
->commit
->filename
)) {
4692 string_copy(opt_file
, blame
->commit
->filename
);
4693 setup_blame_parent_line(view
, blame
);
4694 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
4699 if (!check_blame_commit(blame
, FALSE
))
4702 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
4703 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
4706 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
4707 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
4708 const char *diff_index_argv
[] = {
4709 "git", "diff-index", "--root", "--patch-with-stat",
4710 "-C", "-M", "HEAD", "--", view
->vid
, NULL
4713 if (!blame
->commit
->has_previous
) {
4714 diff_index_argv
[1] = "diff";
4715 diff_index_argv
[2] = "--no-color";
4716 diff_index_argv
[6] = "--";
4717 diff_index_argv
[7] = "/dev/null";
4720 if (!prepare_update(diff
, diff_index_argv
, NULL
, FORMAT_DASH
)) {
4721 report("Failed to allocate diff command");
4724 flags
|= OPEN_PREPARED
;
4727 open_view(view
, REQ_VIEW_DIFF
, flags
);
4728 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4729 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
4740 blame_grep(struct view
*view
, struct line
*line
)
4742 struct blame
*blame
= line
->data
;
4743 struct blame_commit
*commit
= blame
->commit
;
4744 const char *text
[] = {
4746 commit
? commit
->title
: "",
4747 commit
? commit
->id
: "",
4748 commit
&& opt_author
? commit
->author
: "",
4749 commit
&& opt_date
? mkdate(&commit
->time
) : "",
4753 return grep_text(view
, text
);
4757 blame_select(struct view
*view
, struct line
*line
)
4759 struct blame
*blame
= line
->data
;
4760 struct blame_commit
*commit
= blame
->commit
;
4765 if (!strcmp(commit
->id
, NULL_ID
))
4766 string_ncopy(ref_commit
, "HEAD", 4);
4768 string_copy_rev(ref_commit
, commit
->id
);
4771 static struct view_ops blame_ops
= {
4787 const char *author
; /* Author of the last commit. */
4788 time_t time
; /* Date of the last activity. */
4789 struct ref
*ref
; /* Name and commit ID information. */
4792 static const enum sort_field branch_sort_fields
[] = {
4793 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4795 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
4798 branch_compare(const void *l1
, const void *l2
)
4800 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
4801 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
4803 switch (get_sort_field(branch_sort_state
)) {
4805 return sort_order(branch_sort_state
, branch1
->time
- branch2
->time
);
4807 case ORDERBY_AUTHOR
:
4808 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
4812 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
4817 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4819 struct branch
*branch
= line
->data
;
4820 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
4822 if (opt_date
&& draw_date(view
, branch
->author
? &branch
->time
: NULL
))
4825 if (opt_author
&& draw_author(view
, branch
->author
))
4828 draw_text(view
, type
, branch
->ref
->name
, TRUE
);
4833 branch_request(struct view
*view
, enum request request
, struct line
*line
)
4838 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
4841 case REQ_TOGGLE_SORT_FIELD
:
4842 case REQ_TOGGLE_SORT_ORDER
:
4843 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
4847 open_view(view
, REQ_VIEW_MAIN
, OPEN_SPLIT
);
4856 branch_read(struct view
*view
, char *line
)
4858 static char id
[SIZEOF_REV
];
4859 struct branch
*reference
;
4865 switch (get_line_type(line
)) {
4867 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
4871 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
4872 struct branch
*branch
= view
->line
[i
].data
;
4874 if (strcmp(branch
->ref
->id
, id
))
4877 view
->line
[i
].dirty
= TRUE
;
4879 branch
->author
= reference
->author
;
4880 branch
->time
= reference
->time
;
4884 parse_author_line(line
+ STRING_SIZE("author "),
4885 &branch
->author
, &branch
->time
);
4897 branch_open_visitor(void *data
, struct ref
*ref
)
4899 struct view
*view
= data
;
4900 struct branch
*branch
;
4902 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
4905 branch
= calloc(1, sizeof(*branch
));
4910 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
4914 branch_open(struct view
*view
)
4916 const char *branch_log
[] = {
4917 "git", "log", "--no-color", "--pretty=raw",
4918 "--simplify-by-decoration", "--all", NULL
4921 if (!run_io_rd(&view
->io
, branch_log
, FORMAT_NONE
)) {
4922 report("Failed to load branch data");
4926 setup_update(view
, view
->id
);
4927 foreach_ref(branch_open_visitor
, view
);
4928 view
->p_restore
= TRUE
;
4934 branch_grep(struct view
*view
, struct line
*line
)
4936 struct branch
*branch
= line
->data
;
4937 const char *text
[] = {
4943 return grep_text(view
, text
);
4947 branch_select(struct view
*view
, struct line
*line
)
4949 struct branch
*branch
= line
->data
;
4951 string_copy_rev(view
->ref
, branch
->ref
->id
);
4952 string_copy_rev(ref_commit
, branch
->ref
->id
);
4953 string_copy_rev(ref_head
, branch
->ref
->id
);
4956 static struct view_ops branch_ops
= {
4975 char rev
[SIZEOF_REV
];
4976 char name
[SIZEOF_STR
];
4980 char rev
[SIZEOF_REV
];
4981 char name
[SIZEOF_STR
];
4985 static char status_onbranch
[SIZEOF_STR
];
4986 static struct status stage_status
;
4987 static enum line_type stage_line_type
;
4988 static size_t stage_chunks
;
4989 static int *stage_chunk
;
4991 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
4993 /* This should work even for the "On branch" line. */
4995 status_has_none(struct view
*view
, struct line
*line
)
4997 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5000 /* Get fields from the diff line:
5001 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5004 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5006 const char *old_mode
= buf
+ 1;
5007 const char *new_mode
= buf
+ 8;
5008 const char *old_rev
= buf
+ 15;
5009 const char *new_rev
= buf
+ 56;
5010 const char *status
= buf
+ 97;
5013 old_mode
[-1] != ':' ||
5014 new_mode
[-1] != ' ' ||
5015 old_rev
[-1] != ' ' ||
5016 new_rev
[-1] != ' ' ||
5020 file
->status
= *status
;
5022 string_copy_rev(file
->old
.rev
, old_rev
);
5023 string_copy_rev(file
->new.rev
, new_rev
);
5025 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5026 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5028 file
->old
.name
[0] = file
->new.name
[0] = 0;
5034 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5036 struct status
*unmerged
= NULL
;
5040 if (!run_io(&io
, argv
, NULL
, IO_RD
))
5043 add_line_data(view
, NULL
, type
);
5045 while ((buf
= io_get(&io
, 0, TRUE
))) {
5046 struct status
*file
= unmerged
;
5049 file
= calloc(1, sizeof(*file
));
5050 if (!file
|| !add_line_data(view
, file
, type
))
5054 /* Parse diff info part. */
5056 file
->status
= status
;
5058 string_copy(file
->old
.rev
, NULL_ID
);
5060 } else if (!file
->status
|| file
== unmerged
) {
5061 if (!status_get_diff(file
, buf
, strlen(buf
)))
5064 buf
= io_get(&io
, 0, TRUE
);
5068 /* Collapse all modified entries that follow an
5069 * associated unmerged entry. */
5070 if (unmerged
== file
) {
5071 unmerged
->status
= 'U';
5073 } else if (file
->status
== 'U') {
5078 /* Grab the old name for rename/copy. */
5079 if (!*file
->old
.name
&&
5080 (file
->status
== 'R' || file
->status
== 'C')) {
5081 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5083 buf
= io_get(&io
, 0, TRUE
);
5088 /* git-ls-files just delivers a NUL separated list of
5089 * file names similar to the second half of the
5090 * git-diff-* output. */
5091 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5092 if (!*file
->old
.name
)
5093 string_copy(file
->old
.name
, file
->new.name
);
5097 if (io_error(&io
)) {
5103 if (!view
->line
[view
->lines
- 1].data
)
5104 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5110 /* Don't show unmerged entries in the staged section. */
5111 static const char *status_diff_index_argv
[] = {
5112 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5113 "--cached", "-M", "HEAD", NULL
5116 static const char *status_diff_files_argv
[] = {
5117 "git", "diff-files", "-z", NULL
5120 static const char *status_list_other_argv
[] = {
5121 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5124 static const char *status_list_no_head_argv
[] = {
5125 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5128 static const char *update_index_argv
[] = {
5129 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5132 /* Restore the previous line number to stay in the context or select a
5133 * line with something that can be updated. */
5135 status_restore(struct view
*view
)
5137 if (view
->p_lineno
>= view
->lines
)
5138 view
->p_lineno
= view
->lines
- 1;
5139 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5141 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5144 /* If the above fails, always skip the "On branch" line. */
5145 if (view
->p_lineno
< view
->lines
)
5146 view
->lineno
= view
->p_lineno
;
5150 if (view
->lineno
< view
->offset
)
5151 view
->offset
= view
->lineno
;
5152 else if (view
->offset
+ view
->height
<= view
->lineno
)
5153 view
->offset
= view
->lineno
- view
->height
+ 1;
5155 view
->p_restore
= FALSE
;
5159 status_update_onbranch(void)
5161 static const char *paths
[][2] = {
5162 { "rebase-apply/rebasing", "Rebasing" },
5163 { "rebase-apply/applying", "Applying mailbox" },
5164 { "rebase-apply/", "Rebasing mailbox" },
5165 { "rebase-merge/interactive", "Interactive rebase" },
5166 { "rebase-merge/", "Rebase merge" },
5167 { "MERGE_HEAD", "Merging" },
5168 { "BISECT_LOG", "Bisecting" },
5169 { "HEAD", "On branch" },
5171 char buf
[SIZEOF_STR
];
5175 if (is_initial_commit()) {
5176 string_copy(status_onbranch
, "Initial commit");
5180 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5181 char *head
= opt_head
;
5183 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5184 lstat(buf
, &stat
) < 0)
5190 if (string_format(buf
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5191 io_open(&io
, buf
) &&
5192 io_read_buf(&io
, buf
, sizeof(buf
))) {
5194 if (!prefixcmp(head
, "refs/heads/"))
5195 head
+= STRING_SIZE("refs/heads/");
5199 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5200 string_copy(status_onbranch
, opt_head
);
5204 string_copy(status_onbranch
, "Not currently on any branch");
5207 /* First parse staged info using git-diff-index(1), then parse unstaged
5208 * info using git-diff-files(1), and finally untracked files using
5209 * git-ls-files(1). */
5211 status_open(struct view
*view
)
5215 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5216 status_update_onbranch();
5218 run_io_bg(update_index_argv
);
5220 if (is_initial_commit()) {
5221 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5223 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5227 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5228 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5231 /* Restore the exact position or use the specialized restore
5233 if (!view
->p_restore
)
5234 status_restore(view
);
5239 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5241 struct status
*status
= line
->data
;
5242 enum line_type type
;
5246 switch (line
->type
) {
5247 case LINE_STAT_STAGED
:
5248 type
= LINE_STAT_SECTION
;
5249 text
= "Changes to be committed:";
5252 case LINE_STAT_UNSTAGED
:
5253 type
= LINE_STAT_SECTION
;
5254 text
= "Changed but not updated:";
5257 case LINE_STAT_UNTRACKED
:
5258 type
= LINE_STAT_SECTION
;
5259 text
= "Untracked files:";
5262 case LINE_STAT_NONE
:
5263 type
= LINE_DEFAULT
;
5264 text
= " (no files)";
5267 case LINE_STAT_HEAD
:
5268 type
= LINE_STAT_HEAD
;
5269 text
= status_onbranch
;
5276 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5278 buf
[0] = status
->status
;
5279 if (draw_text(view
, line
->type
, buf
, TRUE
))
5281 type
= LINE_DEFAULT
;
5282 text
= status
->new.name
;
5285 draw_text(view
, type
, text
, TRUE
);
5290 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5292 if (displayed_views() == 2 || display
[current_view
] != view
)
5293 maximize_view(view
);
5294 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5299 status_enter(struct view
*view
, struct line
*line
)
5301 struct status
*status
= line
->data
;
5302 const char *oldpath
= status
? status
->old
.name
: NULL
;
5303 /* Diffs for unmerged entries are empty when passing the new
5304 * path, so leave it empty. */
5305 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5307 enum open_flags split
;
5308 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5310 if (line
->type
== LINE_STAT_NONE
||
5311 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5312 report("No file to diff");
5316 switch (line
->type
) {
5317 case LINE_STAT_STAGED
:
5318 if (is_initial_commit()) {
5319 const char *no_head_diff_argv
[] = {
5320 "git", "diff", "--no-color", "--patch-with-stat",
5321 "--", "/dev/null", newpath
, NULL
5324 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
, FORMAT_DASH
))
5325 return status_load_error(view
, stage
, newpath
);
5327 const char *index_show_argv
[] = {
5328 "git", "diff-index", "--root", "--patch-with-stat",
5329 "-C", "-M", "--cached", "HEAD", "--",
5330 oldpath
, newpath
, NULL
5333 if (!prepare_update(stage
, index_show_argv
, opt_cdup
, FORMAT_DASH
))
5334 return status_load_error(view
, stage
, newpath
);
5338 info
= "Staged changes to %s";
5340 info
= "Staged changes";
5343 case LINE_STAT_UNSTAGED
:
5345 const char *files_show_argv
[] = {
5346 "git", "diff-files", "--root", "--patch-with-stat",
5347 "-C", "-M", "--", oldpath
, newpath
, NULL
5350 if (!prepare_update(stage
, files_show_argv
, opt_cdup
, FORMAT_DASH
))
5351 return status_load_error(view
, stage
, newpath
);
5353 info
= "Unstaged changes to %s";
5355 info
= "Unstaged changes";
5358 case LINE_STAT_UNTRACKED
:
5360 report("No file to show");
5364 if (!suffixcmp(status
->new.name
, -1, "/")) {
5365 report("Cannot display a directory");
5369 if (!prepare_update_file(stage
, newpath
))
5370 return status_load_error(view
, stage
, newpath
);
5371 info
= "Untracked file %s";
5374 case LINE_STAT_HEAD
:
5378 die("line type %d not handled in switch", line
->type
);
5381 split
= view_is_displayed(view
) ? OPEN_SPLIT
: 0;
5382 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5383 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5385 stage_status
= *status
;
5387 memset(&stage_status
, 0, sizeof(stage_status
));
5390 stage_line_type
= line
->type
;
5392 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5399 status_exists(struct status
*status
, enum line_type type
)
5401 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5402 unsigned long lineno
;
5404 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5405 struct line
*line
= &view
->line
[lineno
];
5406 struct status
*pos
= line
->data
;
5408 if (line
->type
!= type
)
5410 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5411 select_view_line(view
, lineno
);
5414 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5415 select_view_line(view
, lineno
);
5425 status_update_prepare(struct io
*io
, enum line_type type
)
5427 const char *staged_argv
[] = {
5428 "git", "update-index", "-z", "--index-info", NULL
5430 const char *others_argv
[] = {
5431 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5435 case LINE_STAT_STAGED
:
5436 return run_io(io
, staged_argv
, opt_cdup
, IO_WR
);
5438 case LINE_STAT_UNSTAGED
:
5439 return run_io(io
, others_argv
, opt_cdup
, IO_WR
);
5441 case LINE_STAT_UNTRACKED
:
5442 return run_io(io
, others_argv
, NULL
, IO_WR
);
5445 die("line type %d not handled in switch", type
);
5451 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5453 char buf
[SIZEOF_STR
];
5457 case LINE_STAT_STAGED
:
5458 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5461 status
->old
.name
, 0))
5465 case LINE_STAT_UNSTAGED
:
5466 case LINE_STAT_UNTRACKED
:
5467 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
5472 die("line type %d not handled in switch", type
);
5475 return io_write(io
, buf
, bufsize
);
5479 status_update_file(struct status
*status
, enum line_type type
)
5484 if (!status_update_prepare(&io
, type
))
5487 result
= status_update_write(&io
, status
, type
);
5488 return done_io(&io
) && result
;
5492 status_update_files(struct view
*view
, struct line
*line
)
5494 char buf
[sizeof(view
->ref
)];
5497 struct line
*pos
= view
->line
+ view
->lines
;
5500 int cursor_y
= -1, cursor_x
= -1;
5502 if (!status_update_prepare(&io
, line
->type
))
5505 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
5508 string_copy(buf
, view
->ref
);
5509 getsyx(cursor_y
, cursor_x
);
5510 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
5511 int almost_done
= file
* 100 / files
;
5513 if (almost_done
> done
) {
5515 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
5517 update_view_title(view
);
5518 setsyx(cursor_y
, cursor_x
);
5521 result
= status_update_write(&io
, line
->data
, line
->type
);
5523 string_copy(view
->ref
, buf
);
5525 return done_io(&io
) && result
;
5529 status_update(struct view
*view
)
5531 struct line
*line
= &view
->line
[view
->lineno
];
5533 assert(view
->lines
);
5536 /* This should work even for the "On branch" line. */
5537 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
5538 report("Nothing to update");
5542 if (!status_update_files(view
, line
+ 1)) {
5543 report("Failed to update file status");
5547 } else if (!status_update_file(line
->data
, line
->type
)) {
5548 report("Failed to update file status");
5556 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
5558 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
5559 if (type
== LINE_STAT_STAGED
) {
5560 report("Cannot revert changes to staged files");
5561 } else if (type
== LINE_STAT_UNTRACKED
) {
5562 report("Cannot revert changes to untracked files");
5563 } else if (has_none
) {
5564 report("Nothing to revert");
5566 report("Cannot revert changes to multiple files");
5571 char mode
[10] = "100644";
5572 const char *reset_argv
[] = {
5573 "git", "update-index", "--cacheinfo", mode
,
5574 status
->old
.rev
, status
->old
.name
, NULL
5576 const char *checkout_argv
[] = {
5577 "git", "checkout", "--", status
->old
.name
, NULL
5580 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5582 string_format(mode
, "%o", status
->old
.mode
);
5583 return (status
->status
!= 'U' || run_io_fg(reset_argv
, opt_cdup
)) &&
5584 run_io_fg(checkout_argv
, opt_cdup
);
5589 status_request(struct view
*view
, enum request request
, struct line
*line
)
5591 struct status
*status
= line
->data
;
5594 case REQ_STATUS_UPDATE
:
5595 if (!status_update(view
))
5599 case REQ_STATUS_REVERT
:
5600 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
5604 case REQ_STATUS_MERGE
:
5605 if (!status
|| status
->status
!= 'U') {
5606 report("Merging only possible for files with unmerged status ('U').");
5609 open_mergetool(status
->new.name
);
5615 if (status
->status
== 'D') {
5616 report("File has been deleted.");
5620 open_editor(status
->status
!= '?', status
->new.name
);
5623 case REQ_VIEW_BLAME
:
5625 string_copy(opt_file
, status
->new.name
);
5631 /* After returning the status view has been split to
5632 * show the stage view. No further reloading is
5634 return status_enter(view
, line
);
5637 /* Simply reload the view. */
5644 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
5650 status_select(struct view
*view
, struct line
*line
)
5652 struct status
*status
= line
->data
;
5653 char file
[SIZEOF_STR
] = "all files";
5657 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
5660 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
5663 switch (line
->type
) {
5664 case LINE_STAT_STAGED
:
5665 text
= "Press %s to unstage %s for commit";
5668 case LINE_STAT_UNSTAGED
:
5669 text
= "Press %s to stage %s for commit";
5672 case LINE_STAT_UNTRACKED
:
5673 text
= "Press %s to stage %s for addition";
5676 case LINE_STAT_HEAD
:
5677 case LINE_STAT_NONE
:
5678 text
= "Nothing to update";
5682 die("line type %d not handled in switch", line
->type
);
5685 if (status
&& status
->status
== 'U') {
5686 text
= "Press %s to resolve conflict in %s";
5687 key
= get_key(REQ_STATUS_MERGE
);
5690 key
= get_key(REQ_STATUS_UPDATE
);
5693 string_format(view
->ref
, text
, key
, file
);
5697 status_grep(struct view
*view
, struct line
*line
)
5699 struct status
*status
= line
->data
;
5702 const char buf
[2] = { status
->status
, 0 };
5703 const char *text
[] = { status
->new.name
, buf
, NULL
};
5705 return grep_text(view
, text
);
5711 static struct view_ops status_ops
= {
5724 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
5726 while (line
< end
) {
5727 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
5728 !io_write(io
, "\n", 1))
5731 if (line
->type
== LINE_DIFF_CHUNK
||
5732 line
->type
== LINE_DIFF_HEADER
)
5739 static struct line
*
5740 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
5742 for (; view
->line
< line
; line
--)
5743 if (line
->type
== type
)
5750 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
5752 const char *apply_argv
[SIZEOF_ARG
] = {
5753 "git", "apply", "--whitespace=nowarn", NULL
5755 struct line
*diff_hdr
;
5759 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
5764 apply_argv
[argc
++] = "--cached";
5765 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
5766 apply_argv
[argc
++] = "-R";
5767 apply_argv
[argc
++] = "-";
5768 apply_argv
[argc
++] = NULL
;
5769 if (!run_io(&io
, apply_argv
, opt_cdup
, IO_WR
))
5772 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
5773 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
5777 run_io_bg(update_index_argv
);
5779 return chunk
? TRUE
: FALSE
;
5783 stage_update(struct view
*view
, struct line
*line
)
5785 struct line
*chunk
= NULL
;
5787 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
5788 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
5791 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
5792 report("Failed to apply chunk");
5796 } else if (!stage_status
.status
) {
5797 view
= VIEW(REQ_VIEW_STATUS
);
5799 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
5800 if (line
->type
== stage_line_type
)
5803 if (!status_update_files(view
, line
+ 1)) {
5804 report("Failed to update files");
5808 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
5809 report("Failed to update file");
5817 stage_revert(struct view
*view
, struct line
*line
)
5819 struct line
*chunk
= NULL
;
5821 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
5822 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
5825 if (!prompt_yesno("Are you sure you want to revert changes?"))
5828 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
5829 report("Failed to revert chunk");
5835 return status_revert(stage_status
.status
? &stage_status
: NULL
,
5836 stage_line_type
, FALSE
);
5842 stage_next(struct view
*view
, struct line
*line
)
5846 if (!stage_chunks
) {
5847 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
5848 if (line
->type
!= LINE_DIFF_CHUNK
)
5851 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
5852 report("Allocation failure");
5856 stage_chunk
[stage_chunks
++] = line
- view
->line
;
5860 for (i
= 0; i
< stage_chunks
; i
++) {
5861 if (stage_chunk
[i
] > view
->lineno
) {
5862 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
5863 report("Chunk %d of %d", i
+ 1, stage_chunks
);
5868 report("No next chunk found");
5872 stage_request(struct view
*view
, enum request request
, struct line
*line
)
5875 case REQ_STATUS_UPDATE
:
5876 if (!stage_update(view
, line
))
5880 case REQ_STATUS_REVERT
:
5881 if (!stage_revert(view
, line
))
5885 case REQ_STAGE_NEXT
:
5886 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
5887 report("File is untracked; press %s to add",
5888 get_key(REQ_STATUS_UPDATE
));
5891 stage_next(view
, line
);
5895 if (!stage_status
.new.name
[0])
5897 if (stage_status
.status
== 'D') {
5898 report("File has been deleted.");
5902 open_editor(stage_status
.status
!= '?', stage_status
.new.name
);
5906 /* Reload everything ... */
5909 case REQ_VIEW_BLAME
:
5910 if (stage_status
.new.name
[0]) {
5911 string_copy(opt_file
, stage_status
.new.name
);
5917 return pager_request(view
, request
, line
);
5923 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
5924 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
5926 /* Check whether the staged entry still exists, and close the
5927 * stage view if it doesn't. */
5928 if (!status_exists(&stage_status
, stage_line_type
)) {
5929 status_restore(VIEW(REQ_VIEW_STATUS
));
5930 return REQ_VIEW_CLOSE
;
5933 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
5934 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
5935 report("Cannot display a directory");
5939 if (!prepare_update_file(view
, stage_status
.new.name
)) {
5940 report("Failed to open file: %s", strerror(errno
));
5944 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
5949 static struct view_ops stage_ops
= {
5966 char id
[SIZEOF_REV
]; /* SHA1 ID. */
5967 char title
[128]; /* First line of the commit message. */
5968 const char *author
; /* Author of the commit. */
5969 time_t time
; /* Date from the author ident. */
5970 struct ref_list
*refs
; /* Repository references. */
5971 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
5972 size_t graph_size
; /* The width of the graph array. */
5973 bool has_parents
; /* Rewritten --parents seen. */
5976 /* Size of rev graph with no "padding" columns */
5977 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5980 struct rev_graph
*prev
, *next
, *parents
;
5981 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
5983 struct commit
*commit
;
5985 unsigned int boundary
:1;
5988 /* Parents of the commit being visualized. */
5989 static struct rev_graph graph_parents
[4];
5991 /* The current stack of revisions on the graph. */
5992 static struct rev_graph graph_stacks
[4] = {
5993 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
5994 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
5995 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
5996 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6000 graph_parent_is_merge(struct rev_graph
*graph
)
6002 return graph
->parents
->size
> 1;
6006 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6008 struct commit
*commit
= graph
->commit
;
6010 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6011 commit
->graph
[commit
->graph_size
++] = symbol
;
6015 clear_rev_graph(struct rev_graph
*graph
)
6017 graph
->boundary
= 0;
6018 graph
->size
= graph
->pos
= 0;
6019 graph
->commit
= NULL
;
6020 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6024 done_rev_graph(struct rev_graph
*graph
)
6026 if (graph_parent_is_merge(graph
) &&
6027 graph
->pos
< graph
->size
- 1 &&
6028 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6029 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6031 graph
->commit
->graph_size
= i
* 2;
6032 while (i
< graph
->next
->size
- 1) {
6033 append_to_rev_graph(graph
, ' ');
6034 append_to_rev_graph(graph
, '\\');
6039 clear_rev_graph(graph
);
6043 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6047 /* "Collapse" duplicate parents lines.
6049 * FIXME: This needs to also update update the drawn graph but
6050 * for now it just serves as a method for pruning graph lines. */
6051 for (i
= 0; i
< graph
->size
; i
++)
6052 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6055 if (graph
->size
< SIZEOF_REVITEMS
) {
6056 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6061 get_rev_graph_symbol(struct rev_graph
*graph
)
6065 if (graph
->boundary
)
6066 symbol
= REVGRAPH_BOUND
;
6067 else if (graph
->parents
->size
== 0)
6068 symbol
= REVGRAPH_INIT
;
6069 else if (graph_parent_is_merge(graph
))
6070 symbol
= REVGRAPH_MERGE
;
6071 else if (graph
->pos
>= graph
->size
)
6072 symbol
= REVGRAPH_BRANCH
;
6074 symbol
= REVGRAPH_COMMIT
;
6080 draw_rev_graph(struct rev_graph
*graph
)
6083 chtype separator
, line
;
6085 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6086 static struct rev_filler fillers
[] = {
6092 chtype symbol
= get_rev_graph_symbol(graph
);
6093 struct rev_filler
*filler
;
6096 if (opt_line_graphics
)
6097 fillers
[DEFAULT
].line
= line_graphics
[LINE_GRAPHIC_VLINE
];
6099 filler
= &fillers
[DEFAULT
];
6101 for (i
= 0; i
< graph
->pos
; i
++) {
6102 append_to_rev_graph(graph
, filler
->line
);
6103 if (graph_parent_is_merge(graph
->prev
) &&
6104 graph
->prev
->pos
== i
)
6105 filler
= &fillers
[RSHARP
];
6107 append_to_rev_graph(graph
, filler
->separator
);
6110 /* Place the symbol for this revision. */
6111 append_to_rev_graph(graph
, symbol
);
6113 if (graph
->prev
->size
> graph
->size
)
6114 filler
= &fillers
[RDIAG
];
6116 filler
= &fillers
[DEFAULT
];
6120 for (; i
< graph
->size
; i
++) {
6121 append_to_rev_graph(graph
, filler
->separator
);
6122 append_to_rev_graph(graph
, filler
->line
);
6123 if (graph_parent_is_merge(graph
->prev
) &&
6124 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6125 filler
= &fillers
[RSHARP
];
6126 if (graph
->prev
->size
> graph
->size
)
6127 filler
= &fillers
[LDIAG
];
6130 if (graph
->prev
->size
> graph
->size
) {
6131 append_to_rev_graph(graph
, filler
->separator
);
6132 if (filler
->line
!= ' ')
6133 append_to_rev_graph(graph
, filler
->line
);
6137 /* Prepare the next rev graph */
6139 prepare_rev_graph(struct rev_graph
*graph
)
6143 /* First, traverse all lines of revisions up to the active one. */
6144 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6145 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6148 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6151 /* Interleave the new revision parent(s). */
6152 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6153 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6155 /* Lastly, put any remaining revisions. */
6156 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6157 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6161 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6163 /* If this is the finalizing update ... */
6165 prepare_rev_graph(graph
);
6167 /* Graph visualization needs a one rev look-ahead,
6168 * so the first update doesn't visualize anything. */
6169 if (!graph
->prev
->commit
)
6172 if (view
->lines
> 2)
6173 view
->line
[view
->lines
- 3].dirty
= 1;
6174 if (view
->lines
> 1)
6175 view
->line
[view
->lines
- 2].dirty
= 1;
6176 draw_rev_graph(graph
->prev
);
6177 done_rev_graph(graph
->prev
->prev
);
6185 static const char *main_argv
[SIZEOF_ARG
] = {
6186 "git", "log", "--no-color", "--pretty=raw", "--parents",
6187 "--topo-order", "%(head)", NULL
6191 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6193 struct commit
*commit
= line
->data
;
6195 if (!commit
->author
)
6198 if (opt_date
&& draw_date(view
, &commit
->time
))
6201 if (opt_author
&& draw_author(view
, commit
->author
))
6204 if (opt_rev_graph
&& commit
->graph_size
&&
6205 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6208 if (opt_show_refs
&& commit
->refs
) {
6211 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6212 struct ref
*ref
= commit
->refs
->refs
[i
];
6213 enum line_type type
;
6216 type
= LINE_MAIN_HEAD
;
6218 type
= LINE_MAIN_LOCAL_TAG
;
6220 type
= LINE_MAIN_TAG
;
6221 else if (ref
->tracked
)
6222 type
= LINE_MAIN_TRACKED
;
6223 else if (ref
->remote
)
6224 type
= LINE_MAIN_REMOTE
;
6226 type
= LINE_MAIN_REF
;
6228 if (draw_text(view
, type
, "[", TRUE
) ||
6229 draw_text(view
, type
, ref
->name
, TRUE
) ||
6230 draw_text(view
, type
, "]", TRUE
))
6233 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6238 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6242 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6244 main_read(struct view
*view
, char *line
)
6246 static struct rev_graph
*graph
= graph_stacks
;
6247 enum line_type type
;
6248 struct commit
*commit
;
6253 if (!view
->lines
&& !view
->parent
)
6254 die("No revisions match the given arguments.");
6255 if (view
->lines
> 0) {
6256 commit
= view
->line
[view
->lines
- 1].data
;
6257 view
->line
[view
->lines
- 1].dirty
= 1;
6258 if (!commit
->author
) {
6261 graph
->commit
= NULL
;
6264 update_rev_graph(view
, graph
);
6266 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6267 clear_rev_graph(&graph_stacks
[i
]);
6271 type
= get_line_type(line
);
6272 if (type
== LINE_COMMIT
) {
6273 commit
= calloc(1, sizeof(struct commit
));
6277 line
+= STRING_SIZE("commit ");
6279 graph
->boundary
= 1;
6283 string_copy_rev(commit
->id
, line
);
6284 commit
->refs
= get_ref_list(commit
->id
);
6285 graph
->commit
= commit
;
6286 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6288 while ((line
= strchr(line
, ' '))) {
6290 push_rev_graph(graph
->parents
, line
);
6291 commit
->has_parents
= TRUE
;
6298 commit
= view
->line
[view
->lines
- 1].data
;
6302 if (commit
->has_parents
)
6304 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6308 parse_author_line(line
+ STRING_SIZE("author "),
6309 &commit
->author
, &commit
->time
);
6310 update_rev_graph(view
, graph
);
6311 graph
= graph
->next
;
6315 /* Fill in the commit title if it has not already been set. */
6316 if (commit
->title
[0])
6319 /* Require titles to start with a non-space character at the
6320 * offset used by git log. */
6321 if (strncmp(line
, " ", 4))
6324 /* Well, if the title starts with a whitespace character,
6325 * try to be forgiving. Otherwise we end up with no title. */
6326 while (isspace(*line
))
6330 /* FIXME: More graceful handling of titles; append "..." to
6331 * shortened titles, etc. */
6333 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6334 view
->line
[view
->lines
- 1].dirty
= 1;
6341 main_request(struct view
*view
, enum request request
, struct line
*line
)
6343 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
6347 open_view(view
, REQ_VIEW_DIFF
, flags
);
6351 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6361 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6366 if (!opt_show_refs
|| !list
)
6369 for (i
= 0; i
< list
->size
; i
++) {
6370 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6378 main_grep(struct view
*view
, struct line
*line
)
6380 struct commit
*commit
= line
->data
;
6381 const char *text
[] = {
6383 opt_author
? commit
->author
: "",
6384 opt_date
? mkdate(&commit
->time
) : "",
6388 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6392 main_select(struct view
*view
, struct line
*line
)
6394 struct commit
*commit
= line
->data
;
6396 string_copy_rev(view
->ref
, commit
->id
);
6397 string_copy_rev(ref_commit
, view
->ref
);
6400 static struct view_ops main_ops
= {
6413 * Unicode / UTF-8 handling
6415 * NOTE: Much of the following code for dealing with Unicode is derived from
6416 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6417 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6421 unicode_width(unsigned long c
)
6424 (c
<= 0x115f /* Hangul Jamo */
6427 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
6429 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
6430 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
6431 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
6432 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
6433 || (c
>= 0xffe0 && c
<= 0xffe6)
6434 || (c
>= 0x20000 && c
<= 0x2fffd)
6435 || (c
>= 0x30000 && c
<= 0x3fffd)))
6439 return opt_tab_size
;
6444 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6445 * Illegal bytes are set one. */
6446 static const unsigned char utf8_bytes
[256] = {
6447 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,
6448 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,
6449 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,
6450 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,
6451 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,
6452 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,
6453 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,
6454 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,
6457 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6458 static inline unsigned long
6459 utf8_to_unicode(const char *string
, size_t length
)
6461 unsigned long unicode
;
6465 unicode
= string
[0];
6468 unicode
= (string
[0] & 0x1f) << 6;
6469 unicode
+= (string
[1] & 0x3f);
6472 unicode
= (string
[0] & 0x0f) << 12;
6473 unicode
+= ((string
[1] & 0x3f) << 6);
6474 unicode
+= (string
[2] & 0x3f);
6477 unicode
= (string
[0] & 0x0f) << 18;
6478 unicode
+= ((string
[1] & 0x3f) << 12);
6479 unicode
+= ((string
[2] & 0x3f) << 6);
6480 unicode
+= (string
[3] & 0x3f);
6483 unicode
= (string
[0] & 0x0f) << 24;
6484 unicode
+= ((string
[1] & 0x3f) << 18);
6485 unicode
+= ((string
[2] & 0x3f) << 12);
6486 unicode
+= ((string
[3] & 0x3f) << 6);
6487 unicode
+= (string
[4] & 0x3f);
6490 unicode
= (string
[0] & 0x01) << 30;
6491 unicode
+= ((string
[1] & 0x3f) << 24);
6492 unicode
+= ((string
[2] & 0x3f) << 18);
6493 unicode
+= ((string
[3] & 0x3f) << 12);
6494 unicode
+= ((string
[4] & 0x3f) << 6);
6495 unicode
+= (string
[5] & 0x3f);
6498 die("Invalid Unicode length");
6501 /* Invalid characters could return the special 0xfffd value but NUL
6502 * should be just as good. */
6503 return unicode
> 0xffff ? 0 : unicode
;
6506 /* Calculates how much of string can be shown within the given maximum width
6507 * and sets trimmed parameter to non-zero value if all of string could not be
6508 * shown. If the reserve flag is TRUE, it will reserve at least one
6509 * trailing character, which can be useful when drawing a delimiter.
6511 * Returns the number of bytes to output from string to satisfy max_width. */
6513 utf8_length(const char **start
, size_t skip
, int *width
, size_t max_width
, int *trimmed
, bool reserve
)
6515 const char *string
= *start
;
6516 const char *end
= strchr(string
, '\0');
6517 unsigned char last_bytes
= 0;
6518 size_t last_ucwidth
= 0;
6523 while (string
< end
) {
6524 int c
= *(unsigned char *) string
;
6525 unsigned char bytes
= utf8_bytes
[c
];
6527 unsigned long unicode
;
6529 if (string
+ bytes
> end
)
6532 /* Change representation to figure out whether
6533 * it is a single- or double-width character. */
6535 unicode
= utf8_to_unicode(string
, bytes
);
6536 /* FIXME: Graceful handling of invalid Unicode character. */
6540 ucwidth
= unicode_width(unicode
);
6542 skip
-= ucwidth
<= skip
? ucwidth
: skip
;
6546 if (*width
> max_width
) {
6549 if (reserve
&& *width
== max_width
) {
6550 string
-= last_bytes
;
6551 *width
-= last_ucwidth
;
6557 last_bytes
= ucwidth
? bytes
: 0;
6558 last_ucwidth
= ucwidth
;
6561 return string
- *start
;
6569 /* Whether or not the curses interface has been initialized. */
6570 static bool cursed
= FALSE
;
6572 /* Terminal hacks and workarounds. */
6573 static bool use_scroll_redrawwin
;
6574 static bool use_scroll_status_wclear
;
6576 /* The status window is used for polling keystrokes. */
6577 static WINDOW
*status_win
;
6579 /* Reading from the prompt? */
6580 static bool input_mode
= FALSE
;
6582 static bool status_empty
= FALSE
;
6584 /* Update status and title window. */
6586 report(const char *msg
, ...)
6588 struct view
*view
= display
[current_view
];
6594 char buf
[SIZEOF_STR
];
6597 va_start(args
, msg
);
6598 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6599 buf
[sizeof(buf
) - 1] = 0;
6600 buf
[sizeof(buf
) - 2] = '.';
6601 buf
[sizeof(buf
) - 3] = '.';
6602 buf
[sizeof(buf
) - 4] = '.';
6608 if (!status_empty
|| *msg
) {
6611 va_start(args
, msg
);
6613 wmove(status_win
, 0, 0);
6614 if (view
->has_scrolled
&& use_scroll_status_wclear
)
6617 vwprintw(status_win
, msg
, args
);
6618 status_empty
= FALSE
;
6620 status_empty
= TRUE
;
6622 wclrtoeol(status_win
);
6623 wnoutrefresh(status_win
);
6628 update_view_title(view
);
6631 /* Controls when nodelay should be in effect when polling user input. */
6633 set_nonblocking_input(bool loading
)
6635 static unsigned int loading_views
;
6637 if ((loading
== FALSE
&& loading_views
-- == 1) ||
6638 (loading
== TRUE
&& loading_views
++ == 0))
6639 nodelay(status_win
, loading
);
6648 /* Initialize the curses library */
6649 if (isatty(STDIN_FILENO
)) {
6650 cursed
= !!initscr();
6653 /* Leave stdin and stdout alone when acting as a pager. */
6654 opt_tty
= fopen("/dev/tty", "r+");
6656 die("Failed to open /dev/tty");
6657 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
6661 die("Failed to initialize curses");
6663 nonl(); /* Disable conversion and detect newlines from input. */
6664 cbreak(); /* Take input chars one at a time, no wait for \n */
6665 noecho(); /* Don't echo input */
6666 leaveok(stdscr
, FALSE
);
6671 getmaxyx(stdscr
, y
, x
);
6672 status_win
= newwin(1, 0, y
- 1, 0);
6674 die("Failed to create status window");
6676 /* Enable keyboard mapping */
6677 keypad(status_win
, TRUE
);
6678 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
6680 TABSIZE
= opt_tab_size
;
6681 if (opt_line_graphics
) {
6682 line_graphics
[LINE_GRAPHIC_VLINE
] = ACS_VLINE
;
6685 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
6686 if (term
&& !strcmp(term
, "gnome-terminal")) {
6687 /* In the gnome-terminal-emulator, the message from
6688 * scrolling up one line when impossible followed by
6689 * scrolling down one line causes corruption of the
6690 * status line. This is fixed by calling wclear. */
6691 use_scroll_status_wclear
= TRUE
;
6692 use_scroll_redrawwin
= FALSE
;
6694 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
6695 /* No problems with full optimizations in xrvt-(unicode)
6697 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
6700 /* When scrolling in (u)xterm the last line in the
6701 * scrolling direction will update slowly. */
6702 use_scroll_redrawwin
= TRUE
;
6703 use_scroll_status_wclear
= FALSE
;
6708 get_input(int prompt_position
)
6711 int i
, key
, cursor_y
, cursor_x
;
6713 if (prompt_position
)
6717 foreach_view (view
, i
) {
6719 if (view_is_displayed(view
) && view
->has_scrolled
&&
6720 use_scroll_redrawwin
)
6721 redrawwin(view
->win
);
6722 view
->has_scrolled
= FALSE
;
6725 /* Update the cursor position. */
6726 if (prompt_position
) {
6727 getbegyx(status_win
, cursor_y
, cursor_x
);
6728 cursor_x
= prompt_position
;
6730 view
= display
[current_view
];
6731 getbegyx(view
->win
, cursor_y
, cursor_x
);
6732 cursor_x
= view
->width
- 1;
6733 cursor_y
+= view
->lineno
- view
->offset
;
6735 setsyx(cursor_y
, cursor_x
);
6737 /* Refresh, accept single keystroke of input */
6739 key
= wgetch(status_win
);
6741 /* wgetch() with nodelay() enabled returns ERR when
6742 * there's no input. */
6745 } else if (key
== KEY_RESIZE
) {
6748 getmaxyx(stdscr
, height
, width
);
6750 wresize(status_win
, 1, width
);
6751 mvwin(status_win
, height
- 1, 0);
6752 wnoutrefresh(status_win
);
6754 redraw_display(TRUE
);
6764 prompt_input(const char *prompt
, input_handler handler
, void *data
)
6766 enum input_status status
= INPUT_OK
;
6767 static char buf
[SIZEOF_STR
];
6772 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
6775 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
6776 wclrtoeol(status_win
);
6778 key
= get_input(pos
+ 1);
6783 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
6790 status
= INPUT_CANCEL
;
6794 status
= INPUT_CANCEL
;
6798 if (pos
>= sizeof(buf
)) {
6799 report("Input string too long");
6803 status
= handler(data
, buf
, key
);
6804 if (status
== INPUT_OK
)
6805 buf
[pos
++] = (char) key
;
6809 /* Clear the status window */
6810 status_empty
= FALSE
;
6813 if (status
== INPUT_CANCEL
)
6821 static enum input_status
6822 prompt_yesno_handler(void *data
, char *buf
, int c
)
6824 if (c
== 'y' || c
== 'Y')
6826 if (c
== 'n' || c
== 'N')
6827 return INPUT_CANCEL
;
6832 prompt_yesno(const char *prompt
)
6834 char prompt2
[SIZEOF_STR
];
6836 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
6839 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
6842 static enum input_status
6843 read_prompt_handler(void *data
, char *buf
, int c
)
6845 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
6849 read_prompt(const char *prompt
)
6851 return prompt_input(prompt
, read_prompt_handler
, NULL
);
6854 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
6856 enum input_status status
= INPUT_OK
;
6859 while (items
[size
].text
)
6862 while (status
== INPUT_OK
) {
6863 const struct menu_item
*item
= &items
[*selected
];
6867 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
6868 prompt
, *selected
+ 1, size
);
6870 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
6871 wprintw(status_win
, "%s", item
->text
);
6872 wclrtoeol(status_win
);
6874 key
= get_input(COLS
- 1);
6879 status
= INPUT_STOP
;
6884 *selected
= *selected
- 1;
6886 *selected
= size
- 1;
6891 *selected
= (*selected
+ 1) % size
;
6895 status
= INPUT_CANCEL
;
6899 for (i
= 0; items
[i
].text
; i
++)
6900 if (items
[i
].hotkey
== key
) {
6902 status
= INPUT_STOP
;
6908 /* Clear the status window */
6909 status_empty
= FALSE
;
6912 return status
!= INPUT_CANCEL
;
6916 * Repository properties
6919 static struct ref
**refs
= NULL
;
6920 static size_t refs_size
= 0;
6922 static struct ref_list
**ref_lists
= NULL
;
6923 static size_t ref_lists_size
= 0;
6925 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
6926 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
6927 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
6930 compare_refs(const void *ref1_
, const void *ref2_
)
6932 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
6933 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
6935 if (ref1
->tag
!= ref2
->tag
)
6936 return ref2
->tag
- ref1
->tag
;
6937 if (ref1
->ltag
!= ref2
->ltag
)
6938 return ref2
->ltag
- ref2
->ltag
;
6939 if (ref1
->head
!= ref2
->head
)
6940 return ref2
->head
- ref1
->head
;
6941 if (ref1
->tracked
!= ref2
->tracked
)
6942 return ref2
->tracked
- ref1
->tracked
;
6943 if (ref1
->remote
!= ref2
->remote
)
6944 return ref2
->remote
- ref1
->remote
;
6945 return strcmp(ref1
->name
, ref2
->name
);
6949 foreach_ref(bool (*visitor
)(void *data
, struct ref
*ref
), void *data
)
6953 for (i
= 0; i
< refs_size
; i
++)
6954 if (!visitor(data
, refs
[i
]))
6958 static struct ref_list
*
6959 get_ref_list(const char *id
)
6961 struct ref_list
*list
;
6964 for (i
= 0; i
< ref_lists_size
; i
++)
6965 if (!strcmp(id
, ref_lists
[i
]->id
))
6966 return ref_lists
[i
];
6968 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
6970 list
= calloc(1, sizeof(*list
));
6974 for (i
= 0; i
< refs_size
; i
++) {
6975 if (!strcmp(id
, refs
[i
]->id
) &&
6976 realloc_refs_list(&list
->refs
, list
->size
, 1))
6977 list
->refs
[list
->size
++] = refs
[i
];
6985 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
6986 ref_lists
[ref_lists_size
++] = list
;
6991 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
6993 struct ref
*ref
= NULL
;
6996 bool remote
= FALSE
;
6997 bool tracked
= FALSE
;
6999 int from
= 0, to
= refs_size
- 1;
7001 if (!prefixcmp(name
, "refs/tags/")) {
7002 if (!suffixcmp(name
, namelen
, "^{}")) {
7010 namelen
-= STRING_SIZE("refs/tags/");
7011 name
+= STRING_SIZE("refs/tags/");
7013 } else if (!prefixcmp(name
, "refs/remotes/")) {
7015 namelen
-= STRING_SIZE("refs/remotes/");
7016 name
+= STRING_SIZE("refs/remotes/");
7017 tracked
= !strcmp(opt_remote
, name
);
7019 } else if (!prefixcmp(name
, "refs/heads/")) {
7020 namelen
-= STRING_SIZE("refs/heads/");
7021 name
+= STRING_SIZE("refs/heads/");
7022 head
= !strncmp(opt_head
, name
, namelen
);
7024 } else if (!strcmp(name
, "HEAD")) {
7025 string_ncopy(opt_head_rev
, id
, idlen
);
7029 /* If we are reloading or it's an annotated tag, replace the
7030 * previous SHA1 with the resolved commit id; relies on the fact
7031 * git-ls-remote lists the commit id of an annotated tag right
7032 * before the commit id it points to. */
7033 while (from
<= to
) {
7034 size_t pos
= (to
+ from
) / 2;
7035 int cmp
= strcmp(name
, refs
[pos
]->name
);
7049 if (!realloc_refs(&refs
, refs_size
, 1))
7051 ref
= calloc(1, sizeof(*ref
) + namelen
);
7054 memmove(refs
+ from
+ 1, refs
+ from
,
7055 (refs_size
- from
) * sizeof(*refs
));
7057 strncpy(ref
->name
, name
, namelen
);
7064 ref
->remote
= remote
;
7065 ref
->tracked
= tracked
;
7066 string_copy_rev(ref
->id
, id
);
7074 const char *head_argv
[] = {
7075 "git", "symbolic-ref", "HEAD", NULL
7077 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7078 "git", "ls-remote", opt_git_dir
, NULL
7080 static bool init
= FALSE
;
7084 argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE");
7091 if (run_io_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7092 !prefixcmp(opt_head
, "refs/heads/")) {
7093 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7095 memmove(opt_head
, offset
, strlen(offset
) + 1);
7098 for (i
= 0; i
< refs_size
; i
++)
7101 if (run_io_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7104 /* Update the ref lists to reflect changes. */
7105 for (i
= 0; i
< ref_lists_size
; i
++) {
7106 struct ref_list
*list
= ref_lists
[i
];
7109 for (old
= new = 0; old
< list
->size
; old
++)
7110 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7111 list
->refs
[new++] = list
->refs
[old
];
7119 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7121 if (!strcmp(name
, ".remote")) {
7122 string_ncopy(opt_remote
, value
, valuelen
);
7124 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7125 size_t from
= strlen(opt_remote
);
7127 if (!prefixcmp(value
, "refs/heads/"))
7128 value
+= STRING_SIZE("refs/heads/");
7130 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7136 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7138 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7139 int argc
= 1 + (cmd
== option_set_command
);
7142 if (!argv_from_string(argv
, &argc
, value
))
7143 config_msg
= "Too many option arguments";
7145 error
= cmd(argc
, argv
);
7148 warn("Option 'tig.%s': %s", name
, config_msg
);
7152 set_environment_variable(const char *name
, const char *value
)
7154 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7155 char *env
= malloc(len
);
7158 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7166 set_work_tree(const char *value
)
7168 char cwd
[SIZEOF_STR
];
7170 if (!getcwd(cwd
, sizeof(cwd
)))
7171 die("Failed to get cwd path: %s", strerror(errno
));
7172 if (chdir(opt_git_dir
) < 0)
7173 die("Failed to chdir(%s): %s", strerror(errno
));
7174 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7175 die("Failed to get git path: %s", strerror(errno
));
7177 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7178 if (chdir(value
) < 0)
7179 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7180 if (!getcwd(cwd
, sizeof(cwd
)))
7181 die("Failed to get cwd path: %s", strerror(errno
));
7182 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7183 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7184 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7185 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7186 opt_is_inside_work_tree
= TRUE
;
7190 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7192 if (!strcmp(name
, "i18n.commitencoding"))
7193 string_ncopy(opt_encoding
, value
, valuelen
);
7195 else if (!strcmp(name
, "core.editor"))
7196 string_ncopy(opt_editor
, value
, valuelen
);
7198 else if (!strcmp(name
, "core.worktree"))
7199 set_work_tree(value
);
7201 else if (!prefixcmp(name
, "tig.color."))
7202 set_repo_config_option(name
+ 10, value
, option_color_command
);
7204 else if (!prefixcmp(name
, "tig.bind."))
7205 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7207 else if (!prefixcmp(name
, "tig."))
7208 set_repo_config_option(name
+ 4, value
, option_set_command
);
7210 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7211 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7212 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7218 load_git_config(void)
7220 const char *config_list_argv
[] = { "git", GIT_CONFIG
, "--list", NULL
};
7222 return run_io_load(config_list_argv
, "=", read_repo_config_option
);
7226 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7228 if (!opt_git_dir
[0]) {
7229 string_ncopy(opt_git_dir
, name
, namelen
);
7231 } else if (opt_is_inside_work_tree
== -1) {
7232 /* This can be 3 different values depending on the
7233 * version of git being used. If git-rev-parse does not
7234 * understand --is-inside-work-tree it will simply echo
7235 * the option else either "true" or "false" is printed.
7236 * Default to true for the unknown case. */
7237 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7239 } else if (*name
== '.') {
7240 string_ncopy(opt_cdup
, name
, namelen
);
7243 string_ncopy(opt_prefix
, name
, namelen
);
7250 load_repo_info(void)
7252 const char *rev_parse_argv
[] = {
7253 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7254 "--show-cdup", "--show-prefix", NULL
7257 return run_io_load(rev_parse_argv
, "=", read_repo_info
);
7265 static const char usage
[] =
7266 "tig " TIG_VERSION
" (" __DATE__
")\n"
7268 "Usage: tig [options] [revs] [--] [paths]\n"
7269 " or: tig show [options] [revs] [--] [paths]\n"
7270 " or: tig blame [rev] path\n"
7272 " or: tig < [git command output]\n"
7275 " -v, --version Show version and exit\n"
7276 " -h, --help Show help message and exit";
7278 static void __NORETURN
7281 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7287 static void __NORETURN
7288 die(const char *err
, ...)
7294 va_start(args
, err
);
7295 fputs("tig: ", stderr
);
7296 vfprintf(stderr
, err
, args
);
7297 fputs("\n", stderr
);
7304 warn(const char *msg
, ...)
7308 va_start(args
, msg
);
7309 fputs("tig warning: ", stderr
);
7310 vfprintf(stderr
, msg
, args
);
7311 fputs("\n", stderr
);
7316 parse_options(int argc
, const char *argv
[])
7318 enum request request
= REQ_VIEW_MAIN
;
7319 const char *subcommand
;
7320 bool seen_dashdash
= FALSE
;
7321 /* XXX: This is vulnerable to the user overriding options
7322 * required for the main view parser. */
7323 const char *custom_argv
[SIZEOF_ARG
] = {
7324 "git", "log", "--no-color", "--pretty=raw", "--parents",
7325 "--topo-order", NULL
7329 if (!isatty(STDIN_FILENO
)) {
7330 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7331 return REQ_VIEW_PAGER
;
7337 subcommand
= argv
[1];
7338 if (!strcmp(subcommand
, "status")) {
7340 warn("ignoring arguments after `%s'", subcommand
);
7341 return REQ_VIEW_STATUS
;
7343 } else if (!strcmp(subcommand
, "blame")) {
7344 if (argc
<= 2 || argc
> 4)
7345 die("invalid number of options to blame\n\n%s", usage
);
7349 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7353 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7354 return REQ_VIEW_BLAME
;
7356 } else if (!strcmp(subcommand
, "show")) {
7357 request
= REQ_VIEW_DIFF
;
7364 custom_argv
[1] = subcommand
;
7368 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7369 const char *opt
= argv
[i
];
7371 if (seen_dashdash
|| !strcmp(opt
, "--")) {
7372 seen_dashdash
= TRUE
;
7374 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7375 printf("tig version %s\n", TIG_VERSION
);
7378 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7379 printf("%s\n", usage
);
7383 custom_argv
[j
++] = opt
;
7384 if (j
>= ARRAY_SIZE(custom_argv
))
7385 die("command too long");
7388 if (!prepare_update(VIEW(request
), custom_argv
, NULL
, FORMAT_NONE
))
7389 die("Failed to format arguments");
7395 main(int argc
, const char *argv
[])
7397 enum request request
= parse_options(argc
, argv
);
7401 signal(SIGINT
, quit
);
7402 signal(SIGPIPE
, SIG_IGN
);
7404 if (setlocale(LC_ALL
, "")) {
7405 char *codeset
= nl_langinfo(CODESET
);
7407 string_ncopy(opt_codeset
, codeset
, strlen(codeset
));
7410 if (load_repo_info() == ERR
)
7411 die("Failed to load repo info.");
7413 if (load_options() == ERR
)
7414 die("Failed to load user config.");
7416 if (load_git_config() == ERR
)
7417 die("Failed to load repo config.");
7419 /* Require a git repository unless when running in pager mode. */
7420 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7421 die("Not a git repository");
7423 if (*opt_encoding
&& strcasecmp(opt_encoding
, "UTF-8"))
7426 if (*opt_codeset
&& strcmp(opt_codeset
, opt_encoding
)) {
7427 opt_iconv
= iconv_open(opt_codeset
, opt_encoding
);
7428 if (opt_iconv
== ICONV_NONE
)
7429 die("Failed to initialize character set conversion");
7432 if (load_refs() == ERR
)
7433 die("Failed to load refs.");
7435 foreach_view (view
, i
)
7436 argv_from_env(view
->ops
->argv
, view
->cmd_env
);
7440 if (request
!= REQ_NONE
)
7441 open_view(NULL
, request
, OPEN_PREPARED
);
7442 request
= request
== REQ_NONE
? REQ_VIEW_MAIN
: REQ_NONE
;
7444 while (view_driver(display
[current_view
], request
)) {
7445 int key
= get_input(0);
7447 view
= display
[current_view
];
7448 request
= get_keybinding(view
->keymap
, key
);
7450 /* Some low-level request handling. This keeps access to
7451 * status_win restricted. */
7455 char *cmd
= read_prompt(":");
7457 if (cmd
&& isdigit(*cmd
)) {
7458 int lineno
= view
->lineno
+ 1;
7460 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7461 select_view_line(view
, lineno
- 1);
7464 report("Unable to parse '%s' as a line number", cmd
);
7468 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7469 const char *argv
[SIZEOF_ARG
] = { "git" };
7472 /* When running random commands, initially show the
7473 * command in the title. However, it maybe later be
7474 * overwritten if a commit line is selected. */
7475 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7477 if (!argv_from_string(argv
, &argc
, cmd
)) {
7478 report("Too many arguments");
7479 } else if (!prepare_update(next
, argv
, NULL
, FORMAT_DASH
)) {
7480 report("Failed to format command");
7482 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7490 case REQ_SEARCH_BACK
:
7492 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7493 char *search
= read_prompt(prompt
);
7496 string_ncopy(opt_search
, search
, strlen(search
));
7497 else if (*opt_search
)
7498 request
= request
== REQ_SEARCH
?