static void die(const char *err, ...);
static void report(const char *msg, ...);
+static int read_properties(const char *cmd, int separator, int (*read)(char *, int, char *, int));
static void set_nonblocking_input(bool loading);
static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
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 FILE *opt_pipe = NULL;
/* Returns the index of log or diff command or -1 to exit. */
}
+ if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
+ opt_utf8 = FALSE;
+
return TRUE;
}
wresize(view->win, view->height, view->width);
mvwin(view->win, offset, 0);
mvwin(view->title, offset + view->height, 0);
- wrefresh(view->win);
}
offset += view->height + 1;
}
}
+static void
+update_display_cursor(void)
+{
+ struct view *view = display[current_view];
+
+ /* Move the cursor to the right-most column of the cursor line.
+ *
+ * XXX: This could turn out to be a bit expensive, but it ensures that
+ * the cursor does not jump around. */
+ if (view->lines) {
+ wmove(view->win, view->lineno - view->offset, view->width - 1);
+ wrefresh(view->win);
+ }
+}
/*
* Navigation
}
if (prev && view != prev) {
- /* Continue loading split views in the background. */
- if (!split)
- end_update(prev);
- else if (!backgrounded)
+ if (split && !backgrounded) {
/* "Blur" the previous view. */
update_view_title(prev);
+ }
view->parent = prev;
}
- if (view->pipe) {
+ if (view->pipe && view->lines == 0) {
/* Clear the old view and let the incremental updating refill
* the screen. */
wclear(view->win);
scroll_view(view, REQ_SCROLL_LINE_DOWN);
/* FIXME: A minor workaround. Scrolling the view will call report("")
- * but if we are scolling a non-current view this won't properly update
- * the view title. */
+ * but if we are scrolling a non-current view this won't properly
+ * update the view title. */
if (split)
update_view_title(view);
int col = 0;
size_t timelen;
size_t authorlen;
- int trimmed;
+ int trimmed = 1;
if (!*commit->author)
return FALSE;
if (type != LINE_CURSOR)
wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
- /* FIXME: Make this optional, and add i18n.commitEncoding support. */
- authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
+ if (opt_utf8) {
+ authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
+ } else {
+ authorlen = strlen(commit->author);
+ if (authorlen > AUTHOR_COLS - 2) {
+ authorlen = AUTHOR_COLS - 2;
+ trimmed = 1;
+ }
+ }
if (trimmed) {
waddnstr(view->win, commit->author, authorlen);
}
update_view_title(view);
-
- /* Move the cursor to the right-most column of the cursor line.
- *
- * XXX: This could turn out to be a bit expensive, but it ensures that
- * the cursor does not jump around. */
- if (view->lines) {
- wmove(view->win, view->lineno - view->offset, view->width - 1);
- wrefresh(view->win);
- }
+ update_display_cursor();
}
/* Controls when nodelay should be in effect when polling user input. */
}
static int
+read_ref(char *id, int idlen, char *name, int namelen)
+{
+ struct ref *ref;
+ bool tag = FALSE;
+ bool tag_commit = FALSE;
+
+ /* Commits referenced by tags has "^{}" appended. */
+ if (name[namelen - 1] == '}') {
+ while (namelen > 0 && name[namelen] != '^')
+ namelen--;
+ if (namelen > 0)
+ tag_commit = TRUE;
+ name[namelen] = 0;
+ }
+
+ if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
+ if (!tag_commit)
+ return OK;
+ name += STRING_SIZE("refs/tags/");
+ tag = TRUE;
+
+ } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
+ name += STRING_SIZE("refs/heads/");
+
+ } else if (!strcmp(name, "HEAD")) {
+ return OK;
+ }
+
+ refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
+ if (!refs)
+ return ERR;
+
+ ref = &refs[refs_size++];
+ ref->name = strdup(name);
+ if (!ref->name)
+ return ERR;
+
+ ref->tag = tag;
+ string_copy(ref->id, id);
+
+ return OK;
+}
+
+static int
load_refs(void)
{
const char *cmd_env = getenv("TIG_LS_REMOTE");
const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
+
+ return read_properties(cmd, '\t', read_ref);
+}
+
+static int
+read_config_option(char *name, int namelen, char *value, int valuelen)
+{
+ if (!strcmp(name, "i18n.commitencoding")) {
+ string_copy(opt_encoding, value);
+ }
+
+ return OK;
+}
+
+static int
+load_config(void)
+{
+ return read_properties("git repo-config --list", '=',
+ read_config_option);
+}
+
+static int
+read_properties(const char *cmd, int separator,
+ int (*read_property)(char *, int, char *, int))
+{
FILE *pipe = popen(cmd, "r");
char buffer[BUFSIZ];
- char *line;
+ char *name;
+ int state = OK;
if (!pipe)
return ERR;
- while ((line = fgets(buffer, sizeof(buffer), pipe))) {
- char *name = strchr(line, '\t');
- struct ref *ref;
+ while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
+ char *value = strchr(name, separator);
int namelen;
- bool tag = FALSE;
- bool tag_commit = FALSE;
-
- if (!name)
- continue;
-
- *name++ = 0;
- namelen = strlen(name) - 1;
-
- /* Commits referenced by tags has "^{}" appended. */
- if (name[namelen - 1] == '}') {
- while (namelen > 0 && name[namelen] != '^')
- namelen--;
- if (namelen > 0)
- tag_commit = TRUE;
- }
- name[namelen] = 0;
-
- if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
- if (!tag_commit)
- continue;
- name += STRING_SIZE("refs/tags/");
- tag = TRUE;
+ int valuelen;
- } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
- name += STRING_SIZE("refs/heads/");
+ if (value) {
+ namelen = value - name;
+ *value++ = 0;
+ valuelen = strlen(value);
+ if (valuelen > 0)
+ value[valuelen - 1] = 0;
- } else if (!strcmp(name, "HEAD")) {
- continue;
+ } else {
+ namelen = strlen(name);
+ value = "";
+ valuelen = 0;
}
- refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
- if (!refs)
- return ERR;
-
- ref = &refs[refs_size++];
- ref->tag = tag;
- ref->name = strdup(name);
- if (!ref->name)
- return ERR;
-
- string_copy(ref->id, line);
+ if (namelen)
+ state = read_property(name, namelen, value, valuelen);
}
- if (ferror(pipe))
- return ERR;
+ if (state != ERR && ferror(pipe))
+ state = ERR;
pclose(pipe);
- return OK;
+ return state;
}
+
/*
* Main
*/
signal(SIGINT, quit);
+ /* Load the repo config file first so options can be overwritten from
+ * the command line. */
+ if (load_config() == ERR)
+ die("Failed to load repo config.");
+
if (!parse_options(argc, argv))
return 0;
*
* SEE ALSO
* --------
- * [verse]
- * link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
- * link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
- * gitk(1): git repository browser written using tcl/tk,
- * qgit(1): git repository browser written using c++/Qt,
- * gitview(1): git repository browser written using python/gtk.
+ * - link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
+ * - link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
+ *
+ * Other git repository browsers:
+*
+ * - gitk(1)
+ * - qgit(1)
+ * - gitview(1)
**/