#endif
static void __NORETURN die(const char *err, ...);
+static void warn(const char *msg, ...);
static void report(const char *msg, ...);
static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
static void set_nonblocking_input(bool loading);
static const char usage[] =
"tig " TIG_VERSION " (" __DATE__ ")\n"
"\n"
-"Usage: tig [options]\n"
-" or: tig [options] [--] [git log options]\n"
-" or: tig [options] log [git log options]\n"
-" or: tig [options] diff [git diff options]\n"
-" or: tig [options] show [git show options]\n"
-" or: tig [options] < [git command output]\n"
+"Usage: tig [options] [revs] [--] [paths]\n"
+" or: tig show [options] [revs] [--] [paths]\n"
+" or: tig status\n"
+" or: tig < [git command output]\n"
"\n"
"Options:\n"
-" -l Start up in log view\n"
-" -d Start up in diff view\n"
-" -S Start up in status view\n"
-" -n[I], --line-number[=I] Show line numbers with given interval\n"
-" -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
-" -- Mark end of tig options\n"
-" -v, --version Show version and exit\n"
-" -h, --help Show help message and exit\n";
+" -v, --version Show version and exit\n"
+" -h, --help Show help message and exit\n";
/* Option and state variables. */
static bool opt_line_number = FALSE;
static bool
parse_options(int argc, char *argv[])
{
+ char *altargv[1024];
+ int altargc = 0;
+ char *subcommand = NULL;
int i;
for (i = 1; i < argc; i++) {
char *opt = argv[i];
if (!strcmp(opt, "log") ||
- !strcmp(opt, "diff") ||
- !strcmp(opt, "show")) {
+ !strcmp(opt, "diff")) {
+ subcommand = opt;
opt_request = opt[0] == 'l'
? REQ_VIEW_LOG : REQ_VIEW_DIFF;
+ warn("`tig %s' has been deprecated", opt);
+ break;
+ }
+
+ if (!strcmp(opt, "show")) {
+ subcommand = opt;
+ opt_request = REQ_VIEW_DIFF;
+ break;
+ }
+
+ if (!strcmp(opt, "status")) {
+ subcommand = opt;
+ opt_request = REQ_VIEW_STATUS;
break;
}
}
if (!strcmp(opt, "-S")) {
+ warn("`%s' has been deprecated; use `tig status' instead", opt);
opt_request = REQ_VIEW_STATUS;
continue;
}
if (!strcmp(opt, "-l")) {
opt_request = REQ_VIEW_LOG;
- continue;
- }
-
- if (!strcmp(opt, "-d")) {
+ } else if (!strcmp(opt, "-d")) {
opt_request = REQ_VIEW_DIFF;
- continue;
- }
-
- if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
+ } else if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
opt_line_number = TRUE;
- continue;
- }
-
- if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
+ } else if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
opt_tab_size = MIN(opt_tab_size, TABSIZE);
+ } else {
+ if (altargc >= ARRAY_SIZE(altargv))
+ die("maximum number of arguments exceeded");
+ altargv[altargc++] = opt;
continue;
}
- die("unknown option '%s'\n\n%s", opt, usage);
+ warn("`%s' has been deprecated", opt);
}
+ /* Check that no 'alt' arguments occured before a subcommand. */
+ if (subcommand && i < argc && altargc > 0)
+ die("unknown arguments before `%s'", argv[i]);
+
if (!isatty(STDIN_FILENO)) {
opt_request = REQ_VIEW_PAGER;
opt_pipe = stdin;
- } else if (i < argc) {
+ } else if (opt_request == REQ_VIEW_STATUS) {
+ if (argc - i > 1)
+ warn("ignoring arguments after `%s'", argv[i]);
+
+ } else if (i < argc || altargc > 0) {
+ int alti = 0;
size_t buf_size;
if (opt_request == REQ_VIEW_MAIN)
string_copy(opt_cmd, "git");
buf_size = strlen(opt_cmd);
+ while (buf_size < sizeof(opt_cmd) && alti < altargc) {
+ opt_cmd[buf_size++] = ' ';
+ buf_size = sq_quote(opt_cmd, buf_size, altargv[alti++]);
+ }
+
while (buf_size < sizeof(opt_cmd) && i < argc) {
opt_cmd[buf_size++] = ' ';
buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
return OK;
}
-static int
-load_options(void)
+static void
+load_option_file(const char *path)
{
- char *home = getenv("HOME");
- char buf[SIZEOF_STR];
FILE *file;
+ /* It's ok that the file doesn't exist. */
+ file = fopen(path, "r");
+ if (!file)
+ return;
+
config_lineno = 0;
config_errors = FALSE;
- add_builtin_run_requests();
+ if (read_properties(file, " \t", read_option) == ERR ||
+ config_errors == TRUE)
+ fprintf(stderr, "Errors while loading %s.\n", path);
+}
- if (!home || !string_format(buf, "%s/.tigrc", home))
- return ERR;
+static int
+load_options(void)
+{
+ char *home = getenv("HOME");
+ char *tigrc_user = getenv("TIGRC_USER");
+ char *tigrc_system = getenv("TIGRC_SYSTEM");
+ char buf[SIZEOF_STR];
- /* It's ok that the file doesn't exist. */
- file = fopen(buf, "r");
- if (!file)
- return OK;
+ add_builtin_run_requests();
- if (read_properties(file, " \t", read_option) == ERR ||
- config_errors == TRUE)
- fprintf(stderr, "Errors while loading %s.\n", buf);
+ if (!tigrc_system) {
+ if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
+ return ERR;
+ tigrc_system = buf;
+ }
+ load_option_file(tigrc_system);
+
+ if (!tigrc_user) {
+ if (!home || !string_format(buf, "%s/.tigrc", home))
+ return ERR;
+ tigrc_user = buf;
+ }
+ load_option_file(tigrc_user);
return OK;
}
struct {
mode_t mode;
char rev[SIZEOF_REV];
+ char name[SIZEOF_STR];
} old;
struct {
mode_t mode;
char rev[SIZEOF_REV];
+ char name[SIZEOF_STR];
} new;
- char name[SIZEOF_STR];
};
static struct status stage_status;
char *new_rev = buf + 56;
char *status = buf + 97;
- if (bufsize != 99 ||
+ if (bufsize < 99 ||
old_mode[-1] != ':' ||
new_mode[-1] != ' ' ||
old_rev[-1] != ' ' ||
file->old.mode = strtoul(old_mode, NULL, 8);
file->new.mode = strtoul(new_mode, NULL, 8);
- file->name[0] = 0;
+ file->old.name[0] = file->new.name[0] = 0;
return TRUE;
}
unmerged = file;
} else if (unmerged) {
- int collapse = !strcmp(buf, unmerged->name);
+ int collapse = !strcmp(buf, unmerged->new.name);
unmerged = NULL;
if (collapse) {
}
}
+ /* Grab the old name for rename/copy. */
+ if (!*file->old.name &&
+ (file->status == 'R' || file->status == 'C')) {
+ sepsize = sep - buf + 1;
+ string_ncopy(file->old.name, buf, sepsize);
+ bufsize -= sepsize;
+ memmove(buf, sep + 1, bufsize);
+
+ sep = memchr(buf, 0, bufsize);
+ if (!sep)
+ break;
+ sepsize = sep - buf + 1;
+ }
+
/* git-ls-files just delivers a NUL separated
* list of file names similar to the second half
* of the git-diff-* output. */
- string_ncopy(file->name, buf, sepsize);
+ string_ncopy(file->new.name, buf, sepsize);
+ if (!*file->old.name)
+ string_copy(file->old.name, file->new.name);
bufsize -= sepsize;
memmove(buf, sep + 1, bufsize);
file = NULL;
}
/* Don't show unmerged entries in the staged section. */
-#define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached HEAD"
-#define STATUS_DIFF_FILES_CMD "git diff-files -z"
+#define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
+#define STATUS_DIFF_FILES_CMD "git update-index -q --refresh && git diff-files -z"
#define STATUS_LIST_OTHER_CMD \
"git ls-files -z --others --exclude-per-directory=.gitignore"
#define STATUS_DIFF_INDEX_SHOW_CMD \
- "git diff-index --root --patch-with-stat --find-copies-harder -C --cached HEAD -- %s 2>/dev/null"
+ "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
#define STATUS_DIFF_FILES_SHOW_CMD \
- "git diff-files --root --patch-with-stat --find-copies-harder -C -- %s 2>/dev/null"
+ "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
/* First parse staged info using git-diff-index(1), then parse unstaged
* info using git-diff-files(1), and finally untracked files using
if (!selected)
wattrset(view->win, A_NORMAL);
wmove(view->win, lineno, 4);
- waddstr(view->win, status->name);
+ waddstr(view->win, status->new.name);
return TRUE;
}
status_enter(struct view *view, struct line *line)
{
struct status *status = line->data;
- char path[SIZEOF_STR] = "";
+ char oldpath[SIZEOF_STR] = "";
+ char newpath[SIZEOF_STR] = "";
char *info;
size_t cmdsize = 0;
return REQ_NONE;
}
- if (status && sq_quote(path, 0, status->name) >= sizeof(path))
- return REQ_QUIT;
+ if (status) {
+ if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
+ return REQ_QUIT;
+ /* Diffs for unmerged entries are empty when pasing the
+ * new path, so leave it empty. */
+ if (status->status != 'U' &&
+ sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
+ return REQ_QUIT;
+ }
if (opt_cdup[0] &&
line->type != LINE_STAT_UNTRACKED &&
switch (line->type) {
case LINE_STAT_STAGED:
if (!string_format_from(opt_cmd, &cmdsize,
- STATUS_DIFF_INDEX_SHOW_CMD, path))
+ STATUS_DIFF_INDEX_SHOW_CMD, oldpath, newpath))
return REQ_QUIT;
if (status)
info = "Staged changes to %s";
case LINE_STAT_UNSTAGED:
if (!string_format_from(opt_cmd, &cmdsize,
- STATUS_DIFF_FILES_SHOW_CMD, path))
+ STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
return REQ_QUIT;
if (status)
info = "Unstaged changes to %s";
return REQ_NONE;
}
- opt_pipe = fopen(status->name, "r");
+ opt_pipe = fopen(status->new.name, "r");
info = "Untracked file %s";
break;
}
stage_line_type = line->type;
- string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
+ string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
}
return REQ_NONE;
if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
status->old.mode,
status->old.rev,
- status->name, 0))
+ status->old.name, 0))
return FALSE;
string_add(cmd, cmdsize, "git update-index -z --index-info");
case LINE_STAT_UNSTAGED:
case LINE_STAT_UNTRACKED:
- if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
+ if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
return FALSE;
string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
report("Merging only possible for files with unmerged status ('U').");
return REQ_NONE;
}
- open_mergetool(status->name);
+ open_mergetool(status->new.name);
break;
case REQ_EDIT:
if (!status)
return request;
- open_editor(status->status != '?', status->name);
+ open_editor(status->status != '?', status->new.name);
break;
case REQ_ENTER:
char *text;
char *key;
- if (status && !string_format(file, "'%s'", status->name))
+ if (status && !string_format(file, "'%s'", status->new.name))
return;
if (!status && line[1].type == LINE_STAT_NONE)
char *text;
switch (state) {
- case S_NAME: text = status->name; break;
+ case S_NAME: text = status->new.name; break;
case S_STATUS:
buf[0] = status->status;
text = buf;
break;
case REQ_EDIT:
- if (!stage_status.name[0])
+ if (!stage_status.new.name[0])
return request;
- open_editor(stage_status.status != '?', stage_status.name);
+ open_editor(stage_status.status != '?', stage_status.new.name);
break;
case REQ_ENTER:
exit(1);
}
+static void
+warn(const char *msg, ...)
+{
+ va_list args;
+
+ va_start(args, msg);
+ fputs("tig warning: ", stderr);
+ vfprintf(stderr, msg, args);
+ fputs("\n", stderr);
+ va_end(args);
+}
+
int
main(int argc, char *argv[])
{