X-Git-Url: https://git.distorted.org.uk/~mdw/tig/blobdiff_plain/9fbbd28f0bf6c5e0d401d3ba21cf65d3f43b0019..2fcf54018eb1bd8b9e177e322f5d833fa53107ff:/tig.c diff --git a/tig.c b/tig.c index b0f5382..b08d3f7 100644 --- a/tig.c +++ b/tig.c @@ -1,5 +1,15 @@ /* Copyright (c) 2006 Jonas Fonseca - * See license info at the bottom. */ + * + * 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ /** * TIG(1) * ====== @@ -54,6 +64,7 @@ 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); @@ -227,6 +238,8 @@ 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 FILE *opt_pipe = NULL; /* Returns the index of log or diff command or -1 to exit. */ @@ -425,10 +438,89 @@ parse_options(int argc, char *argv[]) } + if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8")) + opt_utf8 = FALSE; + return TRUE; } +/** + * ENVIRONMENT VARIABLES + * --------------------- + * Several options related to the interface with git can be configured + * via environment options. + * + * Repository references + * ~~~~~~~~~~~~~~~~~~~~~ + * Commits that are referenced by tags and branch heads will be marked + * by the reference name surrounded by '[' and ']': + * + * 2006-03-26 19:42 Petr Baudis | [cogito-0.17.1] Cogito 0.17.1 + * + * If you want to filter out certain directories under `.git/refs/`, say + * `tmp` you can do it by setting the following variable: + * + * $ TIG_LS_REMOTE="git ls-remote . | sed /\/tmp\//d" tig + * + * Or set the variable permanently in your environment. + * + * TIG_LS_REMOTE:: + * Set command for retrieving all repository references. The command + * should output data in the same format as git-ls-remote(1). + **/ + +#define TIG_LS_REMOTE \ + "git ls-remote . 2>/dev/null" + +/** + * [[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: + * + * $ TIG_MAIN_CMD="git log --date-order -n500 --pretty=raw %s" tig + * + * Or set the variable permanently in your environment. + * + * Notice, how `%s` is used to specify the commit reference. There can + * be a maximum of 5 `%s` ref specifications. + * + * TIG_DIFF_CMD:: + * The command used for the diff view. By default, git show is used + * as a backend. + * + * TIG_LOG_CMD:: + * The command used for the log view. If you prefer to have both + * author and committer shown in the log view be sure to pass + * `--pretty=fuller` to git log. + * + * TIG_MAIN_CMD:: + * The command used for the main view. Note, you must always specify + * the option: `--pretty=raw` since the main view parser expects to + * read that format. + **/ + +#define TIG_DIFF_CMD \ + "git show --patch-with-stat --find-copies-harder -B -C %s" + +#define TIG_LOG_CMD \ + "git log --cc --stat -n100 %s" + +#define TIG_MAIN_CMD \ + "git log --topo-order --stat --pretty=raw %s" + +/* ... silently ignore that the following are also exported. */ + +#define TIG_HELP_CMD \ + "man tig 2>/dev/null" + +#define TIG_PAGER_CMD \ + "" + + /* * Line-oriented content detection. */ @@ -549,82 +641,6 @@ struct line { /** - * ENVIRONMENT VARIABLES - * --------------------- - * Several options related to the interface with git can be configured - * via environment options. - * - * Repository references - * ~~~~~~~~~~~~~~~~~~~~~ - * Commits that are referenced by tags and branch heads will be marked - * by the reference name surrounded by '[' and ']': - * - * 2006-03-26 19:42 Petr Baudis | [cogito-0.17.1] Cogito 0.17.1 - * - * If you want to filter out certain directories under `.git/refs/`, say - * `tmp` you can do it by setting the following variable: - * - * $ TIG_LS_REMOTE="git ls-remote . | sed /\/tmp\//d" tig - * - * Or set the variable permanently in your environment. - * - * TIG_LS_REMOTE:: - * Set command for retrieving all repository references. The command - * should output data in the same format as git-ls-remote(1). - **/ - -#define TIG_LS_REMOTE \ - "git ls-remote . 2>/dev/null" - -/** - * [[view-commands]] - * View 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: - * - * $ TIG_MAIN_CMD="git log --date-order -n500 --pretty=raw %s" tig - * - * Or set the variable permanently in your environment. - * - * Notice, how `%s` is used to specify the commit reference. There can - * be a maximum of 5 `%s` ref specifications. - * - * TIG_DIFF_CMD:: - * The command used for the diff view. By default, git show is used - * as a backend. - * - * TIG_LOG_CMD:: - * The command used for the log view. If you prefer to have both - * author and committer shown in the log view be sure to pass - * `--pretty=fuller` to git log. - * - * TIG_MAIN_CMD:: - * The command used for the main view. Note, you must always specify - * the option: `--pretty=raw` since the main view parser expects to - * read that format. - **/ - -#define TIG_DIFF_CMD \ - "git show --patch-with-stat --find-copies-harder -B -C %s" - -#define TIG_LOG_CMD \ - "git log --cc --stat -n100 %s" - -#define TIG_MAIN_CMD \ - "git log --topo-order --stat --pretty=raw %s" - -/* ... silently ignore that the following are also exported. */ - -#define TIG_HELP_CMD \ - "man tig 2>/dev/null" - -#define TIG_PAGER_CMD \ - "" - - -/** * The viewer * ---------- * The display consists of a status window on the last line of the screen and @@ -842,7 +858,7 @@ update_view_title(struct view *view) wprintw(view->title, " %lds", secs); } - + wmove(view->title, 0, view->width - 1); wrefresh(view->title); } @@ -891,7 +907,6 @@ resize_display(void) 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; @@ -910,6 +925,20 @@ redraw_display(void) } } +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 @@ -1329,17 +1358,15 @@ open_view(struct view *prev, enum request request, enum open_flags flags) } 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); @@ -1468,11 +1495,15 @@ view_driver(struct view *view, enum request request) 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; @@ -1606,8 +1637,8 @@ pager_enter(struct view *view, struct line *line) 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); @@ -1643,7 +1674,7 @@ main_draw(struct view *view, struct line *line, unsigned int lineno) int col = 0; size_t timelen; size_t authorlen; - int trimmed; + int trimmed = 1; if (!*commit->author) return FALSE; @@ -1671,8 +1702,15 @@ main_draw(struct view *view, struct line *line, unsigned int lineno) 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); @@ -2185,15 +2223,7 @@ report(const char *msg, ...) } 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. */ @@ -2305,73 +2335,120 @@ get_refs(char *id) } 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(cmd, '\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_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_config(void) +{ + return read_properties("git repo-config --list", '=', + read_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(const char *cmd, int separator, + int (*read_property)(char *, int, char *, int)) +{ + FILE *pipe = popen(cmd, "r"); + 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 = strchr(name, separator); + int namelen; + int valuelen; + + if (value) { + namelen = value - name; + *value++ = 0; + valuelen = strlen(value); + if (valuelen > 0) { + valuelen--; + value[valuelen] = 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 */ @@ -2416,6 +2493,11 @@ main(int argc, char *argv[]) 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; @@ -2500,7 +2582,8 @@ main(int argc, char *argv[]) * * 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 <> section. + * variables described in the <> + * section. * * Limit by path name * ~~~~~~~~~~~~~~~~~~ @@ -2606,7 +2689,7 @@ main(int argc, char *argv[]) * * COPYRIGHT * --------- - * Copyright (c) Jonas Fonseca , 2006 + * Copyright (c) 2006 Jonas Fonseca * * 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 @@ -2615,10 +2698,12 @@ main(int argc, char *argv[]) * * 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) **/