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>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
63 #define __NORETURN __attribute__((__noreturn__))
68 static void __NORETURN
die(const char *err
, ...);
69 static void warn(const char *msg
, ...);
70 static void report(const char *msg
, ...);
71 static void set_nonblocking_input(bool loading
);
72 static size_t utf8_length(const char **string
, size_t col
, int *width
, size_t max_width
, int *trimmed
, bool reserve
);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define MAX(x, y) ((x) > (y) ? (x) : (y))
78 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x) (sizeof(x) - 1)
81 #define SIZEOF_STR 1024 /* Default string size. */
82 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG 32 /* Default argument array size. */
88 #define REVGRAPH_INIT 'I'
89 #define REVGRAPH_MERGE 'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND '^'
94 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT (-1)
99 #define ICONV_NONE ((iconv_t) -1)
101 #define ICONV_CONST /* nothing */
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT "%Y-%m-%d %H:%M"
106 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
111 #define MIN_VIEW_HEIGHT 4
113 #define NULL_ID "0000000000000000000000000000000000000000"
115 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
117 /* Some ASCII-shorthands fitted into the ncurses namespace. */
119 #define KEY_RETURN '\r'
124 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
125 unsigned int head
:1; /* Is it the current HEAD? */
126 unsigned int tag
:1; /* Is it a tag? */
127 unsigned int ltag
:1; /* If so, is the tag local? */
128 unsigned int remote
:1; /* Is it a remote ref? */
129 unsigned int tracked
:1; /* Is it the remote for the current HEAD? */
130 char name
[1]; /* Ref name; tag or head names are shortened. */
134 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
135 size_t size
; /* Number of refs. */
136 struct ref
**refs
; /* References for this ID. */
139 static struct ref_list
*get_ref_list(const char *id
);
140 static void foreach_ref(bool (*visitor
)(void *data
, struct ref
*ref
), void *data
);
141 static int load_refs(void);
144 FORMAT_ALL
, /* Perform replacement in all arguments. */
145 FORMAT_DASH
, /* Perform replacement up until "--". */
146 FORMAT_NONE
/* No replacement should be performed. */
149 static bool format_argv(const char *dst
[], const char *src
[], enum format_flags flags
);
158 typedef enum input_status (*input_handler
)(void *data
, char *buf
, int c
);
160 static char *prompt_input(const char *prompt
, input_handler handler
, void *data
);
161 static bool prompt_yesno(const char *prompt
);
169 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
);
172 * Allocation helpers ... Entering macro hell to never be seen again.
175 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
177 name(type **mem, size_t size, size_t increase) \
179 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
180 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
183 if (mem == NULL || num_chunks != num_chunks_new) { \
184 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
197 string_ncopy_do(char *dst
, size_t dstlen
, const char *src
, size_t srclen
)
199 if (srclen
> dstlen
- 1)
202 strncpy(dst
, src
, srclen
);
206 /* Shorthands for safely copying into a fixed buffer. */
208 #define string_copy(dst, src) \
209 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
211 #define string_ncopy(dst, src, srclen) \
212 string_ncopy_do(dst, sizeof(dst), src, srclen)
214 #define string_copy_rev(dst, src) \
215 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
217 #define string_add(dst, from, src) \
218 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
221 string_expand(char *dst
, size_t dstlen
, const char *src
, int tabsize
)
225 for (size
= pos
= 0; size
< dstlen
- 1 && src
[pos
]; pos
++) {
226 if (src
[pos
] == '\t') {
227 size_t expanded
= tabsize
- (size
% tabsize
);
229 if (expanded
+ size
>= dstlen
- 1)
230 expanded
= dstlen
- size
- 1;
231 memcpy(dst
+ size
, " ", expanded
);
234 dst
[size
++] = src
[pos
];
242 chomp_string(char *name
)
246 while (isspace(*name
))
249 namelen
= strlen(name
) - 1;
250 while (namelen
> 0 && isspace(name
[namelen
]))
257 string_nformat(char *buf
, size_t bufsize
, size_t *bufpos
, const char *fmt
, ...)
260 size_t pos
= bufpos
? *bufpos
: 0;
263 pos
+= vsnprintf(buf
+ pos
, bufsize
- pos
, fmt
, args
);
269 return pos
>= bufsize
? FALSE
: TRUE
;
272 #define string_format(buf, fmt, args...) \
273 string_nformat(buf, sizeof(buf), NULL, fmt, args)
275 #define string_format_from(buf, from, fmt, args...) \
276 string_nformat(buf, sizeof(buf), from, fmt, args)
279 string_enum_compare(const char *str1
, const char *str2
, int len
)
283 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
285 /* Diff-Header == DIFF_HEADER */
286 for (i
= 0; i
< len
; i
++) {
287 if (toupper(str1
[i
]) == toupper(str2
[i
]))
290 if (string_enum_sep(str1
[i
]) &&
291 string_enum_sep(str2
[i
]))
294 return str1
[i
] - str2
[i
];
306 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
309 map_enum_do(const struct enum_map
*map
, size_t map_size
, int *value
, const char *name
)
311 size_t namelen
= strlen(name
);
314 for (i
= 0; i
< map_size
; i
++)
315 if (namelen
== map
[i
].namelen
&&
316 !string_enum_compare(name
, map
[i
].name
, namelen
)) {
317 *value
= map
[i
].value
;
324 #define map_enum(attr, map, name) \
325 map_enum_do(map, ARRAY_SIZE(map), attr, name)
327 #define prefixcmp(str1, str2) \
328 strncmp(str1, str2, STRING_SIZE(str2))
331 suffixcmp(const char *str
, int slen
, const char *suffix
)
333 size_t len
= slen
>= 0 ? slen
: strlen(str
);
334 size_t suffixlen
= strlen(suffix
);
336 return suffixlen
< len
? strcmp(str
+ len
- suffixlen
, suffix
) : -1;
341 * What value of "tz" was in effect back then at "time" in the
344 static int local_tzoffset(time_t time
)
348 int offset
, eastwest
;
351 localtime_r(&t
, &tm
);
352 t_local
= mktime(&tm
);
356 offset
= t
- t_local
;
359 offset
= t_local
- t
;
361 offset
/= 60; /* in minutes */
362 offset
= (offset
% 60) + ((offset
/ 60) * 100);
363 return offset
* eastwest
;
374 string_date(const time_t *time
, enum date date
)
376 static char buf
[DATE_COLS
+ 1];
377 static const struct enum_map reldate
[] = {
378 { "second", 1, 60 * 2 },
379 { "minute", 60, 60 * 60 * 2 },
380 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
381 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
382 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
383 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
387 if (date
== DATE_RELATIVE
) {
389 time_t date
= *time
+ local_tzoffset(*time
);
393 gettimeofday(&now
, NULL
);
394 seconds
= now
.tv_sec
< date
? date
- now
.tv_sec
: now
.tv_sec
- date
;
395 for (i
= 0; i
< ARRAY_SIZE(reldate
); i
++) {
396 if (seconds
>= reldate
[i
].value
)
399 seconds
/= reldate
[i
].namelen
;
400 if (!string_format(buf
, "%ld %s%s %s",
401 seconds
, reldate
[i
].name
,
402 seconds
> 1 ? "s" : "",
403 now
.tv_sec
>= date
? "ago" : "ahead"))
410 return strftime(buf
, sizeof(buf
), DATE_FORMAT
, &tm
) ? buf
: NULL
;
415 argv_from_string(const char *argv
[SIZEOF_ARG
], int *argc
, char *cmd
)
419 while (*cmd
&& *argc
< SIZEOF_ARG
&& (valuelen
= strcspn(cmd
, " \t"))) {
420 bool advance
= cmd
[valuelen
] != 0;
423 argv
[(*argc
)++] = chomp_string(cmd
);
424 cmd
= chomp_string(cmd
+ valuelen
+ advance
);
427 if (*argc
< SIZEOF_ARG
)
429 return *argc
< SIZEOF_ARG
;
433 argv_from_env(const char **argv
, const char *name
)
435 char *env
= argv
? getenv(name
) : NULL
;
440 if (env
&& !argv_from_string(argv
, &argc
, env
))
441 die("Too many arguments in the `%s` environment variable", name
);
446 * Executing external commands.
450 IO_FD
, /* File descriptor based IO. */
451 IO_BG
, /* Execute command in the background. */
452 IO_FG
, /* Execute command with same std{in,out,err}. */
453 IO_RD
, /* Read only fork+exec IO. */
454 IO_WR
, /* Write only fork+exec IO. */
455 IO_AP
, /* Append fork+exec output to file. */
459 enum io_type type
; /* The requested type of pipe. */
460 const char *dir
; /* Directory from which to execute. */
461 pid_t pid
; /* Pipe for reading or writing. */
462 int pipe
; /* Pipe end for reading or writing. */
463 int error
; /* Error status. */
464 const char *argv
[SIZEOF_ARG
]; /* Shell command arguments. */
465 char *buf
; /* Read buffer. */
466 size_t bufalloc
; /* Allocated buffer size. */
467 size_t bufsize
; /* Buffer content size. */
468 char *bufpos
; /* Current buffer position. */
469 unsigned int eof
:1; /* Has end of file been reached. */
473 reset_io(struct io
*io
)
477 io
->buf
= io
->bufpos
= NULL
;
478 io
->bufalloc
= io
->bufsize
= 0;
484 init_io(struct io
*io
, const char *dir
, enum io_type type
)
492 init_io_rd(struct io
*io
, const char *argv
[], const char *dir
,
493 enum format_flags flags
)
495 init_io(io
, dir
, IO_RD
);
496 return format_argv(io
->argv
, argv
, flags
);
500 io_open(struct io
*io
, const char *name
)
502 init_io(io
, NULL
, IO_FD
);
503 io
->pipe
= *name
? open(name
, O_RDONLY
) : STDIN_FILENO
;
506 return io
->pipe
!= -1;
510 kill_io(struct io
*io
)
512 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
516 done_io(struct io
*io
)
527 pid_t waiting
= waitpid(pid
, &status
, 0);
532 report("waitpid failed (%s)", strerror(errno
));
536 return waiting
== pid
&&
537 !WIFSIGNALED(status
) &&
539 !WEXITSTATUS(status
);
546 start_io(struct io
*io
)
548 int pipefds
[2] = { -1, -1 };
550 if (io
->type
== IO_FD
)
553 if ((io
->type
== IO_RD
|| io
->type
== IO_WR
) &&
556 else if (io
->type
== IO_AP
)
557 pipefds
[1] = io
->pipe
;
559 if ((io
->pid
= fork())) {
560 if (pipefds
[!(io
->type
== IO_WR
)] != -1)
561 close(pipefds
[!(io
->type
== IO_WR
)]);
563 io
->pipe
= pipefds
[!!(io
->type
== IO_WR
)];
568 if (io
->type
!= IO_FG
) {
569 int devnull
= open("/dev/null", O_RDWR
);
570 int readfd
= io
->type
== IO_WR
? pipefds
[0] : devnull
;
571 int writefd
= (io
->type
== IO_RD
|| io
->type
== IO_AP
)
572 ? pipefds
[1] : devnull
;
574 dup2(readfd
, STDIN_FILENO
);
575 dup2(writefd
, STDOUT_FILENO
);
576 dup2(devnull
, STDERR_FILENO
);
579 if (pipefds
[0] != -1)
581 if (pipefds
[1] != -1)
585 if (io
->dir
&& *io
->dir
&& chdir(io
->dir
) == -1)
586 die("Failed to change directory: %s", strerror(errno
));
588 execvp(io
->argv
[0], (char *const*) io
->argv
);
589 die("Failed to execute program: %s", strerror(errno
));
592 if (pipefds
[!!(io
->type
== IO_WR
)] != -1)
593 close(pipefds
[!!(io
->type
== IO_WR
)]);
598 run_io(struct io
*io
, const char **argv
, const char *dir
, enum io_type type
)
600 init_io(io
, dir
, type
);
601 if (!format_argv(io
->argv
, argv
, FORMAT_NONE
))
607 run_io_do(struct io
*io
)
609 return start_io(io
) && done_io(io
);
613 run_io_bg(const char **argv
)
617 init_io(&io
, NULL
, IO_BG
);
618 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
620 return run_io_do(&io
);
624 run_io_fg(const char **argv
, const char *dir
)
628 init_io(&io
, dir
, IO_FG
);
629 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
631 return run_io_do(&io
);
635 run_io_append(const char **argv
, enum format_flags flags
, int fd
)
639 init_io(&io
, NULL
, IO_AP
);
641 if (format_argv(io
.argv
, argv
, flags
))
642 return run_io_do(&io
);
648 run_io_rd(struct io
*io
, const char **argv
, const char *dir
, enum format_flags flags
)
650 return init_io_rd(io
, argv
, dir
, flags
) && start_io(io
);
654 run_io_rd_dir(struct io
*io
, const char **argv
, const char *dir
, enum format_flags flags
)
656 return init_io_rd(io
, argv
, dir
, flags
) && start_io(io
);
660 io_eof(struct io
*io
)
666 io_error(struct io
*io
)
672 io_strerror(struct io
*io
)
674 return strerror(io
->error
);
678 io_can_read(struct io
*io
)
680 struct timeval tv
= { 0, 500 };
684 FD_SET(io
->pipe
, &fds
);
686 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
690 io_read(struct io
*io
, void *buf
, size_t bufsize
)
693 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
695 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
697 else if (readsize
== -1)
699 else if (readsize
== 0)
705 DEFINE_ALLOCATOR(realloc_io_buf
, char, BUFSIZ
)
708 io_get(struct io
*io
, int c
, bool can_read
)
714 if (io
->bufsize
> 0) {
715 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
717 char *line
= io
->bufpos
;
720 io
->bufpos
= eol
+ 1;
721 io
->bufsize
-= io
->bufpos
- line
;
728 io
->bufpos
[io
->bufsize
] = 0;
738 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
739 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
741 if (io
->bufalloc
== io
->bufsize
) {
742 if (!realloc_io_buf(&io
->buf
, io
->bufalloc
, BUFSIZ
))
744 io
->bufalloc
+= BUFSIZ
;
747 io
->bufpos
= io
->buf
;
748 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
751 io
->bufsize
+= readsize
;
756 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
760 while (!io_error(io
) && written
< bufsize
) {
763 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
764 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
772 return written
== bufsize
;
776 io_read_buf(struct io
*io
, char buf
[], size_t bufsize
)
778 char *result
= io_get(io
, '\n', TRUE
);
781 result
= chomp_string(result
);
782 string_ncopy_do(buf
, bufsize
, result
, strlen(result
));
785 return done_io(io
) && result
;
789 run_io_buf(const char **argv
, char buf
[], size_t bufsize
)
793 return run_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
794 && io_read_buf(&io
, buf
, bufsize
);
798 io_load(struct io
*io
, const char *separators
,
799 int (*read_property
)(char *, size_t, char *, size_t))
807 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
812 name
= chomp_string(name
);
813 namelen
= strcspn(name
, separators
);
817 value
= chomp_string(name
+ namelen
+ 1);
818 valuelen
= strlen(value
);
825 state
= read_property(name
, namelen
, value
, valuelen
);
828 if (state
!= ERR
&& io_error(io
))
836 run_io_load(const char **argv
, const char *separators
,
837 int (*read_property
)(char *, size_t, char *, size_t))
841 return init_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
842 ? io_load(&io
, separators
, read_property
) : ERR
;
851 /* XXX: Keep the view request first and in sync with views[]. */ \
852 REQ_GROUP("View switching") \
853 REQ_(VIEW_MAIN, "Show main view"), \
854 REQ_(VIEW_DIFF, "Show diff view"), \
855 REQ_(VIEW_LOG, "Show log view"), \
856 REQ_(VIEW_TREE, "Show tree view"), \
857 REQ_(VIEW_BLOB, "Show blob view"), \
858 REQ_(VIEW_BLAME, "Show blame view"), \
859 REQ_(VIEW_BRANCH, "Show branch view"), \
860 REQ_(VIEW_HELP, "Show help page"), \
861 REQ_(VIEW_PAGER, "Show pager view"), \
862 REQ_(VIEW_STATUS, "Show status view"), \
863 REQ_(VIEW_STAGE, "Show stage view"), \
865 REQ_GROUP("View manipulation") \
866 REQ_(ENTER, "Enter current line and scroll"), \
867 REQ_(NEXT, "Move to next"), \
868 REQ_(PREVIOUS, "Move to previous"), \
869 REQ_(PARENT, "Move to parent"), \
870 REQ_(VIEW_NEXT, "Move focus to next view"), \
871 REQ_(REFRESH, "Reload and refresh"), \
872 REQ_(MAXIMIZE, "Maximize the current view"), \
873 REQ_(VIEW_CLOSE, "Close the current view"), \
874 REQ_(QUIT, "Close all views and quit"), \
876 REQ_GROUP("View specific requests") \
877 REQ_(STATUS_UPDATE, "Update file status"), \
878 REQ_(STATUS_REVERT, "Revert file changes"), \
879 REQ_(STATUS_MERGE, "Merge file using external tool"), \
880 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
882 REQ_GROUP("Cursor navigation") \
883 REQ_(MOVE_UP, "Move cursor one line up"), \
884 REQ_(MOVE_DOWN, "Move cursor one line down"), \
885 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
886 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
887 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
888 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
890 REQ_GROUP("Scrolling") \
891 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
892 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
893 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
894 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
895 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
896 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
898 REQ_GROUP("Searching") \
899 REQ_(SEARCH, "Search the view"), \
900 REQ_(SEARCH_BACK, "Search backwards in the view"), \
901 REQ_(FIND_NEXT, "Find next search match"), \
902 REQ_(FIND_PREV, "Find previous search match"), \
904 REQ_GROUP("Option manipulation") \
905 REQ_(OPTIONS, "Open option menu"), \
906 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
907 REQ_(TOGGLE_DATE, "Toggle date display"), \
908 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
909 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
910 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
911 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
912 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
913 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
916 REQ_(PROMPT, "Bring up the prompt"), \
917 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
918 REQ_(SHOW_VERSION, "Show version information"), \
919 REQ_(STOP_LOADING, "Stop all loading views"), \
920 REQ_(EDIT, "Open in editor"), \
921 REQ_(NONE, "Do nothing")
924 /* User action requests. */
926 #define REQ_GROUP(help)
927 #define REQ_(req, help) REQ_##req
929 /* Offset all requests to avoid conflicts with ncurses getch values. */
930 REQ_OFFSET
= KEY_MAX
+ 1,
937 struct request_info
{
938 enum request request
;
944 static const struct request_info req_info
[] = {
945 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
946 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
953 get_request(const char *name
)
955 int namelen
= strlen(name
);
958 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
959 if (req_info
[i
].namelen
== namelen
&&
960 !string_enum_compare(req_info
[i
].name
, name
, namelen
))
961 return req_info
[i
].request
;
971 /* Option and state variables. */
972 static enum date opt_date
= DATE_DEFAULT
;
973 static bool opt_author
= TRUE
;
974 static bool opt_line_number
= FALSE
;
975 static bool opt_line_graphics
= TRUE
;
976 static bool opt_rev_graph
= FALSE
;
977 static bool opt_show_refs
= TRUE
;
978 static int opt_num_interval
= 5;
979 static double opt_hscroll
= 0.50;
980 static double opt_scale_split_view
= 2.0 / 3.0;
981 static int opt_tab_size
= 8;
982 static int opt_author_cols
= 19;
983 static char opt_path
[SIZEOF_STR
] = "";
984 static char opt_file
[SIZEOF_STR
] = "";
985 static char opt_ref
[SIZEOF_REF
] = "";
986 static char opt_head
[SIZEOF_REF
] = "";
987 static char opt_head_rev
[SIZEOF_REV
] = "";
988 static char opt_remote
[SIZEOF_REF
] = "";
989 static char opt_encoding
[20] = "UTF-8";
990 static bool opt_utf8
= TRUE
;
991 static char opt_codeset
[20] = "UTF-8";
992 static iconv_t opt_iconv
= ICONV_NONE
;
993 static char opt_search
[SIZEOF_STR
] = "";
994 static char opt_cdup
[SIZEOF_STR
] = "";
995 static char opt_prefix
[SIZEOF_STR
] = "";
996 static char opt_git_dir
[SIZEOF_STR
] = "";
997 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
998 static char opt_editor
[SIZEOF_STR
] = "";
999 static FILE *opt_tty
= NULL
;
1001 #define is_initial_commit() (!*opt_head_rev)
1002 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1003 #define mkdate(time) string_date(time, opt_date)
1007 * Line-oriented content detection.
1011 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1012 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1013 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1014 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1015 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1016 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1017 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1018 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1019 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1020 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1021 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1022 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1023 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1024 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1025 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1026 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1027 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1028 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1029 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1030 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1031 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1032 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1033 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1034 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1035 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1036 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1037 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1038 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1039 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1040 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1041 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1042 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1043 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1044 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1045 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1046 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1047 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1048 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1049 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1050 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1051 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1052 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1053 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1054 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1055 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1056 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1057 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1058 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1059 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1060 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1061 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1062 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1063 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1064 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1065 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1066 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1067 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1070 #define LINE(type, line, fg, bg, attr) \
1078 const char *name
; /* Option name. */
1079 int namelen
; /* Size of option name. */
1080 const char *line
; /* The start of line to match. */
1081 int linelen
; /* Size of string to match. */
1082 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1085 static struct line_info line_info
[] = {
1086 #define LINE(type, line, fg, bg, attr) \
1087 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1092 static enum line_type
1093 get_line_type(const char *line
)
1095 int linelen
= strlen(line
);
1096 enum line_type type
;
1098 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1099 /* Case insensitive search matches Signed-off-by lines better. */
1100 if (linelen
>= line_info
[type
].linelen
&&
1101 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1104 return LINE_DEFAULT
;
1108 get_line_attr(enum line_type type
)
1110 assert(type
< ARRAY_SIZE(line_info
));
1111 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1114 static struct line_info
*
1115 get_line_info(const char *name
)
1117 size_t namelen
= strlen(name
);
1118 enum line_type type
;
1120 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1121 if (namelen
== line_info
[type
].namelen
&&
1122 !string_enum_compare(line_info
[type
].name
, name
, namelen
))
1123 return &line_info
[type
];
1131 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1132 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1133 enum line_type type
;
1137 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1138 default_bg
= COLOR_BLACK
;
1139 default_fg
= COLOR_WHITE
;
1142 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1143 struct line_info
*info
= &line_info
[type
];
1144 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1145 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1147 init_pair(type
, fg
, bg
);
1152 enum line_type type
;
1155 unsigned int selected
:1;
1156 unsigned int dirty
:1;
1157 unsigned int cleareol
:1;
1158 unsigned int other
:16;
1160 void *data
; /* User data */
1170 enum request request
;
1173 static const struct keybinding default_keybindings
[] = {
1174 /* View switching */
1175 { 'm', REQ_VIEW_MAIN
},
1176 { 'd', REQ_VIEW_DIFF
},
1177 { 'l', REQ_VIEW_LOG
},
1178 { 't', REQ_VIEW_TREE
},
1179 { 'f', REQ_VIEW_BLOB
},
1180 { 'B', REQ_VIEW_BLAME
},
1181 { 'H', REQ_VIEW_BRANCH
},
1182 { 'p', REQ_VIEW_PAGER
},
1183 { 'h', REQ_VIEW_HELP
},
1184 { 'S', REQ_VIEW_STATUS
},
1185 { 'c', REQ_VIEW_STAGE
},
1187 /* View manipulation */
1188 { 'q', REQ_VIEW_CLOSE
},
1189 { KEY_TAB
, REQ_VIEW_NEXT
},
1190 { KEY_RETURN
, REQ_ENTER
},
1191 { KEY_UP
, REQ_PREVIOUS
},
1192 { KEY_DOWN
, REQ_NEXT
},
1193 { 'R', REQ_REFRESH
},
1194 { KEY_F(5), REQ_REFRESH
},
1195 { 'O', REQ_MAXIMIZE
},
1197 /* Cursor navigation */
1198 { 'k', REQ_MOVE_UP
},
1199 { 'j', REQ_MOVE_DOWN
},
1200 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1201 { KEY_END
, REQ_MOVE_LAST_LINE
},
1202 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1203 { ' ', REQ_MOVE_PAGE_DOWN
},
1204 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1205 { 'b', REQ_MOVE_PAGE_UP
},
1206 { '-', REQ_MOVE_PAGE_UP
},
1209 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1210 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1211 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1212 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1213 { 'w', REQ_SCROLL_PAGE_UP
},
1214 { 's', REQ_SCROLL_PAGE_DOWN
},
1217 { '/', REQ_SEARCH
},
1218 { '?', REQ_SEARCH_BACK
},
1219 { 'n', REQ_FIND_NEXT
},
1220 { 'N', REQ_FIND_PREV
},
1224 { 'z', REQ_STOP_LOADING
},
1225 { 'v', REQ_SHOW_VERSION
},
1226 { 'r', REQ_SCREEN_REDRAW
},
1227 { 'o', REQ_OPTIONS
},
1228 { '.', REQ_TOGGLE_LINENO
},
1229 { 'D', REQ_TOGGLE_DATE
},
1230 { 'A', REQ_TOGGLE_AUTHOR
},
1231 { 'g', REQ_TOGGLE_REV_GRAPH
},
1232 { 'F', REQ_TOGGLE_REFS
},
1233 { 'I', REQ_TOGGLE_SORT_ORDER
},
1234 { 'i', REQ_TOGGLE_SORT_FIELD
},
1235 { ':', REQ_PROMPT
},
1236 { 'u', REQ_STATUS_UPDATE
},
1237 { '!', REQ_STATUS_REVERT
},
1238 { 'M', REQ_STATUS_MERGE
},
1239 { '@', REQ_STAGE_NEXT
},
1240 { ',', REQ_PARENT
},
1244 #define KEYMAP_INFO \
1259 #define KEYMAP_(name) KEYMAP_##name
1264 static const struct enum_map keymap_table
[] = {
1265 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1270 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1272 struct keybinding_table
{
1273 struct keybinding
*data
;
1277 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1280 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1282 struct keybinding_table
*table
= &keybindings
[keymap
];
1284 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1286 die("Failed to allocate keybinding");
1287 table
->data
[table
->size
].alias
= key
;
1288 table
->data
[table
->size
++].request
= request
;
1291 /* Looks for a key binding first in the given map, then in the generic map, and
1292 * lastly in the default keybindings. */
1294 get_keybinding(enum keymap keymap
, int key
)
1298 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1299 if (keybindings
[keymap
].data
[i
].alias
== key
)
1300 return keybindings
[keymap
].data
[i
].request
;
1302 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1303 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1304 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1306 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1307 if (default_keybindings
[i
].alias
== key
)
1308 return default_keybindings
[i
].request
;
1310 return (enum request
) key
;
1319 static const struct key key_table
[] = {
1320 { "Enter", KEY_RETURN
},
1322 { "Backspace", KEY_BACKSPACE
},
1324 { "Escape", KEY_ESC
},
1325 { "Left", KEY_LEFT
},
1326 { "Right", KEY_RIGHT
},
1328 { "Down", KEY_DOWN
},
1329 { "Insert", KEY_IC
},
1330 { "Delete", KEY_DC
},
1332 { "Home", KEY_HOME
},
1334 { "PageUp", KEY_PPAGE
},
1335 { "PageDown", KEY_NPAGE
},
1345 { "F10", KEY_F(10) },
1346 { "F11", KEY_F(11) },
1347 { "F12", KEY_F(12) },
1351 get_key_value(const char *name
)
1355 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1356 if (!strcasecmp(key_table
[i
].name
, name
))
1357 return key_table
[i
].value
;
1359 if (strlen(name
) == 1 && isprint(*name
))
1366 get_key_name(int key_value
)
1368 static char key_char
[] = "'X'";
1369 const char *seq
= NULL
;
1372 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1373 if (key_table
[key
].value
== key_value
)
1374 seq
= key_table
[key
].name
;
1378 isprint(key_value
)) {
1379 key_char
[1] = (char) key_value
;
1383 return seq
? seq
: "(no key)";
1387 append_key(char *buf
, size_t *pos
, const struct keybinding
*keybinding
)
1389 const char *sep
= *pos
> 0 ? ", " : "";
1390 const char *keyname
= get_key_name(keybinding
->alias
);
1392 return string_nformat(buf
, BUFSIZ
, pos
, "%s%s", sep
, keyname
);
1396 append_keymap_request_keys(char *buf
, size_t *pos
, enum request request
,
1397 enum keymap keymap
, bool all
)
1401 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1402 if (keybindings
[keymap
].data
[i
].request
== request
) {
1403 if (!append_key(buf
, pos
, &keybindings
[keymap
].data
[i
]))
1413 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1416 get_keys(enum keymap keymap
, enum request request
, bool all
)
1418 static char buf
[BUFSIZ
];
1424 if (!append_keymap_request_keys(buf
, &pos
, request
, keymap
, all
))
1425 return "Too many keybindings!";
1426 if (pos
> 0 && !all
)
1429 if (keymap
!= KEYMAP_GENERIC
) {
1430 /* Only the generic keymap includes the default keybindings when
1431 * listing all keys. */
1435 if (!append_keymap_request_keys(buf
, &pos
, request
, KEYMAP_GENERIC
, all
))
1436 return "Too many keybindings!";
1441 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1442 if (default_keybindings
[i
].request
== request
) {
1443 if (!append_key(buf
, &pos
, &default_keybindings
[i
]))
1444 return "Too many keybindings!";
1453 struct run_request
{
1456 const char *argv
[SIZEOF_ARG
];
1459 static struct run_request
*run_request
;
1460 static size_t run_requests
;
1462 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1465 add_run_request(enum keymap keymap
, int key
, int argc
, const char **argv
)
1467 struct run_request
*req
;
1469 if (argc
>= ARRAY_SIZE(req
->argv
) - 1)
1472 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1475 req
= &run_request
[run_requests
];
1476 req
->keymap
= keymap
;
1478 req
->argv
[0] = NULL
;
1480 if (!format_argv(req
->argv
, argv
, FORMAT_NONE
))
1483 return REQ_NONE
+ ++run_requests
;
1486 static struct run_request
*
1487 get_run_request(enum request request
)
1489 if (request
<= REQ_NONE
)
1491 return &run_request
[request
- REQ_NONE
- 1];
1495 add_builtin_run_requests(void)
1497 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1498 const char *commit
[] = { "git", "commit", NULL
};
1499 const char *gc
[] = { "git", "gc", NULL
};
1506 { KEYMAP_MAIN
, 'C', ARRAY_SIZE(cherry_pick
) - 1, cherry_pick
},
1507 { KEYMAP_STATUS
, 'C', ARRAY_SIZE(commit
) - 1, commit
},
1508 { KEYMAP_GENERIC
, 'G', ARRAY_SIZE(gc
) - 1, gc
},
1512 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1515 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argc
, reqs
[i
].argv
);
1516 if (req
!= REQ_NONE
)
1517 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1522 * User config file handling.
1525 static int config_lineno
;
1526 static bool config_errors
;
1527 static const char *config_msg
;
1529 static const struct enum_map color_map
[] = {
1530 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1542 static const struct enum_map attr_map
[] = {
1543 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1550 ATTR_MAP(UNDERLINE
),
1553 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1555 static int parse_step(double *opt
, const char *arg
)
1558 if (!strchr(arg
, '%'))
1561 /* "Shift down" so 100% and 1 does not conflict. */
1562 *opt
= (*opt
- 1) / 100;
1565 config_msg
= "Step value larger than 100%";
1570 config_msg
= "Invalid step value";
1577 parse_int(int *opt
, const char *arg
, int min
, int max
)
1579 int value
= atoi(arg
);
1581 if (min
<= value
&& value
<= max
) {
1586 config_msg
= "Integer value out of bound";
1591 set_color(int *color
, const char *name
)
1593 if (map_enum(color
, color_map
, name
))
1595 if (!prefixcmp(name
, "color"))
1596 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1600 /* Wants: object fgcolor bgcolor [attribute] */
1602 option_color_command(int argc
, const char *argv
[])
1604 struct line_info
*info
;
1607 config_msg
= "Wrong number of arguments given to color command";
1611 info
= get_line_info(argv
[0]);
1613 static const struct enum_map obsolete
[] = {
1614 ENUM_MAP("main-delim", LINE_DELIMITER
),
1615 ENUM_MAP("main-date", LINE_DATE
),
1616 ENUM_MAP("main-author", LINE_AUTHOR
),
1620 if (!map_enum(&index
, obsolete
, argv
[0])) {
1621 config_msg
= "Unknown color name";
1624 info
= &line_info
[index
];
1627 if (!set_color(&info
->fg
, argv
[1]) ||
1628 !set_color(&info
->bg
, argv
[2])) {
1629 config_msg
= "Unknown color";
1634 while (argc
-- > 3) {
1637 if (!set_attribute(&attr
, argv
[argc
])) {
1638 config_msg
= "Unknown attribute";
1647 static int parse_bool(bool *opt
, const char *arg
)
1649 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1655 parse_string(char *opt
, const char *arg
, size_t optsize
)
1657 int arglen
= strlen(arg
);
1662 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1663 config_msg
= "Unmatched quotation";
1666 arg
+= 1; arglen
-= 2;
1668 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1673 /* Wants: name = value */
1675 option_set_command(int argc
, const char *argv
[])
1678 config_msg
= "Wrong number of arguments given to set command";
1682 if (strcmp(argv
[1], "=")) {
1683 config_msg
= "No value assigned";
1687 if (!strcmp(argv
[0], "show-author"))
1688 return parse_bool(&opt_author
, argv
[2]);
1690 if (!strcmp(argv
[0], "show-date")) {
1693 if (!strcmp(argv
[2], "relative")) {
1694 opt_date
= DATE_RELATIVE
;
1696 } else if (!strcmp(argv
[2], "short")) {
1697 opt_date
= DATE_SHORT
;
1699 } else if (parse_bool(&show_date
, argv
[2])) {
1700 opt_date
= show_date
? DATE_DEFAULT
: DATE_NONE
;
1705 if (!strcmp(argv
[0], "show-rev-graph"))
1706 return parse_bool(&opt_rev_graph
, argv
[2]);
1708 if (!strcmp(argv
[0], "show-refs"))
1709 return parse_bool(&opt_show_refs
, argv
[2]);
1711 if (!strcmp(argv
[0], "show-line-numbers"))
1712 return parse_bool(&opt_line_number
, argv
[2]);
1714 if (!strcmp(argv
[0], "line-graphics"))
1715 return parse_bool(&opt_line_graphics
, argv
[2]);
1717 if (!strcmp(argv
[0], "line-number-interval"))
1718 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
1720 if (!strcmp(argv
[0], "author-width"))
1721 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
1723 if (!strcmp(argv
[0], "horizontal-scroll"))
1724 return parse_step(&opt_hscroll
, argv
[2]);
1726 if (!strcmp(argv
[0], "split-view-height"))
1727 return parse_step(&opt_scale_split_view
, argv
[2]);
1729 if (!strcmp(argv
[0], "tab-size"))
1730 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
1732 if (!strcmp(argv
[0], "commit-encoding"))
1733 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
1735 config_msg
= "Unknown variable name";
1739 /* Wants: mode request key */
1741 option_bind_command(int argc
, const char *argv
[])
1743 enum request request
;
1748 config_msg
= "Wrong number of arguments given to bind command";
1752 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
1753 config_msg
= "Unknown key map";
1757 key
= get_key_value(argv
[1]);
1759 config_msg
= "Unknown key";
1763 request
= get_request(argv
[2]);
1764 if (request
== REQ_NONE
) {
1765 static const struct enum_map obsolete
[] = {
1766 ENUM_MAP("cherry-pick", REQ_NONE
),
1767 ENUM_MAP("screen-resize", REQ_NONE
),
1768 ENUM_MAP("tree-parent", REQ_PARENT
),
1772 if (map_enum(&alias
, obsolete
, argv
[2])) {
1773 if (alias
!= REQ_NONE
)
1774 add_keybinding(keymap
, alias
, key
);
1775 config_msg
= "Obsolete request name";
1779 if (request
== REQ_NONE
&& *argv
[2]++ == '!')
1780 request
= add_run_request(keymap
, key
, argc
- 2, argv
+ 2);
1781 if (request
== REQ_NONE
) {
1782 config_msg
= "Unknown request name";
1786 add_keybinding(keymap
, request
, key
);
1792 set_option(const char *opt
, char *value
)
1794 const char *argv
[SIZEOF_ARG
];
1797 if (!argv_from_string(argv
, &argc
, value
)) {
1798 config_msg
= "Too many option arguments";
1802 if (!strcmp(opt
, "color"))
1803 return option_color_command(argc
, argv
);
1805 if (!strcmp(opt
, "set"))
1806 return option_set_command(argc
, argv
);
1808 if (!strcmp(opt
, "bind"))
1809 return option_bind_command(argc
, argv
);
1811 config_msg
= "Unknown option command";
1816 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
1821 config_msg
= "Internal error";
1823 /* Check for comment markers, since read_properties() will
1824 * only ensure opt and value are split at first " \t". */
1825 optlen
= strcspn(opt
, "#");
1829 if (opt
[optlen
] != 0) {
1830 config_msg
= "No option value";
1834 /* Look for comment endings in the value. */
1835 size_t len
= strcspn(value
, "#");
1837 if (len
< valuelen
) {
1839 value
[valuelen
] = 0;
1842 status
= set_option(opt
, value
);
1845 if (status
== ERR
) {
1846 warn("Error on line %d, near '%.*s': %s",
1847 config_lineno
, (int) optlen
, opt
, config_msg
);
1848 config_errors
= TRUE
;
1851 /* Always keep going if errors are encountered. */
1856 load_option_file(const char *path
)
1860 /* It's OK that the file doesn't exist. */
1861 if (!io_open(&io
, path
))
1865 config_errors
= FALSE
;
1867 if (io_load(&io
, " \t", read_option
) == ERR
||
1868 config_errors
== TRUE
)
1869 warn("Errors while loading %s.", path
);
1875 const char *home
= getenv("HOME");
1876 const char *tigrc_user
= getenv("TIGRC_USER");
1877 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
1878 char buf
[SIZEOF_STR
];
1880 add_builtin_run_requests();
1883 tigrc_system
= SYSCONFDIR
"/tigrc";
1884 load_option_file(tigrc_system
);
1887 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
1891 load_option_file(tigrc_user
);
1904 /* The display array of active views and the index of the current view. */
1905 static struct view
*display
[2];
1906 static unsigned int current_view
;
1908 #define foreach_displayed_view(view, i) \
1909 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1911 #define displayed_views() (display[1] != NULL ? 2 : 1)
1913 /* Current head and commit ID */
1914 static char ref_blob
[SIZEOF_REF
] = "";
1915 static char ref_commit
[SIZEOF_REF
] = "HEAD";
1916 static char ref_head
[SIZEOF_REF
] = "HEAD";
1919 const char *name
; /* View name */
1920 const char *cmd_env
; /* Command line set via environment */
1921 const char *id
; /* Points to either of ref_{head,commit,blob} */
1923 struct view_ops
*ops
; /* View operations */
1925 enum keymap keymap
; /* What keymap does this view have */
1926 bool git_dir
; /* Whether the view requires a git directory. */
1928 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
1929 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
1931 int height
, width
; /* The width and height of the main window */
1932 WINDOW
*win
; /* The main window */
1933 WINDOW
*title
; /* The title window living below the main window */
1936 unsigned long offset
; /* Offset of the window top */
1937 unsigned long yoffset
; /* Offset from the window side. */
1938 unsigned long lineno
; /* Current line number */
1939 unsigned long p_offset
; /* Previous offset of the window top */
1940 unsigned long p_yoffset
;/* Previous offset from the window side */
1941 unsigned long p_lineno
; /* Previous current line number */
1942 bool p_restore
; /* Should the previous position be restored. */
1945 char grep
[SIZEOF_STR
]; /* Search string */
1946 regex_t
*regex
; /* Pre-compiled regexp */
1948 /* If non-NULL, points to the view that opened this view. If this view
1949 * is closed tig will switch back to the parent view. */
1950 struct view
*parent
;
1953 size_t lines
; /* Total number of lines */
1954 struct line
*line
; /* Line index */
1955 unsigned int digits
; /* Number of digits in the lines member. */
1958 struct line
*curline
; /* Line currently being drawn. */
1959 enum line_type curtype
; /* Attribute currently used for drawing. */
1960 unsigned long col
; /* Column when drawing. */
1961 bool has_scrolled
; /* View was scrolled. */
1971 /* What type of content being displayed. Used in the title bar. */
1973 /* Default command arguments. */
1975 /* Open and reads in all view content. */
1976 bool (*open
)(struct view
*view
);
1977 /* Read one line; updates view->line. */
1978 bool (*read
)(struct view
*view
, char *data
);
1979 /* Draw one line; @lineno must be < view->height. */
1980 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
1981 /* Depending on view handle a special requests. */
1982 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
1983 /* Search for regexp in a line. */
1984 bool (*grep
)(struct view
*view
, struct line
*line
);
1986 void (*select
)(struct view
*view
, struct line
*line
);
1987 /* Prepare view for loading */
1988 bool (*prepare
)(struct view
*view
);
1991 static struct view_ops blame_ops
;
1992 static struct view_ops blob_ops
;
1993 static struct view_ops diff_ops
;
1994 static struct view_ops help_ops
;
1995 static struct view_ops log_ops
;
1996 static struct view_ops main_ops
;
1997 static struct view_ops pager_ops
;
1998 static struct view_ops stage_ops
;
1999 static struct view_ops status_ops
;
2000 static struct view_ops tree_ops
;
2001 static struct view_ops branch_ops
;
2003 #define VIEW_STR(name, env, ref, ops, map, git) \
2004 { name, #env, ref, ops, map, git }
2006 #define VIEW_(id, name, ops, git, ref) \
2007 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2010 static struct view views
[] = {
2011 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
2012 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
2013 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
2014 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
2015 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
2016 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
2017 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
2018 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
2019 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
2020 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
2021 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
2024 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2025 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2027 #define foreach_view(view, i) \
2028 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2030 #define view_is_displayed(view) \
2031 (view == display[0] || view == display[1])
2038 static chtype line_graphics
[] = {
2039 /* LINE_GRAPHIC_VLINE: */ '|'
2043 set_view_attr(struct view
*view
, enum line_type type
)
2045 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
2046 wattrset(view
->win
, get_line_attr(type
));
2047 wchgat(view
->win
, -1, 0, type
, NULL
);
2048 view
->curtype
= type
;
2053 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
2054 int max_len
, bool use_tilde
)
2058 int trimmed
= FALSE
;
2059 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2065 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
);
2067 col
= len
= strlen(string
);
2068 if (len
> max_len
) {
2072 col
= len
= max_len
;
2077 set_view_attr(view
, type
);
2079 waddnstr(view
->win
, string
, len
);
2080 if (trimmed
&& use_tilde
) {
2081 set_view_attr(view
, LINE_DELIMITER
);
2082 waddch(view
->win
, '~');
2090 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
2092 static char space
[] = " ";
2095 spaces
= MIN(max
, spaces
);
2097 while (spaces
> 0) {
2098 int len
= MIN(spaces
, sizeof(space
) - 1);
2100 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
2108 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
2110 view
->col
+= draw_chars(view
, type
, string
, view
->width
+ view
->yoffset
- view
->col
, trim
);
2111 return view
->width
+ view
->yoffset
<= view
->col
;
2115 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
2117 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2118 int max
= view
->width
+ view
->yoffset
- view
->col
;
2124 set_view_attr(view
, type
);
2125 /* Using waddch() instead of waddnstr() ensures that
2126 * they'll be rendered correctly for the cursor line. */
2127 for (i
= skip
; i
< size
; i
++)
2128 waddch(view
->win
, graphic
[i
]);
2131 if (size
< max
&& skip
<= size
)
2132 waddch(view
->win
, ' ');
2135 return view
->width
+ view
->yoffset
<= view
->col
;
2139 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2141 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2145 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2147 col
= draw_space(view
, type
, max
- 1, max
- 1);
2150 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2151 return view
->width
+ view
->yoffset
<= view
->col
;
2155 draw_date(struct view
*view
, time_t *time
)
2157 const char *date
= time
? mkdate(time
) : "";
2158 int cols
= opt_date
== DATE_SHORT
? DATE_SHORT_COLS
: DATE_COLS
;
2160 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2164 draw_author(struct view
*view
, const char *author
)
2166 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5 || !author
;
2169 static char initials
[10];
2172 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2174 memset(initials
, 0, sizeof(initials
));
2175 for (pos
= 0; *author
&& pos
< opt_author_cols
- 1; author
++, pos
++) {
2176 while (is_initial_sep(*author
))
2178 strncpy(&initials
[pos
], author
, sizeof(initials
) - 1 - pos
);
2179 while (*author
&& !is_initial_sep(author
[1]))
2186 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2190 draw_mode(struct view
*view
, mode_t mode
)
2196 else if (S_ISLNK(mode
))
2198 else if (S_ISGITLINK(mode
))
2200 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2202 else if (S_ISREG(mode
))
2207 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2211 draw_lineno(struct view
*view
, unsigned int lineno
)
2214 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2215 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2218 lineno
+= view
->offset
+ 1;
2219 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2220 static char fmt
[] = "%1ld";
2222 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2223 if (string_format(number
, fmt
, lineno
))
2227 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2229 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2230 return draw_graphic(view
, LINE_DEFAULT
, &line_graphics
[LINE_GRAPHIC_VLINE
], 1);
2234 draw_view_line(struct view
*view
, unsigned int lineno
)
2237 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2239 assert(view_is_displayed(view
));
2241 if (view
->offset
+ lineno
>= view
->lines
)
2244 line
= &view
->line
[view
->offset
+ lineno
];
2246 wmove(view
->win
, lineno
, 0);
2248 wclrtoeol(view
->win
);
2250 view
->curline
= line
;
2251 view
->curtype
= LINE_NONE
;
2252 line
->selected
= FALSE
;
2253 line
->dirty
= line
->cleareol
= 0;
2256 set_view_attr(view
, LINE_CURSOR
);
2257 line
->selected
= TRUE
;
2258 view
->ops
->select(view
, line
);
2261 return view
->ops
->draw(view
, line
, lineno
);
2265 redraw_view_dirty(struct view
*view
)
2270 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2271 if (view
->offset
+ lineno
>= view
->lines
)
2273 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2276 if (!draw_view_line(view
, lineno
))
2282 wnoutrefresh(view
->win
);
2286 redraw_view_from(struct view
*view
, int lineno
)
2288 assert(0 <= lineno
&& lineno
< view
->height
);
2290 for (; lineno
< view
->height
; lineno
++) {
2291 if (!draw_view_line(view
, lineno
))
2295 wnoutrefresh(view
->win
);
2299 redraw_view(struct view
*view
)
2302 redraw_view_from(view
, 0);
2307 update_view_title(struct view
*view
)
2309 char buf
[SIZEOF_STR
];
2310 char state
[SIZEOF_STR
];
2311 size_t bufpos
= 0, statelen
= 0;
2313 assert(view_is_displayed(view
));
2315 if (view
!= VIEW(REQ_VIEW_STATUS
) && view
->lines
) {
2316 unsigned int view_lines
= view
->offset
+ view
->height
;
2317 unsigned int lines
= view
->lines
2318 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2321 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2330 time_t secs
= time(NULL
) - view
->start_time
;
2332 /* Three git seconds are a long time ... */
2334 string_format_from(state
, &statelen
, " loading %lds", secs
);
2337 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2338 if (*view
->ref
&& bufpos
< view
->width
) {
2339 size_t refsize
= strlen(view
->ref
);
2340 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2342 if (minsize
< view
->width
)
2343 refsize
= view
->width
- minsize
+ 7;
2344 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2347 if (statelen
&& bufpos
< view
->width
) {
2348 string_format_from(buf
, &bufpos
, "%s", state
);
2351 if (view
== display
[current_view
])
2352 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2354 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2356 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2357 wclrtoeol(view
->title
);
2358 wnoutrefresh(view
->title
);
2362 apply_step(double step
, int value
)
2366 value
*= step
+ 0.01;
2367 return value
? value
: 1;
2371 resize_display(void)
2374 struct view
*base
= display
[0];
2375 struct view
*view
= display
[1] ? display
[1] : display
[0];
2377 /* Setup window dimensions */
2379 getmaxyx(stdscr
, base
->height
, base
->width
);
2381 /* Make room for the status window. */
2385 /* Horizontal split. */
2386 view
->width
= base
->width
;
2387 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2388 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2389 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2390 base
->height
-= view
->height
;
2392 /* Make room for the title bar. */
2396 /* Make room for the title bar. */
2401 foreach_displayed_view (view
, i
) {
2403 view
->win
= newwin(view
->height
, 0, offset
, 0);
2405 die("Failed to create %s view", view
->name
);
2407 scrollok(view
->win
, FALSE
);
2409 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2411 die("Failed to create title window");
2414 wresize(view
->win
, view
->height
, view
->width
);
2415 mvwin(view
->win
, offset
, 0);
2416 mvwin(view
->title
, offset
+ view
->height
, 0);
2419 offset
+= view
->height
+ 1;
2424 redraw_display(bool clear
)
2429 foreach_displayed_view (view
, i
) {
2433 update_view_title(view
);
2438 toggle_date_option(enum date
*date
)
2440 static const char *help
[] = {
2447 opt_date
= (opt_date
+ 1) % ARRAY_SIZE(help
);
2448 redraw_display(FALSE
);
2449 report("Displaying %s dates", help
[opt_date
]);
2453 toggle_view_option(bool *option
, const char *help
)
2456 redraw_display(FALSE
);
2457 report("%sabling %s", *option
? "En" : "Dis", help
);
2461 open_option_menu(void)
2463 const struct menu_item menu
[] = {
2464 { '.', "line numbers", &opt_line_number
},
2465 { 'D', "date display", &opt_date
},
2466 { 'A', "author display", &opt_author
},
2467 { 'g', "revision graph display", &opt_rev_graph
},
2468 { 'F', "reference display", &opt_show_refs
},
2473 if (prompt_menu("Toggle option", menu
, &selected
)) {
2474 if (menu
[selected
].data
== &opt_date
)
2475 toggle_date_option(menu
[selected
].data
);
2477 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2482 maximize_view(struct view
*view
)
2484 memset(display
, 0, sizeof(display
));
2486 display
[current_view
] = view
;
2488 redraw_display(FALSE
);
2498 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2500 if (lineno
>= view
->lines
)
2501 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2503 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2504 unsigned long half
= view
->height
/ 2;
2507 offset
= lineno
- half
;
2512 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2513 view
->offset
= offset
;
2514 view
->lineno
= lineno
;
2521 /* Scrolling backend */
2523 do_scroll_view(struct view
*view
, int lines
)
2525 bool redraw_current_line
= FALSE
;
2527 /* The rendering expects the new offset. */
2528 view
->offset
+= lines
;
2530 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2533 /* Move current line into the view. */
2534 if (view
->lineno
< view
->offset
) {
2535 view
->lineno
= view
->offset
;
2536 redraw_current_line
= TRUE
;
2537 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2538 view
->lineno
= view
->offset
+ view
->height
- 1;
2539 redraw_current_line
= TRUE
;
2542 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2544 /* Redraw the whole screen if scrolling is pointless. */
2545 if (view
->height
< ABS(lines
)) {
2549 int line
= lines
> 0 ? view
->height
- lines
: 0;
2550 int end
= line
+ ABS(lines
);
2552 scrollok(view
->win
, TRUE
);
2553 wscrl(view
->win
, lines
);
2554 scrollok(view
->win
, FALSE
);
2556 while (line
< end
&& draw_view_line(view
, line
))
2559 if (redraw_current_line
)
2560 draw_view_line(view
, view
->lineno
- view
->offset
);
2561 wnoutrefresh(view
->win
);
2564 view
->has_scrolled
= TRUE
;
2568 /* Scroll frontend */
2570 scroll_view(struct view
*view
, enum request request
)
2574 assert(view_is_displayed(view
));
2577 case REQ_SCROLL_LEFT
:
2578 if (view
->yoffset
== 0) {
2579 report("Cannot scroll beyond the first column");
2582 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2585 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2586 redraw_view_from(view
, 0);
2589 case REQ_SCROLL_RIGHT
:
2590 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2594 case REQ_SCROLL_PAGE_DOWN
:
2595 lines
= view
->height
;
2596 case REQ_SCROLL_LINE_DOWN
:
2597 if (view
->offset
+ lines
> view
->lines
)
2598 lines
= view
->lines
- view
->offset
;
2600 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2601 report("Cannot scroll beyond the last line");
2606 case REQ_SCROLL_PAGE_UP
:
2607 lines
= view
->height
;
2608 case REQ_SCROLL_LINE_UP
:
2609 if (lines
> view
->offset
)
2610 lines
= view
->offset
;
2613 report("Cannot scroll beyond the first line");
2621 die("request %d not handled in switch", request
);
2624 do_scroll_view(view
, lines
);
2629 move_view(struct view
*view
, enum request request
)
2631 int scroll_steps
= 0;
2635 case REQ_MOVE_FIRST_LINE
:
2636 steps
= -view
->lineno
;
2639 case REQ_MOVE_LAST_LINE
:
2640 steps
= view
->lines
- view
->lineno
- 1;
2643 case REQ_MOVE_PAGE_UP
:
2644 steps
= view
->height
> view
->lineno
2645 ? -view
->lineno
: -view
->height
;
2648 case REQ_MOVE_PAGE_DOWN
:
2649 steps
= view
->lineno
+ view
->height
>= view
->lines
2650 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2662 die("request %d not handled in switch", request
);
2665 if (steps
<= 0 && view
->lineno
== 0) {
2666 report("Cannot move beyond the first line");
2669 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2670 report("Cannot move beyond the last line");
2674 /* Move the current line */
2675 view
->lineno
+= steps
;
2676 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2678 /* Check whether the view needs to be scrolled */
2679 if (view
->lineno
< view
->offset
||
2680 view
->lineno
>= view
->offset
+ view
->height
) {
2681 scroll_steps
= steps
;
2682 if (steps
< 0 && -steps
> view
->offset
) {
2683 scroll_steps
= -view
->offset
;
2685 } else if (steps
> 0) {
2686 if (view
->lineno
== view
->lines
- 1 &&
2687 view
->lines
> view
->height
) {
2688 scroll_steps
= view
->lines
- view
->offset
- 1;
2689 if (scroll_steps
>= view
->height
)
2690 scroll_steps
-= view
->height
- 1;
2695 if (!view_is_displayed(view
)) {
2696 view
->offset
+= scroll_steps
;
2697 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2698 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2702 /* Repaint the old "current" line if we be scrolling */
2703 if (ABS(steps
) < view
->height
)
2704 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
2707 do_scroll_view(view
, scroll_steps
);
2711 /* Draw the current line */
2712 draw_view_line(view
, view
->lineno
- view
->offset
);
2714 wnoutrefresh(view
->win
);
2723 static void search_view(struct view
*view
, enum request request
);
2726 grep_text(struct view
*view
, const char *text
[])
2731 for (i
= 0; text
[i
]; i
++)
2733 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
2739 select_view_line(struct view
*view
, unsigned long lineno
)
2741 unsigned long old_lineno
= view
->lineno
;
2742 unsigned long old_offset
= view
->offset
;
2744 if (goto_view_line(view
, view
->offset
, lineno
)) {
2745 if (view_is_displayed(view
)) {
2746 if (old_offset
!= view
->offset
) {
2749 draw_view_line(view
, old_lineno
- view
->offset
);
2750 draw_view_line(view
, view
->lineno
- view
->offset
);
2751 wnoutrefresh(view
->win
);
2754 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2760 find_next(struct view
*view
, enum request request
)
2762 unsigned long lineno
= view
->lineno
;
2767 report("No previous search");
2769 search_view(view
, request
);
2779 case REQ_SEARCH_BACK
:
2788 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
2789 lineno
+= direction
;
2791 /* Note, lineno is unsigned long so will wrap around in which case it
2792 * will become bigger than view->lines. */
2793 for (; lineno
< view
->lines
; lineno
+= direction
) {
2794 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
2795 select_view_line(view
, lineno
);
2796 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
2801 report("No match found for '%s'", view
->grep
);
2805 search_view(struct view
*view
, enum request request
)
2810 regfree(view
->regex
);
2813 view
->regex
= calloc(1, sizeof(*view
->regex
));
2818 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
2819 if (regex_err
!= 0) {
2820 char buf
[SIZEOF_STR
] = "unknown error";
2822 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
2823 report("Search failed: %s", buf
);
2827 string_copy(view
->grep
, opt_search
);
2829 find_next(view
, request
);
2833 * Incremental updating
2837 reset_view(struct view
*view
)
2841 for (i
= 0; i
< view
->lines
; i
++)
2842 free(view
->line
[i
].data
);
2845 view
->p_offset
= view
->offset
;
2846 view
->p_yoffset
= view
->yoffset
;
2847 view
->p_lineno
= view
->lineno
;
2855 view
->update_secs
= 0;
2859 free_argv(const char *argv
[])
2863 for (argc
= 0; argv
[argc
]; argc
++)
2864 free((void *) argv
[argc
]);
2868 format_argv(const char *dst_argv
[], const char *src_argv
[], enum format_flags flags
)
2870 char buf
[SIZEOF_STR
];
2872 bool noreplace
= flags
== FORMAT_NONE
;
2874 free_argv(dst_argv
);
2876 for (argc
= 0; src_argv
[argc
]; argc
++) {
2877 const char *arg
= src_argv
[argc
];
2881 char *next
= strstr(arg
, "%(");
2882 int len
= next
- arg
;
2885 if (!next
|| noreplace
) {
2886 if (flags
== FORMAT_DASH
&& !strcmp(arg
, "--"))
2891 } else if (!prefixcmp(next
, "%(directory)")) {
2894 } else if (!prefixcmp(next
, "%(file)")) {
2897 } else if (!prefixcmp(next
, "%(ref)")) {
2898 value
= *opt_ref
? opt_ref
: "HEAD";
2900 } else if (!prefixcmp(next
, "%(head)")) {
2903 } else if (!prefixcmp(next
, "%(commit)")) {
2906 } else if (!prefixcmp(next
, "%(blob)")) {
2910 report("Unknown replacement: `%s`", next
);
2914 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
2917 arg
= next
&& !noreplace
? strchr(next
, ')') + 1 : NULL
;
2920 dst_argv
[argc
] = strdup(buf
);
2921 if (!dst_argv
[argc
])
2925 dst_argv
[argc
] = NULL
;
2927 return src_argv
[argc
] == NULL
;
2931 restore_view_position(struct view
*view
)
2933 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
2936 /* Changing the view position cancels the restoring. */
2937 /* FIXME: Changing back to the first line is not detected. */
2938 if (view
->offset
!= 0 || view
->lineno
!= 0) {
2939 view
->p_restore
= FALSE
;
2943 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
2944 view_is_displayed(view
))
2947 view
->yoffset
= view
->p_yoffset
;
2948 view
->p_restore
= FALSE
;
2954 end_update(struct view
*view
, bool force
)
2958 while (!view
->ops
->read(view
, NULL
))
2961 set_nonblocking_input(FALSE
);
2963 kill_io(view
->pipe
);
2964 done_io(view
->pipe
);
2969 setup_update(struct view
*view
, const char *vid
)
2971 set_nonblocking_input(TRUE
);
2973 string_copy_rev(view
->vid
, vid
);
2974 view
->pipe
= &view
->io
;
2975 view
->start_time
= time(NULL
);
2979 prepare_update(struct view
*view
, const char *argv
[], const char *dir
,
2980 enum format_flags flags
)
2983 end_update(view
, TRUE
);
2984 return init_io_rd(&view
->io
, argv
, dir
, flags
);
2988 prepare_update_file(struct view
*view
, const char *name
)
2991 end_update(view
, TRUE
);
2992 return io_open(&view
->io
, name
);
2996 begin_update(struct view
*view
, bool refresh
)
2999 end_update(view
, TRUE
);
3002 if (view
->ops
->prepare
) {
3003 if (!view
->ops
->prepare(view
))
3005 } else if (!init_io_rd(&view
->io
, view
->ops
->argv
, NULL
, FORMAT_ALL
)) {
3009 /* Put the current ref_* value to the view title ref
3010 * member. This is needed by the blob view. Most other
3011 * views sets it automatically after loading because the
3012 * first line is a commit line. */
3013 string_copy_rev(view
->ref
, view
->id
);
3016 if (!start_io(&view
->io
))
3019 setup_update(view
, view
->id
);
3025 update_view(struct view
*view
)
3027 char out_buffer
[BUFSIZ
* 2];
3029 /* Clear the view and redraw everything since the tree sorting
3030 * might have rearranged things. */
3031 bool redraw
= view
->lines
== 0;
3032 bool can_read
= TRUE
;
3037 if (!io_can_read(view
->pipe
)) {
3038 if (view
->lines
== 0 && view_is_displayed(view
)) {
3039 time_t secs
= time(NULL
) - view
->start_time
;
3041 if (secs
> 1 && secs
> view
->update_secs
) {
3042 if (view
->update_secs
== 0)
3044 update_view_title(view
);
3045 view
->update_secs
= secs
;
3051 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
3052 if (opt_iconv
!= ICONV_NONE
) {
3053 ICONV_CONST
char *inbuf
= line
;
3054 size_t inlen
= strlen(line
) + 1;
3056 char *outbuf
= out_buffer
;
3057 size_t outlen
= sizeof(out_buffer
);
3061 ret
= iconv(opt_iconv
, &inbuf
, &inlen
, &outbuf
, &outlen
);
3062 if (ret
!= (size_t) -1)
3066 if (!view
->ops
->read(view
, line
)) {
3067 report("Allocation failure");
3068 end_update(view
, TRUE
);
3074 unsigned long lines
= view
->lines
;
3077 for (digits
= 0; lines
; digits
++)
3080 /* Keep the displayed view in sync with line number scaling. */
3081 if (digits
!= view
->digits
) {
3082 view
->digits
= digits
;
3083 if (opt_line_number
|| view
== VIEW(REQ_VIEW_BLAME
))
3088 if (io_error(view
->pipe
)) {
3089 report("Failed to read: %s", io_strerror(view
->pipe
));
3090 end_update(view
, TRUE
);
3092 } else if (io_eof(view
->pipe
)) {
3094 end_update(view
, FALSE
);
3097 if (restore_view_position(view
))
3100 if (!view_is_displayed(view
))
3104 redraw_view_from(view
, 0);
3106 redraw_view_dirty(view
);
3108 /* Update the title _after_ the redraw so that if the redraw picks up a
3109 * commit reference in view->ref it'll be available here. */
3110 update_view_title(view
);
3114 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
3116 static struct line
*
3117 add_line_data(struct view
*view
, void *data
, enum line_type type
)
3121 if (!realloc_lines(&view
->line
, view
->lines
, 1))
3124 line
= &view
->line
[view
->lines
++];
3125 memset(line
, 0, sizeof(*line
));
3133 static struct line
*
3134 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
3136 char *data
= text
? strdup(text
) : NULL
;
3138 return data
? add_line_data(view
, data
, type
) : NULL
;
3141 static struct line
*
3142 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3144 char buf
[SIZEOF_STR
];
3147 va_start(args
, fmt
);
3148 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3152 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3160 OPEN_DEFAULT
= 0, /* Use default view switching. */
3161 OPEN_SPLIT
= 1, /* Split current view. */
3162 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3163 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3164 OPEN_PREPARED
= 32, /* Open already prepared command. */
3168 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3170 bool split
= !!(flags
& OPEN_SPLIT
);
3171 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3172 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3173 struct view
*view
= VIEW(request
);
3174 int nviews
= displayed_views();
3175 struct view
*base_view
= display
[0];
3177 if (view
== prev
&& nviews
== 1 && !reload
) {
3178 report("Already in %s view", view
->name
);
3182 if (view
->git_dir
&& !opt_git_dir
[0]) {
3183 report("The %s view is disabled in pager view", view
->name
);
3190 } else if (!nomaximize
) {
3191 /* Maximize the current view. */
3192 memset(display
, 0, sizeof(display
));
3194 display
[current_view
] = view
;
3197 /* No parent signals that this is the first loaded view. */
3198 if (prev
&& view
!= prev
) {
3199 view
->parent
= prev
;
3202 /* Resize the view when switching between split- and full-screen,
3203 * or when switching between two different full-screen views. */
3204 if (nviews
!= displayed_views() ||
3205 (nviews
== 1 && base_view
!= display
[0]))
3208 if (view
->ops
->open
) {
3210 end_update(view
, TRUE
);
3211 if (!view
->ops
->open(view
)) {
3212 report("Failed to load %s view", view
->name
);
3215 restore_view_position(view
);
3217 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3218 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3219 report("Failed to load %s view", view
->name
);
3223 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3224 /* Take the title line into account. */
3225 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3227 /* Scroll the view that was split if the current line is
3228 * outside the new limited view. */
3229 do_scroll_view(prev
, lines
);
3232 if (prev
&& view
!= prev
) {
3234 /* "Blur" the previous view. */
3235 update_view_title(prev
);
3239 if (view
->pipe
&& view
->lines
== 0) {
3240 /* Clear the old view and let the incremental updating refill
3243 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3245 } else if (view_is_displayed(view
)) {
3252 open_external_viewer(const char *argv
[], const char *dir
)
3254 def_prog_mode(); /* save current tty modes */
3255 endwin(); /* restore original tty modes */
3256 run_io_fg(argv
, dir
);
3257 fprintf(stderr
, "Press Enter to continue");
3260 redraw_display(TRUE
);
3264 open_mergetool(const char *file
)
3266 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3268 open_external_viewer(mergetool_argv
, opt_cdup
);
3272 open_editor(bool from_root
, const char *file
)
3274 const char *editor_argv
[] = { "vi", file
, NULL
};
3277 editor
= getenv("GIT_EDITOR");
3278 if (!editor
&& *opt_editor
)
3279 editor
= opt_editor
;
3281 editor
= getenv("VISUAL");
3283 editor
= getenv("EDITOR");
3287 editor_argv
[0] = editor
;
3288 open_external_viewer(editor_argv
, from_root
? opt_cdup
: NULL
);
3292 open_run_request(enum request request
)
3294 struct run_request
*req
= get_run_request(request
);
3295 const char *argv
[ARRAY_SIZE(req
->argv
)] = { NULL
};
3298 report("Unknown run request");
3302 if (format_argv(argv
, req
->argv
, FORMAT_ALL
))
3303 open_external_viewer(argv
, NULL
);
3308 * User request switch noodle
3312 view_driver(struct view
*view
, enum request request
)
3316 if (request
== REQ_NONE
)
3319 if (request
> REQ_NONE
) {
3320 open_run_request(request
);
3321 /* FIXME: When all views can refresh always do this. */
3322 if (view
== VIEW(REQ_VIEW_STATUS
) ||
3323 view
== VIEW(REQ_VIEW_MAIN
) ||
3324 view
== VIEW(REQ_VIEW_LOG
) ||
3325 view
== VIEW(REQ_VIEW_BRANCH
) ||
3326 view
== VIEW(REQ_VIEW_STAGE
))
3327 request
= REQ_REFRESH
;
3332 if (view
&& view
->lines
) {
3333 request
= view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
3334 if (request
== REQ_NONE
)
3341 case REQ_MOVE_PAGE_UP
:
3342 case REQ_MOVE_PAGE_DOWN
:
3343 case REQ_MOVE_FIRST_LINE
:
3344 case REQ_MOVE_LAST_LINE
:
3345 move_view(view
, request
);
3348 case REQ_SCROLL_LEFT
:
3349 case REQ_SCROLL_RIGHT
:
3350 case REQ_SCROLL_LINE_DOWN
:
3351 case REQ_SCROLL_LINE_UP
:
3352 case REQ_SCROLL_PAGE_DOWN
:
3353 case REQ_SCROLL_PAGE_UP
:
3354 scroll_view(view
, request
);
3357 case REQ_VIEW_BLAME
:
3359 report("No file chosen, press %s to open tree view",
3360 get_key(view
->keymap
, REQ_VIEW_TREE
));
3363 open_view(view
, request
, OPEN_DEFAULT
);
3368 report("No file chosen, press %s to open tree view",
3369 get_key(view
->keymap
, REQ_VIEW_TREE
));
3372 open_view(view
, request
, OPEN_DEFAULT
);
3375 case REQ_VIEW_PAGER
:
3376 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3377 report("No pager content, press %s to run command from prompt",
3378 get_key(view
->keymap
, REQ_PROMPT
));
3381 open_view(view
, request
, OPEN_DEFAULT
);
3384 case REQ_VIEW_STAGE
:
3385 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3386 report("No stage content, press %s to open the status view and choose file",
3387 get_key(view
->keymap
, REQ_VIEW_STATUS
));
3390 open_view(view
, request
, OPEN_DEFAULT
);
3393 case REQ_VIEW_STATUS
:
3394 if (opt_is_inside_work_tree
== FALSE
) {
3395 report("The status view requires a working tree");
3398 open_view(view
, request
, OPEN_DEFAULT
);
3406 case REQ_VIEW_BRANCH
:
3407 open_view(view
, request
, OPEN_DEFAULT
);
3412 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3414 if ((view
== VIEW(REQ_VIEW_DIFF
) &&
3415 view
->parent
== VIEW(REQ_VIEW_MAIN
)) ||
3416 (view
== VIEW(REQ_VIEW_DIFF
) &&
3417 view
->parent
== VIEW(REQ_VIEW_BLAME
)) ||
3418 (view
== VIEW(REQ_VIEW_STAGE
) &&
3419 view
->parent
== VIEW(REQ_VIEW_STATUS
)) ||
3420 (view
== VIEW(REQ_VIEW_BLOB
) &&
3421 view
->parent
== VIEW(REQ_VIEW_TREE
)) ||
3422 (view
== VIEW(REQ_VIEW_MAIN
) &&
3423 view
->parent
== VIEW(REQ_VIEW_BRANCH
))) {
3426 view
= view
->parent
;
3427 line
= view
->lineno
;
3428 move_view(view
, request
);
3429 if (view_is_displayed(view
))
3430 update_view_title(view
);
3431 if (line
!= view
->lineno
)
3432 view
->ops
->request(view
, REQ_ENTER
,
3433 &view
->line
[view
->lineno
]);
3436 move_view(view
, request
);
3442 int nviews
= displayed_views();
3443 int next_view
= (current_view
+ 1) % nviews
;
3445 if (next_view
== current_view
) {
3446 report("Only one view is displayed");
3450 current_view
= next_view
;
3451 /* Blur out the title of the previous view. */
3452 update_view_title(view
);
3457 report("Refreshing is not yet supported for the %s view", view
->name
);
3461 if (displayed_views() == 2)
3462 maximize_view(view
);
3469 case REQ_TOGGLE_LINENO
:
3470 toggle_view_option(&opt_line_number
, "line numbers");
3473 case REQ_TOGGLE_DATE
:
3474 toggle_date_option(&opt_date
);
3477 case REQ_TOGGLE_AUTHOR
:
3478 toggle_view_option(&opt_author
, "author display");
3481 case REQ_TOGGLE_REV_GRAPH
:
3482 toggle_view_option(&opt_rev_graph
, "revision graph display");
3485 case REQ_TOGGLE_REFS
:
3486 toggle_view_option(&opt_show_refs
, "reference display");
3489 case REQ_TOGGLE_SORT_FIELD
:
3490 case REQ_TOGGLE_SORT_ORDER
:
3491 report("Sorting is not yet supported for the %s view", view
->name
);
3495 case REQ_SEARCH_BACK
:
3496 search_view(view
, request
);
3501 find_next(view
, request
);
3504 case REQ_STOP_LOADING
:
3505 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
3508 report("Stopped loading the %s view", view
->name
),
3509 end_update(view
, TRUE
);
3513 case REQ_SHOW_VERSION
:
3514 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3517 case REQ_SCREEN_REDRAW
:
3518 redraw_display(TRUE
);
3522 report("Nothing to edit");
3526 report("Nothing to enter");
3529 case REQ_VIEW_CLOSE
:
3530 /* XXX: Mark closed views by letting view->parent point to the
3531 * view itself. Parents to closed view should never be
3534 view
->parent
->parent
!= view
->parent
) {
3535 maximize_view(view
->parent
);
3536 view
->parent
= view
;
3544 report("Unknown key, press %s for help",
3545 get_key(view
->keymap
, REQ_VIEW_HELP
));
3554 * View backend utilities
3564 const enum sort_field
*fields
;
3565 size_t size
, current
;
3569 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3570 #define get_sort_field(state) ((state).fields[(state).current])
3571 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3574 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3575 int (*compare
)(const void *, const void *))
3578 case REQ_TOGGLE_SORT_FIELD
:
3579 state
->current
= (state
->current
+ 1) % state
->size
;
3582 case REQ_TOGGLE_SORT_ORDER
:
3583 state
->reverse
= !state
->reverse
;
3586 die("Not a sort request");
3589 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3593 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3595 /* Small author cache to reduce memory consumption. It uses binary
3596 * search to lookup or find place to position new entries. No entries
3597 * are ever freed. */
3599 get_author(const char *name
)
3601 static const char **authors
;
3602 static size_t authors_size
;
3603 int from
= 0, to
= authors_size
- 1;
3605 while (from
<= to
) {
3606 size_t pos
= (to
+ from
) / 2;
3607 int cmp
= strcmp(name
, authors
[pos
]);
3610 return authors
[pos
];
3618 if (!realloc_authors(&authors
, authors_size
, 1))
3620 name
= strdup(name
);
3624 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3625 authors
[from
] = name
;
3632 parse_timezone(time_t *time
, const char *zone
)
3636 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3637 tz
+= ('0' - zone
[2]) * 60 * 60;
3638 tz
+= ('0' - zone
[3]) * 60;
3639 tz
+= ('0' - zone
[4]);
3647 /* Parse author lines where the name may be empty:
3648 * author <email@address.tld> 1138474660 +0100
3651 parse_author_line(char *ident
, const char **author
, time_t *time
)
3653 char *nameend
= strchr(ident
, '<');
3654 char *emailend
= strchr(ident
, '>');
3656 if (nameend
&& emailend
)
3657 *nameend
= *emailend
= 0;
3658 ident
= chomp_string(ident
);
3661 ident
= chomp_string(nameend
+ 1);
3666 *author
= get_author(ident
);
3668 /* Parse epoch and timezone */
3669 if (emailend
&& emailend
[1] == ' ') {
3670 char *secs
= emailend
+ 2;
3671 char *zone
= strchr(secs
, ' ');
3673 *time
= (time_t) atol(secs
);
3675 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
3676 parse_timezone(time
, zone
+ 1);
3681 open_commit_parent_menu(char buf
[SIZEOF_STR
], int *parents
)
3683 char rev
[SIZEOF_REV
];
3684 const char *revlist_argv
[] = {
3685 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev
, NULL
3687 struct menu_item
*items
;
3688 char text
[SIZEOF_STR
];
3692 items
= calloc(*parents
+ 1, sizeof(*items
));
3696 for (i
= 0; i
< *parents
; i
++) {
3697 string_copy_rev(rev
, &buf
[SIZEOF_REV
* i
]);
3698 if (!run_io_buf(revlist_argv
, text
, sizeof(text
)) ||
3699 !(items
[i
].text
= strdup(text
))) {
3707 ok
= prompt_menu("Select parent", items
, parents
);
3709 for (i
= 0; items
[i
].text
; i
++)
3710 free((char *) items
[i
].text
);
3716 select_commit_parent(const char *id
, char rev
[SIZEOF_REV
], const char *path
)
3718 char buf
[SIZEOF_STR
* 4];
3719 const char *revlist_argv
[] = {
3720 "git", "log", "--no-color", "-1",
3721 "--pretty=format:%P", id
, "--", path
, NULL
3725 if (!run_io_buf(revlist_argv
, buf
, sizeof(buf
)) ||
3726 (parents
= strlen(buf
) / 40) < 0) {
3727 report("Failed to get parent information");
3730 } else if (parents
== 0) {
3732 report("Path '%s' does not exist in the parent", path
);
3734 report("The selected commit has no parents");
3738 if (parents
> 1 && !open_commit_parent_menu(buf
, &parents
))
3741 string_copy_rev(rev
, &buf
[41 * parents
]);
3750 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
3752 char text
[SIZEOF_STR
];
3754 if (opt_line_number
&& draw_lineno(view
, lineno
))
3757 string_expand(text
, sizeof(text
), line
->data
, opt_tab_size
);
3758 draw_text(view
, line
->type
, text
, TRUE
);
3763 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
3765 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
3766 char ref
[SIZEOF_STR
];
3768 if (!run_io_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
3771 /* This is the only fatal call, since it can "corrupt" the buffer. */
3772 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
3779 add_pager_refs(struct view
*view
, struct line
*line
)
3781 char buf
[SIZEOF_STR
];
3782 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
3783 struct ref_list
*list
;
3784 size_t bufpos
= 0, i
;
3785 const char *sep
= "Refs: ";
3786 bool is_tag
= FALSE
;
3788 assert(line
->type
== LINE_COMMIT
);
3790 list
= get_ref_list(commit_id
);
3792 if (view
== VIEW(REQ_VIEW_DIFF
))
3793 goto try_add_describe_ref
;
3797 for (i
= 0; i
< list
->size
; i
++) {
3798 struct ref
*ref
= list
->refs
[i
];
3799 const char *fmt
= ref
->tag
? "%s[%s]" :
3800 ref
->remote
? "%s<%s>" : "%s%s";
3802 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
3809 if (!is_tag
&& view
== VIEW(REQ_VIEW_DIFF
)) {
3810 try_add_describe_ref
:
3811 /* Add <tag>-g<commit_id> "fake" reference. */
3812 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
3819 add_line_text(view
, buf
, LINE_PP_REFS
);
3823 pager_read(struct view
*view
, char *data
)
3830 line
= add_line_text(view
, data
, get_line_type(data
));
3834 if (line
->type
== LINE_COMMIT
&&
3835 (view
== VIEW(REQ_VIEW_DIFF
) ||
3836 view
== VIEW(REQ_VIEW_LOG
)))
3837 add_pager_refs(view
, line
);
3843 pager_request(struct view
*view
, enum request request
, struct line
*line
)
3847 if (request
!= REQ_ENTER
)
3850 if (line
->type
== LINE_COMMIT
&&
3851 (view
== VIEW(REQ_VIEW_LOG
) ||
3852 view
== VIEW(REQ_VIEW_PAGER
))) {
3853 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
3857 /* Always scroll the view even if it was split. That way
3858 * you can use Enter to scroll through the log view and
3859 * split open each commit diff. */
3860 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
3862 /* FIXME: A minor workaround. Scrolling the view will call report("")
3863 * but if we are scrolling a non-current view this won't properly
3864 * update the view title. */
3866 update_view_title(view
);
3872 pager_grep(struct view
*view
, struct line
*line
)
3874 const char *text
[] = { line
->data
, NULL
};
3876 return grep_text(view
, text
);
3880 pager_select(struct view
*view
, struct line
*line
)
3882 if (line
->type
== LINE_COMMIT
) {
3883 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
3885 if (view
!= VIEW(REQ_VIEW_PAGER
))
3886 string_copy_rev(view
->ref
, text
);
3887 string_copy_rev(ref_commit
, text
);
3891 static struct view_ops pager_ops
= {
3902 static const char *log_argv
[SIZEOF_ARG
] = {
3903 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3907 log_request(struct view
*view
, enum request request
, struct line
*line
)
3912 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
3915 return pager_request(view
, request
, line
);
3919 static struct view_ops log_ops
= {
3930 static const char *diff_argv
[SIZEOF_ARG
] = {
3931 "git", "show", "--pretty=fuller", "--no-color", "--root",
3932 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3935 static struct view_ops diff_ops
= {
3950 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
3953 help_name(char buf
[SIZEOF_STR
], const char *name
, size_t namelen
)
3957 for (bufpos
= 0; bufpos
<= namelen
; bufpos
++) {
3958 buf
[bufpos
] = tolower(name
[bufpos
]);
3959 if (buf
[bufpos
] == '_')
3967 #define help_keymap_name(buf, keymap) \
3968 help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3971 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
3973 char buf
[SIZEOF_STR
];
3976 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
3977 help_keymap_hidden
[keymap
] ? '+' : '-',
3978 help_keymap_name(buf
, keymap
));
3980 line
->other
= keymap
;
3982 return help_keymap_hidden
[keymap
];
3986 help_open_keymap(struct view
*view
, enum keymap keymap
)
3988 const char *group
= NULL
;
3989 char buf
[SIZEOF_STR
];
3991 bool add_title
= TRUE
;
3994 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
3995 const char *key
= NULL
;
3997 if (req_info
[i
].request
== REQ_NONE
)
4000 if (!req_info
[i
].request
) {
4001 group
= req_info
[i
].help
;
4005 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4009 if (add_title
&& help_open_keymap_title(view
, keymap
))
4014 add_line_text(view
, group
, LINE_HELP_GROUP
);
4018 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4019 help_name(buf
, req_info
[i
].name
, req_info
[i
].namelen
),
4023 group
= "External commands:";
4025 for (i
= 0; i
< run_requests
; i
++) {
4026 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4030 if (!req
|| req
->keymap
!= keymap
)
4033 key
= get_key_name(req
->key
);
4035 key
= "(no key defined)";
4037 if (add_title
&& help_open_keymap_title(view
, keymap
))
4040 add_line_text(view
, group
, LINE_HELP_GROUP
);
4044 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4045 if (!string_format_from(buf
, &bufpos
, "%s%s",
4046 argc
? " " : "", req
->argv
[argc
]))
4049 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4054 help_open(struct view
*view
)
4059 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4060 add_line_text(view
, "", LINE_DEFAULT
);
4062 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4063 help_open_keymap(view
, keymap
);
4069 help_request(struct view
*view
, enum request request
, struct line
*line
)
4073 if (line
->type
== LINE_HELP_KEYMAP
) {
4074 help_keymap_hidden
[line
->other
] =
4075 !help_keymap_hidden
[line
->other
];
4076 view
->p_restore
= TRUE
;
4077 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4082 return pager_request(view
, request
, line
);
4086 static struct view_ops help_ops
= {
4102 struct tree_stack_entry
{
4103 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4104 unsigned long lineno
; /* Line number to restore */
4105 char *name
; /* Position of name in opt_path */
4108 /* The top of the path stack. */
4109 static struct tree_stack_entry
*tree_stack
= NULL
;
4110 unsigned long tree_lineno
= 0;
4113 pop_tree_stack_entry(void)
4115 struct tree_stack_entry
*entry
= tree_stack
;
4117 tree_lineno
= entry
->lineno
;
4119 tree_stack
= entry
->prev
;
4124 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4126 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4127 size_t pathlen
= strlen(opt_path
);
4132 entry
->prev
= tree_stack
;
4133 entry
->name
= opt_path
+ pathlen
;
4136 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4137 pop_tree_stack_entry();
4141 /* Move the current line to the first tree entry. */
4143 entry
->lineno
= lineno
;
4146 /* Parse output from git-ls-tree(1):
4148 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4151 #define SIZEOF_TREE_ATTR \
4152 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4154 #define SIZEOF_TREE_MODE \
4155 STRING_SIZE("100644 ")
4157 #define TREE_ID_OFFSET \
4158 STRING_SIZE("100644 blob ")
4161 char id
[SIZEOF_REV
];
4163 time_t time
; /* Date from the author ident. */
4164 const char *author
; /* Author of the commit. */
4169 tree_path(const struct line
*line
)
4171 return ((struct tree_entry
*) line
->data
)->name
;
4175 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4177 if (line1
->type
!= line2
->type
)
4178 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4179 return strcmp(tree_path(line1
), tree_path(line2
));
4182 static const enum sort_field tree_sort_fields
[] = {
4183 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4185 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4188 tree_compare(const void *l1
, const void *l2
)
4190 const struct line
*line1
= (const struct line
*) l1
;
4191 const struct line
*line2
= (const struct line
*) l2
;
4192 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4193 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4195 if (line1
->type
== LINE_TREE_HEAD
)
4197 if (line2
->type
== LINE_TREE_HEAD
)
4200 switch (get_sort_field(tree_sort_state
)) {
4202 return sort_order(tree_sort_state
, entry1
->time
- entry2
->time
);
4204 case ORDERBY_AUTHOR
:
4205 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4209 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4214 static struct line
*
4215 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4216 const char *mode
, const char *id
)
4218 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4219 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4221 if (!entry
|| !line
) {
4226 strncpy(entry
->name
, path
, strlen(path
));
4228 entry
->mode
= strtoul(mode
, NULL
, 8);
4230 string_copy_rev(entry
->id
, id
);
4236 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4238 static const char *author_name
;
4239 static time_t author_time
;
4241 if (!text
&& *read_date
) {
4246 char *path
= *opt_path
? opt_path
: ".";
4247 /* Find next entry to process */
4248 const char *log_file
[] = {
4249 "git", "log", "--no-color", "--pretty=raw",
4250 "--cc", "--raw", view
->id
, "--", path
, NULL
4255 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4256 report("Tree is empty");
4260 if (!run_io_rd_dir(&io
, log_file
, opt_cdup
, FORMAT_NONE
)) {
4261 report("Failed to load tree data");
4265 done_io(view
->pipe
);
4270 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4271 parse_author_line(text
+ STRING_SIZE("author "),
4272 &author_name
, &author_time
);
4274 } else if (*text
== ':') {
4276 size_t annotated
= 1;
4279 pos
= strchr(text
, '\t');
4283 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4284 text
+= strlen(opt_path
);
4285 pos
= strchr(text
, '/');
4289 for (i
= 1; i
< view
->lines
; i
++) {
4290 struct line
*line
= &view
->line
[i
];
4291 struct tree_entry
*entry
= line
->data
;
4293 annotated
+= !!entry
->author
;
4294 if (entry
->author
|| strcmp(entry
->name
, text
))
4297 entry
->author
= author_name
;
4298 entry
->time
= author_time
;
4303 if (annotated
== view
->lines
)
4304 kill_io(view
->pipe
);
4310 tree_read(struct view
*view
, char *text
)
4312 static bool read_date
= FALSE
;
4313 struct tree_entry
*data
;
4314 struct line
*entry
, *line
;
4315 enum line_type type
;
4316 size_t textlen
= text
? strlen(text
) : 0;
4317 char *path
= text
+ SIZEOF_TREE_ATTR
;
4319 if (read_date
|| !text
)
4320 return tree_read_date(view
, text
, &read_date
);
4322 if (textlen
<= SIZEOF_TREE_ATTR
)
4324 if (view
->lines
== 0 &&
4325 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4328 /* Strip the path part ... */
4330 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4331 size_t striplen
= strlen(opt_path
);
4333 if (pathlen
> striplen
)
4334 memmove(path
, path
+ striplen
,
4335 pathlen
- striplen
+ 1);
4337 /* Insert "link" to parent directory. */
4338 if (view
->lines
== 1 &&
4339 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4343 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4344 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4349 /* Skip "Directory ..." and ".." line. */
4350 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4351 if (tree_compare_entry(line
, entry
) <= 0)
4354 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4358 for (; line
<= entry
; line
++)
4359 line
->dirty
= line
->cleareol
= 1;
4363 if (tree_lineno
> view
->lineno
) {
4364 view
->lineno
= tree_lineno
;
4372 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4374 struct tree_entry
*entry
= line
->data
;
4376 if (line
->type
== LINE_TREE_HEAD
) {
4377 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4380 if (draw_mode(view
, entry
->mode
))
4383 if (opt_author
&& draw_author(view
, entry
->author
))
4386 if (opt_date
&& draw_date(view
, entry
->author
? &entry
->time
: NULL
))
4389 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4397 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4398 int fd
= mkstemp(file
);
4401 report("Failed to create temporary file");
4402 else if (!run_io_append(blob_ops
.argv
, FORMAT_ALL
, fd
))
4403 report("Failed to save blob data to file");
4405 open_editor(FALSE
, file
);
4411 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4413 enum open_flags flags
;
4416 case REQ_VIEW_BLAME
:
4417 if (line
->type
!= LINE_TREE_FILE
) {
4418 report("Blame only supported for files");
4422 string_copy(opt_ref
, view
->vid
);
4426 if (line
->type
!= LINE_TREE_FILE
) {
4427 report("Edit only supported for files");
4428 } else if (!is_head_commit(view
->vid
)) {
4431 open_editor(TRUE
, opt_file
);
4435 case REQ_TOGGLE_SORT_FIELD
:
4436 case REQ_TOGGLE_SORT_ORDER
:
4437 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4442 /* quit view if at top of tree */
4443 return REQ_VIEW_CLOSE
;
4446 line
= &view
->line
[1];
4456 /* Cleanup the stack if the tree view is at a different tree. */
4457 while (!*opt_path
&& tree_stack
)
4458 pop_tree_stack_entry();
4460 switch (line
->type
) {
4462 /* Depending on whether it is a subdirectory or parent link
4463 * mangle the path buffer. */
4464 if (line
== &view
->line
[1] && *opt_path
) {
4465 pop_tree_stack_entry();
4468 const char *basename
= tree_path(line
);
4470 push_tree_stack_entry(basename
, view
->lineno
);
4473 /* Trees and subtrees share the same ID, so they are not not
4474 * unique like blobs. */
4475 flags
= OPEN_RELOAD
;
4476 request
= REQ_VIEW_TREE
;
4479 case LINE_TREE_FILE
:
4480 flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4481 request
= REQ_VIEW_BLOB
;
4488 open_view(view
, request
, flags
);
4489 if (request
== REQ_VIEW_TREE
)
4490 view
->lineno
= tree_lineno
;
4496 tree_grep(struct view
*view
, struct line
*line
)
4498 struct tree_entry
*entry
= line
->data
;
4499 const char *text
[] = {
4501 opt_author
? entry
->author
: "",
4502 opt_date
? mkdate(&entry
->time
) : "",
4506 return grep_text(view
, text
);
4510 tree_select(struct view
*view
, struct line
*line
)
4512 struct tree_entry
*entry
= line
->data
;
4514 if (line
->type
== LINE_TREE_FILE
) {
4515 string_copy_rev(ref_blob
, entry
->id
);
4516 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4518 } else if (line
->type
!= LINE_TREE_DIR
) {
4522 string_copy_rev(view
->ref
, entry
->id
);
4526 tree_prepare(struct view
*view
)
4528 if (view
->lines
== 0 && opt_prefix
[0]) {
4529 char *pos
= opt_prefix
;
4531 while (pos
&& *pos
) {
4532 char *end
= strchr(pos
, '/');
4536 push_tree_stack_entry(pos
, 0);
4544 } else if (strcmp(view
->vid
, view
->id
)) {
4548 return init_io_rd(&view
->io
, view
->ops
->argv
, opt_cdup
, FORMAT_ALL
);
4551 static const char *tree_argv
[SIZEOF_ARG
] = {
4552 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4555 static struct view_ops tree_ops
= {
4568 blob_read(struct view
*view
, char *line
)
4572 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4576 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4583 return pager_request(view
, request
, line
);
4587 static const char *blob_argv
[SIZEOF_ARG
] = {
4588 "git", "cat-file", "blob", "%(blob)", NULL
4591 static struct view_ops blob_ops
= {
4605 * Loading the blame view is a two phase job:
4607 * 1. File content is read either using opt_file from the
4608 * filesystem or using git-cat-file.
4609 * 2. Then blame information is incrementally added by
4610 * reading output from git-blame.
4613 static const char *blame_head_argv
[] = {
4614 "git", "blame", "--incremental", "--", "%(file)", NULL
4617 static const char *blame_ref_argv
[] = {
4618 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4621 static const char *blame_cat_file_argv
[] = {
4622 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4625 struct blame_commit
{
4626 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4627 char title
[128]; /* First line of the commit message. */
4628 const char *author
; /* Author of the commit. */
4629 time_t time
; /* Date from the author ident. */
4630 char filename
[128]; /* Name of file. */
4631 bool has_previous
; /* Was a "previous" line detected. */
4635 struct blame_commit
*commit
;
4636 unsigned long lineno
;
4641 blame_open(struct view
*view
)
4643 char path
[SIZEOF_STR
];
4645 if (!view
->parent
&& *opt_prefix
) {
4646 string_copy(path
, opt_file
);
4647 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4651 if (!string_format(path
, "%s%s", opt_cdup
, opt_file
))
4654 if (*opt_ref
|| !io_open(&view
->io
, path
)) {
4655 if (!run_io_rd_dir(&view
->io
, blame_cat_file_argv
, opt_cdup
, FORMAT_ALL
))
4659 setup_update(view
, opt_file
);
4660 string_format(view
->ref
, "%s ...", opt_file
);
4665 static struct blame_commit
*
4666 get_blame_commit(struct view
*view
, const char *id
)
4670 for (i
= 0; i
< view
->lines
; i
++) {
4671 struct blame
*blame
= view
->line
[i
].data
;
4676 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4677 return blame
->commit
;
4681 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4684 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4690 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4692 const char *pos
= *posref
;
4695 pos
= strchr(pos
+ 1, ' ');
4696 if (!pos
|| !isdigit(pos
[1]))
4698 *number
= atoi(pos
+ 1);
4699 if (*number
< min
|| *number
> max
)
4706 static struct blame_commit
*
4707 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4709 struct blame_commit
*commit
;
4710 struct blame
*blame
;
4711 const char *pos
= text
+ SIZEOF_REV
- 2;
4712 size_t orig_lineno
= 0;
4716 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4719 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4720 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4721 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4724 commit
= get_blame_commit(view
, text
);
4730 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4733 blame
->commit
= commit
;
4734 blame
->lineno
= orig_lineno
+ group
- 1;
4742 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4745 const char **argv
= *opt_ref
? blame_ref_argv
: blame_head_argv
;
4748 if (view
->lines
== 0 && !view
->parent
)
4749 die("No blame exist for %s", view
->vid
);
4751 if (view
->lines
== 0 || !run_io_rd_dir(&io
, argv
, opt_cdup
, FORMAT_ALL
)) {
4752 report("Failed to load blame data");
4756 done_io(view
->pipe
);
4762 size_t linelen
= strlen(line
);
4763 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
4768 blame
->commit
= NULL
;
4769 strncpy(blame
->text
, line
, linelen
);
4770 blame
->text
[linelen
] = 0;
4771 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
4776 match_blame_header(const char *name
, char **line
)
4778 size_t namelen
= strlen(name
);
4779 bool matched
= !strncmp(name
, *line
, namelen
);
4788 blame_read(struct view
*view
, char *line
)
4790 static struct blame_commit
*commit
= NULL
;
4791 static int blamed
= 0;
4792 static bool read_file
= TRUE
;
4795 return blame_read_file(view
, line
, &read_file
);
4802 string_format(view
->ref
, "%s", view
->vid
);
4803 if (view_is_displayed(view
)) {
4804 update_view_title(view
);
4805 redraw_view_from(view
, 0);
4811 commit
= parse_blame_commit(view
, line
, &blamed
);
4812 string_format(view
->ref
, "%s %2d%%", view
->vid
,
4813 view
->lines
? blamed
* 100 / view
->lines
: 0);
4815 } else if (match_blame_header("author ", &line
)) {
4816 commit
->author
= get_author(line
);
4818 } else if (match_blame_header("author-time ", &line
)) {
4819 commit
->time
= (time_t) atol(line
);
4821 } else if (match_blame_header("author-tz ", &line
)) {
4822 parse_timezone(&commit
->time
, line
);
4824 } else if (match_blame_header("summary ", &line
)) {
4825 string_ncopy(commit
->title
, line
, strlen(line
));
4827 } else if (match_blame_header("previous ", &line
)) {
4828 commit
->has_previous
= TRUE
;
4830 } else if (match_blame_header("filename ", &line
)) {
4831 string_ncopy(commit
->filename
, line
, strlen(line
));
4839 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4841 struct blame
*blame
= line
->data
;
4842 time_t *time
= NULL
;
4843 const char *id
= NULL
, *author
= NULL
;
4844 char text
[SIZEOF_STR
];
4846 if (blame
->commit
&& *blame
->commit
->filename
) {
4847 id
= blame
->commit
->id
;
4848 author
= blame
->commit
->author
;
4849 time
= &blame
->commit
->time
;
4852 if (opt_date
&& draw_date(view
, time
))
4855 if (opt_author
&& draw_author(view
, author
))
4858 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
4861 if (draw_lineno(view
, lineno
))
4864 string_expand(text
, sizeof(text
), blame
->text
, opt_tab_size
);
4865 draw_text(view
, LINE_DEFAULT
, text
, TRUE
);
4870 check_blame_commit(struct blame
*blame
, bool check_null_id
)
4873 report("Commit data not loaded yet");
4874 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4875 report("No commit exist for the selected line");
4882 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
4884 const char *diff_tree_argv
[] = {
4885 "git", "diff-tree", "-U0", blame
->commit
->id
,
4886 "--", blame
->commit
->filename
, NULL
4889 int parent_lineno
= -1;
4890 int blamed_lineno
= -1;
4893 if (!run_io(&io
, diff_tree_argv
, NULL
, IO_RD
))
4896 while ((line
= io_get(&io
, '\n', TRUE
))) {
4898 char *pos
= strchr(line
, '+');
4900 parent_lineno
= atoi(line
+ 4);
4902 blamed_lineno
= atoi(pos
+ 1);
4904 } else if (*line
== '+' && parent_lineno
!= -1) {
4905 if (blame
->lineno
== blamed_lineno
- 1 &&
4906 !strcmp(blame
->text
, line
+ 1)) {
4907 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
4918 blame_request(struct view
*view
, enum request request
, struct line
*line
)
4920 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4921 struct blame
*blame
= line
->data
;
4924 case REQ_VIEW_BLAME
:
4925 if (check_blame_commit(blame
, TRUE
)) {
4926 string_copy(opt_ref
, blame
->commit
->id
);
4927 string_copy(opt_file
, blame
->commit
->filename
);
4929 view
->lineno
= blame
->lineno
;
4930 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
4935 if (check_blame_commit(blame
, TRUE
) &&
4936 select_commit_parent(blame
->commit
->id
, opt_ref
,
4937 blame
->commit
->filename
)) {
4938 string_copy(opt_file
, blame
->commit
->filename
);
4939 setup_blame_parent_line(view
, blame
);
4940 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
4945 if (!check_blame_commit(blame
, FALSE
))
4948 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
4949 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
4952 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
4953 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
4954 const char *diff_index_argv
[] = {
4955 "git", "diff-index", "--root", "--patch-with-stat",
4956 "-C", "-M", "HEAD", "--", view
->vid
, NULL
4959 if (!blame
->commit
->has_previous
) {
4960 diff_index_argv
[1] = "diff";
4961 diff_index_argv
[2] = "--no-color";
4962 diff_index_argv
[6] = "--";
4963 diff_index_argv
[7] = "/dev/null";
4966 if (!prepare_update(diff
, diff_index_argv
, NULL
, FORMAT_DASH
)) {
4967 report("Failed to allocate diff command");
4970 flags
|= OPEN_PREPARED
;
4973 open_view(view
, REQ_VIEW_DIFF
, flags
);
4974 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4975 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
4986 blame_grep(struct view
*view
, struct line
*line
)
4988 struct blame
*blame
= line
->data
;
4989 struct blame_commit
*commit
= blame
->commit
;
4990 const char *text
[] = {
4992 commit
? commit
->title
: "",
4993 commit
? commit
->id
: "",
4994 commit
&& opt_author
? commit
->author
: "",
4995 commit
&& opt_date
? mkdate(&commit
->time
) : "",
4999 return grep_text(view
, text
);
5003 blame_select(struct view
*view
, struct line
*line
)
5005 struct blame
*blame
= line
->data
;
5006 struct blame_commit
*commit
= blame
->commit
;
5011 if (!strcmp(commit
->id
, NULL_ID
))
5012 string_ncopy(ref_commit
, "HEAD", 4);
5014 string_copy_rev(ref_commit
, commit
->id
);
5017 static struct view_ops blame_ops
= {
5033 const char *author
; /* Author of the last commit. */
5034 time_t time
; /* Date of the last activity. */
5035 struct ref
*ref
; /* Name and commit ID information. */
5038 static const enum sort_field branch_sort_fields
[] = {
5039 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5041 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5044 branch_compare(const void *l1
, const void *l2
)
5046 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5047 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5049 switch (get_sort_field(branch_sort_state
)) {
5051 return sort_order(branch_sort_state
, branch1
->time
- branch2
->time
);
5053 case ORDERBY_AUTHOR
:
5054 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5058 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5063 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5065 struct branch
*branch
= line
->data
;
5066 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5068 if (opt_date
&& draw_date(view
, branch
->author
? &branch
->time
: NULL
))
5071 if (opt_author
&& draw_author(view
, branch
->author
))
5074 draw_text(view
, type
, branch
->ref
->name
, TRUE
);
5079 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5084 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5087 case REQ_TOGGLE_SORT_FIELD
:
5088 case REQ_TOGGLE_SORT_ORDER
:
5089 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5093 open_view(view
, REQ_VIEW_MAIN
, OPEN_SPLIT
);
5102 branch_read(struct view
*view
, char *line
)
5104 static char id
[SIZEOF_REV
];
5105 struct branch
*reference
;
5111 switch (get_line_type(line
)) {
5113 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5117 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5118 struct branch
*branch
= view
->line
[i
].data
;
5120 if (strcmp(branch
->ref
->id
, id
))
5123 view
->line
[i
].dirty
= TRUE
;
5125 branch
->author
= reference
->author
;
5126 branch
->time
= reference
->time
;
5130 parse_author_line(line
+ STRING_SIZE("author "),
5131 &branch
->author
, &branch
->time
);
5143 branch_open_visitor(void *data
, struct ref
*ref
)
5145 struct view
*view
= data
;
5146 struct branch
*branch
;
5148 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5151 branch
= calloc(1, sizeof(*branch
));
5156 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5160 branch_open(struct view
*view
)
5162 const char *branch_log
[] = {
5163 "git", "log", "--no-color", "--pretty=raw",
5164 "--simplify-by-decoration", "--all", NULL
5167 if (!run_io_rd(&view
->io
, branch_log
, NULL
, FORMAT_NONE
)) {
5168 report("Failed to load branch data");
5172 setup_update(view
, view
->id
);
5173 foreach_ref(branch_open_visitor
, view
);
5174 view
->p_restore
= TRUE
;
5180 branch_grep(struct view
*view
, struct line
*line
)
5182 struct branch
*branch
= line
->data
;
5183 const char *text
[] = {
5189 return grep_text(view
, text
);
5193 branch_select(struct view
*view
, struct line
*line
)
5195 struct branch
*branch
= line
->data
;
5197 string_copy_rev(view
->ref
, branch
->ref
->id
);
5198 string_copy_rev(ref_commit
, branch
->ref
->id
);
5199 string_copy_rev(ref_head
, branch
->ref
->id
);
5202 static struct view_ops branch_ops
= {
5221 char rev
[SIZEOF_REV
];
5222 char name
[SIZEOF_STR
];
5226 char rev
[SIZEOF_REV
];
5227 char name
[SIZEOF_STR
];
5231 static char status_onbranch
[SIZEOF_STR
];
5232 static struct status stage_status
;
5233 static enum line_type stage_line_type
;
5234 static size_t stage_chunks
;
5235 static int *stage_chunk
;
5237 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5239 /* This should work even for the "On branch" line. */
5241 status_has_none(struct view
*view
, struct line
*line
)
5243 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5246 /* Get fields from the diff line:
5247 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5250 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5252 const char *old_mode
= buf
+ 1;
5253 const char *new_mode
= buf
+ 8;
5254 const char *old_rev
= buf
+ 15;
5255 const char *new_rev
= buf
+ 56;
5256 const char *status
= buf
+ 97;
5259 old_mode
[-1] != ':' ||
5260 new_mode
[-1] != ' ' ||
5261 old_rev
[-1] != ' ' ||
5262 new_rev
[-1] != ' ' ||
5266 file
->status
= *status
;
5268 string_copy_rev(file
->old
.rev
, old_rev
);
5269 string_copy_rev(file
->new.rev
, new_rev
);
5271 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5272 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5274 file
->old
.name
[0] = file
->new.name
[0] = 0;
5280 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5282 struct status
*unmerged
= NULL
;
5286 if (!run_io(&io
, argv
, NULL
, IO_RD
))
5289 add_line_data(view
, NULL
, type
);
5291 while ((buf
= io_get(&io
, 0, TRUE
))) {
5292 struct status
*file
= unmerged
;
5295 file
= calloc(1, sizeof(*file
));
5296 if (!file
|| !add_line_data(view
, file
, type
))
5300 /* Parse diff info part. */
5302 file
->status
= status
;
5304 string_copy(file
->old
.rev
, NULL_ID
);
5306 } else if (!file
->status
|| file
== unmerged
) {
5307 if (!status_get_diff(file
, buf
, strlen(buf
)))
5310 buf
= io_get(&io
, 0, TRUE
);
5314 /* Collapse all modified entries that follow an
5315 * associated unmerged entry. */
5316 if (unmerged
== file
) {
5317 unmerged
->status
= 'U';
5319 } else if (file
->status
== 'U') {
5324 /* Grab the old name for rename/copy. */
5325 if (!*file
->old
.name
&&
5326 (file
->status
== 'R' || file
->status
== 'C')) {
5327 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5329 buf
= io_get(&io
, 0, TRUE
);
5334 /* git-ls-files just delivers a NUL separated list of
5335 * file names similar to the second half of the
5336 * git-diff-* output. */
5337 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5338 if (!*file
->old
.name
)
5339 string_copy(file
->old
.name
, file
->new.name
);
5343 if (io_error(&io
)) {
5349 if (!view
->line
[view
->lines
- 1].data
)
5350 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5356 /* Don't show unmerged entries in the staged section. */
5357 static const char *status_diff_index_argv
[] = {
5358 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5359 "--cached", "-M", "HEAD", NULL
5362 static const char *status_diff_files_argv
[] = {
5363 "git", "diff-files", "-z", NULL
5366 static const char *status_list_other_argv
[] = {
5367 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5370 static const char *status_list_no_head_argv
[] = {
5371 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5374 static const char *update_index_argv
[] = {
5375 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5378 /* Restore the previous line number to stay in the context or select a
5379 * line with something that can be updated. */
5381 status_restore(struct view
*view
)
5383 if (view
->p_lineno
>= view
->lines
)
5384 view
->p_lineno
= view
->lines
- 1;
5385 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5387 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5390 /* If the above fails, always skip the "On branch" line. */
5391 if (view
->p_lineno
< view
->lines
)
5392 view
->lineno
= view
->p_lineno
;
5396 if (view
->lineno
< view
->offset
)
5397 view
->offset
= view
->lineno
;
5398 else if (view
->offset
+ view
->height
<= view
->lineno
)
5399 view
->offset
= view
->lineno
- view
->height
+ 1;
5401 view
->p_restore
= FALSE
;
5405 status_update_onbranch(void)
5407 static const char *paths
[][2] = {
5408 { "rebase-apply/rebasing", "Rebasing" },
5409 { "rebase-apply/applying", "Applying mailbox" },
5410 { "rebase-apply/", "Rebasing mailbox" },
5411 { "rebase-merge/interactive", "Interactive rebase" },
5412 { "rebase-merge/", "Rebase merge" },
5413 { "MERGE_HEAD", "Merging" },
5414 { "BISECT_LOG", "Bisecting" },
5415 { "HEAD", "On branch" },
5417 char buf
[SIZEOF_STR
];
5421 if (is_initial_commit()) {
5422 string_copy(status_onbranch
, "Initial commit");
5426 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5427 char *head
= opt_head
;
5429 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5430 lstat(buf
, &stat
) < 0)
5436 if (string_format(buf
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5437 io_open(&io
, buf
) &&
5438 io_read_buf(&io
, buf
, sizeof(buf
))) {
5440 if (!prefixcmp(head
, "refs/heads/"))
5441 head
+= STRING_SIZE("refs/heads/");
5445 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5446 string_copy(status_onbranch
, opt_head
);
5450 string_copy(status_onbranch
, "Not currently on any branch");
5453 /* First parse staged info using git-diff-index(1), then parse unstaged
5454 * info using git-diff-files(1), and finally untracked files using
5455 * git-ls-files(1). */
5457 status_open(struct view
*view
)
5461 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5462 status_update_onbranch();
5464 run_io_bg(update_index_argv
);
5466 if (is_initial_commit()) {
5467 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5469 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5473 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5474 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5477 /* Restore the exact position or use the specialized restore
5479 if (!view
->p_restore
)
5480 status_restore(view
);
5485 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5487 struct status
*status
= line
->data
;
5488 enum line_type type
;
5492 switch (line
->type
) {
5493 case LINE_STAT_STAGED
:
5494 type
= LINE_STAT_SECTION
;
5495 text
= "Changes to be committed:";
5498 case LINE_STAT_UNSTAGED
:
5499 type
= LINE_STAT_SECTION
;
5500 text
= "Changed but not updated:";
5503 case LINE_STAT_UNTRACKED
:
5504 type
= LINE_STAT_SECTION
;
5505 text
= "Untracked files:";
5508 case LINE_STAT_NONE
:
5509 type
= LINE_DEFAULT
;
5510 text
= " (no files)";
5513 case LINE_STAT_HEAD
:
5514 type
= LINE_STAT_HEAD
;
5515 text
= status_onbranch
;
5522 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5524 buf
[0] = status
->status
;
5525 if (draw_text(view
, line
->type
, buf
, TRUE
))
5527 type
= LINE_DEFAULT
;
5528 text
= status
->new.name
;
5531 draw_text(view
, type
, text
, TRUE
);
5536 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5538 if (displayed_views() == 2 || display
[current_view
] != view
)
5539 maximize_view(view
);
5540 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5545 status_enter(struct view
*view
, struct line
*line
)
5547 struct status
*status
= line
->data
;
5548 const char *oldpath
= status
? status
->old
.name
: NULL
;
5549 /* Diffs for unmerged entries are empty when passing the new
5550 * path, so leave it empty. */
5551 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5553 enum open_flags split
;
5554 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5556 if (line
->type
== LINE_STAT_NONE
||
5557 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5558 report("No file to diff");
5562 switch (line
->type
) {
5563 case LINE_STAT_STAGED
:
5564 if (is_initial_commit()) {
5565 const char *no_head_diff_argv
[] = {
5566 "git", "diff", "--no-color", "--patch-with-stat",
5567 "--", "/dev/null", newpath
, NULL
5570 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
, FORMAT_DASH
))
5571 return status_load_error(view
, stage
, newpath
);
5573 const char *index_show_argv
[] = {
5574 "git", "diff-index", "--root", "--patch-with-stat",
5575 "-C", "-M", "--cached", "HEAD", "--",
5576 oldpath
, newpath
, NULL
5579 if (!prepare_update(stage
, index_show_argv
, opt_cdup
, FORMAT_DASH
))
5580 return status_load_error(view
, stage
, newpath
);
5584 info
= "Staged changes to %s";
5586 info
= "Staged changes";
5589 case LINE_STAT_UNSTAGED
:
5591 const char *files_show_argv
[] = {
5592 "git", "diff-files", "--root", "--patch-with-stat",
5593 "-C", "-M", "--", oldpath
, newpath
, NULL
5596 if (!prepare_update(stage
, files_show_argv
, opt_cdup
, FORMAT_DASH
))
5597 return status_load_error(view
, stage
, newpath
);
5599 info
= "Unstaged changes to %s";
5601 info
= "Unstaged changes";
5604 case LINE_STAT_UNTRACKED
:
5606 report("No file to show");
5610 if (!suffixcmp(status
->new.name
, -1, "/")) {
5611 report("Cannot display a directory");
5615 if (!prepare_update_file(stage
, newpath
))
5616 return status_load_error(view
, stage
, newpath
);
5617 info
= "Untracked file %s";
5620 case LINE_STAT_HEAD
:
5624 die("line type %d not handled in switch", line
->type
);
5627 split
= view_is_displayed(view
) ? OPEN_SPLIT
: 0;
5628 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5629 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5631 stage_status
= *status
;
5633 memset(&stage_status
, 0, sizeof(stage_status
));
5636 stage_line_type
= line
->type
;
5638 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5645 status_exists(struct status
*status
, enum line_type type
)
5647 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5648 unsigned long lineno
;
5650 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5651 struct line
*line
= &view
->line
[lineno
];
5652 struct status
*pos
= line
->data
;
5654 if (line
->type
!= type
)
5656 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5657 select_view_line(view
, lineno
);
5660 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5661 select_view_line(view
, lineno
);
5671 status_update_prepare(struct io
*io
, enum line_type type
)
5673 const char *staged_argv
[] = {
5674 "git", "update-index", "-z", "--index-info", NULL
5676 const char *others_argv
[] = {
5677 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5681 case LINE_STAT_STAGED
:
5682 return run_io(io
, staged_argv
, opt_cdup
, IO_WR
);
5684 case LINE_STAT_UNSTAGED
:
5685 return run_io(io
, others_argv
, opt_cdup
, IO_WR
);
5687 case LINE_STAT_UNTRACKED
:
5688 return run_io(io
, others_argv
, NULL
, IO_WR
);
5691 die("line type %d not handled in switch", type
);
5697 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5699 char buf
[SIZEOF_STR
];
5703 case LINE_STAT_STAGED
:
5704 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5707 status
->old
.name
, 0))
5711 case LINE_STAT_UNSTAGED
:
5712 case LINE_STAT_UNTRACKED
:
5713 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
5718 die("line type %d not handled in switch", type
);
5721 return io_write(io
, buf
, bufsize
);
5725 status_update_file(struct status
*status
, enum line_type type
)
5730 if (!status_update_prepare(&io
, type
))
5733 result
= status_update_write(&io
, status
, type
);
5734 return done_io(&io
) && result
;
5738 status_update_files(struct view
*view
, struct line
*line
)
5740 char buf
[sizeof(view
->ref
)];
5743 struct line
*pos
= view
->line
+ view
->lines
;
5746 int cursor_y
= -1, cursor_x
= -1;
5748 if (!status_update_prepare(&io
, line
->type
))
5751 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
5754 string_copy(buf
, view
->ref
);
5755 getsyx(cursor_y
, cursor_x
);
5756 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
5757 int almost_done
= file
* 100 / files
;
5759 if (almost_done
> done
) {
5761 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
5763 update_view_title(view
);
5764 setsyx(cursor_y
, cursor_x
);
5767 result
= status_update_write(&io
, line
->data
, line
->type
);
5769 string_copy(view
->ref
, buf
);
5771 return done_io(&io
) && result
;
5775 status_update(struct view
*view
)
5777 struct line
*line
= &view
->line
[view
->lineno
];
5779 assert(view
->lines
);
5782 /* This should work even for the "On branch" line. */
5783 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
5784 report("Nothing to update");
5788 if (!status_update_files(view
, line
+ 1)) {
5789 report("Failed to update file status");
5793 } else if (!status_update_file(line
->data
, line
->type
)) {
5794 report("Failed to update file status");
5802 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
5804 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
5805 if (type
== LINE_STAT_STAGED
) {
5806 report("Cannot revert changes to staged files");
5807 } else if (type
== LINE_STAT_UNTRACKED
) {
5808 report("Cannot revert changes to untracked files");
5809 } else if (has_none
) {
5810 report("Nothing to revert");
5812 report("Cannot revert changes to multiple files");
5817 char mode
[10] = "100644";
5818 const char *reset_argv
[] = {
5819 "git", "update-index", "--cacheinfo", mode
,
5820 status
->old
.rev
, status
->old
.name
, NULL
5822 const char *checkout_argv
[] = {
5823 "git", "checkout", "--", status
->old
.name
, NULL
5826 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5828 string_format(mode
, "%o", status
->old
.mode
);
5829 return (status
->status
!= 'U' || run_io_fg(reset_argv
, opt_cdup
)) &&
5830 run_io_fg(checkout_argv
, opt_cdup
);
5835 status_request(struct view
*view
, enum request request
, struct line
*line
)
5837 struct status
*status
= line
->data
;
5840 case REQ_STATUS_UPDATE
:
5841 if (!status_update(view
))
5845 case REQ_STATUS_REVERT
:
5846 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
5850 case REQ_STATUS_MERGE
:
5851 if (!status
|| status
->status
!= 'U') {
5852 report("Merging only possible for files with unmerged status ('U').");
5855 open_mergetool(status
->new.name
);
5861 if (status
->status
== 'D') {
5862 report("File has been deleted.");
5866 open_editor(status
->status
!= '?', status
->new.name
);
5869 case REQ_VIEW_BLAME
:
5871 string_copy(opt_file
, status
->new.name
);
5877 /* After returning the status view has been split to
5878 * show the stage view. No further reloading is
5880 return status_enter(view
, line
);
5883 /* Simply reload the view. */
5890 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
5896 status_select(struct view
*view
, struct line
*line
)
5898 struct status
*status
= line
->data
;
5899 char file
[SIZEOF_STR
] = "all files";
5903 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
5906 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
5909 switch (line
->type
) {
5910 case LINE_STAT_STAGED
:
5911 text
= "Press %s to unstage %s for commit";
5914 case LINE_STAT_UNSTAGED
:
5915 text
= "Press %s to stage %s for commit";
5918 case LINE_STAT_UNTRACKED
:
5919 text
= "Press %s to stage %s for addition";
5922 case LINE_STAT_HEAD
:
5923 case LINE_STAT_NONE
:
5924 text
= "Nothing to update";
5928 die("line type %d not handled in switch", line
->type
);
5931 if (status
&& status
->status
== 'U') {
5932 text
= "Press %s to resolve conflict in %s";
5933 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
5936 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
5939 string_format(view
->ref
, text
, key
, file
);
5943 status_grep(struct view
*view
, struct line
*line
)
5945 struct status
*status
= line
->data
;
5948 const char buf
[2] = { status
->status
, 0 };
5949 const char *text
[] = { status
->new.name
, buf
, NULL
};
5951 return grep_text(view
, text
);
5957 static struct view_ops status_ops
= {
5970 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
5972 while (line
< end
) {
5973 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
5974 !io_write(io
, "\n", 1))
5977 if (line
->type
== LINE_DIFF_CHUNK
||
5978 line
->type
== LINE_DIFF_HEADER
)
5985 static struct line
*
5986 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
5988 for (; view
->line
< line
; line
--)
5989 if (line
->type
== type
)
5996 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
5998 const char *apply_argv
[SIZEOF_ARG
] = {
5999 "git", "apply", "--whitespace=nowarn", NULL
6001 struct line
*diff_hdr
;
6005 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6010 apply_argv
[argc
++] = "--cached";
6011 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6012 apply_argv
[argc
++] = "-R";
6013 apply_argv
[argc
++] = "-";
6014 apply_argv
[argc
++] = NULL
;
6015 if (!run_io(&io
, apply_argv
, opt_cdup
, IO_WR
))
6018 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6019 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6023 run_io_bg(update_index_argv
);
6025 return chunk
? TRUE
: FALSE
;
6029 stage_update(struct view
*view
, struct line
*line
)
6031 struct line
*chunk
= NULL
;
6033 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6034 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6037 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6038 report("Failed to apply chunk");
6042 } else if (!stage_status
.status
) {
6043 view
= VIEW(REQ_VIEW_STATUS
);
6045 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6046 if (line
->type
== stage_line_type
)
6049 if (!status_update_files(view
, line
+ 1)) {
6050 report("Failed to update files");
6054 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6055 report("Failed to update file");
6063 stage_revert(struct view
*view
, struct line
*line
)
6065 struct line
*chunk
= NULL
;
6067 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6068 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6071 if (!prompt_yesno("Are you sure you want to revert changes?"))
6074 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6075 report("Failed to revert chunk");
6081 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6082 stage_line_type
, FALSE
);
6088 stage_next(struct view
*view
, struct line
*line
)
6092 if (!stage_chunks
) {
6093 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6094 if (line
->type
!= LINE_DIFF_CHUNK
)
6097 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6098 report("Allocation failure");
6102 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6106 for (i
= 0; i
< stage_chunks
; i
++) {
6107 if (stage_chunk
[i
] > view
->lineno
) {
6108 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6109 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6114 report("No next chunk found");
6118 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6121 case REQ_STATUS_UPDATE
:
6122 if (!stage_update(view
, line
))
6126 case REQ_STATUS_REVERT
:
6127 if (!stage_revert(view
, line
))
6131 case REQ_STAGE_NEXT
:
6132 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6133 report("File is untracked; press %s to add",
6134 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6137 stage_next(view
, line
);
6141 if (!stage_status
.new.name
[0])
6143 if (stage_status
.status
== 'D') {
6144 report("File has been deleted.");
6148 open_editor(stage_status
.status
!= '?', stage_status
.new.name
);
6152 /* Reload everything ... */
6155 case REQ_VIEW_BLAME
:
6156 if (stage_status
.new.name
[0]) {
6157 string_copy(opt_file
, stage_status
.new.name
);
6163 return pager_request(view
, request
, line
);
6169 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6170 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6172 /* Check whether the staged entry still exists, and close the
6173 * stage view if it doesn't. */
6174 if (!status_exists(&stage_status
, stage_line_type
)) {
6175 status_restore(VIEW(REQ_VIEW_STATUS
));
6176 return REQ_VIEW_CLOSE
;
6179 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6180 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6181 report("Cannot display a directory");
6185 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6186 report("Failed to open file: %s", strerror(errno
));
6190 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6195 static struct view_ops stage_ops
= {
6212 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6213 char title
[128]; /* First line of the commit message. */
6214 const char *author
; /* Author of the commit. */
6215 time_t time
; /* Date from the author ident. */
6216 struct ref_list
*refs
; /* Repository references. */
6217 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6218 size_t graph_size
; /* The width of the graph array. */
6219 bool has_parents
; /* Rewritten --parents seen. */
6222 /* Size of rev graph with no "padding" columns */
6223 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6226 struct rev_graph
*prev
, *next
, *parents
;
6227 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6229 struct commit
*commit
;
6231 unsigned int boundary
:1;
6234 /* Parents of the commit being visualized. */
6235 static struct rev_graph graph_parents
[4];
6237 /* The current stack of revisions on the graph. */
6238 static struct rev_graph graph_stacks
[4] = {
6239 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6240 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6241 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6242 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6246 graph_parent_is_merge(struct rev_graph
*graph
)
6248 return graph
->parents
->size
> 1;
6252 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6254 struct commit
*commit
= graph
->commit
;
6256 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6257 commit
->graph
[commit
->graph_size
++] = symbol
;
6261 clear_rev_graph(struct rev_graph
*graph
)
6263 graph
->boundary
= 0;
6264 graph
->size
= graph
->pos
= 0;
6265 graph
->commit
= NULL
;
6266 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6270 done_rev_graph(struct rev_graph
*graph
)
6272 if (graph_parent_is_merge(graph
) &&
6273 graph
->pos
< graph
->size
- 1 &&
6274 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6275 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6277 graph
->commit
->graph_size
= i
* 2;
6278 while (i
< graph
->next
->size
- 1) {
6279 append_to_rev_graph(graph
, ' ');
6280 append_to_rev_graph(graph
, '\\');
6285 clear_rev_graph(graph
);
6289 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6293 /* "Collapse" duplicate parents lines.
6295 * FIXME: This needs to also update update the drawn graph but
6296 * for now it just serves as a method for pruning graph lines. */
6297 for (i
= 0; i
< graph
->size
; i
++)
6298 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6301 if (graph
->size
< SIZEOF_REVITEMS
) {
6302 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6307 get_rev_graph_symbol(struct rev_graph
*graph
)
6311 if (graph
->boundary
)
6312 symbol
= REVGRAPH_BOUND
;
6313 else if (graph
->parents
->size
== 0)
6314 symbol
= REVGRAPH_INIT
;
6315 else if (graph_parent_is_merge(graph
))
6316 symbol
= REVGRAPH_MERGE
;
6317 else if (graph
->pos
>= graph
->size
)
6318 symbol
= REVGRAPH_BRANCH
;
6320 symbol
= REVGRAPH_COMMIT
;
6326 draw_rev_graph(struct rev_graph
*graph
)
6329 chtype separator
, line
;
6331 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6332 static struct rev_filler fillers
[] = {
6338 chtype symbol
= get_rev_graph_symbol(graph
);
6339 struct rev_filler
*filler
;
6342 if (opt_line_graphics
)
6343 fillers
[DEFAULT
].line
= line_graphics
[LINE_GRAPHIC_VLINE
];
6345 filler
= &fillers
[DEFAULT
];
6347 for (i
= 0; i
< graph
->pos
; i
++) {
6348 append_to_rev_graph(graph
, filler
->line
);
6349 if (graph_parent_is_merge(graph
->prev
) &&
6350 graph
->prev
->pos
== i
)
6351 filler
= &fillers
[RSHARP
];
6353 append_to_rev_graph(graph
, filler
->separator
);
6356 /* Place the symbol for this revision. */
6357 append_to_rev_graph(graph
, symbol
);
6359 if (graph
->prev
->size
> graph
->size
)
6360 filler
= &fillers
[RDIAG
];
6362 filler
= &fillers
[DEFAULT
];
6366 for (; i
< graph
->size
; i
++) {
6367 append_to_rev_graph(graph
, filler
->separator
);
6368 append_to_rev_graph(graph
, filler
->line
);
6369 if (graph_parent_is_merge(graph
->prev
) &&
6370 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6371 filler
= &fillers
[RSHARP
];
6372 if (graph
->prev
->size
> graph
->size
)
6373 filler
= &fillers
[LDIAG
];
6376 if (graph
->prev
->size
> graph
->size
) {
6377 append_to_rev_graph(graph
, filler
->separator
);
6378 if (filler
->line
!= ' ')
6379 append_to_rev_graph(graph
, filler
->line
);
6383 /* Prepare the next rev graph */
6385 prepare_rev_graph(struct rev_graph
*graph
)
6389 /* First, traverse all lines of revisions up to the active one. */
6390 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6391 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6394 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6397 /* Interleave the new revision parent(s). */
6398 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6399 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6401 /* Lastly, put any remaining revisions. */
6402 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6403 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6407 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6409 /* If this is the finalizing update ... */
6411 prepare_rev_graph(graph
);
6413 /* Graph visualization needs a one rev look-ahead,
6414 * so the first update doesn't visualize anything. */
6415 if (!graph
->prev
->commit
)
6418 if (view
->lines
> 2)
6419 view
->line
[view
->lines
- 3].dirty
= 1;
6420 if (view
->lines
> 1)
6421 view
->line
[view
->lines
- 2].dirty
= 1;
6422 draw_rev_graph(graph
->prev
);
6423 done_rev_graph(graph
->prev
->prev
);
6431 static const char *main_argv
[SIZEOF_ARG
] = {
6432 "git", "log", "--no-color", "--pretty=raw", "--parents",
6433 "--topo-order", "%(head)", NULL
6437 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6439 struct commit
*commit
= line
->data
;
6441 if (!commit
->author
)
6444 if (opt_date
&& draw_date(view
, &commit
->time
))
6447 if (opt_author
&& draw_author(view
, commit
->author
))
6450 if (opt_rev_graph
&& commit
->graph_size
&&
6451 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6454 if (opt_show_refs
&& commit
->refs
) {
6457 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6458 struct ref
*ref
= commit
->refs
->refs
[i
];
6459 enum line_type type
;
6462 type
= LINE_MAIN_HEAD
;
6464 type
= LINE_MAIN_LOCAL_TAG
;
6466 type
= LINE_MAIN_TAG
;
6467 else if (ref
->tracked
)
6468 type
= LINE_MAIN_TRACKED
;
6469 else if (ref
->remote
)
6470 type
= LINE_MAIN_REMOTE
;
6472 type
= LINE_MAIN_REF
;
6474 if (draw_text(view
, type
, "[", TRUE
) ||
6475 draw_text(view
, type
, ref
->name
, TRUE
) ||
6476 draw_text(view
, type
, "]", TRUE
))
6479 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6484 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6488 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6490 main_read(struct view
*view
, char *line
)
6492 static struct rev_graph
*graph
= graph_stacks
;
6493 enum line_type type
;
6494 struct commit
*commit
;
6499 if (!view
->lines
&& !view
->parent
)
6500 die("No revisions match the given arguments.");
6501 if (view
->lines
> 0) {
6502 commit
= view
->line
[view
->lines
- 1].data
;
6503 view
->line
[view
->lines
- 1].dirty
= 1;
6504 if (!commit
->author
) {
6507 graph
->commit
= NULL
;
6510 update_rev_graph(view
, graph
);
6512 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6513 clear_rev_graph(&graph_stacks
[i
]);
6517 type
= get_line_type(line
);
6518 if (type
== LINE_COMMIT
) {
6519 commit
= calloc(1, sizeof(struct commit
));
6523 line
+= STRING_SIZE("commit ");
6525 graph
->boundary
= 1;
6529 string_copy_rev(commit
->id
, line
);
6530 commit
->refs
= get_ref_list(commit
->id
);
6531 graph
->commit
= commit
;
6532 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6534 while ((line
= strchr(line
, ' '))) {
6536 push_rev_graph(graph
->parents
, line
);
6537 commit
->has_parents
= TRUE
;
6544 commit
= view
->line
[view
->lines
- 1].data
;
6548 if (commit
->has_parents
)
6550 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6554 parse_author_line(line
+ STRING_SIZE("author "),
6555 &commit
->author
, &commit
->time
);
6556 update_rev_graph(view
, graph
);
6557 graph
= graph
->next
;
6561 /* Fill in the commit title if it has not already been set. */
6562 if (commit
->title
[0])
6565 /* Require titles to start with a non-space character at the
6566 * offset used by git log. */
6567 if (strncmp(line
, " ", 4))
6570 /* Well, if the title starts with a whitespace character,
6571 * try to be forgiving. Otherwise we end up with no title. */
6572 while (isspace(*line
))
6576 /* FIXME: More graceful handling of titles; append "..." to
6577 * shortened titles, etc. */
6579 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6580 view
->line
[view
->lines
- 1].dirty
= 1;
6587 main_request(struct view
*view
, enum request request
, struct line
*line
)
6589 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
6593 open_view(view
, REQ_VIEW_DIFF
, flags
);
6597 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6607 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6612 if (!opt_show_refs
|| !list
)
6615 for (i
= 0; i
< list
->size
; i
++) {
6616 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6624 main_grep(struct view
*view
, struct line
*line
)
6626 struct commit
*commit
= line
->data
;
6627 const char *text
[] = {
6629 opt_author
? commit
->author
: "",
6630 opt_date
? mkdate(&commit
->time
) : "",
6634 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6638 main_select(struct view
*view
, struct line
*line
)
6640 struct commit
*commit
= line
->data
;
6642 string_copy_rev(view
->ref
, commit
->id
);
6643 string_copy_rev(ref_commit
, view
->ref
);
6646 static struct view_ops main_ops
= {
6659 * Unicode / UTF-8 handling
6661 * NOTE: Much of the following code for dealing with Unicode is derived from
6662 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6663 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6667 unicode_width(unsigned long c
)
6670 (c
<= 0x115f /* Hangul Jamo */
6673 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
6675 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
6676 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
6677 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
6678 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
6679 || (c
>= 0xffe0 && c
<= 0xffe6)
6680 || (c
>= 0x20000 && c
<= 0x2fffd)
6681 || (c
>= 0x30000 && c
<= 0x3fffd)))
6685 return opt_tab_size
;
6690 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6691 * Illegal bytes are set one. */
6692 static const unsigned char utf8_bytes
[256] = {
6693 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,
6694 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,
6695 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,
6696 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,
6697 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,
6698 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,
6699 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,
6700 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,
6703 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6704 static inline unsigned long
6705 utf8_to_unicode(const char *string
, size_t length
)
6707 unsigned long unicode
;
6711 unicode
= string
[0];
6714 unicode
= (string
[0] & 0x1f) << 6;
6715 unicode
+= (string
[1] & 0x3f);
6718 unicode
= (string
[0] & 0x0f) << 12;
6719 unicode
+= ((string
[1] & 0x3f) << 6);
6720 unicode
+= (string
[2] & 0x3f);
6723 unicode
= (string
[0] & 0x0f) << 18;
6724 unicode
+= ((string
[1] & 0x3f) << 12);
6725 unicode
+= ((string
[2] & 0x3f) << 6);
6726 unicode
+= (string
[3] & 0x3f);
6729 unicode
= (string
[0] & 0x0f) << 24;
6730 unicode
+= ((string
[1] & 0x3f) << 18);
6731 unicode
+= ((string
[2] & 0x3f) << 12);
6732 unicode
+= ((string
[3] & 0x3f) << 6);
6733 unicode
+= (string
[4] & 0x3f);
6736 unicode
= (string
[0] & 0x01) << 30;
6737 unicode
+= ((string
[1] & 0x3f) << 24);
6738 unicode
+= ((string
[2] & 0x3f) << 18);
6739 unicode
+= ((string
[3] & 0x3f) << 12);
6740 unicode
+= ((string
[4] & 0x3f) << 6);
6741 unicode
+= (string
[5] & 0x3f);
6744 die("Invalid Unicode length");
6747 /* Invalid characters could return the special 0xfffd value but NUL
6748 * should be just as good. */
6749 return unicode
> 0xffff ? 0 : unicode
;
6752 /* Calculates how much of string can be shown within the given maximum width
6753 * and sets trimmed parameter to non-zero value if all of string could not be
6754 * shown. If the reserve flag is TRUE, it will reserve at least one
6755 * trailing character, which can be useful when drawing a delimiter.
6757 * Returns the number of bytes to output from string to satisfy max_width. */
6759 utf8_length(const char **start
, size_t skip
, int *width
, size_t max_width
, int *trimmed
, bool reserve
)
6761 const char *string
= *start
;
6762 const char *end
= strchr(string
, '\0');
6763 unsigned char last_bytes
= 0;
6764 size_t last_ucwidth
= 0;
6769 while (string
< end
) {
6770 int c
= *(unsigned char *) string
;
6771 unsigned char bytes
= utf8_bytes
[c
];
6773 unsigned long unicode
;
6775 if (string
+ bytes
> end
)
6778 /* Change representation to figure out whether
6779 * it is a single- or double-width character. */
6781 unicode
= utf8_to_unicode(string
, bytes
);
6782 /* FIXME: Graceful handling of invalid Unicode character. */
6786 ucwidth
= unicode_width(unicode
);
6788 skip
-= ucwidth
<= skip
? ucwidth
: skip
;
6792 if (*width
> max_width
) {
6795 if (reserve
&& *width
== max_width
) {
6796 string
-= last_bytes
;
6797 *width
-= last_ucwidth
;
6803 last_bytes
= ucwidth
? bytes
: 0;
6804 last_ucwidth
= ucwidth
;
6807 return string
- *start
;
6815 /* Whether or not the curses interface has been initialized. */
6816 static bool cursed
= FALSE
;
6818 /* Terminal hacks and workarounds. */
6819 static bool use_scroll_redrawwin
;
6820 static bool use_scroll_status_wclear
;
6822 /* The status window is used for polling keystrokes. */
6823 static WINDOW
*status_win
;
6825 /* Reading from the prompt? */
6826 static bool input_mode
= FALSE
;
6828 static bool status_empty
= FALSE
;
6830 /* Update status and title window. */
6832 report(const char *msg
, ...)
6834 struct view
*view
= display
[current_view
];
6840 char buf
[SIZEOF_STR
];
6843 va_start(args
, msg
);
6844 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6845 buf
[sizeof(buf
) - 1] = 0;
6846 buf
[sizeof(buf
) - 2] = '.';
6847 buf
[sizeof(buf
) - 3] = '.';
6848 buf
[sizeof(buf
) - 4] = '.';
6854 if (!status_empty
|| *msg
) {
6857 va_start(args
, msg
);
6859 wmove(status_win
, 0, 0);
6860 if (view
->has_scrolled
&& use_scroll_status_wclear
)
6863 vwprintw(status_win
, msg
, args
);
6864 status_empty
= FALSE
;
6866 status_empty
= TRUE
;
6868 wclrtoeol(status_win
);
6869 wnoutrefresh(status_win
);
6874 update_view_title(view
);
6877 /* Controls when nodelay should be in effect when polling user input. */
6879 set_nonblocking_input(bool loading
)
6881 static unsigned int loading_views
;
6883 if ((loading
== FALSE
&& loading_views
-- == 1) ||
6884 (loading
== TRUE
&& loading_views
++ == 0))
6885 nodelay(status_win
, loading
);
6894 /* Initialize the curses library */
6895 if (isatty(STDIN_FILENO
)) {
6896 cursed
= !!initscr();
6899 /* Leave stdin and stdout alone when acting as a pager. */
6900 opt_tty
= fopen("/dev/tty", "r+");
6902 die("Failed to open /dev/tty");
6903 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
6907 die("Failed to initialize curses");
6909 nonl(); /* Disable conversion and detect newlines from input. */
6910 cbreak(); /* Take input chars one at a time, no wait for \n */
6911 noecho(); /* Don't echo input */
6912 leaveok(stdscr
, FALSE
);
6917 getmaxyx(stdscr
, y
, x
);
6918 status_win
= newwin(1, 0, y
- 1, 0);
6920 die("Failed to create status window");
6922 /* Enable keyboard mapping */
6923 keypad(status_win
, TRUE
);
6924 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
6926 TABSIZE
= opt_tab_size
;
6927 if (opt_line_graphics
) {
6928 line_graphics
[LINE_GRAPHIC_VLINE
] = ACS_VLINE
;
6931 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
6932 if (term
&& !strcmp(term
, "gnome-terminal")) {
6933 /* In the gnome-terminal-emulator, the message from
6934 * scrolling up one line when impossible followed by
6935 * scrolling down one line causes corruption of the
6936 * status line. This is fixed by calling wclear. */
6937 use_scroll_status_wclear
= TRUE
;
6938 use_scroll_redrawwin
= FALSE
;
6940 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
6941 /* No problems with full optimizations in xrvt-(unicode)
6943 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
6946 /* When scrolling in (u)xterm the last line in the
6947 * scrolling direction will update slowly. */
6948 use_scroll_redrawwin
= TRUE
;
6949 use_scroll_status_wclear
= FALSE
;
6954 get_input(int prompt_position
)
6957 int i
, key
, cursor_y
, cursor_x
;
6959 if (prompt_position
)
6963 foreach_view (view
, i
) {
6965 if (view_is_displayed(view
) && view
->has_scrolled
&&
6966 use_scroll_redrawwin
)
6967 redrawwin(view
->win
);
6968 view
->has_scrolled
= FALSE
;
6971 /* Update the cursor position. */
6972 if (prompt_position
) {
6973 getbegyx(status_win
, cursor_y
, cursor_x
);
6974 cursor_x
= prompt_position
;
6976 view
= display
[current_view
];
6977 getbegyx(view
->win
, cursor_y
, cursor_x
);
6978 cursor_x
= view
->width
- 1;
6979 cursor_y
+= view
->lineno
- view
->offset
;
6981 setsyx(cursor_y
, cursor_x
);
6983 /* Refresh, accept single keystroke of input */
6985 key
= wgetch(status_win
);
6987 /* wgetch() with nodelay() enabled returns ERR when
6988 * there's no input. */
6991 } else if (key
== KEY_RESIZE
) {
6994 getmaxyx(stdscr
, height
, width
);
6996 wresize(status_win
, 1, width
);
6997 mvwin(status_win
, height
- 1, 0);
6998 wnoutrefresh(status_win
);
7000 redraw_display(TRUE
);
7010 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7012 enum input_status status
= INPUT_OK
;
7013 static char buf
[SIZEOF_STR
];
7018 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7021 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7022 wclrtoeol(status_win
);
7024 key
= get_input(pos
+ 1);
7029 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7036 status
= INPUT_CANCEL
;
7040 status
= INPUT_CANCEL
;
7044 if (pos
>= sizeof(buf
)) {
7045 report("Input string too long");
7049 status
= handler(data
, buf
, key
);
7050 if (status
== INPUT_OK
)
7051 buf
[pos
++] = (char) key
;
7055 /* Clear the status window */
7056 status_empty
= FALSE
;
7059 if (status
== INPUT_CANCEL
)
7067 static enum input_status
7068 prompt_yesno_handler(void *data
, char *buf
, int c
)
7070 if (c
== 'y' || c
== 'Y')
7072 if (c
== 'n' || c
== 'N')
7073 return INPUT_CANCEL
;
7078 prompt_yesno(const char *prompt
)
7080 char prompt2
[SIZEOF_STR
];
7082 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7085 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7088 static enum input_status
7089 read_prompt_handler(void *data
, char *buf
, int c
)
7091 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7095 read_prompt(const char *prompt
)
7097 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7100 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7102 enum input_status status
= INPUT_OK
;
7105 while (items
[size
].text
)
7108 while (status
== INPUT_OK
) {
7109 const struct menu_item
*item
= &items
[*selected
];
7113 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7114 prompt
, *selected
+ 1, size
);
7116 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7117 wprintw(status_win
, "%s", item
->text
);
7118 wclrtoeol(status_win
);
7120 key
= get_input(COLS
- 1);
7125 status
= INPUT_STOP
;
7130 *selected
= *selected
- 1;
7132 *selected
= size
- 1;
7137 *selected
= (*selected
+ 1) % size
;
7141 status
= INPUT_CANCEL
;
7145 for (i
= 0; items
[i
].text
; i
++)
7146 if (items
[i
].hotkey
== key
) {
7148 status
= INPUT_STOP
;
7154 /* Clear the status window */
7155 status_empty
= FALSE
;
7158 return status
!= INPUT_CANCEL
;
7162 * Repository properties
7165 static struct ref
**refs
= NULL
;
7166 static size_t refs_size
= 0;
7168 static struct ref_list
**ref_lists
= NULL
;
7169 static size_t ref_lists_size
= 0;
7171 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7172 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7173 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7176 compare_refs(const void *ref1_
, const void *ref2_
)
7178 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7179 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7181 if (ref1
->tag
!= ref2
->tag
)
7182 return ref2
->tag
- ref1
->tag
;
7183 if (ref1
->ltag
!= ref2
->ltag
)
7184 return ref2
->ltag
- ref2
->ltag
;
7185 if (ref1
->head
!= ref2
->head
)
7186 return ref2
->head
- ref1
->head
;
7187 if (ref1
->tracked
!= ref2
->tracked
)
7188 return ref2
->tracked
- ref1
->tracked
;
7189 if (ref1
->remote
!= ref2
->remote
)
7190 return ref2
->remote
- ref1
->remote
;
7191 return strcmp(ref1
->name
, ref2
->name
);
7195 foreach_ref(bool (*visitor
)(void *data
, struct ref
*ref
), void *data
)
7199 for (i
= 0; i
< refs_size
; i
++)
7200 if (!visitor(data
, refs
[i
]))
7204 static struct ref_list
*
7205 get_ref_list(const char *id
)
7207 struct ref_list
*list
;
7210 for (i
= 0; i
< ref_lists_size
; i
++)
7211 if (!strcmp(id
, ref_lists
[i
]->id
))
7212 return ref_lists
[i
];
7214 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7216 list
= calloc(1, sizeof(*list
));
7220 for (i
= 0; i
< refs_size
; i
++) {
7221 if (!strcmp(id
, refs
[i
]->id
) &&
7222 realloc_refs_list(&list
->refs
, list
->size
, 1))
7223 list
->refs
[list
->size
++] = refs
[i
];
7231 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7232 ref_lists
[ref_lists_size
++] = list
;
7237 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
7239 struct ref
*ref
= NULL
;
7242 bool remote
= FALSE
;
7243 bool tracked
= FALSE
;
7245 int from
= 0, to
= refs_size
- 1;
7247 if (!prefixcmp(name
, "refs/tags/")) {
7248 if (!suffixcmp(name
, namelen
, "^{}")) {
7256 namelen
-= STRING_SIZE("refs/tags/");
7257 name
+= STRING_SIZE("refs/tags/");
7259 } else if (!prefixcmp(name
, "refs/remotes/")) {
7261 namelen
-= STRING_SIZE("refs/remotes/");
7262 name
+= STRING_SIZE("refs/remotes/");
7263 tracked
= !strcmp(opt_remote
, name
);
7265 } else if (!prefixcmp(name
, "refs/heads/")) {
7266 namelen
-= STRING_SIZE("refs/heads/");
7267 name
+= STRING_SIZE("refs/heads/");
7268 head
= !strncmp(opt_head
, name
, namelen
);
7270 } else if (!strcmp(name
, "HEAD")) {
7271 string_ncopy(opt_head_rev
, id
, idlen
);
7275 /* If we are reloading or it's an annotated tag, replace the
7276 * previous SHA1 with the resolved commit id; relies on the fact
7277 * git-ls-remote lists the commit id of an annotated tag right
7278 * before the commit id it points to. */
7279 while (from
<= to
) {
7280 size_t pos
= (to
+ from
) / 2;
7281 int cmp
= strcmp(name
, refs
[pos
]->name
);
7295 if (!realloc_refs(&refs
, refs_size
, 1))
7297 ref
= calloc(1, sizeof(*ref
) + namelen
);
7300 memmove(refs
+ from
+ 1, refs
+ from
,
7301 (refs_size
- from
) * sizeof(*refs
));
7303 strncpy(ref
->name
, name
, namelen
);
7310 ref
->remote
= remote
;
7311 ref
->tracked
= tracked
;
7312 string_copy_rev(ref
->id
, id
);
7320 const char *head_argv
[] = {
7321 "git", "symbolic-ref", "HEAD", NULL
7323 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7324 "git", "ls-remote", opt_git_dir
, NULL
7326 static bool init
= FALSE
;
7330 argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE");
7337 if (run_io_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7338 !prefixcmp(opt_head
, "refs/heads/")) {
7339 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7341 memmove(opt_head
, offset
, strlen(offset
) + 1);
7344 for (i
= 0; i
< refs_size
; i
++)
7347 if (run_io_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7350 /* Update the ref lists to reflect changes. */
7351 for (i
= 0; i
< ref_lists_size
; i
++) {
7352 struct ref_list
*list
= ref_lists
[i
];
7355 for (old
= new = 0; old
< list
->size
; old
++)
7356 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7357 list
->refs
[new++] = list
->refs
[old
];
7365 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7367 if (!strcmp(name
, ".remote")) {
7368 string_ncopy(opt_remote
, value
, valuelen
);
7370 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7371 size_t from
= strlen(opt_remote
);
7373 if (!prefixcmp(value
, "refs/heads/"))
7374 value
+= STRING_SIZE("refs/heads/");
7376 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7382 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7384 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7385 int argc
= 1 + (cmd
== option_set_command
);
7388 if (!argv_from_string(argv
, &argc
, value
))
7389 config_msg
= "Too many option arguments";
7391 error
= cmd(argc
, argv
);
7394 warn("Option 'tig.%s': %s", name
, config_msg
);
7398 set_environment_variable(const char *name
, const char *value
)
7400 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7401 char *env
= malloc(len
);
7404 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7412 set_work_tree(const char *value
)
7414 char cwd
[SIZEOF_STR
];
7416 if (!getcwd(cwd
, sizeof(cwd
)))
7417 die("Failed to get cwd path: %s", strerror(errno
));
7418 if (chdir(opt_git_dir
) < 0)
7419 die("Failed to chdir(%s): %s", strerror(errno
));
7420 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7421 die("Failed to get git path: %s", strerror(errno
));
7423 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7424 if (chdir(value
) < 0)
7425 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7426 if (!getcwd(cwd
, sizeof(cwd
)))
7427 die("Failed to get cwd path: %s", strerror(errno
));
7428 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7429 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7430 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7431 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7432 opt_is_inside_work_tree
= TRUE
;
7436 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7438 if (!strcmp(name
, "i18n.commitencoding"))
7439 string_ncopy(opt_encoding
, value
, valuelen
);
7441 else if (!strcmp(name
, "core.editor"))
7442 string_ncopy(opt_editor
, value
, valuelen
);
7444 else if (!strcmp(name
, "core.worktree"))
7445 set_work_tree(value
);
7447 else if (!prefixcmp(name
, "tig.color."))
7448 set_repo_config_option(name
+ 10, value
, option_color_command
);
7450 else if (!prefixcmp(name
, "tig.bind."))
7451 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7453 else if (!prefixcmp(name
, "tig."))
7454 set_repo_config_option(name
+ 4, value
, option_set_command
);
7456 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7457 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7458 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7464 load_git_config(void)
7466 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7468 return run_io_load(config_list_argv
, "=", read_repo_config_option
);
7472 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7474 if (!opt_git_dir
[0]) {
7475 string_ncopy(opt_git_dir
, name
, namelen
);
7477 } else if (opt_is_inside_work_tree
== -1) {
7478 /* This can be 3 different values depending on the
7479 * version of git being used. If git-rev-parse does not
7480 * understand --is-inside-work-tree it will simply echo
7481 * the option else either "true" or "false" is printed.
7482 * Default to true for the unknown case. */
7483 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7485 } else if (*name
== '.') {
7486 string_ncopy(opt_cdup
, name
, namelen
);
7489 string_ncopy(opt_prefix
, name
, namelen
);
7496 load_repo_info(void)
7498 const char *rev_parse_argv
[] = {
7499 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7500 "--show-cdup", "--show-prefix", NULL
7503 return run_io_load(rev_parse_argv
, "=", read_repo_info
);
7511 static const char usage
[] =
7512 "tig " TIG_VERSION
" (" __DATE__
")\n"
7514 "Usage: tig [options] [revs] [--] [paths]\n"
7515 " or: tig show [options] [revs] [--] [paths]\n"
7516 " or: tig blame [rev] path\n"
7518 " or: tig < [git command output]\n"
7521 " -v, --version Show version and exit\n"
7522 " -h, --help Show help message and exit";
7524 static void __NORETURN
7527 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7533 static void __NORETURN
7534 die(const char *err
, ...)
7540 va_start(args
, err
);
7541 fputs("tig: ", stderr
);
7542 vfprintf(stderr
, err
, args
);
7543 fputs("\n", stderr
);
7550 warn(const char *msg
, ...)
7554 va_start(args
, msg
);
7555 fputs("tig warning: ", stderr
);
7556 vfprintf(stderr
, msg
, args
);
7557 fputs("\n", stderr
);
7562 parse_options(int argc
, const char *argv
[])
7564 enum request request
= REQ_VIEW_MAIN
;
7565 const char *subcommand
;
7566 bool seen_dashdash
= FALSE
;
7567 /* XXX: This is vulnerable to the user overriding options
7568 * required for the main view parser. */
7569 const char *custom_argv
[SIZEOF_ARG
] = {
7570 "git", "log", "--no-color", "--pretty=raw", "--parents",
7571 "--topo-order", NULL
7575 if (!isatty(STDIN_FILENO
)) {
7576 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7577 return REQ_VIEW_PAGER
;
7583 subcommand
= argv
[1];
7584 if (!strcmp(subcommand
, "status")) {
7586 warn("ignoring arguments after `%s'", subcommand
);
7587 return REQ_VIEW_STATUS
;
7589 } else if (!strcmp(subcommand
, "blame")) {
7590 if (argc
<= 2 || argc
> 4)
7591 die("invalid number of options to blame\n\n%s", usage
);
7595 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7599 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7600 return REQ_VIEW_BLAME
;
7602 } else if (!strcmp(subcommand
, "show")) {
7603 request
= REQ_VIEW_DIFF
;
7610 custom_argv
[1] = subcommand
;
7614 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7615 const char *opt
= argv
[i
];
7617 if (seen_dashdash
|| !strcmp(opt
, "--")) {
7618 seen_dashdash
= TRUE
;
7620 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7621 printf("tig version %s\n", TIG_VERSION
);
7624 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7625 printf("%s\n", usage
);
7629 custom_argv
[j
++] = opt
;
7630 if (j
>= ARRAY_SIZE(custom_argv
))
7631 die("command too long");
7634 if (!prepare_update(VIEW(request
), custom_argv
, NULL
, FORMAT_NONE
))
7635 die("Failed to format arguments");
7641 main(int argc
, const char *argv
[])
7643 enum request request
= parse_options(argc
, argv
);
7647 signal(SIGINT
, quit
);
7648 signal(SIGPIPE
, SIG_IGN
);
7650 if (setlocale(LC_ALL
, "")) {
7651 char *codeset
= nl_langinfo(CODESET
);
7653 string_ncopy(opt_codeset
, codeset
, strlen(codeset
));
7656 if (load_repo_info() == ERR
)
7657 die("Failed to load repo info.");
7659 if (load_options() == ERR
)
7660 die("Failed to load user config.");
7662 if (load_git_config() == ERR
)
7663 die("Failed to load repo config.");
7665 /* Require a git repository unless when running in pager mode. */
7666 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7667 die("Not a git repository");
7669 if (*opt_encoding
&& strcasecmp(opt_encoding
, "UTF-8"))
7672 if (*opt_codeset
&& strcmp(opt_codeset
, opt_encoding
)) {
7673 opt_iconv
= iconv_open(opt_codeset
, opt_encoding
);
7674 if (opt_iconv
== ICONV_NONE
)
7675 die("Failed to initialize character set conversion");
7678 if (load_refs() == ERR
)
7679 die("Failed to load refs.");
7681 foreach_view (view
, i
)
7682 argv_from_env(view
->ops
->argv
, view
->cmd_env
);
7686 if (request
!= REQ_NONE
)
7687 open_view(NULL
, request
, OPEN_PREPARED
);
7688 request
= request
== REQ_NONE
? REQ_VIEW_MAIN
: REQ_NONE
;
7690 while (view_driver(display
[current_view
], request
)) {
7691 int key
= get_input(0);
7693 view
= display
[current_view
];
7694 request
= get_keybinding(view
->keymap
, key
);
7696 /* Some low-level request handling. This keeps access to
7697 * status_win restricted. */
7701 char *cmd
= read_prompt(":");
7703 if (cmd
&& isdigit(*cmd
)) {
7704 int lineno
= view
->lineno
+ 1;
7706 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7707 select_view_line(view
, lineno
- 1);
7710 report("Unable to parse '%s' as a line number", cmd
);
7714 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7715 const char *argv
[SIZEOF_ARG
] = { "git" };
7718 /* When running random commands, initially show the
7719 * command in the title. However, it maybe later be
7720 * overwritten if a commit line is selected. */
7721 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7723 if (!argv_from_string(argv
, &argc
, cmd
)) {
7724 report("Too many arguments");
7725 } else if (!prepare_update(next
, argv
, NULL
, FORMAT_DASH
)) {
7726 report("Failed to format command");
7728 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7736 case REQ_SEARCH_BACK
:
7738 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7739 char *search
= read_prompt(prompt
);
7742 string_ncopy(opt_search
, search
, strlen(search
));
7743 else if (*opt_search
)
7744 request
= request
== REQ_SEARCH
?