*/
#ifndef VERSION
-#define VERSION "tig-0.3"
+#define VERSION "tig-0.4.git"
#endif
#ifndef DEBUG
#include <unistd.h>
#include <time.h>
+#include <locale.h>
+#include <langinfo.h>
+#include <iconv.h>
+
#include <curses.h>
#if __GNUC__ >= 3
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
#define STRING_SIZE(x) (sizeof(x) - 1)
+#define SIZEOF_STR 1024 /* Default string size. */
#define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
-#define SIZEOF_CMD 1024 /* Size of command buffer. */
#define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
/* This color name can be used to refer to the default term colors. */
#define COLOR_DEFAULT (-1)
+#define ICONV_NONE ((iconv_t) -1)
+
/* 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 ")
"git ls-remote . 2>/dev/null"
#define TIG_DIFF_CMD \
- "git show --patch-with-stat --find-copies-harder -B -C %s"
+ "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
#define TIG_LOG_CMD \
- "git log --cc --stat -n100 %s"
+ "git log --cc --stat -n100 %s 2>/dev/null"
#define TIG_MAIN_CMD \
- "git log --topo-order --stat --pretty=raw %s"
+ "git log --topo-order --pretty=raw %s 2>/dev/null"
/* XXX: Needs to be defined to the empty string. */
#define TIG_HELP_CMD ""
*/
static size_t
-sq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src)
+sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
{
char c;
-#define BUFPUT(x) do { if (bufsize < SIZEOF_CMD) buf[bufsize++] = (x); } while (0)
+#define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
BUFPUT('\'');
while ((c = *src++)) {
static int opt_num_interval = NUMBER_INTERVAL;
static int opt_tab_size = TABSIZE;
static enum request opt_request = REQ_VIEW_MAIN;
-static char opt_cmd[SIZEOF_CMD] = "";
-static char opt_encoding[20] = "";
-static bool opt_utf8 = TRUE;
+static char opt_cmd[SIZEOF_STR] = "";
static FILE *opt_pipe = NULL;
+static char opt_encoding[20] = "UTF-8";
+static bool opt_utf8 = TRUE;
+static char opt_codeset[20] = "UTF-8";
+static iconv_t opt_iconv = ICONV_NONE;
enum option_type {
OPT_NONE,
{ 'v', REQ_SHOW_VERSION },
{ 'r', REQ_SCREEN_REDRAW },
{ 'n', REQ_TOGGLE_LINENO },
- { 'g', REQ_TOGGLE_REV_GRAPH},
+ { 'g', REQ_TOGGLE_REV_GRAPH },
{ ':', REQ_PROMPT },
/* wgetch() with nodelay() enabled returns ERR when there's no input. */
{ "Down", KEY_DOWN },
{ "Insert", KEY_IC },
{ "Delete", KEY_DC },
+ { "Hash", '#' },
{ "Home", KEY_HOME },
{ "End", KEY_END },
{ "PageUp", KEY_PPAGE },
}
if (!strcmp(argv[0], "commit-encoding")) {
- string_copy(opt_encoding, argv[2]);
- return OK;
+ char *arg = argv[2];
+ int delimiter = *arg;
+ int i;
+
+ switch (delimiter) {
+ case '"':
+ case '\'':
+ for (arg++, i = 0; arg[i]; i++)
+ if (arg[i] == delimiter) {
+ arg[i] = 0;
+ break;
+ }
+ default:
+ string_copy(opt_encoding, arg);
+ return OK;
+ }
}
config_msg = "Unknown variable name";
/* Check for comment markers, since read_properties() will
* only ensure opt and value are split at first " \t". */
- optlen = strcspn(opt, "#;");
+ optlen = strcspn(opt, "#");
if (optlen == 0)
return OK;
} else {
/* Look for comment endings in the value. */
- int len = strcspn(value, "#;");
+ int len = strcspn(value, "#");
if (len < valuelen) {
valuelen = len;
load_options(void)
{
char *home = getenv("HOME");
- char buf[1024];
+ char buf[SIZEOF_STR];
FILE *file;
config_lineno = 0;
enum keymap keymap; /* What keymap does this view have */
- char cmd[SIZEOF_CMD]; /* Command buffer */
+ char cmd[SIZEOF_STR]; /* Command buffer */
char ref[SIZEOF_REF]; /* Hovered commit reference */
char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
static bool
update_view(struct view *view)
{
- char buffer[BUFSIZ];
+ char in_buffer[BUFSIZ];
+ char out_buffer[BUFSIZ * 2];
char *line;
/* The number of lines to read. If too low it will cause too much
* redrawing (and possible flickering), if too high responsiveness
if (!realloc_lines(view, view->lines + lines))
goto alloc_error;
- while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
- int linelen = strlen(line);
+ while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
+ size_t linelen = strlen(line);
if (linelen)
line[linelen - 1] = 0;
+ if (opt_iconv != ICONV_NONE) {
+ char *inbuf = line;
+ size_t inlen = linelen;
+
+ char *outbuf = out_buffer;
+ size_t outlen = sizeof(out_buffer);
+
+ size_t ret;
+
+ ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
+ if (ret != (size_t) -1) {
+ line = out_buffer;
+ linelen = strlen(out_buffer);
+ }
+ }
+
if (!view->ops->read(view, line))
goto alloc_error;
return TRUE;
}
+static bool
+add_describe_ref(char *buf, int *bufpos, char *commit_id, const char *sep)
+{
+ char refbuf[SIZEOF_STR];
+ char *ref = NULL;
+ FILE *pipe;
+
+ if (!string_format(refbuf, "git describe %s", commit_id))
+ return TRUE;
+
+ pipe = popen(refbuf, "r");
+ if (!pipe)
+ return TRUE;
+
+ if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
+ ref = chomp_string(ref);
+ pclose(pipe);
+
+ if (!ref || !*ref)
+ return TRUE;
+
+ /* This is the only fatal call, since it can "corrupt" the buffer. */
+ if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
+ return FALSE;
+
+ return TRUE;
+}
+
static void
add_pager_refs(struct view *view, struct line *line)
{
- char buf[1024];
- char *data = line->data;
+ char buf[SIZEOF_STR];
+ char *commit_id = line->data + STRING_SIZE("commit ");
struct ref **refs;
int bufpos = 0, refpos = 0;
const char *sep = "Refs: ";
+ bool is_tag = FALSE;
assert(line->type == LINE_COMMIT);
- refs = get_refs(data + STRING_SIZE("commit "));
- if (!refs)
+ refs = get_refs(commit_id);
+ if (!refs) {
+ if (view == VIEW(REQ_VIEW_DIFF))
+ goto try_add_describe_ref;
return;
+ }
do {
struct ref *ref = refs[refpos];
if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
return;
sep = ", ";
+ if (ref->tag)
+ is_tag = TRUE;
} while (refs[refpos++]->next);
+ if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
+try_add_describe_ref:
+ if (!add_describe_ref(buf, &bufpos, commit_id, sep))
+ return;
+ }
+
if (!realloc_lines(view, view->line_size + 1))
return;
break;
if (end) {
+ char *email = end + 1;
+
for (; end > ident && isspace(end[-1]); end--) ;
+
+ if (end == ident && *email) {
+ ident = email;
+ end = strchr(ident, '>');
+ for (; end > ident && isspace(end[-1]); end--) ;
+ }
*end = 0;
}
+ /* End is NULL or ident meaning there's no author. */
+ if (end <= ident)
+ ident = "Unknown";
+
string_copy(commit->author, ident);
/* Parse epoch and timezone */
/* Leave stdin and stdout alone when acting as a pager. */
FILE *io = fopen("/dev/tty", "r+");
+ if (!io)
+ die("Failed to open /dev/tty");
cursed = !!newterm(NULL, io, io);
}
wbkgdset(status_win, get_line_attr(LINE_STATUS));
}
+static int
+read_prompt(const char *prompt)
+{
+ enum { READING, STOP, CANCEL } status = READING;
+ char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
+ int pos = 0;
+
+ while (status == READING) {
+ struct view *view;
+ int i, key;
+
+ foreach_view (view, i)
+ update_view(view);
+
+ report("%s%.*s", prompt, pos, buf);
+ /* Refresh, accept single keystroke of input */
+ key = wgetch(status_win);
+ switch (key) {
+ case KEY_RETURN:
+ case KEY_ENTER:
+ case '\n':
+ status = pos ? STOP : CANCEL;
+ break;
+
+ case KEY_BACKSPACE:
+ if (pos > 0)
+ pos--;
+ else
+ status = CANCEL;
+ break;
+
+ case KEY_ESC:
+ status = CANCEL;
+ break;
+
+ case ERR:
+ break;
+
+ default:
+ if (pos >= sizeof(buf)) {
+ report("Input string too long");
+ return ERR;
+ }
+
+ if (isprint(key))
+ buf[pos++] = (char) key;
+ }
+ }
+
+ if (status == CANCEL) {
+ /* Clear the status window */
+ report("");
+ return ERR;
+ }
+
+ buf[pos++] = 0;
+ if (!string_format(opt_cmd, "git %s", buf))
+ return ERR;
+ if (strncmp(buf, "show", 4) && isspace(buf[4]))
+ opt_request = REQ_VIEW_DIFF;
+ else
+ opt_request = REQ_VIEW_PAGER;
+
+ return OK;
+}
/*
* Repository references
signal(SIGINT, quit);
+ if (setlocale(LC_ALL, "")) {
+ string_copy(opt_codeset, nl_langinfo(CODESET));
+ }
+
if (load_options() == ERR)
die("Failed to load user config.");
if (!parse_options(argc, argv))
return 0;
+ if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
+ opt_iconv = iconv_open(opt_codeset, opt_encoding);
+ if (opt_iconv == (iconv_t) -1)
+ die("Failed to initialize character set conversion");
+ }
+
if (load_refs() == ERR)
die("Failed to load refs.");
* status_win restricted. */
switch (request) {
case REQ_PROMPT:
- report(":");
- /* Temporarily switch to line-oriented and echoed
- * input. */
- nocbreak();
- echo();
-
- if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
- memcpy(opt_cmd, "git ", 4);
- opt_request = REQ_VIEW_PAGER;
- } else {
- report("Prompt interrupted by loading view, "
- "press 'z' to stop loading views");
+ if (read_prompt(":") == ERR)
request = REQ_SCREEN_UPDATE;
- }
-
- noecho();
- cbreak();
break;
case REQ_SCREEN_RESIZE: