1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
37 #include <sys/select.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
62 #define __NORETURN __attribute__((__noreturn__))
67 static void __NORETURN
die(const char *err
, ...);
68 static void warn(const char *msg
, ...);
69 static void report(const char *msg
, ...);
70 static void set_nonblocking_input(bool loading
);
71 static size_t utf8_length(const char *string
, int *width
, size_t max_width
, int *trimmed
, bool reserve
);
72 static int load_refs(void);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x) (sizeof(x) - 1)
80 #define SIZEOF_STR 1024 /* Default string size. */
81 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG 32 /* Default argument array size. */
87 #define REVGRAPH_INIT 'I'
88 #define REVGRAPH_MERGE 'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND '^'
93 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT (-1)
98 #define ICONV_NONE ((iconv_t) -1)
100 #define ICONV_CONST /* nothing */
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT "%Y-%m-%d %H:%M"
105 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS 20
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
115 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
117 #define NULL_ID "0000000000000000000000000000000000000000"
120 #define GIT_CONFIG "config"
123 /* Some ascii-shorthands fitted into the ncurses namespace. */
125 #define KEY_RETURN '\r'
130 char *name
; /* Ref name; tag or head names are shortened. */
131 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
132 unsigned int head
:1; /* Is it the current HEAD? */
133 unsigned int tag
:1; /* Is it a tag? */
134 unsigned int ltag
:1; /* If so, is the tag local? */
135 unsigned int remote
:1; /* Is it a remote ref? */
136 unsigned int tracked
:1; /* Is it the remote for the current HEAD? */
137 unsigned int next
:1; /* For ref lists: are there more refs? */
140 static struct ref
**get_refs(const char *id
);
143 FORMAT_ALL
, /* Perform replacement in all arguments. */
144 FORMAT_DASH
, /* Perform replacement up until "--". */
145 FORMAT_NONE
/* No replacement should be performed. */
148 static bool format_argv(const char *dst
[], const char *src
[], enum format_flags flags
);
157 set_from_int_map(struct int_map
*map
, size_t map_size
,
158 int *value
, const char *name
, int namelen
)
163 for (i
= 0; i
< map_size
; i
++)
164 if (namelen
== map
[i
].namelen
&&
165 !strncasecmp(name
, map
[i
].name
, namelen
)) {
166 *value
= map
[i
].value
;
180 typedef enum input_status (*input_handler
)(void *data
, char *buf
, int c
);
182 static char *prompt_input(const char *prompt
, input_handler handler
, void *data
);
183 static bool prompt_yesno(const char *prompt
);
190 string_ncopy_do(char *dst
, size_t dstlen
, const char *src
, size_t srclen
)
192 if (srclen
> dstlen
- 1)
195 strncpy(dst
, src
, srclen
);
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205 string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
214 chomp_string(char *name
)
218 while (isspace(*name
))
221 namelen
= strlen(name
) - 1;
222 while (namelen
> 0 && isspace(name
[namelen
]))
229 string_nformat(char *buf
, size_t bufsize
, size_t *bufpos
, const char *fmt
, ...)
232 size_t pos
= bufpos
? *bufpos
: 0;
235 pos
+= vsnprintf(buf
+ pos
, bufsize
- pos
, fmt
, args
);
241 return pos
>= bufsize
? FALSE
: TRUE
;
244 #define string_format(buf, fmt, args...) \
245 string_nformat(buf, sizeof(buf), NULL, fmt, args)
247 #define string_format_from(buf, from, fmt, args...) \
248 string_nformat(buf, sizeof(buf), from, fmt, args)
251 string_enum_compare(const char *str1
, const char *str2
, int len
)
255 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
257 /* Diff-Header == DIFF_HEADER */
258 for (i
= 0; i
< len
; i
++) {
259 if (toupper(str1
[i
]) == toupper(str2
[i
]))
262 if (string_enum_sep(str1
[i
]) &&
263 string_enum_sep(str2
[i
]))
266 return str1
[i
] - str2
[i
];
272 #define prefixcmp(str1, str2) \
273 strncmp(str1, str2, STRING_SIZE(str2))
276 suffixcmp(const char *str
, int slen
, const char *suffix
)
278 size_t len
= slen
>= 0 ? slen
: strlen(str
);
279 size_t suffixlen
= strlen(suffix
);
281 return suffixlen
< len
? strcmp(str
+ len
- suffixlen
, suffix
) : -1;
286 argv_from_string(const char *argv
[SIZEOF_ARG
], int *argc
, char *cmd
)
290 while (*cmd
&& *argc
< SIZEOF_ARG
&& (valuelen
= strcspn(cmd
, " \t"))) {
291 bool advance
= cmd
[valuelen
] != 0;
294 argv
[(*argc
)++] = chomp_string(cmd
);
295 cmd
= chomp_string(cmd
+ valuelen
+ advance
);
298 if (*argc
< SIZEOF_ARG
)
300 return *argc
< SIZEOF_ARG
;
304 argv_from_env(const char **argv
, const char *name
)
306 char *env
= argv
? getenv(name
) : NULL
;
311 if (env
&& !argv_from_string(argv
, &argc
, env
))
312 die("Too many arguments in the `%s` environment variable", name
);
317 * Executing external commands.
321 IO_FD
, /* File descriptor based IO. */
322 IO_BG
, /* Execute command in the background. */
323 IO_FG
, /* Execute command with same std{in,out,err}. */
324 IO_RD
, /* Read only fork+exec IO. */
325 IO_WR
, /* Write only fork+exec IO. */
326 IO_AP
, /* Append fork+exec output to file. */
330 enum io_type type
; /* The requested type of pipe. */
331 const char *dir
; /* Directory from which to execute. */
332 pid_t pid
; /* Pipe for reading or writing. */
333 int pipe
; /* Pipe end for reading or writing. */
334 int error
; /* Error status. */
335 const char *argv
[SIZEOF_ARG
]; /* Shell command arguments. */
336 char *buf
; /* Read buffer. */
337 size_t bufalloc
; /* Allocated buffer size. */
338 size_t bufsize
; /* Buffer content size. */
339 char *bufpos
; /* Current buffer position. */
340 unsigned int eof
:1; /* Has end of file been reached. */
344 reset_io(struct io
*io
)
348 io
->buf
= io
->bufpos
= NULL
;
349 io
->bufalloc
= io
->bufsize
= 0;
355 init_io(struct io
*io
, const char *dir
, enum io_type type
)
363 init_io_rd(struct io
*io
, const char *argv
[], const char *dir
,
364 enum format_flags flags
)
366 init_io(io
, dir
, IO_RD
);
367 return format_argv(io
->argv
, argv
, flags
);
371 io_open(struct io
*io
, const char *name
)
373 init_io(io
, NULL
, IO_FD
);
374 io
->pipe
= *name
? open(name
, O_RDONLY
) : STDIN_FILENO
;
375 return io
->pipe
!= -1;
379 kill_io(struct io
*io
)
381 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
385 done_io(struct io
*io
)
396 pid_t waiting
= waitpid(pid
, &status
, 0);
401 report("waitpid failed (%s)", strerror(errno
));
405 return waiting
== pid
&&
406 !WIFSIGNALED(status
) &&
408 !WEXITSTATUS(status
);
415 start_io(struct io
*io
)
417 int pipefds
[2] = { -1, -1 };
419 if (io
->type
== IO_FD
)
422 if ((io
->type
== IO_RD
|| io
->type
== IO_WR
) &&
425 else if (io
->type
== IO_AP
)
426 pipefds
[1] = io
->pipe
;
428 if ((io
->pid
= fork())) {
429 if (pipefds
[!(io
->type
== IO_WR
)] != -1)
430 close(pipefds
[!(io
->type
== IO_WR
)]);
432 io
->pipe
= pipefds
[!!(io
->type
== IO_WR
)];
437 if (io
->type
!= IO_FG
) {
438 int devnull
= open("/dev/null", O_RDWR
);
439 int readfd
= io
->type
== IO_WR
? pipefds
[0] : devnull
;
440 int writefd
= (io
->type
== IO_RD
|| io
->type
== IO_AP
)
441 ? pipefds
[1] : devnull
;
443 dup2(readfd
, STDIN_FILENO
);
444 dup2(writefd
, STDOUT_FILENO
);
445 dup2(devnull
, STDERR_FILENO
);
448 if (pipefds
[0] != -1)
450 if (pipefds
[1] != -1)
454 if (io
->dir
&& *io
->dir
&& chdir(io
->dir
) == -1)
455 die("Failed to change directory: %s", strerror(errno
));
457 execvp(io
->argv
[0], (char *const*) io
->argv
);
458 die("Failed to execute program: %s", strerror(errno
));
461 if (pipefds
[!!(io
->type
== IO_WR
)] != -1)
462 close(pipefds
[!!(io
->type
== IO_WR
)]);
467 run_io(struct io
*io
, const char **argv
, const char *dir
, enum io_type type
)
469 init_io(io
, dir
, type
);
470 if (!format_argv(io
->argv
, argv
, FORMAT_NONE
))
476 run_io_do(struct io
*io
)
478 return start_io(io
) && done_io(io
);
482 run_io_bg(const char **argv
)
486 init_io(&io
, NULL
, IO_BG
);
487 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
489 return run_io_do(&io
);
493 run_io_fg(const char **argv
, const char *dir
)
497 init_io(&io
, dir
, IO_FG
);
498 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
500 return run_io_do(&io
);
504 run_io_append(const char **argv
, enum format_flags flags
, int fd
)
508 init_io(&io
, NULL
, IO_AP
);
510 if (format_argv(io
.argv
, argv
, flags
))
511 return run_io_do(&io
);
517 run_io_rd(struct io
*io
, const char **argv
, enum format_flags flags
)
519 return init_io_rd(io
, argv
, NULL
, flags
) && start_io(io
);
523 io_eof(struct io
*io
)
529 io_error(struct io
*io
)
535 io_strerror(struct io
*io
)
537 return strerror(io
->error
);
541 io_can_read(struct io
*io
)
543 struct timeval tv
= { 0, 500 };
547 FD_SET(io
->pipe
, &fds
);
549 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
553 io_read(struct io
*io
, void *buf
, size_t bufsize
)
556 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
558 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
560 else if (readsize
== -1)
562 else if (readsize
== 0)
569 io_get(struct io
*io
, int c
, bool can_read
)
575 io
->buf
= io
->bufpos
= malloc(BUFSIZ
);
578 io
->bufalloc
= BUFSIZ
;
583 if (io
->bufsize
> 0) {
584 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
586 char *line
= io
->bufpos
;
589 io
->bufpos
= eol
+ 1;
590 io
->bufsize
-= io
->bufpos
- line
;
597 io
->bufpos
[io
->bufsize
] = 0;
607 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
608 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
610 io
->bufpos
= io
->buf
;
611 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
614 io
->bufsize
+= readsize
;
619 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
623 while (!io_error(io
) && written
< bufsize
) {
626 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
627 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
635 return written
== bufsize
;
639 run_io_buf(const char **argv
, char buf
[], size_t bufsize
)
644 if (!run_io_rd(&io
, argv
, FORMAT_NONE
))
647 io
.buf
= io
.bufpos
= buf
;
648 io
.bufalloc
= bufsize
;
649 error
= !io_get(&io
, '\n', TRUE
) && io_error(&io
);
652 return done_io(&io
) || error
;
655 static int read_properties(struct io
*io
, const char *separators
, int (*read
)(char *, size_t, char *, size_t));
662 /* XXX: Keep the view request first and in sync with views[]. */ \
663 REQ_GROUP("View switching") \
664 REQ_(VIEW_MAIN, "Show main view"), \
665 REQ_(VIEW_DIFF, "Show diff view"), \
666 REQ_(VIEW_LOG, "Show log view"), \
667 REQ_(VIEW_TREE, "Show tree view"), \
668 REQ_(VIEW_BLOB, "Show blob view"), \
669 REQ_(VIEW_BLAME, "Show blame view"), \
670 REQ_(VIEW_HELP, "Show help page"), \
671 REQ_(VIEW_PAGER, "Show pager view"), \
672 REQ_(VIEW_STATUS, "Show status view"), \
673 REQ_(VIEW_STAGE, "Show stage view"), \
675 REQ_GROUP("View manipulation") \
676 REQ_(ENTER, "Enter current line and scroll"), \
677 REQ_(NEXT, "Move to next"), \
678 REQ_(PREVIOUS, "Move to previous"), \
679 REQ_(PARENT, "Move to parent"), \
680 REQ_(VIEW_NEXT, "Move focus to next view"), \
681 REQ_(REFRESH, "Reload and refresh"), \
682 REQ_(MAXIMIZE, "Maximize the current view"), \
683 REQ_(VIEW_CLOSE, "Close the current view"), \
684 REQ_(QUIT, "Close all views and quit"), \
686 REQ_GROUP("View specific requests") \
687 REQ_(STATUS_UPDATE, "Update file status"), \
688 REQ_(STATUS_REVERT, "Revert file changes"), \
689 REQ_(STATUS_MERGE, "Merge file using external tool"), \
690 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
692 REQ_GROUP("Cursor navigation") \
693 REQ_(MOVE_UP, "Move cursor one line up"), \
694 REQ_(MOVE_DOWN, "Move cursor one line down"), \
695 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
696 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
697 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
698 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
700 REQ_GROUP("Scrolling") \
701 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
702 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
703 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
704 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
706 REQ_GROUP("Searching") \
707 REQ_(SEARCH, "Search the view"), \
708 REQ_(SEARCH_BACK, "Search backwards in the view"), \
709 REQ_(FIND_NEXT, "Find next search match"), \
710 REQ_(FIND_PREV, "Find previous search match"), \
712 REQ_GROUP("Option manipulation") \
713 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
714 REQ_(TOGGLE_DATE, "Toggle date display"), \
715 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
716 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
717 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
720 REQ_(PROMPT, "Bring up the prompt"), \
721 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
722 REQ_(SHOW_VERSION, "Show version information"), \
723 REQ_(STOP_LOADING, "Stop all loading views"), \
724 REQ_(EDIT, "Open in editor"), \
725 REQ_(NONE, "Do nothing")
728 /* User action requests. */
730 #define REQ_GROUP(help)
731 #define REQ_(req, help) REQ_##req
733 /* Offset all requests to avoid conflicts with ncurses getch values. */
734 REQ_OFFSET
= KEY_MAX
+ 1,
741 struct request_info
{
742 enum request request
;
748 static struct request_info req_info
[] = {
749 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
750 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
757 get_request(const char *name
)
759 int namelen
= strlen(name
);
762 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
763 if (req_info
[i
].namelen
== namelen
&&
764 !string_enum_compare(req_info
[i
].name
, name
, namelen
))
765 return req_info
[i
].request
;
775 static const char usage
[] =
776 "tig " TIG_VERSION
" (" __DATE__
")\n"
778 "Usage: tig [options] [revs] [--] [paths]\n"
779 " or: tig show [options] [revs] [--] [paths]\n"
780 " or: tig blame [rev] path\n"
782 " or: tig < [git command output]\n"
785 " -v, --version Show version and exit\n"
786 " -h, --help Show help message and exit";
788 /* Option and state variables. */
789 static bool opt_date
= TRUE
;
790 static bool opt_author
= TRUE
;
791 static bool opt_line_number
= FALSE
;
792 static bool opt_line_graphics
= TRUE
;
793 static bool opt_rev_graph
= FALSE
;
794 static bool opt_show_refs
= TRUE
;
795 static int opt_num_interval
= NUMBER_INTERVAL
;
796 static int opt_tab_size
= TAB_SIZE
;
797 static int opt_author_cols
= AUTHOR_COLS
-1;
798 static char opt_path
[SIZEOF_STR
] = "";
799 static char opt_file
[SIZEOF_STR
] = "";
800 static char opt_ref
[SIZEOF_REF
] = "";
801 static char opt_head
[SIZEOF_REF
] = "";
802 static char opt_head_rev
[SIZEOF_REV
] = "";
803 static char opt_remote
[SIZEOF_REF
] = "";
804 static char opt_encoding
[20] = "UTF-8";
805 static bool opt_utf8
= TRUE
;
806 static char opt_codeset
[20] = "UTF-8";
807 static iconv_t opt_iconv
= ICONV_NONE
;
808 static char opt_search
[SIZEOF_STR
] = "";
809 static char opt_cdup
[SIZEOF_STR
] = "";
810 static char opt_prefix
[SIZEOF_STR
] = "";
811 static char opt_git_dir
[SIZEOF_STR
] = "";
812 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
813 static char opt_editor
[SIZEOF_STR
] = "";
814 static FILE *opt_tty
= NULL
;
816 #define is_initial_commit() (!*opt_head_rev)
817 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
820 parse_options(int argc
, const char *argv
[], const char ***run_argv
)
822 enum request request
= REQ_VIEW_MAIN
;
823 const char *subcommand
;
824 bool seen_dashdash
= FALSE
;
825 /* XXX: This is vulnerable to the user overriding options
826 * required for the main view parser. */
827 static const char *custom_argv
[SIZEOF_ARG
] = {
828 "git", "log", "--no-color", "--pretty=raw", "--parents",
833 if (!isatty(STDIN_FILENO
))
834 return REQ_VIEW_PAGER
;
837 return REQ_VIEW_MAIN
;
839 subcommand
= argv
[1];
840 if (!strcmp(subcommand
, "status") || !strcmp(subcommand
, "-S")) {
841 if (!strcmp(subcommand
, "-S"))
842 warn("`-S' has been deprecated; use `tig status' instead");
844 warn("ignoring arguments after `%s'", subcommand
);
845 return REQ_VIEW_STATUS
;
847 } else if (!strcmp(subcommand
, "blame")) {
848 if (argc
<= 2 || argc
> 4)
849 die("invalid number of options to blame\n\n%s", usage
);
853 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
857 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
858 return REQ_VIEW_BLAME
;
860 } else if (!strcmp(subcommand
, "show")) {
861 request
= REQ_VIEW_DIFF
;
863 } else if (!strcmp(subcommand
, "log") || !strcmp(subcommand
, "diff")) {
864 request
= subcommand
[0] == 'l' ? REQ_VIEW_LOG
: REQ_VIEW_DIFF
;
865 warn("`tig %s' has been deprecated", subcommand
);
872 custom_argv
[1] = subcommand
;
876 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
877 const char *opt
= argv
[i
];
879 if (seen_dashdash
|| !strcmp(opt
, "--")) {
880 seen_dashdash
= TRUE
;
882 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
883 printf("tig version %s\n", TIG_VERSION
);
886 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
887 printf("%s\n", usage
);
891 custom_argv
[j
++] = opt
;
892 if (j
>= ARRAY_SIZE(custom_argv
))
893 die("command too long");
896 custom_argv
[j
] = NULL
;
897 *run_argv
= custom_argv
;
904 * Line-oriented content detection.
908 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
909 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
910 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
911 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
912 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
913 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
914 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
915 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
916 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
917 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
918 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
919 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
920 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
921 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
922 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
923 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
924 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
925 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
926 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
927 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
928 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
929 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
930 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
931 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
932 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
933 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
934 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
935 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
936 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
937 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
938 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
939 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
940 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
941 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
942 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
943 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
944 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
945 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
946 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
947 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
948 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
949 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
950 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
951 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
952 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
953 LINE(TREE_PARENT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
954 LINE(TREE_MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
955 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
956 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
957 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
958 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
959 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
960 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
961 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
962 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
963 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
966 #define LINE(type, line, fg, bg, attr) \
974 const char *name
; /* Option name. */
975 int namelen
; /* Size of option name. */
976 const char *line
; /* The start of line to match. */
977 int linelen
; /* Size of string to match. */
978 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
981 static struct line_info line_info
[] = {
982 #define LINE(type, line, fg, bg, attr) \
983 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
988 static enum line_type
989 get_line_type(const char *line
)
991 int linelen
= strlen(line
);
994 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
995 /* Case insensitive search matches Signed-off-by lines better. */
996 if (linelen
>= line_info
[type
].linelen
&&
997 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1000 return LINE_DEFAULT
;
1004 get_line_attr(enum line_type type
)
1006 assert(type
< ARRAY_SIZE(line_info
));
1007 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1010 static struct line_info
*
1011 get_line_info(const char *name
)
1013 size_t namelen
= strlen(name
);
1014 enum line_type type
;
1016 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1017 if (namelen
== line_info
[type
].namelen
&&
1018 !string_enum_compare(line_info
[type
].name
, name
, namelen
))
1019 return &line_info
[type
];
1027 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1028 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1029 enum line_type type
;
1033 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1034 default_bg
= COLOR_BLACK
;
1035 default_fg
= COLOR_WHITE
;
1038 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1039 struct line_info
*info
= &line_info
[type
];
1040 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1041 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1043 init_pair(type
, fg
, bg
);
1048 enum line_type type
;
1051 unsigned int selected
:1;
1052 unsigned int dirty
:1;
1053 unsigned int cleareol
:1;
1055 void *data
; /* User data */
1065 enum request request
;
1068 static struct keybinding default_keybindings
[] = {
1069 /* View switching */
1070 { 'm', REQ_VIEW_MAIN
},
1071 { 'd', REQ_VIEW_DIFF
},
1072 { 'l', REQ_VIEW_LOG
},
1073 { 't', REQ_VIEW_TREE
},
1074 { 'f', REQ_VIEW_BLOB
},
1075 { 'B', REQ_VIEW_BLAME
},
1076 { 'p', REQ_VIEW_PAGER
},
1077 { 'h', REQ_VIEW_HELP
},
1078 { 'S', REQ_VIEW_STATUS
},
1079 { 'c', REQ_VIEW_STAGE
},
1081 /* View manipulation */
1082 { 'q', REQ_VIEW_CLOSE
},
1083 { KEY_TAB
, REQ_VIEW_NEXT
},
1084 { KEY_RETURN
, REQ_ENTER
},
1085 { KEY_UP
, REQ_PREVIOUS
},
1086 { KEY_DOWN
, REQ_NEXT
},
1087 { 'R', REQ_REFRESH
},
1088 { KEY_F(5), REQ_REFRESH
},
1089 { 'O', REQ_MAXIMIZE
},
1091 /* Cursor navigation */
1092 { 'k', REQ_MOVE_UP
},
1093 { 'j', REQ_MOVE_DOWN
},
1094 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1095 { KEY_END
, REQ_MOVE_LAST_LINE
},
1096 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1097 { ' ', REQ_MOVE_PAGE_DOWN
},
1098 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1099 { 'b', REQ_MOVE_PAGE_UP
},
1100 { '-', REQ_MOVE_PAGE_UP
},
1103 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1104 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1105 { 'w', REQ_SCROLL_PAGE_UP
},
1106 { 's', REQ_SCROLL_PAGE_DOWN
},
1109 { '/', REQ_SEARCH
},
1110 { '?', REQ_SEARCH_BACK
},
1111 { 'n', REQ_FIND_NEXT
},
1112 { 'N', REQ_FIND_PREV
},
1116 { 'z', REQ_STOP_LOADING
},
1117 { 'v', REQ_SHOW_VERSION
},
1118 { 'r', REQ_SCREEN_REDRAW
},
1119 { '.', REQ_TOGGLE_LINENO
},
1120 { 'D', REQ_TOGGLE_DATE
},
1121 { 'A', REQ_TOGGLE_AUTHOR
},
1122 { 'g', REQ_TOGGLE_REV_GRAPH
},
1123 { 'F', REQ_TOGGLE_REFS
},
1124 { ':', REQ_PROMPT
},
1125 { 'u', REQ_STATUS_UPDATE
},
1126 { '!', REQ_STATUS_REVERT
},
1127 { 'M', REQ_STATUS_MERGE
},
1128 { '@', REQ_STAGE_NEXT
},
1129 { ',', REQ_PARENT
},
1133 #define KEYMAP_INFO \
1147 #define KEYMAP_(name) KEYMAP_##name
1152 static struct int_map keymap_table
[] = {
1153 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1158 #define set_keymap(map, name) \
1159 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1161 struct keybinding_table
{
1162 struct keybinding
*data
;
1166 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1169 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1171 struct keybinding_table
*table
= &keybindings
[keymap
];
1173 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1175 die("Failed to allocate keybinding");
1176 table
->data
[table
->size
].alias
= key
;
1177 table
->data
[table
->size
++].request
= request
;
1180 /* Looks for a key binding first in the given map, then in the generic map, and
1181 * lastly in the default keybindings. */
1183 get_keybinding(enum keymap keymap
, int key
)
1187 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1188 if (keybindings
[keymap
].data
[i
].alias
== key
)
1189 return keybindings
[keymap
].data
[i
].request
;
1191 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1192 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1193 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1195 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1196 if (default_keybindings
[i
].alias
== key
)
1197 return default_keybindings
[i
].request
;
1199 return (enum request
) key
;
1208 static struct key key_table
[] = {
1209 { "Enter", KEY_RETURN
},
1211 { "Backspace", KEY_BACKSPACE
},
1213 { "Escape", KEY_ESC
},
1214 { "Left", KEY_LEFT
},
1215 { "Right", KEY_RIGHT
},
1217 { "Down", KEY_DOWN
},
1218 { "Insert", KEY_IC
},
1219 { "Delete", KEY_DC
},
1221 { "Home", KEY_HOME
},
1223 { "PageUp", KEY_PPAGE
},
1224 { "PageDown", KEY_NPAGE
},
1234 { "F10", KEY_F(10) },
1235 { "F11", KEY_F(11) },
1236 { "F12", KEY_F(12) },
1240 get_key_value(const char *name
)
1244 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1245 if (!strcasecmp(key_table
[i
].name
, name
))
1246 return key_table
[i
].value
;
1248 if (strlen(name
) == 1 && isprint(*name
))
1255 get_key_name(int key_value
)
1257 static char key_char
[] = "'X'";
1258 const char *seq
= NULL
;
1261 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1262 if (key_table
[key
].value
== key_value
)
1263 seq
= key_table
[key
].name
;
1267 isprint(key_value
)) {
1268 key_char
[1] = (char) key_value
;
1272 return seq
? seq
: "(no key)";
1276 get_key(enum request request
)
1278 static char buf
[BUFSIZ
];
1285 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1286 struct keybinding
*keybinding
= &default_keybindings
[i
];
1288 if (keybinding
->request
!= request
)
1291 if (!string_format_from(buf
, &pos
, "%s%s", sep
,
1292 get_key_name(keybinding
->alias
)))
1293 return "Too many keybindings!";
1300 struct run_request
{
1303 const char *argv
[SIZEOF_ARG
];
1306 static struct run_request
*run_request
;
1307 static size_t run_requests
;
1310 add_run_request(enum keymap keymap
, int key
, int argc
, const char **argv
)
1312 struct run_request
*req
;
1314 if (argc
>= ARRAY_SIZE(req
->argv
) - 1)
1317 req
= realloc(run_request
, (run_requests
+ 1) * sizeof(*run_request
));
1322 req
= &run_request
[run_requests
];
1323 req
->keymap
= keymap
;
1325 req
->argv
[0] = NULL
;
1327 if (!format_argv(req
->argv
, argv
, FORMAT_NONE
))
1330 return REQ_NONE
+ ++run_requests
;
1333 static struct run_request
*
1334 get_run_request(enum request request
)
1336 if (request
<= REQ_NONE
)
1338 return &run_request
[request
- REQ_NONE
- 1];
1342 add_builtin_run_requests(void)
1344 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1345 const char *gc
[] = { "git", "gc", NULL
};
1352 { KEYMAP_MAIN
, 'C', ARRAY_SIZE(cherry_pick
) - 1, cherry_pick
},
1353 { KEYMAP_GENERIC
, 'G', ARRAY_SIZE(gc
) - 1, gc
},
1357 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1360 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argc
, reqs
[i
].argv
);
1361 if (req
!= REQ_NONE
)
1362 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1367 * User config file handling.
1370 static struct int_map color_map
[] = {
1371 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1383 #define set_color(color, name) \
1384 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1386 static struct int_map attr_map
[] = {
1387 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1394 ATTR_MAP(UNDERLINE
),
1397 #define set_attribute(attr, name) \
1398 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1400 static int config_lineno
;
1401 static bool config_errors
;
1402 static const char *config_msg
;
1404 /* Wants: object fgcolor bgcolor [attr] */
1406 option_color_command(int argc
, const char *argv
[])
1408 struct line_info
*info
;
1410 if (argc
!= 3 && argc
!= 4) {
1411 config_msg
= "Wrong number of arguments given to color command";
1415 info
= get_line_info(argv
[0]);
1417 if (!string_enum_compare(argv
[0], "main-delim", strlen("main-delim"))) {
1418 info
= get_line_info("delimiter");
1420 } else if (!string_enum_compare(argv
[0], "main-date", strlen("main-date"))) {
1421 info
= get_line_info("date");
1424 config_msg
= "Unknown color name";
1429 if (set_color(&info
->fg
, argv
[1]) == ERR
||
1430 set_color(&info
->bg
, argv
[2]) == ERR
) {
1431 config_msg
= "Unknown color";
1435 if (argc
== 4 && set_attribute(&info
->attr
, argv
[3]) == ERR
) {
1436 config_msg
= "Unknown attribute";
1443 static bool parse_bool(const char *s
)
1445 return (!strcmp(s
, "1") || !strcmp(s
, "true") ||
1446 !strcmp(s
, "yes")) ? TRUE
: FALSE
;
1450 parse_int(const char *s
, int default_value
, int min
, int max
)
1452 int value
= atoi(s
);
1454 return (value
< min
|| value
> max
) ? default_value
: value
;
1457 /* Wants: name = value */
1459 option_set_command(int argc
, const char *argv
[])
1462 config_msg
= "Wrong number of arguments given to set command";
1466 if (strcmp(argv
[1], "=")) {
1467 config_msg
= "No value assigned";
1471 if (!strcmp(argv
[0], "show-author")) {
1472 opt_author
= parse_bool(argv
[2]);
1476 if (!strcmp(argv
[0], "show-date")) {
1477 opt_date
= parse_bool(argv
[2]);
1481 if (!strcmp(argv
[0], "show-rev-graph")) {
1482 opt_rev_graph
= parse_bool(argv
[2]);
1486 if (!strcmp(argv
[0], "show-refs")) {
1487 opt_show_refs
= parse_bool(argv
[2]);
1491 if (!strcmp(argv
[0], "show-line-numbers")) {
1492 opt_line_number
= parse_bool(argv
[2]);
1496 if (!strcmp(argv
[0], "line-graphics")) {
1497 opt_line_graphics
= parse_bool(argv
[2]);
1501 if (!strcmp(argv
[0], "line-number-interval")) {
1502 opt_num_interval
= parse_int(argv
[2], opt_num_interval
, 1, 1024);
1506 if (!strcmp(argv
[0], "author-width")) {
1507 opt_author_cols
= parse_int(argv
[2], opt_author_cols
, 0, 1024);
1511 if (!strcmp(argv
[0], "tab-size")) {
1512 opt_tab_size
= parse_int(argv
[2], opt_tab_size
, 1, 1024);
1516 if (!strcmp(argv
[0], "commit-encoding")) {
1517 const char *arg
= argv
[2];
1518 int arglen
= strlen(arg
);
1523 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1524 config_msg
= "Unmatched quotation";
1527 arg
+= 1; arglen
-= 2;
1529 string_ncopy(opt_encoding
, arg
, strlen(arg
));
1534 config_msg
= "Unknown variable name";
1538 /* Wants: mode request key */
1540 option_bind_command(int argc
, const char *argv
[])
1542 enum request request
;
1547 config_msg
= "Wrong number of arguments given to bind command";
1551 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
1552 config_msg
= "Unknown key map";
1556 key
= get_key_value(argv
[1]);
1558 config_msg
= "Unknown key";
1562 request
= get_request(argv
[2]);
1563 if (request
== REQ_NONE
) {
1566 enum request request
;
1568 { "cherry-pick", REQ_NONE
},
1569 { "screen-resize", REQ_NONE
},
1570 { "tree-parent", REQ_PARENT
},
1572 size_t namelen
= strlen(argv
[2]);
1575 for (i
= 0; i
< ARRAY_SIZE(obsolete
); i
++) {
1576 if (namelen
!= strlen(obsolete
[i
].name
) ||
1577 string_enum_compare(obsolete
[i
].name
, argv
[2], namelen
))
1579 if (obsolete
[i
].request
!= REQ_NONE
)
1580 add_keybinding(keymap
, obsolete
[i
].request
, key
);
1581 config_msg
= "Obsolete request name";
1585 if (request
== REQ_NONE
&& *argv
[2]++ == '!')
1586 request
= add_run_request(keymap
, key
, argc
- 2, argv
+ 2);
1587 if (request
== REQ_NONE
) {
1588 config_msg
= "Unknown request name";
1592 add_keybinding(keymap
, request
, key
);
1598 set_option(const char *opt
, char *value
)
1600 const char *argv
[SIZEOF_ARG
];
1603 if (!argv_from_string(argv
, &argc
, value
)) {
1604 config_msg
= "Too many option arguments";
1608 if (!strcmp(opt
, "color"))
1609 return option_color_command(argc
, argv
);
1611 if (!strcmp(opt
, "set"))
1612 return option_set_command(argc
, argv
);
1614 if (!strcmp(opt
, "bind"))
1615 return option_bind_command(argc
, argv
);
1617 config_msg
= "Unknown option command";
1622 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
1627 config_msg
= "Internal error";
1629 /* Check for comment markers, since read_properties() will
1630 * only ensure opt and value are split at first " \t". */
1631 optlen
= strcspn(opt
, "#");
1635 if (opt
[optlen
] != 0) {
1636 config_msg
= "No option value";
1640 /* Look for comment endings in the value. */
1641 size_t len
= strcspn(value
, "#");
1643 if (len
< valuelen
) {
1645 value
[valuelen
] = 0;
1648 status
= set_option(opt
, value
);
1651 if (status
== ERR
) {
1652 fprintf(stderr
, "Error on line %d, near '%.*s': %s\n",
1653 config_lineno
, (int) optlen
, opt
, config_msg
);
1654 config_errors
= TRUE
;
1657 /* Always keep going if errors are encountered. */
1662 load_option_file(const char *path
)
1666 /* It's ok that the file doesn't exist. */
1667 if (!io_open(&io
, path
))
1671 config_errors
= FALSE
;
1673 if (read_properties(&io
, " \t", read_option
) == ERR
||
1674 config_errors
== TRUE
)
1675 fprintf(stderr
, "Errors while loading %s.\n", path
);
1681 const char *home
= getenv("HOME");
1682 const char *tigrc_user
= getenv("TIGRC_USER");
1683 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
1684 char buf
[SIZEOF_STR
];
1686 add_builtin_run_requests();
1688 if (!tigrc_system
) {
1689 if (!string_format(buf
, "%s/tigrc", SYSCONFDIR
))
1693 load_option_file(tigrc_system
);
1696 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
1700 load_option_file(tigrc_user
);
1713 /* The display array of active views and the index of the current view. */
1714 static struct view
*display
[2];
1715 static unsigned int current_view
;
1717 #define foreach_displayed_view(view, i) \
1718 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1720 #define displayed_views() (display[1] != NULL ? 2 : 1)
1722 /* Current head and commit ID */
1723 static char ref_blob
[SIZEOF_REF
] = "";
1724 static char ref_commit
[SIZEOF_REF
] = "HEAD";
1725 static char ref_head
[SIZEOF_REF
] = "HEAD";
1728 const char *name
; /* View name */
1729 const char *cmd_env
; /* Command line set via environment */
1730 const char *id
; /* Points to either of ref_{head,commit,blob} */
1732 struct view_ops
*ops
; /* View operations */
1734 enum keymap keymap
; /* What keymap does this view have */
1735 bool git_dir
; /* Whether the view requires a git directory. */
1737 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
1738 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
1740 int height
, width
; /* The width and height of the main window */
1741 WINDOW
*win
; /* The main window */
1742 WINDOW
*title
; /* The title window living below the main window */
1745 unsigned long offset
; /* Offset of the window top */
1746 unsigned long lineno
; /* Current line number */
1747 unsigned long p_offset
; /* Previous offset of the window top */
1748 unsigned long p_lineno
; /* Previous current line number */
1749 bool p_restore
; /* Should the previous position be restored. */
1752 char grep
[SIZEOF_STR
]; /* Search string */
1753 regex_t
*regex
; /* Pre-compiled regex */
1755 /* If non-NULL, points to the view that opened this view. If this view
1756 * is closed tig will switch back to the parent view. */
1757 struct view
*parent
;
1760 size_t lines
; /* Total number of lines */
1761 struct line
*line
; /* Line index */
1762 size_t line_alloc
; /* Total number of allocated lines */
1763 unsigned int digits
; /* Number of digits in the lines member. */
1766 struct line
*curline
; /* Line currently being drawn. */
1767 enum line_type curtype
; /* Attribute currently used for drawing. */
1768 unsigned long col
; /* Column when drawing. */
1778 /* What type of content being displayed. Used in the title bar. */
1780 /* Default command arguments. */
1782 /* Open and reads in all view content. */
1783 bool (*open
)(struct view
*view
);
1784 /* Read one line; updates view->line. */
1785 bool (*read
)(struct view
*view
, char *data
);
1786 /* Draw one line; @lineno must be < view->height. */
1787 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
1788 /* Depending on view handle a special requests. */
1789 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
1790 /* Search for regex in a line. */
1791 bool (*grep
)(struct view
*view
, struct line
*line
);
1793 void (*select
)(struct view
*view
, struct line
*line
);
1796 static struct view_ops blame_ops
;
1797 static struct view_ops blob_ops
;
1798 static struct view_ops diff_ops
;
1799 static struct view_ops help_ops
;
1800 static struct view_ops log_ops
;
1801 static struct view_ops main_ops
;
1802 static struct view_ops pager_ops
;
1803 static struct view_ops stage_ops
;
1804 static struct view_ops status_ops
;
1805 static struct view_ops tree_ops
;
1807 #define VIEW_STR(name, env, ref, ops, map, git) \
1808 { name, #env, ref, ops, map, git }
1810 #define VIEW_(id, name, ops, git, ref) \
1811 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1814 static struct view views
[] = {
1815 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
1816 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
1817 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
1818 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
1819 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
1820 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
1821 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
1822 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
1823 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
1824 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
1827 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1828 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1830 #define foreach_view(view, i) \
1831 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1833 #define view_is_displayed(view) \
1834 (view == display[0] || view == display[1])
1841 static int line_graphics
[] = {
1842 /* LINE_GRAPHIC_VLINE: */ '|'
1846 set_view_attr(struct view
*view
, enum line_type type
)
1848 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
1849 wattrset(view
->win
, get_line_attr(type
));
1850 wchgat(view
->win
, -1, 0, type
, NULL
);
1851 view
->curtype
= type
;
1856 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
1857 int max_len
, bool use_tilde
)
1861 int trimmed
= FALSE
;
1867 len
= utf8_length(string
, &col
, max_len
, &trimmed
, use_tilde
);
1869 col
= len
= strlen(string
);
1870 if (len
> max_len
) {
1874 col
= len
= max_len
;
1879 set_view_attr(view
, type
);
1880 waddnstr(view
->win
, string
, len
);
1881 if (trimmed
&& use_tilde
) {
1882 set_view_attr(view
, LINE_DELIMITER
);
1883 waddch(view
->win
, '~');
1891 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
1893 static char space
[] = " ";
1896 spaces
= MIN(max
, spaces
);
1898 while (spaces
> 0) {
1899 int len
= MIN(spaces
, sizeof(space
) - 1);
1901 col
+= draw_chars(view
, type
, space
, spaces
, FALSE
);
1909 draw_lineno(struct view
*view
, unsigned int lineno
)
1912 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
1913 int max_number
= MIN(digits3
, STRING_SIZE(number
));
1914 int max
= view
->width
- view
->col
;
1917 if (max
< max_number
)
1920 lineno
+= view
->offset
+ 1;
1921 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
1922 static char fmt
[] = "%1ld";
1924 if (view
->digits
<= 9)
1925 fmt
[1] = '0' + digits3
;
1927 if (!string_format(number
, fmt
, lineno
))
1929 col
= draw_chars(view
, LINE_LINE_NUMBER
, number
, max_number
, TRUE
);
1931 col
= draw_space(view
, LINE_LINE_NUMBER
, max_number
, max_number
);
1935 set_view_attr(view
, LINE_DEFAULT
);
1936 waddch(view
->win
, line_graphics
[LINE_GRAPHIC_VLINE
]);
1941 col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, 1);
1944 return view
->width
- view
->col
<= 0;
1948 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
1950 view
->col
+= draw_chars(view
, type
, string
, view
->width
- view
->col
, trim
);
1951 return view
->width
- view
->col
<= 0;
1955 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
1957 int max
= view
->width
- view
->col
;
1963 set_view_attr(view
, type
);
1964 /* Using waddch() instead of waddnstr() ensures that
1965 * they'll be rendered correctly for the cursor line. */
1966 for (i
= 0; i
< size
; i
++)
1967 waddch(view
->win
, graphic
[i
]);
1971 waddch(view
->win
, ' ');
1975 return view
->width
- view
->col
<= 0;
1979 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
1981 int max
= MIN(view
->width
- view
->col
, len
);
1985 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
1987 col
= draw_space(view
, type
, max
- 1, max
- 1);
1989 view
->col
+= col
+ draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
1990 return view
->width
- view
->col
<= 0;
1994 draw_date(struct view
*view
, struct tm
*time
)
1996 char buf
[DATE_COLS
];
2001 timelen
= strftime(buf
, sizeof(buf
), DATE_FORMAT
, time
);
2002 date
= timelen
? buf
: NULL
;
2004 return draw_field(view
, LINE_DATE
, date
, DATE_COLS
, FALSE
);
2008 draw_author(struct view
*view
, const char *author
)
2010 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5 || !author
;
2013 static char initials
[10];
2016 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2018 memset(initials
, 0, sizeof(initials
));
2019 for (pos
= 0; *author
&& pos
< opt_author_cols
- 1; author
++, pos
++) {
2020 while (is_initial_sep(*author
))
2022 strncpy(&initials
[pos
], author
, sizeof(initials
) - 1 - pos
);
2023 while (*author
&& !is_initial_sep(author
[1]))
2030 return draw_field(view
, LINE_MAIN_AUTHOR
, author
, opt_author_cols
, trim
);
2034 draw_view_line(struct view
*view
, unsigned int lineno
)
2037 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2039 assert(view_is_displayed(view
));
2041 if (view
->offset
+ lineno
>= view
->lines
)
2044 line
= &view
->line
[view
->offset
+ lineno
];
2046 wmove(view
->win
, lineno
, 0);
2048 wclrtoeol(view
->win
);
2050 view
->curline
= line
;
2051 view
->curtype
= LINE_NONE
;
2052 line
->selected
= FALSE
;
2053 line
->dirty
= line
->cleareol
= 0;
2056 set_view_attr(view
, LINE_CURSOR
);
2057 line
->selected
= TRUE
;
2058 view
->ops
->select(view
, line
);
2061 return view
->ops
->draw(view
, line
, lineno
);
2065 redraw_view_dirty(struct view
*view
)
2070 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2071 if (view
->offset
+ lineno
>= view
->lines
)
2073 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2076 if (!draw_view_line(view
, lineno
))
2082 wnoutrefresh(view
->win
);
2086 redraw_view_from(struct view
*view
, int lineno
)
2088 assert(0 <= lineno
&& lineno
< view
->height
);
2090 for (; lineno
< view
->height
; lineno
++) {
2091 if (!draw_view_line(view
, lineno
))
2095 wnoutrefresh(view
->win
);
2099 redraw_view(struct view
*view
)
2102 redraw_view_from(view
, 0);
2107 update_display_cursor(struct view
*view
)
2109 /* Move the cursor to the right-most column of the cursor line.
2111 * XXX: This could turn out to be a bit expensive, but it ensures that
2112 * the cursor does not jump around. */
2114 wmove(view
->win
, view
->lineno
- view
->offset
, view
->width
- 1);
2115 wnoutrefresh(view
->win
);
2120 update_view_title(struct view
*view
)
2122 char buf
[SIZEOF_STR
];
2123 char state
[SIZEOF_STR
];
2124 size_t bufpos
= 0, statelen
= 0;
2126 assert(view_is_displayed(view
));
2128 if (view
!= VIEW(REQ_VIEW_STATUS
) && view
->lines
) {
2129 unsigned int view_lines
= view
->offset
+ view
->height
;
2130 unsigned int lines
= view
->lines
2131 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2134 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2143 time_t secs
= time(NULL
) - view
->start_time
;
2145 /* Three git seconds are a long time ... */
2147 string_format_from(state
, &statelen
, " loading %lds", secs
);
2150 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2151 if (*view
->ref
&& bufpos
< view
->width
) {
2152 size_t refsize
= strlen(view
->ref
);
2153 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2155 if (minsize
< view
->width
)
2156 refsize
= view
->width
- minsize
+ 7;
2157 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2160 if (statelen
&& bufpos
< view
->width
) {
2161 string_format_from(buf
, &bufpos
, "%s", state
);
2164 if (view
== display
[current_view
])
2165 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2167 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2169 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2170 wclrtoeol(view
->title
);
2171 wmove(view
->title
, 0, view
->width
- 1);
2172 wnoutrefresh(view
->title
);
2176 resize_display(void)
2179 struct view
*base
= display
[0];
2180 struct view
*view
= display
[1] ? display
[1] : display
[0];
2182 /* Setup window dimensions */
2184 getmaxyx(stdscr
, base
->height
, base
->width
);
2186 /* Make room for the status window. */
2190 /* Horizontal split. */
2191 view
->width
= base
->width
;
2192 view
->height
= SCALE_SPLIT_VIEW(base
->height
);
2193 base
->height
-= view
->height
;
2195 /* Make room for the title bar. */
2199 /* Make room for the title bar. */
2204 foreach_displayed_view (view
, i
) {
2206 view
->win
= newwin(view
->height
, 0, offset
, 0);
2208 die("Failed to create %s view", view
->name
);
2210 scrollok(view
->win
, FALSE
);
2212 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2214 die("Failed to create title window");
2217 wresize(view
->win
, view
->height
, view
->width
);
2218 mvwin(view
->win
, offset
, 0);
2219 mvwin(view
->title
, offset
+ view
->height
, 0);
2222 offset
+= view
->height
+ 1;
2227 redraw_display(bool clear
)
2232 foreach_displayed_view (view
, i
) {
2236 update_view_title(view
);
2239 if (display
[current_view
])
2240 update_display_cursor(display
[current_view
]);
2244 toggle_view_option(bool *option
, const char *help
)
2247 redraw_display(FALSE
);
2248 report("%sabling %s", *option
? "En" : "Dis", help
);
2255 /* Scrolling backend */
2257 do_scroll_view(struct view
*view
, int lines
)
2259 bool redraw_current_line
= FALSE
;
2261 /* The rendering expects the new offset. */
2262 view
->offset
+= lines
;
2264 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2267 /* Move current line into the view. */
2268 if (view
->lineno
< view
->offset
) {
2269 view
->lineno
= view
->offset
;
2270 redraw_current_line
= TRUE
;
2271 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2272 view
->lineno
= view
->offset
+ view
->height
- 1;
2273 redraw_current_line
= TRUE
;
2276 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2278 /* Redraw the whole screen if scrolling is pointless. */
2279 if (view
->height
< ABS(lines
)) {
2283 int line
= lines
> 0 ? view
->height
- lines
: 0;
2284 int end
= line
+ ABS(lines
);
2286 scrollok(view
->win
, TRUE
);
2287 wscrl(view
->win
, lines
);
2288 scrollok(view
->win
, FALSE
);
2290 while (line
< end
&& draw_view_line(view
, line
))
2293 if (redraw_current_line
)
2294 draw_view_line(view
, view
->lineno
- view
->offset
);
2295 wnoutrefresh(view
->win
);
2301 /* Scroll frontend */
2303 scroll_view(struct view
*view
, enum request request
)
2307 assert(view_is_displayed(view
));
2310 case REQ_SCROLL_PAGE_DOWN
:
2311 lines
= view
->height
;
2312 case REQ_SCROLL_LINE_DOWN
:
2313 if (view
->offset
+ lines
> view
->lines
)
2314 lines
= view
->lines
- view
->offset
;
2316 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2317 report("Cannot scroll beyond the last line");
2322 case REQ_SCROLL_PAGE_UP
:
2323 lines
= view
->height
;
2324 case REQ_SCROLL_LINE_UP
:
2325 if (lines
> view
->offset
)
2326 lines
= view
->offset
;
2329 report("Cannot scroll beyond the first line");
2337 die("request %d not handled in switch", request
);
2340 do_scroll_view(view
, lines
);
2345 move_view(struct view
*view
, enum request request
)
2347 int scroll_steps
= 0;
2351 case REQ_MOVE_FIRST_LINE
:
2352 steps
= -view
->lineno
;
2355 case REQ_MOVE_LAST_LINE
:
2356 steps
= view
->lines
- view
->lineno
- 1;
2359 case REQ_MOVE_PAGE_UP
:
2360 steps
= view
->height
> view
->lineno
2361 ? -view
->lineno
: -view
->height
;
2364 case REQ_MOVE_PAGE_DOWN
:
2365 steps
= view
->lineno
+ view
->height
>= view
->lines
2366 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2378 die("request %d not handled in switch", request
);
2381 if (steps
<= 0 && view
->lineno
== 0) {
2382 report("Cannot move beyond the first line");
2385 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2386 report("Cannot move beyond the last line");
2390 /* Move the current line */
2391 view
->lineno
+= steps
;
2392 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2394 /* Check whether the view needs to be scrolled */
2395 if (view
->lineno
< view
->offset
||
2396 view
->lineno
>= view
->offset
+ view
->height
) {
2397 scroll_steps
= steps
;
2398 if (steps
< 0 && -steps
> view
->offset
) {
2399 scroll_steps
= -view
->offset
;
2401 } else if (steps
> 0) {
2402 if (view
->lineno
== view
->lines
- 1 &&
2403 view
->lines
> view
->height
) {
2404 scroll_steps
= view
->lines
- view
->offset
- 1;
2405 if (scroll_steps
>= view
->height
)
2406 scroll_steps
-= view
->height
- 1;
2411 if (!view_is_displayed(view
)) {
2412 view
->offset
+= scroll_steps
;
2413 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2414 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2418 /* Repaint the old "current" line if we be scrolling */
2419 if (ABS(steps
) < view
->height
)
2420 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
2423 do_scroll_view(view
, scroll_steps
);
2427 /* Draw the current line */
2428 draw_view_line(view
, view
->lineno
- view
->offset
);
2430 wnoutrefresh(view
->win
);
2439 static void search_view(struct view
*view
, enum request request
);
2442 select_view_line(struct view
*view
, unsigned long lineno
)
2444 if (lineno
- view
->offset
>= view
->height
) {
2445 view
->offset
= lineno
;
2446 view
->lineno
= lineno
;
2447 if (view_is_displayed(view
))
2451 unsigned long old_lineno
= view
->lineno
- view
->offset
;
2453 view
->lineno
= lineno
;
2454 if (view_is_displayed(view
)) {
2455 draw_view_line(view
, old_lineno
);
2456 draw_view_line(view
, view
->lineno
- view
->offset
);
2457 wnoutrefresh(view
->win
);
2459 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2465 find_next(struct view
*view
, enum request request
)
2467 unsigned long lineno
= view
->lineno
;
2472 report("No previous search");
2474 search_view(view
, request
);
2484 case REQ_SEARCH_BACK
:
2493 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
2494 lineno
+= direction
;
2496 /* Note, lineno is unsigned long so will wrap around in which case it
2497 * will become bigger than view->lines. */
2498 for (; lineno
< view
->lines
; lineno
+= direction
) {
2499 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
2500 select_view_line(view
, lineno
);
2501 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
2506 report("No match found for '%s'", view
->grep
);
2510 search_view(struct view
*view
, enum request request
)
2515 regfree(view
->regex
);
2518 view
->regex
= calloc(1, sizeof(*view
->regex
));
2523 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
2524 if (regex_err
!= 0) {
2525 char buf
[SIZEOF_STR
] = "unknown error";
2527 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
2528 report("Search failed: %s", buf
);
2532 string_copy(view
->grep
, opt_search
);
2534 find_next(view
, request
);
2538 * Incremental updating
2542 reset_view(struct view
*view
)
2546 for (i
= 0; i
< view
->lines
; i
++)
2547 free(view
->line
[i
].data
);
2550 view
->p_offset
= view
->offset
;
2551 view
->p_lineno
= view
->lineno
;
2557 view
->line_alloc
= 0;
2559 view
->update_secs
= 0;
2563 free_argv(const char *argv
[])
2567 for (argc
= 0; argv
[argc
]; argc
++)
2568 free((void *) argv
[argc
]);
2572 format_argv(const char *dst_argv
[], const char *src_argv
[], enum format_flags flags
)
2574 char buf
[SIZEOF_STR
];
2576 bool noreplace
= flags
== FORMAT_NONE
;
2578 free_argv(dst_argv
);
2580 for (argc
= 0; src_argv
[argc
]; argc
++) {
2581 const char *arg
= src_argv
[argc
];
2585 char *next
= strstr(arg
, "%(");
2586 int len
= next
- arg
;
2589 if (!next
|| noreplace
) {
2590 if (flags
== FORMAT_DASH
&& !strcmp(arg
, "--"))
2595 } else if (!prefixcmp(next
, "%(directory)")) {
2598 } else if (!prefixcmp(next
, "%(file)")) {
2601 } else if (!prefixcmp(next
, "%(ref)")) {
2602 value
= *opt_ref
? opt_ref
: "HEAD";
2604 } else if (!prefixcmp(next
, "%(head)")) {
2607 } else if (!prefixcmp(next
, "%(commit)")) {
2610 } else if (!prefixcmp(next
, "%(blob)")) {
2614 report("Unknown replacement: `%s`", next
);
2618 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
2621 arg
= next
&& !noreplace
? strchr(next
, ')') + 1 : NULL
;
2624 dst_argv
[argc
] = strdup(buf
);
2625 if (!dst_argv
[argc
])
2629 dst_argv
[argc
] = NULL
;
2631 return src_argv
[argc
] == NULL
;
2635 restore_view_position(struct view
*view
)
2637 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
2640 /* Changing the view position cancels the restoring. */
2641 /* FIXME: Changing back to the first line is not detected. */
2642 if (view
->offset
!= 0 || view
->lineno
!= 0) {
2643 view
->p_restore
= FALSE
;
2647 if (view
->p_lineno
>= view
->lines
) {
2648 view
->p_lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2649 if (view
->p_offset
>= view
->p_lineno
) {
2650 unsigned long half
= view
->height
/ 2;
2652 if (view
->p_lineno
> half
)
2653 view
->p_offset
= view
->p_lineno
- half
;
2659 if (view_is_displayed(view
) &&
2660 view
->offset
!= view
->p_offset
&&
2661 view
->lineno
!= view
->p_lineno
)
2664 view
->offset
= view
->p_offset
;
2665 view
->lineno
= view
->p_lineno
;
2666 view
->p_restore
= FALSE
;
2672 end_update(struct view
*view
, bool force
)
2676 while (!view
->ops
->read(view
, NULL
))
2679 set_nonblocking_input(FALSE
);
2681 kill_io(view
->pipe
);
2682 done_io(view
->pipe
);
2687 setup_update(struct view
*view
, const char *vid
)
2689 set_nonblocking_input(TRUE
);
2691 string_copy_rev(view
->vid
, vid
);
2692 view
->pipe
= &view
->io
;
2693 view
->start_time
= time(NULL
);
2697 prepare_update(struct view
*view
, const char *argv
[], const char *dir
,
2698 enum format_flags flags
)
2701 end_update(view
, TRUE
);
2702 return init_io_rd(&view
->io
, argv
, dir
, flags
);
2706 prepare_update_file(struct view
*view
, const char *name
)
2709 end_update(view
, TRUE
);
2710 return io_open(&view
->io
, name
);
2714 begin_update(struct view
*view
, bool refresh
)
2717 end_update(view
, TRUE
);
2720 if (!start_io(&view
->io
))
2724 if (view
== VIEW(REQ_VIEW_TREE
) && strcmp(view
->vid
, view
->id
))
2727 if (!run_io_rd(&view
->io
, view
->ops
->argv
, FORMAT_ALL
))
2730 /* Put the current ref_* value to the view title ref
2731 * member. This is needed by the blob view. Most other
2732 * views sets it automatically after loading because the
2733 * first line is a commit line. */
2734 string_copy_rev(view
->ref
, view
->id
);
2737 setup_update(view
, view
->id
);
2742 #define ITEM_CHUNK_SIZE 256
2744 realloc_items(void *mem
, size_t *size
, size_t new_size
, size_t item_size
)
2746 size_t num_chunks
= *size
/ ITEM_CHUNK_SIZE
;
2747 size_t num_chunks_new
= (new_size
+ ITEM_CHUNK_SIZE
- 1) / ITEM_CHUNK_SIZE
;
2749 if (mem
== NULL
|| num_chunks
!= num_chunks_new
) {
2750 *size
= num_chunks_new
* ITEM_CHUNK_SIZE
;
2751 mem
= realloc(mem
, *size
* item_size
);
2757 static struct line
*
2758 realloc_lines(struct view
*view
, size_t line_size
)
2760 size_t alloc
= view
->line_alloc
;
2761 struct line
*tmp
= realloc_items(view
->line
, &alloc
, line_size
,
2762 sizeof(*view
->line
));
2768 view
->line_alloc
= alloc
;
2773 update_view(struct view
*view
)
2775 char out_buffer
[BUFSIZ
* 2];
2777 /* Clear the view and redraw everything since the tree sorting
2778 * might have rearranged things. */
2779 bool redraw
= view
->lines
== 0;
2780 bool can_read
= TRUE
;
2785 if (!io_can_read(view
->pipe
)) {
2786 if (view
->lines
== 0) {
2787 time_t secs
= time(NULL
) - view
->start_time
;
2789 if (secs
> view
->update_secs
) {
2790 if (view
->update_secs
== 0)
2792 update_view_title(view
);
2793 view
->update_secs
= secs
;
2799 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
2800 if (opt_iconv
!= ICONV_NONE
) {
2801 ICONV_CONST
char *inbuf
= line
;
2802 size_t inlen
= strlen(line
) + 1;
2804 char *outbuf
= out_buffer
;
2805 size_t outlen
= sizeof(out_buffer
);
2809 ret
= iconv(opt_iconv
, &inbuf
, &inlen
, &outbuf
, &outlen
);
2810 if (ret
!= (size_t) -1)
2814 if (!view
->ops
->read(view
, line
)) {
2815 report("Allocation failure");
2816 end_update(view
, TRUE
);
2822 unsigned long lines
= view
->lines
;
2825 for (digits
= 0; lines
; digits
++)
2828 /* Keep the displayed view in sync with line number scaling. */
2829 if (digits
!= view
->digits
) {
2830 view
->digits
= digits
;
2831 if (opt_line_number
|| view
== VIEW(REQ_VIEW_BLAME
))
2836 if (io_error(view
->pipe
)) {
2837 report("Failed to read: %s", io_strerror(view
->pipe
));
2838 end_update(view
, TRUE
);
2840 } else if (io_eof(view
->pipe
)) {
2842 end_update(view
, FALSE
);
2845 if (restore_view_position(view
))
2848 if (!view_is_displayed(view
))
2852 redraw_view_from(view
, 0);
2854 redraw_view_dirty(view
);
2856 /* Update the title _after_ the redraw so that if the redraw picks up a
2857 * commit reference in view->ref it'll be available here. */
2858 update_view_title(view
);
2859 update_display_cursor(view
);
2863 static struct line
*
2864 add_line_data(struct view
*view
, void *data
, enum line_type type
)
2868 if (!realloc_lines(view
, view
->lines
+ 1))
2871 line
= &view
->line
[view
->lines
++];
2872 memset(line
, 0, sizeof(*line
));
2880 static struct line
*
2881 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
2883 char *data
= text
? strdup(text
) : NULL
;
2885 return data
? add_line_data(view
, data
, type
) : NULL
;
2888 static struct line
*
2889 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
2891 char buf
[SIZEOF_STR
];
2894 va_start(args
, fmt
);
2895 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
2899 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
2907 OPEN_DEFAULT
= 0, /* Use default view switching. */
2908 OPEN_SPLIT
= 1, /* Split current view. */
2909 OPEN_BACKGROUNDED
= 2, /* Backgrounded. */
2910 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
2911 OPEN_NOMAXIMIZE
= 8, /* Do not maximize the current view. */
2912 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
2913 OPEN_PREPARED
= 32, /* Open already prepared command. */
2917 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
2919 bool backgrounded
= !!(flags
& OPEN_BACKGROUNDED
);
2920 bool split
= !!(flags
& OPEN_SPLIT
);
2921 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
2922 bool nomaximize
= !!(flags
& (OPEN_NOMAXIMIZE
| OPEN_REFRESH
));
2923 struct view
*view
= VIEW(request
);
2924 int nviews
= displayed_views();
2925 struct view
*base_view
= display
[0];
2927 if (view
== prev
&& nviews
== 1 && !reload
) {
2928 report("Already in %s view", view
->name
);
2932 if (view
->git_dir
&& !opt_git_dir
[0]) {
2933 report("The %s view is disabled in pager view", view
->name
);
2941 } else if (!nomaximize
) {
2942 /* Maximize the current view. */
2943 memset(display
, 0, sizeof(display
));
2945 display
[current_view
] = view
;
2948 /* Resize the view when switching between split- and full-screen,
2949 * or when switching between two different full-screen views. */
2950 if (nviews
!= displayed_views() ||
2951 (nviews
== 1 && base_view
!= display
[0]))
2954 if (view
->ops
->open
) {
2956 end_update(view
, TRUE
);
2957 if (!view
->ops
->open(view
)) {
2958 report("Failed to load %s view", view
->name
);
2961 restore_view_position(view
);
2963 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
2964 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
2965 report("Failed to load %s view", view
->name
);
2969 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
2970 /* Take the title line into account. */
2971 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
2973 /* Scroll the view that was split if the current line is
2974 * outside the new limited view. */
2975 do_scroll_view(prev
, lines
);
2978 if (prev
&& view
!= prev
) {
2979 if (split
&& !backgrounded
) {
2980 /* "Blur" the previous view. */
2981 update_view_title(prev
);
2984 view
->parent
= prev
;
2987 if (view
->pipe
&& view
->lines
== 0) {
2988 /* Clear the old view and let the incremental updating refill
2991 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
2993 } else if (view_is_displayed(view
)) {
2998 /* If the view is backgrounded the above calls to report()
2999 * won't redraw the view title. */
3001 update_view_title(view
);
3005 open_external_viewer(const char *argv
[], const char *dir
)
3007 def_prog_mode(); /* save current tty modes */
3008 endwin(); /* restore original tty modes */
3009 run_io_fg(argv
, dir
);
3010 fprintf(stderr
, "Press Enter to continue");
3013 redraw_display(TRUE
);
3017 open_mergetool(const char *file
)
3019 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3021 open_external_viewer(mergetool_argv
, opt_cdup
);
3025 open_editor(bool from_root
, const char *file
)
3027 const char *editor_argv
[] = { "vi", file
, NULL
};
3030 editor
= getenv("GIT_EDITOR");
3031 if (!editor
&& *opt_editor
)
3032 editor
= opt_editor
;
3034 editor
= getenv("VISUAL");
3036 editor
= getenv("EDITOR");
3040 editor_argv
[0] = editor
;
3041 open_external_viewer(editor_argv
, from_root
? opt_cdup
: NULL
);
3045 open_run_request(enum request request
)
3047 struct run_request
*req
= get_run_request(request
);
3048 const char *argv
[ARRAY_SIZE(req
->argv
)] = { NULL
};
3051 report("Unknown run request");
3055 if (format_argv(argv
, req
->argv
, FORMAT_ALL
))
3056 open_external_viewer(argv
, NULL
);
3061 * User request switch noodle
3065 view_driver(struct view
*view
, enum request request
)
3069 if (request
== REQ_NONE
) {
3074 if (request
> REQ_NONE
) {
3075 open_run_request(request
);
3076 /* FIXME: When all views can refresh always do this. */
3077 if (view
== VIEW(REQ_VIEW_STATUS
) ||
3078 view
== VIEW(REQ_VIEW_MAIN
) ||
3079 view
== VIEW(REQ_VIEW_LOG
) ||
3080 view
== VIEW(REQ_VIEW_STAGE
))
3081 request
= REQ_REFRESH
;
3086 if (view
&& view
->lines
) {
3087 request
= view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
3088 if (request
== REQ_NONE
)
3095 case REQ_MOVE_PAGE_UP
:
3096 case REQ_MOVE_PAGE_DOWN
:
3097 case REQ_MOVE_FIRST_LINE
:
3098 case REQ_MOVE_LAST_LINE
:
3099 move_view(view
, request
);
3102 case REQ_SCROLL_LINE_DOWN
:
3103 case REQ_SCROLL_LINE_UP
:
3104 case REQ_SCROLL_PAGE_DOWN
:
3105 case REQ_SCROLL_PAGE_UP
:
3106 scroll_view(view
, request
);
3109 case REQ_VIEW_BLAME
:
3111 report("No file chosen, press %s to open tree view",
3112 get_key(REQ_VIEW_TREE
));
3115 open_view(view
, request
, OPEN_DEFAULT
);
3120 report("No file chosen, press %s to open tree view",
3121 get_key(REQ_VIEW_TREE
));
3124 open_view(view
, request
, OPEN_DEFAULT
);
3127 case REQ_VIEW_PAGER
:
3128 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3129 report("No pager content, press %s to run command from prompt",
3130 get_key(REQ_PROMPT
));
3133 open_view(view
, request
, OPEN_DEFAULT
);
3136 case REQ_VIEW_STAGE
:
3137 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3138 report("No stage content, press %s to open the status view and choose file",
3139 get_key(REQ_VIEW_STATUS
));
3142 open_view(view
, request
, OPEN_DEFAULT
);
3145 case REQ_VIEW_STATUS
:
3146 if (opt_is_inside_work_tree
== FALSE
) {
3147 report("The status view requires a working tree");
3150 open_view(view
, request
, OPEN_DEFAULT
);
3158 open_view(view
, request
, OPEN_DEFAULT
);
3163 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3165 if ((view
== VIEW(REQ_VIEW_DIFF
) &&
3166 view
->parent
== VIEW(REQ_VIEW_MAIN
)) ||
3167 (view
== VIEW(REQ_VIEW_DIFF
) &&
3168 view
->parent
== VIEW(REQ_VIEW_BLAME
)) ||
3169 (view
== VIEW(REQ_VIEW_STAGE
) &&
3170 view
->parent
== VIEW(REQ_VIEW_STATUS
)) ||
3171 (view
== VIEW(REQ_VIEW_BLOB
) &&
3172 view
->parent
== VIEW(REQ_VIEW_TREE
))) {
3175 view
= view
->parent
;
3176 line
= view
->lineno
;
3177 move_view(view
, request
);
3178 if (view_is_displayed(view
))
3179 update_view_title(view
);
3180 if (line
!= view
->lineno
)
3181 view
->ops
->request(view
, REQ_ENTER
,
3182 &view
->line
[view
->lineno
]);
3185 move_view(view
, request
);
3191 int nviews
= displayed_views();
3192 int next_view
= (current_view
+ 1) % nviews
;
3194 if (next_view
== current_view
) {
3195 report("Only one view is displayed");
3199 current_view
= next_view
;
3200 /* Blur out the title of the previous view. */
3201 update_view_title(view
);
3206 report("Refreshing is not yet supported for the %s view", view
->name
);
3210 if (displayed_views() == 2)
3211 open_view(view
, VIEW_REQ(view
), OPEN_DEFAULT
);
3214 case REQ_TOGGLE_LINENO
:
3215 toggle_view_option(&opt_line_number
, "line numbers");
3218 case REQ_TOGGLE_DATE
:
3219 toggle_view_option(&opt_date
, "date display");
3222 case REQ_TOGGLE_AUTHOR
:
3223 toggle_view_option(&opt_author
, "author display");
3226 case REQ_TOGGLE_REV_GRAPH
:
3227 toggle_view_option(&opt_rev_graph
, "revision graph display");
3230 case REQ_TOGGLE_REFS
:
3231 toggle_view_option(&opt_show_refs
, "reference display");
3235 case REQ_SEARCH_BACK
:
3236 search_view(view
, request
);
3241 find_next(view
, request
);
3244 case REQ_STOP_LOADING
:
3245 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
3248 report("Stopped loading the %s view", view
->name
),
3249 end_update(view
, TRUE
);
3253 case REQ_SHOW_VERSION
:
3254 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3257 case REQ_SCREEN_REDRAW
:
3258 redraw_display(TRUE
);
3262 report("Nothing to edit");
3266 report("Nothing to enter");
3269 case REQ_VIEW_CLOSE
:
3270 /* XXX: Mark closed views by letting view->parent point to the
3271 * view itself. Parents to closed view should never be
3274 view
->parent
->parent
!= view
->parent
) {
3275 memset(display
, 0, sizeof(display
));
3277 display
[current_view
] = view
->parent
;
3278 view
->parent
= view
;
3280 redraw_display(FALSE
);
3289 report("Unknown key, press 'h' for help");
3298 * View backend utilities
3301 /* Parse author lines where the name may be empty:
3302 * author <email@address.tld> 1138474660 +0100
3305 parse_author_line(char *ident
, char *author
, size_t authorsize
, struct tm
*tm
)
3307 char *nameend
= strchr(ident
, '<');
3308 char *emailend
= strchr(ident
, '>');
3310 if (nameend
&& emailend
)
3311 *nameend
= *emailend
= 0;
3312 ident
= chomp_string(ident
);
3315 ident
= chomp_string(nameend
+ 1);
3320 string_ncopy_do(author
, authorsize
, ident
, strlen(ident
));
3322 /* Parse epoch and timezone */
3323 if (emailend
&& emailend
[1] == ' ') {
3324 char *secs
= emailend
+ 2;
3325 char *zone
= strchr(secs
, ' ');
3326 time_t time
= (time_t) atol(secs
);
3328 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700")) {
3332 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3333 tz
+= ('0' - zone
[2]) * 60 * 60;
3334 tz
+= ('0' - zone
[3]) * 60;
3335 tz
+= ('0' - zone
[4]) * 60;
3343 gmtime_r(&time
, tm
);
3347 static enum input_status
3348 select_commit_parent_handler(void *data
, char *buf
, int c
)
3350 size_t parents
= *(size_t *) data
;
3357 parent
= atoi(buf
) * 10;
3360 if (parent
> parents
)
3366 select_commit_parent(const char *id
, char rev
[SIZEOF_REV
])
3368 char buf
[SIZEOF_STR
* 4];
3369 const char *revlist_argv
[] = {
3370 "git", "rev-list", "-1", "--parents", id
, NULL
3374 if (!run_io_buf(revlist_argv
, buf
, sizeof(buf
)) ||
3375 !*chomp_string(buf
) ||
3376 (parents
= (strlen(buf
) / 40) - 1) < 0) {
3377 report("Failed to get parent information");
3380 } else if (parents
== 0) {
3381 report("The selected commit has no parents");
3386 char prompt
[SIZEOF_STR
];
3389 if (!string_format(prompt
, "Which parent? [1..%d] ", parents
))
3391 result
= prompt_input(prompt
, select_commit_parent_handler
, &parents
);
3394 parents
= atoi(result
);
3397 string_copy_rev(rev
, &buf
[41 * parents
]);
3406 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
3408 char *text
= line
->data
;
3410 if (opt_line_number
&& draw_lineno(view
, lineno
))
3413 draw_text(view
, line
->type
, text
, TRUE
);
3418 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
3420 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
3421 char refbuf
[SIZEOF_STR
];
3424 if (run_io_buf(describe_argv
, refbuf
, sizeof(refbuf
)))
3425 ref
= chomp_string(refbuf
);
3430 /* This is the only fatal call, since it can "corrupt" the buffer. */
3431 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
3438 add_pager_refs(struct view
*view
, struct line
*line
)
3440 char buf
[SIZEOF_STR
];
3441 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
3443 size_t bufpos
= 0, refpos
= 0;
3444 const char *sep
= "Refs: ";
3445 bool is_tag
= FALSE
;
3447 assert(line
->type
== LINE_COMMIT
);
3449 refs
= get_refs(commit_id
);
3451 if (view
== VIEW(REQ_VIEW_DIFF
))
3452 goto try_add_describe_ref
;
3457 struct ref
*ref
= refs
[refpos
];
3458 const char *fmt
= ref
->tag
? "%s[%s]" :
3459 ref
->remote
? "%s<%s>" : "%s%s";
3461 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
3466 } while (refs
[refpos
++]->next
);
3468 if (!is_tag
&& view
== VIEW(REQ_VIEW_DIFF
)) {
3469 try_add_describe_ref
:
3470 /* Add <tag>-g<commit_id> "fake" reference. */
3471 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
3478 add_line_text(view
, buf
, LINE_PP_REFS
);
3482 pager_read(struct view
*view
, char *data
)
3489 line
= add_line_text(view
, data
, get_line_type(data
));
3493 if (line
->type
== LINE_COMMIT
&&
3494 (view
== VIEW(REQ_VIEW_DIFF
) ||
3495 view
== VIEW(REQ_VIEW_LOG
)))
3496 add_pager_refs(view
, line
);
3502 pager_request(struct view
*view
, enum request request
, struct line
*line
)
3506 if (request
!= REQ_ENTER
)
3509 if (line
->type
== LINE_COMMIT
&&
3510 (view
== VIEW(REQ_VIEW_LOG
) ||
3511 view
== VIEW(REQ_VIEW_PAGER
))) {
3512 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
3516 /* Always scroll the view even if it was split. That way
3517 * you can use Enter to scroll through the log view and
3518 * split open each commit diff. */
3519 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
3521 /* FIXME: A minor workaround. Scrolling the view will call report("")
3522 * but if we are scrolling a non-current view this won't properly
3523 * update the view title. */
3525 update_view_title(view
);
3531 pager_grep(struct view
*view
, struct line
*line
)
3534 char *text
= line
->data
;
3539 if (regexec(view
->regex
, text
, 1, &pmatch
, 0) == REG_NOMATCH
)
3546 pager_select(struct view
*view
, struct line
*line
)
3548 if (line
->type
== LINE_COMMIT
) {
3549 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
3551 if (view
!= VIEW(REQ_VIEW_PAGER
))
3552 string_copy_rev(view
->ref
, text
);
3553 string_copy_rev(ref_commit
, text
);
3557 static struct view_ops pager_ops
= {
3568 static const char *log_argv
[SIZEOF_ARG
] = {
3569 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3573 log_request(struct view
*view
, enum request request
, struct line
*line
)
3578 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
3581 return pager_request(view
, request
, line
);
3585 static struct view_ops log_ops
= {
3596 static const char *diff_argv
[SIZEOF_ARG
] = {
3597 "git", "show", "--pretty=fuller", "--no-color", "--root",
3598 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3601 static struct view_ops diff_ops
= {
3617 help_open(struct view
*view
)
3619 char buf
[SIZEOF_STR
];
3623 if (view
->lines
> 0)
3626 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
3628 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
3631 if (req_info
[i
].request
== REQ_NONE
)
3634 if (!req_info
[i
].request
) {
3635 add_line_text(view
, "", LINE_DEFAULT
);
3636 add_line_text(view
, req_info
[i
].help
, LINE_DEFAULT
);
3640 key
= get_key(req_info
[i
].request
);
3642 key
= "(no key defined)";
3644 for (bufpos
= 0; bufpos
<= req_info
[i
].namelen
; bufpos
++) {
3645 buf
[bufpos
] = tolower(req_info
[i
].name
[bufpos
]);
3646 if (buf
[bufpos
] == '_')
3650 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s",
3651 key
, buf
, req_info
[i
].help
);
3655 add_line_text(view
, "", LINE_DEFAULT
);
3656 add_line_text(view
, "External commands:", LINE_DEFAULT
);
3659 for (i
= 0; i
< run_requests
; i
++) {
3660 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
3667 key
= get_key_name(req
->key
);
3669 key
= "(no key defined)";
3671 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
3672 if (!string_format_from(buf
, &bufpos
, "%s%s",
3673 argc
? " " : "", req
->argv
[argc
]))
3676 add_line_format(view
, LINE_DEFAULT
, " %-10s %-14s `%s`",
3677 keymap_table
[req
->keymap
].name
, key
, buf
);
3683 static struct view_ops help_ops
= {
3699 struct tree_stack_entry
{
3700 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
3701 unsigned long lineno
; /* Line number to restore */
3702 char *name
; /* Position of name in opt_path */
3705 /* The top of the path stack. */
3706 static struct tree_stack_entry
*tree_stack
= NULL
;
3707 unsigned long tree_lineno
= 0;
3710 pop_tree_stack_entry(void)
3712 struct tree_stack_entry
*entry
= tree_stack
;
3714 tree_lineno
= entry
->lineno
;
3716 tree_stack
= entry
->prev
;
3721 push_tree_stack_entry(const char *name
, unsigned long lineno
)
3723 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
3724 size_t pathlen
= strlen(opt_path
);
3729 entry
->prev
= tree_stack
;
3730 entry
->name
= opt_path
+ pathlen
;
3733 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
3734 pop_tree_stack_entry();
3738 /* Move the current line to the first tree entry. */
3740 entry
->lineno
= lineno
;
3743 /* Parse output from git-ls-tree(1):
3745 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3746 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3747 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3748 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3751 #define SIZEOF_TREE_ATTR \
3752 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3754 #define SIZEOF_TREE_MODE \
3755 STRING_SIZE("100644 ")
3757 #define TREE_ID_OFFSET \
3758 STRING_SIZE("100644 blob ")
3761 char id
[SIZEOF_REV
];
3763 struct tm time
; /* Date from the author ident. */
3764 char author
[75]; /* Author of the commit. */
3769 tree_path(struct line
*line
)
3771 return ((struct tree_entry
*) line
->data
)->name
;
3776 tree_compare_entry(struct line
*line1
, struct line
*line2
)
3778 if (line1
->type
!= line2
->type
)
3779 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
3780 return strcmp(tree_path(line1
), tree_path(line2
));
3783 static struct line
*
3784 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
3785 const char *mode
, const char *id
)
3787 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
3788 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
3790 if (!entry
|| !line
) {
3795 strncpy(entry
->name
, path
, strlen(path
));
3797 entry
->mode
= strtoul(mode
, NULL
, 8);
3799 string_copy_rev(entry
->id
, id
);
3805 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
3807 static char author_name
[SIZEOF_STR
];
3808 static struct tm author_time
;
3810 if (!text
&& *read_date
) {
3815 char *path
= *opt_path
? opt_path
: ".";
3816 /* Find next entry to process */
3817 const char *log_file
[] = {
3818 "git", "log", "--no-color", "--pretty=raw",
3819 "--cc", "--raw", view
->id
, "--", path
, NULL
3823 if (!run_io_rd(&io
, log_file
, FORMAT_NONE
)) {
3824 report("Failed to load tree data");
3828 done_io(view
->pipe
);
3833 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
3834 parse_author_line(text
+ STRING_SIZE("author "),
3835 author_name
, sizeof(author_name
), &author_time
);
3837 } else if (*text
== ':') {
3839 size_t annotated
= 1;
3842 pos
= strchr(text
, '\t');
3846 if (*opt_prefix
&& !strncmp(text
, opt_prefix
, strlen(opt_prefix
)))
3847 text
+= strlen(opt_prefix
);
3848 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
3849 text
+= strlen(opt_path
);
3850 pos
= strchr(text
, '/');
3854 for (i
= 1; i
< view
->lines
; i
++) {
3855 struct line
*line
= &view
->line
[i
];
3856 struct tree_entry
*entry
= line
->data
;
3858 annotated
+= !!*entry
->author
;
3859 if (*entry
->author
|| strcmp(entry
->name
, text
))
3862 string_copy(entry
->author
, author_name
);
3863 memcpy(&entry
->time
, &author_time
, sizeof(entry
->time
));
3868 if (annotated
== view
->lines
)
3869 kill_io(view
->pipe
);
3875 tree_read(struct view
*view
, char *text
)
3877 static bool read_date
= FALSE
;
3878 struct tree_entry
*data
;
3879 struct line
*entry
, *line
;
3880 enum line_type type
;
3881 size_t textlen
= text
? strlen(text
) : 0;
3882 char *path
= text
+ SIZEOF_TREE_ATTR
;
3884 if (read_date
|| !text
)
3885 return tree_read_date(view
, text
, &read_date
);
3887 if (textlen
<= SIZEOF_TREE_ATTR
)
3889 if (view
->lines
== 0 &&
3890 !tree_entry(view
, LINE_TREE_PARENT
, opt_path
, NULL
, NULL
))
3893 /* Strip the path part ... */
3895 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
3896 size_t striplen
= strlen(opt_path
);
3898 if (pathlen
> striplen
)
3899 memmove(path
, path
+ striplen
,
3900 pathlen
- striplen
+ 1);
3902 /* Insert "link" to parent directory. */
3903 if (view
->lines
== 1 &&
3904 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
3908 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
3909 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
3914 /* Skip "Directory ..." and ".." line. */
3915 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
3916 if (tree_compare_entry(line
, entry
) <= 0)
3919 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
3923 for (; line
<= entry
; line
++)
3924 line
->dirty
= line
->cleareol
= 1;
3928 if (tree_lineno
> view
->lineno
) {
3929 view
->lineno
= tree_lineno
;
3937 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
3939 struct tree_entry
*entry
= line
->data
;
3941 if (line
->type
== LINE_TREE_PARENT
) {
3942 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
3945 char mode
[11] = "-r--r--r--";
3947 if (S_ISDIR(entry
->mode
)) {
3948 mode
[3] = mode
[6] = mode
[9] = 'x';
3951 if (S_ISLNK(entry
->mode
))
3953 if (entry
->mode
& S_IWUSR
)
3955 if (entry
->mode
& S_IXUSR
)
3957 if (entry
->mode
& S_IXGRP
)
3959 if (entry
->mode
& S_IXOTH
)
3961 if (draw_field(view
, LINE_TREE_MODE
, mode
, 11, TRUE
))
3964 if (opt_author
&& draw_author(view
, entry
->author
))
3967 if (opt_date
&& draw_date(view
, *entry
->author
? &entry
->time
: NULL
))
3970 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
3978 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
3979 int fd
= mkstemp(file
);
3982 report("Failed to create temporary file");
3983 else if (!run_io_append(blob_ops
.argv
, FORMAT_ALL
, fd
))
3984 report("Failed to save blob data to file");
3986 open_editor(FALSE
, file
);
3992 tree_request(struct view
*view
, enum request request
, struct line
*line
)
3994 enum open_flags flags
;
3997 case REQ_VIEW_BLAME
:
3998 if (line
->type
!= LINE_TREE_FILE
) {
3999 report("Blame only supported for files");
4003 string_copy(opt_ref
, view
->vid
);
4007 if (line
->type
!= LINE_TREE_FILE
) {
4008 report("Edit only supported for files");
4009 } else if (!is_head_commit(view
->vid
)) {
4012 open_editor(TRUE
, opt_file
);
4018 /* quit view if at top of tree */
4019 return REQ_VIEW_CLOSE
;
4022 line
= &view
->line
[1];
4032 /* Cleanup the stack if the tree view is at a different tree. */
4033 while (!*opt_path
&& tree_stack
)
4034 pop_tree_stack_entry();
4036 switch (line
->type
) {
4038 /* Depending on whether it is a subdir or parent (updir?) link
4039 * mangle the path buffer. */
4040 if (line
== &view
->line
[1] && *opt_path
) {
4041 pop_tree_stack_entry();
4044 const char *basename
= tree_path(line
);
4046 push_tree_stack_entry(basename
, view
->lineno
);
4049 /* Trees and subtrees share the same ID, so they are not not
4050 * unique like blobs. */
4051 flags
= OPEN_RELOAD
;
4052 request
= REQ_VIEW_TREE
;
4055 case LINE_TREE_FILE
:
4056 flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4057 request
= REQ_VIEW_BLOB
;
4064 open_view(view
, request
, flags
);
4065 if (request
== REQ_VIEW_TREE
)
4066 view
->lineno
= tree_lineno
;
4072 tree_select(struct view
*view
, struct line
*line
)
4074 struct tree_entry
*entry
= line
->data
;
4076 if (line
->type
== LINE_TREE_FILE
) {
4077 string_copy_rev(ref_blob
, entry
->id
);
4078 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4080 } else if (line
->type
!= LINE_TREE_DIR
) {
4084 string_copy_rev(view
->ref
, entry
->id
);
4087 static const char *tree_argv
[SIZEOF_ARG
] = {
4088 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4091 static struct view_ops tree_ops
= {
4103 blob_read(struct view
*view
, char *line
)
4107 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4111 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4118 return pager_request(view
, request
, line
);
4122 static const char *blob_argv
[SIZEOF_ARG
] = {
4123 "git", "cat-file", "blob", "%(blob)", NULL
4126 static struct view_ops blob_ops
= {
4140 * Loading the blame view is a two phase job:
4142 * 1. File content is read either using opt_file from the
4143 * filesystem or using git-cat-file.
4144 * 2. Then blame information is incrementally added by
4145 * reading output from git-blame.
4148 static const char *blame_head_argv
[] = {
4149 "git", "blame", "--incremental", "--", "%(file)", NULL
4152 static const char *blame_ref_argv
[] = {
4153 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4156 static const char *blame_cat_file_argv
[] = {
4157 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4160 struct blame_commit
{
4161 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4162 char title
[128]; /* First line of the commit message. */
4163 char author
[75]; /* Author of the commit. */
4164 struct tm time
; /* Date from the author ident. */
4165 char filename
[128]; /* Name of file. */
4166 bool has_previous
; /* Was a "previous" line detected. */
4170 struct blame_commit
*commit
;
4175 blame_open(struct view
*view
)
4177 if (*opt_ref
|| !io_open(&view
->io
, opt_file
)) {
4178 if (!run_io_rd(&view
->io
, blame_cat_file_argv
, FORMAT_ALL
))
4182 setup_update(view
, opt_file
);
4183 string_format(view
->ref
, "%s ...", opt_file
);
4188 static struct blame_commit
*
4189 get_blame_commit(struct view
*view
, const char *id
)
4193 for (i
= 0; i
< view
->lines
; i
++) {
4194 struct blame
*blame
= view
->line
[i
].data
;
4199 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4200 return blame
->commit
;
4204 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4207 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4213 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4215 const char *pos
= *posref
;
4218 pos
= strchr(pos
+ 1, ' ');
4219 if (!pos
|| !isdigit(pos
[1]))
4221 *number
= atoi(pos
+ 1);
4222 if (*number
< min
|| *number
> max
)
4229 static struct blame_commit
*
4230 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4232 struct blame_commit
*commit
;
4233 struct blame
*blame
;
4234 const char *pos
= text
+ SIZEOF_REV
- 1;
4238 if (strlen(text
) <= SIZEOF_REV
|| *pos
!= ' ')
4241 if (!parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4242 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4245 commit
= get_blame_commit(view
, text
);
4251 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4254 blame
->commit
= commit
;
4262 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4265 const char **argv
= *opt_ref
? blame_ref_argv
: blame_head_argv
;
4268 if (view
->lines
== 0 && !view
->parent
)
4269 die("No blame exist for %s", view
->vid
);
4271 if (view
->lines
== 0 || !run_io_rd(&io
, argv
, FORMAT_ALL
)) {
4272 report("Failed to load blame data");
4276 done_io(view
->pipe
);
4282 size_t linelen
= strlen(line
);
4283 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
4285 blame
->commit
= NULL
;
4286 strncpy(blame
->text
, line
, linelen
);
4287 blame
->text
[linelen
] = 0;
4288 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
4293 match_blame_header(const char *name
, char **line
)
4295 size_t namelen
= strlen(name
);
4296 bool matched
= !strncmp(name
, *line
, namelen
);
4305 blame_read(struct view
*view
, char *line
)
4307 static struct blame_commit
*commit
= NULL
;
4308 static int blamed
= 0;
4309 static time_t author_time
;
4310 static bool read_file
= TRUE
;
4313 return blame_read_file(view
, line
, &read_file
);
4320 string_format(view
->ref
, "%s", view
->vid
);
4321 if (view_is_displayed(view
)) {
4322 update_view_title(view
);
4323 redraw_view_from(view
, 0);
4329 commit
= parse_blame_commit(view
, line
, &blamed
);
4330 string_format(view
->ref
, "%s %2d%%", view
->vid
,
4331 view
->lines
? blamed
* 100 / view
->lines
: 0);
4333 } else if (match_blame_header("author ", &line
)) {
4334 string_ncopy(commit
->author
, line
, strlen(line
));
4336 } else if (match_blame_header("author-time ", &line
)) {
4337 author_time
= (time_t) atol(line
);
4339 } else if (match_blame_header("author-tz ", &line
)) {
4342 tz
= ('0' - line
[1]) * 60 * 60 * 10;
4343 tz
+= ('0' - line
[2]) * 60 * 60;
4344 tz
+= ('0' - line
[3]) * 60;
4345 tz
+= ('0' - line
[4]) * 60;
4351 gmtime_r(&author_time
, &commit
->time
);
4353 } else if (match_blame_header("summary ", &line
)) {
4354 string_ncopy(commit
->title
, line
, strlen(line
));
4356 } else if (match_blame_header("previous ", &line
)) {
4357 commit
->has_previous
= TRUE
;
4359 } else if (match_blame_header("filename ", &line
)) {
4360 string_ncopy(commit
->filename
, line
, strlen(line
));
4368 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4370 struct blame
*blame
= line
->data
;
4371 struct tm
*time
= NULL
;
4372 const char *id
= NULL
, *author
= NULL
;
4374 if (blame
->commit
&& *blame
->commit
->filename
) {
4375 id
= blame
->commit
->id
;
4376 author
= blame
->commit
->author
;
4377 time
= &blame
->commit
->time
;
4380 if (opt_date
&& draw_date(view
, time
))
4383 if (opt_author
&& draw_author(view
, author
))
4386 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
4389 if (draw_lineno(view
, lineno
))
4392 draw_text(view
, LINE_DEFAULT
, blame
->text
, TRUE
);
4397 check_blame_commit(struct blame
*blame
)
4400 report("Commit data not loaded yet");
4401 else if (!strcmp(blame
->commit
->id
, NULL_ID
))
4402 report("No commit exist for the selected line");
4409 blame_request(struct view
*view
, enum request request
, struct line
*line
)
4411 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4412 struct blame
*blame
= line
->data
;
4415 case REQ_VIEW_BLAME
:
4416 if (check_blame_commit(blame
)) {
4417 string_copy(opt_ref
, blame
->commit
->id
);
4418 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
4423 if (check_blame_commit(blame
) &&
4424 select_commit_parent(blame
->commit
->id
, opt_ref
))
4425 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
4429 if (!blame
->commit
) {
4430 report("No commit loaded yet");
4434 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
4435 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
4438 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
4439 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
4440 const char *diff_index_argv
[] = {
4441 "git", "diff-index", "--root", "--patch-with-stat",
4442 "-C", "-M", "HEAD", "--", view
->vid
, NULL
4445 if (!blame
->commit
->has_previous
) {
4446 diff_index_argv
[1] = "diff";
4447 diff_index_argv
[2] = "--no-color";
4448 diff_index_argv
[6] = "--";
4449 diff_index_argv
[7] = "/dev/null";
4452 if (!prepare_update(diff
, diff_index_argv
, NULL
, FORMAT_DASH
)) {
4453 report("Failed to allocate diff command");
4456 flags
|= OPEN_PREPARED
;
4459 open_view(view
, REQ_VIEW_DIFF
, flags
);
4460 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4461 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
4472 blame_grep(struct view
*view
, struct line
*line
)
4474 struct blame
*blame
= line
->data
;
4475 struct blame_commit
*commit
= blame
->commit
;
4478 #define MATCH(text, on) \
4479 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4482 char buf
[DATE_COLS
+ 1];
4484 if (MATCH(commit
->title
, 1) ||
4485 MATCH(commit
->author
, opt_author
) ||
4486 MATCH(commit
->id
, opt_date
))
4489 if (strftime(buf
, sizeof(buf
), DATE_FORMAT
, &commit
->time
) &&
4494 return MATCH(blame
->text
, 1);
4500 blame_select(struct view
*view
, struct line
*line
)
4502 struct blame
*blame
= line
->data
;
4503 struct blame_commit
*commit
= blame
->commit
;
4508 if (!strcmp(commit
->id
, NULL_ID
))
4509 string_ncopy(ref_commit
, "HEAD", 4);
4511 string_copy_rev(ref_commit
, commit
->id
);
4514 static struct view_ops blame_ops
= {
4533 char rev
[SIZEOF_REV
];
4534 char name
[SIZEOF_STR
];
4538 char rev
[SIZEOF_REV
];
4539 char name
[SIZEOF_STR
];
4543 static char status_onbranch
[SIZEOF_STR
];
4544 static struct status stage_status
;
4545 static enum line_type stage_line_type
;
4546 static size_t stage_chunks
;
4547 static int *stage_chunk
;
4549 /* This should work even for the "On branch" line. */
4551 status_has_none(struct view
*view
, struct line
*line
)
4553 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
4556 /* Get fields from the diff line:
4557 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4560 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
4562 const char *old_mode
= buf
+ 1;
4563 const char *new_mode
= buf
+ 8;
4564 const char *old_rev
= buf
+ 15;
4565 const char *new_rev
= buf
+ 56;
4566 const char *status
= buf
+ 97;
4569 old_mode
[-1] != ':' ||
4570 new_mode
[-1] != ' ' ||
4571 old_rev
[-1] != ' ' ||
4572 new_rev
[-1] != ' ' ||
4576 file
->status
= *status
;
4578 string_copy_rev(file
->old
.rev
, old_rev
);
4579 string_copy_rev(file
->new.rev
, new_rev
);
4581 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
4582 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
4584 file
->old
.name
[0] = file
->new.name
[0] = 0;
4590 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
4592 struct status
*file
= NULL
;
4593 struct status
*unmerged
= NULL
;
4597 if (!run_io(&io
, argv
, NULL
, IO_RD
))
4600 add_line_data(view
, NULL
, type
);
4602 while ((buf
= io_get(&io
, 0, TRUE
))) {
4604 file
= calloc(1, sizeof(*file
));
4605 if (!file
|| !add_line_data(view
, file
, type
))
4609 /* Parse diff info part. */
4611 file
->status
= status
;
4613 string_copy(file
->old
.rev
, NULL_ID
);
4615 } else if (!file
->status
) {
4616 if (!status_get_diff(file
, buf
, strlen(buf
)))
4619 buf
= io_get(&io
, 0, TRUE
);
4623 /* Collapse all 'M'odified entries that follow a
4624 * associated 'U'nmerged entry. */
4625 if (file
->status
== 'U') {
4628 } else if (unmerged
) {
4629 int collapse
= !strcmp(buf
, unmerged
->new.name
);
4641 /* Grab the old name for rename/copy. */
4642 if (!*file
->old
.name
&&
4643 (file
->status
== 'R' || file
->status
== 'C')) {
4644 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
4646 buf
= io_get(&io
, 0, TRUE
);
4651 /* git-ls-files just delivers a NUL separated list of
4652 * file names similar to the second half of the
4653 * git-diff-* output. */
4654 string_ncopy(file
->new.name
, buf
, strlen(buf
));
4655 if (!*file
->old
.name
)
4656 string_copy(file
->old
.name
, file
->new.name
);
4660 if (io_error(&io
)) {
4666 if (!view
->line
[view
->lines
- 1].data
)
4667 add_line_data(view
, NULL
, LINE_STAT_NONE
);
4673 /* Don't show unmerged entries in the staged section. */
4674 static const char *status_diff_index_argv
[] = {
4675 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4676 "--cached", "-M", "HEAD", NULL
4679 static const char *status_diff_files_argv
[] = {
4680 "git", "diff-files", "-z", NULL
4683 static const char *status_list_other_argv
[] = {
4684 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4687 static const char *status_list_no_head_argv
[] = {
4688 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4691 static const char *update_index_argv
[] = {
4692 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4695 /* Restore the previous line number to stay in the context or select a
4696 * line with something that can be updated. */
4698 status_restore(struct view
*view
)
4700 if (view
->p_lineno
>= view
->lines
)
4701 view
->p_lineno
= view
->lines
- 1;
4702 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
4704 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
4707 /* If the above fails, always skip the "On branch" line. */
4708 if (view
->p_lineno
< view
->lines
)
4709 view
->lineno
= view
->p_lineno
;
4713 if (view
->lineno
< view
->offset
)
4714 view
->offset
= view
->lineno
;
4715 else if (view
->offset
+ view
->height
<= view
->lineno
)
4716 view
->offset
= view
->lineno
- view
->height
+ 1;
4718 view
->p_restore
= FALSE
;
4721 /* First parse staged info using git-diff-index(1), then parse unstaged
4722 * info using git-diff-files(1), and finally untracked files using
4723 * git-ls-files(1). */
4725 status_open(struct view
*view
)
4729 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
4730 if (is_initial_commit())
4731 string_copy(status_onbranch
, "Initial commit");
4732 else if (!*opt_head
)
4733 string_copy(status_onbranch
, "Not currently on any branch");
4734 else if (!string_format(status_onbranch
, "On branch %s", opt_head
))
4737 run_io_bg(update_index_argv
);
4739 if (is_initial_commit()) {
4740 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
4742 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
4746 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
4747 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
4750 /* Restore the exact position or use the specialized restore
4752 if (!view
->p_restore
)
4753 status_restore(view
);
4758 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4760 struct status
*status
= line
->data
;
4761 enum line_type type
;
4765 switch (line
->type
) {
4766 case LINE_STAT_STAGED
:
4767 type
= LINE_STAT_SECTION
;
4768 text
= "Changes to be committed:";
4771 case LINE_STAT_UNSTAGED
:
4772 type
= LINE_STAT_SECTION
;
4773 text
= "Changed but not updated:";
4776 case LINE_STAT_UNTRACKED
:
4777 type
= LINE_STAT_SECTION
;
4778 text
= "Untracked files:";
4781 case LINE_STAT_NONE
:
4782 type
= LINE_DEFAULT
;
4783 text
= " (no files)";
4786 case LINE_STAT_HEAD
:
4787 type
= LINE_STAT_HEAD
;
4788 text
= status_onbranch
;
4795 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
4797 buf
[0] = status
->status
;
4798 if (draw_text(view
, line
->type
, buf
, TRUE
))
4800 type
= LINE_DEFAULT
;
4801 text
= status
->new.name
;
4804 draw_text(view
, type
, text
, TRUE
);
4809 status_enter(struct view
*view
, struct line
*line
)
4811 struct status
*status
= line
->data
;
4812 const char *oldpath
= status
? status
->old
.name
: NULL
;
4813 /* Diffs for unmerged entries are empty when passing the new
4814 * path, so leave it empty. */
4815 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
4817 enum open_flags split
;
4818 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
4820 if (line
->type
== LINE_STAT_NONE
||
4821 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
4822 report("No file to diff");
4826 switch (line
->type
) {
4827 case LINE_STAT_STAGED
:
4828 if (is_initial_commit()) {
4829 const char *no_head_diff_argv
[] = {
4830 "git", "diff", "--no-color", "--patch-with-stat",
4831 "--", "/dev/null", newpath
, NULL
4834 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
, FORMAT_DASH
))
4837 const char *index_show_argv
[] = {
4838 "git", "diff-index", "--root", "--patch-with-stat",
4839 "-C", "-M", "--cached", "HEAD", "--",
4840 oldpath
, newpath
, NULL
4843 if (!prepare_update(stage
, index_show_argv
, opt_cdup
, FORMAT_DASH
))
4848 info
= "Staged changes to %s";
4850 info
= "Staged changes";
4853 case LINE_STAT_UNSTAGED
:
4855 const char *files_show_argv
[] = {
4856 "git", "diff-files", "--root", "--patch-with-stat",
4857 "-C", "-M", "--", oldpath
, newpath
, NULL
4860 if (!prepare_update(stage
, files_show_argv
, opt_cdup
, FORMAT_DASH
))
4863 info
= "Unstaged changes to %s";
4865 info
= "Unstaged changes";
4868 case LINE_STAT_UNTRACKED
:
4870 report("No file to show");
4874 if (!suffixcmp(status
->new.name
, -1, "/")) {
4875 report("Cannot display a directory");
4879 if (!prepare_update_file(stage
, newpath
))
4881 info
= "Untracked file %s";
4884 case LINE_STAT_HEAD
:
4888 die("line type %d not handled in switch", line
->type
);
4891 split
= view_is_displayed(view
) ? OPEN_SPLIT
: 0;
4892 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
4893 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
4895 stage_status
= *status
;
4897 memset(&stage_status
, 0, sizeof(stage_status
));
4900 stage_line_type
= line
->type
;
4902 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
4909 status_exists(struct status
*status
, enum line_type type
)
4911 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
4912 unsigned long lineno
;
4914 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
4915 struct line
*line
= &view
->line
[lineno
];
4916 struct status
*pos
= line
->data
;
4918 if (line
->type
!= type
)
4920 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
4921 select_view_line(view
, lineno
);
4924 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
4925 select_view_line(view
, lineno
);
4935 status_update_prepare(struct io
*io
, enum line_type type
)
4937 const char *staged_argv
[] = {
4938 "git", "update-index", "-z", "--index-info", NULL
4940 const char *others_argv
[] = {
4941 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4945 case LINE_STAT_STAGED
:
4946 return run_io(io
, staged_argv
, opt_cdup
, IO_WR
);
4948 case LINE_STAT_UNSTAGED
:
4949 return run_io(io
, others_argv
, opt_cdup
, IO_WR
);
4951 case LINE_STAT_UNTRACKED
:
4952 return run_io(io
, others_argv
, NULL
, IO_WR
);
4955 die("line type %d not handled in switch", type
);
4961 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
4963 char buf
[SIZEOF_STR
];
4967 case LINE_STAT_STAGED
:
4968 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
4971 status
->old
.name
, 0))
4975 case LINE_STAT_UNSTAGED
:
4976 case LINE_STAT_UNTRACKED
:
4977 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
4982 die("line type %d not handled in switch", type
);
4985 return io_write(io
, buf
, bufsize
);
4989 status_update_file(struct status
*status
, enum line_type type
)
4994 if (!status_update_prepare(&io
, type
))
4997 result
= status_update_write(&io
, status
, type
);
5003 status_update_files(struct view
*view
, struct line
*line
)
5007 struct line
*pos
= view
->line
+ view
->lines
;
5011 if (!status_update_prepare(&io
, line
->type
))
5014 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
5017 for (file
= 0, done
= 0; result
&& file
< files
; line
++, file
++) {
5018 int almost_done
= file
* 100 / files
;
5020 if (almost_done
> done
) {
5022 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
5024 update_view_title(view
);
5026 result
= status_update_write(&io
, line
->data
, line
->type
);
5034 status_update(struct view
*view
)
5036 struct line
*line
= &view
->line
[view
->lineno
];
5038 assert(view
->lines
);
5041 /* This should work even for the "On branch" line. */
5042 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
5043 report("Nothing to update");
5047 if (!status_update_files(view
, line
+ 1)) {
5048 report("Failed to update file status");
5052 } else if (!status_update_file(line
->data
, line
->type
)) {
5053 report("Failed to update file status");
5061 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
5063 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
5064 if (type
== LINE_STAT_STAGED
) {
5065 report("Cannot revert changes to staged files");
5066 } else if (type
== LINE_STAT_UNTRACKED
) {
5067 report("Cannot revert changes to untracked files");
5068 } else if (has_none
) {
5069 report("Nothing to revert");
5071 report("Cannot revert changes to multiple files");
5076 const char *checkout_argv
[] = {
5077 "git", "checkout", "--", status
->old
.name
, NULL
5080 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5082 return run_io_fg(checkout_argv
, opt_cdup
);
5087 status_request(struct view
*view
, enum request request
, struct line
*line
)
5089 struct status
*status
= line
->data
;
5092 case REQ_STATUS_UPDATE
:
5093 if (!status_update(view
))
5097 case REQ_STATUS_REVERT
:
5098 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
5102 case REQ_STATUS_MERGE
:
5103 if (!status
|| status
->status
!= 'U') {
5104 report("Merging only possible for files with unmerged status ('U').");
5107 open_mergetool(status
->new.name
);
5113 if (status
->status
== 'D') {
5114 report("File has been deleted.");
5118 open_editor(status
->status
!= '?', status
->new.name
);
5121 case REQ_VIEW_BLAME
:
5123 string_copy(opt_file
, status
->new.name
);
5129 /* After returning the status view has been split to
5130 * show the stage view. No further reloading is
5132 status_enter(view
, line
);
5136 /* Simply reload the view. */
5143 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
5149 status_select(struct view
*view
, struct line
*line
)
5151 struct status
*status
= line
->data
;
5152 char file
[SIZEOF_STR
] = "all files";
5156 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
5159 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
5162 switch (line
->type
) {
5163 case LINE_STAT_STAGED
:
5164 text
= "Press %s to unstage %s for commit";
5167 case LINE_STAT_UNSTAGED
:
5168 text
= "Press %s to stage %s for commit";
5171 case LINE_STAT_UNTRACKED
:
5172 text
= "Press %s to stage %s for addition";
5175 case LINE_STAT_HEAD
:
5176 case LINE_STAT_NONE
:
5177 text
= "Nothing to update";
5181 die("line type %d not handled in switch", line
->type
);
5184 if (status
&& status
->status
== 'U') {
5185 text
= "Press %s to resolve conflict in %s";
5186 key
= get_key(REQ_STATUS_MERGE
);
5189 key
= get_key(REQ_STATUS_UPDATE
);
5192 string_format(view
->ref
, text
, key
, file
);
5196 status_grep(struct view
*view
, struct line
*line
)
5198 struct status
*status
= line
->data
;
5199 enum { S_STATUS
, S_NAME
, S_END
} state
;
5206 for (state
= S_STATUS
; state
< S_END
; state
++) {
5210 case S_NAME
: text
= status
->new.name
; break;
5212 buf
[0] = status
->status
;
5220 if (regexec(view
->regex
, text
, 1, &pmatch
, 0) != REG_NOMATCH
)
5227 static struct view_ops status_ops
= {
5240 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
5242 while (line
< end
) {
5243 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
5244 !io_write(io
, "\n", 1))
5247 if (line
->type
== LINE_DIFF_CHUNK
||
5248 line
->type
== LINE_DIFF_HEADER
)
5255 static struct line
*
5256 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
5258 for (; view
->line
< line
; line
--)
5259 if (line
->type
== type
)
5266 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
5268 const char *apply_argv
[SIZEOF_ARG
] = {
5269 "git", "apply", "--whitespace=nowarn", NULL
5271 struct line
*diff_hdr
;
5275 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
5280 apply_argv
[argc
++] = "--cached";
5281 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
5282 apply_argv
[argc
++] = "-R";
5283 apply_argv
[argc
++] = "-";
5284 apply_argv
[argc
++] = NULL
;
5285 if (!run_io(&io
, apply_argv
, opt_cdup
, IO_WR
))
5288 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
5289 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
5293 run_io_bg(update_index_argv
);
5295 return chunk
? TRUE
: FALSE
;
5299 stage_update(struct view
*view
, struct line
*line
)
5301 struct line
*chunk
= NULL
;
5303 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
5304 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
5307 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
5308 report("Failed to apply chunk");
5312 } else if (!stage_status
.status
) {
5313 view
= VIEW(REQ_VIEW_STATUS
);
5315 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
5316 if (line
->type
== stage_line_type
)
5319 if (!status_update_files(view
, line
+ 1)) {
5320 report("Failed to update files");
5324 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
5325 report("Failed to update file");
5333 stage_revert(struct view
*view
, struct line
*line
)
5335 struct line
*chunk
= NULL
;
5337 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
5338 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
5341 if (!prompt_yesno("Are you sure you want to revert changes?"))
5344 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
5345 report("Failed to revert chunk");
5351 return status_revert(stage_status
.status
? &stage_status
: NULL
,
5352 stage_line_type
, FALSE
);
5358 stage_next(struct view
*view
, struct line
*line
)
5362 if (!stage_chunks
) {
5363 static size_t alloc
= 0;
5366 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
5367 if (line
->type
!= LINE_DIFF_CHUNK
)
5370 tmp
= realloc_items(stage_chunk
, &alloc
,
5371 stage_chunks
, sizeof(*tmp
));
5373 report("Allocation failure");
5378 stage_chunk
[stage_chunks
++] = line
- view
->line
;
5382 for (i
= 0; i
< stage_chunks
; i
++) {
5383 if (stage_chunk
[i
] > view
->lineno
) {
5384 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
5385 report("Chunk %d of %d", i
+ 1, stage_chunks
);
5390 report("No next chunk found");
5394 stage_request(struct view
*view
, enum request request
, struct line
*line
)
5397 case REQ_STATUS_UPDATE
:
5398 if (!stage_update(view
, line
))
5402 case REQ_STATUS_REVERT
:
5403 if (!stage_revert(view
, line
))
5407 case REQ_STAGE_NEXT
:
5408 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
5409 report("File is untracked; press %s to add",
5410 get_key(REQ_STATUS_UPDATE
));
5413 stage_next(view
, line
);
5417 if (!stage_status
.new.name
[0])
5419 if (stage_status
.status
== 'D') {
5420 report("File has been deleted.");
5424 open_editor(stage_status
.status
!= '?', stage_status
.new.name
);
5428 /* Reload everything ... */
5431 case REQ_VIEW_BLAME
:
5432 if (stage_status
.new.name
[0]) {
5433 string_copy(opt_file
, stage_status
.new.name
);
5439 return pager_request(view
, request
, line
);
5445 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
5446 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
| OPEN_NOMAXIMIZE
);
5448 /* Check whether the staged entry still exists, and close the
5449 * stage view if it doesn't. */
5450 if (!status_exists(&stage_status
, stage_line_type
)) {
5451 status_restore(VIEW(REQ_VIEW_STATUS
));
5452 return REQ_VIEW_CLOSE
;
5455 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
5456 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
5457 report("Cannot display a directory");
5461 if (!prepare_update_file(view
, stage_status
.new.name
)) {
5462 report("Failed to open file: %s", strerror(errno
));
5466 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
5471 static struct view_ops stage_ops
= {
5488 char id
[SIZEOF_REV
]; /* SHA1 ID. */
5489 char title
[128]; /* First line of the commit message. */
5490 char author
[75]; /* Author of the commit. */
5491 struct tm time
; /* Date from the author ident. */
5492 struct ref
**refs
; /* Repository references. */
5493 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
5494 size_t graph_size
; /* The width of the graph array. */
5495 bool has_parents
; /* Rewritten --parents seen. */
5498 /* Size of rev graph with no "padding" columns */
5499 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5502 struct rev_graph
*prev
, *next
, *parents
;
5503 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
5505 struct commit
*commit
;
5507 unsigned int boundary
:1;
5510 /* Parents of the commit being visualized. */
5511 static struct rev_graph graph_parents
[4];
5513 /* The current stack of revisions on the graph. */
5514 static struct rev_graph graph_stacks
[4] = {
5515 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
5516 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
5517 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
5518 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
5522 graph_parent_is_merge(struct rev_graph
*graph
)
5524 return graph
->parents
->size
> 1;
5528 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
5530 struct commit
*commit
= graph
->commit
;
5532 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
5533 commit
->graph
[commit
->graph_size
++] = symbol
;
5537 clear_rev_graph(struct rev_graph
*graph
)
5539 graph
->boundary
= 0;
5540 graph
->size
= graph
->pos
= 0;
5541 graph
->commit
= NULL
;
5542 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
5546 done_rev_graph(struct rev_graph
*graph
)
5548 if (graph_parent_is_merge(graph
) &&
5549 graph
->pos
< graph
->size
- 1 &&
5550 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
5551 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
5553 graph
->commit
->graph_size
= i
* 2;
5554 while (i
< graph
->next
->size
- 1) {
5555 append_to_rev_graph(graph
, ' ');
5556 append_to_rev_graph(graph
, '\\');
5561 clear_rev_graph(graph
);
5565 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
5569 /* "Collapse" duplicate parents lines.
5571 * FIXME: This needs to also update update the drawn graph but
5572 * for now it just serves as a method for pruning graph lines. */
5573 for (i
= 0; i
< graph
->size
; i
++)
5574 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
5577 if (graph
->size
< SIZEOF_REVITEMS
) {
5578 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
5583 get_rev_graph_symbol(struct rev_graph
*graph
)
5587 if (graph
->boundary
)
5588 symbol
= REVGRAPH_BOUND
;
5589 else if (graph
->parents
->size
== 0)
5590 symbol
= REVGRAPH_INIT
;
5591 else if (graph_parent_is_merge(graph
))
5592 symbol
= REVGRAPH_MERGE
;
5593 else if (graph
->pos
>= graph
->size
)
5594 symbol
= REVGRAPH_BRANCH
;
5596 symbol
= REVGRAPH_COMMIT
;
5602 draw_rev_graph(struct rev_graph
*graph
)
5605 chtype separator
, line
;
5607 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
5608 static struct rev_filler fillers
[] = {
5614 chtype symbol
= get_rev_graph_symbol(graph
);
5615 struct rev_filler
*filler
;
5618 if (opt_line_graphics
)
5619 fillers
[DEFAULT
].line
= line_graphics
[LINE_GRAPHIC_VLINE
];
5621 filler
= &fillers
[DEFAULT
];
5623 for (i
= 0; i
< graph
->pos
; i
++) {
5624 append_to_rev_graph(graph
, filler
->line
);
5625 if (graph_parent_is_merge(graph
->prev
) &&
5626 graph
->prev
->pos
== i
)
5627 filler
= &fillers
[RSHARP
];
5629 append_to_rev_graph(graph
, filler
->separator
);
5632 /* Place the symbol for this revision. */
5633 append_to_rev_graph(graph
, symbol
);
5635 if (graph
->prev
->size
> graph
->size
)
5636 filler
= &fillers
[RDIAG
];
5638 filler
= &fillers
[DEFAULT
];
5642 for (; i
< graph
->size
; i
++) {
5643 append_to_rev_graph(graph
, filler
->separator
);
5644 append_to_rev_graph(graph
, filler
->line
);
5645 if (graph_parent_is_merge(graph
->prev
) &&
5646 i
< graph
->prev
->pos
+ graph
->parents
->size
)
5647 filler
= &fillers
[RSHARP
];
5648 if (graph
->prev
->size
> graph
->size
)
5649 filler
= &fillers
[LDIAG
];
5652 if (graph
->prev
->size
> graph
->size
) {
5653 append_to_rev_graph(graph
, filler
->separator
);
5654 if (filler
->line
!= ' ')
5655 append_to_rev_graph(graph
, filler
->line
);
5659 /* Prepare the next rev graph */
5661 prepare_rev_graph(struct rev_graph
*graph
)
5665 /* First, traverse all lines of revisions up to the active one. */
5666 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
5667 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
5670 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
5673 /* Interleave the new revision parent(s). */
5674 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
5675 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
5677 /* Lastly, put any remaining revisions. */
5678 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
5679 push_rev_graph(graph
->next
, graph
->rev
[i
]);
5683 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
5685 /* If this is the finalizing update ... */
5687 prepare_rev_graph(graph
);
5689 /* Graph visualization needs a one rev look-ahead,
5690 * so the first update doesn't visualize anything. */
5691 if (!graph
->prev
->commit
)
5694 if (view
->lines
> 2)
5695 view
->line
[view
->lines
- 3].dirty
= 1;
5696 if (view
->lines
> 1)
5697 view
->line
[view
->lines
- 2].dirty
= 1;
5698 draw_rev_graph(graph
->prev
);
5699 done_rev_graph(graph
->prev
->prev
);
5707 static const char *main_argv
[SIZEOF_ARG
] = {
5708 "git", "log", "--no-color", "--pretty=raw", "--parents",
5709 "--topo-order", "%(head)", NULL
5713 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5715 struct commit
*commit
= line
->data
;
5717 if (!*commit
->author
)
5720 if (opt_date
&& draw_date(view
, &commit
->time
))
5723 if (opt_author
&& draw_author(view
, commit
->author
))
5726 if (opt_rev_graph
&& commit
->graph_size
&&
5727 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
5730 if (opt_show_refs
&& commit
->refs
) {
5734 enum line_type type
;
5736 if (commit
->refs
[i
]->head
)
5737 type
= LINE_MAIN_HEAD
;
5738 else if (commit
->refs
[i
]->ltag
)
5739 type
= LINE_MAIN_LOCAL_TAG
;
5740 else if (commit
->refs
[i
]->tag
)
5741 type
= LINE_MAIN_TAG
;
5742 else if (commit
->refs
[i
]->tracked
)
5743 type
= LINE_MAIN_TRACKED
;
5744 else if (commit
->refs
[i
]->remote
)
5745 type
= LINE_MAIN_REMOTE
;
5747 type
= LINE_MAIN_REF
;
5749 if (draw_text(view
, type
, "[", TRUE
) ||
5750 draw_text(view
, type
, commit
->refs
[i
]->name
, TRUE
) ||
5751 draw_text(view
, type
, "]", TRUE
))
5754 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
5756 } while (commit
->refs
[i
++]->next
);
5759 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
5763 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5765 main_read(struct view
*view
, char *line
)
5767 static struct rev_graph
*graph
= graph_stacks
;
5768 enum line_type type
;
5769 struct commit
*commit
;
5774 if (!view
->lines
&& !view
->parent
)
5775 die("No revisions match the given arguments.");
5776 if (view
->lines
> 0) {
5777 commit
= view
->line
[view
->lines
- 1].data
;
5778 view
->line
[view
->lines
- 1].dirty
= 1;
5779 if (!*commit
->author
) {
5782 graph
->commit
= NULL
;
5785 update_rev_graph(view
, graph
);
5787 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
5788 clear_rev_graph(&graph_stacks
[i
]);
5792 type
= get_line_type(line
);
5793 if (type
== LINE_COMMIT
) {
5794 commit
= calloc(1, sizeof(struct commit
));
5798 line
+= STRING_SIZE("commit ");
5800 graph
->boundary
= 1;
5804 string_copy_rev(commit
->id
, line
);
5805 commit
->refs
= get_refs(commit
->id
);
5806 graph
->commit
= commit
;
5807 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
5809 while ((line
= strchr(line
, ' '))) {
5811 push_rev_graph(graph
->parents
, line
);
5812 commit
->has_parents
= TRUE
;
5819 commit
= view
->line
[view
->lines
- 1].data
;
5823 if (commit
->has_parents
)
5825 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
5829 parse_author_line(line
+ STRING_SIZE("author "),
5830 commit
->author
, sizeof(commit
->author
),
5832 update_rev_graph(view
, graph
);
5833 graph
= graph
->next
;
5837 /* Fill in the commit title if it has not already been set. */
5838 if (commit
->title
[0])
5841 /* Require titles to start with a non-space character at the
5842 * offset used by git log. */
5843 if (strncmp(line
, " ", 4))
5846 /* Well, if the title starts with a whitespace character,
5847 * try to be forgiving. Otherwise we end up with no title. */
5848 while (isspace(*line
))
5852 /* FIXME: More graceful handling of titles; append "..." to
5853 * shortened titles, etc. */
5855 string_ncopy(commit
->title
, line
, strlen(line
));
5856 view
->line
[view
->lines
- 1].dirty
= 1;
5863 main_request(struct view
*view
, enum request request
, struct line
*line
)
5865 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
5869 open_view(view
, REQ_VIEW_DIFF
, flags
);
5873 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
5883 grep_refs(struct ref
**refs
, regex_t
*regex
)
5891 if (regexec(regex
, refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
5893 } while (refs
[i
++]->next
);
5899 main_grep(struct view
*view
, struct line
*line
)
5901 struct commit
*commit
= line
->data
;
5902 enum { S_TITLE
, S_AUTHOR
, S_DATE
, S_REFS
, S_END
} state
;
5903 char buf
[DATE_COLS
+ 1];
5906 for (state
= S_TITLE
; state
< S_END
; state
++) {
5910 case S_TITLE
: text
= commit
->title
; break;
5914 text
= commit
->author
;
5919 if (!strftime(buf
, sizeof(buf
), DATE_FORMAT
, &commit
->time
))
5926 if (grep_refs(commit
->refs
, view
->regex
) == TRUE
)
5933 if (regexec(view
->regex
, text
, 1, &pmatch
, 0) != REG_NOMATCH
)
5941 main_select(struct view
*view
, struct line
*line
)
5943 struct commit
*commit
= line
->data
;
5945 string_copy_rev(view
->ref
, commit
->id
);
5946 string_copy_rev(ref_commit
, view
->ref
);
5949 static struct view_ops main_ops
= {
5962 * Unicode / UTF-8 handling
5964 * NOTE: Much of the following code for dealing with unicode is derived from
5965 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5966 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5969 /* I've (over)annotated a lot of code snippets because I am not entirely
5970 * confident that the approach taken by this small UTF-8 interface is correct.
5974 unicode_width(unsigned long c
)
5977 (c
<= 0x115f /* Hangul Jamo */
5980 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
5982 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
5983 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
5984 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
5985 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
5986 || (c
>= 0xffe0 && c
<= 0xffe6)
5987 || (c
>= 0x20000 && c
<= 0x2fffd)
5988 || (c
>= 0x30000 && c
<= 0x3fffd)))
5992 return opt_tab_size
;
5997 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5998 * Illegal bytes are set one. */
5999 static const unsigned char utf8_bytes
[256] = {
6000 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,
6001 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,
6002 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,
6003 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,
6004 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,
6005 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,
6006 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,
6007 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,
6010 /* Decode UTF-8 multi-byte representation into a unicode character. */
6011 static inline unsigned long
6012 utf8_to_unicode(const char *string
, size_t length
)
6014 unsigned long unicode
;
6018 unicode
= string
[0];
6021 unicode
= (string
[0] & 0x1f) << 6;
6022 unicode
+= (string
[1] & 0x3f);
6025 unicode
= (string
[0] & 0x0f) << 12;
6026 unicode
+= ((string
[1] & 0x3f) << 6);
6027 unicode
+= (string
[2] & 0x3f);
6030 unicode
= (string
[0] & 0x0f) << 18;
6031 unicode
+= ((string
[1] & 0x3f) << 12);
6032 unicode
+= ((string
[2] & 0x3f) << 6);
6033 unicode
+= (string
[3] & 0x3f);
6036 unicode
= (string
[0] & 0x0f) << 24;
6037 unicode
+= ((string
[1] & 0x3f) << 18);
6038 unicode
+= ((string
[2] & 0x3f) << 12);
6039 unicode
+= ((string
[3] & 0x3f) << 6);
6040 unicode
+= (string
[4] & 0x3f);
6043 unicode
= (string
[0] & 0x01) << 30;
6044 unicode
+= ((string
[1] & 0x3f) << 24);
6045 unicode
+= ((string
[2] & 0x3f) << 18);
6046 unicode
+= ((string
[3] & 0x3f) << 12);
6047 unicode
+= ((string
[4] & 0x3f) << 6);
6048 unicode
+= (string
[5] & 0x3f);
6051 die("Invalid unicode length");
6054 /* Invalid characters could return the special 0xfffd value but NUL
6055 * should be just as good. */
6056 return unicode
> 0xffff ? 0 : unicode
;
6059 /* Calculates how much of string can be shown within the given maximum width
6060 * and sets trimmed parameter to non-zero value if all of string could not be
6061 * shown. If the reserve flag is TRUE, it will reserve at least one
6062 * trailing character, which can be useful when drawing a delimiter.
6064 * Returns the number of bytes to output from string to satisfy max_width. */
6066 utf8_length(const char *string
, int *width
, size_t max_width
, int *trimmed
, bool reserve
)
6068 const char *start
= string
;
6069 const char *end
= strchr(string
, '\0');
6070 unsigned char last_bytes
= 0;
6071 size_t last_ucwidth
= 0;
6076 while (string
< end
) {
6077 int c
= *(unsigned char *) string
;
6078 unsigned char bytes
= utf8_bytes
[c
];
6080 unsigned long unicode
;
6082 if (string
+ bytes
> end
)
6085 /* Change representation to figure out whether
6086 * it is a single- or double-width character. */
6088 unicode
= utf8_to_unicode(string
, bytes
);
6089 /* FIXME: Graceful handling of invalid unicode character. */
6093 ucwidth
= unicode_width(unicode
);
6095 if (*width
> max_width
) {
6098 if (reserve
&& *width
== max_width
) {
6099 string
-= last_bytes
;
6100 *width
-= last_ucwidth
;
6107 last_ucwidth
= ucwidth
;
6110 return string
- start
;
6118 /* Whether or not the curses interface has been initialized. */
6119 static bool cursed
= FALSE
;
6121 /* The status window is used for polling keystrokes. */
6122 static WINDOW
*status_win
;
6124 /* Reading from the prompt? */
6125 static bool input_mode
= FALSE
;
6127 static bool status_empty
= FALSE
;
6129 /* Update status and title window. */
6131 report(const char *msg
, ...)
6133 struct view
*view
= display
[current_view
];
6139 char buf
[SIZEOF_STR
];
6142 va_start(args
, msg
);
6143 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6144 buf
[sizeof(buf
) - 1] = 0;
6145 buf
[sizeof(buf
) - 2] = '.';
6146 buf
[sizeof(buf
) - 3] = '.';
6147 buf
[sizeof(buf
) - 4] = '.';
6153 if (!status_empty
|| *msg
) {
6156 va_start(args
, msg
);
6158 wmove(status_win
, 0, 0);
6160 vwprintw(status_win
, msg
, args
);
6161 status_empty
= FALSE
;
6163 status_empty
= TRUE
;
6165 wclrtoeol(status_win
);
6166 wnoutrefresh(status_win
);
6171 update_view_title(view
);
6172 update_display_cursor(view
);
6175 /* Controls when nodelay should be in effect when polling user input. */
6177 set_nonblocking_input(bool loading
)
6179 static unsigned int loading_views
;
6181 if ((loading
== FALSE
&& loading_views
-- == 1) ||
6182 (loading
== TRUE
&& loading_views
++ == 0))
6183 nodelay(status_win
, loading
);
6191 /* Initialize the curses library */
6192 if (isatty(STDIN_FILENO
)) {
6193 cursed
= !!initscr();
6196 /* Leave stdin and stdout alone when acting as a pager. */
6197 opt_tty
= fopen("/dev/tty", "r+");
6199 die("Failed to open /dev/tty");
6200 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
6204 die("Failed to initialize curses");
6206 nonl(); /* Tell curses not to do NL->CR/NL on output */
6207 cbreak(); /* Take input chars one at a time, no wait for \n */
6208 noecho(); /* Don't echo input */
6209 leaveok(stdscr
, TRUE
);
6214 getmaxyx(stdscr
, y
, x
);
6215 status_win
= newwin(1, 0, y
- 1, 0);
6217 die("Failed to create status window");
6219 /* Enable keyboard mapping */
6220 keypad(status_win
, TRUE
);
6221 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
6223 TABSIZE
= opt_tab_size
;
6224 if (opt_line_graphics
) {
6225 line_graphics
[LINE_GRAPHIC_VLINE
] = ACS_VLINE
;
6230 get_input(bool prompting
)
6239 foreach_view (view
, i
)
6242 /* Refresh, accept single keystroke of input */
6244 key
= wgetch(status_win
);
6246 /* wgetch() with nodelay() enabled returns ERR when
6247 * there's no input. */
6250 } else if (key
== KEY_RESIZE
) {
6253 getmaxyx(stdscr
, height
, width
);
6255 /* Resize the status view first so the cursor is
6256 * placed properly. */
6257 wresize(status_win
, 1, width
);
6258 mvwin(status_win
, height
- 1, 0);
6259 wnoutrefresh(status_win
);
6261 redraw_display(TRUE
);
6271 prompt_input(const char *prompt
, input_handler handler
, void *data
)
6273 enum input_status status
= INPUT_OK
;
6274 static char buf
[SIZEOF_STR
];
6279 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
6282 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
6283 wclrtoeol(status_win
);
6285 key
= get_input(TRUE
);
6290 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
6297 status
= INPUT_CANCEL
;
6301 status
= INPUT_CANCEL
;
6305 if (pos
>= sizeof(buf
)) {
6306 report("Input string too long");
6310 status
= handler(data
, buf
, key
);
6311 if (status
== INPUT_OK
)
6312 buf
[pos
++] = (char) key
;
6316 /* Clear the status window */
6317 status_empty
= FALSE
;
6320 if (status
== INPUT_CANCEL
)
6328 static enum input_status
6329 prompt_yesno_handler(void *data
, char *buf
, int c
)
6331 if (c
== 'y' || c
== 'Y')
6333 if (c
== 'n' || c
== 'N')
6334 return INPUT_CANCEL
;
6339 prompt_yesno(const char *prompt
)
6341 char prompt2
[SIZEOF_STR
];
6343 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
6346 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
6349 static enum input_status
6350 read_prompt_handler(void *data
, char *buf
, int c
)
6352 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
6356 read_prompt(const char *prompt
)
6358 return prompt_input(prompt
, read_prompt_handler
, NULL
);
6362 * Repository properties
6366 git_properties(const char **argv
, const char *separators
,
6367 int (*read_property
)(char *, size_t, char *, size_t))
6371 if (init_io_rd(&io
, argv
, NULL
, FORMAT_NONE
))
6372 return read_properties(&io
, separators
, read_property
);
6376 static struct ref
*refs
= NULL
;
6377 static size_t refs_alloc
= 0;
6378 static size_t refs_size
= 0;
6380 /* Id <-> ref store */
6381 static struct ref
***id_refs
= NULL
;
6382 static size_t id_refs_alloc
= 0;
6383 static size_t id_refs_size
= 0;
6386 compare_refs(const void *ref1_
, const void *ref2_
)
6388 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
6389 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
6391 if (ref1
->tag
!= ref2
->tag
)
6392 return ref2
->tag
- ref1
->tag
;
6393 if (ref1
->ltag
!= ref2
->ltag
)
6394 return ref2
->ltag
- ref2
->ltag
;
6395 if (ref1
->head
!= ref2
->head
)
6396 return ref2
->head
- ref1
->head
;
6397 if (ref1
->tracked
!= ref2
->tracked
)
6398 return ref2
->tracked
- ref1
->tracked
;
6399 if (ref1
->remote
!= ref2
->remote
)
6400 return ref2
->remote
- ref1
->remote
;
6401 return strcmp(ref1
->name
, ref2
->name
);
6404 static struct ref
**
6405 get_refs(const char *id
)
6407 struct ref
***tmp_id_refs
;
6408 struct ref
**ref_list
= NULL
;
6409 size_t ref_list_alloc
= 0;
6410 size_t ref_list_size
= 0;
6413 for (i
= 0; i
< id_refs_size
; i
++)
6414 if (!strcmp(id
, id_refs
[i
][0]->id
))
6417 tmp_id_refs
= realloc_items(id_refs
, &id_refs_alloc
, id_refs_size
+ 1,
6422 id_refs
= tmp_id_refs
;
6424 for (i
= 0; i
< refs_size
; i
++) {
6427 if (strcmp(id
, refs
[i
].id
))
6430 tmp
= realloc_items(ref_list
, &ref_list_alloc
,
6431 ref_list_size
+ 1, sizeof(*ref_list
));
6439 ref_list
[ref_list_size
] = &refs
[i
];
6440 /* XXX: The properties of the commit chains ensures that we can
6441 * safely modify the shared ref. The repo references will
6442 * always be similar for the same id. */
6443 ref_list
[ref_list_size
]->next
= 1;
6449 qsort(ref_list
, ref_list_size
, sizeof(*ref_list
), compare_refs
);
6450 ref_list
[ref_list_size
- 1]->next
= 0;
6451 id_refs
[id_refs_size
++] = ref_list
;
6458 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
6463 bool remote
= FALSE
;
6464 bool tracked
= FALSE
;
6465 bool check_replace
= FALSE
;
6468 if (!prefixcmp(name
, "refs/tags/")) {
6469 if (!suffixcmp(name
, namelen
, "^{}")) {
6472 if (refs_size
> 0 && refs
[refs_size
- 1].ltag
== TRUE
)
6473 check_replace
= TRUE
;
6479 namelen
-= STRING_SIZE("refs/tags/");
6480 name
+= STRING_SIZE("refs/tags/");
6482 } else if (!prefixcmp(name
, "refs/remotes/")) {
6484 namelen
-= STRING_SIZE("refs/remotes/");
6485 name
+= STRING_SIZE("refs/remotes/");
6486 tracked
= !strcmp(opt_remote
, name
);
6488 } else if (!prefixcmp(name
, "refs/heads/")) {
6489 namelen
-= STRING_SIZE("refs/heads/");
6490 name
+= STRING_SIZE("refs/heads/");
6491 head
= !strncmp(opt_head
, name
, namelen
);
6493 } else if (!strcmp(name
, "HEAD")) {
6494 string_ncopy(opt_head_rev
, id
, idlen
);
6498 if (check_replace
&& !strcmp(name
, refs
[refs_size
- 1].name
)) {
6499 /* it's an annotated tag, replace the previous sha1 with the
6500 * resolved commit id; relies on the fact git-ls-remote lists
6501 * the commit id of an annotated tag right before the commit id
6503 refs
[refs_size
- 1].ltag
= ltag
;
6504 string_copy_rev(refs
[refs_size
- 1].id
, id
);
6508 refs
= realloc_items(refs
, &refs_alloc
, refs_size
+ 1, sizeof(*refs
));
6512 ref
= &refs
[refs_size
++];
6513 ref
->name
= malloc(namelen
+ 1);
6517 strncpy(ref
->name
, name
, namelen
);
6518 ref
->name
[namelen
] = 0;
6522 ref
->remote
= remote
;
6523 ref
->tracked
= tracked
;
6524 string_copy_rev(ref
->id
, id
);
6532 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
6533 "git", "ls-remote", ".", NULL
6535 static bool init
= FALSE
;
6538 argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE");
6545 while (refs_size
> 0)
6546 free(refs
[--refs_size
].name
);
6547 while (id_refs_size
> 0)
6548 free(id_refs
[--id_refs_size
]);
6550 return git_properties(ls_remote_argv
, "\t", read_ref
);
6554 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
6556 if (!strcmp(name
, "i18n.commitencoding"))
6557 string_ncopy(opt_encoding
, value
, valuelen
);
6559 if (!strcmp(name
, "core.editor"))
6560 string_ncopy(opt_editor
, value
, valuelen
);
6562 /* branch.<head>.remote */
6564 !strncmp(name
, "branch.", 7) &&
6565 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)) &&
6566 !strcmp(name
+ 7 + strlen(opt_head
), ".remote"))
6567 string_ncopy(opt_remote
, value
, valuelen
);
6569 if (*opt_head
&& *opt_remote
&&
6570 !strncmp(name
, "branch.", 7) &&
6571 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)) &&
6572 !strcmp(name
+ 7 + strlen(opt_head
), ".merge")) {
6573 size_t from
= strlen(opt_remote
);
6575 if (!prefixcmp(value
, "refs/heads/")) {
6576 value
+= STRING_SIZE("refs/heads/");
6577 valuelen
-= STRING_SIZE("refs/heads/");
6580 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
6588 load_git_config(void)
6590 const char *config_list_argv
[] = { "git", GIT_CONFIG
, "--list", NULL
};
6592 return git_properties(config_list_argv
, "=", read_repo_config_option
);
6596 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
6598 if (!opt_git_dir
[0]) {
6599 string_ncopy(opt_git_dir
, name
, namelen
);
6601 } else if (opt_is_inside_work_tree
== -1) {
6602 /* This can be 3 different values depending on the
6603 * version of git being used. If git-rev-parse does not
6604 * understand --is-inside-work-tree it will simply echo
6605 * the option else either "true" or "false" is printed.
6606 * Default to true for the unknown case. */
6607 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
6609 } else if (*name
== '.') {
6610 string_ncopy(opt_cdup
, name
, namelen
);
6613 string_ncopy(opt_prefix
, name
, namelen
);
6620 load_repo_info(void)
6622 const char *head_argv
[] = {
6623 "git", "symbolic-ref", "HEAD", NULL
6625 const char *rev_parse_argv
[] = {
6626 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6627 "--show-cdup", "--show-prefix", NULL
6630 if (run_io_buf(head_argv
, opt_head
, sizeof(opt_head
))) {
6631 chomp_string(opt_head
);
6632 if (!prefixcmp(opt_head
, "refs/heads/")) {
6633 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
6635 memmove(opt_head
, offset
, strlen(offset
) + 1);
6639 return git_properties(rev_parse_argv
, "=", read_repo_info
);
6643 read_properties(struct io
*io
, const char *separators
,
6644 int (*read_property
)(char *, size_t, char *, size_t))
6652 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
6657 name
= chomp_string(name
);
6658 namelen
= strcspn(name
, separators
);
6660 if (name
[namelen
]) {
6662 value
= chomp_string(name
+ namelen
+ 1);
6663 valuelen
= strlen(value
);
6670 state
= read_property(name
, namelen
, value
, valuelen
);
6673 if (state
!= ERR
&& io_error(io
))
6685 static void __NORETURN
6688 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6694 static void __NORETURN
6695 die(const char *err
, ...)
6701 va_start(args
, err
);
6702 fputs("tig: ", stderr
);
6703 vfprintf(stderr
, err
, args
);
6704 fputs("\n", stderr
);
6711 warn(const char *msg
, ...)
6715 va_start(args
, msg
);
6716 fputs("tig warning: ", stderr
);
6717 vfprintf(stderr
, msg
, args
);
6718 fputs("\n", stderr
);
6723 main(int argc
, const char *argv
[])
6725 const char **run_argv
= NULL
;
6727 enum request request
;
6730 signal(SIGINT
, quit
);
6732 if (setlocale(LC_ALL
, "")) {
6733 char *codeset
= nl_langinfo(CODESET
);
6735 string_ncopy(opt_codeset
, codeset
, strlen(codeset
));
6738 if (load_repo_info() == ERR
)
6739 die("Failed to load repo info.");
6741 if (load_options() == ERR
)
6742 die("Failed to load user config.");
6744 if (load_git_config() == ERR
)
6745 die("Failed to load repo config.");
6747 request
= parse_options(argc
, argv
, &run_argv
);
6748 if (request
== REQ_NONE
)
6751 /* Require a git repository unless when running in pager mode. */
6752 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
6753 die("Not a git repository");
6755 if (*opt_encoding
&& strcasecmp(opt_encoding
, "UTF-8"))
6758 if (*opt_codeset
&& strcmp(opt_codeset
, opt_encoding
)) {
6759 opt_iconv
= iconv_open(opt_codeset
, opt_encoding
);
6760 if (opt_iconv
== ICONV_NONE
)
6761 die("Failed to initialize character set conversion");
6764 if (load_refs() == ERR
)
6765 die("Failed to load refs.");
6767 foreach_view (view
, i
)
6768 argv_from_env(view
->ops
->argv
, view
->cmd_env
);
6772 if (request
== REQ_VIEW_PAGER
|| run_argv
) {
6773 if (request
== REQ_VIEW_PAGER
)
6774 io_open(&VIEW(request
)->io
, "");
6775 else if (!prepare_update(VIEW(request
), run_argv
, NULL
, FORMAT_NONE
))
6776 die("Failed to format arguments");
6777 open_view(NULL
, request
, OPEN_PREPARED
);
6781 while (view_driver(display
[current_view
], request
)) {
6782 int key
= get_input(FALSE
);
6784 view
= display
[current_view
];
6785 request
= get_keybinding(view
->keymap
, key
);
6787 /* Some low-level request handling. This keeps access to
6788 * status_win restricted. */
6792 char *cmd
= read_prompt(":");
6795 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
6796 const char *argv
[SIZEOF_ARG
] = { "git" };
6799 /* When running random commands, initially show the
6800 * command in the title. However, it maybe later be
6801 * overwritten if a commit line is selected. */
6802 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
6804 if (!argv_from_string(argv
, &argc
, cmd
)) {
6805 report("Too many arguments");
6806 } else if (!prepare_update(next
, argv
, NULL
, FORMAT_DASH
)) {
6807 report("Failed to format command");
6809 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
6817 case REQ_SEARCH_BACK
:
6819 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
6820 char *search
= read_prompt(prompt
);
6823 string_ncopy(opt_search
, search
, strlen(search
));