index 725ace02579782ab55d4b511cfd89c8c8fc2f19d..2a3ab3afc1f1f3b292cec1a8f5c2b7f76cdfae2b 100644 (file)
--- a/tig.c
+++ b/tig.c
static char *prompt_input(const char *prompt, input_handler handler, void *data);
static bool prompt_yesno(const char *prompt);
+/*
+ * Allocation helpers ... Entering macro hell to never be seen again.
+ */
+
+#define DEFINE_ALLOCATOR(name, type, chunk_size) \
+static type * \
+name(type **mem, size_t size, size_t increase) \
+{ \
+ size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
+ size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
+ type *tmp = *mem; \
+ \
+ if (mem == NULL || num_chunks != num_chunks_new) { \
+ tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
+ if (tmp) \
+ *mem = tmp; \
+ } \
+ \
+ return tmp; \
+}
+
/*
* String helpers
*/
}
+static const char *
+mkdate(const time_t *time)
+{
+ static char buf[DATE_COLS + 1];
+ struct tm tm;
+
+ gmtime_r(time, &tm);
+ return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
+}
+
+
static bool
argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
{
} while (1);
}
+DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
+
static char *
io_get(struct io *io, int c, bool can_read)
{
char *eol;
ssize_t readsize;
- if (!io->buf) {
- io->buf = io->bufpos = malloc(BUFSIZ);
- if (!io->buf)
- return NULL;
- io->bufalloc = BUFSIZ;
- io->bufsize = 0;
- }
-
while (TRUE) {
if (io->bufsize > 0) {
eol = memchr(io->bufpos, c, io->bufsize);
if (io->bufsize > 0 && io->bufpos > io->buf)
memmove(io->buf, io->bufpos, io->bufsize);
+ if (io->bufalloc == io->bufsize) {
+ if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
+ return NULL;
+ io->bufalloc += BUFSIZ;
+ }
+
io->bufpos = io->buf;
readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
if (io_error(io))
static bool
io_read_buf(struct io *io, char buf[], size_t bufsize)
{
- bool error;
+ char *result = io_get(io, '\n', TRUE);
- io->buf = io->bufpos = buf;
- io->bufalloc = bufsize;
- error = !io_get(io, '\n', TRUE) && io_error(io);
- io->buf = NULL;
+ if (result) {
+ result = chomp_string(result);
+ string_ncopy_do(buf, bufsize, result, strlen(result));
+ }
- return done_io(io) || error;
+ return done_io(io) && result;
}
static bool
static struct run_request *run_request;
static size_t run_requests;
+DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
+
static enum request
add_run_request(enum keymap keymap, int key, int argc, const char **argv)
{
if (argc >= ARRAY_SIZE(req->argv) - 1)
return REQ_NONE;
- req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
- if (!req)
+ if (!realloc_run_requests(&run_request, run_requests, 1))
return REQ_NONE;
- run_request = req;
req = &run_request[run_requests];
req->keymap = keymap;
req->key = key;
/* Buffering */
size_t lines; /* Total number of lines */
struct line *line; /* Line index */
- size_t line_alloc; /* Total number of allocated lines */
unsigned int digits; /* Number of digits in the lines member. */
/* Drawing */
@@ -1960,15 +1991,9 @@ draw_field(struct view *view, enum line_type type, const char *text, int len, bo
}
static bool
-draw_date(struct view *view, struct tm *time)
+draw_date(struct view *view, time_t *time)
{
- char buf[DATE_COLS];
- char *date;
- int timelen = 0;
-
- if (time)
- timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
- date = timelen ? buf : NULL;
+ const char *date = mkdate(time);
return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
}
if (lineno == 1 || (lineno % opt_num_interval) == 0) {
static char fmt[] = "%1ld";
- if (view->digits <= 9)
- fmt[1] = '0' + digits3;
-
+ fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
if (string_format(number, fmt, lineno))
text = number;
}
static void search_view(struct view *view, enum request request);
+static bool
+grep_text(struct view *view, const char *text[])
+{
+ regmatch_t pmatch;
+ size_t i;
+
+ for (i = 0; text[i]; i++)
+ if (*text[i] &&
+ regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
+ return TRUE;
+ return FALSE;
+}
+
static void
select_view_line(struct view *view, unsigned long lineno)
{
view->yoffset = 0;
view->lines = 0;
view->lineno = 0;
- view->line_alloc = 0;
view->vid[0] = 0;
view->update_secs = 0;
}
return TRUE;
}
-#define ITEM_CHUNK_SIZE 256
-static void *
-realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
-{
- size_t num_chunks = *size / ITEM_CHUNK_SIZE;
- size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
-
- if (mem == NULL || num_chunks != num_chunks_new) {
- *size = num_chunks_new * ITEM_CHUNK_SIZE;
- mem = realloc(mem, *size * item_size);
- }
-
- return mem;
-}
-
-static struct line *
-realloc_lines(struct view *view, size_t line_size)
-{
- size_t alloc = view->line_alloc;
- struct line *tmp = realloc_items(view->line, &alloc, line_size,
- sizeof(*view->line));
-
- if (!tmp)
- return NULL;
-
- view->line = tmp;
- view->line_alloc = alloc;
- return view->line;
-}
-
static bool
update_view(struct view *view)
{
return TRUE;
}
+DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
+
static struct line *
add_line_data(struct view *view, void *data, enum line_type type)
{
struct line *line;
- if (!realloc_lines(view, view->lines + 1))
+ if (!realloc_lines(&view->line, view->lines, 1))
return NULL;
line = &view->line[view->lines++];
{
int i;
- if (request == REQ_NONE) {
- doupdate();
+ if (request == REQ_NONE)
return TRUE;
- }
if (request > REQ_NONE) {
open_run_request(request);
* View backend utilities
*/
+DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
+
+/* Small author cache to reduce memory consumption. It uses binary
+ * search to lookup or find place to position new entries. No entries
+ * are ever freed. */
+static const char *
+get_author(const char *name)
+{
+ static const char **authors;
+ static size_t authors_size;
+ int from = 0, to = authors_size - 1;
+
+ while (from <= to) {
+ size_t pos = (to + from) / 2;
+ int cmp = strcmp(name, authors[pos]);
+
+ if (!cmp)
+ return authors[pos];
+
+ if (cmp < 0)
+ to = pos - 1;
+ else
+ from = pos + 1;
+ }
+
+ if (!realloc_authors(&authors, authors_size, 1))
+ return NULL;
+ name = strdup(name);
+ if (!name)
+ return NULL;
+
+ memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
+ authors[from] = name;
+ authors_size++;
+
+ return name;
+}
+
static void
parse_timezone(time_t *time, const char *zone)
{
* author <email@address.tld> 1138474660 +0100
*/
static void
-parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
+parse_author_line(char *ident, const char **author, time_t *time)
{
char *nameend = strchr(ident, '<');
char *emailend = strchr(ident, '>');
@@ -3367,18 +3410,17 @@ parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
ident = "Unknown";
}
- string_ncopy_do(author, authorsize, ident, strlen(ident));
+ *author = get_author(ident);
/* Parse epoch and timezone */
if (emailend && emailend[1] == ' ') {
char *secs = emailend + 2;
char *zone = strchr(secs, ' ');
- time_t time = (time_t) atol(secs);
- if (zone && strlen(zone) == STRING_SIZE(" +0700"))
- parse_timezone(&time, zone + 1);
+ *time = (time_t) atol(secs);
- gmtime_r(&time, tm);
+ if (zone && strlen(zone) == STRING_SIZE(" +0700"))
+ parse_timezone(time, zone + 1);
}
}
int parents;
if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
- !*chomp_string(buf) ||
(parents = (strlen(buf) / 40) - 1) < 0) {
report("Failed to get parent information");
return FALSE;
add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
{
const char *describe_argv[] = { "git", "describe", commit_id, NULL };
- char refbuf[SIZEOF_STR];
- char *ref = NULL;
+ char ref[SIZEOF_STR];
- if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
- ref = chomp_string(refbuf);
-
- if (!ref || !*ref)
+ if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
return TRUE;
/* This is the only fatal call, since it can "corrupt" the buffer. */
static bool
pager_grep(struct view *view, struct line *line)
{
- regmatch_t pmatch;
- char *text = line->data;
-
- if (!*text)
- return FALSE;
-
- if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
- return FALSE;
+ const char *text[] = { line->data, NULL };
- return TRUE;
+ return grep_text(view, text);
}
static void
struct tree_entry {
char id[SIZEOF_REV];
mode_t mode;
- struct tm time; /* Date from the author ident. */
- char author[75]; /* Author of the commit. */
+ time_t time; /* Date from the author ident. */
+ const char *author; /* Author of the commit. */
char name[1];
};
static bool
tree_read_date(struct view *view, char *text, bool *read_date)
{
- static char author_name[SIZEOF_STR];
- static struct tm author_time;
+ static const char *author_name;
+ static time_t author_time;
if (!text && *read_date) {
*read_date = FALSE;
} else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
parse_author_line(text + STRING_SIZE("author "),
- author_name, sizeof(author_name), &author_time);
+ &author_name, &author_time);
} else if (*text == ':') {
char *pos;
struct line *line = &view->line[i];
struct tree_entry *entry = line->data;
- annotated += !!*entry->author;
- if (*entry->author || strcmp(entry->name, text))
+ annotated += !!entry->author;
+ if (entry->author || strcmp(entry->name, text))
continue;
- string_copy(entry->author, author_name);
- memcpy(&entry->time, &author_time, sizeof(entry->time));
+ entry->author = author_name;
+ entry->time = author_time;
line->dirty = 1;
break;
}
if (opt_author && draw_author(view, entry->author))
return TRUE;
- if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
+ if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
return TRUE;
}
if (draw_text(view, line->type, entry->name, TRUE))
return REQ_NONE;
}
+static bool
+tree_grep(struct view *view, struct line *line)
+{
+ struct tree_entry *entry = line->data;
+ const char *text[] = {
+ entry->name,
+ opt_author ? entry->author : "",
+ opt_date ? mkdate(&entry->time) : "",
+ NULL
+ };
+
+ return grep_text(view, text);
+}
+
static void
tree_select(struct view *view, struct line *line)
{
tree_read,
tree_draw,
tree_request,
- pager_grep,
+ tree_grep,
tree_select,
};
struct blame_commit {
char id[SIZEOF_REV]; /* SHA1 ID. */
char title[128]; /* First line of the commit message. */
- char author[75]; /* Author of the commit. */
- struct tm time; /* Date from the author ident. */
+ const char *author; /* Author of the commit. */
+ time_t time; /* Date from the author ident. */
char filename[128]; /* Name of file. */
bool has_previous; /* Was a "previous" line detected. */
};
{
static struct blame_commit *commit = NULL;
static int blamed = 0;
- static time_t author_time;
static bool read_file = TRUE;
if (read_file)
view->lines ? blamed * 100 / view->lines : 0);
} else if (match_blame_header("author ", &line)) {
- string_ncopy(commit->author, line, strlen(line));
+ commit->author = get_author(line);
} else if (match_blame_header("author-time ", &line)) {
- author_time = (time_t) atol(line);
+ commit->time = (time_t) atol(line);
} else if (match_blame_header("author-tz ", &line)) {
- parse_timezone(&author_time, line);
- gmtime_r(&author_time, &commit->time);
+ parse_timezone(&commit->time, line);
} else if (match_blame_header("summary ", &line)) {
string_ncopy(commit->title, line, strlen(line));
blame_draw(struct view *view, struct line *line, unsigned int lineno)
{
struct blame *blame = line->data;
- struct tm *time = NULL;
+ time_t *time = NULL;
const char *id = NULL, *author = NULL;
char text[SIZEOF_STR];
{
struct blame *blame = line->data;
struct blame_commit *commit = blame->commit;
- regmatch_t pmatch;
-
-#define MATCH(text, on) \
- (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
-
- if (commit) {
- char buf[DATE_COLS + 1];
-
- if (MATCH(commit->title, 1) ||
- MATCH(commit->author, opt_author) ||
- MATCH(commit->id, opt_date))
- return TRUE;
-
- if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
- MATCH(buf, 1))
- return TRUE;
- }
-
- return MATCH(blame->text, 1);
+ const char *text[] = {
+ blame->text,
+ commit ? commit->title : "",
+ commit ? commit->id : "",
+ commit && opt_author ? commit->author : "",
+ commit && opt_date ? mkdate(&commit->time) : "",
+ NULL
+ };
-#undef MATCH
+ return grep_text(view, text);
}
static void
static size_t stage_chunks;
static int *stage_chunk;
+DEFINE_ALLOCATOR(realloc_ints, int, 32)
+
/* This should work even for the "On branch" line. */
static inline bool
status_has_none(struct view *view, struct line *line)
if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
io_open(&io, buf) &&
io_read_buf(&io, buf, sizeof(buf))) {
- head = chomp_string(buf);
+ head = buf;
if (!prefixcmp(head, "refs/heads/"))
head += STRING_SIZE("refs/heads/");
}
struct line *pos = view->line + view->lines;
int files = 0;
int file, done;
+ int cursor_y, cursor_x;
if (!status_update_prepare(&io, line->type))
return FALSE;
files++;
string_copy(buf, view->ref);
+ getsyx(cursor_y, cursor_x);
for (file = 0, done = 5; result && file < files; line++, file++) {
int almost_done = file * 100 / files;
string_format(view->ref, "updating file %u of %u (%d%% done)",
file, files, done);
update_view_title(view);
+ setsyx(cursor_y, cursor_x);
doupdate();
}
result = status_update_write(&io, line->data, line->type);
status_grep(struct view *view, struct line *line)
{
struct status *status = line->data;
- enum { S_STATUS, S_NAME, S_END } state;
- char buf[2] = "?";
- regmatch_t pmatch;
-
- if (!status)
- return FALSE;
-
- for (state = S_STATUS; state < S_END; state++) {
- const char *text;
-
- switch (state) {
- case S_NAME: text = status->new.name; break;
- case S_STATUS:
- buf[0] = status->status;
- text = buf;
- break;
- default:
- return FALSE;
- }
+ if (status) {
+ const char buf[2] = { status->status, 0 };
+ const char *text[] = { status->new.name, buf, NULL };
- if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
- return TRUE;
+ return grep_text(view, text);
}
return FALSE;
int i;
if (!stage_chunks) {
- static size_t alloc = 0;
- int *tmp;
-
for (line = view->line; line < view->line + view->lines; line++) {
if (line->type != LINE_DIFF_CHUNK)
continue;
- tmp = realloc_items(stage_chunk, &alloc,
- stage_chunks, sizeof(*tmp));
- if (!tmp) {
+ if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
report("Allocation failure");
return;
}
- stage_chunk = tmp;
stage_chunk[stage_chunks++] = line - view->line;
}
}
struct commit {
char id[SIZEOF_REV]; /* SHA1 ID. */
char title[128]; /* First line of the commit message. */
- char author[75]; /* Author of the commit. */
- struct tm time; /* Date from the author ident. */
+ const char *author; /* Author of the commit. */
+ time_t time; /* Date from the author ident. */
struct ref **refs; /* Repository references. */
chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
size_t graph_size; /* The width of the graph array. */
{
struct commit *commit = line->data;
- if (!*commit->author)
+ if (!commit->author)
return FALSE;
if (opt_date && draw_date(view, &commit->time))
if (view->lines > 0) {
commit = view->line[view->lines - 1].data;
view->line[view->lines - 1].dirty = 1;
- if (!*commit->author) {
+ if (!commit->author) {
view->lines--;
free(commit);
graph->commit = NULL;
case LINE_AUTHOR:
parse_author_line(line + STRING_SIZE("author "),
- commit->author, sizeof(commit->author),
- &commit->time);
+ &commit->author, &commit->time);
update_rev_graph(view, graph);
graph = graph->next;
break;
regmatch_t pmatch;
size_t i = 0;
- if (!refs)
+ if (!opt_show_refs || !refs)
return FALSE;
do {
if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
main_grep(struct view *view, struct line *line)
{
struct commit *commit = line->data;
- enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
- char buf[DATE_COLS + 1];
- regmatch_t pmatch;
-
- for (state = S_TITLE; state < S_END; state++) {
- char *text;
-
- switch (state) {
- case S_TITLE: text = commit->title; break;
- case S_AUTHOR:
- if (!opt_author)
- continue;
- text = commit->author;
- break;
- case S_DATE:
- if (!opt_date)
- continue;
- if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
- continue;
- text = buf;
- break;
- case S_REFS:
- if (!opt_show_refs)
- continue;
- if (grep_refs(commit->refs, view->regex) == TRUE)
- return TRUE;
- continue;
- default:
- return FALSE;
- }
-
- if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
- return TRUE;
- }
+ const char *text[] = {
+ commit->title,
+ opt_author ? commit->author : "",
+ opt_date ? mkdate(&commit->time) : "",
+ NULL
+ };
- return FALSE;
+ return grep_text(view, text) || grep_refs(commit->refs, view->regex);
}
static void
*/
static struct ref *refs = NULL;
-static size_t refs_alloc = 0;
static size_t refs_size = 0;
/* Id <-> ref store */
static struct ref ***id_refs = NULL;
-static size_t id_refs_alloc = 0;
static size_t id_refs_size = 0;
+DEFINE_ALLOCATOR(realloc_refs, struct ref, 256)
+DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
+DEFINE_ALLOCATOR(realloc_refs_lists, struct ref **, 8)
+
static int
compare_refs(const void *ref1_, const void *ref2_)
{
static struct ref **
get_refs(const char *id)
{
- struct ref ***tmp_id_refs;
struct ref **ref_list = NULL;
- size_t ref_list_alloc = 0;
size_t ref_list_size = 0;
size_t i;
if (!strcmp(id, id_refs[i][0]->id))
return id_refs[i];
- tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
- sizeof(*id_refs));
- if (!tmp_id_refs)
+ if (!realloc_refs_lists(&id_refs, id_refs_size, 1))
return NULL;
- id_refs = tmp_id_refs;
-
for (i = 0; i < refs_size; i++) {
- struct ref **tmp;
-
if (strcmp(id, refs[i].id))
continue;
- tmp = realloc_items(ref_list, &ref_list_alloc,
- ref_list_size + 1, sizeof(*ref_list));
- if (!tmp) {
- if (ref_list)
- free(ref_list);
- return NULL;
- }
+ if (!realloc_refs_list(&ref_list, ref_list_size, 1))
+ return ref_list;
- ref_list = tmp;
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++;
}
return OK;
}
- refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
- if (!refs)
+
+ if (!realloc_refs(&refs, refs_size, 1))
return ERR;
ref = &refs[refs_size++];
static int
load_refs(void)
{
+ const char *head_argv[] = {
+ "git", "symbolic-ref", "HEAD", NULL
+ };
static const char *ls_remote_argv[SIZEOF_ARG] = {
"git", "ls-remote", opt_git_dir, NULL
};
if (!*opt_git_dir)
return OK;
+ if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
+ !prefixcmp(opt_head, "refs/heads/")) {
+ char *offset = opt_head + STRING_SIZE("refs/heads/");
+
+ memmove(opt_head, offset, strlen(offset) + 1);
+ }
+
while (refs_size > 0)
free(refs[--refs_size].name);
while (id_refs_size > 0)
static int
load_repo_info(void)
{
- const char *head_argv[] = {
- "git", "symbolic-ref", "HEAD", NULL
- };
const char *rev_parse_argv[] = {
"git", "rev-parse", "--git-dir", "--is-inside-work-tree",
"--show-cdup", "--show-prefix", NULL
};
- if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
- chomp_string(opt_head);
- if (!prefixcmp(opt_head, "refs/heads/")) {
- char *offset = opt_head + STRING_SIZE("refs/heads/");
-
- memmove(opt_head, offset, strlen(offset) + 1);
- }
- }
-
return run_io_load(rev_parse_argv, "=", read_repo_info);
}