index 47b0d4faa278cf507dd697893db12128236aae7d..222b3698de6bcc60395ab74519b4c9048e2e5ed6 100644 (file)
--- a/tig.c
+++ b/tig.c
#include <sys/stat.h>
#include <sys/select.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <unistd.h>
+#include <sys/time.h>
#include <time.h>
#include <fcntl.h>
#include <time.h>
#include <fcntl.h>
@@ -103,6 +104,7 @@ static size_t utf8_length(const char **string, size_t col, int *width, size_t ma
/* The format and size of the date column in the main view. */
#define DATE_FORMAT "%Y-%m-%d %H:%M"
#define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
/* The format and size of the date column in the main view. */
#define DATE_FORMAT "%Y-%m-%d %H:%M"
#define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
+#define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
#define ID_COLS 8
#define ID_COLS 8
@@ -112,10 +114,6 @@ static size_t utf8_length(const char **string, size_t col, int *width, size_t ma
#define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
#define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
-#ifndef GIT_CONFIG
-#define GIT_CONFIG "config"
-#endif
-
/* Some ASCII-shorthands fitted into the ncurses namespace. */
#define KEY_TAB '\t'
#define KEY_RETURN '\r'
/* Some ASCII-shorthands fitted into the ncurses namespace. */
#define KEY_TAB '\t'
#define KEY_RETURN '\r'
}
}
-static const char *
-mkdate(const time_t *time)
+/*
+ * What value of "tz" was in effect back then at "time" in the
+ * local timezone?
+ */
+static int local_tzoffset(time_t time)
+{
+ time_t t, t_local;
+ struct tm tm;
+ int offset, eastwest;
+
+ t = time;
+ localtime_r(&t, &tm);
+ t_local = mktime(&tm);
+
+ if (t_local < t) {
+ eastwest = -1;
+ offset = t - t_local;
+ } else {
+ eastwest = 1;
+ offset = t_local - t;
+ }
+ offset /= 60; /* in minutes */
+ offset = (offset % 60) + ((offset / 60) * 100);
+ return offset * eastwest;
+}
+
+enum date {
+ DATE_NONE = 0,
+ DATE_DEFAULT,
+ DATE_RELATIVE,
+ DATE_SHORT
+};
+
+static char *
+string_date(const time_t *time, enum date date)
{
static char buf[DATE_COLS + 1];
{
static char buf[DATE_COLS + 1];
+ static const struct enum_map reldate[] = {
+ { "second", 1, 60 * 2 },
+ { "minute", 60, 60 * 60 * 2 },
+ { "hour", 60 * 60, 60 * 60 * 24 * 2 },
+ { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
+ { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
+ { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
+ };
struct tm tm;
struct tm tm;
+ if (date == DATE_RELATIVE) {
+ struct timeval now;
+ time_t date = *time + local_tzoffset(*time);
+ time_t seconds;
+ int i;
+
+ gettimeofday(&now, NULL);
+ seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
+ for (i = 0; i < ARRAY_SIZE(reldate); i++) {
+ if (seconds >= reldate[i].value)
+ continue;
+
+ seconds /= reldate[i].namelen;
+ if (!string_format(buf, "%ld %s%s %s",
+ seconds, reldate[i].name,
+ seconds > 1 ? "s" : "",
+ now.tv_sec >= date ? "ago" : "ahead"))
+ break;
+ return buf;
+ }
+ }
+
gmtime_r(time, &tm);
return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
}
gmtime_r(time, &tm);
return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
}
}
static bool
}
static bool
-io_open(struct io *io, const char *name)
+io_open(struct io *io, const char *fmt, ...)
{
{
+ char name[SIZEOF_STR] = "";
+ bool fits;
+ va_list args;
+
init_io(io, NULL, IO_FD);
init_io(io, NULL, IO_FD);
+
+ va_start(args, fmt);
+ fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
+ va_end(args);
+
+ if (!fits) {
+ io->error = ENAMETOOLONG;
+ return FALSE;
+ }
io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
if (io->pipe == -1)
io->error = errno;
io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
if (io->pipe == -1)
io->error = errno;
}
static bool
}
static bool
-run_io_rd(struct io *io, const char **argv, enum format_flags flags)
+run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
{
{
- return init_io_rd(io, argv, NULL, flags) && start_io(io);
+ return init_io_rd(io, argv, dir, flags) && start_io(io);
}
static bool
}
static bool
{
struct io io = {};
{
struct io io = {};
- return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
+ return run_io_rd(&io, argv, NULL, FORMAT_NONE)
+ && io_read_buf(&io, buf, bufsize);
}
static int
}
static int
REQ_(OPTIONS, "Open option menu"), \
REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
REQ_(TOGGLE_DATE, "Toggle date display"), \
REQ_(OPTIONS, "Open option menu"), \
REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
REQ_(TOGGLE_DATE, "Toggle date display"), \
+ REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
*/
/* Option and state variables. */
*/
/* Option and state variables. */
-static bool opt_date = TRUE;
+static enum date opt_date = DATE_DEFAULT;
static bool opt_author = TRUE;
static bool opt_line_number = FALSE;
static bool opt_line_graphics = TRUE;
static bool opt_author = TRUE;
static bool opt_line_number = FALSE;
static bool opt_line_graphics = TRUE;
#define is_initial_commit() (!*opt_head_rev)
#define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
#define is_initial_commit() (!*opt_head_rev)
#define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
+#define mkdate(time) string_date(time, opt_date)
/*
/*
if (!strcmp(argv[0], "show-author"))
return parse_bool(&opt_author, argv[2]);
if (!strcmp(argv[0], "show-author"))
return parse_bool(&opt_author, argv[2]);
- if (!strcmp(argv[0], "show-date"))
- return parse_bool(&opt_date, argv[2]);
+ if (!strcmp(argv[0], "show-date")) {
+ bool show_date;
+
+ if (!strcmp(argv[2], "relative")) {
+ opt_date = DATE_RELATIVE;
+ return OK;
+ } else if (!strcmp(argv[2], "short")) {
+ opt_date = DATE_SHORT;
+ return OK;
+ } else if (parse_bool(&show_date, argv[2])) {
+ opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
+ }
+ return ERR;
+ }
if (!strcmp(argv[0], "show-rev-graph"))
return parse_bool(&opt_rev_graph, argv[2]);
if (!strcmp(argv[0], "show-rev-graph"))
return parse_bool(&opt_rev_graph, argv[2]);
struct io io = {};
/* It's OK that the file doesn't exist. */
struct io io = {};
/* It's OK that the file doesn't exist. */
- if (!io_open(&io, path))
+ if (!io_open(&io, "%s", path))
return;
config_lineno = 0;
return;
config_lineno = 0;
bool (*grep)(struct view *view, struct line *line);
/* Select line */
void (*select)(struct view *view, struct line *line);
bool (*grep)(struct view *view, struct line *line);
/* Select line */
void (*select)(struct view *view, struct line *line);
+ /* Prepare view for loading */
+ bool (*prepare)(struct view *view);
};
static struct view_ops blame_ops;
};
static struct view_ops blame_ops;
@@ -2070,9 +2161,10 @@ draw_field(struct view *view, enum line_type type, const char *text, int len, bo
static bool
draw_date(struct view *view, time_t *time)
{
static bool
draw_date(struct view *view, time_t *time)
{
- const char *date = mkdate(time);
+ const char *date = time ? mkdate(time) : "";
+ int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
- return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
+ return draw_field(view, LINE_DATE, date, cols, FALSE);
}
static bool
}
static bool
}
}
}
}
+static void
+toggle_date_option(enum date *date)
+{
+ static const char *help[] = {
+ "no",
+ "default",
+ "relative",
+ "short"
+ };
+
+ opt_date = (opt_date + 1) % ARRAY_SIZE(help);
+ redraw_display(FALSE);
+ report("Displaying %s dates", help[opt_date]);
+}
+
static void
toggle_view_option(bool *option, const char *help)
{
static void
toggle_view_option(bool *option, const char *help)
{
};
int selected = 0;
};
int selected = 0;
- if (prompt_menu("Toggle option", menu, &selected))
- toggle_view_option(menu[selected].data, menu[selected].text);
+ if (prompt_menu("Toggle option", menu, &selected)) {
+ if (menu[selected].data == &opt_date)
+ toggle_date_option(menu[selected].data);
+ else
+ toggle_view_option(menu[selected].data, menu[selected].text);
+ }
}
static void
}
static void
{
if (view->pipe)
end_update(view, TRUE);
{
if (view->pipe)
end_update(view, TRUE);
- return io_open(&view->io, name);
+ return io_open(&view->io, "%s", name);
}
static bool
}
static bool
if (view->pipe)
end_update(view, TRUE);
if (view->pipe)
end_update(view, TRUE);
- if (refresh) {
- if (!start_io(&view->io))
- return FALSE;
-
- } else {
- if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
- opt_path[0] = 0;
-
- if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
+ if (!refresh) {
+ if (view->ops->prepare) {
+ if (!view->ops->prepare(view))
+ return FALSE;
+ } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
return FALSE;
return FALSE;
+ }
/* Put the current ref_* value to the view title ref
* member. This is needed by the blob view. Most other
/* Put the current ref_* value to the view title ref
* member. This is needed by the blob view. Most other
string_copy_rev(view->ref, view->id);
}
string_copy_rev(view->ref, view->id);
}
+ if (!start_io(&view->io))
+ return FALSE;
+
setup_update(view, view->id);
return TRUE;
setup_update(view, view->id);
return TRUE;
display[current_view] = view;
}
display[current_view] = view;
}
+ /* No parent signals that this is the first loaded view. */
+ if (prev && view != prev) {
+ view->parent = prev;
+ }
+
/* Resize the view when switching between split- and full-screen,
* or when switching between two different full-screen views. */
if (nviews != displayed_views() ||
/* Resize the view when switching between split- and full-screen,
* or when switching between two different full-screen views. */
if (nviews != displayed_views() ||
/* "Blur" the previous view. */
update_view_title(prev);
}
/* "Blur" the previous view. */
update_view_title(prev);
}
-
- view->parent = prev;
}
if (view->pipe && view->lines == 0) {
}
if (view->pipe && view->lines == 0) {
break;
case REQ_TOGGLE_DATE:
break;
case REQ_TOGGLE_DATE:
- toggle_view_option(&opt_date, "date display");
+ toggle_date_option(&opt_date);
break;
case REQ_TOGGLE_AUTHOR:
break;
case REQ_TOGGLE_AUTHOR:
return TRUE;
}
return TRUE;
}
- if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
+ if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
report("Failed to load tree data");
return TRUE;
}
report("Failed to load tree data");
return TRUE;
}
if (!pos)
return TRUE;
text = pos + 1;
if (!pos)
return TRUE;
text = pos + 1;
- if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
- text += strlen(opt_prefix);
if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
text += strlen(opt_path);
pos = strchr(text, '/');
if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
text += strlen(opt_path);
pos = strchr(text, '/');
string_copy_rev(view->ref, entry->id);
}
string_copy_rev(view->ref, entry->id);
}
+static bool
+tree_prepare(struct view *view)
+{
+ if (view->lines == 0 && opt_prefix[0]) {
+ char *pos = opt_prefix;
+
+ while (pos && *pos) {
+ char *end = strchr(pos, '/');
+
+ if (end)
+ *end = 0;
+ push_tree_stack_entry(pos, 0);
+ pos = end;
+ if (end) {
+ *end = '/';
+ pos++;
+ }
+ }
+
+ } else if (strcmp(view->vid, view->id)) {
+ opt_path[0] = 0;
+ }
+
+ return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
+}
+
static const char *tree_argv[SIZEOF_ARG] = {
"git", "ls-tree", "%(commit)", "%(directory)", NULL
};
static const char *tree_argv[SIZEOF_ARG] = {
"git", "ls-tree", "%(commit)", "%(directory)", NULL
};
tree_request,
tree_grep,
tree_select,
tree_request,
tree_grep,
tree_select,
+ tree_prepare,
};
static bool
};
static bool
static bool
blame_open(struct view *view)
{
static bool
blame_open(struct view *view)
{
- if (*opt_ref || !io_open(&view->io, opt_file)) {
- if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
+ char path[SIZEOF_STR];
+
+ if (!view->parent && *opt_prefix) {
+ string_copy(path, opt_file);
+ if (!string_format(opt_file, "%s%s", opt_prefix, path))
+ return FALSE;
+ }
+
+ if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
+ if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
return FALSE;
}
return FALSE;
}
if (view->lines == 0 && !view->parent)
die("No blame exist for %s", view->vid);
if (view->lines == 0 && !view->parent)
die("No blame exist for %s", view->vid);
- if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
+ if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
report("Failed to load blame data");
return TRUE;
}
report("Failed to load blame data");
return TRUE;
}
"--simplify-by-decoration", "--all", NULL
};
"--simplify-by-decoration", "--all", NULL
};
- if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
+ if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
report("Failed to load branch data");
return TRUE;
}
report("Failed to load branch data");
return TRUE;
}
if (!*opt_head) {
struct io io = {};
if (!*opt_head) {
struct io io = {};
- if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
- io_open(&io, buf) &&
+ if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
io_read_buf(&io, buf, sizeof(buf))) {
head = buf;
if (!prefixcmp(head, "refs/heads/"))
io_read_buf(&io, buf, sizeof(buf))) {
head = buf;
if (!prefixcmp(head, "refs/heads/"))
} else {
report("Cannot revert changes to multiple files");
}
} else {
report("Cannot revert changes to multiple files");
}
- return FALSE;
- } else {
+ } else if (prompt_yesno("Are you sure you want to revert changes?")) {
char mode[10] = "100644";
const char *reset_argv[] = {
"git", "update-index", "--cacheinfo", mode,
char mode[10] = "100644";
const char *reset_argv[] = {
"git", "update-index", "--cacheinfo", mode,
"git", "checkout", "--", status->old.name, NULL
};
"git", "checkout", "--", status->old.name, NULL
};
- if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
- return FALSE;
- string_format(mode, "%o", status->old.mode);
- return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
- run_io_fg(checkout_argv, opt_cdup);
+ if (status->status == 'U') {
+ string_format(mode, "%5o", status->old.mode);
+
+ if (status->old.mode == 0 && status->new.mode == 0) {
+ reset_argv[2] = "--force-remove";
+ reset_argv[3] = status->old.name;
+ reset_argv[4] = NULL;
+ }
+
+ if (!run_io_fg(reset_argv, opt_cdup))
+ return FALSE;
+ if (status->old.mode == 0 && status->new.mode == 0)
+ return TRUE;
+ }
+
+ return run_io_fg(checkout_argv, opt_cdup);
}
}
+
+ return FALSE;
}
static enum request
}
static enum request
@@ -7320,7 +7478,7 @@ read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen
static int
load_git_config(void)
{
static int
load_git_config(void)
{
- const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
+ const char *config_list_argv[] = { "git", "config", "--list", NULL };
return run_io_load(config_list_argv, "=", read_repo_config_option);
}
return run_io_load(config_list_argv, "=", read_repo_config_option);
}
die("command too long");
}
die("command too long");
}
- if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
- die("Failed to format arguments");
+ if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
+ die("Failed to format arguments");
return request;
}
return request;
}