/* Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
static void die(const char *err, ...);
static void report(const char *msg, ...);
+static int read_properties(FILE *pipe, const char *separators, 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 struct ref **get_refs(char *id);
+struct int_map {
+ const char *name;
+ int namelen;
+ int value;
+};
+
+static int
+set_from_int_map(struct int_map *map, size_t map_size,
+ int *value, const char *name, int namelen)
+{
+
+ int i;
+
+ for (i = 0; i < map_size; i++)
+ if (namelen == map[i].namelen &&
+ !strncasecmp(name, map[i].name, namelen)) {
+ *value = map[i].value;
+ return OK;
+ }
+
+ return ERR;
+}
+
/*
* String helpers
#define string_copy(dst, src) \
string_ncopy(dst, src, sizeof(dst))
+static char *
+chomp_string(char *name)
+{
+ int namelen;
+
+ while (isspace(*name))
+ name++;
+
+ namelen = strlen(name) - 1;
+ while (namelen > 0 && isspace(name[namelen]))
+ name[namelen--] = 0;
+
+ return name;
+}
+
/* Shell quoting
*
" -l Start up in log view\n"
" -d Start up in diff view\n"
" -n[I], --line-number[=I] Show line numbers with given interval\n"
-" -t[N], --tab-size[=N] Set number of spaces for tab expansion\n"
+" -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
" -- Mark end of tig options\n"
" -v, --version Show version and exit\n"
" -h, --help Show help message and exit\n";
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. */
}
/**
- * -t[NSPACES], --tab-size[=NSPACES]::
+ * -b[NSPACES], --tab-size[=NSPACES]::
* Set the number of spaces tabs should be expanded to.
**/
- if (!strncmp(opt, "-t", 2) ||
+ if (!strncmp(opt, "-b", 2) ||
!strncmp(opt, "--tab-size", 10)) {
char *num = opt;
- if (opt[1] == 't') {
+ if (opt[1] == 'b') {
num = opt + 2;
} else if (opt[STRING_SIZE("--tab-size")] == '=') {
}
+ if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
+ opt_utf8 = FALSE;
+
return TRUE;
}
"git ls-remote . 2>/dev/null"
/**
- * [[view-commands]]
- * View commands
- * ~~~~~~~~~~~~~
+ * [[history-commands]]
+ * History commands
+ * ~~~~~~~~~~~~~~~~
* It is possible to alter which commands are used for the different views.
* If for example you prefer commits in the main view to be sorted by date
* and only show 500 commits, use:
""
-/*
- * Line-oriented content detection.
- */
+/**
+ * FILES
+ * -----
+ * '~/.tig'::
+ * User configuration file. See "<<config-options, Configuration options>>"
+ * section for examples.
+ *
+ * '.git/config'::
+ * Repository config file. Read on startup with the help of
+ * git-repo-config(1).
+ **/
+/**
+ * [[config-options]]
+ * User Configuration file
+ * -----------------------
+ * You can permanently set an option by putting it in the `~/.tig` file.
+ * The file consists of a series of 'commands'. Each
+ * line of the file may contain only one command.
+ *
+ * The hash mark ('#'), or semi-colon (';') is used as a 'comment' character.
+ * All text after the comment character to the end of the line is ignored.
+ * You can use comments to annotate your initialization file.
+ *
+ * Some sample options:
+ *
+ * ==========================================================================
+ * # Diff colors
+ * color diff-header yellow default
+ * color diff-index blue default
+ * color diff-chunk magenta default
+ * # UI colors
+ * color title-blur white blue
+ * color title-focus white blue bold
+ * ==========================================================================
+ *
+ * [[color-options]]
+ * Color options
+ * ~~~~~~~~~~~~~
+ * Color options control highlighting and the user interface styles.
+ * If your terminal supports color, these commands can be used to assign
+ * foreground/backgound combinations to certain areas. Optionally, an
+ * attribute can be given as the last parameter. The syntax is:
+ *
+ * [verse]
+ * ..........................................................................
+ * *color* 'area' 'fgcolor' 'bgcolor' '[attributes]'
+ * ..........................................................................
+ *
+ * Valid colors include: *white*, *black*, *green*, *magenta*, *blue*, *cyan*,
+ * *yellow*, *red*, *default*. Use *default* to refer to the default terminal
+ * colors.
+ **/
+
+static struct int_map color_map[] = {
+#define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
+ COLOR_MAP(DEFAULT),
+ COLOR_MAP(BLACK),
+ COLOR_MAP(BLUE),
+ COLOR_MAP(CYAN),
+ COLOR_MAP(GREEN),
+ COLOR_MAP(MAGENTA),
+ COLOR_MAP(RED),
+ COLOR_MAP(WHITE),
+ COLOR_MAP(YELLOW),
+};
+/**
+ * Valid attributes include: *normal*, *blink*, *bold*, *dim*, *reverse*, *standout*,
+ * and *underline*. Note, not all attributes may be supported by the terminal.
+ **/
+
+static struct int_map attr_map[] = {
+#define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
+ ATTR_MAP(NORMAL),
+ ATTR_MAP(BLINK),
+ ATTR_MAP(BOLD),
+ ATTR_MAP(DIM),
+ ATTR_MAP(REVERSE),
+ ATTR_MAP(STANDOUT),
+ ATTR_MAP(UNDERLINE),
+};
+
+/**
+ * Valid area names are described below. Note, all names are case-insensitive,
+ * and you may use '-', '_', and '.' interchangeably. So "Diff-Header",
+ * "DIFF_HEADER", and "diff.header" are the same.
+ *
+ * --
+ **/
#define LINE_INFO \
-/* Line type String to match Foreground Background Attributes
- * --------- --------------- ---------- ---------- ---------- */ \
-/* Diff markup */ \
-LINE(DIFF, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
-LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
+/**
+ * Diff markup::
+ *
+ * Options concerning diff start, chunks and lines added and deleted.
+ *
+ * *diff-header*, *diff-chunk*, *diff-add*, *diff-del*
+ **/ \
+LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
-LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
-LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
-LINE(DIFF_COPY, "copy ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
-LINE(DIFF_RENAME, "rename ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
-LINE(DIFF_SIM, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
-LINE(DIFF_DISSIM, "dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
-/* Pretty print commit header */ \
+/**
+ * Enhanced git diff markup::
+ *
+ * Extra diff information emitted by the git diff machinery, such as mode
+ * changes, rename detection, and similarity.
+ *
+ * *diff-oldmode*, *diff-newmode*, *diff-copy-from*, *diff-copy-to*,
+ * *diff-rename-from*, *diff-rename-to*, *diff-similarity* *diff-dissimilarity*
+ * *diff-tree*, *diff-index*
+ **/ \
+LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
+LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
+/**
+ * Pretty print commit headers::
+ *
+ * Commit diffs and the revision logs are usually formatted using pretty
+ * printed headers , unless `--pretty=raw` was given. This includes lines,
+ * such as merge info, commit ID, and author and comitter date.
+ *
+ * *pp-author*, *pp-commit*, *pp-merge*, *pp-date*, *pp-adate*, *pp-cdate*
+ **/ \
LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
-/* Raw commit header */ \
+/**
+ * Raw commit header::
+ *
+ * Usually shown when `--pretty=raw` is given, however 'commit' is pretty
+ * much omnipresent.
+ *
+ * *commit*, *parent*, *tree*, *author*, *committer*
+ **/ \
LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
-/* Misc */ \
-LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
+/**
+ * Commit message::
+ *
+ * For now only `Signed-off-by lines` are colorized.
+ *
+ * *signoff*
+ **/ \
LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
-/* UI colors */ \
+/**
+ * UI colors::
+ *
+ * Colors for text not matching any of the above: *default*
+ *
+ * Status window colors: *status*
+ *
+ * Title window colors: *title-blur*, *title-focus*
+ *
+ * Cursor line colors: *cursor*
+ *
+ * Main view specific: *main-date*, *main-author*, *main-commit*, *main-delim*,
+ * *main-tag*, *main-ref*
+ **/ \
LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
-LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD),
+LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
+/**
+ * --
+ **/
+
+
+/*
+ * Line-oriented content detection.
+ */
enum line_type {
#define LINE(type, line, fg, bg, attr) \
};
struct line_info {
+ const char *name; /* Option name. */
+ int namelen; /* Size of option name. */
const char *line; /* The start of line to match. */
int linelen; /* Size of string to match. */
int fg, bg, attr; /* Color and text attributes for the lines. */
static struct line_info line_info[] = {
#define LINE(type, line, fg, bg, attr) \
- { (line), STRING_SIZE(line), (fg), (bg), (attr) }
+ { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
LINE_INFO
#undef LINE
};
return COLOR_PAIR(type) | line_info[type].attr;
}
+static struct line_info *
+get_line_info(char *name, int namelen)
+{
+ enum line_type type;
+ int i;
+
+ /* Diff-Header -> DIFF_HEADER */
+ for (i = 0; i < namelen; i++) {
+ if (name[i] == '-')
+ name[i] = '_';
+ else if (name[i] == '.')
+ name[i] = '_';
+ }
+
+ for (type = 0; type < ARRAY_SIZE(line_info); type++)
+ if (namelen == line_info[type].namelen &&
+ !strncasecmp(line_info[type].name, name, namelen))
+ return &line_info[type];
+
+ return NULL;
+}
+
static void
init_colors(void)
{
};
+/*
+ * User config file handling.
+ */
+
+#define set_color(color, name, namelen) \
+ set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, namelen)
+
+#define set_attribute(attr, name, namelen) \
+ set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, namelen)
+
+static int config_lineno;
+static bool config_errors;
+static char *config_msg;
+
+static int
+set_option(char *opt, int optlen, char *value, int valuelen)
+{
+ /* Reads: "color" object fgcolor bgcolor [attr] */
+ if (!strcmp(opt, "color")) {
+ struct line_info *info;
+
+ value = chomp_string(value);
+ valuelen = strcspn(value, " \t");
+ info = get_line_info(value, valuelen);
+ if (!info) {
+ config_msg = "Unknown color name";
+ return ERR;
+ }
+
+ value = chomp_string(value + valuelen);
+ valuelen = strcspn(value, " \t");
+ if (set_color(&info->fg, value, valuelen) == ERR) {
+ config_msg = "Unknown color";
+ return ERR;
+ }
+
+ value = chomp_string(value + valuelen);
+ valuelen = strcspn(value, " \t");
+ if (set_color(&info->bg, value, valuelen) == ERR) {
+ config_msg = "Unknown color";
+ return ERR;
+ }
+
+ value = chomp_string(value + valuelen);
+ if (*value &&
+ set_attribute(&info->attr, value, strlen(value)) == ERR) {
+ config_msg = "Unknown attribute";
+ return ERR;
+ }
+
+ return OK;
+ }
+
+ return ERR;
+}
+
+static int
+read_option(char *opt, int optlen, char *value, int valuelen)
+{
+ config_lineno++;
+ config_msg = "Internal error";
+
+ optlen = strcspn(opt, "#;");
+ if (optlen == 0) {
+ /* The whole line is a commend or empty. */
+ return OK;
+
+ } else if (opt[optlen] != 0) {
+ /* Part of the option name is a comment, so the value part
+ * should be ignored. */
+ valuelen = 0;
+ opt[optlen] = value[valuelen] = 0;
+ } else {
+ /* Else look for comment endings in the value. */
+ valuelen = strcspn(value, "#;");
+ value[valuelen] = 0;
+ }
+
+ if (set_option(opt, optlen, value, valuelen) == ERR) {
+ fprintf(stderr, "Error on line %d, near '%.*s' option: %s\n",
+ config_lineno, optlen, opt, config_msg);
+ config_errors = TRUE;
+ }
+
+ /* Always keep going if errors are encountered. */
+ return OK;
+}
+
+static int
+load_options(void)
+{
+ char *home = getenv("HOME");
+ char buf[1024];
+ FILE *file;
+
+ config_lineno = 0;
+ config_errors = FALSE;
+
+ if (!home ||
+ snprintf(buf, sizeof(buf), "%s/.tig", home) >= sizeof(buf))
+ return ERR;
+
+ /* It's ok that the file doesn't exist. */
+ file = fopen(buf, "r");
+ if (!file)
+ return OK;
+
+ if (read_properties(file, " \t", read_option) == ERR ||
+ config_errors == TRUE)
+ fprintf(stderr, "Errors while loading %s.\n", buf);
+
+ return OK;
+}
+
+
/**
* The viewer
* ----------
* [main] c622eefaa485995320bc743431bae0d497b1d875 - commit 1 of 61 (1%)
*
* By default, the title of the current view is highlighted using bold font.
+ * For long loading views (taking over 3 seconds) the time since loading
+ * started will be appended:
+ *
+ * [main] 77d9e40fbcea3238015aea403e06f61542df9a31 - commit 1 of 779 (0%) 5s
**/
static void
wprintw(view->title, " %lds", secs);
}
-
+ wmove(view->title, 0, view->width - 1);
wrefresh(view->title);
}
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);
break;
case REQ_STOP_LOADING:
- foreach_view (view, i) {
+ for (i = 0; i < ARRAY_SIZE(views); i++) {
+ view = &views[i];
if (view->pipe)
- report("Stopped loaded the %s view", view->name),
+ report("Stopped loading the %s view", view->name),
end_update(view);
}
break;
return TRUE;
case REQ_VIEW_CLOSE:
- if (view->parent) {
+ /* XXX: Mark closed views by letting view->parent point to the
+ * view itself. Parents to closed view should never be
+ * followed. */
+ if (view->parent &&
+ view->parent->parent != view->parent) {
memset(display, 0, sizeof(display));
current_view = 0;
display[current_view] = view->parent;
- view->parent = NULL;
+ view->parent = view;
resize_display();
redraw_display();
break;
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;
- FILE *pipe = popen(cmd, "r");
- char buffer[BUFSIZ];
- char *line;
- if (!pipe)
- return ERR;
+ return read_properties(popen(cmd, "r"), "\t", read_ref);
+}
- while ((line = fgets(buffer, sizeof(buffer), pipe))) {
- char *name = strchr(line, '\t');
- struct ref *ref;
- int namelen;
- bool tag = FALSE;
- bool tag_commit = FALSE;
+static int
+read_repo_config_option(char *name, int namelen, char *value, int valuelen)
+{
+ if (!strcmp(name, "i18n.commitencoding")) {
+ string_copy(opt_encoding, value);
+ }
- if (!name)
- continue;
+ return OK;
+}
- *name++ = 0;
- namelen = strlen(name) - 1;
+static int
+load_repo_config(void)
+{
+ return read_properties(popen("git repo-config --list", "r"),
+ "=", read_repo_config_option);
+}
- /* 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;
+static int
+read_properties(FILE *pipe, const char *separators,
+ int (*read_property)(char *, int, char *, int))
+{
+ char buffer[BUFSIZ];
+ char *name;
+ int state = OK;
- if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
- if (!tag_commit)
- continue;
- name += STRING_SIZE("refs/tags/");
- tag = TRUE;
+ if (!pipe)
+ return ERR;
- } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
- name += STRING_SIZE("refs/heads/");
+ while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
+ char *value;
+ size_t namelen;
+ size_t valuelen;
- } else if (!strcmp(name, "HEAD")) {
- continue;
- }
+ name = chomp_string(name);
+ namelen = strcspn(name, separators);
- refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
- if (!refs)
- return ERR;
+ if (name[namelen]) {
+ name[namelen] = 0;
+ value = chomp_string(name + namelen + 1);
+ valuelen = strlen(value);
- ref = &refs[refs_size++];
- ref->tag = tag;
- ref->name = strdup(name);
- if (!ref->name)
- return ERR;
+ } else {
+ value = "";
+ valuelen = 0;
+ }
- string_copy(ref->id, line);
+ 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);
+ if (load_options() == ERR)
+ die("Failed to load user config.");
+
+ /* Load the repo config file so options can be overwritten from
+ * the command line. */
+ if (load_repo_config() == ERR)
+ die("Failed to load repo config.");
+
if (!parse_options(argc, argv))
return 0;
memcpy(opt_cmd, "git ", 4);
opt_request = REQ_VIEW_PAGER;
} else {
- request = ERR;
+ report("Prompt interrupted by loading view, "
+ "press 'z' to stop loading views");
+ request = REQ_SCREEN_UPDATE;
}
noecho();
*
* You can tune the interaction with git by making use of the options
* explained in this section. For example, by configuring the environment
- * variables described in the <<view-commands, "View commands">> section.
+ * variables described in the <<history-commands, "History commands">>
+ * section.
*
* Limit by path name
* ~~~~~~~~~~~~~~~~~~
* - The cursor can wrap-around on the last line and cause the
* window to scroll.
*
+ * - The prompt doesn't work while loading.
+ *
* TODO
* ----
* Features that should be explored.
* - link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
*
* Other git repository browsers:
-*
+ *
* - gitk(1)
* - qgit(1)
* - gitview(1)