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
, enum format_flags flags
)
650 return init_io_rd(io
, argv
, NULL
, 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
, FORMAT_NONE
) && io_read_buf(&io
, buf
, bufsize
);
797 io_load(struct io
*io
, const char *separators
,
798 int (*read_property
)(char *, size_t, char *, size_t))
806 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
811 name
= chomp_string(name
);
812 namelen
= strcspn(name
, separators
);
816 value
= chomp_string(name
+ namelen
+ 1);
817 valuelen
= strlen(value
);
824 state
= read_property(name
, namelen
, value
, valuelen
);
827 if (state
!= ERR
&& io_error(io
))
835 run_io_load(const char **argv
, const char *separators
,
836 int (*read_property
)(char *, size_t, char *, size_t))
840 return init_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
841 ? io_load(&io
, separators
, read_property
) : ERR
;
850 /* XXX: Keep the view request first and in sync with views[]. */ \
851 REQ_GROUP("View switching") \
852 REQ_(VIEW_MAIN, "Show main view"), \
853 REQ_(VIEW_DIFF, "Show diff view"), \
854 REQ_(VIEW_LOG, "Show log view"), \
855 REQ_(VIEW_TREE, "Show tree view"), \
856 REQ_(VIEW_BLOB, "Show blob view"), \
857 REQ_(VIEW_BLAME, "Show blame view"), \
858 REQ_(VIEW_BRANCH, "Show branch view"), \
859 REQ_(VIEW_HELP, "Show help page"), \
860 REQ_(VIEW_PAGER, "Show pager view"), \
861 REQ_(VIEW_STATUS, "Show status view"), \
862 REQ_(VIEW_STAGE, "Show stage view"), \
864 REQ_GROUP("View manipulation") \
865 REQ_(ENTER, "Enter current line and scroll"), \
866 REQ_(NEXT, "Move to next"), \
867 REQ_(PREVIOUS, "Move to previous"), \
868 REQ_(PARENT, "Move to parent"), \
869 REQ_(VIEW_NEXT, "Move focus to next view"), \
870 REQ_(REFRESH, "Reload and refresh"), \
871 REQ_(MAXIMIZE, "Maximize the current view"), \
872 REQ_(VIEW_CLOSE, "Close the current view"), \
873 REQ_(QUIT, "Close all views and quit"), \
875 REQ_GROUP("View specific requests") \
876 REQ_(STATUS_UPDATE, "Update file status"), \
877 REQ_(STATUS_REVERT, "Revert file changes"), \
878 REQ_(STATUS_MERGE, "Merge file using external tool"), \
879 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
881 REQ_GROUP("Cursor navigation") \
882 REQ_(MOVE_UP, "Move cursor one line up"), \
883 REQ_(MOVE_DOWN, "Move cursor one line down"), \
884 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
885 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
886 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
887 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
889 REQ_GROUP("Scrolling") \
890 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
891 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
892 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
893 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
894 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
895 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
897 REQ_GROUP("Searching") \
898 REQ_(SEARCH, "Search the view"), \
899 REQ_(SEARCH_BACK, "Search backwards in the view"), \
900 REQ_(FIND_NEXT, "Find next search match"), \
901 REQ_(FIND_PREV, "Find previous search match"), \
903 REQ_GROUP("Option manipulation") \
904 REQ_(OPTIONS, "Open option menu"), \
905 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
906 REQ_(TOGGLE_DATE, "Toggle date display"), \
907 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
908 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
909 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
910 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
911 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
912 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
915 REQ_(PROMPT, "Bring up the prompt"), \
916 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
917 REQ_(SHOW_VERSION, "Show version information"), \
918 REQ_(STOP_LOADING, "Stop all loading views"), \
919 REQ_(EDIT, "Open in editor"), \
920 REQ_(NONE, "Do nothing")
923 /* User action requests. */
925 #define REQ_GROUP(help)
926 #define REQ_(req, help) REQ_##req
928 /* Offset all requests to avoid conflicts with ncurses getch values. */
929 REQ_OFFSET
= KEY_MAX
+ 1,
936 struct request_info
{
937 enum request request
;
943 static const struct request_info req_info
[] = {
944 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
945 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
952 get_request(const char *name
)
954 int namelen
= strlen(name
);
957 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
958 if (req_info
[i
].namelen
== namelen
&&
959 !string_enum_compare(req_info
[i
].name
, name
, namelen
))
960 return req_info
[i
].request
;
970 /* Option and state variables. */
971 static enum date opt_date
= DATE_DEFAULT
;
972 static bool opt_author
= TRUE
;
973 static bool opt_line_number
= FALSE
;
974 static bool opt_line_graphics
= TRUE
;
975 static bool opt_rev_graph
= FALSE
;
976 static bool opt_show_refs
= TRUE
;
977 static int opt_num_interval
= 5;
978 static double opt_hscroll
= 0.50;
979 static double opt_scale_split_view
= 2.0 / 3.0;
980 static int opt_tab_size
= 8;
981 static int opt_author_cols
= 19;
982 static char opt_path
[SIZEOF_STR
] = "";
983 static char opt_file
[SIZEOF_STR
] = "";
984 static char opt_ref
[SIZEOF_REF
] = "";
985 static char opt_head
[SIZEOF_REF
] = "";
986 static char opt_head_rev
[SIZEOF_REV
] = "";
987 static char opt_remote
[SIZEOF_REF
] = "";
988 static char opt_encoding
[20] = "UTF-8";
989 static bool opt_utf8
= TRUE
;
990 static char opt_codeset
[20] = "UTF-8";
991 static iconv_t opt_iconv
= ICONV_NONE
;
992 static char opt_search
[SIZEOF_STR
] = "";
993 static char opt_cdup
[SIZEOF_STR
] = "";
994 static char opt_prefix
[SIZEOF_STR
] = "";
995 static char opt_git_dir
[SIZEOF_STR
] = "";
996 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
997 static char opt_editor
[SIZEOF_STR
] = "";
998 static FILE *opt_tty
= NULL
;
1000 #define is_initial_commit() (!*opt_head_rev)
1001 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1002 #define mkdate(time) string_date(time, opt_date)
1006 * Line-oriented content detection.
1010 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1011 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1012 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1013 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1014 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1015 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1016 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1017 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1018 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1019 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1020 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1021 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1022 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1023 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1024 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1025 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1026 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1027 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1028 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1029 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1030 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1031 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1032 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1033 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1034 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1035 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1036 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1037 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1038 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1039 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1040 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1041 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1042 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1043 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1044 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1045 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1046 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1047 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1048 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1049 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1050 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1051 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1052 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1053 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1054 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1055 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1056 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1057 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1058 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1059 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1060 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1061 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1062 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1063 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1064 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1065 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1066 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1069 #define LINE(type, line, fg, bg, attr) \
1077 const char *name
; /* Option name. */
1078 int namelen
; /* Size of option name. */
1079 const char *line
; /* The start of line to match. */
1080 int linelen
; /* Size of string to match. */
1081 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1084 static struct line_info line_info
[] = {
1085 #define LINE(type, line, fg, bg, attr) \
1086 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1091 static enum line_type
1092 get_line_type(const char *line
)
1094 int linelen
= strlen(line
);
1095 enum line_type type
;
1097 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1098 /* Case insensitive search matches Signed-off-by lines better. */
1099 if (linelen
>= line_info
[type
].linelen
&&
1100 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1103 return LINE_DEFAULT
;
1107 get_line_attr(enum line_type type
)
1109 assert(type
< ARRAY_SIZE(line_info
));
1110 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1113 static struct line_info
*
1114 get_line_info(const char *name
)
1116 size_t namelen
= strlen(name
);
1117 enum line_type type
;
1119 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1120 if (namelen
== line_info
[type
].namelen
&&
1121 !string_enum_compare(line_info
[type
].name
, name
, namelen
))
1122 return &line_info
[type
];
1130 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1131 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1132 enum line_type type
;
1136 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1137 default_bg
= COLOR_BLACK
;
1138 default_fg
= COLOR_WHITE
;
1141 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1142 struct line_info
*info
= &line_info
[type
];
1143 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1144 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1146 init_pair(type
, fg
, bg
);
1151 enum line_type type
;
1154 unsigned int selected
:1;
1155 unsigned int dirty
:1;
1156 unsigned int cleareol
:1;
1157 unsigned int other
:16;
1159 void *data
; /* User data */
1169 enum request request
;
1172 static const struct keybinding default_keybindings
[] = {
1173 /* View switching */
1174 { 'm', REQ_VIEW_MAIN
},
1175 { 'd', REQ_VIEW_DIFF
},
1176 { 'l', REQ_VIEW_LOG
},
1177 { 't', REQ_VIEW_TREE
},
1178 { 'f', REQ_VIEW_BLOB
},
1179 { 'B', REQ_VIEW_BLAME
},
1180 { 'H', REQ_VIEW_BRANCH
},
1181 { 'p', REQ_VIEW_PAGER
},
1182 { 'h', REQ_VIEW_HELP
},
1183 { 'S', REQ_VIEW_STATUS
},
1184 { 'c', REQ_VIEW_STAGE
},
1186 /* View manipulation */
1187 { 'q', REQ_VIEW_CLOSE
},
1188 { KEY_TAB
, REQ_VIEW_NEXT
},
1189 { KEY_RETURN
, REQ_ENTER
},
1190 { KEY_UP
, REQ_PREVIOUS
},
1191 { KEY_DOWN
, REQ_NEXT
},
1192 { 'R', REQ_REFRESH
},
1193 { KEY_F(5), REQ_REFRESH
},
1194 { 'O', REQ_MAXIMIZE
},
1196 /* Cursor navigation */
1197 { 'k', REQ_MOVE_UP
},
1198 { 'j', REQ_MOVE_DOWN
},
1199 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1200 { KEY_END
, REQ_MOVE_LAST_LINE
},
1201 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1202 { ' ', REQ_MOVE_PAGE_DOWN
},
1203 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1204 { 'b', REQ_MOVE_PAGE_UP
},
1205 { '-', REQ_MOVE_PAGE_UP
},
1208 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1209 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1210 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1211 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1212 { 'w', REQ_SCROLL_PAGE_UP
},
1213 { 's', REQ_SCROLL_PAGE_DOWN
},
1216 { '/', REQ_SEARCH
},
1217 { '?', REQ_SEARCH_BACK
},
1218 { 'n', REQ_FIND_NEXT
},
1219 { 'N', REQ_FIND_PREV
},
1223 { 'z', REQ_STOP_LOADING
},
1224 { 'v', REQ_SHOW_VERSION
},
1225 { 'r', REQ_SCREEN_REDRAW
},
1226 { 'o', REQ_OPTIONS
},
1227 { '.', REQ_TOGGLE_LINENO
},
1228 { 'D', REQ_TOGGLE_DATE
},
1229 { 'A', REQ_TOGGLE_AUTHOR
},
1230 { 'g', REQ_TOGGLE_REV_GRAPH
},
1231 { 'F', REQ_TOGGLE_REFS
},
1232 { 'I', REQ_TOGGLE_SORT_ORDER
},
1233 { 'i', REQ_TOGGLE_SORT_FIELD
},
1234 { ':', REQ_PROMPT
},
1235 { 'u', REQ_STATUS_UPDATE
},
1236 { '!', REQ_STATUS_REVERT
},
1237 { 'M', REQ_STATUS_MERGE
},
1238 { '@', REQ_STAGE_NEXT
},
1239 { ',', REQ_PARENT
},
1243 #define KEYMAP_INFO \
1258 #define KEYMAP_(name) KEYMAP_##name
1263 static const struct enum_map keymap_table
[] = {
1264 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1269 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1271 struct keybinding_table
{
1272 struct keybinding
*data
;
1276 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1279 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1281 struct keybinding_table
*table
= &keybindings
[keymap
];
1283 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1285 die("Failed to allocate keybinding");
1286 table
->data
[table
->size
].alias
= key
;
1287 table
->data
[table
->size
++].request
= request
;
1290 /* Looks for a key binding first in the given map, then in the generic map, and
1291 * lastly in the default keybindings. */
1293 get_keybinding(enum keymap keymap
, int key
)
1297 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1298 if (keybindings
[keymap
].data
[i
].alias
== key
)
1299 return keybindings
[keymap
].data
[i
].request
;
1301 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1302 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1303 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1305 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1306 if (default_keybindings
[i
].alias
== key
)
1307 return default_keybindings
[i
].request
;
1309 return (enum request
) key
;
1318 static const struct key key_table
[] = {
1319 { "Enter", KEY_RETURN
},
1321 { "Backspace", KEY_BACKSPACE
},
1323 { "Escape", KEY_ESC
},
1324 { "Left", KEY_LEFT
},
1325 { "Right", KEY_RIGHT
},
1327 { "Down", KEY_DOWN
},
1328 { "Insert", KEY_IC
},
1329 { "Delete", KEY_DC
},
1331 { "Home", KEY_HOME
},
1333 { "PageUp", KEY_PPAGE
},
1334 { "PageDown", KEY_NPAGE
},
1344 { "F10", KEY_F(10) },
1345 { "F11", KEY_F(11) },
1346 { "F12", KEY_F(12) },
1350 get_key_value(const char *name
)
1354 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1355 if (!strcasecmp(key_table
[i
].name
, name
))
1356 return key_table
[i
].value
;
1358 if (strlen(name
) == 1 && isprint(*name
))
1365 get_key_name(int key_value
)
1367 static char key_char
[] = "'X'";
1368 const char *seq
= NULL
;
1371 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1372 if (key_table
[key
].value
== key_value
)
1373 seq
= key_table
[key
].name
;
1377 isprint(key_value
)) {
1378 key_char
[1] = (char) key_value
;
1382 return seq
? seq
: "(no key)";
1386 append_key(char *buf
, size_t *pos
, const struct keybinding
*keybinding
)
1388 const char *sep
= *pos
> 0 ? ", " : "";
1389 const char *keyname
= get_key_name(keybinding
->alias
);
1391 return string_nformat(buf
, BUFSIZ
, pos
, "%s%s", sep
, keyname
);
1395 append_keymap_request_keys(char *buf
, size_t *pos
, enum request request
,
1396 enum keymap keymap
, bool all
)
1400 for (i
= 0; i
< keybindings
[keymap
].size
; i
++) {
1401 if (keybindings
[keymap
].data
[i
].request
== request
) {
1402 if (!append_key(buf
, pos
, &keybindings
[keymap
].data
[i
]))
1412 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1415 get_keys(enum keymap keymap
, enum request request
, bool all
)
1417 static char buf
[BUFSIZ
];
1423 if (!append_keymap_request_keys(buf
, &pos
, request
, keymap
, all
))
1424 return "Too many keybindings!";
1425 if (pos
> 0 && !all
)
1428 if (keymap
!= KEYMAP_GENERIC
) {
1429 /* Only the generic keymap includes the default keybindings when
1430 * listing all keys. */
1434 if (!append_keymap_request_keys(buf
, &pos
, request
, KEYMAP_GENERIC
, all
))
1435 return "Too many keybindings!";
1440 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1441 if (default_keybindings
[i
].request
== request
) {
1442 if (!append_key(buf
, &pos
, &default_keybindings
[i
]))
1443 return "Too many keybindings!";
1452 struct run_request
{
1455 const char *argv
[SIZEOF_ARG
];
1458 static struct run_request
*run_request
;
1459 static size_t run_requests
;
1461 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1464 add_run_request(enum keymap keymap
, int key
, int argc
, const char **argv
)
1466 struct run_request
*req
;
1468 if (argc
>= ARRAY_SIZE(req
->argv
) - 1)
1471 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1474 req
= &run_request
[run_requests
];
1475 req
->keymap
= keymap
;
1477 req
->argv
[0] = NULL
;
1479 if (!format_argv(req
->argv
, argv
, FORMAT_NONE
))
1482 return REQ_NONE
+ ++run_requests
;
1485 static struct run_request
*
1486 get_run_request(enum request request
)
1488 if (request
<= REQ_NONE
)
1490 return &run_request
[request
- REQ_NONE
- 1];
1494 add_builtin_run_requests(void)
1496 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1497 const char *commit
[] = { "git", "commit", NULL
};
1498 const char *gc
[] = { "git", "gc", NULL
};
1505 { KEYMAP_MAIN
, 'C', ARRAY_SIZE(cherry_pick
) - 1, cherry_pick
},
1506 { KEYMAP_STATUS
, 'C', ARRAY_SIZE(commit
) - 1, commit
},
1507 { KEYMAP_GENERIC
, 'G', ARRAY_SIZE(gc
) - 1, gc
},
1511 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1514 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argc
, reqs
[i
].argv
);
1515 if (req
!= REQ_NONE
)
1516 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1521 * User config file handling.
1524 static int config_lineno
;
1525 static bool config_errors
;
1526 static const char *config_msg
;
1528 static const struct enum_map color_map
[] = {
1529 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1541 static const struct enum_map attr_map
[] = {
1542 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1549 ATTR_MAP(UNDERLINE
),
1552 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1554 static int parse_step(double *opt
, const char *arg
)
1557 if (!strchr(arg
, '%'))
1560 /* "Shift down" so 100% and 1 does not conflict. */
1561 *opt
= (*opt
- 1) / 100;
1564 config_msg
= "Step value larger than 100%";
1569 config_msg
= "Invalid step value";
1576 parse_int(int *opt
, const char *arg
, int min
, int max
)
1578 int value
= atoi(arg
);
1580 if (min
<= value
&& value
<= max
) {
1585 config_msg
= "Integer value out of bound";
1590 set_color(int *color
, const char *name
)
1592 if (map_enum(color
, color_map
, name
))
1594 if (!prefixcmp(name
, "color"))
1595 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1599 /* Wants: object fgcolor bgcolor [attribute] */
1601 option_color_command(int argc
, const char *argv
[])
1603 struct line_info
*info
;
1606 config_msg
= "Wrong number of arguments given to color command";
1610 info
= get_line_info(argv
[0]);
1612 static const struct enum_map obsolete
[] = {
1613 ENUM_MAP("main-delim", LINE_DELIMITER
),
1614 ENUM_MAP("main-date", LINE_DATE
),
1615 ENUM_MAP("main-author", LINE_AUTHOR
),
1619 if (!map_enum(&index
, obsolete
, argv
[0])) {
1620 config_msg
= "Unknown color name";
1623 info
= &line_info
[index
];
1626 if (!set_color(&info
->fg
, argv
[1]) ||
1627 !set_color(&info
->bg
, argv
[2])) {
1628 config_msg
= "Unknown color";
1633 while (argc
-- > 3) {
1636 if (!set_attribute(&attr
, argv
[argc
])) {
1637 config_msg
= "Unknown attribute";
1646 static int parse_bool(bool *opt
, const char *arg
)
1648 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1654 parse_string(char *opt
, const char *arg
, size_t optsize
)
1656 int arglen
= strlen(arg
);
1661 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1662 config_msg
= "Unmatched quotation";
1665 arg
+= 1; arglen
-= 2;
1667 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1672 /* Wants: name = value */
1674 option_set_command(int argc
, const char *argv
[])
1677 config_msg
= "Wrong number of arguments given to set command";
1681 if (strcmp(argv
[1], "=")) {
1682 config_msg
= "No value assigned";
1686 if (!strcmp(argv
[0], "show-author"))
1687 return parse_bool(&opt_author
, argv
[2]);
1689 if (!strcmp(argv
[0], "show-date")) {
1692 if (!strcmp(argv
[2], "relative")) {
1693 opt_date
= DATE_RELATIVE
;
1695 } else if (!strcmp(argv
[2], "short")) {
1696 opt_date
= DATE_SHORT
;
1698 } else if (parse_bool(&show_date
, argv
[2])) {
1699 opt_date
= show_date
? DATE_DEFAULT
: DATE_NONE
;
1704 if (!strcmp(argv
[0], "show-rev-graph"))
1705 return parse_bool(&opt_rev_graph
, argv
[2]);
1707 if (!strcmp(argv
[0], "show-refs"))
1708 return parse_bool(&opt_show_refs
, argv
[2]);
1710 if (!strcmp(argv
[0], "show-line-numbers"))
1711 return parse_bool(&opt_line_number
, argv
[2]);
1713 if (!strcmp(argv
[0], "line-graphics"))
1714 return parse_bool(&opt_line_graphics
, argv
[2]);
1716 if (!strcmp(argv
[0], "line-number-interval"))
1717 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
1719 if (!strcmp(argv
[0], "author-width"))
1720 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
1722 if (!strcmp(argv
[0], "horizontal-scroll"))
1723 return parse_step(&opt_hscroll
, argv
[2]);
1725 if (!strcmp(argv
[0], "split-view-height"))
1726 return parse_step(&opt_scale_split_view
, argv
[2]);
1728 if (!strcmp(argv
[0], "tab-size"))
1729 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
1731 if (!strcmp(argv
[0], "commit-encoding"))
1732 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
1734 config_msg
= "Unknown variable name";
1738 /* Wants: mode request key */
1740 option_bind_command(int argc
, const char *argv
[])
1742 enum request request
;
1747 config_msg
= "Wrong number of arguments given to bind command";
1751 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
1752 config_msg
= "Unknown key map";
1756 key
= get_key_value(argv
[1]);
1758 config_msg
= "Unknown key";
1762 request
= get_request(argv
[2]);
1763 if (request
== REQ_NONE
) {
1764 static const struct enum_map obsolete
[] = {
1765 ENUM_MAP("cherry-pick", REQ_NONE
),
1766 ENUM_MAP("screen-resize", REQ_NONE
),
1767 ENUM_MAP("tree-parent", REQ_PARENT
),
1771 if (map_enum(&alias
, obsolete
, argv
[2])) {
1772 if (alias
!= REQ_NONE
)
1773 add_keybinding(keymap
, alias
, key
);
1774 config_msg
= "Obsolete request name";
1778 if (request
== REQ_NONE
&& *argv
[2]++ == '!')
1779 request
= add_run_request(keymap
, key
, argc
- 2, argv
+ 2);
1780 if (request
== REQ_NONE
) {
1781 config_msg
= "Unknown request name";
1785 add_keybinding(keymap
, request
, key
);
1791 set_option(const char *opt
, char *value
)
1793 const char *argv
[SIZEOF_ARG
];
1796 if (!argv_from_string(argv
, &argc
, value
)) {
1797 config_msg
= "Too many option arguments";
1801 if (!strcmp(opt
, "color"))
1802 return option_color_command(argc
, argv
);
1804 if (!strcmp(opt
, "set"))
1805 return option_set_command(argc
, argv
);
1807 if (!strcmp(opt
, "bind"))
1808 return option_bind_command(argc
, argv
);
1810 config_msg
= "Unknown option command";
1815 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
1820 config_msg
= "Internal error";
1822 /* Check for comment markers, since read_properties() will
1823 * only ensure opt and value are split at first " \t". */
1824 optlen
= strcspn(opt
, "#");
1828 if (opt
[optlen
] != 0) {
1829 config_msg
= "No option value";
1833 /* Look for comment endings in the value. */
1834 size_t len
= strcspn(value
, "#");
1836 if (len
< valuelen
) {
1838 value
[valuelen
] = 0;
1841 status
= set_option(opt
, value
);
1844 if (status
== ERR
) {
1845 warn("Error on line %d, near '%.*s': %s",
1846 config_lineno
, (int) optlen
, opt
, config_msg
);
1847 config_errors
= TRUE
;
1850 /* Always keep going if errors are encountered. */
1855 load_option_file(const char *path
)
1859 /* It's OK that the file doesn't exist. */
1860 if (!io_open(&io
, path
))
1864 config_errors
= FALSE
;
1866 if (io_load(&io
, " \t", read_option
) == ERR
||
1867 config_errors
== TRUE
)
1868 warn("Errors while loading %s.", path
);
1874 const char *home
= getenv("HOME");
1875 const char *tigrc_user
= getenv("TIGRC_USER");
1876 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
1877 char buf
[SIZEOF_STR
];
1879 add_builtin_run_requests();
1882 tigrc_system
= SYSCONFDIR
"/tigrc";
1883 load_option_file(tigrc_system
);
1886 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
1890 load_option_file(tigrc_user
);
1903 /* The display array of active views and the index of the current view. */
1904 static struct view
*display
[2];
1905 static unsigned int current_view
;
1907 #define foreach_displayed_view(view, i) \
1908 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1910 #define displayed_views() (display[1] != NULL ? 2 : 1)
1912 /* Current head and commit ID */
1913 static char ref_blob
[SIZEOF_REF
] = "";
1914 static char ref_commit
[SIZEOF_REF
] = "HEAD";
1915 static char ref_head
[SIZEOF_REF
] = "HEAD";
1918 const char *name
; /* View name */
1919 const char *cmd_env
; /* Command line set via environment */
1920 const char *id
; /* Points to either of ref_{head,commit,blob} */
1922 struct view_ops
*ops
; /* View operations */
1924 enum keymap keymap
; /* What keymap does this view have */
1925 bool git_dir
; /* Whether the view requires a git directory. */
1927 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
1928 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
1930 int height
, width
; /* The width and height of the main window */
1931 WINDOW
*win
; /* The main window */
1932 WINDOW
*title
; /* The title window living below the main window */
1935 unsigned long offset
; /* Offset of the window top */
1936 unsigned long yoffset
; /* Offset from the window side. */
1937 unsigned long lineno
; /* Current line number */
1938 unsigned long p_offset
; /* Previous offset of the window top */
1939 unsigned long p_yoffset
;/* Previous offset from the window side */
1940 unsigned long p_lineno
; /* Previous current line number */
1941 bool p_restore
; /* Should the previous position be restored. */
1944 char grep
[SIZEOF_STR
]; /* Search string */
1945 regex_t
*regex
; /* Pre-compiled regexp */
1947 /* If non-NULL, points to the view that opened this view. If this view
1948 * is closed tig will switch back to the parent view. */
1949 struct view
*parent
;
1952 size_t lines
; /* Total number of lines */
1953 struct line
*line
; /* Line index */
1954 unsigned int digits
; /* Number of digits in the lines member. */
1957 struct line
*curline
; /* Line currently being drawn. */
1958 enum line_type curtype
; /* Attribute currently used for drawing. */
1959 unsigned long col
; /* Column when drawing. */
1960 bool has_scrolled
; /* View was scrolled. */
1970 /* What type of content being displayed. Used in the title bar. */
1972 /* Default command arguments. */
1974 /* Open and reads in all view content. */
1975 bool (*open
)(struct view
*view
);
1976 /* Read one line; updates view->line. */
1977 bool (*read
)(struct view
*view
, char *data
);
1978 /* Draw one line; @lineno must be < view->height. */
1979 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
1980 /* Depending on view handle a special requests. */
1981 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
1982 /* Search for regexp in a line. */
1983 bool (*grep
)(struct view
*view
, struct line
*line
);
1985 void (*select
)(struct view
*view
, struct line
*line
);
1986 /* Prepare view for loading */
1987 bool (*prepare
)(struct view
*view
);
1990 static struct view_ops blame_ops
;
1991 static struct view_ops blob_ops
;
1992 static struct view_ops diff_ops
;
1993 static struct view_ops help_ops
;
1994 static struct view_ops log_ops
;
1995 static struct view_ops main_ops
;
1996 static struct view_ops pager_ops
;
1997 static struct view_ops stage_ops
;
1998 static struct view_ops status_ops
;
1999 static struct view_ops tree_ops
;
2000 static struct view_ops branch_ops
;
2002 #define VIEW_STR(name, env, ref, ops, map, git) \
2003 { name, #env, ref, ops, map, git }
2005 #define VIEW_(id, name, ops, git, ref) \
2006 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2009 static struct view views
[] = {
2010 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
2011 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
2012 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
2013 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
2014 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
2015 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
2016 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
2017 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
2018 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
2019 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
2020 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
2023 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2024 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2026 #define foreach_view(view, i) \
2027 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2029 #define view_is_displayed(view) \
2030 (view == display[0] || view == display[1])
2037 static chtype line_graphics
[] = {
2038 /* LINE_GRAPHIC_VLINE: */ '|'
2042 set_view_attr(struct view
*view
, enum line_type type
)
2044 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
2045 wattrset(view
->win
, get_line_attr(type
));
2046 wchgat(view
->win
, -1, 0, type
, NULL
);
2047 view
->curtype
= type
;
2052 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
2053 int max_len
, bool use_tilde
)
2057 int trimmed
= FALSE
;
2058 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2064 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
);
2066 col
= len
= strlen(string
);
2067 if (len
> max_len
) {
2071 col
= len
= max_len
;
2076 set_view_attr(view
, type
);
2078 waddnstr(view
->win
, string
, len
);
2079 if (trimmed
&& use_tilde
) {
2080 set_view_attr(view
, LINE_DELIMITER
);
2081 waddch(view
->win
, '~');
2089 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
2091 static char space
[] = " ";
2094 spaces
= MIN(max
, spaces
);
2096 while (spaces
> 0) {
2097 int len
= MIN(spaces
, sizeof(space
) - 1);
2099 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
2107 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
2109 view
->col
+= draw_chars(view
, type
, string
, view
->width
+ view
->yoffset
- view
->col
, trim
);
2110 return view
->width
+ view
->yoffset
<= view
->col
;
2114 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
2116 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
2117 int max
= view
->width
+ view
->yoffset
- view
->col
;
2123 set_view_attr(view
, type
);
2124 /* Using waddch() instead of waddnstr() ensures that
2125 * they'll be rendered correctly for the cursor line. */
2126 for (i
= skip
; i
< size
; i
++)
2127 waddch(view
->win
, graphic
[i
]);
2130 if (size
< max
&& skip
<= size
)
2131 waddch(view
->win
, ' ');
2134 return view
->width
+ view
->yoffset
<= view
->col
;
2138 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2140 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2144 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2146 col
= draw_space(view
, type
, max
- 1, max
- 1);
2149 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2150 return view
->width
+ view
->yoffset
<= view
->col
;
2154 draw_date(struct view
*view
, time_t *time
)
2156 const char *date
= time
? mkdate(time
) : "";
2157 int cols
= opt_date
== DATE_SHORT
? DATE_SHORT_COLS
: DATE_COLS
;
2159 return draw_field(view
, LINE_DATE
, date
, cols
, FALSE
);
2163 draw_author(struct view
*view
, const char *author
)
2165 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5 || !author
;
2168 static char initials
[10];
2171 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2173 memset(initials
, 0, sizeof(initials
));
2174 for (pos
= 0; *author
&& pos
< opt_author_cols
- 1; author
++, pos
++) {
2175 while (is_initial_sep(*author
))
2177 strncpy(&initials
[pos
], author
, sizeof(initials
) - 1 - pos
);
2178 while (*author
&& !is_initial_sep(author
[1]))
2185 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2189 draw_mode(struct view
*view
, mode_t mode
)
2195 else if (S_ISLNK(mode
))
2197 else if (S_ISGITLINK(mode
))
2199 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2201 else if (S_ISREG(mode
))
2206 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2210 draw_lineno(struct view
*view
, unsigned int lineno
)
2213 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2214 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2217 lineno
+= view
->offset
+ 1;
2218 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2219 static char fmt
[] = "%1ld";
2221 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2222 if (string_format(number
, fmt
, lineno
))
2226 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2228 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2229 return draw_graphic(view
, LINE_DEFAULT
, &line_graphics
[LINE_GRAPHIC_VLINE
], 1);
2233 draw_view_line(struct view
*view
, unsigned int lineno
)
2236 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2238 assert(view_is_displayed(view
));
2240 if (view
->offset
+ lineno
>= view
->lines
)
2243 line
= &view
->line
[view
->offset
+ lineno
];
2245 wmove(view
->win
, lineno
, 0);
2247 wclrtoeol(view
->win
);
2249 view
->curline
= line
;
2250 view
->curtype
= LINE_NONE
;
2251 line
->selected
= FALSE
;
2252 line
->dirty
= line
->cleareol
= 0;
2255 set_view_attr(view
, LINE_CURSOR
);
2256 line
->selected
= TRUE
;
2257 view
->ops
->select(view
, line
);
2260 return view
->ops
->draw(view
, line
, lineno
);
2264 redraw_view_dirty(struct view
*view
)
2269 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2270 if (view
->offset
+ lineno
>= view
->lines
)
2272 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2275 if (!draw_view_line(view
, lineno
))
2281 wnoutrefresh(view
->win
);
2285 redraw_view_from(struct view
*view
, int lineno
)
2287 assert(0 <= lineno
&& lineno
< view
->height
);
2289 for (; lineno
< view
->height
; lineno
++) {
2290 if (!draw_view_line(view
, lineno
))
2294 wnoutrefresh(view
->win
);
2298 redraw_view(struct view
*view
)
2301 redraw_view_from(view
, 0);
2306 update_view_title(struct view
*view
)
2308 char buf
[SIZEOF_STR
];
2309 char state
[SIZEOF_STR
];
2310 size_t bufpos
= 0, statelen
= 0;
2312 assert(view_is_displayed(view
));
2314 if (view
!= VIEW(REQ_VIEW_STATUS
) && view
->lines
) {
2315 unsigned int view_lines
= view
->offset
+ view
->height
;
2316 unsigned int lines
= view
->lines
2317 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2320 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2329 time_t secs
= time(NULL
) - view
->start_time
;
2331 /* Three git seconds are a long time ... */
2333 string_format_from(state
, &statelen
, " loading %lds", secs
);
2336 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2337 if (*view
->ref
&& bufpos
< view
->width
) {
2338 size_t refsize
= strlen(view
->ref
);
2339 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2341 if (minsize
< view
->width
)
2342 refsize
= view
->width
- minsize
+ 7;
2343 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2346 if (statelen
&& bufpos
< view
->width
) {
2347 string_format_from(buf
, &bufpos
, "%s", state
);
2350 if (view
== display
[current_view
])
2351 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2353 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2355 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2356 wclrtoeol(view
->title
);
2357 wnoutrefresh(view
->title
);
2361 apply_step(double step
, int value
)
2365 value
*= step
+ 0.01;
2366 return value
? value
: 1;
2370 resize_display(void)
2373 struct view
*base
= display
[0];
2374 struct view
*view
= display
[1] ? display
[1] : display
[0];
2376 /* Setup window dimensions */
2378 getmaxyx(stdscr
, base
->height
, base
->width
);
2380 /* Make room for the status window. */
2384 /* Horizontal split. */
2385 view
->width
= base
->width
;
2386 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2387 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2388 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2389 base
->height
-= view
->height
;
2391 /* Make room for the title bar. */
2395 /* Make room for the title bar. */
2400 foreach_displayed_view (view
, i
) {
2402 view
->win
= newwin(view
->height
, 0, offset
, 0);
2404 die("Failed to create %s view", view
->name
);
2406 scrollok(view
->win
, FALSE
);
2408 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2410 die("Failed to create title window");
2413 wresize(view
->win
, view
->height
, view
->width
);
2414 mvwin(view
->win
, offset
, 0);
2415 mvwin(view
->title
, offset
+ view
->height
, 0);
2418 offset
+= view
->height
+ 1;
2423 redraw_display(bool clear
)
2428 foreach_displayed_view (view
, i
) {
2432 update_view_title(view
);
2437 toggle_date_option(enum date
*date
)
2439 static const char *help
[] = {
2446 opt_date
= (opt_date
+ 1) % ARRAY_SIZE(help
);
2447 redraw_display(FALSE
);
2448 report("Displaying %s dates", help
[opt_date
]);
2452 toggle_view_option(bool *option
, const char *help
)
2455 redraw_display(FALSE
);
2456 report("%sabling %s", *option
? "En" : "Dis", help
);
2460 open_option_menu(void)
2462 const struct menu_item menu
[] = {
2463 { '.', "line numbers", &opt_line_number
},
2464 { 'D', "date display", &opt_date
},
2465 { 'A', "author display", &opt_author
},
2466 { 'g', "revision graph display", &opt_rev_graph
},
2467 { 'F', "reference display", &opt_show_refs
},
2472 if (prompt_menu("Toggle option", menu
, &selected
)) {
2473 if (menu
[selected
].data
== &opt_date
)
2474 toggle_date_option(menu
[selected
].data
);
2476 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2481 maximize_view(struct view
*view
)
2483 memset(display
, 0, sizeof(display
));
2485 display
[current_view
] = view
;
2487 redraw_display(FALSE
);
2497 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2499 if (lineno
>= view
->lines
)
2500 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2502 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2503 unsigned long half
= view
->height
/ 2;
2506 offset
= lineno
- half
;
2511 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2512 view
->offset
= offset
;
2513 view
->lineno
= lineno
;
2520 /* Scrolling backend */
2522 do_scroll_view(struct view
*view
, int lines
)
2524 bool redraw_current_line
= FALSE
;
2526 /* The rendering expects the new offset. */
2527 view
->offset
+= lines
;
2529 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2532 /* Move current line into the view. */
2533 if (view
->lineno
< view
->offset
) {
2534 view
->lineno
= view
->offset
;
2535 redraw_current_line
= TRUE
;
2536 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2537 view
->lineno
= view
->offset
+ view
->height
- 1;
2538 redraw_current_line
= TRUE
;
2541 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2543 /* Redraw the whole screen if scrolling is pointless. */
2544 if (view
->height
< ABS(lines
)) {
2548 int line
= lines
> 0 ? view
->height
- lines
: 0;
2549 int end
= line
+ ABS(lines
);
2551 scrollok(view
->win
, TRUE
);
2552 wscrl(view
->win
, lines
);
2553 scrollok(view
->win
, FALSE
);
2555 while (line
< end
&& draw_view_line(view
, line
))
2558 if (redraw_current_line
)
2559 draw_view_line(view
, view
->lineno
- view
->offset
);
2560 wnoutrefresh(view
->win
);
2563 view
->has_scrolled
= TRUE
;
2567 /* Scroll frontend */
2569 scroll_view(struct view
*view
, enum request request
)
2573 assert(view_is_displayed(view
));
2576 case REQ_SCROLL_LEFT
:
2577 if (view
->yoffset
== 0) {
2578 report("Cannot scroll beyond the first column");
2581 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2584 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2585 redraw_view_from(view
, 0);
2588 case REQ_SCROLL_RIGHT
:
2589 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2593 case REQ_SCROLL_PAGE_DOWN
:
2594 lines
= view
->height
;
2595 case REQ_SCROLL_LINE_DOWN
:
2596 if (view
->offset
+ lines
> view
->lines
)
2597 lines
= view
->lines
- view
->offset
;
2599 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2600 report("Cannot scroll beyond the last line");
2605 case REQ_SCROLL_PAGE_UP
:
2606 lines
= view
->height
;
2607 case REQ_SCROLL_LINE_UP
:
2608 if (lines
> view
->offset
)
2609 lines
= view
->offset
;
2612 report("Cannot scroll beyond the first line");
2620 die("request %d not handled in switch", request
);
2623 do_scroll_view(view
, lines
);
2628 move_view(struct view
*view
, enum request request
)
2630 int scroll_steps
= 0;
2634 case REQ_MOVE_FIRST_LINE
:
2635 steps
= -view
->lineno
;
2638 case REQ_MOVE_LAST_LINE
:
2639 steps
= view
->lines
- view
->lineno
- 1;
2642 case REQ_MOVE_PAGE_UP
:
2643 steps
= view
->height
> view
->lineno
2644 ? -view
->lineno
: -view
->height
;
2647 case REQ_MOVE_PAGE_DOWN
:
2648 steps
= view
->lineno
+ view
->height
>= view
->lines
2649 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2661 die("request %d not handled in switch", request
);
2664 if (steps
<= 0 && view
->lineno
== 0) {
2665 report("Cannot move beyond the first line");
2668 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2669 report("Cannot move beyond the last line");
2673 /* Move the current line */
2674 view
->lineno
+= steps
;
2675 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2677 /* Check whether the view needs to be scrolled */
2678 if (view
->lineno
< view
->offset
||
2679 view
->lineno
>= view
->offset
+ view
->height
) {
2680 scroll_steps
= steps
;
2681 if (steps
< 0 && -steps
> view
->offset
) {
2682 scroll_steps
= -view
->offset
;
2684 } else if (steps
> 0) {
2685 if (view
->lineno
== view
->lines
- 1 &&
2686 view
->lines
> view
->height
) {
2687 scroll_steps
= view
->lines
- view
->offset
- 1;
2688 if (scroll_steps
>= view
->height
)
2689 scroll_steps
-= view
->height
- 1;
2694 if (!view_is_displayed(view
)) {
2695 view
->offset
+= scroll_steps
;
2696 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2697 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2701 /* Repaint the old "current" line if we be scrolling */
2702 if (ABS(steps
) < view
->height
)
2703 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
2706 do_scroll_view(view
, scroll_steps
);
2710 /* Draw the current line */
2711 draw_view_line(view
, view
->lineno
- view
->offset
);
2713 wnoutrefresh(view
->win
);
2722 static void search_view(struct view
*view
, enum request request
);
2725 grep_text(struct view
*view
, const char *text
[])
2730 for (i
= 0; text
[i
]; i
++)
2732 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
2738 select_view_line(struct view
*view
, unsigned long lineno
)
2740 unsigned long old_lineno
= view
->lineno
;
2741 unsigned long old_offset
= view
->offset
;
2743 if (goto_view_line(view
, view
->offset
, lineno
)) {
2744 if (view_is_displayed(view
)) {
2745 if (old_offset
!= view
->offset
) {
2748 draw_view_line(view
, old_lineno
- view
->offset
);
2749 draw_view_line(view
, view
->lineno
- view
->offset
);
2750 wnoutrefresh(view
->win
);
2753 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2759 find_next(struct view
*view
, enum request request
)
2761 unsigned long lineno
= view
->lineno
;
2766 report("No previous search");
2768 search_view(view
, request
);
2778 case REQ_SEARCH_BACK
:
2787 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
2788 lineno
+= direction
;
2790 /* Note, lineno is unsigned long so will wrap around in which case it
2791 * will become bigger than view->lines. */
2792 for (; lineno
< view
->lines
; lineno
+= direction
) {
2793 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
2794 select_view_line(view
, lineno
);
2795 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
2800 report("No match found for '%s'", view
->grep
);
2804 search_view(struct view
*view
, enum request request
)
2809 regfree(view
->regex
);
2812 view
->regex
= calloc(1, sizeof(*view
->regex
));
2817 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
2818 if (regex_err
!= 0) {
2819 char buf
[SIZEOF_STR
] = "unknown error";
2821 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
2822 report("Search failed: %s", buf
);
2826 string_copy(view
->grep
, opt_search
);
2828 find_next(view
, request
);
2832 * Incremental updating
2836 reset_view(struct view
*view
)
2840 for (i
= 0; i
< view
->lines
; i
++)
2841 free(view
->line
[i
].data
);
2844 view
->p_offset
= view
->offset
;
2845 view
->p_yoffset
= view
->yoffset
;
2846 view
->p_lineno
= view
->lineno
;
2854 view
->update_secs
= 0;
2858 free_argv(const char *argv
[])
2862 for (argc
= 0; argv
[argc
]; argc
++)
2863 free((void *) argv
[argc
]);
2867 format_argv(const char *dst_argv
[], const char *src_argv
[], enum format_flags flags
)
2869 char buf
[SIZEOF_STR
];
2871 bool noreplace
= flags
== FORMAT_NONE
;
2873 free_argv(dst_argv
);
2875 for (argc
= 0; src_argv
[argc
]; argc
++) {
2876 const char *arg
= src_argv
[argc
];
2880 char *next
= strstr(arg
, "%(");
2881 int len
= next
- arg
;
2884 if (!next
|| noreplace
) {
2885 if (flags
== FORMAT_DASH
&& !strcmp(arg
, "--"))
2890 } else if (!prefixcmp(next
, "%(directory)")) {
2893 } else if (!prefixcmp(next
, "%(file)")) {
2896 } else if (!prefixcmp(next
, "%(ref)")) {
2897 value
= *opt_ref
? opt_ref
: "HEAD";
2899 } else if (!prefixcmp(next
, "%(head)")) {
2902 } else if (!prefixcmp(next
, "%(commit)")) {
2905 } else if (!prefixcmp(next
, "%(blob)")) {
2909 report("Unknown replacement: `%s`", next
);
2913 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
2916 arg
= next
&& !noreplace
? strchr(next
, ')') + 1 : NULL
;
2919 dst_argv
[argc
] = strdup(buf
);
2920 if (!dst_argv
[argc
])
2924 dst_argv
[argc
] = NULL
;
2926 return src_argv
[argc
] == NULL
;
2930 restore_view_position(struct view
*view
)
2932 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
2935 /* Changing the view position cancels the restoring. */
2936 /* FIXME: Changing back to the first line is not detected. */
2937 if (view
->offset
!= 0 || view
->lineno
!= 0) {
2938 view
->p_restore
= FALSE
;
2942 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
2943 view_is_displayed(view
))
2946 view
->yoffset
= view
->p_yoffset
;
2947 view
->p_restore
= FALSE
;
2953 end_update(struct view
*view
, bool force
)
2957 while (!view
->ops
->read(view
, NULL
))
2960 set_nonblocking_input(FALSE
);
2962 kill_io(view
->pipe
);
2963 done_io(view
->pipe
);
2968 setup_update(struct view
*view
, const char *vid
)
2970 set_nonblocking_input(TRUE
);
2972 string_copy_rev(view
->vid
, vid
);
2973 view
->pipe
= &view
->io
;
2974 view
->start_time
= time(NULL
);
2978 prepare_update(struct view
*view
, const char *argv
[], const char *dir
,
2979 enum format_flags flags
)
2982 end_update(view
, TRUE
);
2983 return init_io_rd(&view
->io
, argv
, dir
, flags
);
2987 prepare_update_file(struct view
*view
, const char *name
)
2990 end_update(view
, TRUE
);
2991 return io_open(&view
->io
, name
);
2995 begin_update(struct view
*view
, bool refresh
)
2998 end_update(view
, TRUE
);
3001 if (view
->ops
->prepare
) {
3002 if (!view
->ops
->prepare(view
))
3004 } else if (!init_io_rd(&view
->io
, view
->ops
->argv
, NULL
, FORMAT_ALL
)) {
3008 /* Put the current ref_* value to the view title ref
3009 * member. This is needed by the blob view. Most other
3010 * views sets it automatically after loading because the
3011 * first line is a commit line. */
3012 string_copy_rev(view
->ref
, view
->id
);
3015 if (!start_io(&view
->io
))
3018 setup_update(view
, view
->id
);
3024 update_view(struct view
*view
)
3026 char out_buffer
[BUFSIZ
* 2];
3028 /* Clear the view and redraw everything since the tree sorting
3029 * might have rearranged things. */
3030 bool redraw
= view
->lines
== 0;
3031 bool can_read
= TRUE
;
3036 if (!io_can_read(view
->pipe
)) {
3037 if (view
->lines
== 0 && view_is_displayed(view
)) {
3038 time_t secs
= time(NULL
) - view
->start_time
;
3040 if (secs
> 1 && secs
> view
->update_secs
) {
3041 if (view
->update_secs
== 0)
3043 update_view_title(view
);
3044 view
->update_secs
= secs
;
3050 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
3051 if (opt_iconv
!= ICONV_NONE
) {
3052 ICONV_CONST
char *inbuf
= line
;
3053 size_t inlen
= strlen(line
) + 1;
3055 char *outbuf
= out_buffer
;
3056 size_t outlen
= sizeof(out_buffer
);
3060 ret
= iconv(opt_iconv
, &inbuf
, &inlen
, &outbuf
, &outlen
);
3061 if (ret
!= (size_t) -1)
3065 if (!view
->ops
->read(view
, line
)) {
3066 report("Allocation failure");
3067 end_update(view
, TRUE
);
3073 unsigned long lines
= view
->lines
;
3076 for (digits
= 0; lines
; digits
++)
3079 /* Keep the displayed view in sync with line number scaling. */
3080 if (digits
!= view
->digits
) {
3081 view
->digits
= digits
;
3082 if (opt_line_number
|| view
== VIEW(REQ_VIEW_BLAME
))
3087 if (io_error(view
->pipe
)) {
3088 report("Failed to read: %s", io_strerror(view
->pipe
));
3089 end_update(view
, TRUE
);
3091 } else if (io_eof(view
->pipe
)) {
3093 end_update(view
, FALSE
);
3096 if (restore_view_position(view
))
3099 if (!view_is_displayed(view
))
3103 redraw_view_from(view
, 0);
3105 redraw_view_dirty(view
);
3107 /* Update the title _after_ the redraw so that if the redraw picks up a
3108 * commit reference in view->ref it'll be available here. */
3109 update_view_title(view
);
3113 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
3115 static struct line
*
3116 add_line_data(struct view
*view
, void *data
, enum line_type type
)
3120 if (!realloc_lines(&view
->line
, view
->lines
, 1))
3123 line
= &view
->line
[view
->lines
++];
3124 memset(line
, 0, sizeof(*line
));
3132 static struct line
*
3133 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
3135 char *data
= text
? strdup(text
) : NULL
;
3137 return data
? add_line_data(view
, data
, type
) : NULL
;
3140 static struct line
*
3141 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
3143 char buf
[SIZEOF_STR
];
3146 va_start(args
, fmt
);
3147 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
3151 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3159 OPEN_DEFAULT
= 0, /* Use default view switching. */
3160 OPEN_SPLIT
= 1, /* Split current view. */
3161 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3162 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3163 OPEN_PREPARED
= 32, /* Open already prepared command. */
3167 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3169 bool split
= !!(flags
& OPEN_SPLIT
);
3170 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3171 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3172 struct view
*view
= VIEW(request
);
3173 int nviews
= displayed_views();
3174 struct view
*base_view
= display
[0];
3176 if (view
== prev
&& nviews
== 1 && !reload
) {
3177 report("Already in %s view", view
->name
);
3181 if (view
->git_dir
&& !opt_git_dir
[0]) {
3182 report("The %s view is disabled in pager view", view
->name
);
3189 } else if (!nomaximize
) {
3190 /* Maximize the current view. */
3191 memset(display
, 0, sizeof(display
));
3193 display
[current_view
] = view
;
3196 /* No parent signals that this is the first loaded view. */
3197 if (prev
&& view
!= prev
) {
3198 view
->parent
= prev
;
3201 /* Resize the view when switching between split- and full-screen,
3202 * or when switching between two different full-screen views. */
3203 if (nviews
!= displayed_views() ||
3204 (nviews
== 1 && base_view
!= display
[0]))
3207 if (view
->ops
->open
) {
3209 end_update(view
, TRUE
);
3210 if (!view
->ops
->open(view
)) {
3211 report("Failed to load %s view", view
->name
);
3214 restore_view_position(view
);
3216 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3217 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3218 report("Failed to load %s view", view
->name
);
3222 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3223 /* Take the title line into account. */
3224 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3226 /* Scroll the view that was split if the current line is
3227 * outside the new limited view. */
3228 do_scroll_view(prev
, lines
);
3231 if (prev
&& view
!= prev
) {
3233 /* "Blur" the previous view. */
3234 update_view_title(prev
);
3238 if (view
->pipe
&& view
->lines
== 0) {
3239 /* Clear the old view and let the incremental updating refill
3242 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3244 } else if (view_is_displayed(view
)) {
3251 open_external_viewer(const char *argv
[], const char *dir
)
3253 def_prog_mode(); /* save current tty modes */
3254 endwin(); /* restore original tty modes */
3255 run_io_fg(argv
, dir
);
3256 fprintf(stderr
, "Press Enter to continue");
3259 redraw_display(TRUE
);
3263 open_mergetool(const char *file
)
3265 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3267 open_external_viewer(mergetool_argv
, opt_cdup
);
3271 open_editor(bool from_root
, const char *file
)
3273 const char *editor_argv
[] = { "vi", file
, NULL
};
3276 editor
= getenv("GIT_EDITOR");
3277 if (!editor
&& *opt_editor
)
3278 editor
= opt_editor
;
3280 editor
= getenv("VISUAL");
3282 editor
= getenv("EDITOR");
3286 editor_argv
[0] = editor
;
3287 open_external_viewer(editor_argv
, from_root
? opt_cdup
: NULL
);
3291 open_run_request(enum request request
)
3293 struct run_request
*req
= get_run_request(request
);
3294 const char *argv
[ARRAY_SIZE(req
->argv
)] = { NULL
};
3297 report("Unknown run request");
3301 if (format_argv(argv
, req
->argv
, FORMAT_ALL
))
3302 open_external_viewer(argv
, NULL
);
3307 * User request switch noodle
3311 view_driver(struct view
*view
, enum request request
)
3315 if (request
== REQ_NONE
)
3318 if (request
> REQ_NONE
) {
3319 open_run_request(request
);
3320 /* FIXME: When all views can refresh always do this. */
3321 if (view
== VIEW(REQ_VIEW_STATUS
) ||
3322 view
== VIEW(REQ_VIEW_MAIN
) ||
3323 view
== VIEW(REQ_VIEW_LOG
) ||
3324 view
== VIEW(REQ_VIEW_BRANCH
) ||
3325 view
== VIEW(REQ_VIEW_STAGE
))
3326 request
= REQ_REFRESH
;
3331 if (view
&& view
->lines
) {
3332 request
= view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
3333 if (request
== REQ_NONE
)
3340 case REQ_MOVE_PAGE_UP
:
3341 case REQ_MOVE_PAGE_DOWN
:
3342 case REQ_MOVE_FIRST_LINE
:
3343 case REQ_MOVE_LAST_LINE
:
3344 move_view(view
, request
);
3347 case REQ_SCROLL_LEFT
:
3348 case REQ_SCROLL_RIGHT
:
3349 case REQ_SCROLL_LINE_DOWN
:
3350 case REQ_SCROLL_LINE_UP
:
3351 case REQ_SCROLL_PAGE_DOWN
:
3352 case REQ_SCROLL_PAGE_UP
:
3353 scroll_view(view
, request
);
3356 case REQ_VIEW_BLAME
:
3358 report("No file chosen, press %s to open tree view",
3359 get_key(view
->keymap
, REQ_VIEW_TREE
));
3362 open_view(view
, request
, OPEN_DEFAULT
);
3367 report("No file chosen, press %s to open tree view",
3368 get_key(view
->keymap
, REQ_VIEW_TREE
));
3371 open_view(view
, request
, OPEN_DEFAULT
);
3374 case REQ_VIEW_PAGER
:
3375 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3376 report("No pager content, press %s to run command from prompt",
3377 get_key(view
->keymap
, REQ_PROMPT
));
3380 open_view(view
, request
, OPEN_DEFAULT
);
3383 case REQ_VIEW_STAGE
:
3384 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3385 report("No stage content, press %s to open the status view and choose file",
3386 get_key(view
->keymap
, REQ_VIEW_STATUS
));
3389 open_view(view
, request
, OPEN_DEFAULT
);
3392 case REQ_VIEW_STATUS
:
3393 if (opt_is_inside_work_tree
== FALSE
) {
3394 report("The status view requires a working tree");
3397 open_view(view
, request
, OPEN_DEFAULT
);
3405 case REQ_VIEW_BRANCH
:
3406 open_view(view
, request
, OPEN_DEFAULT
);
3411 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3413 if ((view
== VIEW(REQ_VIEW_DIFF
) &&
3414 view
->parent
== VIEW(REQ_VIEW_MAIN
)) ||
3415 (view
== VIEW(REQ_VIEW_DIFF
) &&
3416 view
->parent
== VIEW(REQ_VIEW_BLAME
)) ||
3417 (view
== VIEW(REQ_VIEW_STAGE
) &&
3418 view
->parent
== VIEW(REQ_VIEW_STATUS
)) ||
3419 (view
== VIEW(REQ_VIEW_BLOB
) &&
3420 view
->parent
== VIEW(REQ_VIEW_TREE
)) ||
3421 (view
== VIEW(REQ_VIEW_MAIN
) &&
3422 view
->parent
== VIEW(REQ_VIEW_BRANCH
))) {
3425 view
= view
->parent
;
3426 line
= view
->lineno
;
3427 move_view(view
, request
);
3428 if (view_is_displayed(view
))
3429 update_view_title(view
);
3430 if (line
!= view
->lineno
)
3431 view
->ops
->request(view
, REQ_ENTER
,
3432 &view
->line
[view
->lineno
]);
3435 move_view(view
, request
);
3441 int nviews
= displayed_views();
3442 int next_view
= (current_view
+ 1) % nviews
;
3444 if (next_view
== current_view
) {
3445 report("Only one view is displayed");
3449 current_view
= next_view
;
3450 /* Blur out the title of the previous view. */
3451 update_view_title(view
);
3456 report("Refreshing is not yet supported for the %s view", view
->name
);
3460 if (displayed_views() == 2)
3461 maximize_view(view
);
3468 case REQ_TOGGLE_LINENO
:
3469 toggle_view_option(&opt_line_number
, "line numbers");
3472 case REQ_TOGGLE_DATE
:
3473 toggle_date_option(&opt_date
);
3476 case REQ_TOGGLE_AUTHOR
:
3477 toggle_view_option(&opt_author
, "author display");
3480 case REQ_TOGGLE_REV_GRAPH
:
3481 toggle_view_option(&opt_rev_graph
, "revision graph display");
3484 case REQ_TOGGLE_REFS
:
3485 toggle_view_option(&opt_show_refs
, "reference display");
3488 case REQ_TOGGLE_SORT_FIELD
:
3489 case REQ_TOGGLE_SORT_ORDER
:
3490 report("Sorting is not yet supported for the %s view", view
->name
);
3494 case REQ_SEARCH_BACK
:
3495 search_view(view
, request
);
3500 find_next(view
, request
);
3503 case REQ_STOP_LOADING
:
3504 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
3507 report("Stopped loading the %s view", view
->name
),
3508 end_update(view
, TRUE
);
3512 case REQ_SHOW_VERSION
:
3513 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3516 case REQ_SCREEN_REDRAW
:
3517 redraw_display(TRUE
);
3521 report("Nothing to edit");
3525 report("Nothing to enter");
3528 case REQ_VIEW_CLOSE
:
3529 /* XXX: Mark closed views by letting view->parent point to the
3530 * view itself. Parents to closed view should never be
3533 view
->parent
->parent
!= view
->parent
) {
3534 maximize_view(view
->parent
);
3535 view
->parent
= view
;
3543 report("Unknown key, press %s for help",
3544 get_key(view
->keymap
, REQ_VIEW_HELP
));
3553 * View backend utilities
3563 const enum sort_field
*fields
;
3564 size_t size
, current
;
3568 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3569 #define get_sort_field(state) ((state).fields[(state).current])
3570 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3573 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3574 int (*compare
)(const void *, const void *))
3577 case REQ_TOGGLE_SORT_FIELD
:
3578 state
->current
= (state
->current
+ 1) % state
->size
;
3581 case REQ_TOGGLE_SORT_ORDER
:
3582 state
->reverse
= !state
->reverse
;
3585 die("Not a sort request");
3588 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3592 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3594 /* Small author cache to reduce memory consumption. It uses binary
3595 * search to lookup or find place to position new entries. No entries
3596 * are ever freed. */
3598 get_author(const char *name
)
3600 static const char **authors
;
3601 static size_t authors_size
;
3602 int from
= 0, to
= authors_size
- 1;
3604 while (from
<= to
) {
3605 size_t pos
= (to
+ from
) / 2;
3606 int cmp
= strcmp(name
, authors
[pos
]);
3609 return authors
[pos
];
3617 if (!realloc_authors(&authors
, authors_size
, 1))
3619 name
= strdup(name
);
3623 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3624 authors
[from
] = name
;
3631 parse_timezone(time_t *time
, const char *zone
)
3635 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3636 tz
+= ('0' - zone
[2]) * 60 * 60;
3637 tz
+= ('0' - zone
[3]) * 60;
3638 tz
+= ('0' - zone
[4]);
3646 /* Parse author lines where the name may be empty:
3647 * author <email@address.tld> 1138474660 +0100
3650 parse_author_line(char *ident
, const char **author
, time_t *time
)
3652 char *nameend
= strchr(ident
, '<');
3653 char *emailend
= strchr(ident
, '>');
3655 if (nameend
&& emailend
)
3656 *nameend
= *emailend
= 0;
3657 ident
= chomp_string(ident
);
3660 ident
= chomp_string(nameend
+ 1);
3665 *author
= get_author(ident
);
3667 /* Parse epoch and timezone */
3668 if (emailend
&& emailend
[1] == ' ') {
3669 char *secs
= emailend
+ 2;
3670 char *zone
= strchr(secs
, ' ');
3672 *time
= (time_t) atol(secs
);
3674 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
3675 parse_timezone(time
, zone
+ 1);
3680 open_commit_parent_menu(char buf
[SIZEOF_STR
], int *parents
)
3682 char rev
[SIZEOF_REV
];
3683 const char *revlist_argv
[] = {
3684 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev
, NULL
3686 struct menu_item
*items
;
3687 char text
[SIZEOF_STR
];
3691 items
= calloc(*parents
+ 1, sizeof(*items
));
3695 for (i
= 0; i
< *parents
; i
++) {
3696 string_copy_rev(rev
, &buf
[SIZEOF_REV
* i
]);
3697 if (!run_io_buf(revlist_argv
, text
, sizeof(text
)) ||
3698 !(items
[i
].text
= strdup(text
))) {
3706 ok
= prompt_menu("Select parent", items
, parents
);
3708 for (i
= 0; items
[i
].text
; i
++)
3709 free((char *) items
[i
].text
);
3715 select_commit_parent(const char *id
, char rev
[SIZEOF_REV
], const char *path
)
3717 char buf
[SIZEOF_STR
* 4];
3718 const char *revlist_argv
[] = {
3719 "git", "log", "--no-color", "-1",
3720 "--pretty=format:%P", id
, "--", path
, NULL
3724 if (!run_io_buf(revlist_argv
, buf
, sizeof(buf
)) ||
3725 (parents
= strlen(buf
) / 40) < 0) {
3726 report("Failed to get parent information");
3729 } else if (parents
== 0) {
3731 report("Path '%s' does not exist in the parent", path
);
3733 report("The selected commit has no parents");
3737 if (parents
> 1 && !open_commit_parent_menu(buf
, &parents
))
3740 string_copy_rev(rev
, &buf
[41 * parents
]);
3749 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
3751 char text
[SIZEOF_STR
];
3753 if (opt_line_number
&& draw_lineno(view
, lineno
))
3756 string_expand(text
, sizeof(text
), line
->data
, opt_tab_size
);
3757 draw_text(view
, line
->type
, text
, TRUE
);
3762 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
3764 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
3765 char ref
[SIZEOF_STR
];
3767 if (!run_io_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
3770 /* This is the only fatal call, since it can "corrupt" the buffer. */
3771 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
3778 add_pager_refs(struct view
*view
, struct line
*line
)
3780 char buf
[SIZEOF_STR
];
3781 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
3782 struct ref_list
*list
;
3783 size_t bufpos
= 0, i
;
3784 const char *sep
= "Refs: ";
3785 bool is_tag
= FALSE
;
3787 assert(line
->type
== LINE_COMMIT
);
3789 list
= get_ref_list(commit_id
);
3791 if (view
== VIEW(REQ_VIEW_DIFF
))
3792 goto try_add_describe_ref
;
3796 for (i
= 0; i
< list
->size
; i
++) {
3797 struct ref
*ref
= list
->refs
[i
];
3798 const char *fmt
= ref
->tag
? "%s[%s]" :
3799 ref
->remote
? "%s<%s>" : "%s%s";
3801 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
3808 if (!is_tag
&& view
== VIEW(REQ_VIEW_DIFF
)) {
3809 try_add_describe_ref
:
3810 /* Add <tag>-g<commit_id> "fake" reference. */
3811 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
3818 add_line_text(view
, buf
, LINE_PP_REFS
);
3822 pager_read(struct view
*view
, char *data
)
3829 line
= add_line_text(view
, data
, get_line_type(data
));
3833 if (line
->type
== LINE_COMMIT
&&
3834 (view
== VIEW(REQ_VIEW_DIFF
) ||
3835 view
== VIEW(REQ_VIEW_LOG
)))
3836 add_pager_refs(view
, line
);
3842 pager_request(struct view
*view
, enum request request
, struct line
*line
)
3846 if (request
!= REQ_ENTER
)
3849 if (line
->type
== LINE_COMMIT
&&
3850 (view
== VIEW(REQ_VIEW_LOG
) ||
3851 view
== VIEW(REQ_VIEW_PAGER
))) {
3852 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
3856 /* Always scroll the view even if it was split. That way
3857 * you can use Enter to scroll through the log view and
3858 * split open each commit diff. */
3859 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
3861 /* FIXME: A minor workaround. Scrolling the view will call report("")
3862 * but if we are scrolling a non-current view this won't properly
3863 * update the view title. */
3865 update_view_title(view
);
3871 pager_grep(struct view
*view
, struct line
*line
)
3873 const char *text
[] = { line
->data
, NULL
};
3875 return grep_text(view
, text
);
3879 pager_select(struct view
*view
, struct line
*line
)
3881 if (line
->type
== LINE_COMMIT
) {
3882 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
3884 if (view
!= VIEW(REQ_VIEW_PAGER
))
3885 string_copy_rev(view
->ref
, text
);
3886 string_copy_rev(ref_commit
, text
);
3890 static struct view_ops pager_ops
= {
3901 static const char *log_argv
[SIZEOF_ARG
] = {
3902 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3906 log_request(struct view
*view
, enum request request
, struct line
*line
)
3911 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
3914 return pager_request(view
, request
, line
);
3918 static struct view_ops log_ops
= {
3929 static const char *diff_argv
[SIZEOF_ARG
] = {
3930 "git", "show", "--pretty=fuller", "--no-color", "--root",
3931 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3934 static struct view_ops diff_ops
= {
3949 static bool help_keymap_hidden
[ARRAY_SIZE(keymap_table
)];
3952 help_name(char buf
[SIZEOF_STR
], const char *name
, size_t namelen
)
3956 for (bufpos
= 0; bufpos
<= namelen
; bufpos
++) {
3957 buf
[bufpos
] = tolower(name
[bufpos
]);
3958 if (buf
[bufpos
] == '_')
3966 #define help_keymap_name(buf, keymap) \
3967 help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3970 help_open_keymap_title(struct view
*view
, enum keymap keymap
)
3972 char buf
[SIZEOF_STR
];
3975 line
= add_line_format(view
, LINE_HELP_KEYMAP
, "[%c] %s bindings",
3976 help_keymap_hidden
[keymap
] ? '+' : '-',
3977 help_keymap_name(buf
, keymap
));
3979 line
->other
= keymap
;
3981 return help_keymap_hidden
[keymap
];
3985 help_open_keymap(struct view
*view
, enum keymap keymap
)
3987 const char *group
= NULL
;
3988 char buf
[SIZEOF_STR
];
3990 bool add_title
= TRUE
;
3993 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
3994 const char *key
= NULL
;
3996 if (req_info
[i
].request
== REQ_NONE
)
3999 if (!req_info
[i
].request
) {
4000 group
= req_info
[i
].help
;
4004 key
= get_keys(keymap
, req_info
[i
].request
, TRUE
);
4008 if (add_title
&& help_open_keymap_title(view
, keymap
))
4013 add_line_text(view
, group
, LINE_HELP_GROUP
);
4017 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s", key
,
4018 help_name(buf
, req_info
[i
].name
, req_info
[i
].namelen
),
4022 group
= "External commands:";
4024 for (i
= 0; i
< run_requests
; i
++) {
4025 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
4029 if (!req
|| req
->keymap
!= keymap
)
4032 key
= get_key_name(req
->key
);
4034 key
= "(no key defined)";
4036 if (add_title
&& help_open_keymap_title(view
, keymap
))
4039 add_line_text(view
, group
, LINE_HELP_GROUP
);
4043 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
4044 if (!string_format_from(buf
, &bufpos
, "%s%s",
4045 argc
? " " : "", req
->argv
[argc
]))
4048 add_line_format(view
, LINE_DEFAULT
, " %-25s `%s`", key
, buf
);
4053 help_open(struct view
*view
)
4058 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
4059 add_line_text(view
, "", LINE_DEFAULT
);
4061 for (keymap
= 0; keymap
< ARRAY_SIZE(keymap_table
); keymap
++)
4062 help_open_keymap(view
, keymap
);
4068 help_request(struct view
*view
, enum request request
, struct line
*line
)
4072 if (line
->type
== LINE_HELP_KEYMAP
) {
4073 help_keymap_hidden
[line
->other
] =
4074 !help_keymap_hidden
[line
->other
];
4075 view
->p_restore
= TRUE
;
4076 open_view(view
, REQ_VIEW_HELP
, OPEN_REFRESH
);
4081 return pager_request(view
, request
, line
);
4085 static struct view_ops help_ops
= {
4101 struct tree_stack_entry
{
4102 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
4103 unsigned long lineno
; /* Line number to restore */
4104 char *name
; /* Position of name in opt_path */
4107 /* The top of the path stack. */
4108 static struct tree_stack_entry
*tree_stack
= NULL
;
4109 unsigned long tree_lineno
= 0;
4112 pop_tree_stack_entry(void)
4114 struct tree_stack_entry
*entry
= tree_stack
;
4116 tree_lineno
= entry
->lineno
;
4118 tree_stack
= entry
->prev
;
4123 push_tree_stack_entry(const char *name
, unsigned long lineno
)
4125 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
4126 size_t pathlen
= strlen(opt_path
);
4131 entry
->prev
= tree_stack
;
4132 entry
->name
= opt_path
+ pathlen
;
4135 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
4136 pop_tree_stack_entry();
4140 /* Move the current line to the first tree entry. */
4142 entry
->lineno
= lineno
;
4145 /* Parse output from git-ls-tree(1):
4147 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4150 #define SIZEOF_TREE_ATTR \
4151 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4153 #define SIZEOF_TREE_MODE \
4154 STRING_SIZE("100644 ")
4156 #define TREE_ID_OFFSET \
4157 STRING_SIZE("100644 blob ")
4160 char id
[SIZEOF_REV
];
4162 time_t time
; /* Date from the author ident. */
4163 const char *author
; /* Author of the commit. */
4168 tree_path(const struct line
*line
)
4170 return ((struct tree_entry
*) line
->data
)->name
;
4174 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
4176 if (line1
->type
!= line2
->type
)
4177 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
4178 return strcmp(tree_path(line1
), tree_path(line2
));
4181 static const enum sort_field tree_sort_fields
[] = {
4182 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4184 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
4187 tree_compare(const void *l1
, const void *l2
)
4189 const struct line
*line1
= (const struct line
*) l1
;
4190 const struct line
*line2
= (const struct line
*) l2
;
4191 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
4192 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
4194 if (line1
->type
== LINE_TREE_HEAD
)
4196 if (line2
->type
== LINE_TREE_HEAD
)
4199 switch (get_sort_field(tree_sort_state
)) {
4201 return sort_order(tree_sort_state
, entry1
->time
- entry2
->time
);
4203 case ORDERBY_AUTHOR
:
4204 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
4208 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
4213 static struct line
*
4214 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
4215 const char *mode
, const char *id
)
4217 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
4218 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
4220 if (!entry
|| !line
) {
4225 strncpy(entry
->name
, path
, strlen(path
));
4227 entry
->mode
= strtoul(mode
, NULL
, 8);
4229 string_copy_rev(entry
->id
, id
);
4235 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4237 static const char *author_name
;
4238 static time_t author_time
;
4240 if (!text
&& *read_date
) {
4245 char *path
= *opt_path
? opt_path
: ".";
4246 /* Find next entry to process */
4247 const char *log_file
[] = {
4248 "git", "log", "--no-color", "--pretty=raw",
4249 "--cc", "--raw", view
->id
, "--", path
, NULL
4254 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4255 report("Tree is empty");
4259 if (!run_io_rd_dir(&io
, log_file
, opt_cdup
, FORMAT_NONE
)) {
4260 report("Failed to load tree data");
4264 done_io(view
->pipe
);
4269 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4270 parse_author_line(text
+ STRING_SIZE("author "),
4271 &author_name
, &author_time
);
4273 } else if (*text
== ':') {
4275 size_t annotated
= 1;
4278 pos
= strchr(text
, '\t');
4282 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4283 text
+= strlen(opt_path
);
4284 pos
= strchr(text
, '/');
4288 for (i
= 1; i
< view
->lines
; i
++) {
4289 struct line
*line
= &view
->line
[i
];
4290 struct tree_entry
*entry
= line
->data
;
4292 annotated
+= !!entry
->author
;
4293 if (entry
->author
|| strcmp(entry
->name
, text
))
4296 entry
->author
= author_name
;
4297 entry
->time
= author_time
;
4302 if (annotated
== view
->lines
)
4303 kill_io(view
->pipe
);
4309 tree_read(struct view
*view
, char *text
)
4311 static bool read_date
= FALSE
;
4312 struct tree_entry
*data
;
4313 struct line
*entry
, *line
;
4314 enum line_type type
;
4315 size_t textlen
= text
? strlen(text
) : 0;
4316 char *path
= text
+ SIZEOF_TREE_ATTR
;
4318 if (read_date
|| !text
)
4319 return tree_read_date(view
, text
, &read_date
);
4321 if (textlen
<= SIZEOF_TREE_ATTR
)
4323 if (view
->lines
== 0 &&
4324 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4327 /* Strip the path part ... */
4329 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4330 size_t striplen
= strlen(opt_path
);
4332 if (pathlen
> striplen
)
4333 memmove(path
, path
+ striplen
,
4334 pathlen
- striplen
+ 1);
4336 /* Insert "link" to parent directory. */
4337 if (view
->lines
== 1 &&
4338 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4342 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4343 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4348 /* Skip "Directory ..." and ".." line. */
4349 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4350 if (tree_compare_entry(line
, entry
) <= 0)
4353 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4357 for (; line
<= entry
; line
++)
4358 line
->dirty
= line
->cleareol
= 1;
4362 if (tree_lineno
> view
->lineno
) {
4363 view
->lineno
= tree_lineno
;
4371 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4373 struct tree_entry
*entry
= line
->data
;
4375 if (line
->type
== LINE_TREE_HEAD
) {
4376 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4379 if (draw_mode(view
, entry
->mode
))
4382 if (opt_author
&& draw_author(view
, entry
->author
))
4385 if (opt_date
&& draw_date(view
, entry
->author
? &entry
->time
: NULL
))
4388 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4396 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4397 int fd
= mkstemp(file
);
4400 report("Failed to create temporary file");
4401 else if (!run_io_append(blob_ops
.argv
, FORMAT_ALL
, fd
))
4402 report("Failed to save blob data to file");
4404 open_editor(FALSE
, file
);
4410 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4412 enum open_flags flags
;
4415 case REQ_VIEW_BLAME
:
4416 if (line
->type
!= LINE_TREE_FILE
) {
4417 report("Blame only supported for files");
4421 string_copy(opt_ref
, view
->vid
);
4425 if (line
->type
!= LINE_TREE_FILE
) {
4426 report("Edit only supported for files");
4427 } else if (!is_head_commit(view
->vid
)) {
4430 open_editor(TRUE
, opt_file
);
4434 case REQ_TOGGLE_SORT_FIELD
:
4435 case REQ_TOGGLE_SORT_ORDER
:
4436 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4441 /* quit view if at top of tree */
4442 return REQ_VIEW_CLOSE
;
4445 line
= &view
->line
[1];
4455 /* Cleanup the stack if the tree view is at a different tree. */
4456 while (!*opt_path
&& tree_stack
)
4457 pop_tree_stack_entry();
4459 switch (line
->type
) {
4461 /* Depending on whether it is a subdirectory or parent link
4462 * mangle the path buffer. */
4463 if (line
== &view
->line
[1] && *opt_path
) {
4464 pop_tree_stack_entry();
4467 const char *basename
= tree_path(line
);
4469 push_tree_stack_entry(basename
, view
->lineno
);
4472 /* Trees and subtrees share the same ID, so they are not not
4473 * unique like blobs. */
4474 flags
= OPEN_RELOAD
;
4475 request
= REQ_VIEW_TREE
;
4478 case LINE_TREE_FILE
:
4479 flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4480 request
= REQ_VIEW_BLOB
;
4487 open_view(view
, request
, flags
);
4488 if (request
== REQ_VIEW_TREE
)
4489 view
->lineno
= tree_lineno
;
4495 tree_grep(struct view
*view
, struct line
*line
)
4497 struct tree_entry
*entry
= line
->data
;
4498 const char *text
[] = {
4500 opt_author
? entry
->author
: "",
4501 opt_date
? mkdate(&entry
->time
) : "",
4505 return grep_text(view
, text
);
4509 tree_select(struct view
*view
, struct line
*line
)
4511 struct tree_entry
*entry
= line
->data
;
4513 if (line
->type
== LINE_TREE_FILE
) {
4514 string_copy_rev(ref_blob
, entry
->id
);
4515 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4517 } else if (line
->type
!= LINE_TREE_DIR
) {
4521 string_copy_rev(view
->ref
, entry
->id
);
4525 tree_prepare(struct view
*view
)
4527 if (view
->lines
== 0 && opt_prefix
[0]) {
4528 char *pos
= opt_prefix
;
4530 while (pos
&& *pos
) {
4531 char *end
= strchr(pos
, '/');
4535 push_tree_stack_entry(pos
, 0);
4543 } else if (strcmp(view
->vid
, view
->id
)) {
4547 return init_io_rd(&view
->io
, view
->ops
->argv
, opt_cdup
, FORMAT_ALL
);
4550 static const char *tree_argv
[SIZEOF_ARG
] = {
4551 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4554 static struct view_ops tree_ops
= {
4567 blob_read(struct view
*view
, char *line
)
4571 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4575 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4582 return pager_request(view
, request
, line
);
4586 static const char *blob_argv
[SIZEOF_ARG
] = {
4587 "git", "cat-file", "blob", "%(blob)", NULL
4590 static struct view_ops blob_ops
= {
4604 * Loading the blame view is a two phase job:
4606 * 1. File content is read either using opt_file from the
4607 * filesystem or using git-cat-file.
4608 * 2. Then blame information is incrementally added by
4609 * reading output from git-blame.
4612 static const char *blame_head_argv
[] = {
4613 "git", "blame", "--incremental", "--", "%(file)", NULL
4616 static const char *blame_ref_argv
[] = {
4617 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4620 static const char *blame_cat_file_argv
[] = {
4621 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4624 struct blame_commit
{
4625 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4626 char title
[128]; /* First line of the commit message. */
4627 const char *author
; /* Author of the commit. */
4628 time_t time
; /* Date from the author ident. */
4629 char filename
[128]; /* Name of file. */
4630 bool has_previous
; /* Was a "previous" line detected. */
4634 struct blame_commit
*commit
;
4635 unsigned long lineno
;
4640 blame_open(struct view
*view
)
4642 char path
[SIZEOF_STR
];
4644 if (!view
->parent
&& *opt_prefix
) {
4645 string_copy(path
, opt_file
);
4646 if (!string_format(opt_file
, "%s%s", opt_prefix
, path
))
4650 if (!string_format(path
, "%s%s", opt_cdup
, opt_file
))
4653 if (*opt_ref
|| !io_open(&view
->io
, path
)) {
4654 if (!run_io_rd_dir(&view
->io
, blame_cat_file_argv
, opt_cdup
, FORMAT_ALL
))
4658 setup_update(view
, opt_file
);
4659 string_format(view
->ref
, "%s ...", opt_file
);
4664 static struct blame_commit
*
4665 get_blame_commit(struct view
*view
, const char *id
)
4669 for (i
= 0; i
< view
->lines
; i
++) {
4670 struct blame
*blame
= view
->line
[i
].data
;
4675 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4676 return blame
->commit
;
4680 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4683 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4689 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4691 const char *pos
= *posref
;
4694 pos
= strchr(pos
+ 1, ' ');
4695 if (!pos
|| !isdigit(pos
[1]))
4697 *number
= atoi(pos
+ 1);
4698 if (*number
< min
|| *number
> max
)
4705 static struct blame_commit
*
4706 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4708 struct blame_commit
*commit
;
4709 struct blame
*blame
;
4710 const char *pos
= text
+ SIZEOF_REV
- 2;
4711 size_t orig_lineno
= 0;
4715 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4718 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4719 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4720 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4723 commit
= get_blame_commit(view
, text
);
4729 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4732 blame
->commit
= commit
;
4733 blame
->lineno
= orig_lineno
+ group
- 1;
4741 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4744 const char **argv
= *opt_ref
? blame_ref_argv
: blame_head_argv
;
4747 if (view
->lines
== 0 && !view
->parent
)
4748 die("No blame exist for %s", view
->vid
);
4750 if (view
->lines
== 0 || !run_io_rd_dir(&io
, argv
, opt_cdup
, FORMAT_ALL
)) {
4751 report("Failed to load blame data");
4755 done_io(view
->pipe
);
4761 size_t linelen
= strlen(line
);
4762 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
4767 blame
->commit
= NULL
;
4768 strncpy(blame
->text
, line
, linelen
);
4769 blame
->text
[linelen
] = 0;
4770 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
4775 match_blame_header(const char *name
, char **line
)
4777 size_t namelen
= strlen(name
);
4778 bool matched
= !strncmp(name
, *line
, namelen
);
4787 blame_read(struct view
*view
, char *line
)
4789 static struct blame_commit
*commit
= NULL
;
4790 static int blamed
= 0;
4791 static bool read_file
= TRUE
;
4794 return blame_read_file(view
, line
, &read_file
);
4801 string_format(view
->ref
, "%s", view
->vid
);
4802 if (view_is_displayed(view
)) {
4803 update_view_title(view
);
4804 redraw_view_from(view
, 0);
4810 commit
= parse_blame_commit(view
, line
, &blamed
);
4811 string_format(view
->ref
, "%s %2d%%", view
->vid
,
4812 view
->lines
? blamed
* 100 / view
->lines
: 0);
4814 } else if (match_blame_header("author ", &line
)) {
4815 commit
->author
= get_author(line
);
4817 } else if (match_blame_header("author-time ", &line
)) {
4818 commit
->time
= (time_t) atol(line
);
4820 } else if (match_blame_header("author-tz ", &line
)) {
4821 parse_timezone(&commit
->time
, line
);
4823 } else if (match_blame_header("summary ", &line
)) {
4824 string_ncopy(commit
->title
, line
, strlen(line
));
4826 } else if (match_blame_header("previous ", &line
)) {
4827 commit
->has_previous
= TRUE
;
4829 } else if (match_blame_header("filename ", &line
)) {
4830 string_ncopy(commit
->filename
, line
, strlen(line
));
4838 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4840 struct blame
*blame
= line
->data
;
4841 time_t *time
= NULL
;
4842 const char *id
= NULL
, *author
= NULL
;
4843 char text
[SIZEOF_STR
];
4845 if (blame
->commit
&& *blame
->commit
->filename
) {
4846 id
= blame
->commit
->id
;
4847 author
= blame
->commit
->author
;
4848 time
= &blame
->commit
->time
;
4851 if (opt_date
&& draw_date(view
, time
))
4854 if (opt_author
&& draw_author(view
, author
))
4857 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
4860 if (draw_lineno(view
, lineno
))
4863 string_expand(text
, sizeof(text
), blame
->text
, opt_tab_size
);
4864 draw_text(view
, LINE_DEFAULT
, text
, TRUE
);
4869 check_blame_commit(struct blame
*blame
, bool check_null_id
)
4872 report("Commit data not loaded yet");
4873 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4874 report("No commit exist for the selected line");
4881 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
4883 const char *diff_tree_argv
[] = {
4884 "git", "diff-tree", "-U0", blame
->commit
->id
,
4885 "--", blame
->commit
->filename
, NULL
4888 int parent_lineno
= -1;
4889 int blamed_lineno
= -1;
4892 if (!run_io(&io
, diff_tree_argv
, NULL
, IO_RD
))
4895 while ((line
= io_get(&io
, '\n', TRUE
))) {
4897 char *pos
= strchr(line
, '+');
4899 parent_lineno
= atoi(line
+ 4);
4901 blamed_lineno
= atoi(pos
+ 1);
4903 } else if (*line
== '+' && parent_lineno
!= -1) {
4904 if (blame
->lineno
== blamed_lineno
- 1 &&
4905 !strcmp(blame
->text
, line
+ 1)) {
4906 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
4917 blame_request(struct view
*view
, enum request request
, struct line
*line
)
4919 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4920 struct blame
*blame
= line
->data
;
4923 case REQ_VIEW_BLAME
:
4924 if (check_blame_commit(blame
, TRUE
)) {
4925 string_copy(opt_ref
, blame
->commit
->id
);
4926 string_copy(opt_file
, blame
->commit
->filename
);
4928 view
->lineno
= blame
->lineno
;
4929 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
4934 if (check_blame_commit(blame
, TRUE
) &&
4935 select_commit_parent(blame
->commit
->id
, opt_ref
,
4936 blame
->commit
->filename
)) {
4937 string_copy(opt_file
, blame
->commit
->filename
);
4938 setup_blame_parent_line(view
, blame
);
4939 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
4944 if (!check_blame_commit(blame
, FALSE
))
4947 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
4948 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
4951 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
4952 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
4953 const char *diff_index_argv
[] = {
4954 "git", "diff-index", "--root", "--patch-with-stat",
4955 "-C", "-M", "HEAD", "--", view
->vid
, NULL
4958 if (!blame
->commit
->has_previous
) {
4959 diff_index_argv
[1] = "diff";
4960 diff_index_argv
[2] = "--no-color";
4961 diff_index_argv
[6] = "--";
4962 diff_index_argv
[7] = "/dev/null";
4965 if (!prepare_update(diff
, diff_index_argv
, NULL
, FORMAT_DASH
)) {
4966 report("Failed to allocate diff command");
4969 flags
|= OPEN_PREPARED
;
4972 open_view(view
, REQ_VIEW_DIFF
, flags
);
4973 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4974 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
4985 blame_grep(struct view
*view
, struct line
*line
)
4987 struct blame
*blame
= line
->data
;
4988 struct blame_commit
*commit
= blame
->commit
;
4989 const char *text
[] = {
4991 commit
? commit
->title
: "",
4992 commit
? commit
->id
: "",
4993 commit
&& opt_author
? commit
->author
: "",
4994 commit
&& opt_date
? mkdate(&commit
->time
) : "",
4998 return grep_text(view
, text
);
5002 blame_select(struct view
*view
, struct line
*line
)
5004 struct blame
*blame
= line
->data
;
5005 struct blame_commit
*commit
= blame
->commit
;
5010 if (!strcmp(commit
->id
, NULL_ID
))
5011 string_ncopy(ref_commit
, "HEAD", 4);
5013 string_copy_rev(ref_commit
, commit
->id
);
5016 static struct view_ops blame_ops
= {
5032 const char *author
; /* Author of the last commit. */
5033 time_t time
; /* Date of the last activity. */
5034 struct ref
*ref
; /* Name and commit ID information. */
5037 static const enum sort_field branch_sort_fields
[] = {
5038 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
5040 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
5043 branch_compare(const void *l1
, const void *l2
)
5045 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
5046 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
5048 switch (get_sort_field(branch_sort_state
)) {
5050 return sort_order(branch_sort_state
, branch1
->time
- branch2
->time
);
5052 case ORDERBY_AUTHOR
:
5053 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
5057 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
5062 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5064 struct branch
*branch
= line
->data
;
5065 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
5067 if (opt_date
&& draw_date(view
, branch
->author
? &branch
->time
: NULL
))
5070 if (opt_author
&& draw_author(view
, branch
->author
))
5073 draw_text(view
, type
, branch
->ref
->name
, TRUE
);
5078 branch_request(struct view
*view
, enum request request
, struct line
*line
)
5083 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
5086 case REQ_TOGGLE_SORT_FIELD
:
5087 case REQ_TOGGLE_SORT_ORDER
:
5088 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
5092 open_view(view
, REQ_VIEW_MAIN
, OPEN_SPLIT
);
5101 branch_read(struct view
*view
, char *line
)
5103 static char id
[SIZEOF_REV
];
5104 struct branch
*reference
;
5110 switch (get_line_type(line
)) {
5112 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
5116 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
5117 struct branch
*branch
= view
->line
[i
].data
;
5119 if (strcmp(branch
->ref
->id
, id
))
5122 view
->line
[i
].dirty
= TRUE
;
5124 branch
->author
= reference
->author
;
5125 branch
->time
= reference
->time
;
5129 parse_author_line(line
+ STRING_SIZE("author "),
5130 &branch
->author
, &branch
->time
);
5142 branch_open_visitor(void *data
, struct ref
*ref
)
5144 struct view
*view
= data
;
5145 struct branch
*branch
;
5147 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
5150 branch
= calloc(1, sizeof(*branch
));
5155 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
5159 branch_open(struct view
*view
)
5161 const char *branch_log
[] = {
5162 "git", "log", "--no-color", "--pretty=raw",
5163 "--simplify-by-decoration", "--all", NULL
5166 if (!run_io_rd(&view
->io
, branch_log
, FORMAT_NONE
)) {
5167 report("Failed to load branch data");
5171 setup_update(view
, view
->id
);
5172 foreach_ref(branch_open_visitor
, view
);
5173 view
->p_restore
= TRUE
;
5179 branch_grep(struct view
*view
, struct line
*line
)
5181 struct branch
*branch
= line
->data
;
5182 const char *text
[] = {
5188 return grep_text(view
, text
);
5192 branch_select(struct view
*view
, struct line
*line
)
5194 struct branch
*branch
= line
->data
;
5196 string_copy_rev(view
->ref
, branch
->ref
->id
);
5197 string_copy_rev(ref_commit
, branch
->ref
->id
);
5198 string_copy_rev(ref_head
, branch
->ref
->id
);
5201 static struct view_ops branch_ops
= {
5220 char rev
[SIZEOF_REV
];
5221 char name
[SIZEOF_STR
];
5225 char rev
[SIZEOF_REV
];
5226 char name
[SIZEOF_STR
];
5230 static char status_onbranch
[SIZEOF_STR
];
5231 static struct status stage_status
;
5232 static enum line_type stage_line_type
;
5233 static size_t stage_chunks
;
5234 static int *stage_chunk
;
5236 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
5238 /* This should work even for the "On branch" line. */
5240 status_has_none(struct view
*view
, struct line
*line
)
5242 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
5245 /* Get fields from the diff line:
5246 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5249 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
5251 const char *old_mode
= buf
+ 1;
5252 const char *new_mode
= buf
+ 8;
5253 const char *old_rev
= buf
+ 15;
5254 const char *new_rev
= buf
+ 56;
5255 const char *status
= buf
+ 97;
5258 old_mode
[-1] != ':' ||
5259 new_mode
[-1] != ' ' ||
5260 old_rev
[-1] != ' ' ||
5261 new_rev
[-1] != ' ' ||
5265 file
->status
= *status
;
5267 string_copy_rev(file
->old
.rev
, old_rev
);
5268 string_copy_rev(file
->new.rev
, new_rev
);
5270 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5271 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5273 file
->old
.name
[0] = file
->new.name
[0] = 0;
5279 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5281 struct status
*unmerged
= NULL
;
5285 if (!run_io(&io
, argv
, NULL
, IO_RD
))
5288 add_line_data(view
, NULL
, type
);
5290 while ((buf
= io_get(&io
, 0, TRUE
))) {
5291 struct status
*file
= unmerged
;
5294 file
= calloc(1, sizeof(*file
));
5295 if (!file
|| !add_line_data(view
, file
, type
))
5299 /* Parse diff info part. */
5301 file
->status
= status
;
5303 string_copy(file
->old
.rev
, NULL_ID
);
5305 } else if (!file
->status
|| file
== unmerged
) {
5306 if (!status_get_diff(file
, buf
, strlen(buf
)))
5309 buf
= io_get(&io
, 0, TRUE
);
5313 /* Collapse all modified entries that follow an
5314 * associated unmerged entry. */
5315 if (unmerged
== file
) {
5316 unmerged
->status
= 'U';
5318 } else if (file
->status
== 'U') {
5323 /* Grab the old name for rename/copy. */
5324 if (!*file
->old
.name
&&
5325 (file
->status
== 'R' || file
->status
== 'C')) {
5326 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5328 buf
= io_get(&io
, 0, TRUE
);
5333 /* git-ls-files just delivers a NUL separated list of
5334 * file names similar to the second half of the
5335 * git-diff-* output. */
5336 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5337 if (!*file
->old
.name
)
5338 string_copy(file
->old
.name
, file
->new.name
);
5342 if (io_error(&io
)) {
5348 if (!view
->line
[view
->lines
- 1].data
)
5349 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5355 /* Don't show unmerged entries in the staged section. */
5356 static const char *status_diff_index_argv
[] = {
5357 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5358 "--cached", "-M", "HEAD", NULL
5361 static const char *status_diff_files_argv
[] = {
5362 "git", "diff-files", "-z", NULL
5365 static const char *status_list_other_argv
[] = {
5366 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5369 static const char *status_list_no_head_argv
[] = {
5370 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5373 static const char *update_index_argv
[] = {
5374 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5377 /* Restore the previous line number to stay in the context or select a
5378 * line with something that can be updated. */
5380 status_restore(struct view
*view
)
5382 if (view
->p_lineno
>= view
->lines
)
5383 view
->p_lineno
= view
->lines
- 1;
5384 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5386 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5389 /* If the above fails, always skip the "On branch" line. */
5390 if (view
->p_lineno
< view
->lines
)
5391 view
->lineno
= view
->p_lineno
;
5395 if (view
->lineno
< view
->offset
)
5396 view
->offset
= view
->lineno
;
5397 else if (view
->offset
+ view
->height
<= view
->lineno
)
5398 view
->offset
= view
->lineno
- view
->height
+ 1;
5400 view
->p_restore
= FALSE
;
5404 status_update_onbranch(void)
5406 static const char *paths
[][2] = {
5407 { "rebase-apply/rebasing", "Rebasing" },
5408 { "rebase-apply/applying", "Applying mailbox" },
5409 { "rebase-apply/", "Rebasing mailbox" },
5410 { "rebase-merge/interactive", "Interactive rebase" },
5411 { "rebase-merge/", "Rebase merge" },
5412 { "MERGE_HEAD", "Merging" },
5413 { "BISECT_LOG", "Bisecting" },
5414 { "HEAD", "On branch" },
5416 char buf
[SIZEOF_STR
];
5420 if (is_initial_commit()) {
5421 string_copy(status_onbranch
, "Initial commit");
5425 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5426 char *head
= opt_head
;
5428 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5429 lstat(buf
, &stat
) < 0)
5435 if (string_format(buf
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5436 io_open(&io
, buf
) &&
5437 io_read_buf(&io
, buf
, sizeof(buf
))) {
5439 if (!prefixcmp(head
, "refs/heads/"))
5440 head
+= STRING_SIZE("refs/heads/");
5444 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5445 string_copy(status_onbranch
, opt_head
);
5449 string_copy(status_onbranch
, "Not currently on any branch");
5452 /* First parse staged info using git-diff-index(1), then parse unstaged
5453 * info using git-diff-files(1), and finally untracked files using
5454 * git-ls-files(1). */
5456 status_open(struct view
*view
)
5460 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5461 status_update_onbranch();
5463 run_io_bg(update_index_argv
);
5465 if (is_initial_commit()) {
5466 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5468 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5472 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5473 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5476 /* Restore the exact position or use the specialized restore
5478 if (!view
->p_restore
)
5479 status_restore(view
);
5484 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5486 struct status
*status
= line
->data
;
5487 enum line_type type
;
5491 switch (line
->type
) {
5492 case LINE_STAT_STAGED
:
5493 type
= LINE_STAT_SECTION
;
5494 text
= "Changes to be committed:";
5497 case LINE_STAT_UNSTAGED
:
5498 type
= LINE_STAT_SECTION
;
5499 text
= "Changed but not updated:";
5502 case LINE_STAT_UNTRACKED
:
5503 type
= LINE_STAT_SECTION
;
5504 text
= "Untracked files:";
5507 case LINE_STAT_NONE
:
5508 type
= LINE_DEFAULT
;
5509 text
= " (no files)";
5512 case LINE_STAT_HEAD
:
5513 type
= LINE_STAT_HEAD
;
5514 text
= status_onbranch
;
5521 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5523 buf
[0] = status
->status
;
5524 if (draw_text(view
, line
->type
, buf
, TRUE
))
5526 type
= LINE_DEFAULT
;
5527 text
= status
->new.name
;
5530 draw_text(view
, type
, text
, TRUE
);
5535 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5537 if (displayed_views() == 2 || display
[current_view
] != view
)
5538 maximize_view(view
);
5539 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5544 status_enter(struct view
*view
, struct line
*line
)
5546 struct status
*status
= line
->data
;
5547 const char *oldpath
= status
? status
->old
.name
: NULL
;
5548 /* Diffs for unmerged entries are empty when passing the new
5549 * path, so leave it empty. */
5550 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5552 enum open_flags split
;
5553 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5555 if (line
->type
== LINE_STAT_NONE
||
5556 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5557 report("No file to diff");
5561 switch (line
->type
) {
5562 case LINE_STAT_STAGED
:
5563 if (is_initial_commit()) {
5564 const char *no_head_diff_argv
[] = {
5565 "git", "diff", "--no-color", "--patch-with-stat",
5566 "--", "/dev/null", newpath
, NULL
5569 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
, FORMAT_DASH
))
5570 return status_load_error(view
, stage
, newpath
);
5572 const char *index_show_argv
[] = {
5573 "git", "diff-index", "--root", "--patch-with-stat",
5574 "-C", "-M", "--cached", "HEAD", "--",
5575 oldpath
, newpath
, NULL
5578 if (!prepare_update(stage
, index_show_argv
, opt_cdup
, FORMAT_DASH
))
5579 return status_load_error(view
, stage
, newpath
);
5583 info
= "Staged changes to %s";
5585 info
= "Staged changes";
5588 case LINE_STAT_UNSTAGED
:
5590 const char *files_show_argv
[] = {
5591 "git", "diff-files", "--root", "--patch-with-stat",
5592 "-C", "-M", "--", oldpath
, newpath
, NULL
5595 if (!prepare_update(stage
, files_show_argv
, opt_cdup
, FORMAT_DASH
))
5596 return status_load_error(view
, stage
, newpath
);
5598 info
= "Unstaged changes to %s";
5600 info
= "Unstaged changes";
5603 case LINE_STAT_UNTRACKED
:
5605 report("No file to show");
5609 if (!suffixcmp(status
->new.name
, -1, "/")) {
5610 report("Cannot display a directory");
5614 if (!prepare_update_file(stage
, newpath
))
5615 return status_load_error(view
, stage
, newpath
);
5616 info
= "Untracked file %s";
5619 case LINE_STAT_HEAD
:
5623 die("line type %d not handled in switch", line
->type
);
5626 split
= view_is_displayed(view
) ? OPEN_SPLIT
: 0;
5627 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5628 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5630 stage_status
= *status
;
5632 memset(&stage_status
, 0, sizeof(stage_status
));
5635 stage_line_type
= line
->type
;
5637 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5644 status_exists(struct status
*status
, enum line_type type
)
5646 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5647 unsigned long lineno
;
5649 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5650 struct line
*line
= &view
->line
[lineno
];
5651 struct status
*pos
= line
->data
;
5653 if (line
->type
!= type
)
5655 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5656 select_view_line(view
, lineno
);
5659 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5660 select_view_line(view
, lineno
);
5670 status_update_prepare(struct io
*io
, enum line_type type
)
5672 const char *staged_argv
[] = {
5673 "git", "update-index", "-z", "--index-info", NULL
5675 const char *others_argv
[] = {
5676 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5680 case LINE_STAT_STAGED
:
5681 return run_io(io
, staged_argv
, opt_cdup
, IO_WR
);
5683 case LINE_STAT_UNSTAGED
:
5684 return run_io(io
, others_argv
, opt_cdup
, IO_WR
);
5686 case LINE_STAT_UNTRACKED
:
5687 return run_io(io
, others_argv
, NULL
, IO_WR
);
5690 die("line type %d not handled in switch", type
);
5696 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5698 char buf
[SIZEOF_STR
];
5702 case LINE_STAT_STAGED
:
5703 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5706 status
->old
.name
, 0))
5710 case LINE_STAT_UNSTAGED
:
5711 case LINE_STAT_UNTRACKED
:
5712 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
5717 die("line type %d not handled in switch", type
);
5720 return io_write(io
, buf
, bufsize
);
5724 status_update_file(struct status
*status
, enum line_type type
)
5729 if (!status_update_prepare(&io
, type
))
5732 result
= status_update_write(&io
, status
, type
);
5733 return done_io(&io
) && result
;
5737 status_update_files(struct view
*view
, struct line
*line
)
5739 char buf
[sizeof(view
->ref
)];
5742 struct line
*pos
= view
->line
+ view
->lines
;
5745 int cursor_y
= -1, cursor_x
= -1;
5747 if (!status_update_prepare(&io
, line
->type
))
5750 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
5753 string_copy(buf
, view
->ref
);
5754 getsyx(cursor_y
, cursor_x
);
5755 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
5756 int almost_done
= file
* 100 / files
;
5758 if (almost_done
> done
) {
5760 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
5762 update_view_title(view
);
5763 setsyx(cursor_y
, cursor_x
);
5766 result
= status_update_write(&io
, line
->data
, line
->type
);
5768 string_copy(view
->ref
, buf
);
5770 return done_io(&io
) && result
;
5774 status_update(struct view
*view
)
5776 struct line
*line
= &view
->line
[view
->lineno
];
5778 assert(view
->lines
);
5781 /* This should work even for the "On branch" line. */
5782 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
5783 report("Nothing to update");
5787 if (!status_update_files(view
, line
+ 1)) {
5788 report("Failed to update file status");
5792 } else if (!status_update_file(line
->data
, line
->type
)) {
5793 report("Failed to update file status");
5801 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
5803 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
5804 if (type
== LINE_STAT_STAGED
) {
5805 report("Cannot revert changes to staged files");
5806 } else if (type
== LINE_STAT_UNTRACKED
) {
5807 report("Cannot revert changes to untracked files");
5808 } else if (has_none
) {
5809 report("Nothing to revert");
5811 report("Cannot revert changes to multiple files");
5816 char mode
[10] = "100644";
5817 const char *reset_argv
[] = {
5818 "git", "update-index", "--cacheinfo", mode
,
5819 status
->old
.rev
, status
->old
.name
, NULL
5821 const char *checkout_argv
[] = {
5822 "git", "checkout", "--", status
->old
.name
, NULL
5825 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5827 string_format(mode
, "%o", status
->old
.mode
);
5828 return (status
->status
!= 'U' || run_io_fg(reset_argv
, opt_cdup
)) &&
5829 run_io_fg(checkout_argv
, opt_cdup
);
5834 status_request(struct view
*view
, enum request request
, struct line
*line
)
5836 struct status
*status
= line
->data
;
5839 case REQ_STATUS_UPDATE
:
5840 if (!status_update(view
))
5844 case REQ_STATUS_REVERT
:
5845 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
5849 case REQ_STATUS_MERGE
:
5850 if (!status
|| status
->status
!= 'U') {
5851 report("Merging only possible for files with unmerged status ('U').");
5854 open_mergetool(status
->new.name
);
5860 if (status
->status
== 'D') {
5861 report("File has been deleted.");
5865 open_editor(status
->status
!= '?', status
->new.name
);
5868 case REQ_VIEW_BLAME
:
5870 string_copy(opt_file
, status
->new.name
);
5876 /* After returning the status view has been split to
5877 * show the stage view. No further reloading is
5879 return status_enter(view
, line
);
5882 /* Simply reload the view. */
5889 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
5895 status_select(struct view
*view
, struct line
*line
)
5897 struct status
*status
= line
->data
;
5898 char file
[SIZEOF_STR
] = "all files";
5902 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
5905 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
5908 switch (line
->type
) {
5909 case LINE_STAT_STAGED
:
5910 text
= "Press %s to unstage %s for commit";
5913 case LINE_STAT_UNSTAGED
:
5914 text
= "Press %s to stage %s for commit";
5917 case LINE_STAT_UNTRACKED
:
5918 text
= "Press %s to stage %s for addition";
5921 case LINE_STAT_HEAD
:
5922 case LINE_STAT_NONE
:
5923 text
= "Nothing to update";
5927 die("line type %d not handled in switch", line
->type
);
5930 if (status
&& status
->status
== 'U') {
5931 text
= "Press %s to resolve conflict in %s";
5932 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_MERGE
);
5935 key
= get_key(KEYMAP_STATUS
, REQ_STATUS_UPDATE
);
5938 string_format(view
->ref
, text
, key
, file
);
5942 status_grep(struct view
*view
, struct line
*line
)
5944 struct status
*status
= line
->data
;
5947 const char buf
[2] = { status
->status
, 0 };
5948 const char *text
[] = { status
->new.name
, buf
, NULL
};
5950 return grep_text(view
, text
);
5956 static struct view_ops status_ops
= {
5969 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
5971 while (line
< end
) {
5972 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
5973 !io_write(io
, "\n", 1))
5976 if (line
->type
== LINE_DIFF_CHUNK
||
5977 line
->type
== LINE_DIFF_HEADER
)
5984 static struct line
*
5985 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
5987 for (; view
->line
< line
; line
--)
5988 if (line
->type
== type
)
5995 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
5997 const char *apply_argv
[SIZEOF_ARG
] = {
5998 "git", "apply", "--whitespace=nowarn", NULL
6000 struct line
*diff_hdr
;
6004 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
6009 apply_argv
[argc
++] = "--cached";
6010 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
6011 apply_argv
[argc
++] = "-R";
6012 apply_argv
[argc
++] = "-";
6013 apply_argv
[argc
++] = NULL
;
6014 if (!run_io(&io
, apply_argv
, opt_cdup
, IO_WR
))
6017 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
6018 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
6022 run_io_bg(update_index_argv
);
6024 return chunk
? TRUE
: FALSE
;
6028 stage_update(struct view
*view
, struct line
*line
)
6030 struct line
*chunk
= NULL
;
6032 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
6033 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6036 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
6037 report("Failed to apply chunk");
6041 } else if (!stage_status
.status
) {
6042 view
= VIEW(REQ_VIEW_STATUS
);
6044 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
6045 if (line
->type
== stage_line_type
)
6048 if (!status_update_files(view
, line
+ 1)) {
6049 report("Failed to update files");
6053 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
6054 report("Failed to update file");
6062 stage_revert(struct view
*view
, struct line
*line
)
6064 struct line
*chunk
= NULL
;
6066 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
6067 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
6070 if (!prompt_yesno("Are you sure you want to revert changes?"))
6073 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
6074 report("Failed to revert chunk");
6080 return status_revert(stage_status
.status
? &stage_status
: NULL
,
6081 stage_line_type
, FALSE
);
6087 stage_next(struct view
*view
, struct line
*line
)
6091 if (!stage_chunks
) {
6092 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
6093 if (line
->type
!= LINE_DIFF_CHUNK
)
6096 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
6097 report("Allocation failure");
6101 stage_chunk
[stage_chunks
++] = line
- view
->line
;
6105 for (i
= 0; i
< stage_chunks
; i
++) {
6106 if (stage_chunk
[i
] > view
->lineno
) {
6107 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
6108 report("Chunk %d of %d", i
+ 1, stage_chunks
);
6113 report("No next chunk found");
6117 stage_request(struct view
*view
, enum request request
, struct line
*line
)
6120 case REQ_STATUS_UPDATE
:
6121 if (!stage_update(view
, line
))
6125 case REQ_STATUS_REVERT
:
6126 if (!stage_revert(view
, line
))
6130 case REQ_STAGE_NEXT
:
6131 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6132 report("File is untracked; press %s to add",
6133 get_key(KEYMAP_STAGE
, REQ_STATUS_UPDATE
));
6136 stage_next(view
, line
);
6140 if (!stage_status
.new.name
[0])
6142 if (stage_status
.status
== 'D') {
6143 report("File has been deleted.");
6147 open_editor(stage_status
.status
!= '?', stage_status
.new.name
);
6151 /* Reload everything ... */
6154 case REQ_VIEW_BLAME
:
6155 if (stage_status
.new.name
[0]) {
6156 string_copy(opt_file
, stage_status
.new.name
);
6162 return pager_request(view
, request
, line
);
6168 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
6169 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
6171 /* Check whether the staged entry still exists, and close the
6172 * stage view if it doesn't. */
6173 if (!status_exists(&stage_status
, stage_line_type
)) {
6174 status_restore(VIEW(REQ_VIEW_STATUS
));
6175 return REQ_VIEW_CLOSE
;
6178 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
6179 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
6180 report("Cannot display a directory");
6184 if (!prepare_update_file(view
, stage_status
.new.name
)) {
6185 report("Failed to open file: %s", strerror(errno
));
6189 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
6194 static struct view_ops stage_ops
= {
6211 char id
[SIZEOF_REV
]; /* SHA1 ID. */
6212 char title
[128]; /* First line of the commit message. */
6213 const char *author
; /* Author of the commit. */
6214 time_t time
; /* Date from the author ident. */
6215 struct ref_list
*refs
; /* Repository references. */
6216 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
6217 size_t graph_size
; /* The width of the graph array. */
6218 bool has_parents
; /* Rewritten --parents seen. */
6221 /* Size of rev graph with no "padding" columns */
6222 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6225 struct rev_graph
*prev
, *next
, *parents
;
6226 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
6228 struct commit
*commit
;
6230 unsigned int boundary
:1;
6233 /* Parents of the commit being visualized. */
6234 static struct rev_graph graph_parents
[4];
6236 /* The current stack of revisions on the graph. */
6237 static struct rev_graph graph_stacks
[4] = {
6238 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
6239 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
6240 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
6241 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
6245 graph_parent_is_merge(struct rev_graph
*graph
)
6247 return graph
->parents
->size
> 1;
6251 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
6253 struct commit
*commit
= graph
->commit
;
6255 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
6256 commit
->graph
[commit
->graph_size
++] = symbol
;
6260 clear_rev_graph(struct rev_graph
*graph
)
6262 graph
->boundary
= 0;
6263 graph
->size
= graph
->pos
= 0;
6264 graph
->commit
= NULL
;
6265 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6269 done_rev_graph(struct rev_graph
*graph
)
6271 if (graph_parent_is_merge(graph
) &&
6272 graph
->pos
< graph
->size
- 1 &&
6273 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6274 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6276 graph
->commit
->graph_size
= i
* 2;
6277 while (i
< graph
->next
->size
- 1) {
6278 append_to_rev_graph(graph
, ' ');
6279 append_to_rev_graph(graph
, '\\');
6284 clear_rev_graph(graph
);
6288 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6292 /* "Collapse" duplicate parents lines.
6294 * FIXME: This needs to also update update the drawn graph but
6295 * for now it just serves as a method for pruning graph lines. */
6296 for (i
= 0; i
< graph
->size
; i
++)
6297 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6300 if (graph
->size
< SIZEOF_REVITEMS
) {
6301 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6306 get_rev_graph_symbol(struct rev_graph
*graph
)
6310 if (graph
->boundary
)
6311 symbol
= REVGRAPH_BOUND
;
6312 else if (graph
->parents
->size
== 0)
6313 symbol
= REVGRAPH_INIT
;
6314 else if (graph_parent_is_merge(graph
))
6315 symbol
= REVGRAPH_MERGE
;
6316 else if (graph
->pos
>= graph
->size
)
6317 symbol
= REVGRAPH_BRANCH
;
6319 symbol
= REVGRAPH_COMMIT
;
6325 draw_rev_graph(struct rev_graph
*graph
)
6328 chtype separator
, line
;
6330 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6331 static struct rev_filler fillers
[] = {
6337 chtype symbol
= get_rev_graph_symbol(graph
);
6338 struct rev_filler
*filler
;
6341 if (opt_line_graphics
)
6342 fillers
[DEFAULT
].line
= line_graphics
[LINE_GRAPHIC_VLINE
];
6344 filler
= &fillers
[DEFAULT
];
6346 for (i
= 0; i
< graph
->pos
; i
++) {
6347 append_to_rev_graph(graph
, filler
->line
);
6348 if (graph_parent_is_merge(graph
->prev
) &&
6349 graph
->prev
->pos
== i
)
6350 filler
= &fillers
[RSHARP
];
6352 append_to_rev_graph(graph
, filler
->separator
);
6355 /* Place the symbol for this revision. */
6356 append_to_rev_graph(graph
, symbol
);
6358 if (graph
->prev
->size
> graph
->size
)
6359 filler
= &fillers
[RDIAG
];
6361 filler
= &fillers
[DEFAULT
];
6365 for (; i
< graph
->size
; i
++) {
6366 append_to_rev_graph(graph
, filler
->separator
);
6367 append_to_rev_graph(graph
, filler
->line
);
6368 if (graph_parent_is_merge(graph
->prev
) &&
6369 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6370 filler
= &fillers
[RSHARP
];
6371 if (graph
->prev
->size
> graph
->size
)
6372 filler
= &fillers
[LDIAG
];
6375 if (graph
->prev
->size
> graph
->size
) {
6376 append_to_rev_graph(graph
, filler
->separator
);
6377 if (filler
->line
!= ' ')
6378 append_to_rev_graph(graph
, filler
->line
);
6382 /* Prepare the next rev graph */
6384 prepare_rev_graph(struct rev_graph
*graph
)
6388 /* First, traverse all lines of revisions up to the active one. */
6389 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6390 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6393 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6396 /* Interleave the new revision parent(s). */
6397 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6398 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6400 /* Lastly, put any remaining revisions. */
6401 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6402 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6406 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6408 /* If this is the finalizing update ... */
6410 prepare_rev_graph(graph
);
6412 /* Graph visualization needs a one rev look-ahead,
6413 * so the first update doesn't visualize anything. */
6414 if (!graph
->prev
->commit
)
6417 if (view
->lines
> 2)
6418 view
->line
[view
->lines
- 3].dirty
= 1;
6419 if (view
->lines
> 1)
6420 view
->line
[view
->lines
- 2].dirty
= 1;
6421 draw_rev_graph(graph
->prev
);
6422 done_rev_graph(graph
->prev
->prev
);
6430 static const char *main_argv
[SIZEOF_ARG
] = {
6431 "git", "log", "--no-color", "--pretty=raw", "--parents",
6432 "--topo-order", "%(head)", NULL
6436 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6438 struct commit
*commit
= line
->data
;
6440 if (!commit
->author
)
6443 if (opt_date
&& draw_date(view
, &commit
->time
))
6446 if (opt_author
&& draw_author(view
, commit
->author
))
6449 if (opt_rev_graph
&& commit
->graph_size
&&
6450 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6453 if (opt_show_refs
&& commit
->refs
) {
6456 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6457 struct ref
*ref
= commit
->refs
->refs
[i
];
6458 enum line_type type
;
6461 type
= LINE_MAIN_HEAD
;
6463 type
= LINE_MAIN_LOCAL_TAG
;
6465 type
= LINE_MAIN_TAG
;
6466 else if (ref
->tracked
)
6467 type
= LINE_MAIN_TRACKED
;
6468 else if (ref
->remote
)
6469 type
= LINE_MAIN_REMOTE
;
6471 type
= LINE_MAIN_REF
;
6473 if (draw_text(view
, type
, "[", TRUE
) ||
6474 draw_text(view
, type
, ref
->name
, TRUE
) ||
6475 draw_text(view
, type
, "]", TRUE
))
6478 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6483 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6487 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6489 main_read(struct view
*view
, char *line
)
6491 static struct rev_graph
*graph
= graph_stacks
;
6492 enum line_type type
;
6493 struct commit
*commit
;
6498 if (!view
->lines
&& !view
->parent
)
6499 die("No revisions match the given arguments.");
6500 if (view
->lines
> 0) {
6501 commit
= view
->line
[view
->lines
- 1].data
;
6502 view
->line
[view
->lines
- 1].dirty
= 1;
6503 if (!commit
->author
) {
6506 graph
->commit
= NULL
;
6509 update_rev_graph(view
, graph
);
6511 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6512 clear_rev_graph(&graph_stacks
[i
]);
6516 type
= get_line_type(line
);
6517 if (type
== LINE_COMMIT
) {
6518 commit
= calloc(1, sizeof(struct commit
));
6522 line
+= STRING_SIZE("commit ");
6524 graph
->boundary
= 1;
6528 string_copy_rev(commit
->id
, line
);
6529 commit
->refs
= get_ref_list(commit
->id
);
6530 graph
->commit
= commit
;
6531 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6533 while ((line
= strchr(line
, ' '))) {
6535 push_rev_graph(graph
->parents
, line
);
6536 commit
->has_parents
= TRUE
;
6543 commit
= view
->line
[view
->lines
- 1].data
;
6547 if (commit
->has_parents
)
6549 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6553 parse_author_line(line
+ STRING_SIZE("author "),
6554 &commit
->author
, &commit
->time
);
6555 update_rev_graph(view
, graph
);
6556 graph
= graph
->next
;
6560 /* Fill in the commit title if it has not already been set. */
6561 if (commit
->title
[0])
6564 /* Require titles to start with a non-space character at the
6565 * offset used by git log. */
6566 if (strncmp(line
, " ", 4))
6569 /* Well, if the title starts with a whitespace character,
6570 * try to be forgiving. Otherwise we end up with no title. */
6571 while (isspace(*line
))
6575 /* FIXME: More graceful handling of titles; append "..." to
6576 * shortened titles, etc. */
6578 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6579 view
->line
[view
->lines
- 1].dirty
= 1;
6586 main_request(struct view
*view
, enum request request
, struct line
*line
)
6588 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
6592 open_view(view
, REQ_VIEW_DIFF
, flags
);
6596 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6606 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6611 if (!opt_show_refs
|| !list
)
6614 for (i
= 0; i
< list
->size
; i
++) {
6615 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6623 main_grep(struct view
*view
, struct line
*line
)
6625 struct commit
*commit
= line
->data
;
6626 const char *text
[] = {
6628 opt_author
? commit
->author
: "",
6629 opt_date
? mkdate(&commit
->time
) : "",
6633 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6637 main_select(struct view
*view
, struct line
*line
)
6639 struct commit
*commit
= line
->data
;
6641 string_copy_rev(view
->ref
, commit
->id
);
6642 string_copy_rev(ref_commit
, view
->ref
);
6645 static struct view_ops main_ops
= {
6658 * Unicode / UTF-8 handling
6660 * NOTE: Much of the following code for dealing with Unicode is derived from
6661 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6662 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6666 unicode_width(unsigned long c
)
6669 (c
<= 0x115f /* Hangul Jamo */
6672 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
6674 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
6675 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
6676 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
6677 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
6678 || (c
>= 0xffe0 && c
<= 0xffe6)
6679 || (c
>= 0x20000 && c
<= 0x2fffd)
6680 || (c
>= 0x30000 && c
<= 0x3fffd)))
6684 return opt_tab_size
;
6689 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6690 * Illegal bytes are set one. */
6691 static const unsigned char utf8_bytes
[256] = {
6692 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,
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 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,
6699 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,
6702 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6703 static inline unsigned long
6704 utf8_to_unicode(const char *string
, size_t length
)
6706 unsigned long unicode
;
6710 unicode
= string
[0];
6713 unicode
= (string
[0] & 0x1f) << 6;
6714 unicode
+= (string
[1] & 0x3f);
6717 unicode
= (string
[0] & 0x0f) << 12;
6718 unicode
+= ((string
[1] & 0x3f) << 6);
6719 unicode
+= (string
[2] & 0x3f);
6722 unicode
= (string
[0] & 0x0f) << 18;
6723 unicode
+= ((string
[1] & 0x3f) << 12);
6724 unicode
+= ((string
[2] & 0x3f) << 6);
6725 unicode
+= (string
[3] & 0x3f);
6728 unicode
= (string
[0] & 0x0f) << 24;
6729 unicode
+= ((string
[1] & 0x3f) << 18);
6730 unicode
+= ((string
[2] & 0x3f) << 12);
6731 unicode
+= ((string
[3] & 0x3f) << 6);
6732 unicode
+= (string
[4] & 0x3f);
6735 unicode
= (string
[0] & 0x01) << 30;
6736 unicode
+= ((string
[1] & 0x3f) << 24);
6737 unicode
+= ((string
[2] & 0x3f) << 18);
6738 unicode
+= ((string
[3] & 0x3f) << 12);
6739 unicode
+= ((string
[4] & 0x3f) << 6);
6740 unicode
+= (string
[5] & 0x3f);
6743 die("Invalid Unicode length");
6746 /* Invalid characters could return the special 0xfffd value but NUL
6747 * should be just as good. */
6748 return unicode
> 0xffff ? 0 : unicode
;
6751 /* Calculates how much of string can be shown within the given maximum width
6752 * and sets trimmed parameter to non-zero value if all of string could not be
6753 * shown. If the reserve flag is TRUE, it will reserve at least one
6754 * trailing character, which can be useful when drawing a delimiter.
6756 * Returns the number of bytes to output from string to satisfy max_width. */
6758 utf8_length(const char **start
, size_t skip
, int *width
, size_t max_width
, int *trimmed
, bool reserve
)
6760 const char *string
= *start
;
6761 const char *end
= strchr(string
, '\0');
6762 unsigned char last_bytes
= 0;
6763 size_t last_ucwidth
= 0;
6768 while (string
< end
) {
6769 int c
= *(unsigned char *) string
;
6770 unsigned char bytes
= utf8_bytes
[c
];
6772 unsigned long unicode
;
6774 if (string
+ bytes
> end
)
6777 /* Change representation to figure out whether
6778 * it is a single- or double-width character. */
6780 unicode
= utf8_to_unicode(string
, bytes
);
6781 /* FIXME: Graceful handling of invalid Unicode character. */
6785 ucwidth
= unicode_width(unicode
);
6787 skip
-= ucwidth
<= skip
? ucwidth
: skip
;
6791 if (*width
> max_width
) {
6794 if (reserve
&& *width
== max_width
) {
6795 string
-= last_bytes
;
6796 *width
-= last_ucwidth
;
6802 last_bytes
= ucwidth
? bytes
: 0;
6803 last_ucwidth
= ucwidth
;
6806 return string
- *start
;
6814 /* Whether or not the curses interface has been initialized. */
6815 static bool cursed
= FALSE
;
6817 /* Terminal hacks and workarounds. */
6818 static bool use_scroll_redrawwin
;
6819 static bool use_scroll_status_wclear
;
6821 /* The status window is used for polling keystrokes. */
6822 static WINDOW
*status_win
;
6824 /* Reading from the prompt? */
6825 static bool input_mode
= FALSE
;
6827 static bool status_empty
= FALSE
;
6829 /* Update status and title window. */
6831 report(const char *msg
, ...)
6833 struct view
*view
= display
[current_view
];
6839 char buf
[SIZEOF_STR
];
6842 va_start(args
, msg
);
6843 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6844 buf
[sizeof(buf
) - 1] = 0;
6845 buf
[sizeof(buf
) - 2] = '.';
6846 buf
[sizeof(buf
) - 3] = '.';
6847 buf
[sizeof(buf
) - 4] = '.';
6853 if (!status_empty
|| *msg
) {
6856 va_start(args
, msg
);
6858 wmove(status_win
, 0, 0);
6859 if (view
->has_scrolled
&& use_scroll_status_wclear
)
6862 vwprintw(status_win
, msg
, args
);
6863 status_empty
= FALSE
;
6865 status_empty
= TRUE
;
6867 wclrtoeol(status_win
);
6868 wnoutrefresh(status_win
);
6873 update_view_title(view
);
6876 /* Controls when nodelay should be in effect when polling user input. */
6878 set_nonblocking_input(bool loading
)
6880 static unsigned int loading_views
;
6882 if ((loading
== FALSE
&& loading_views
-- == 1) ||
6883 (loading
== TRUE
&& loading_views
++ == 0))
6884 nodelay(status_win
, loading
);
6893 /* Initialize the curses library */
6894 if (isatty(STDIN_FILENO
)) {
6895 cursed
= !!initscr();
6898 /* Leave stdin and stdout alone when acting as a pager. */
6899 opt_tty
= fopen("/dev/tty", "r+");
6901 die("Failed to open /dev/tty");
6902 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
6906 die("Failed to initialize curses");
6908 nonl(); /* Disable conversion and detect newlines from input. */
6909 cbreak(); /* Take input chars one at a time, no wait for \n */
6910 noecho(); /* Don't echo input */
6911 leaveok(stdscr
, FALSE
);
6916 getmaxyx(stdscr
, y
, x
);
6917 status_win
= newwin(1, 0, y
- 1, 0);
6919 die("Failed to create status window");
6921 /* Enable keyboard mapping */
6922 keypad(status_win
, TRUE
);
6923 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
6925 TABSIZE
= opt_tab_size
;
6926 if (opt_line_graphics
) {
6927 line_graphics
[LINE_GRAPHIC_VLINE
] = ACS_VLINE
;
6930 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
6931 if (term
&& !strcmp(term
, "gnome-terminal")) {
6932 /* In the gnome-terminal-emulator, the message from
6933 * scrolling up one line when impossible followed by
6934 * scrolling down one line causes corruption of the
6935 * status line. This is fixed by calling wclear. */
6936 use_scroll_status_wclear
= TRUE
;
6937 use_scroll_redrawwin
= FALSE
;
6939 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
6940 /* No problems with full optimizations in xrvt-(unicode)
6942 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
6945 /* When scrolling in (u)xterm the last line in the
6946 * scrolling direction will update slowly. */
6947 use_scroll_redrawwin
= TRUE
;
6948 use_scroll_status_wclear
= FALSE
;
6953 get_input(int prompt_position
)
6956 int i
, key
, cursor_y
, cursor_x
;
6958 if (prompt_position
)
6962 foreach_view (view
, i
) {
6964 if (view_is_displayed(view
) && view
->has_scrolled
&&
6965 use_scroll_redrawwin
)
6966 redrawwin(view
->win
);
6967 view
->has_scrolled
= FALSE
;
6970 /* Update the cursor position. */
6971 if (prompt_position
) {
6972 getbegyx(status_win
, cursor_y
, cursor_x
);
6973 cursor_x
= prompt_position
;
6975 view
= display
[current_view
];
6976 getbegyx(view
->win
, cursor_y
, cursor_x
);
6977 cursor_x
= view
->width
- 1;
6978 cursor_y
+= view
->lineno
- view
->offset
;
6980 setsyx(cursor_y
, cursor_x
);
6982 /* Refresh, accept single keystroke of input */
6984 key
= wgetch(status_win
);
6986 /* wgetch() with nodelay() enabled returns ERR when
6987 * there's no input. */
6990 } else if (key
== KEY_RESIZE
) {
6993 getmaxyx(stdscr
, height
, width
);
6995 wresize(status_win
, 1, width
);
6996 mvwin(status_win
, height
- 1, 0);
6997 wnoutrefresh(status_win
);
6999 redraw_display(TRUE
);
7009 prompt_input(const char *prompt
, input_handler handler
, void *data
)
7011 enum input_status status
= INPUT_OK
;
7012 static char buf
[SIZEOF_STR
];
7017 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
7020 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
7021 wclrtoeol(status_win
);
7023 key
= get_input(pos
+ 1);
7028 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
7035 status
= INPUT_CANCEL
;
7039 status
= INPUT_CANCEL
;
7043 if (pos
>= sizeof(buf
)) {
7044 report("Input string too long");
7048 status
= handler(data
, buf
, key
);
7049 if (status
== INPUT_OK
)
7050 buf
[pos
++] = (char) key
;
7054 /* Clear the status window */
7055 status_empty
= FALSE
;
7058 if (status
== INPUT_CANCEL
)
7066 static enum input_status
7067 prompt_yesno_handler(void *data
, char *buf
, int c
)
7069 if (c
== 'y' || c
== 'Y')
7071 if (c
== 'n' || c
== 'N')
7072 return INPUT_CANCEL
;
7077 prompt_yesno(const char *prompt
)
7079 char prompt2
[SIZEOF_STR
];
7081 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
7084 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
7087 static enum input_status
7088 read_prompt_handler(void *data
, char *buf
, int c
)
7090 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
7094 read_prompt(const char *prompt
)
7096 return prompt_input(prompt
, read_prompt_handler
, NULL
);
7099 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
7101 enum input_status status
= INPUT_OK
;
7104 while (items
[size
].text
)
7107 while (status
== INPUT_OK
) {
7108 const struct menu_item
*item
= &items
[*selected
];
7112 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
7113 prompt
, *selected
+ 1, size
);
7115 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
7116 wprintw(status_win
, "%s", item
->text
);
7117 wclrtoeol(status_win
);
7119 key
= get_input(COLS
- 1);
7124 status
= INPUT_STOP
;
7129 *selected
= *selected
- 1;
7131 *selected
= size
- 1;
7136 *selected
= (*selected
+ 1) % size
;
7140 status
= INPUT_CANCEL
;
7144 for (i
= 0; items
[i
].text
; i
++)
7145 if (items
[i
].hotkey
== key
) {
7147 status
= INPUT_STOP
;
7153 /* Clear the status window */
7154 status_empty
= FALSE
;
7157 return status
!= INPUT_CANCEL
;
7161 * Repository properties
7164 static struct ref
**refs
= NULL
;
7165 static size_t refs_size
= 0;
7167 static struct ref_list
**ref_lists
= NULL
;
7168 static size_t ref_lists_size
= 0;
7170 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
7171 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
7172 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
7175 compare_refs(const void *ref1_
, const void *ref2_
)
7177 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
7178 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
7180 if (ref1
->tag
!= ref2
->tag
)
7181 return ref2
->tag
- ref1
->tag
;
7182 if (ref1
->ltag
!= ref2
->ltag
)
7183 return ref2
->ltag
- ref2
->ltag
;
7184 if (ref1
->head
!= ref2
->head
)
7185 return ref2
->head
- ref1
->head
;
7186 if (ref1
->tracked
!= ref2
->tracked
)
7187 return ref2
->tracked
- ref1
->tracked
;
7188 if (ref1
->remote
!= ref2
->remote
)
7189 return ref2
->remote
- ref1
->remote
;
7190 return strcmp(ref1
->name
, ref2
->name
);
7194 foreach_ref(bool (*visitor
)(void *data
, struct ref
*ref
), void *data
)
7198 for (i
= 0; i
< refs_size
; i
++)
7199 if (!visitor(data
, refs
[i
]))
7203 static struct ref_list
*
7204 get_ref_list(const char *id
)
7206 struct ref_list
*list
;
7209 for (i
= 0; i
< ref_lists_size
; i
++)
7210 if (!strcmp(id
, ref_lists
[i
]->id
))
7211 return ref_lists
[i
];
7213 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
7215 list
= calloc(1, sizeof(*list
));
7219 for (i
= 0; i
< refs_size
; i
++) {
7220 if (!strcmp(id
, refs
[i
]->id
) &&
7221 realloc_refs_list(&list
->refs
, list
->size
, 1))
7222 list
->refs
[list
->size
++] = refs
[i
];
7230 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
7231 ref_lists
[ref_lists_size
++] = list
;
7236 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
7238 struct ref
*ref
= NULL
;
7241 bool remote
= FALSE
;
7242 bool tracked
= FALSE
;
7244 int from
= 0, to
= refs_size
- 1;
7246 if (!prefixcmp(name
, "refs/tags/")) {
7247 if (!suffixcmp(name
, namelen
, "^{}")) {
7255 namelen
-= STRING_SIZE("refs/tags/");
7256 name
+= STRING_SIZE("refs/tags/");
7258 } else if (!prefixcmp(name
, "refs/remotes/")) {
7260 namelen
-= STRING_SIZE("refs/remotes/");
7261 name
+= STRING_SIZE("refs/remotes/");
7262 tracked
= !strcmp(opt_remote
, name
);
7264 } else if (!prefixcmp(name
, "refs/heads/")) {
7265 namelen
-= STRING_SIZE("refs/heads/");
7266 name
+= STRING_SIZE("refs/heads/");
7267 head
= !strncmp(opt_head
, name
, namelen
);
7269 } else if (!strcmp(name
, "HEAD")) {
7270 string_ncopy(opt_head_rev
, id
, idlen
);
7274 /* If we are reloading or it's an annotated tag, replace the
7275 * previous SHA1 with the resolved commit id; relies on the fact
7276 * git-ls-remote lists the commit id of an annotated tag right
7277 * before the commit id it points to. */
7278 while (from
<= to
) {
7279 size_t pos
= (to
+ from
) / 2;
7280 int cmp
= strcmp(name
, refs
[pos
]->name
);
7294 if (!realloc_refs(&refs
, refs_size
, 1))
7296 ref
= calloc(1, sizeof(*ref
) + namelen
);
7299 memmove(refs
+ from
+ 1, refs
+ from
,
7300 (refs_size
- from
) * sizeof(*refs
));
7302 strncpy(ref
->name
, name
, namelen
);
7309 ref
->remote
= remote
;
7310 ref
->tracked
= tracked
;
7311 string_copy_rev(ref
->id
, id
);
7319 const char *head_argv
[] = {
7320 "git", "symbolic-ref", "HEAD", NULL
7322 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7323 "git", "ls-remote", opt_git_dir
, NULL
7325 static bool init
= FALSE
;
7329 argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE");
7336 if (run_io_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7337 !prefixcmp(opt_head
, "refs/heads/")) {
7338 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7340 memmove(opt_head
, offset
, strlen(offset
) + 1);
7343 for (i
= 0; i
< refs_size
; i
++)
7346 if (run_io_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7349 /* Update the ref lists to reflect changes. */
7350 for (i
= 0; i
< ref_lists_size
; i
++) {
7351 struct ref_list
*list
= ref_lists
[i
];
7354 for (old
= new = 0; old
< list
->size
; old
++)
7355 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7356 list
->refs
[new++] = list
->refs
[old
];
7364 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7366 if (!strcmp(name
, ".remote")) {
7367 string_ncopy(opt_remote
, value
, valuelen
);
7369 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7370 size_t from
= strlen(opt_remote
);
7372 if (!prefixcmp(value
, "refs/heads/"))
7373 value
+= STRING_SIZE("refs/heads/");
7375 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7381 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7383 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7384 int argc
= 1 + (cmd
== option_set_command
);
7387 if (!argv_from_string(argv
, &argc
, value
))
7388 config_msg
= "Too many option arguments";
7390 error
= cmd(argc
, argv
);
7393 warn("Option 'tig.%s': %s", name
, config_msg
);
7397 set_environment_variable(const char *name
, const char *value
)
7399 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7400 char *env
= malloc(len
);
7403 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7411 set_work_tree(const char *value
)
7413 char cwd
[SIZEOF_STR
];
7415 if (!getcwd(cwd
, sizeof(cwd
)))
7416 die("Failed to get cwd path: %s", strerror(errno
));
7417 if (chdir(opt_git_dir
) < 0)
7418 die("Failed to chdir(%s): %s", strerror(errno
));
7419 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7420 die("Failed to get git path: %s", strerror(errno
));
7422 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7423 if (chdir(value
) < 0)
7424 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7425 if (!getcwd(cwd
, sizeof(cwd
)))
7426 die("Failed to get cwd path: %s", strerror(errno
));
7427 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7428 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7429 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7430 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7431 opt_is_inside_work_tree
= TRUE
;
7435 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7437 if (!strcmp(name
, "i18n.commitencoding"))
7438 string_ncopy(opt_encoding
, value
, valuelen
);
7440 else if (!strcmp(name
, "core.editor"))
7441 string_ncopy(opt_editor
, value
, valuelen
);
7443 else if (!strcmp(name
, "core.worktree"))
7444 set_work_tree(value
);
7446 else if (!prefixcmp(name
, "tig.color."))
7447 set_repo_config_option(name
+ 10, value
, option_color_command
);
7449 else if (!prefixcmp(name
, "tig.bind."))
7450 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7452 else if (!prefixcmp(name
, "tig."))
7453 set_repo_config_option(name
+ 4, value
, option_set_command
);
7455 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7456 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7457 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7463 load_git_config(void)
7465 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
7467 return run_io_load(config_list_argv
, "=", read_repo_config_option
);
7471 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7473 if (!opt_git_dir
[0]) {
7474 string_ncopy(opt_git_dir
, name
, namelen
);
7476 } else if (opt_is_inside_work_tree
== -1) {
7477 /* This can be 3 different values depending on the
7478 * version of git being used. If git-rev-parse does not
7479 * understand --is-inside-work-tree it will simply echo
7480 * the option else either "true" or "false" is printed.
7481 * Default to true for the unknown case. */
7482 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7484 } else if (*name
== '.') {
7485 string_ncopy(opt_cdup
, name
, namelen
);
7488 string_ncopy(opt_prefix
, name
, namelen
);
7495 load_repo_info(void)
7497 const char *rev_parse_argv
[] = {
7498 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7499 "--show-cdup", "--show-prefix", NULL
7502 return run_io_load(rev_parse_argv
, "=", read_repo_info
);
7510 static const char usage
[] =
7511 "tig " TIG_VERSION
" (" __DATE__
")\n"
7513 "Usage: tig [options] [revs] [--] [paths]\n"
7514 " or: tig show [options] [revs] [--] [paths]\n"
7515 " or: tig blame [rev] path\n"
7517 " or: tig < [git command output]\n"
7520 " -v, --version Show version and exit\n"
7521 " -h, --help Show help message and exit";
7523 static void __NORETURN
7526 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7532 static void __NORETURN
7533 die(const char *err
, ...)
7539 va_start(args
, err
);
7540 fputs("tig: ", stderr
);
7541 vfprintf(stderr
, err
, args
);
7542 fputs("\n", stderr
);
7549 warn(const char *msg
, ...)
7553 va_start(args
, msg
);
7554 fputs("tig warning: ", stderr
);
7555 vfprintf(stderr
, msg
, args
);
7556 fputs("\n", stderr
);
7561 parse_options(int argc
, const char *argv
[])
7563 enum request request
= REQ_VIEW_MAIN
;
7564 const char *subcommand
;
7565 bool seen_dashdash
= FALSE
;
7566 /* XXX: This is vulnerable to the user overriding options
7567 * required for the main view parser. */
7568 const char *custom_argv
[SIZEOF_ARG
] = {
7569 "git", "log", "--no-color", "--pretty=raw", "--parents",
7570 "--topo-order", NULL
7574 if (!isatty(STDIN_FILENO
)) {
7575 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7576 return REQ_VIEW_PAGER
;
7582 subcommand
= argv
[1];
7583 if (!strcmp(subcommand
, "status")) {
7585 warn("ignoring arguments after `%s'", subcommand
);
7586 return REQ_VIEW_STATUS
;
7588 } else if (!strcmp(subcommand
, "blame")) {
7589 if (argc
<= 2 || argc
> 4)
7590 die("invalid number of options to blame\n\n%s", usage
);
7594 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7598 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7599 return REQ_VIEW_BLAME
;
7601 } else if (!strcmp(subcommand
, "show")) {
7602 request
= REQ_VIEW_DIFF
;
7609 custom_argv
[1] = subcommand
;
7613 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7614 const char *opt
= argv
[i
];
7616 if (seen_dashdash
|| !strcmp(opt
, "--")) {
7617 seen_dashdash
= TRUE
;
7619 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7620 printf("tig version %s\n", TIG_VERSION
);
7623 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7624 printf("%s\n", usage
);
7628 custom_argv
[j
++] = opt
;
7629 if (j
>= ARRAY_SIZE(custom_argv
))
7630 die("command too long");
7633 if (!prepare_update(VIEW(request
), custom_argv
, NULL
, FORMAT_NONE
))
7634 die("Failed to format arguments");
7640 main(int argc
, const char *argv
[])
7642 enum request request
= parse_options(argc
, argv
);
7646 signal(SIGINT
, quit
);
7647 signal(SIGPIPE
, SIG_IGN
);
7649 if (setlocale(LC_ALL
, "")) {
7650 char *codeset
= nl_langinfo(CODESET
);
7652 string_ncopy(opt_codeset
, codeset
, strlen(codeset
));
7655 if (load_repo_info() == ERR
)
7656 die("Failed to load repo info.");
7658 if (load_options() == ERR
)
7659 die("Failed to load user config.");
7661 if (load_git_config() == ERR
)
7662 die("Failed to load repo config.");
7664 /* Require a git repository unless when running in pager mode. */
7665 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7666 die("Not a git repository");
7668 if (*opt_encoding
&& strcasecmp(opt_encoding
, "UTF-8"))
7671 if (*opt_codeset
&& strcmp(opt_codeset
, opt_encoding
)) {
7672 opt_iconv
= iconv_open(opt_codeset
, opt_encoding
);
7673 if (opt_iconv
== ICONV_NONE
)
7674 die("Failed to initialize character set conversion");
7677 if (load_refs() == ERR
)
7678 die("Failed to load refs.");
7680 foreach_view (view
, i
)
7681 argv_from_env(view
->ops
->argv
, view
->cmd_env
);
7685 if (request
!= REQ_NONE
)
7686 open_view(NULL
, request
, OPEN_PREPARED
);
7687 request
= request
== REQ_NONE
? REQ_VIEW_MAIN
: REQ_NONE
;
7689 while (view_driver(display
[current_view
], request
)) {
7690 int key
= get_input(0);
7692 view
= display
[current_view
];
7693 request
= get_keybinding(view
->keymap
, key
);
7695 /* Some low-level request handling. This keeps access to
7696 * status_win restricted. */
7700 char *cmd
= read_prompt(":");
7702 if (cmd
&& isdigit(*cmd
)) {
7703 int lineno
= view
->lineno
+ 1;
7705 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7706 select_view_line(view
, lineno
- 1);
7709 report("Unable to parse '%s' as a line number", cmd
);
7713 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7714 const char *argv
[SIZEOF_ARG
] = { "git" };
7717 /* When running random commands, initially show the
7718 * command in the title. However, it maybe later be
7719 * overwritten if a commit line is selected. */
7720 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7722 if (!argv_from_string(argv
, &argc
, cmd
)) {
7723 report("Too many arguments");
7724 } else if (!prepare_update(next
, argv
, NULL
, FORMAT_DASH
)) {
7725 report("Failed to format command");
7727 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7735 case REQ_SEARCH_BACK
:
7737 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7738 char *search
= read_prompt(prompt
);
7741 string_ncopy(opt_search
, search
, strlen(search
));
7742 else if (*opt_search
)
7743 request
= request
== REQ_SEARCH
?