index a4eae7d01e624b3a65d5e81da1934b6026899134..3d04317cd6da6beb23f804bfe9bd23ffe8de4939 100644 (file)
--- a/tig.c
+++ b/tig.c
static void warn(const char *msg, ...);
static void report(const char *msg, ...);
static void set_nonblocking_input(bool loading);
-static int load_refs(void);
static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
#define ABS(x) ((x) >= 0 ? (x) : -(x))
#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
#define STRING_SIZE(x) (sizeof(x) - 1)
@@ -104,15 +104,9 @@ static size_t utf8_length(const char **string, size_t col, int *width, size_t ma
#define DATE_FORMAT "%Y-%m-%d %H:%M"
#define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
-#define AUTHOR_COLS 20
#define ID_COLS 8
-/* The default interval between line numbers. */
-#define NUMBER_INTERVAL 5
-
-#define TAB_SIZE 8
-
-#define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
+#define MIN_VIEW_HEIGHT 4
#define NULL_ID "0000000000000000000000000000000000000000"
@@ -129,18 +123,24 @@ static size_t utf8_length(const char **string, size_t col, int *width, size_t ma
struct ref {
- char *name; /* Ref name; tag or head names are shortened. */
char id[SIZEOF_REV]; /* Commit SHA1 ID */
unsigned int head:1; /* Is it the current HEAD? */
unsigned int tag:1; /* Is it a tag? */
unsigned int ltag:1; /* If so, is the tag local? */
unsigned int remote:1; /* Is it a remote ref? */
unsigned int tracked:1; /* Is it the remote for the current HEAD? */
- unsigned int next:1; /* For ref lists: are there more refs? */
+ char name[1]; /* Ref name; tag or head names are shortened. */
};
-static struct ref **get_refs(const char *id);
+struct ref_list {
+ char id[SIZEOF_REV]; /* Commit SHA1 ID */
+ size_t size; /* Number of refs. */
+ struct ref **refs; /* References for this ID. */
+};
+
+static struct ref_list *get_ref_list(const char *id);
static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
+static int load_refs(void);
enum format_flags {
FORMAT_ALL, /* Perform replacement in all arguments. */
static char *prompt_input(const char *prompt, input_handler handler, void *data);
static bool prompt_yesno(const char *prompt);
+struct menu_item {
+ int hotkey;
+ const char *text;
+ void *data;
+};
+
+static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
+
/*
* Allocation helpers ... Entering macro hell to never be seen again.
*/
REQ_(FIND_PREV, "Find previous search match"), \
\
REQ_GROUP("Option manipulation") \
+ REQ_(OPTIONS, "Open option menu"), \
REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
REQ_(TOGGLE_DATE, "Toggle date display"), \
REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
static bool opt_line_graphics = TRUE;
static bool opt_rev_graph = FALSE;
static bool opt_show_refs = TRUE;
-static int opt_num_interval = NUMBER_INTERVAL;
+static int opt_num_interval = 5;
static double opt_hscroll = 0.50;
-static int opt_tab_size = TAB_SIZE;
-static int opt_author_cols = AUTHOR_COLS-1;
+static double opt_scale_split_view = 2.0 / 3.0;
+static int opt_tab_size = 8;
+static int opt_author_cols = 19;
static char opt_path[SIZEOF_STR] = "";
static char opt_file[SIZEOF_STR] = "";
static char opt_ref[SIZEOF_REF] = "";
{ 'z', REQ_STOP_LOADING },
{ 'v', REQ_SHOW_VERSION },
{ 'r', REQ_SCREEN_REDRAW },
+ { 'o', REQ_OPTIONS },
{ '.', REQ_TOGGLE_LINENO },
{ 'D', REQ_TOGGLE_DATE },
{ 'A', REQ_TOGGLE_AUTHOR },
add_builtin_run_requests(void)
{
const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
+ const char *commit[] = { "git", "commit", NULL };
const char *gc[] = { "git", "gc", NULL };
struct {
enum keymap keymap;
const char **argv;
} reqs[] = {
{ KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
+ { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
{ KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
};
int i;
if (!strcmp(argv[0], "horizontal-scroll"))
return parse_step(&opt_hscroll, argv[2]);
+ if (!strcmp(argv[0], "split-view-height"))
+ return parse_step(&opt_scale_split_view, argv[2]);
+
if (!strcmp(argv[0], "tab-size"))
return parse_int(&opt_tab_size, argv[2], 1, 1024);
wnoutrefresh(view->title);
}
+static int
+apply_step(double step, int value)
+{
+ if (step >= 1)
+ return (int) step;
+ value *= step + 0.01;
+ return value ? value : 1;
+}
+
static void
resize_display(void)
{
if (view != base) {
/* Horizontal split. */
view->width = base->width;
- view->height = SCALE_SPLIT_VIEW(base->height);
+ view->height = apply_step(opt_scale_split_view, base->height);
+ view->height = MAX(view->height, MIN_VIEW_HEIGHT);
+ view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
base->height -= view->height;
/* Make room for the title bar. */
report("%sabling %s", *option ? "En" : "Dis", help);
}
+static void
+open_option_menu(void)
+{
+ const struct menu_item menu[] = {
+ { '.', "line numbers", &opt_line_number },
+ { 'D', "date display", &opt_date },
+ { 'A', "author display", &opt_author },
+ { 'g', "revision graph display", &opt_rev_graph },
+ { 'F', "reference display", &opt_show_refs },
+ { 0 }
+ };
+ int selected = 0;
+
+ if (prompt_menu("Toggle option", menu, &selected))
+ toggle_view_option(menu[selected].data, menu[selected].text);
+}
+
static void
maximize_view(struct view *view)
{
@@ -2319,15 +2363,6 @@ goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
return FALSE;
}
-static int
-apply_step(double step, int value)
-{
- if (step >= 1)
- return (int) step;
- value *= step + 0.01;
- return value ? value : 1;
-}
-
/* Scrolling backend */
static void
do_scroll_view(struct view *view, int lines)
(view == VIEW(REQ_VIEW_STAGE) &&
view->parent == VIEW(REQ_VIEW_STATUS)) ||
(view == VIEW(REQ_VIEW_BLOB) &&
- view->parent == VIEW(REQ_VIEW_TREE))) {
+ view->parent == VIEW(REQ_VIEW_TREE)) ||
+ (view == VIEW(REQ_VIEW_MAIN) &&
+ view->parent == VIEW(REQ_VIEW_BRANCH))) {
int line;
view = view->parent;
maximize_view(view);
break;
+ case REQ_OPTIONS:
+ open_option_menu();
+ break;
+
case REQ_TOGGLE_LINENO:
toggle_view_option(&opt_line_number, "line numbers");
break;
}
}
-static enum input_status
-select_commit_parent_handler(void *data, char *buf, int c)
+static bool
+open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
{
- size_t parents = *(size_t *) data;
- int parent = 0;
+ char rev[SIZEOF_REV];
+ const char *revlist_argv[] = {
+ "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
+ };
+ struct menu_item *items;
+ char text[SIZEOF_STR];
+ bool ok = TRUE;
+ int i;
- if (!isdigit(c))
- return INPUT_SKIP;
+ items = calloc(*parents + 1, sizeof(*items));
+ if (!items)
+ return FALSE;
- if (*buf)
- parent = atoi(buf) * 10;
- parent += c - '0';
+ for (i = 0; i < *parents; i++) {
+ string_copy_rev(rev, &buf[SIZEOF_REV * i]);
+ if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
+ !(items[i].text = strdup(text))) {
+ ok = FALSE;
+ break;
+ }
+ }
- if (parent > parents)
- return INPUT_SKIP;
- return INPUT_OK;
+ if (ok) {
+ *parents = 0;
+ ok = prompt_menu("Select parent", items, parents);
+ }
+ for (i = 0; items[i].text; i++)
+ free((char *) items[i].text);
+ free(items);
+ return ok;
}
static bool
@@ -3500,12 +3558,13 @@ select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
{
char buf[SIZEOF_STR * 4];
const char *revlist_argv[] = {
- "git", "rev-list", "-1", "--parents", id, "--", path, NULL
+ "git", "log", "--no-color", "-1",
+ "--pretty=format:%P", id, "--", path, NULL
};
int parents;
if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
- (parents = (strlen(buf) / 40) - 1) < 0) {
+ (parents = strlen(buf) / 40) < 0) {
report("Failed to get parent information");
return FALSE;
return FALSE;
}
- if (parents > 1) {
- char prompt[SIZEOF_STR];
- char *result;
-
- if (!string_format(prompt, "Which parent? [1..%d] ", parents))
- return FALSE;
- result = prompt_input(prompt, select_commit_parent_handler, &parents);
- if (!result)
- return FALSE;
- parents = atoi(result);
- }
+ if (parents > 1 && !open_commit_parent_menu(buf, &parents))
+ return FALSE;
string_copy_rev(rev, &buf[41 * parents]);
return TRUE;
{
char buf[SIZEOF_STR];
char *commit_id = (char *)line->data + STRING_SIZE("commit ");
- struct ref **refs;
- size_t bufpos = 0, refpos = 0;
+ struct ref_list *list;
+ size_t bufpos = 0, i;
const char *sep = "Refs: ";
bool is_tag = FALSE;
assert(line->type == LINE_COMMIT);
- refs = get_refs(commit_id);
- if (!refs) {
+ list = get_ref_list(commit_id);
+ if (!list) {
if (view == VIEW(REQ_VIEW_DIFF))
goto try_add_describe_ref;
return;
}
- do {
- struct ref *ref = refs[refpos];
+ for (i = 0; i < list->size; i++) {
+ struct ref *ref = list->refs[i];
const char *fmt = ref->tag ? "%s[%s]" :
ref->remote ? "%s<%s>" : "%s%s";
sep = ", ";
if (ref->tag)
is_tag = TRUE;
- } while (refs[refpos++]->next);
+ }
if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
try_add_describe_ref:
struct ref *ref; /* Name and commit ID information. */
};
+static const enum sort_field branch_sort_fields[] = {
+ ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
+};
+static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
+
+static int
+branch_compare(const void *l1, const void *l2)
+{
+ const struct branch *branch1 = ((const struct line *) l1)->data;
+ const struct branch *branch2 = ((const struct line *) l2)->data;
+
+ switch (get_sort_field(branch_sort_state)) {
+ case ORDERBY_DATE:
+ return sort_order(branch_sort_state, branch1->time - branch2->time);
+
+ case ORDERBY_AUTHOR:
+ return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
+
+ case ORDERBY_NAME:
+ default:
+ return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
+ }
+}
+
static bool
branch_draw(struct view *view, struct line *line, unsigned int lineno)
{
open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
return REQ_NONE;
+ case REQ_TOGGLE_SORT_FIELD:
+ case REQ_TOGGLE_SORT_ORDER:
+ sort_view(view, request, &branch_sort_state, branch_compare);
+ return REQ_NONE;
+
case REQ_ENTER:
open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
return REQ_NONE;
setup_update(view, view->id);
foreach_ref(branch_open_visitor, view);
+ view->p_restore = TRUE;
return TRUE;
}
char title[128]; /* First line of the commit message. */
const char *author; /* Author of the commit. */
time_t time; /* Date from the author ident. */
- struct ref **refs; /* Repository references. */
+ struct ref_list *refs; /* Repository references. */
chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
size_t graph_size; /* The width of the graph array. */
bool has_parents; /* Rewritten --parents seen. */
return TRUE;
if (opt_show_refs && commit->refs) {
- size_t i = 0;
+ size_t i;
- do {
- struct ref *ref = commit->refs[i];
+ for (i = 0; i < commit->refs->size; i++) {
+ struct ref *ref = commit->refs->refs[i];
enum line_type type;
if (ref->head)
if (draw_text(view, LINE_DEFAULT, " ", TRUE))
return TRUE;
- } while (commit->refs[i++]->next);
+ }
}
draw_text(view, LINE_DEFAULT, commit->title, TRUE);
}
string_copy_rev(commit->id, line);
- commit->refs = get_refs(commit->id);
+ commit->refs = get_ref_list(commit->id);
graph->commit = commit;
add_line_data(view, commit, LINE_MAIN_COMMIT);
}
static bool
-grep_refs(struct ref **refs, regex_t *regex)
+grep_refs(struct ref_list *list, regex_t *regex)
{
regmatch_t pmatch;
- size_t i = 0;
+ size_t i;
- if (!opt_show_refs || !refs)
+ if (!opt_show_refs || !list)
return FALSE;
- do {
- if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
+
+ for (i = 0; i < list->size; i++) {
+ if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
return TRUE;
- } while (refs[i++]->next);
+ }
return FALSE;
}
return prompt_input(prompt, read_prompt_handler, NULL);
}
+static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
+{
+ enum input_status status = INPUT_OK;
+ int size = 0;
+
+ while (items[size].text)
+ size++;
+
+ while (status == INPUT_OK) {
+ const struct menu_item *item = &items[*selected];
+ int key;
+ int i;
+
+ mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
+ prompt, *selected + 1, size);
+ if (item->hotkey)
+ wprintw(status_win, "[%c] ", (char) item->hotkey);
+ wprintw(status_win, "%s", item->text);
+ wclrtoeol(status_win);
+
+ key = get_input(COLS - 1);
+ switch (key) {
+ case KEY_RETURN:
+ case KEY_ENTER:
+ case '\n':
+ status = INPUT_STOP;
+ break;
+
+ case KEY_LEFT:
+ case KEY_UP:
+ *selected = *selected - 1;
+ if (*selected < 0)
+ *selected = size - 1;
+ break;
+
+ case KEY_RIGHT:
+ case KEY_DOWN:
+ *selected = (*selected + 1) % size;
+ break;
+
+ case KEY_ESC:
+ status = INPUT_CANCEL;
+ break;
+
+ default:
+ for (i = 0; items[i].text; i++)
+ if (items[i].hotkey == key) {
+ *selected = i;
+ status = INPUT_STOP;
+ break;
+ }
+ }
+ }
+
+ /* Clear the status window */
+ status_empty = FALSE;
+ report("");
+
+ return status != INPUT_CANCEL;
+}
+
/*
* Repository properties
*/
-static struct ref *refs = NULL;
+static struct ref **refs = NULL;
static size_t refs_size = 0;
-/* Id <-> ref store */
-static struct ref ***id_refs = NULL;
-static size_t id_refs_size = 0;
+static struct ref_list **ref_lists = NULL;
+static size_t ref_lists_size = 0;
-DEFINE_ALLOCATOR(realloc_refs, struct ref, 256)
+DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
-DEFINE_ALLOCATOR(realloc_refs_lists, struct ref **, 8)
+DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
static int
compare_refs(const void *ref1_, const void *ref2_)
size_t i;
for (i = 0; i < refs_size; i++)
- if (!visitor(data, &refs[i]))
+ if (!visitor(data, refs[i]))
break;
}
-static struct ref **
-get_refs(const char *id)
+static struct ref_list *
+get_ref_list(const char *id)
{
- struct ref **ref_list = NULL;
- size_t ref_list_size = 0;
+ struct ref_list *list;
size_t i;
- for (i = 0; i < id_refs_size; i++)
- if (!strcmp(id, id_refs[i][0]->id))
- return id_refs[i];
+ for (i = 0; i < ref_lists_size; i++)
+ if (!strcmp(id, ref_lists[i]->id))
+ return ref_lists[i];
- if (!realloc_refs_lists(&id_refs, id_refs_size, 1))
+ if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
+ return NULL;
+ list = calloc(1, sizeof(*list));
+ if (!list)
return NULL;
for (i = 0; i < refs_size; i++) {
- if (strcmp(id, refs[i].id))
- continue;
-
- if (!realloc_refs_list(&ref_list, ref_list_size, 1))
- break;
-
- ref_list[ref_list_size] = &refs[i];
- /* XXX: The properties of the commit chains ensures that we can
- * safely modify the shared ref. The repo references will
- * always be similar for the same id. */
- ref_list[ref_list_size]->next = 1;
- ref_list_size++;
+ if (!strcmp(id, refs[i]->id) &&
+ realloc_refs_list(&list->refs, list->size, 1))
+ list->refs[list->size++] = refs[i];
}
- if (ref_list) {
- qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
- ref_list[ref_list_size - 1]->next = 0;
- id_refs[id_refs_size++] = ref_list;
+ if (!list->refs) {
+ free(list);
+ return NULL;
}
- return ref_list;
+ qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
+ ref_lists[ref_lists_size++] = list;
+ return list;
}
static int
read_ref(char *id, size_t idlen, char *name, size_t namelen)
{
- struct ref *ref;
+ struct ref *ref = NULL;
bool tag = FALSE;
bool ltag = FALSE;
bool remote = FALSE;
bool tracked = FALSE;
- bool check_replace = FALSE;
bool head = FALSE;
+ int from = 0, to = refs_size - 1;
if (!prefixcmp(name, "refs/tags/")) {
if (!suffixcmp(name, namelen, "^{}")) {
namelen -= 3;
name[namelen] = 0;
- if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
- check_replace = TRUE;
} else {
ltag = TRUE;
}
return OK;
}
- if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
- /* it's an annotated tag, replace the previous SHA1 with the
- * resolved commit id; relies on the fact git-ls-remote lists
- * the commit id of an annotated tag right before the commit id
- * it points to. */
- refs[refs_size - 1].ltag = ltag;
- string_copy_rev(refs[refs_size - 1].id, id);
+ /* If we are reloading or it's an annotated tag, replace the
+ * previous SHA1 with the resolved commit id; relies on the fact
+ * git-ls-remote lists the commit id of an annotated tag right
+ * before the commit id it points to. */
+ while (from <= to) {
+ size_t pos = (to + from) / 2;
+ int cmp = strcmp(name, refs[pos]->name);
- return OK;
- }
+ if (!cmp) {
+ ref = refs[pos];
+ break;
+ }
- if (!realloc_refs(&refs, refs_size, 1))
- return ERR;
+ if (cmp < 0)
+ to = pos - 1;
+ else
+ from = pos + 1;
+ }
- ref = &refs[refs_size++];
- ref->name = malloc(namelen + 1);
- if (!ref->name)
- return ERR;
+ if (!ref) {
+ if (!realloc_refs(&refs, refs_size, 1))
+ return ERR;
+ ref = calloc(1, sizeof(*ref) + namelen);
+ if (!ref)
+ return ERR;
+ memmove(refs + from + 1, refs + from,
+ (refs_size - from) * sizeof(*refs));
+ refs[from] = ref;
+ strncpy(ref->name, name, namelen);
+ refs_size++;
+ }
- strncpy(ref->name, name, namelen);
- ref->name[namelen] = 0;
ref->head = head;
ref->tag = tag;
ref->ltag = ltag;
"git", "ls-remote", opt_git_dir, NULL
};
static bool init = FALSE;
+ size_t i;
if (!init) {
argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
memmove(opt_head, offset, strlen(offset) + 1);
}
- while (refs_size > 0)
- free(refs[--refs_size].name);
- while (id_refs_size > 0)
- free(id_refs[--id_refs_size]);
+ for (i = 0; i < refs_size; i++)
+ refs[i]->id[0] = 0;
+
+ if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
+ return ERR;
- return run_io_load(ls_remote_argv, "\t", read_ref);
+ /* Update the ref lists to reflect changes. */
+ for (i = 0; i < ref_lists_size; i++) {
+ struct ref_list *list = ref_lists[i];
+ size_t old, new;
+
+ for (old = new = 0; old < list->size; old++)
+ if (!strcmp(list->id, list->refs[old]->id))
+ list->refs[new++] = list->refs[old];
+ list->size = new;
+ }
+
+ return OK;
}
static void